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