Qt: Add extra related packet indicator types.

Add the ability to set frame number types: none, request, or response.
Use the types to draw different related packet indicators in the packet
list.

Track the conversation in PacketListRecord. Use it to draw dashed lines
for unrelated frames.

Set frame number types for DNS and ICMP.

Instead of drawing a transparent QImage, alpha blend our foreground
color and draw directly in our painter. Blend more toward the foreground
color.

Add FRAMENUM_TYPE to checkAPIs.

Change-Id: I2495945bb436413e05d6ec697184a0b4fd5ad214
Reviewed-on: https://code.wireshark.org/review/7436
Reviewed-by: Gerald Combs <gerald@wireshark.org>
This commit is contained in:
Gerald Combs 2015-02-23 17:56:14 -08:00
parent a065fefe68
commit f2b35a180f
15 changed files with 183 additions and 68 deletions

View File

@ -5177,12 +5177,12 @@ proto_register_dns(void)
{ &hf_dns_response_in,
{ "Response In", "dns.response_in",
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0,
"The response to this DNS query is in this frame", HFILL }},
{ &hf_dns_response_to,
{ "Request In", "dns.response_to",
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0,
"This is a response to the DNS query in this frame", HFILL }},
{ &hf_dns_time,

View File

@ -1872,7 +1872,7 @@ void proto_register_icmp(void)
{&hf_icmp_resp_in,
{"Response frame", "icmp.resp_in", FT_FRAMENUM, BASE_NONE,
NULL, 0x0,
FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0,
"The frame number of the corresponding response",
HFILL}},
@ -1884,7 +1884,7 @@ void proto_register_icmp(void)
{&hf_icmp_resp_to,
{"Request frame", "icmp.resp_to", FT_FRAMENUM, BASE_NONE,
NULL, 0x0,
FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0,
"The frame number of the corresponding request", HFILL}},
{&hf_icmp_resptime,

View File

@ -97,6 +97,14 @@ enum ftenum {
typedef enum ftenum ftenum_t;
enum ft_framenum_type {
FT_FRAMENUM_NONE,
FT_FRAMENUM_REQUEST,
FT_FRAMENUM_RESPONSE
};
typedef enum ft_framenum_type ft_framenum_type_t;
struct _ftype_t;
typedef struct _ftype_t ftype_t;

View File

@ -5636,8 +5636,9 @@ tmp_fld_check_assert(header_field_info *hfinfo)
(hfinfo->type == FT_INT56) ||
(hfinfo->type == FT_INT64) ||
(hfinfo->type == FT_BOOLEAN) ||
(hfinfo->type == FT_PROTOCOL) ))
g_error("Field '%s' (%s) has a 'strings' value but is of type %s"
(hfinfo->type == FT_PROTOCOL) ||
(hfinfo->type == FT_FRAMENUM) ))
g_error("Field '%s' (%s) has a 'strings' value but is of type %s"
" (which is not allowed to have strings)\n",
hfinfo->name, hfinfo->abbrev, ftype_name(hfinfo->type));
@ -6637,7 +6638,7 @@ fill_label_number(field_info *fi, gchar *label_str, gboolean is_signed)
fmtfunc(tmp, value);
label_fill(label_str, 0, hfinfo, tmp);
}
else if (hfinfo->strings) {
else if (hfinfo->strings && hfinfo->type != FT_FRAMENUM) { /* Add fill_label_framenum? */
const char *val_str = hf_try_val_to_str_const(value, hfinfo, "Unknown");
out = hfinfo_number_vals_format(hfinfo, buf, value);

View File

@ -98,6 +98,9 @@ typedef void (*custom_fmt_func_64_t)(gchar *, guint64);
* header_field_info.strings */
#define RVALS(x) (const struct _range_string*)(x)
/** Cast a ft_framenum_type_t, used to set header_field_info.strings */
#define FRAMENUM_TYPE(x) GINT_TO_POINTER(x)
struct _protocol;
/** Structure for information about a protocol */

View File

@ -1817,8 +1817,8 @@ sub check_hf_entries($$)
print STDERR "Error: $hf is passing the address of a pointer to RVALS in $filename\n";
$errorCount++;
}
if ($convert !~ m/^((0[xX]0?)?0$|NULL$|VALS|VALS64|RVALS|TFS|CF_FUNC|&)/ && $display !~ /BASE_CUSTOM/) {
print STDERR "Error: non-null $hf 'convert' field missing 'VALS|VALS64|RVALS|TFS|CF_FUNC|&' in $filename ?\n";
if ($convert !~ m/^((0[xX]0?)?0$|NULL$|VALS|VALS64|RVALS|TFS|CF_FUNC|FRAMENUM_TYPE|&)/ && $display !~ /BASE_CUSTOM/) {
print STDERR "Error: non-null $hf 'convert' field missing 'VALS|VALS64|RVALS|TFS|CF_FUNC|FRAMENUM_TYPE|&' in $filename ?\n";
$errorCount++;
}
## Benign...

View File

@ -85,6 +85,7 @@ set(WIRESHARK_QT_HEADERS
protocol_hierarchy_dialog.h
qcustomplot.h
recent_file_status.h
related_packet_delegate.h
rtp_stream_dialog.h
sctp_all_assocs_dialog.h
sctp_assoc_analyse_dialog.h
@ -119,7 +120,6 @@ endif()
file(GLOB EXTRA_QT_HEADERS
packet_list_record.h
qt_ui_utils.h
related_packet_delegate.h
sparkline_delegate.h
stock_icon.h
)

View File

@ -425,7 +425,8 @@ void PacketList::setProtoTree (ProtoTree *proto_tree) {
proto_tree_ = proto_tree;
connect(proto_tree_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
connect(proto_tree_, SIGNAL(relatedFrame(int)), this, SLOT(addRelatedFrame(int)));
connect(proto_tree_, SIGNAL(relatedFrame(int,ft_framenum_type_t)),
&related_packet_delegate_, SLOT(addRelatedFrame(int,ft_framenum_type_t)));
}
void PacketList::setByteViewTab (ByteViewTab *byte_view_tab) {
@ -456,12 +457,13 @@ void PacketList::selectionChanged (const QItemSelection & selected, const QItemS
if (!cap_file_->edt) return;
if (proto_tree_ && cap_file_->edt->tree) {
proto_tree_->fillProtocolTree(cap_file_->edt->tree);
packet_info *pi = &cap_file_->edt->pi;
related_packet_delegate_.setCurrentFrame(pi->fd->num);
proto_tree_->fillProtocolTree(cap_file_->edt->tree);
conversation_t *conv = find_conversation(pi->fd->num, &pi->src, &pi->dst, pi->ptype,
pi->srcport, pi->destport, 0);
if (conv) {
related_packet_delegate_.setConversationSpan(conv->setup_frame, conv->last_frame);
related_packet_delegate_.setConversation(conv);
}
viewport()->update();
}
@ -1038,11 +1040,6 @@ void PacketList::unsetAllTimeReferences()
redrawVisiblePackets();
}
void PacketList::addRelatedFrame(int related_frame)
{
related_packet_delegate_.addRelatedFrame(related_frame);
}
void PacketList::showHeaderMenu(QPoint pos)
{
header_ctx_column_ = header()->logicalIndexAt(pos);

View File

@ -130,7 +130,6 @@ public slots:
void applyRecentColumnWidths();
private slots:
void addRelatedFrame(int related_frame);
void showHeaderMenu(QPoint pos);
void headerMenuTriggered();
void columnVisibilityTriggered();

View File

@ -26,6 +26,7 @@
#include <epan/epan_dissect.h>
#include <epan/column-info.h>
#include <epan/column.h>
#include <epan/conversation.h>
#include "color.h"
#include "color_filters.h"
@ -39,7 +40,8 @@ unsigned PacketListRecord::col_data_ver_ = 1;
PacketListRecord::PacketListRecord(frame_data *frameData) :
fdata_(frameData),
data_ver_(0),
colorized_(false)
colorized_(false),
conv_(NULL)
{
}
@ -159,6 +161,10 @@ void PacketListRecord::dissect(capture_file *cap_file, bool dissect_color)
}
data_ver_ = col_data_ver_;
packet_info *pi = &edt.pi;
conv_ = find_conversation(pi->fd->num, &pi->src, &pi->dst, pi->ptype,
pi->srcport, pi->destport, 0);
epan_dissect_cleanup(&edt);
ws_buffer_free(&buf);
}

View File

@ -35,6 +35,8 @@
#include <QList>
#include <QVariant>
struct conversation;
class PacketListRecord
{
public:
@ -44,6 +46,7 @@ public:
frame_data *frameData() const { return fdata_; }
// packet_list->col_to_text in gtk/packet_list_store.c
static int textColumn(int column) { return cinfo_column_.value(column, -1); }
struct conversation *conversation() { return conv_; }
int columnTextSize(const char *str);
static void resetColumns(column_info *cinfo);
@ -63,6 +66,9 @@ private:
/** Has this record been colorized? */
bool colorized_;
/** Conversation. Used by RelatedPacketDelegate */
struct conversation *conv_;
void dissect(capture_file *cap_file, bool dissect_color = false);
void cacheColumnStrings(column_info *cinfo);

View File

@ -107,7 +107,8 @@ proto_tree_draw_node(proto_node *node, gpointer data)
item->setData(0, Qt::FontRole, font);
if (fi->hfinfo->type == FT_FRAMENUM) {
proto_tree->emitRelatedFrame(fi->value.value.uinteger);
ft_framenum_type_t framenum_type = (ft_framenum_type_t)GPOINTER_TO_INT(fi->hfinfo->strings);
proto_tree->emitRelatedFrame(fi->value.value.uinteger, framenum_type);
}
}
}
@ -294,9 +295,9 @@ void ProtoTree::fillProtocolTree(proto_tree *protocol_tree) {
proto_tree_children_foreach(protocol_tree, proto_tree_draw_node, invisibleRootItem());
}
void ProtoTree::emitRelatedFrame(int related_frame)
void ProtoTree::emitRelatedFrame(int related_frame, ft_framenum_type_t framenum_type)
{
emit relatedFrame(related_frame);
emit relatedFrame(related_frame, framenum_type);
}
void ProtoTree::updateSelectionStatus(QTreeWidgetItem* item) {

View File

@ -37,7 +37,7 @@ class ProtoTree : public QTreeWidget
public:
explicit ProtoTree(QWidget *parent = 0);
void fillProtocolTree(proto_tree *protocol_tree);
void emitRelatedFrame(int related_frame);
void emitRelatedFrame(int related_frame, ft_framenum_type_t framenum_type = FT_FRAMENUM_NONE);
void clear();
protected:
@ -53,7 +53,7 @@ signals:
void protoItemSelected(field_info *);
void openPacketInNewWindow(bool);
void goToPacket(int);
void relatedFrame(int);
void relatedFrame(int, ft_framenum_type_t);
public slots:
void setMonospaceFont(const QFont &mono_font);

View File

@ -22,22 +22,45 @@
#include "related_packet_delegate.h"
#include "packet_list_record.h"
#include <QPainter>
#include "color_utils.h"
#include <QApplication>
#include <QPainter>
// To do:
// - Add other frame types and symbols (ACKs, etc).
// - Add tooltips. It looks like this needs to be done in
// PacketListModel::data.
// - Add "Go -> Next Related" and "Go -> Previous Related"?
// - Apply as filter?
RelatedPacketDelegate::RelatedPacketDelegate(QWidget *parent) :
QStyledItemDelegate(parent),
conv_(NULL),
current_frame_(0)
{
clear();
}
void RelatedPacketDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int en_w = option.fontMetrics.height() / 2;
QStyleOptionViewItemV4 optv4 = option;
QStyledItemDelegate::initStyleOption(&optv4, index);
int em_w = optv4.fontMetrics.height();
int en_w = (em_w + 1) / 2;
optv4.features |= QStyleOptionViewItemV4::HasDecoration;
optv4.decorationSize.setHeight(1);
optv4.decorationSize.setWidth(en_w);
optv4.decorationSize.setWidth(em_w);
QStyledItemDelegate::paint(painter, optv4, index);
guint32 setup_frame = 0, last_frame = 0;
if (conv_) {
setup_frame = (int) conv_->setup_frame;
last_frame = (int) conv_->last_frame;
}
const frame_data *fd;
PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
if (!record || (fd = record->frameData()) == NULL) {
@ -62,41 +85,78 @@ void RelatedPacketDelegate::paint(QPainter *painter, const QStyleOptionViewItem
} else {
fg = optv4.palette.color(cg, QPalette::Text);
}
qreal alpha = 0.20; // Arbitrary. Should arguably be a preference.
// We draw in the same place more than once so we first draw on a
// QImage at 100% opacity then draw that on our packet list item.
QImage overlay = QImage(en_w * 2, optv4.rect.height(), QImage::Format_ARGB32_Premultiplied);
QPainter op(&overlay);
fg = ColorUtils::alphaBlend(fg, optv4.palette.color(cg, QPalette::Base), 0.5);
QPen line_pen(fg);
line_pen.setWidth(optv4.fontMetrics.lineWidth());
line_pen.setJoinStyle(Qt::RoundJoin);
overlay.fill(Qt::transparent);
op.setPen(fg);
op.translate(en_w + 0.5, 0.5);
op.setRenderHint(QPainter::Antialiasing, true);
painter->setPen(line_pen);
painter->translate(optv4.rect.x(), optv4.rect.y());
painter->translate(en_w + 0.5, 0.5);
painter->setRenderHint(QPainter::Antialiasing, true);
int height = optv4.rect.height();
// Uncomment to make the boundary visible.
// painter->save();
// painter->setPen(Qt::darkRed);
// painter->drawRect(QRectF(0.5, 0.5, en_w - 1, height - 1));
// painter->restore();
// The current decorations are based on what looked good and were easy
// to code. W might want to improve them by drawing small dots or tick
// marks for frames in the same conversation XOR draw a gap for unrelated
// frames.
if (first_frame_ > 0 && last_frame_ > 0 && first_frame_ != last_frame_) {
int height = optv4.rect.height();
if ((int) fd->num == first_frame_) {
op.drawLine(0, height / 2, 0, height);
op.drawLine(1, height / 2, en_w, height / 2);
} else if ((int) fd->num > first_frame_ && (int) fd->num < last_frame_) {
op.drawLine(0, 0, 0, height);
} else if ((int) fd->num == last_frame_) {
op.drawLine(0, 0, 0, height / 2);
op.drawLine(1, height / 2, en_w, height / 2);
// to code.
// Vertical line. Lower and upper half for the start and end of the
// conversation respectively, solid for conversation member, dashed
// for other packets in the start-end range.
if (setup_frame > 0 && last_frame > 0 && setup_frame != last_frame) {
if (fd->num == setup_frame) {
painter->drawLine(0, height / 2, 0, height);
painter->drawLine(1, height / 2, en_w - 1, height / 2);
} else if (fd->num > setup_frame && fd->num < last_frame) {
painter->save();
if (conv_ != record->conversation()) {
QPen other_pen(line_pen);
other_pen.setStyle(Qt::DashLine);
painter->setPen(other_pen);
}
painter->drawLine(0, 0, 0, height);
painter->restore();
} else if (fd->num == last_frame) {
painter->drawLine(0, 0, 0, height / 2);
painter->drawLine(1, height / 2, en_w, height / 2);
}
}
// Related packet indicator. Rightward arrow for requests, leftward
// arrow for responses, circle for others.
if (related_frames_.contains(fd->num)) {
op.setBrush(fg);
op.drawEllipse(QPointF(0.0, optv4.rect.height() / 2), 2, 2);
painter->setBrush(fg);
switch (related_frames_[fd->num]) {
// Request and response arrows are moved forward one pixel in order to
// maximize white space between the heads and the conversation line.
case FT_FRAMENUM_REQUEST:
{
int hh = height / 2;
QPoint tail(2 - en_w, hh);
QPoint head(en_w, hh);
drawArrow(painter, tail, head, hh / 2);
break;
}
case FT_FRAMENUM_RESPONSE:
{
int hh = height / 2;
QPoint tail(en_w - 1, hh);
QPoint head(1 - en_w, hh);
drawArrow(painter, tail, head, hh / 2);
break;
}
case FT_FRAMENUM_NONE:
default:
painter->drawEllipse(QPointF(0.0, optv4.rect.height() / 2), 2, 2);
}
}
painter->setOpacity(alpha);
painter->drawImage(optv4.rect.x(), optv4.rect.y(), overlay);
painter->restore();
}
@ -106,21 +166,47 @@ QSize RelatedPacketDelegate::sizeHint(const QStyleOptionViewItem &option,
QStyledItemDelegate::sizeHint(option, index).height());
}
void RelatedPacketDelegate::drawArrow(QPainter *painter, QPoint tail, QPoint head, int head_size) const
{
int x_mul = head.x() > tail.x() ? -1 : 1;
QPoint head_points[] = {
head,
QPoint(head.x() + (head_size * x_mul), head.y() + (head_size / 2)),
QPoint(head.x() + (head_size * x_mul), head.y() - (head_size / 2)),
};
painter->drawLine(tail.x(), tail.y(), head.x() + (head_size * x_mul), head.y());
painter->drawPolygon(head_points, 3);
}
void RelatedPacketDelegate::clear()
{
related_frames_.clear();
first_frame_ = last_frame_ = -1;
current_frame_ = 0;
conv_ = NULL;
}
void RelatedPacketDelegate::addRelatedFrame(int frame_num)
void RelatedPacketDelegate::addRelatedFrame(int frame_num, ft_framenum_type_t framenum_type)
{
related_frames_ << frame_num;
related_frames_[frame_num] = framenum_type;
// Last match wins. Last match might not make sense, however.
if (current_frame_ > 0) {
switch (framenum_type) {
case FT_FRAMENUM_REQUEST:
related_frames_[current_frame_] = FT_FRAMENUM_RESPONSE;
break;
case FT_FRAMENUM_RESPONSE:
related_frames_[current_frame_] = FT_FRAMENUM_REQUEST;
break;
default:
break;
}
}
}
void RelatedPacketDelegate::setConversationSpan(int first_frame, int last_frame)
void RelatedPacketDelegate::setConversation(conversation *conv)
{
first_frame_ = first_frame;
last_frame_ = last_frame;
conv_ = conv;
}
/*

View File

@ -26,16 +26,23 @@
#include "epan/conversation.h"
#include <QList>
#include <QHash>
#include <QStyledItemDelegate>
class QPainter;
struct conversation;
class RelatedPacketDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
RelatedPacketDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) { clear(); }
RelatedPacketDelegate(QWidget *parent = 0);
void clear();
void addRelatedFrame(int frame_num);
void setConversationSpan(int first_frame, int last_frame);
void setCurrentFrame(guint32 current_frame) { current_frame_ = current_frame; }
void setConversation(struct conversation *conv);
public slots:
void addRelatedFrame(int frame_num, ft_framenum_type_t framenum_type = FT_FRAMENUM_NONE);
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
@ -44,10 +51,11 @@ protected:
const QModelIndex &index) const;
private:
QList<int> related_frames_;
int first_frame_;
int last_frame_;
QHash<int, ft_framenum_type_t> related_frames_;
struct conversation *conv_;
guint32 current_frame_;
void drawArrow(QPainter *painter, QPoint tail, QPoint head, int head_size) const;
signals: