1 /* sequence_diagram.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "sequence_diagram.h"
11 
12 #include "epan/addr_resolv.h"
13 #include "epan/sequence_analysis.h"
14 
15 #include <ui/qt/utils/color_utils.h>
16 #include <ui/qt/utils/qt_ui_utils.h>
17 #include "ui/recent.h"
18 
19 #include <QFont>
20 #include <QFontMetrics>
21 #include <QPalette>
22 #include <QPen>
23 #include <QPointF>
24 
25 const int max_comment_em_width_ = 20;
26 
27 // UML-like network node sequence diagrams.
28 // https://developer.ibm.com/articles/the-sequence-diagram/
29 
WSCPSeqData()30 WSCPSeqData::WSCPSeqData() :
31   key(0),
32   value(NULL)
33 {
34 }
35 
WSCPSeqData(double key,struct _seq_analysis_item * value)36 WSCPSeqData::WSCPSeqData(double key, struct _seq_analysis_item *value) :
37   key(key),
38   value(value)
39 {
40 }
41 
SequenceDiagram(QCPAxis * keyAxis,QCPAxis * valueAxis,QCPAxis * commentAxis)42 SequenceDiagram::SequenceDiagram(QCPAxis *keyAxis, QCPAxis *valueAxis, QCPAxis *commentAxis) :
43     QCPAbstractPlottable(keyAxis, valueAxis),
44     key_axis_(keyAxis),
45     value_axis_(valueAxis),
46     comment_axis_(commentAxis),
47     data_(NULL),
48     sainfo_(NULL),
49     selected_packet_(0),
50     selected_key_(-1.0)
51 {
52     data_ = new WSCPSeqDataMap();
53     // xaxis (value): Address
54     // yaxis (key): Time
55     // yaxis2 (comment): Extra info ("Comment" in GTK+)
56 
57 //    valueAxis->setAutoTickStep(false);
58     QList<QCPAxis *> axes;
59     axes << value_axis_ << key_axis_ << comment_axis_;
60     QPen no_pen(Qt::NoPen);
61     foreach (QCPAxis *axis, axes) {
62         QSharedPointer<QCPAxisTicker> ticker(new QCPAxisTickerText);
63         axis->setTicker(ticker);
64         axis->setSubTickPen(no_pen);
65         axis->setTickPen(no_pen);
66         axis->setBasePen(no_pen);
67     }
68 
69     value_axis_->grid()->setVisible(false);
70 
71     key_axis_->setRangeReversed(true);
72     key_axis_->grid()->setVisible(false);
73 
74     comment_axis_->setRangeReversed(true);
75     comment_axis_->grid()->setVisible(false);
76 
77     QFont comment_font = comment_axis_->tickLabelFont();
78     comment_font.setPointSizeF(comment_font.pointSizeF() * 0.8);
79     smooth_font_size(comment_font);
80     comment_axis_->setTickLabelFont(comment_font);
81     comment_axis_->setSelectedTickLabelFont(QFont(comment_font.family(), comment_font.pointSizeF(), QFont::Bold));
82     //             frame_label
83     // port_src -----------------> port_dst
84 
85 //    setTickVectorLabels
86     //    valueAxis->setTickLabelRotation(30);
87 }
88 
~SequenceDiagram()89 SequenceDiagram::~SequenceDiagram()
90 {
91     delete data_;
92 }
93 
adjacentPacket(bool next)94 int SequenceDiagram::adjacentPacket(bool next)
95 {
96     int adjacent_packet = -1;
97     WSCPSeqDataMap::const_iterator it;
98 
99     if (data_->size() < 1) return adjacent_packet;
100 
101     if (selected_packet_ < 1) {
102         if (next) {
103             it = data_->constBegin();
104         } else {
105             it = data_->constEnd();
106             --it;
107         }
108         selected_key_ = it.value().key;
109         return it.value().value->frame_number;
110     }
111 
112     if (next) {
113         for (it = data_->constBegin(); it != data_->constEnd(); ++it) {
114             if (it.value().value->frame_number == selected_packet_) {
115                 ++it;
116                 if (it != data_->constEnd()) {
117                     adjacent_packet = it.value().value->frame_number;
118                     selected_key_ = it.value().key;
119                 }
120                 break;
121             }
122         }
123     } else {
124         it = data_->constEnd();
125         --it;
126         while (it != data_->constBegin()) {
127             guint32 prev_frame = it.value().value->frame_number;
128             --it;
129             if (prev_frame == selected_packet_) {
130                 adjacent_packet = it.value().value->frame_number;
131                 selected_key_ = it.value().key;
132                 break;
133             }
134         }
135     }
136 
137     return adjacent_packet;
138 }
139 
setData(_seq_analysis_info * sainfo)140 void SequenceDiagram::setData(_seq_analysis_info *sainfo)
141 {
142     data_->clear();
143     sainfo_ = sainfo;
144     if (!sainfo) return;
145 
146     double cur_key = 0.0;
147     QVector<double> key_ticks, val_ticks;
148     QVector<QString> key_labels, val_labels, com_labels;
149     QFontMetrics com_fm(comment_axis_->tickLabelFont());
150     int elide_w = com_fm.height() * max_comment_em_width_;
151     char* addr_str;
152 
153     for (GList *cur = g_queue_peek_nth_link(sainfo->items, 0); cur; cur = gxx_list_next(cur)) {
154         seq_analysis_item_t *sai = gxx_list_data(seq_analysis_item_t *, cur);
155         if (sai->display) {
156             WSCPSeqData new_data;
157 
158             new_data.key = cur_key;
159             new_data.value = sai;
160             data_->insert(new_data.key, new_data);
161 
162             key_ticks.append(cur_key);
163             key_labels.append(sai->time_str);
164 
165             com_labels.append(com_fm.elidedText(sai->comment, Qt::ElideRight, elide_w));
166 
167             cur_key++;
168         }
169     }
170 
171     for (unsigned int i = 0; i < sainfo_->num_nodes; i++) {
172         val_ticks.append(i);
173         addr_str = address_to_display(Q_NULLPTR, &(sainfo_->nodes[i]));
174         val_labels.append(addr_str);
175         if (i % 2 == 0) {
176             val_labels.last().append("\n");
177         }
178 
179         wmem_free(Q_NULLPTR, addr_str);
180     }
181 
182     QSharedPointer<QCPAxisTickerText> key_ticker = qSharedPointerCast<QCPAxisTickerText>(keyAxis()->ticker());
183     key_ticker->setTicks(key_ticks, key_labels);
184     QSharedPointer<QCPAxisTickerText> value_ticker = qSharedPointerCast<QCPAxisTickerText>(valueAxis()->ticker());
185     value_ticker->setTicks(val_ticks, val_labels);
186     QSharedPointer<QCPAxisTickerText> comment_ticker = qSharedPointerCast<QCPAxisTickerText>(comment_axis_->ticker());
187     comment_ticker->setTicks(key_ticks, com_labels);
188 }
189 
setSelectedPacket(int selected_packet)190 void SequenceDiagram::setSelectedPacket(int selected_packet)
191 {
192     selected_key_ = -1;
193     if (selected_packet > 0) {
194         selected_packet_ = selected_packet;
195     } else {
196         selected_packet_ = 0;
197     }
198     mParentPlot->replot();
199 }
200 
itemForPosY(int ypos)201 _seq_analysis_item *SequenceDiagram::itemForPosY(int ypos)
202 {
203     double key_pos = qRound(key_axis_->pixelToCoord(ypos));
204 
205     if (key_pos >= 0 && key_pos < data_->size()) {
206         return data_->value(key_pos).value;
207     }
208     return NULL;
209 }
210 
selectTest(const QPointF & pos,bool,QVariant *) const211 double SequenceDiagram::selectTest(const QPointF &pos, bool, QVariant *) const
212 {
213     double key_pos = qRound(key_axis_->pixelToCoord(pos.y()));
214 
215     if (key_pos >= 0 && key_pos < data_->size()) {
216         return 1.0;
217     }
218 
219     return -1.0;
220 }
221 
draw(QCPPainter * painter)222 void SequenceDiagram::draw(QCPPainter *painter)
223 {
224     QPen fg_pen;
225     qreal alpha = 0.50;
226 
227     // Lifelines (node lines). Will likely be overdrawn below.
228     painter->save();
229     painter->setOpacity(alpha);
230     fg_pen = pen();
231     fg_pen.setStyle(Qt::DashLine);
232     painter->setPen(fg_pen);
233     for (int ll_x = value_axis_->range().lower; ll_x < value_axis_->range().upper; ll_x++) {
234         // Only draw where we have arrows.
235         if (ll_x < 0 || ll_x >= value_axis_->tickVector().size()) continue;
236         QPoint ll_start(coordsToPixels(key_axis_->range().upper, ll_x).toPoint());
237         QPoint ll_end(coordsToPixels(key_axis_->range().lower, ll_x).toPoint());
238         painter->drawLine(ll_start, ll_end);
239     }
240     painter->restore();
241     fg_pen = pen();
242 
243     WSCPSeqDataMap::const_iterator it;
244     for (it = data_->constBegin(); it != data_->constEnd(); ++it) {
245         double cur_key = it.key();
246         seq_analysis_item_t *sai = it.value().value;
247         QColor bg_color;
248 
249         if (sai->frame_number == selected_packet_) {
250             QPalette sel_pal;
251             fg_pen.setColor(sel_pal.color(QPalette::HighlightedText));
252             bg_color = sel_pal.color(QPalette::Highlight);
253             selected_key_ = cur_key;
254         } else if ((sai->has_color_filter) && (recent.packet_list_colorize)) {
255             fg_pen.setColor(QColor().fromRgb(sai->fg_color));
256             bg_color = QColor().fromRgb(sai->bg_color);
257         } else {
258             fg_pen.setColor(Qt::black);
259             bg_color = ColorUtils::sequenceColor(sai->conv_num);
260         }
261 
262         // Highlighted background
263 //        painter->save();
264         QRect bg_rect(
265                     QPoint(coordsToPixels(cur_key - 0.5, value_axis_->range().lower).toPoint()),
266                     QPoint(coordsToPixels(cur_key + 0.5, value_axis_->range().upper).toPoint()));
267         if (bg_color.isValid()) {
268             painter->fillRect(bg_rect, bg_color);
269         }
270 //        painter->restore();
271 
272         // Highlighted lifelines
273         painter->save();
274         QPen hl_pen = fg_pen;
275         hl_pen.setStyle(Qt::DashLine);
276         painter->setPen(hl_pen);
277         painter->setOpacity(alpha);
278         for (int ll_x = value_axis_->range().lower; ll_x < value_axis_->range().upper; ll_x++) {
279             // Only draw where we have arrows.
280             if (ll_x < 0 || ll_x >= value_axis_->tickVector().size()) continue;
281             QPoint ll_start(coordsToPixels(cur_key - 0.5, ll_x).toPoint());
282             QPoint ll_end(coordsToPixels(cur_key + 0.5, ll_x).toPoint());
283             hl_pen.setDashOffset(bg_rect.top() - ll_start.x());
284             painter->drawLine(ll_start, ll_end);
285         }
286         painter->restore();
287 
288         if (cur_key < key_axis_->range().lower || cur_key > key_axis_->range().upper) {
289             continue;
290         }
291         if (sai->dst_node > sai->src_node && (sai->dst_node < value_axis_->range().lower || sai->src_node > value_axis_->range().upper)) {
292             continue;
293         }
294         if (sai->src_node > sai->dst_node && (sai->src_node < value_axis_->range().lower || sai->dst_node > value_axis_->range().upper)) {
295             continue;
296         }
297 
298         // Message
299         if (pen().style() != Qt::NoPen && pen().color().alpha() != 0) {
300             painter->save();
301 
302             QFontMetrics cfm(comment_axis_->tickLabelFont());
303             double en_w = cfm.height() / 2.0;
304             int dir_mul = (sai->src_node < sai->dst_node) ? 1 : -1;
305             double ah_size = (cfm.height() / 5) * dir_mul;
306             QPoint arrow_start(coordsToPixels(cur_key, sai->src_node).toPoint());
307             arrow_start.setY(arrow_start.y() + (en_w / 2));
308             QPoint arrow_end(coordsToPixels(cur_key, sai->dst_node).toPoint());
309             arrow_end.setY(arrow_start.y());
310             QLine arrow_line(arrow_start, arrow_end);
311             QPolygon arrow_head;
312             arrow_head
313                     << QPoint(arrow_end.x() - (ah_size*3), arrow_end.y() - ah_size)
314                     << arrow_end
315                     << QPoint(arrow_end.x() - (ah_size*3), arrow_end.y() + ah_size);
316 
317             painter->setBrush(fg_pen.color());
318             painter->setPen(fg_pen);
319             painter->drawLine(arrow_line);
320             painter->drawPolygon(arrow_head);
321 
322             double comment_start = (sai->src_node < sai->dst_node)
323                     ? arrow_start.x() : arrow_end.x();
324             double arrow_width = (arrow_end.x() - arrow_start.x()) * dir_mul;
325             QString arrow_label = cfm.elidedText(sai->frame_label, Qt::ElideRight, arrow_width);
326             int arrow_label_width = 0;
327 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
328             arrow_label_width = cfm.horizontalAdvance(arrow_label);
329 #else
330             arrow_label_width = cfm.width(arrow_label);
331 #endif
332             QPoint text_pt(comment_start + ((arrow_width - arrow_label_width) / 2),
333                           arrow_start.y() - (en_w / 2));
334 
335             painter->setFont(comment_axis_->tickLabelFont());
336             painter->drawText(text_pt, arrow_label);
337 
338             if (sai->port_src && sai->port_dst) {
339                 int left_x = dir_mul > 0 ? arrow_start.x() : arrow_end.x();
340                 int right_x = dir_mul > 0 ? arrow_end.x() : arrow_start.x();
341                 QString port_left = QString::number(dir_mul > 0 ? sai->port_src : sai->port_dst);
342                 QString port_right = QString::number(dir_mul > 0 ? sai->port_dst : sai->port_src);
343                 int port_left_width = 0;
344 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
345                 port_left_width = cfm.horizontalAdvance(port_left);
346 #else
347                 port_left_width = cfm.width(port_left);
348 #endif
349                 text_pt = QPoint(left_x - en_w - port_left_width, arrow_start.y() + (en_w / 2));
350                 painter->drawText(text_pt, port_left);
351 
352                 text_pt.setX(right_x + en_w);
353                 painter->drawText(text_pt, port_right);
354             }
355             painter->restore();
356         }
357     }
358 }
359 
drawLegendIcon(QCPPainter *,const QRectF &) const360 void SequenceDiagram::drawLegendIcon(QCPPainter *, const QRectF &) const
361 {
362 }
363 
getKeyRange(bool & validRange,QCP::SignDomain) const364 QCPRange SequenceDiagram::getKeyRange(bool &validRange, QCP::SignDomain) const
365 {
366     QCPRange range;
367     bool valid = false;
368 
369     WSCPSeqDataMap::const_iterator it = data_->constBegin();
370     while (it != data_->constEnd()) {
371         double cur_key = it.key();
372         if (!valid) {
373             range.lower = range.upper = cur_key;
374             valid = true;
375         } else if (cur_key < range.lower) {
376             range.lower = cur_key;
377         } else if (cur_key > range.upper) {
378             range.upper = cur_key;
379         }
380         ++it;
381     }
382     validRange = valid;
383     return range;
384 }
385 
getValueRange(bool & validRange,QCP::SignDomain,const QCPRange &) const386 QCPRange SequenceDiagram::getValueRange(bool &validRange, QCP::SignDomain, const QCPRange &) const
387 {
388     QCPRange range;
389     bool valid = false;
390 
391     if (sainfo_) {
392         range.lower = 0;
393         range.upper = data_->size();
394         valid = true;
395     }
396     validRange = valid;
397     return range;
398 }
399