1 /* sctp_graph_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include <wsutil/utf8_entities.h>
12 #include "sctp_graph_dialog.h"
13 #include <ui_sctp_graph_dialog.h>
14 #include "sctp_assoc_analyse_dialog.h"
18 #include <epan/dissectors/packet-sctp.h>
19 #include "epan/packet.h"
21 #include "ui/tap-sctp-analysis.h"
23 #include <QMessageBox>
25 #include <ui/qt/utils/qt_ui_utils.h>
26 #include <ui/qt/widgets/qcustomplot.h>
27 #include "ui/qt/widgets/wireshark_file_dialog.h"
28 #include "main_application.h"
30 SCTPGraphDialog::SCTPGraphDialog(QWidget
*parent
, const sctp_assoc_info_t
*assoc
,
31 capture_file
*cf
, int dir
) :
33 ui(new Ui::SCTPGraphDialog
),
41 selected_assoc_id
= assoc
->assoc_id
;
44 Qt::WindowFlags flags
= Qt::Window
| Qt::WindowSystemMenuHint
45 | Qt::WindowMinimizeButtonHint
46 | Qt::WindowMaximizeButtonHint
47 | Qt::WindowCloseButtonHint
;
48 this->setWindowFlags(flags
);
49 this->setWindowTitle(tr("SCTP TSNs and SACKs over Time: %1 Port1 %2 Port2 %3")
50 .arg(gchar_free_to_qstring(cf_get_display_name(cap_file_
))).arg(assoc
->port1
).arg(assoc
->port2
));
51 if ((direction
== 1 && assoc
->n_array_tsn1
== 0) || (direction
== 2 && assoc
->n_array_tsn2
== 0)) {
53 msgBox
.setText(tr("No Data Chunks sent"));
61 SCTPGraphDialog::~SCTPGraphDialog()
66 void SCTPGraphDialog::drawNRSACKGraph(const sctp_assoc_info_t
* selected_assoc
)
68 tsn_t
*sack
= Q_NULLPTR
;
69 GList
*list
= Q_NULLPTR
, *tlist
= Q_NULLPTR
;
70 uint16_t gap_start
=0, gap_end
=0, i
, numberOf_gaps
, numberOf_nr_gaps
;
72 uint32_t tsnumber
, j
= 0, min_tsn
, rel
= 0;
73 struct nr_sack_chunk_header
*nr_sack_header
= Q_NULLPTR
;
74 struct gaps
*nr_gap
= Q_NULLPTR
;
75 /* This holds the sum of gap acks and nr gap acks */
76 uint16_t total_gaps
= 0;
79 list
= g_list_last(selected_assoc
->sack1
);
80 min_tsn
= selected_assoc
->min_tsn1
;
82 list
= g_list_last(selected_assoc
->sack2
);
83 min_tsn
= selected_assoc
->min_tsn2
;
89 sack
= gxx_list_data(tsn_t
*, list
);
90 tlist
= g_list_first(sack
->tsns
);
92 type
= gxx_list_data(struct chunk_header
*, tlist
)->type
;
93 if (type
== SCTP_NR_SACK_CHUNK_ID
) {
94 nr_sack_header
= gxx_list_data(struct nr_sack_chunk_header
*, tlist
);
95 numberOf_nr_gaps
=g_ntohs(nr_sack_header
->nr_of_nr_gaps
);
96 numberOf_gaps
=g_ntohs(nr_sack_header
->nr_of_gaps
);
97 tsnumber
= g_ntohl(nr_sack_header
->cum_tsn_ack
);
98 total_gaps
= numberOf_gaps
+ numberOf_nr_gaps
;
99 /* If the number of nr_gaps is greater than 0 */
100 if (total_gaps
> 0) {
101 nr_gap
= &nr_sack_header
->gaps
[0];
102 for (i
= 0; i
< total_gaps
; i
++) {
103 gap_start
= g_ntohs(nr_gap
->start
);
104 gap_end
= g_ntohs(nr_gap
->end
);
105 for (j
= gap_start
; j
<= gap_end
; j
++) {
106 if (i
>= numberOf_gaps
) {
107 yn
.append(j
+ tsnumber
- rel
);
108 xn
.append(sack
->secs
+ sack
->usecs
/1000000.0);
109 fn
.append(sack
->frame_number
);
111 yg
.append(j
+ tsnumber
- rel
);
112 xg
.append(sack
->secs
+ sack
->usecs
/1000000.0);
113 fg
.append(sack
->frame_number
);
116 if (i
< total_gaps
-1)
120 if (tsnumber
>=min_tsn
) {
121 ys
.append(j
+ tsnumber
- rel
);
122 xs
.append(sack
->secs
+ sack
->usecs
/1000000.0);
123 fs
.append(sack
->frame_number
);
127 tlist
= gxx_list_next(tlist
);
129 list
= gxx_list_previous(list
);
133 void SCTPGraphDialog::drawSACKGraph(const sctp_assoc_info_t
* selected_assoc
)
135 GList
*listSACK
= Q_NULLPTR
, *tlist
= Q_NULLPTR
;
136 uint16_t gap_start
=0, gap_end
=0, nr
, dup_nr
;
137 struct sack_chunk_header
*sack_header
= Q_NULLPTR
;
138 struct gaps
*gap
= Q_NULLPTR
;
139 tsn_t
*tsn
= Q_NULLPTR
;
141 uint32_t tsnumber
=0, rel
= 0;
143 uint32_t *dup_list
= Q_NULLPTR
;
146 if (direction
== 1) {
147 minTSN
= selected_assoc
->min_tsn1
;
148 listSACK
= g_list_last(selected_assoc
->sack1
);
150 minTSN
= selected_assoc
->min_tsn2
;
151 listSACK
= g_list_last(selected_assoc
->sack2
);
157 tsn
= gxx_list_data(tsn_t
*, listSACK
);
158 tlist
= g_list_first(tsn
->tsns
);
160 type
= gxx_list_data(struct chunk_header
*, tlist
)->type
;
161 if (type
== SCTP_SACK_CHUNK_ID
) {
162 sack_header
= gxx_list_data(struct sack_chunk_header
*, tlist
);
163 nr
=g_ntohs(sack_header
->nr_of_gaps
);
164 tsnumber
= g_ntohl(sack_header
->cum_tsn_ack
);
165 dup_nr
=g_ntohs(sack_header
->nr_of_dups
);
166 if (nr
>0) { // Gap Reports green
167 gap
= &sack_header
->gaps
[0];
168 for (i
=0;i
<nr
; i
++) {
169 gap_start
=g_ntohs(gap
->start
);
170 gap_end
= g_ntohs(gap
->end
);
171 for (j
=gap_start
; j
<=gap_end
; j
++) {
172 yg
.append(j
+ tsnumber
- rel
);
173 xg
.append(tsn
->secs
+ tsn
->usecs
/1000000.0);
174 fg
.append(tsn
->frame_number
);
180 if (tsnumber
>=minTSN
) { // CumTSNAck red
181 ys
.append(tsnumber
- rel
);
182 xs
.append(tsn
->secs
+ tsn
->usecs
/1000000.0);
183 fs
.append(tsn
->frame_number
);
185 if (dup_nr
> 0) { // Duplicates cyan
186 dup_list
= &sack_header
->a_rwnd
+ 2 + nr
;
187 for (i
= 0; i
< dup_nr
; i
++) {
188 tsnumber
= g_ntohl(dup_list
[i
]);
189 if (tsnumber
>= minTSN
) {
190 yd
.append(tsnumber
- rel
);
191 xd
.append(tsn
->secs
+ tsn
->usecs
/1000000.0);
192 fd
.append(tsn
->frame_number
);
197 tlist
= gxx_list_next(tlist
);
199 listSACK
= gxx_list_previous(listSACK
);
202 QCPScatterStyle myScatter
;
203 myScatter
.setShape(QCPScatterStyle::ssCircle
);
204 myScatter
.setSize(3);
206 int graphcount
= ui
->sctpPlot
->graphCount();
207 // create graph and assign data to it:
211 QCPGraph
*gr
= ui
->sctpPlot
->addGraph();
212 gr
->setName(QStringLiteral("SACK"));
213 myScatter
.setPen(QPen(Qt::red
));
214 myScatter
.setBrush(Qt::red
);
215 ui
->sctpPlot
->graph(graphcount
)->setScatterStyle(myScatter
);
216 ui
->sctpPlot
->graph(graphcount
)->setLineStyle(QCPGraph::lsNone
);
217 ui
->sctpPlot
->graph(graphcount
)->setData(xs
, ys
);
218 typeStrings
.insert(graphcount
, tr("CumTSNAck"));
224 QCPGraph
*gr
= ui
->sctpPlot
->addGraph();
225 gr
->setName(QStringLiteral("GAP"));
226 myScatter
.setPen(QPen(Qt::green
));
227 myScatter
.setBrush(Qt::green
);
228 ui
->sctpPlot
->graph(graphcount
)->setScatterStyle(myScatter
);
229 ui
->sctpPlot
->graph(graphcount
)->setLineStyle(QCPGraph::lsNone
);
230 ui
->sctpPlot
->graph(graphcount
)->setData(xg
, yg
);
231 typeStrings
.insert(graphcount
, tr("Gap Ack"));
237 QCPGraph
*gr
= ui
->sctpPlot
->addGraph();
238 gr
->setName(QStringLiteral("NR_GAP"));
239 myScatter
.setPen(QPen(Qt::blue
));
240 myScatter
.setBrush(Qt::blue
);
241 ui
->sctpPlot
->graph(graphcount
)->setScatterStyle(myScatter
);
242 ui
->sctpPlot
->graph(graphcount
)->setLineStyle(QCPGraph::lsNone
);
243 ui
->sctpPlot
->graph(graphcount
)->setData(xn
, yn
);
244 typeStrings
.insert(graphcount
, tr("NR Gap Ack"));
250 QCPGraph
*gr
= ui
->sctpPlot
->addGraph();
251 gr
->setName(QStringLiteral("DUP"));
252 myScatter
.setPen(QPen(Qt::cyan
));
253 myScatter
.setBrush(Qt::cyan
);
254 ui
->sctpPlot
->graph(graphcount
)->setScatterStyle(myScatter
);
255 ui
->sctpPlot
->graph(graphcount
)->setLineStyle(QCPGraph::lsNone
);
256 ui
->sctpPlot
->graph(graphcount
)->setData(xd
, yd
);
257 typeStrings
.insert(graphcount
, tr("Duplicate Ack"));
261 void SCTPGraphDialog::drawTSNGraph(const sctp_assoc_info_t
* selected_assoc
)
263 GList
*listTSN
= Q_NULLPTR
,*tlist
= Q_NULLPTR
;
264 tsn_t
*tsn
= Q_NULLPTR
;
266 uint32_t tsnumber
=0, rel
= 0, minTSN
;
268 if (direction
== 1) {
269 listTSN
= g_list_last(selected_assoc
->tsn1
);
270 minTSN
= selected_assoc
->min_tsn1
;
272 listTSN
= g_list_last(selected_assoc
->tsn2
);
273 minTSN
= selected_assoc
->min_tsn2
;
281 tsn
= gxx_list_data(tsn_t
*, listTSN
);
282 tlist
= g_list_first(tsn
->tsns
);
285 type
= gxx_list_data(struct chunk_header
*, tlist
)->type
;
286 if (type
== SCTP_DATA_CHUNK_ID
|| type
== SCTP_I_DATA_CHUNK_ID
|| type
== SCTP_FORWARD_TSN_CHUNK_ID
) {
287 tsnumber
= g_ntohl(gxx_list_data(struct data_chunk_header
*, tlist
)->tsn
);
288 yt
.append(tsnumber
- rel
);
289 xt
.append(tsn
->secs
+ tsn
->usecs
/1000000.0);
290 ft
.append(tsn
->frame_number
);
292 tlist
= gxx_list_next(tlist
);
294 listTSN
= gxx_list_previous(listTSN
);
297 QCPScatterStyle myScatter
;
298 myScatter
.setShape(QCPScatterStyle::ssCircle
);
299 myScatter
.setSize(3);
301 int graphcount
= ui
->sctpPlot
->graphCount();
302 // create graph and assign data to it:
306 QCPGraph
*gr
= ui
->sctpPlot
->addGraph();
307 gr
->setName(QStringLiteral("TSN"));
308 myScatter
.setPen(QPen(Qt::black
));
309 myScatter
.setBrush(Qt::black
);
310 ui
->sctpPlot
->graph(graphcount
)->setScatterStyle(myScatter
);
311 ui
->sctpPlot
->graph(graphcount
)->setLineStyle(QCPGraph::lsNone
);
312 ui
->sctpPlot
->graph(graphcount
)->setData(xt
, yt
);
313 typeStrings
.insert(graphcount
, tr("TSN"));
317 void SCTPGraphDialog::drawGraph(const sctp_assoc_info_t
* selected_assoc
)
319 if (!selected_assoc
) {
320 selected_assoc
= SCTPAssocAnalyseDialog::findAssoc(this, selected_assoc_id
);
321 if (!selected_assoc
) return;
324 uint32_t maxTSN
, minTSN
;
326 if (direction
== 1) {
327 maxTSN
= selected_assoc
->max_tsn1
;
328 minTSN
= selected_assoc
->min_tsn1
;
330 maxTSN
= selected_assoc
->max_tsn2
;
331 minTSN
= selected_assoc
->min_tsn2
;
333 ui
->sctpPlot
->clearGraphs();
352 drawSACKGraph(selected_assoc
);
353 drawNRSACKGraph(selected_assoc
);
356 drawTSNGraph(selected_assoc
);
359 drawTSNGraph(selected_assoc
);
360 drawSACKGraph(selected_assoc
);
361 drawNRSACKGraph(selected_assoc
);
364 drawTSNGraph(selected_assoc
);
365 drawSACKGraph(selected_assoc
);
366 drawNRSACKGraph(selected_assoc
);
370 // give the axes some labels:
371 ui
->sctpPlot
->xAxis
->setLabel(tr("time [secs]"));
372 ui
->sctpPlot
->yAxis
->setLabel(tr("TSNs"));
373 ui
->sctpPlot
->setInteractions(QCP::iRangeZoom
| QCP::iRangeDrag
| QCP::iSelectPlottables
);
374 connect(ui
->sctpPlot
, &QCustomPlot::plottableClick
, this, &SCTPGraphDialog::graphClicked
);
375 // set axes ranges, so we see all data:
376 QCPRange
myXRange(selected_assoc
->min_secs
, (selected_assoc
->max_secs
+1));
378 QCPRange
myYRange(0, maxTSN
- minTSN
+ 1);
379 ui
->sctpPlot
->yAxis
->setRange(myYRange
);
381 QCPRange
myYRange(minTSN
, maxTSN
+ 1);
382 ui
->sctpPlot
->yAxis
->setRange(myYRange
);
384 ui
->sctpPlot
->xAxis
->setRange(myXRange
);
385 ui
->sctpPlot
->replot();
388 void SCTPGraphDialog::on_pushButton_clicked()
394 void SCTPGraphDialog::on_pushButton_2_clicked()
400 void SCTPGraphDialog::on_pushButton_3_clicked()
406 void SCTPGraphDialog::on_pushButton_4_clicked()
408 const sctp_assoc_info_t
* selected_assoc
= SCTPAssocAnalyseDialog::findAssoc(this, selected_assoc_id
);
409 if (!selected_assoc
) return;
411 ui
->sctpPlot
->xAxis
->setRange(selected_assoc
->min_secs
, selected_assoc
->max_secs
+1);
413 if (direction
== 1) {
414 ui
->sctpPlot
->yAxis
->setRange(0, selected_assoc
->max_tsn1
- selected_assoc
->min_tsn1
);
416 ui
->sctpPlot
->yAxis
->setRange(0, selected_assoc
->max_tsn2
- selected_assoc
->min_tsn2
);
419 if (direction
== 1) {
420 ui
->sctpPlot
->yAxis
->setRange(selected_assoc
->min_tsn1
, selected_assoc
->max_tsn1
);
422 ui
->sctpPlot
->yAxis
->setRange(selected_assoc
->min_tsn2
, selected_assoc
->max_tsn2
);
425 ui
->sctpPlot
->replot();
428 void SCTPGraphDialog::graphClicked(QCPAbstractPlottable
* plottable
, int, QMouseEvent
* event
)
432 double times
= ui
->sctpPlot
->xAxis
->pixelToCoord(event
->pos().x());
433 if (plottable
->name().contains("TSN", Qt::CaseInsensitive
)) {
434 for (i
= 0; i
< xt
.size(); i
++) {
435 if (times
<= xt
.value(i
)) {
436 frame_num
= ft
.at(i
);
440 } else if (plottable
->name().contains("SACK", Qt::CaseInsensitive
)) {
441 for (i
= 0; i
< xs
.size(); i
++) {
442 if (times
<= xs
.value(i
)) {
443 frame_num
= fs
.at(i
);
447 } else if (plottable
->name().contains("DUP", Qt::CaseInsensitive
)) {
448 for (i
= 0; i
< xd
.size(); i
++) {
449 if (times
<= xd
.value(i
)) {
450 frame_num
= fd
.at(i
);
454 } else if (plottable
->name().contains("NR_GAP", Qt::CaseInsensitive
)) {
455 for (i
= 0; i
< xn
.size(); i
++) {
456 if (times
<= xn
.value(i
)) {
457 frame_num
= fn
.at(i
);
461 } else if (plottable
->name().contains("GAP", Qt::CaseInsensitive
)) {
462 for (i
= 0; i
< xs
.size(); i
++) {
463 if (times
<= xs
.value(i
)) {
464 frame_num
= fs
.at(i
);
469 if (cap_file_
&& frame_num
> 0) {
470 cf_goto_frame(cap_file_
, frame_num
, false);
472 ui
->hintLabel
->setText(tr("<small><i>%1: %2 Time: %3 secs </i></small>")
473 .arg(plottable
->name())
474 .arg(floor(ui
->sctpPlot
->yAxis
->pixelToCoord(event
->pos().y()) + 0.5))
475 .arg(ui
->sctpPlot
->xAxis
->pixelToCoord(event
->pos().x())));
478 void SCTPGraphDialog::save_graph(QDialog
*dlg
, QCustomPlot
*plot
)
480 QString file_name
, extension
;
481 QDir
path(mainApp
->openDialogInitialDir());
482 QString pdf_filter
= tr("Portable Document Format (*.pdf)");
483 QString png_filter
= tr("Portable Network Graphics (*.png)");
484 QString bmp_filter
= tr("Windows Bitmap (*.bmp)");
485 // Gaze upon my beautiful graph with lossy artifacts!
486 QString jpeg_filter
= tr("JPEG File Interchange Format (*.jpeg *.jpg)");
487 QString filter
= QStringLiteral("%1;;%2;;%3;;%4")
493 file_name
= WiresharkFileDialog::getSaveFileName(dlg
, mainApp
->windowTitleString(tr("Save Graph As…")),
494 path
.canonicalPath(), filter
, &extension
);
496 if (file_name
.length() > 0) {
497 bool save_ok
= false;
498 if (extension
.compare(pdf_filter
) == 0) {
499 save_ok
= plot
->savePdf(file_name
);
500 } else if (extension
.compare(png_filter
) == 0) {
501 save_ok
= plot
->savePng(file_name
);
502 } else if (extension
.compare(bmp_filter
) == 0) {
503 save_ok
= plot
->saveBmp(file_name
);
504 } else if (extension
.compare(jpeg_filter
) == 0) {
505 save_ok
= plot
->saveJpg(file_name
);
507 // else error dialog?
509 mainApp
->setLastOpenDirFromFilename(file_name
);
515 void SCTPGraphDialog::on_saveButton_clicked()
517 save_graph(this, ui
->sctpPlot
);
520 void SCTPGraphDialog::on_relativeTsn_stateChanged(int arg1
)