1 /* tcp_stream_dialog.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 "tcp_stream_dialog.h"
11 #include <ui_tcp_stream_dialog.h>
12 
13 #include <algorithm> // for std::sort
14 #include <utility> // for std::pair
15 #include <vector>
16 
17 #include "epan/to_str.h"
18 
19 #include "wsutil/str_util.h"
20 
21 #include <wsutil/utf8_entities.h>
22 
23 #include <ui/qt/utils/tango_colors.h>
24 #include <ui/qt/utils/qt_ui_utils.h>
25 #include "progress_frame.h"
26 #include "wireshark_application.h"
27 #include "ui/qt/widgets/wireshark_file_dialog.h"
28 
29 #include <QCursor>
30 #include <QDir>
31 #include <QIcon>
32 #include <QPushButton>
33 
34 #include <QDebug>
35 
36 // To do:
37 // - Make the Help button work.
38 // - Show a message or disable the graph if we don't have any data.
39 // - Add a bytes in flight graph
40 // - Make the crosshairs tracer a vertical band?
41 // - Implement File->Copy
42 // - Add UDP graphs
43 // - Make the first throughput MA period a dotted/dashed line?
44 // - Add range scroll bars?
45 // - ACK & RWIN segment ticks in tcptrace graph
46 // - Add missing elements (retrans, URG, SACK, etc) to tcptrace. It probably makes
47 //   sense to subclass QCPGraph for this.
48 
49 // The GTK+ version computes a 20 (or 21!) segment moving average. Comment
50 // out the line below to use that. By default we use a 1 second MA.
51 #define MA_1_SECOND
52 
53 #ifndef MA_1_SECOND
54 const int moving_avg_period_ = 20;
55 #endif
56 
57 const QRgb graph_color_1 = tango_sky_blue_5;
58 const QRgb graph_color_2 = tango_butter_6;
59 const QRgb graph_color_3 = tango_chameleon_5;
60 const QRgb graph_color_4 = tango_scarlet_red_4;
61 const QRgb graph_color_5 = tango_scarlet_red_6;
62 
63 // Size of selectable packet points in the base graph
64 const double pkt_point_size_ = 3.0;
65 
66 // Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
67 // in zoom mode.
68 const int min_zoom_pixels_ = 20;
69 
70 const QString average_throughput_label_ = QObject::tr("Average Throughput (bits/s)");
71 const QString round_trip_time_ms_label_ = QObject::tr("Round Trip Time (ms)");
72 const QString segment_length_label_ = QObject::tr("Segment Length (B)");
73 const QString sequence_number_label_ = QObject::tr("Sequence Number (B)");
74 const QString time_s_label_ = QObject::tr("Time (s)");
75 const QString window_size_label_ = QObject::tr("Window Size (B)");
76 
77 QCPErrorBarsNotSelectable::QCPErrorBarsNotSelectable(QCPAxis *keyAxis, QCPAxis *valueAxis) :
78     QCPErrorBars(keyAxis, valueAxis)
79 {
80 }
81 
82 QCPErrorBarsNotSelectable::~QCPErrorBarsNotSelectable()
83 {
84 }
85 
86 double QCPErrorBarsNotSelectable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
87 {
88     Q_UNUSED(pos);
89     Q_UNUSED(onlySelectable);
90     Q_UNUSED(details);
91     return -1.0;
92 }
93 
94 TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_type graph_type) :
95     GeometryStateDialog(parent),
96     ui(new Ui::TCPStreamDialog),
97     cap_file_(cf),
98     ts_offset_(0),
99     ts_origin_conn_(true),
100     seq_offset_(0),
101     seq_origin_zero_(true),
102     title_(nullptr),
103     base_graph_(nullptr),
104     tput_graph_(nullptr),
105     goodput_graph_(nullptr),
106     seg_graph_(nullptr),
107     seg_eb_(nullptr),
108     ack_graph_(nullptr),
109     sack_graph_(nullptr),
110     sack_eb_(nullptr),
111     sack2_graph_(nullptr),
112     sack2_eb_(nullptr),
113     rwin_graph_(nullptr),
114     dup_ack_graph_(nullptr),
115     zero_win_graph_(nullptr),
116     tracer_(nullptr),
117     packet_num_(0),
118     mouse_drags_(true),
119     rubber_band_(nullptr),
120     graph_updater_(this),
121     num_dsegs_(-1),
122     num_acks_(-1),
123     num_sack_ranges_(-1),
124     ma_window_size_(1.0)
125 {
126     int graph_idx = -1;
127 
128     memset(&graph_, 0, sizeof(graph_));
129 
130     ui->setupUi(this);
131     if (parent) loadGeometry(parent->width() * 2 / 3, parent->height() * 4 / 5);
132     setAttribute(Qt::WA_DeleteOnClose, true);
133 
134     guint32 th_stream = select_tcpip_session(cap_file_);
135     if (th_stream == G_MAXUINT32) {
136         done(QDialog::Rejected);
137         return;
138     }
139 
140     QComboBox *gtcb = ui->graphTypeComboBox;
141     gtcb->setUpdatesEnabled(false);
142     gtcb->addItem(ui->actionRoundTripTime->text(), GRAPH_RTT);
143     if (graph_type == GRAPH_RTT) graph_idx = gtcb->count() - 1;
144     gtcb->addItem(ui->actionThroughput->text(), GRAPH_THROUGHPUT);
145     if (graph_type == GRAPH_THROUGHPUT) graph_idx = gtcb->count() - 1;
146     gtcb->addItem(ui->actionStevens->text(), GRAPH_TSEQ_STEVENS);
147     if (graph_type == GRAPH_TSEQ_STEVENS) graph_idx = gtcb->count() - 1;
148     gtcb->addItem(ui->actionTcptrace->text(), GRAPH_TSEQ_TCPTRACE);
149     if (graph_type == GRAPH_TSEQ_TCPTRACE) graph_idx = gtcb->count() - 1;
150     gtcb->addItem(ui->actionWindowScaling->text(), GRAPH_WSCALE);
151     if (graph_type == GRAPH_WSCALE) graph_idx = gtcb->count() - 1;
152     gtcb->setUpdatesEnabled(true);
153 
154     ui->dragRadioButton->setChecked(mouse_drags_);
155 
156     ctx_menu_.addAction(ui->actionZoomIn);
157     ctx_menu_.addAction(ui->actionZoomInX);
158     ctx_menu_.addAction(ui->actionZoomInY);
159     ctx_menu_.addAction(ui->actionZoomOut);
160     ctx_menu_.addAction(ui->actionZoomOutX);
161     ctx_menu_.addAction(ui->actionZoomOutY);
162     ctx_menu_.addAction(ui->actionReset);
163     ctx_menu_.addSeparator();
164     ctx_menu_.addAction(ui->actionMoveRight10);
165     ctx_menu_.addAction(ui->actionMoveLeft10);
166     ctx_menu_.addAction(ui->actionMoveUp10);
167     ctx_menu_.addAction(ui->actionMoveDown10);
168     ctx_menu_.addAction(ui->actionMoveRight1);
169     ctx_menu_.addAction(ui->actionMoveLeft1);
170     ctx_menu_.addAction(ui->actionMoveUp1);
171     ctx_menu_.addAction(ui->actionMoveDown1);
172     ctx_menu_.addSeparator();
173     ctx_menu_.addAction(ui->actionNextStream);
174     ctx_menu_.addAction(ui->actionPreviousStream);
175     ctx_menu_.addAction(ui->actionSwitchDirection);
176     ctx_menu_.addAction(ui->actionGoToPacket);
177     ctx_menu_.addSeparator();
178     ctx_menu_.addAction(ui->actionDragZoom);
179     ctx_menu_.addAction(ui->actionToggleSequenceNumbers);
180     ctx_menu_.addAction(ui->actionToggleTimeOrigin);
181     ctx_menu_.addAction(ui->actionCrosshairs);
182     ctx_menu_.addSeparator();
183     ctx_menu_.addAction(ui->actionRoundTripTime);
184     ctx_menu_.addAction(ui->actionThroughput);
185     ctx_menu_.addAction(ui->actionStevens);
186     ctx_menu_.addAction(ui->actionTcptrace);
187     ctx_menu_.addAction(ui->actionWindowScaling);
188     set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
189 
190     graph_.type = graph_type;
191     graph_.stream = th_stream;
192     findStream();
193 
194     showWidgetsForGraphType();
195 
196     ui->streamNumberSpinBox->blockSignals(true);
197     ui->streamNumberSpinBox->setMaximum(get_tcp_stream_count() - 1);
198     ui->streamNumberSpinBox->setValue(graph_.stream);
199     ui->streamNumberSpinBox->blockSignals(false);
200 
201 #ifdef MA_1_SECOND
202     ui->maWindowSizeSpinBox->blockSignals(true);
203     ui->maWindowSizeSpinBox->setDecimals(6);
204     ui->maWindowSizeSpinBox->setMinimum(0.000001);
205     ui->maWindowSizeSpinBox->setValue(ma_window_size_);
206     ui->maWindowSizeSpinBox->blockSignals(false);
207 #endif
208 
209     // set which Throughput graphs are displayed by default
210     ui->showSegLengthCheckBox->blockSignals(true);
211     ui->showSegLengthCheckBox->setChecked(true);
212     ui->showSegLengthCheckBox->blockSignals(false);
213 
214     ui->showThroughputCheckBox->blockSignals(true);
215     ui->showThroughputCheckBox->setChecked(true);
216     ui->showThroughputCheckBox->blockSignals(false);
217 
218     // set which WScale graphs are displayed by default
219     ui->showRcvWinCheckBox->blockSignals(true);
220     ui->showRcvWinCheckBox->setChecked(true);
221     ui->showRcvWinCheckBox->blockSignals(false);
222 
223     ui->showBytesOutCheckBox->blockSignals(true);
224     ui->showBytesOutCheckBox->setChecked(true);
225     ui->showBytesOutCheckBox->blockSignals(false);
226 
227     QCustomPlot *sp = ui->streamPlot;
228     QCPTextElement *file_title = new QCPTextElement(sp, gchar_free_to_qstring(cf_get_display_name(cap_file_)));
229     file_title->setFont(sp->xAxis->labelFont());
230     title_ = new QCPTextElement(sp);
231     sp->plotLayout()->insertRow(0);
232     sp->plotLayout()->addElement(0, 0, file_title);
233     sp->plotLayout()->insertRow(0);
234     sp->plotLayout()->addElement(0, 0, title_);
235 
236     qreal pen_width = 0.5;
237     // Base Graph - enables selecting segments (both data and SACKs)
238     base_graph_ = sp->addGraph();
239     base_graph_->setPen(QPen(QBrush(graph_color_1), pen_width));
240 
241     // Throughput Graph - rate of sent bytes
242     tput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2);
243     tput_graph_->setPen(QPen(QBrush(graph_color_2), pen_width));
244     tput_graph_->setLineStyle(QCPGraph::lsStepLeft);
245 
246     // Goodput Graph - rate of ACKed bytes
247     goodput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2);
248     goodput_graph_->setPen(QPen(QBrush(graph_color_3), pen_width));
249     goodput_graph_->setLineStyle(QCPGraph::lsStepLeft);
250 
251     // Seg Graph - displays forward data segments on tcptrace graph
252     seg_graph_ = sp->addGraph();
253     seg_graph_->setLineStyle(QCPGraph::lsNone);
254     seg_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0));
255     seg_eb_ = new QCPErrorBarsNotSelectable(sp->xAxis, sp->yAxis);
256     seg_eb_->setErrorType(QCPErrorBars::etValueError);
257     seg_eb_->setPen(QPen(QBrush(graph_color_1), pen_width));
258     seg_eb_->setSymbolGap(0.0); // draw error spine as single line
259     seg_eb_->setWhiskerWidth(pkt_point_size_);
260     seg_eb_->removeFromLegend();
261     seg_eb_->setDataPlottable(seg_graph_);
262 
263     // Ack Graph - displays ack numbers from reverse packets
264     ack_graph_ = sp->addGraph();
265     ack_graph_->setPen(QPen(QBrush(graph_color_2), pen_width));
266     ack_graph_->setLineStyle(QCPGraph::lsStepLeft);
267 
268     // Sack Graph - displays highest number (most recent) SACK block
269     sack_graph_ = sp->addGraph();
270     sack_graph_->setLineStyle(QCPGraph::lsNone);
271     sack_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0));
272     sack_eb_ = new QCPErrorBarsNotSelectable(sp->xAxis, sp->yAxis);
273     sack_eb_->setErrorType(QCPErrorBars::etValueError);
274     sack_eb_->setPen(QPen(QBrush(graph_color_4), pen_width));
275     sack_eb_->setSymbolGap(0.0); // draw error spine as single line
276     sack_eb_->setWhiskerWidth(0.0);
277     sack_eb_->removeFromLegend();
278     sack_eb_->setDataPlottable(sack_graph_);
279 
280     // Sack Graph 2 - displays subsequent SACK blocks
281     sack2_graph_ = sp->addGraph();
282     sack2_graph_->setLineStyle(QCPGraph::lsNone);
283     sack2_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0));
284     sack2_eb_ = new QCPErrorBarsNotSelectable(sp->xAxis, sp->yAxis);
285     sack2_eb_->setErrorType(QCPErrorBars::etValueError);
286     sack2_eb_->setPen(QPen(QBrush(graph_color_5), pen_width));
287     sack2_eb_->setSymbolGap(0.0); // draw error spine as single line
288     sack2_eb_->setWhiskerWidth(0.0);
289     sack2_eb_->removeFromLegend();
290     sack2_eb_->setDataPlottable(sack2_graph_);
291 
292     // RWin graph - displays upper extent of RWIN advertised on reverse packets
293     rwin_graph_ = sp->addGraph();
294     rwin_graph_->setPen(QPen(QBrush(graph_color_3), pen_width));
295     rwin_graph_->setLineStyle(QCPGraph::lsStepLeft);
296 
297     // Duplicate ACK Graph - displays duplicate ack ticks
298     // QCustomPlot doesn't have QCPScatterStyle::ssTick so we have to make our own.
299     int tick_len = 3;
300     tick_len *= devicePixelRatio();
301     QPixmap da_tick_pm = QPixmap(1, tick_len * 2);
302     da_tick_pm.fill(Qt::transparent);
303     QPainter painter(&da_tick_pm);
304     QPen da_tick_pen;
305     da_tick_pen.setColor(graph_color_2);
306     da_tick_pen.setWidthF(pen_width);
307     painter.setPen(da_tick_pen);
308     painter.drawLine(0, tick_len, 0, tick_len * 2);
309     dup_ack_graph_ = sp->addGraph();
310     dup_ack_graph_->setLineStyle(QCPGraph::lsNone);
311     QCPScatterStyle da_ss = QCPScatterStyle(QCPScatterStyle::ssPixmap, graph_color_2, 0);
312     da_ss.setPixmap(da_tick_pm);
313     dup_ack_graph_->setScatterStyle(da_ss);
314 
315     // Zero Window Graph - displays zero window crosses (x)
316     zero_win_graph_ = sp->addGraph();
317     zero_win_graph_->setLineStyle(QCPGraph::lsNone);
318     zero_win_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCross, graph_color_1, 5));
319 
320     tracer_ = new QCPItemTracer(sp);
321 
322     // Triggers fillGraph() [ UNLESS the index is already graph_idx!! ]
323     if (graph_idx != ui->graphTypeComboBox->currentIndex())
324         // changing the current index will call fillGraph
325         ui->graphTypeComboBox->setCurrentIndex(graph_idx);
326     else
327         // the current index is what we want - so fillGraph() manually
328         fillGraph();
329 
330     sp->setMouseTracking(true);
331 
332     sp->yAxis->setLabelColor(QColor(graph_color_1));
333     sp->yAxis->setTickLabelColor(QColor(graph_color_1));
334 
335     tracer_->setVisible(false);
336     toggleTracerStyle(true);
337 
338     QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
339     save_bt->setText(tr("Save As…"));
340 
341     QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
342     if (close_bt) {
343         close_bt->setDefault(true);
344     }
345 
346     ProgressFrame::addToButtonBox(ui->buttonBox, parent);
347 
348     connect(sp, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*)));
349     connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
350     connect(sp, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
351     connect(sp, SIGNAL(axisClick(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)),
352             this, SLOT(axisClicked(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)));
353     connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(transformYRange(QCPRange)));
354     disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
355     this->setResult(QDialog::Accepted);
356 }
357 
358 TCPStreamDialog::~TCPStreamDialog()
359 {
360     graph_segment_list_free(&graph_);
361 
362     delete ui;
363 }
364 
365 void TCPStreamDialog::showEvent(QShowEvent *)
366 {
367     resetAxes();
368 }
369 
370 void TCPStreamDialog::keyPressEvent(QKeyEvent *event)
371 {
372     int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
373 
374     QWidget* focusWidget = QApplication::focusWidget();
375 
376     // Block propagation of "Enter" key when focus is not default (e.g. SpinBox)
377     //  [ Note that if focus was on, e.g. Close button, event would never reach
378     //      here ]
379     if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) &&
380         focusWidget !=NULL && focusWidget != ui->streamPlot) {
381 
382         // reset focus to default, and accept event
383         ui->streamPlot->setFocus();
384         event->accept();
385         return;
386     }
387 
388     // XXX - This differs from the main window but matches other applications (e.g. Mozilla and Safari)
389     switch(event->key()) {
390     case Qt::Key_Minus:
391     case Qt::Key_Underscore:    // Shifted minus on U.S. keyboards
392     case Qt::Key_O:             // GTK+
393         zoomAxes(false);
394         break;
395     case Qt::Key_Plus:
396     case Qt::Key_Equal:         // Unshifted plus on U.S. keyboards
397     case Qt::Key_I:             // GTK+
398         zoomAxes(true);
399         break;
400     case Qt::Key_X:             // Zoom X axis only
401         if (event->modifiers() & Qt::ShiftModifier) {
402             zoomXAxis(false);   // upper case X -> Zoom out
403         } else {
404             zoomXAxis(true);    // lower case x -> Zoom in
405         }
406         break;
407     case Qt::Key_Y:             // Zoom Y axis only
408         if (event->modifiers() & Qt::ShiftModifier) {
409             zoomYAxis(false);   // upper case Y -> Zoom out
410         } else {
411             zoomYAxis(true);    // lower case y -> Zoom in
412         }
413         break;
414     case Qt::Key_Right:
415     case Qt::Key_L:
416         panAxes(pan_pixels, 0);
417         break;
418     case Qt::Key_Left:
419     case Qt::Key_H:
420         panAxes(-1 * pan_pixels, 0);
421         break;
422     case Qt::Key_Up:
423     case Qt::Key_K:
424         panAxes(0, pan_pixels);
425         break;
426     case Qt::Key_Down:
427     case Qt::Key_J:
428         panAxes(0, -1 * pan_pixels);
429         break;
430 
431     case Qt::Key_Space:
432         toggleTracerStyle();
433         break;
434 
435     case Qt::Key_0:
436     case Qt::Key_ParenRight:    // Shifted 0 on U.S. keyboards
437     case Qt::Key_R:
438     case Qt::Key_Home:
439         resetAxes();
440         break;
441 
442     case Qt::Key_PageUp:
443         on_actionNextStream_triggered();
444         break;
445     case Qt::Key_PageDown:
446         on_actionPreviousStream_triggered();
447         break;
448 
449     case Qt::Key_D:
450         on_actionSwitchDirection_triggered();
451         break;
452     case Qt::Key_G:
453         on_actionGoToPacket_triggered();
454         break;
455     case Qt::Key_S:
456         on_actionToggleSequenceNumbers_triggered();
457         break;
458     case Qt::Key_T:
459         on_actionToggleTimeOrigin_triggered();
460         break;
461     case Qt::Key_Z:
462         on_actionDragZoom_triggered();
463         break;
464 
465     case Qt::Key_1:
466         on_actionRoundTripTime_triggered();
467         break;
468     case Qt::Key_2:
469         on_actionThroughput_triggered();
470         break;
471     case Qt::Key_3:
472         on_actionStevens_triggered();
473         break;
474     case Qt::Key_4:
475         on_actionTcptrace_triggered();
476         break;
477     case Qt::Key_5:
478         on_actionWindowScaling_triggered();
479         break;
480         // Alas, there is no Blade Runner-style Qt::Key_Enhance
481     }
482 
483     QDialog::keyPressEvent(event);
484 }
485 
486 void TCPStreamDialog::mousePressEvent(QMouseEvent *event)
487 {
488     // if no-one else wants the event, then this is a click on blank space.
489     //   Use this opportunity to set focus back to default, and accept event.
490     ui->streamPlot->setFocus();
491     event->accept();
492 }
493 
494 void TCPStreamDialog::mouseReleaseEvent(QMouseEvent *event)
495 {
496     mouseReleased(event);
497 }
498 
499 void TCPStreamDialog::findStream()
500 {
501     QCustomPlot *sp = ui->streamPlot;
502 
503     disconnect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
504     // if streamNumberSpinBox has focus -
505     //   first clear focus, then disable/enable, then restore focus
506     bool spin_box_focused = ui->streamNumberSpinBox->hasFocus();
507     if (spin_box_focused)
508         ui->streamNumberSpinBox->clearFocus();
509     ui->streamNumberSpinBox->setEnabled(false);
510     graph_segment_list_free(&graph_);
511     graph_segment_list_get(cap_file_, &graph_);
512     ui->streamNumberSpinBox->setEnabled(true);
513     if (spin_box_focused)
514         ui->streamNumberSpinBox->setFocus();
515 
516     connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
517 }
518 
519 void TCPStreamDialog::fillGraph(bool reset_axes, bool set_focus)
520 {
521     QCustomPlot *sp = ui->streamPlot;
522 
523     if (sp->graphCount() < 1) return;
524 
525     base_graph_->setLineStyle(QCPGraph::lsNone);
526     tracer_->setGraph(NULL);
527 
528     // base_graph_ is always visible.
529     for (int i = 0; i < sp->graphCount(); i++) {
530         sp->graph(i)->data()->clear();
531         sp->graph(i)->setVisible(i == 0 ? true : false);
532     }
533     // also clear and hide ErrorBars plottables
534     seg_eb_->setVisible(false);
535     seg_eb_->data()->clear();
536     sack_eb_->setVisible(false);
537     sack_eb_->data()->clear();
538     sack2_eb_->setVisible(false);
539     sack2_eb_->data()->clear();
540 
541     base_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, pkt_point_size_));
542 
543     sp->xAxis->setLabel(time_s_label_);
544     sp->xAxis->setNumberFormat("gb");
545     // Use enough precision to mark microseconds
546     //    when zooming in on a <100s capture
547     sp->xAxis->setNumberPrecision(8);
548     sp->yAxis->setNumberFormat("f");
549     sp->yAxis->setNumberPrecision(0);
550     sp->yAxis2->setVisible(false);
551     sp->yAxis2->setLabel(QString());
552 
553     if (!cap_file_) {
554         QString dlg_title = QString(tr("No Capture Data"));
555         setWindowTitle(dlg_title);
556         title_->setText(dlg_title);
557         sp->setEnabled(false);
558         sp->yAxis->setLabel(QString());
559         sp->replot();
560         return;
561     }
562 
563     ts_offset_ = 0;
564     seq_offset_ = 0;
565     bool first = true;
566     guint64 bytes_fwd = 0;
567     guint64 bytes_rev = 0;
568     int pkts_fwd = 0;
569     int pkts_rev = 0;
570 
571     time_stamp_map_.clear();
572     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
573         // NOTE - adding both forward and reverse packets to time_stamp_map_
574         //   so that both data and acks are selectable
575         //   (this is important especially in selecting particular SACK pkts)
576         bool insert = true;
577         if (!compareHeaders(seg)) {
578             bytes_rev += seg->th_seglen;
579             pkts_rev++;
580             // only insert reverse packets if SACK present
581             insert = (seg->num_sack_ranges != 0);
582         } else {
583             bytes_fwd += seg->th_seglen;
584             pkts_fwd++;
585         }
586         double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
587         if (first) {
588             if (ts_origin_conn_) ts_offset_ = ts;
589             if (seq_origin_zero_) {
590                 if (compareHeaders(seg))
591                     seq_offset_ = seg->th_seq;
592                 else
593                     seq_offset_ = seg->th_ack;
594             }
595             first = false;
596         }
597         if (insert) {
598             time_stamp_map_.insert(ts - ts_offset_, seg);
599         }
600     }
601 
602     switch (graph_.type) {
603     case GRAPH_TSEQ_STEVENS:
604         fillStevens();
605         break;
606     case GRAPH_TSEQ_TCPTRACE:
607         fillTcptrace();
608         break;
609     case GRAPH_THROUGHPUT:
610         fillThroughput();
611         break;
612     case GRAPH_RTT:
613         fillRoundTripTime();
614         break;
615     case GRAPH_WSCALE:
616         fillWindowScale();
617         break;
618     default:
619         break;
620     }
621     sp->setEnabled(true);
622 
623     stream_desc_ = tr("%1 %2 pkts, %3 %4 %5 pkts, %6 ")
624             .arg(UTF8_RIGHTWARDS_ARROW)
625             .arg(gchar_free_to_qstring(format_size(pkts_fwd, format_size_unit_none|format_size_prefix_si)))
626             .arg(gchar_free_to_qstring(format_size(bytes_fwd, format_size_unit_bytes|format_size_prefix_si)))
627             .arg(UTF8_LEFTWARDS_ARROW)
628             .arg(gchar_free_to_qstring(format_size(pkts_rev, format_size_unit_none|format_size_prefix_si)))
629             .arg(gchar_free_to_qstring(format_size(bytes_rev, format_size_unit_bytes|format_size_prefix_si)));
630     mouseMoved(NULL);
631     if (reset_axes)
632         resetAxes();
633     else
634         sp->replot();
635     // Throughput and Window Scale graphs can hide base_graph_
636     if (base_graph_->visible())
637         tracer_->setGraph(base_graph_);
638 
639     // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
640     if (set_focus)
641         sp->setFocus();
642 }
643 
644 void TCPStreamDialog::showWidgetsForGraphType()
645 {
646     if (graph_.type == GRAPH_RTT) {
647         ui->bySeqNumberCheckBox->setVisible(true);
648     } else {
649         ui->bySeqNumberCheckBox->setVisible(false);
650     }
651     if (graph_.type == GRAPH_THROUGHPUT) {
652 #ifdef MA_1_SECOND
653         ui->maWindowSizeLabel->setVisible(true);
654         ui->maWindowSizeSpinBox->setVisible(true);
655 #else
656         ui->maWindowSizeLabel->setVisible(false);
657         ui->maWindowSizeSpinBox->setVisible(false);
658 #endif
659         ui->showSegLengthCheckBox->setVisible(true);
660         ui->showThroughputCheckBox->setVisible(true);
661         ui->showGoodputCheckBox->setVisible(true);
662     } else {
663         ui->maWindowSizeLabel->setVisible(false);
664         ui->maWindowSizeSpinBox->setVisible(false);
665         ui->showSegLengthCheckBox->setVisible(false);
666         ui->showThroughputCheckBox->setVisible(false);
667         ui->showGoodputCheckBox->setVisible(false);
668     }
669 
670     if (graph_.type == GRAPH_TSEQ_TCPTRACE) {
671         ui->selectSACKsCheckBox->setVisible(true);
672     } else {
673         ui->selectSACKsCheckBox->setVisible(false);
674     }
675 
676     if (graph_.type == GRAPH_WSCALE) {
677         ui->showRcvWinCheckBox->setVisible(true);
678         ui->showBytesOutCheckBox->setVisible(true);
679     } else {
680         ui->showRcvWinCheckBox->setVisible(false);
681         ui->showBytesOutCheckBox->setVisible(false);
682     }
683 }
684 
685 void TCPStreamDialog::zoomAxes(bool in)
686 {
687     QCustomPlot *sp = ui->streamPlot;
688     double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
689     double v_factor = sp->axisRect()->rangeZoomFactor(Qt::Vertical);
690 
691     if (!in) {
692         h_factor = pow(h_factor, -1);
693         v_factor = pow(v_factor, -1);
694     }
695 
696     sp->xAxis->scaleRange(h_factor, sp->xAxis->range().center());
697     sp->yAxis->scaleRange(v_factor, sp->yAxis->range().center());
698     sp->replot();
699 }
700 
701 void TCPStreamDialog::zoomXAxis(bool in)
702 {
703     QCustomPlot *sp = ui->streamPlot;
704     double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
705 
706     if (!in) {
707         h_factor = pow(h_factor, -1);
708     }
709 
710     sp->xAxis->scaleRange(h_factor, sp->xAxis->range().center());
711     sp->replot();
712 }
713 
714 void TCPStreamDialog::zoomYAxis(bool in)
715 {
716     QCustomPlot *sp = ui->streamPlot;
717     double v_factor = sp->axisRect()->rangeZoomFactor(Qt::Vertical);
718 
719     if (!in) {
720         v_factor = pow(v_factor, -1);
721     }
722 
723     sp->yAxis->scaleRange(v_factor, sp->yAxis->range().center());
724     sp->replot();
725 }
726 
727 void TCPStreamDialog::panAxes(int x_pixels, int y_pixels)
728 {
729     QCustomPlot *sp = ui->streamPlot;
730     double h_pan = 0.0;
731     double v_pan = 0.0;
732 
733     h_pan = sp->xAxis->range().size() * x_pixels / sp->xAxis->axisRect()->width();
734     v_pan = sp->yAxis->range().size() * y_pixels / sp->yAxis->axisRect()->height();
735     // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
736     if (h_pan) {
737         sp->xAxis->moveRange(h_pan);
738         sp->replot();
739     }
740     if (v_pan) {
741         sp->yAxis->moveRange(v_pan);
742         sp->replot();
743     }
744 }
745 
746 void TCPStreamDialog::resetAxes()
747 {
748     QCustomPlot *sp = ui->streamPlot;
749 
750     y_axis_xfrm_.reset();
751     double pixel_pad = 10.0; // per side
752 
753     sp->rescaleAxes(true);
754 //    tput_graph_->rescaleValueAxis(false, true);
755 //    base_graph_->rescaleAxes(false, true);
756 //    for (int i = 0; i < sp->graphCount(); i++) {
757 //        sp->graph(i)->rescaleValueAxis(false, true);
758 //    }
759 
760     double axis_pixels = sp->xAxis->axisRect()->width();
761     sp->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, sp->xAxis->range().center());
762 
763     if (sp->yAxis2->visible()) {
764         double ratio = sp->yAxis2->range().size() / sp->yAxis->range().size();
765         y_axis_xfrm_.translate(0.0, sp->yAxis2->range().lower - (sp->yAxis->range().lower * ratio));
766         y_axis_xfrm_.scale(1.0, ratio);
767     }
768 
769     axis_pixels = sp->yAxis->axisRect()->height();
770     sp->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, sp->yAxis->range().center());
771 
772     sp->replot();
773 }
774 
775 void TCPStreamDialog::fillStevens()
776 {
777     QString dlg_title = QString(tr("Sequence Numbers (Stevens)")) + streamDescription();
778     setWindowTitle(dlg_title);
779     title_->setText(dlg_title);
780 
781     QCustomPlot *sp = ui->streamPlot;
782     sp->yAxis->setLabel(sequence_number_label_);
783 
784     // True Stevens-style graphs don't have lines but I like them - gcc
785     base_graph_->setLineStyle(QCPGraph::lsStepLeft);
786 
787     QVector<double> rel_time, seq;
788     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
789         if (!compareHeaders(seg)) {
790             continue;
791         }
792 
793         double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
794         rel_time.append(ts - ts_offset_);
795         seq.append(seg->th_seq - seq_offset_);
796     }
797     base_graph_->setData(rel_time, seq);
798 }
799 
800 void TCPStreamDialog::fillTcptrace()
801 {
802     QString dlg_title = QString(tr("Sequence Numbers (tcptrace)")) + streamDescription();
803     setWindowTitle(dlg_title);
804     title_->setText(dlg_title);
805 
806     bool allow_sack_select = ui->selectSACKsCheckBox->isChecked();
807 
808     QCustomPlot *sp = ui->streamPlot;
809     sp->yAxis->setLabel(sequence_number_label_);
810 
811     base_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot));
812 
813     seg_graph_->setVisible(true);
814     seg_eb_->setVisible(true);
815     ack_graph_->setVisible(true);
816     sack_graph_->setVisible(true);
817     sack_eb_->setVisible(true);
818     sack2_graph_->setVisible(true);
819     sack2_eb_->setVisible(true);
820     rwin_graph_->setVisible(true);
821     dup_ack_graph_->setVisible(true);
822     zero_win_graph_->setVisible(true);
823 
824     QVector<double> pkt_time, pkt_seqnums;
825     QVector<double> sb_time, sb_center, sb_span;
826     QVector<double> ackrwin_time, ack, rwin;
827     QVector<double> sack_time, sack_center, sack_span;
828     QVector<double> sack2_time, sack2_center, sack2_span;
829     QVector<double> dup_ack_time, dup_ack;
830     QVector<double> zero_win_time, zero_win;
831 
832     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
833         double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_;
834         if (compareHeaders(seg)) {
835             double half = seg->th_seglen / 2.0;
836             double center = seg->th_seq - seq_offset_ + half;
837 
838             // Add forward direction to base_graph_ (to select data packets)
839             // Forward direction: seq + data
840             pkt_time.append(ts);
841             pkt_seqnums.append(center);
842 
843             // QCP doesn't have a segment graph type. For now, fake
844             // it with error bars.
845             if (seg->th_seglen > 0) {
846                 sb_time.append(ts);
847                 sb_center.append(center);
848                 sb_span.append(half);
849             }
850 
851             // Look for zero window sizes.
852             // Should match the TCP_A_ZERO_WINDOW test in packet-tcp.c.
853             if (seg->th_win == 0 && (seg->th_flags & (TH_RST|TH_FIN|TH_SYN)) == 0) {
854                 zero_win_time.append(ts);
855                 zero_win.append(center);
856             }
857         } else {
858             // Reverse direction: ACK + RWIN
859             if (! (seg->th_flags & TH_ACK)) {
860                 // SYNs and RSTs do not necessarily have ACKs
861                 continue;
862             }
863             double ackno = seg->th_ack - seq_offset_;
864             // add SACK segments to sack, sack2, and selectable packet graph
865             for (int i = 0; i < seg->num_sack_ranges; ++i) {
866                 double half = seg->sack_right_edge[i] - seg->sack_left_edge[i];
867                 half = half/2.0;
868                 double center = seg->sack_left_edge[i] - seq_offset_ + half;
869                 if (i == 0) {
870                     sack_time.append(ts);
871                     sack_center.append(center);
872                     sack_span.append(half);
873                     if (allow_sack_select) {
874                         pkt_time.append(ts);
875                         pkt_seqnums.append(center);
876                     }
877                 } else {
878                     sack2_time.append(ts);
879                     sack2_center.append(center);
880                     sack2_span.append(half);
881                 }
882             }
883             // If ackno is the same as our last one mark it as a duplicate.
884             //   (but don't mark window updates as duplicate acks)
885             if (ack.size() > 0 && ack.last() == ackno
886                   && rwin.last() == ackno + seg->th_win) {
887                 dup_ack_time.append(ts);
888                 dup_ack.append(ackno);
889             }
890             // Also add reverse packets to the ack_graph_
891             ackrwin_time.append(ts);
892             ack.append(ackno);
893             rwin.append(ackno + seg->th_win);
894         }
895     }
896     base_graph_->setData(pkt_time, pkt_seqnums, true);
897     ack_graph_->setData(ackrwin_time, ack, true);
898     seg_graph_->setData(sb_time, sb_center, true);
899     seg_eb_->setData(sb_span);
900     sack_graph_->setData(sack_time, sack_center, true);
901     sack_eb_->setData(sack_span);
902     sack2_graph_->setData(sack2_time, sack2_center, true);
903     sack2_eb_->setData(sack2_span);
904     rwin_graph_->setData(ackrwin_time, rwin, true);
905     dup_ack_graph_->setData(dup_ack_time, dup_ack, true);
906     zero_win_graph_->setData(zero_win_time, zero_win, true);
907 }
908 
909 // If the current implementation of incorporating SACKs in goodput calc
910 //   is slow, comment out the following line to ignore SACKs in goodput calc.
911 #define USE_SACKS_IN_GOODPUT_CALC
912 
913 #ifdef USE_SACKS_IN_GOODPUT_CALC
914 // to incorporate SACKED segments into goodput calculation,
915 //   need to keep track of all the SACK blocks we haven't yet
916 //   fully ACKed.
917 // I expect this to be _relatively_ small, so using vector to store
918 //   them.  If this performs badly, it can be refactored with std::list
919 //   or std::map.
920 typedef std::pair<guint32, guint32> sack_t;
921 typedef std::vector<sack_t> sack_list_t;
922 static inline bool compare_sack(const sack_t& s1, const sack_t& s2) {
923     return tcp_seq_before(s1.first, s2.first);
924 }
925 
926 // Helper function to adjust an acked seglen for goodput:
927 //   - removes previously sacked ranges from seglen (and from old_sacks),
928 //   - adds newly sacked ranges to seglen (and to old_sacks)
929 static void
930 goodput_adjust_for_sacks(guint32 *seglen, guint32 last_ack,
931                          sack_list_t& new_sacks, guint8 num_sack_ranges,
932                          sack_list_t& old_sacks) {
933 
934     // Step 1 - For any old_sacks acked by last_ack,
935     //   delete their acked length from seglen,
936     //   and remove the sack block (or portion)
937     //   from (sorted) old_sacks.
938     sack_list_t::iterator unacked = old_sacks.begin();
939     while (unacked != old_sacks.end()) {
940         // break on first sack not fully acked
941         if (tcp_seq_before(last_ack, unacked->second)) {
942             if (tcp_seq_after(last_ack, unacked->first)) {
943                 // partially acked - modify to remove acked part
944                 *seglen -= (last_ack - unacked->first);
945                 unacked->first = last_ack;
946             }
947             break;
948         }
949         // remove fully acked sacks from seglen and move on
950         //   (we'll actually remove from the list when loop is done)
951         *seglen -= (unacked->second - unacked->first);
952         ++unacked;
953     }
954     // actually remove all fully acked sacks from old_sacks list
955     if (unacked != old_sacks.begin())
956         old_sacks.erase(old_sacks.begin(), unacked);
957 
958     // Step 2 - for any new_sacks that precede last_ack,
959     //   ignore them. (These would generally be SACKed dup-acks of
960     //   a retransmitted seg).
961     //   [ in the unlikely case that any new SACK straddles last_ack,
962     //       the sack block will be modified to remove the acked portion ]
963     int next_new_idx = 0;
964     while (next_new_idx < num_sack_ranges) {
965         if (tcp_seq_before(last_ack, new_sacks[next_new_idx].second)) {
966             // if a new SACK block is unacked by its own packet, then it's
967             //   likely fully unacked, but let's check for partial ack anyway,
968             //   and truncate the SACK so that it's fully unacked:
969             if (tcp_seq_before(new_sacks[next_new_idx].first, last_ack))
970                 new_sacks[next_new_idx].first = last_ack;
971             break;
972         }
973         ++next_new_idx;
974     }
975 
976     // Step 3 - for any byte ranges in remaining new_sacks
977     //   that don't already exist in old_sacks, add
978     //   their length to seglen
979     //   and add that range (by extension, if possible) to
980     //   the list of old_sacks.
981 
982     sack_list_t::iterator next_old = old_sacks.begin();
983 
984     while (next_new_idx < num_sack_ranges &&
985            next_old != old_sacks.end()) {
986         sack_t* next_new = &new_sacks[next_new_idx];
987 
988         // Assumptions / Invariants:
989         //  - new and old lists are sorted
990         //  - span of leftmost to rightmost endpt. is less than half uint32 range
991         //      [ensures transitivity - e.g. before(a,b) and before(b,c) ==> before(a,c)]
992         //  - all SACKs are non-empty (sack.left before sack.right)
993         //  - adjacent SACKs in list always have a gap between them
994         //      (sack.right before next_sack.left)
995 
996         // Given these assumptions, and noting that there are only three
997         //   possible comparisons for a pair of points (before/equal/after),
998         //   there are only a few possible relative configurations
999         //   of next_old and next_new:
1000         // next_new:
1001         //                         [-------------)
1002         // next_old:
1003         //  1.             [---)
1004         //  2.             [-------)
1005         //  3.             [----------------)
1006         //  4.             [---------------------)
1007         //  5.             [----------------------------)
1008         //  6.                     [--------)
1009         //  7.                     [-------------)
1010         //  8.                     [--------------------)
1011         //  9.                          [---)
1012         // 10.                          [--------)
1013         // 11.                          [---------------)
1014         // 12.                                   [------)
1015         // 13.                                       [--)
1016 
1017         // Case 1: end of next_old is before beginning of next_new
1018         // next_new:
1019         //                         [-------------) ... <end>
1020         // next_old:
1021         //  1.             [---) ... <end>
1022         if (tcp_seq_before(next_old->second, next_new->first)) {
1023             // Actions:
1024             //   advance to the next sack in old_sacks
1025             ++next_old;
1026             //   retry from the top
1027             continue;
1028         }
1029 
1030         // Case 13: end of next_new is before beginning of next_old
1031         // next_new:
1032         //                         [-------------)   ... <end>
1033         // next_old:
1034         // 13.                                       [--) ... <end>
1035         if (tcp_seq_before(next_new->second, next_old->first)) {
1036             // Actions:
1037             //   add then entire length of next_new into seglen
1038             *seglen += (next_new->second - next_new->first);
1039             //   insert next_new before next_old in old_sacks
1040             // (be sure to save and restore next_old iterator around insert!)
1041             int next_old_idx = int(next_old - old_sacks.begin());
1042             old_sacks.insert(next_old, *next_new);
1043             next_old = old_sacks.begin() + next_old_idx + 1;
1044             //   advance to the next remaining sack in new_sacks
1045             ++next_new_idx;
1046             //   retry from the top
1047             continue;
1048         }
1049 
1050         // Remaining possible configurations:
1051         // next_new:
1052         //                         [-------------)
1053         // next_old:
1054         //  2.             [-------)
1055         //  3.             [----------------)
1056         //  4.             [---------------------)
1057         //  5.             [----------------------------)
1058         //  6.                     [--------)
1059         //  7.                     [-------------)
1060         //  8.                     [--------------------)
1061         //  9.                          [---)
1062         // 10.                          [--------)
1063         // 11.                          [---------------)
1064         // 12.                                   [------)
1065 
1066         // Cases 2,3,6,9: end of next_old is before end of next_new
1067         // next_new:
1068         //                         [-------------)
1069         // next_old:
1070         //  2.             [-------)
1071         //  3.             [----------------)
1072         //  6.                     [--------)
1073         //  9.                          [---)
1074         // Actions:
1075         //   until end of next_old is equal or after end of next_new,
1076         //     repeatedly extend next_old, coalescing with next_next_old
1077         //     if necessary.  (and add extended bytes to seglen)
1078         while (tcp_seq_before(next_old->second, next_new->second)) {
1079             // if end of next_new doesn't collide with start of next_next_old,
1080             if (((next_old+1) == old_sacks.end()) ||
1081                 tcp_seq_before(next_new->second, (next_old + 1)->first)) {
1082                 // extend end of next_old up to end of next_new,
1083                 // adding extended bytes to seglen
1084                 *seglen += (next_new->second - next_old->second);
1085                 next_old->second = next_new->second;
1086             }
1087             // otherwise, coalesce next_old with next_next_old
1088             else {
1089                 // add bytes to close gap between sacks to seglen
1090                 *seglen += ((next_old + 1)->first - next_old->second);
1091                 // coalesce next_next_old into next_old
1092                 next_old->second = (next_old + 1)->second;
1093                 old_sacks.erase(next_old + 1);
1094             }
1095         }
1096         // This operation turns:
1097         //   Cases 2 and 3 into Case 4 or 5
1098         //   Case 6 into Case 7
1099         //   Case 9 into Case 10
1100         // Leaving:
1101 
1102         // Remaining possible configurations:
1103         // next_new:
1104         //                         [-------------)
1105         // next_old:
1106         //  4.             [---------------------)
1107         //  5.             [----------------------------)
1108         //  7.                     [-------------)
1109         //  8.                     [--------------------)
1110         // 10.                          [--------)
1111         // 11.                          [---------------)
1112         // 12.                                   [------)
1113 
1114         // Cases 10,11,12: start of next_new is before start of next_old
1115         // next_new:
1116         //                         [-------------)
1117         // next_old:
1118         // 10.                          [--------)
1119         // 11.                          [---------------)
1120         // 12.                                   [------)
1121         if (tcp_seq_before(next_new->first, next_old->first)) {
1122             // Actions:
1123             //   add the unaccounted bytes in next_new to seglen
1124             *seglen += (next_old->first - next_new->first);
1125             //   then pull the start of next_old back to the start of next_new
1126             next_old->first = next_new->first;
1127         }
1128         // This operation turns:
1129         //   Case 10 into Case 7
1130         //   Cases 11 and 12 into Case 8
1131         // Leaving:
1132 
1133         // Remaining possible configurations:
1134         // next_new:
1135         //                         [-------------)
1136         // next_old:
1137         //  4.             [---------------------)
1138         //  5.             [----------------------------)
1139         //  7.                     [-------------)
1140         //  8.                     [--------------------)
1141 
1142         // In these cases, the bytes in next_new are fully accounted
1143         //   by the bytes in next_old, so we can move on to look at
1144         //   the next sack block in new_sacks
1145         ++next_new_idx;
1146     }
1147     // Conditions for leaving loop:
1148     //   - we processed all remaining new_sacks - nothing left to do
1149     //     (next_new_idx == num_sack_ranges)
1150     //  OR
1151     //   - all remaining new_sacks start at least one byte after
1152     //       the rightmost edge of the last old_sack
1153     //     (meaning we can just add the remaining new_sacks to old_sacks list,
1154     //      and add them directly to the goodput seglen)
1155     while (next_new_idx < num_sack_ranges) {
1156         sack_t* next_new = &new_sacks[next_new_idx];
1157         *seglen += (next_new->second - next_new->first);
1158         old_sacks.push_back(*next_new);
1159         ++next_new_idx;
1160     }
1161 }
1162 #endif // USE_SACKS_IN_GOODPUT_CALC
1163 
1164 void TCPStreamDialog::fillThroughput()
1165 {
1166     QString dlg_title = QString(tr("Throughput")) + streamDescription();
1167 #ifdef MA_1_SECOND
1168     dlg_title.append(tr(" (MA)"));
1169 #else
1170     dlg_title.append(QString(tr(" (%1 Segment MA)")).arg(moving_avg_period_));
1171 #endif
1172     setWindowTitle(dlg_title);
1173     title_->setText(dlg_title);
1174 
1175     QCustomPlot *sp = ui->streamPlot;
1176     sp->yAxis->setLabel(segment_length_label_);
1177     sp->yAxis2->setLabel(average_throughput_label_);
1178     sp->yAxis2->setLabelColor(QColor(graph_color_2));
1179     sp->yAxis2->setTickLabelColor(QColor(graph_color_2));
1180     sp->yAxis2->setVisible(true);
1181 
1182     base_graph_->setVisible(ui->showSegLengthCheckBox->isChecked());
1183     tput_graph_->setVisible(ui->showThroughputCheckBox->isChecked());
1184     goodput_graph_->setVisible(ui->showGoodputCheckBox->isChecked());
1185 
1186 #ifdef MA_1_SECOND
1187     if (!graph_.segments) {
1188 #else
1189     if (!graph_.segments || !graph_.segments->next) {
1190 #endif
1191         dlg_title.append(tr(" [not enough data]"));
1192         return;
1193     }
1194 
1195     QVector<double> seg_rel_times, ack_rel_times;
1196     QVector<double> seg_lens, ack_lens;
1197     QVector<double> tput_times, gput_times;
1198     QVector<double> tputs, gputs;
1199     int oldest_seg = 0, oldest_ack = 0;
1200     guint64 seg_sum = 0, ack_sum = 0;
1201     guint32 seglen = 0;
1202 
1203 #ifdef USE_SACKS_IN_GOODPUT_CALC
1204     // to incorporate SACKED segments into goodput calculation,
1205     //   need to keep track of all the SACK blocks we haven't yet
1206     //   fully ACKed.
1207     sack_list_t old_sacks, new_sacks;
1208     new_sacks.reserve(MAX_TCP_SACK_RANGES);
1209     // statically allocate current_sacks vector
1210     //   [ std::array might be better, but that is C++11 ]
1211     for (int i = 0; i < MAX_TCP_SACK_RANGES; ++i) {
1212         new_sacks.push_back(sack_t(0,0));
1213     }
1214     old_sacks.reserve(2*MAX_TCP_SACK_RANGES);
1215 #endif // USE_SACKS_IN_GOODPUT_CALC
1216 
1217     // need first acked sequence number to jump-start
1218     //    computation of acked bytes per packet
1219     guint32 last_ack = 0;
1220     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1221         // first reverse packet with ACK flag tells us first acked sequence #
1222         if (!compareHeaders(seg) && (seg->th_flags & TH_ACK)) {
1223             last_ack = seg->th_ack;
1224             break;
1225         }
1226     }
1227     // Financial charts don't show MA data until a full period has elapsed.
1228     //  [ NOTE - this is because they assume that there's old data that they
1229     //      don't have access to - but in our case we know that there's NO
1230     //      data prior to the first packet in the stream - so it's fine to
1231     //      spit out the MA immediately... ]
1232     // The Rosetta Code MA examples start spitting out values immediately.
1233     // For now use not-really-correct initial values just to keep our vector
1234     // lengths the same.
1235 #ifdef MA_1_SECOND
1236     // NOTE that for the time-based MA case, you certainly can start with the
1237     //  first segment!
1238     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1239 #else
1240     for (struct segment *seg = graph_.segments->next; seg != NULL; seg = seg->next) {
1241 #endif
1242         bool is_forward_seg = compareHeaders(seg);
1243         QVector<double>& r_pkt_times = is_forward_seg ? seg_rel_times : ack_rel_times;
1244         QVector<double>& r_lens = is_forward_seg ? seg_lens : ack_lens;
1245         QVector<double>& r_Xput_times = is_forward_seg ? tput_times : gput_times;
1246         QVector<double>& r_Xputs = is_forward_seg ? tputs : gputs;
1247         int& r_oldest = is_forward_seg ? oldest_seg : oldest_ack;
1248         guint64& r_sum = is_forward_seg ? seg_sum : ack_sum;
1249 
1250         double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_;
1251 
1252         if (is_forward_seg) {
1253             seglen = seg->th_seglen;
1254         } else {
1255             if ((seg->th_flags & TH_ACK) &&
1256                 tcp_seq_eq_or_after(seg->th_ack, last_ack)) {
1257                 seglen = seg->th_ack - last_ack;
1258                 last_ack = seg->th_ack;
1259 #ifdef USE_SACKS_IN_GOODPUT_CALC
1260                 // copy any sack_ranges into new_sacks, and sort.
1261                 for (int i = 0; i < seg->num_sack_ranges; ++i) {
1262                     new_sacks[i].first = seg->sack_left_edge[i];
1263                     new_sacks[i].second = seg->sack_right_edge[i];
1264                 }
1265                 std::sort(new_sacks.begin(),
1266                           new_sacks.begin() + seg->num_sack_ranges,
1267                           compare_sack);
1268 
1269                 // adjust the seglen based on new and old sacks,
1270                 //   and update the old_sacks list
1271                 goodput_adjust_for_sacks(&seglen, last_ack,
1272                                          new_sacks, seg->num_sack_ranges,
1273                                          old_sacks);
1274 #endif // USE_SACKS_IN_GOODPUT_CALC
1275             } else {
1276                 seglen = 0;
1277             }
1278         }
1279 
1280         r_pkt_times.append(ts);
1281         r_lens.append(seglen);
1282 
1283 #ifdef MA_1_SECOND
1284         while (r_oldest < r_pkt_times.size() && ts - r_pkt_times[r_oldest] > ma_window_size_) {
1285             r_sum -= r_lens[r_oldest];
1286             // append points where a packet LEAVES the MA window
1287             //   (as well as, below, where they ENTER the MA window)
1288             r_Xputs.append(r_sum * 8.0 / ma_window_size_);
1289             r_Xput_times.append(r_pkt_times[r_oldest] + ma_window_size_);
1290             r_oldest++;
1291         }
1292 #else
1293         if (r_lens.size() > moving_avg_period_) {
1294             r_sum -= r_lens[r_oldest];
1295             r_oldest++;
1296         }
1297 #endif
1298 
1299         // av_Xput computes Xput, i.e.:
1300         //    throughput for forward packets
1301         //    goodput for reverse packets
1302         double av_Xput;
1303         r_sum += seglen;
1304 #ifdef MA_1_SECOND
1305         // for time-based MA, delta_t is constant
1306         av_Xput = r_sum * 8.0 / ma_window_size_;
1307 #else
1308         double dtime = 0.0;
1309         if (r_oldest > 0)
1310             dtime = ts - r_pkt_times[r_oldest-1];
1311         if (dtime > 0.0) {
1312             av_Xput = r_sum * 8.0 / dtime;
1313         } else {
1314             av_Xput = 0.0;
1315         }
1316 #endif
1317 
1318         // Add a data point only if our time window has advanced. Otherwise
1319         // update the most recent point. (We might want to show a warning
1320         // for out-of-order packets.)
1321         if (r_Xput_times.size() > 0 && ts <= r_Xput_times.last()) {
1322             r_Xputs[r_Xputs.size() - 1] = av_Xput;
1323         } else {
1324             r_Xputs.append(av_Xput);
1325             r_Xput_times.append(ts);
1326         }
1327     }
1328     base_graph_->setData(seg_rel_times, seg_lens);
1329     tput_graph_->setData(tput_times, tputs);
1330     goodput_graph_->setData(gput_times, gputs);
1331 }
1332 
1333 // rtt_selectively_ack_range:
1334 //    "Helper" function for fillRoundTripTime
1335 //    given an rtt_unack list, two pointers to a range of segments in the list,
1336 //    and the [left,right) edges of a SACK block, selectively ACKs the range
1337 //    from "begin" to "end" - possibly splitting one segment in the range
1338 //    into two (and relinking the new segment in order after the first)
1339 //
1340 // Assumptions:
1341 //    "begin must be non-NULL
1342 //    "begin" must precede "end" (or "end" must be NULL)
1343 //    [ there are minor optimizations that could be added if
1344 //        the range from "begin" to "end" are in sequence number order.
1345 //        (this function would preserve that as an invariant). ]
1346 static struct rtt_unack *
1347 rtt_selectively_ack_range(QVector<double>& x_vals, bool bySeqNumber,
1348                     QVector<double>& rtt,
1349                     struct rtt_unack **list,
1350                     struct rtt_unack *begin, struct rtt_unack *end,
1351                     unsigned int left, unsigned int right, double rt_val) {
1352     struct rtt_unack *cur, *next;
1353     // sanity check:
1354     if (tcp_seq_eq_or_after(left, right))
1355         return begin;
1356     // real work:
1357     for (cur = begin; cur != end; cur = next) {
1358         next = cur->next;
1359         // check #1: does [left,right) intersect current unack at all?
1360         //   (if not, we can just move on to the next unack)
1361         if (tcp_seq_eq_or_after(cur->seqno, right) ||
1362             tcp_seq_eq_or_after(left, cur->end_seqno)) {
1363             // no intersection - just skip this.
1364             continue;
1365         }
1366         // yes, we intersect!
1367         int left_end_acked = tcp_seq_eq_or_after(cur->seqno, left);
1368         int right_end_acked = tcp_seq_eq_or_after(right, cur->end_seqno);
1369         // check #2 - did we fully ack the current unack?
1370         //   (if so, we can delete it and move on)
1371         if (left_end_acked && right_end_acked) {
1372             // ACK the whole segment
1373             if (bySeqNumber) {
1374                 x_vals.append(cur->seqno);
1375             } else {
1376                 x_vals.append(cur->time);
1377             }
1378             rtt.append((rt_val - cur->time) * 1000.0);
1379             // in this case, we will delete current unack
1380             // [ update "begin" if necessary - we will return it to the
1381             //     caller to let them know we deleted it ]
1382             if (cur == begin)
1383                 begin = next;
1384              rtt_delete_unack_from_list(list, cur);
1385              continue;
1386         }
1387         // check #3 - did we ACK the left-hand side of the current unack?
1388         //   (if so, we can just modify it and move on)
1389         if (left_end_acked) { // and right_end_not_acked
1390             // ACK the left end
1391             if (bySeqNumber) {
1392                 x_vals.append(cur->seqno);
1393             } else {
1394                 x_vals.append(cur->time);
1395             }
1396             rtt.append((rt_val - cur->time) * 1000.0);
1397             // in this case, "right" marks the start of remaining bytes
1398             cur->seqno = right;
1399             continue;
1400         }
1401         // check #4 - did we ACK the right-hand side of the current unack?
1402         //   (if so, we can just modify it and move on)
1403         if (right_end_acked) { // and left_end_not_acked
1404             // ACK the right end
1405             if (bySeqNumber) {
1406                 x_vals.append(left);
1407             } else {
1408                 x_vals.append(cur->time);
1409             }
1410             rtt.append((rt_val - cur->time) * 1000.0);
1411             // in this case, "left" is just beyond the remaining bytes
1412             cur->end_seqno = left;
1413             continue;
1414         }
1415         // at this point, we know:
1416         //   - the SACK block does intersect this unack, but
1417         //   - it does not intersect the left or right endpoints
1418         // Therefore, it must intersect the middle, so we must split the unack
1419         //   into left and right unacked segments:
1420         // ACK the SACK block
1421         if (bySeqNumber) {
1422             x_vals.append(left);
1423         } else {
1424             x_vals.append(cur->time);
1425         }
1426         rtt.append((rt_val - cur->time) * 1000.0);
1427         // then split cur into two unacked segments
1428         //   (linking the right-hand unack after the left)
1429         cur->next = rtt_get_new_unack(cur->time, right, cur->end_seqno - right);
1430         cur->next->next = next;
1431         cur->end_seqno = left;
1432     }
1433     return begin;
1434 }
1435 
1436 void TCPStreamDialog::fillRoundTripTime()
1437 {
1438     QString dlg_title = QString(tr("Round Trip Time")) + streamDescription();
1439     setWindowTitle(dlg_title);
1440     title_->setText(dlg_title);
1441 
1442     QCustomPlot *sp = ui->streamPlot;
1443     bool bySeqNumber = ui->bySeqNumberCheckBox->isChecked();
1444 
1445     if (bySeqNumber) {
1446         sequence_num_map_.clear();
1447         sp->xAxis->setLabel(sequence_number_label_);
1448         sp->xAxis->setNumberFormat("f");
1449         sp->xAxis->setNumberPrecision(0);
1450     }
1451     sp->yAxis->setLabel(round_trip_time_ms_label_);
1452     sp->yAxis->setNumberFormat("gb");
1453     sp->yAxis->setNumberPrecision(3);
1454 
1455     base_graph_->setLineStyle(QCPGraph::lsLine);
1456 
1457     QVector<double> x_vals, rtt;
1458     guint32 seq_base = 0;
1459     struct rtt_unack *unack_list = NULL, *u = NULL;
1460     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1461         if (compareHeaders(seg)) {
1462             seq_base = seg->th_seq;
1463             break;
1464         }
1465     }
1466     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1467         if (compareHeaders(seg)) {
1468             guint32 seqno = seg->th_seq - seq_base;
1469             if (seg->th_seglen && !rtt_is_retrans(unack_list, seqno)) {
1470                 double rt_val = seg->rel_secs + seg->rel_usecs / 1000000.0;
1471                 rt_val -= ts_offset_;
1472                 u = rtt_get_new_unack(rt_val, seqno, seg->th_seglen);
1473                 if (!u) {
1474                     // make sure to free list before returning!
1475                     rtt_destroy_unack_list(&unack_list);
1476                     return;
1477                 }
1478                 rtt_put_unack_on_list(&unack_list, u);
1479             }
1480         } else {
1481             guint32 ack_no = seg->th_ack - seq_base;
1482             double rt_val = seg->rel_secs + seg->rel_usecs / 1000000.0;
1483             rt_val -= ts_offset_;
1484             struct rtt_unack *v;
1485 
1486             for (u = unack_list; u; u = v) {
1487                 if (tcp_seq_after(ack_no, u->seqno)) {
1488                     // full or partial ack of seg by ack_no
1489                     if (bySeqNumber) {
1490                         x_vals.append(u->seqno);
1491                         sequence_num_map_.insert(u->seqno, seg);
1492                     } else {
1493                         x_vals.append(u->time);
1494                     }
1495                     rtt.append((rt_val - u->time) * 1000.0);
1496                     if (tcp_seq_eq_or_after(ack_no, u->end_seqno)) {
1497                         // fully acked segment - nothing more to see here
1498                         v = u->next;
1499                         rtt_delete_unack_from_list(&unack_list, u);
1500                         // no need to compare SACK blocks for fully ACKed seg
1501                         continue;
1502                     } else {
1503                         // partial ack of GSO seg
1504                         u->seqno = ack_no;
1505                         // (keep going - still need to compare SACK blocks...)
1506                     }
1507                 }
1508                 v = u->next;
1509                 // selectively acking u more than once
1510                 //   can shatter it into multiple intervals.
1511                 //   If we link those back into the list between u and v,
1512                 //   then each subsequent SACK selectively ACKs that range.
1513                 for (int i = 0; i < seg->num_sack_ranges; ++i) {
1514                     guint32 left = seg->sack_left_edge[i] - seq_base;
1515                     guint32 right = seg->sack_right_edge[i] - seq_base;
1516                     u = rtt_selectively_ack_range(x_vals, bySeqNumber, rtt,
1517                                                   &unack_list, u, v,
1518                                                   left, right, rt_val);
1519                     // if range is empty after selective ack, we can
1520                     //   skip the rest of the SACK blocks
1521                     if (u == v) break;
1522                 }
1523             }
1524         }
1525     }
1526     // it's possible there's still unacked segs - so be sure to free list!
1527     rtt_destroy_unack_list(&unack_list);
1528     base_graph_->setData(x_vals, rtt);
1529 }
1530 
1531 void TCPStreamDialog::fillWindowScale()
1532 {
1533     QString dlg_title = QString(tr("Window Scaling")) + streamDescription();
1534     setWindowTitle(dlg_title);
1535     title_->setText(dlg_title);
1536 
1537     QCustomPlot *sp = ui->streamPlot;
1538     // use base_graph_ to represent unacked window size
1539     //  (estimate of congestion window)
1540     base_graph_->setLineStyle(QCPGraph::lsStepLeft);
1541     // use rwin_graph_ here to show rwin window scale
1542     //  (derived from ACK packets)
1543     base_graph_->setVisible(ui->showBytesOutCheckBox->isChecked());
1544     rwin_graph_->setVisible(ui->showRcvWinCheckBox->isChecked());
1545 
1546     QVector<double> rel_time, win_size;
1547     QVector<double> cwnd_time, cwnd_size;
1548     guint32 last_ack = 0;
1549     bool found_first_ack = false;
1550     for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1551         double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
1552 
1553         // The receive window that applies to this flow comes
1554         //   from packets in the opposite direction
1555         if (compareHeaders(seg)) {
1556             // compute bytes_in_flight for cwnd graph
1557             guint32 end_seq = seg->th_seq + seg->th_seglen;
1558             if (found_first_ack &&
1559                 tcp_seq_eq_or_after(end_seq, last_ack)) {
1560                 cwnd_time.append(ts - ts_offset_);
1561                 cwnd_size.append((double)(end_seq - last_ack));
1562             }
1563         } else {
1564             // packet in opposite direction - has advertised rwin
1565             guint16 flags = seg->th_flags;
1566 
1567             if ((flags & (TH_SYN|TH_RST)) == 0) {
1568                 rel_time.append(ts - ts_offset_);
1569                 win_size.append(seg->th_win);
1570             }
1571             if ((flags & (TH_ACK)) != 0) {
1572                 // use this to update last_ack
1573                 if (!found_first_ack ||
1574                     tcp_seq_eq_or_after(seg->th_ack, last_ack)) {
1575                     last_ack = seg->th_ack;
1576                     found_first_ack = true;
1577                 }
1578             }
1579         }
1580     }
1581     base_graph_->setData(cwnd_time, cwnd_size);
1582     rwin_graph_->setData(rel_time, win_size);
1583     sp->yAxis->setLabel(window_size_label_);
1584 }
1585 
1586 QString TCPStreamDialog::streamDescription()
1587 {
1588     QString description(tr(" for %1:%2 %3 %4:%5")
1589             .arg(address_to_qstring(&graph_.src_address))
1590             .arg(graph_.src_port)
1591             .arg(UTF8_RIGHTWARDS_ARROW)
1592             .arg(address_to_qstring(&graph_.dst_address))
1593             .arg(graph_.dst_port));
1594     return description;
1595 }
1596 
1597 bool TCPStreamDialog::compareHeaders(segment *seg)
1598 {
1599     return (compare_headers(&graph_.src_address, &graph_.dst_address,
1600                          graph_.src_port, graph_.dst_port,
1601                          &seg->ip_src, &seg->ip_dst,
1602                          seg->th_sport, seg->th_dport,
1603                             COMPARE_CURR_DIR));
1604 }
1605 
1606 void TCPStreamDialog::toggleTracerStyle(bool force_default)
1607 {
1608     if (!tracer_->visible() && !force_default) return;
1609 
1610     QPen sp_pen = ui->streamPlot->graph(0)->pen();
1611     QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair;
1612     QPen tr_pen = QPen(tracer_->pen());
1613     QColor tr_color = sp_pen.color();
1614 
1615     if (force_default || tracer_->style() != QCPItemTracer::tsCircle) {
1616         tstyle = QCPItemTracer::tsCircle;
1617         tr_color.setAlphaF(1.0);
1618         tr_pen.setWidthF(1.5);
1619     } else {
1620         tr_color.setAlphaF(0.5);
1621         tr_pen.setWidthF(1.0);
1622     }
1623 
1624     tracer_->setStyle(tstyle);
1625     tr_pen.setColor(tr_color);
1626     tracer_->setPen(tr_pen);
1627     ui->streamPlot->replot();
1628 }
1629 
1630 QRectF TCPStreamDialog::getZoomRanges(QRect zoom_rect)
1631 {
1632     QRectF zoom_ranges = QRectF();
1633 
1634     QCustomPlot *sp = ui->streamPlot;
1635     QRect zr = zoom_rect.normalized();
1636 
1637     if (zr.width() < min_zoom_pixels_ && zr.height() < min_zoom_pixels_) {
1638         return zoom_ranges;
1639     }
1640 
1641     QRect ar = sp->axisRect()->rect();
1642     if (ar.intersects(zr)) {
1643         QRect zsr = ar.intersected(zr);
1644         zoom_ranges.setX(sp->xAxis->range().lower
1645                          + sp->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width());
1646         zoom_ranges.setWidth(sp->xAxis->range().size() * zsr.width() / ar.width());
1647 
1648         // QRects grow down
1649         zoom_ranges.setY(sp->yAxis->range().lower
1650                          + sp->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height());
1651         zoom_ranges.setHeight(sp->yAxis->range().size() * zsr.height() / ar.height());
1652     }
1653     return zoom_ranges;
1654 }
1655 
1656 void TCPStreamDialog::graphClicked(QMouseEvent *event)
1657 {
1658     QCustomPlot *sp = ui->streamPlot;
1659 
1660     // mouse press on graph should reset focus to graph
1661     sp->setFocus();
1662 
1663     if (event->button() == Qt::RightButton) {
1664         // XXX We should find some way to get streamPlot to handle a
1665         // contextMenuEvent instead.
1666         ctx_menu_.exec(event->globalPos());
1667     } else  if (mouse_drags_) {
1668         if (sp->axisRect()->rect().contains(event->pos())) {
1669             sp->setCursor(QCursor(Qt::ClosedHandCursor));
1670         }
1671         on_actionGoToPacket_triggered();
1672     } else {
1673         if (!rubber_band_) {
1674             rubber_band_ = new QRubberBand(QRubberBand::Rectangle, sp);
1675         }
1676         rb_origin_ = event->pos();
1677         rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
1678         rubber_band_->show();
1679     }
1680 }
1681 
1682 void TCPStreamDialog::axisClicked(QCPAxis *axis, QCPAxis::SelectablePart, QMouseEvent *)
1683 {
1684     QCustomPlot *sp = ui->streamPlot;
1685 
1686     if (axis == sp->xAxis) {
1687         switch (graph_.type) {
1688         case GRAPH_THROUGHPUT:
1689         case GRAPH_TSEQ_STEVENS:
1690         case GRAPH_TSEQ_TCPTRACE:
1691         case GRAPH_WSCALE:
1692         case GRAPH_RTT:
1693             ts_origin_conn_ = ts_origin_conn_ ? false : true;
1694             fillGraph();
1695             break;
1696         default:
1697             break;
1698         }
1699     } else if (axis == sp->yAxis) {
1700         switch (graph_.type) {
1701         case GRAPH_TSEQ_STEVENS:
1702         case GRAPH_TSEQ_TCPTRACE:
1703             seq_origin_zero_ = seq_origin_zero_ ? false : true;
1704             fillGraph();
1705             break;
1706         default:
1707             break;
1708         }
1709     }
1710 }
1711 
1712 // Setting mouseTracking on our streamPlot may not be as reliable
1713 // as we need. If it's not we might want to poll the mouse position
1714 // using a QTimer instead.
1715 void TCPStreamDialog::mouseMoved(QMouseEvent *event)
1716 {
1717     QCustomPlot *sp = ui->streamPlot;
1718     Qt::CursorShape shape = Qt::ArrowCursor;
1719     if (event) {
1720         if (event->buttons().testFlag(Qt::LeftButton)) {
1721             if (mouse_drags_) {
1722                 shape = Qt::ClosedHandCursor;
1723             } else {
1724                 shape = Qt::CrossCursor;
1725             }
1726         } else {
1727             if (sp->axisRect()->rect().contains(event->pos())) {
1728                 if (mouse_drags_) {
1729                     shape = Qt::OpenHandCursor;
1730                 } else {
1731                     shape = Qt::CrossCursor;
1732                 }
1733             }
1734         }
1735     }
1736     sp->setCursor(QCursor(shape));
1737 
1738     QString hint = "<small><i>";
1739     if (mouse_drags_) {
1740         double tr_key = tracer_->position->key();
1741         struct segment *packet_seg = NULL;
1742         packet_num_ = 0;
1743 
1744         // XXX If we have multiple packets with the same timestamp tr_key
1745         // may not return the packet we want. It might be possible to fudge
1746         // unique keys using nextafter().
1747         if (event && tracer_->graph() && tracer_->position->axisRect()->rect().contains(event->pos())) {
1748             switch (graph_.type) {
1749             case GRAPH_TSEQ_STEVENS:
1750             case GRAPH_TSEQ_TCPTRACE:
1751             case GRAPH_THROUGHPUT:
1752             case GRAPH_WSCALE:
1753                 packet_seg = time_stamp_map_.value(tr_key, NULL);
1754                 break;
1755             case GRAPH_RTT:
1756                 if (ui->bySeqNumberCheckBox->isChecked())
1757                     packet_seg = sequence_num_map_.value(tr_key, NULL);
1758                 else
1759                     packet_seg = time_stamp_map_.value(tr_key, NULL);
1760             default:
1761                 break;
1762             }
1763         }
1764 
1765         if (!packet_seg) {
1766             tracer_->setVisible(false);
1767             hint += "Hover over the graph for details. " + stream_desc_ + "</i></small>";
1768             ui->hintLabel->setText(hint);
1769             ui->streamPlot->replot();
1770             return;
1771         }
1772 
1773         tracer_->setVisible(true);
1774         packet_num_ = packet_seg->num;
1775         hint += tr("%1 %2 (%3s len %4 seq %5 ack %6 win %7)")
1776                 .arg(cap_file_ ? tr("Click to select packet") : tr("Packet"))
1777                 .arg(packet_num_)
1778                 .arg(QString::number(packet_seg->rel_secs + packet_seg->rel_usecs / 1000000.0, 'g', 4))
1779                 .arg(packet_seg->th_seglen)
1780                 .arg(packet_seg->th_seq)
1781                 .arg(packet_seg->th_ack)
1782                 .arg(packet_seg->th_win);
1783         tracer_->setGraphKey(ui->streamPlot->xAxis->pixelToCoord(event->pos().x()));
1784         sp->replot();
1785     } else {
1786         if (rubber_band_ && rubber_band_->isVisible() && event) {
1787             rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
1788             QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1789             if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1790                 hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4")
1791                         .arg(zoom_ranges.x())
1792                         .arg(zoom_ranges.x() + zoom_ranges.width())
1793                         .arg(zoom_ranges.y())
1794                         .arg(zoom_ranges.y() + zoom_ranges.height());
1795             } else {
1796                 hint += tr("Unable to select range.");
1797             }
1798         } else {
1799             hint += tr("Click to select a portion of the graph.");
1800         }
1801     }
1802     hint += " " + stream_desc_ + "</i></small>";
1803     ui->hintLabel->setText(hint);
1804 }
1805 
1806 void TCPStreamDialog::mouseReleased(QMouseEvent *event)
1807 {
1808     if (rubber_band_ && rubber_band_->isVisible()) {
1809         rubber_band_->hide();
1810         if (!mouse_drags_) {
1811             QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1812             if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1813                 QCustomPlot *sp = ui->streamPlot;
1814                 sp->xAxis->setRangeLower(zoom_ranges.x());
1815                 sp->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width());
1816                 sp->yAxis->setRangeLower(zoom_ranges.y());
1817                 sp->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height());
1818                 sp->replot();
1819             }
1820         }
1821     } else if (ui->streamPlot->cursor().shape() == Qt::ClosedHandCursor) {
1822         ui->streamPlot->setCursor(QCursor(Qt::OpenHandCursor));
1823     }
1824 }
1825 
1826 void TCPStreamDialog::transformYRange(const QCPRange &y_range1)
1827 {
1828     if (y_axis_xfrm_.isIdentity()) return;
1829 
1830     QCustomPlot *sp = ui->streamPlot;
1831     QLineF yp1 = QLineF(1.0, y_range1.lower, 1.0, y_range1.upper);
1832     QLineF yp2 = y_axis_xfrm_.map(yp1);
1833 
1834     sp->yAxis2->setRangeUpper(yp2.y2());
1835     sp->yAxis2->setRangeLower(yp2.y1());
1836 }
1837 
1838 // XXX - We have similar code in io_graph_dialog and packet_diagram. Should this be a common routine?
1839 void TCPStreamDialog::on_buttonBox_accepted()
1840 {
1841     QString file_name, extension;
1842     QDir path(wsApp->lastOpenDir());
1843     QString pdf_filter = tr("Portable Document Format (*.pdf)");
1844     QString png_filter = tr("Portable Network Graphics (*.png)");
1845     QString bmp_filter = tr("Windows Bitmap (*.bmp)");
1846     // Gaze upon my beautiful graph with lossy artifacts!
1847     QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
1848     QString filter = QString("%1;;%2;;%3;;%4")
1849             .arg(pdf_filter)
1850             .arg(png_filter)
1851             .arg(bmp_filter)
1852             .arg(jpeg_filter);
1853 
1854     file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
1855                                              path.canonicalPath(), filter, &extension);
1856 
1857     if (file_name.length() > 0) {
1858         bool save_ok = false;
1859         if (extension.compare(pdf_filter) == 0) {
1860             save_ok = ui->streamPlot->savePdf(file_name);
1861         } else if (extension.compare(png_filter) == 0) {
1862             save_ok = ui->streamPlot->savePng(file_name);
1863         } else if (extension.compare(bmp_filter) == 0) {
1864             save_ok = ui->streamPlot->saveBmp(file_name);
1865         } else if (extension.compare(jpeg_filter) == 0) {
1866             save_ok = ui->streamPlot->saveJpg(file_name);
1867         }
1868         // else error dialog?
1869         if (save_ok) {
1870             wsApp->setLastOpenDirFromFilename(file_name);
1871         }
1872     }
1873 }
1874 
1875 void TCPStreamDialog::on_graphTypeComboBox_currentIndexChanged(int index)
1876 {
1877     if (index < 0) return;
1878     graph_.type = static_cast<tcp_graph_type>(ui->graphTypeComboBox->itemData(index).toInt());
1879     showWidgetsForGraphType();
1880 
1881     fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
1882 }
1883 
1884 void TCPStreamDialog::on_resetButton_clicked()
1885 {
1886     resetAxes();
1887 }
1888 
1889 void TCPStreamDialog::setCaptureFile(capture_file *cf)
1890 {
1891     if (!cf) { // We only want to know when the file closes.
1892         cap_file_ = NULL;
1893     }
1894 }
1895 
1896 void TCPStreamDialog::updateGraph()
1897 {
1898     graph_updater_.doUpdate();
1899 }
1900 
1901 void TCPStreamDialog::on_streamNumberSpinBox_valueChanged(int new_stream)
1902 {
1903     if (new_stream >= 0 && new_stream < int(get_tcp_stream_count())) {
1904         graph_updater_.triggerUpdate(1000, /*reset_axes =*/true);
1905     }
1906 }
1907 
1908 void TCPStreamDialog::on_streamNumberSpinBox_editingFinished()
1909 {
1910     updateGraph();
1911 }
1912 
1913 void TCPStreamDialog::on_maWindowSizeSpinBox_valueChanged(double new_ma_size)
1914 {
1915     if (new_ma_size > 0.0) {
1916         ma_window_size_ = new_ma_size;
1917         graph_updater_.triggerUpdate(1000, /*reset_axes =*/false);
1918     }
1919 }
1920 
1921 void TCPStreamDialog::on_maWindowSizeSpinBox_editingFinished()
1922 {
1923     updateGraph();
1924 }
1925 
1926 void TCPStreamDialog::on_selectSACKsCheckBox_stateChanged(int /* state */)
1927 {
1928     fillGraph(/*reset_axes=*/false, /*set_focus=*/false);
1929 }
1930 
1931 void TCPStreamDialog::on_otherDirectionButton_clicked()
1932 {
1933     on_actionSwitchDirection_triggered();
1934 }
1935 
1936 void TCPStreamDialog::on_dragRadioButton_toggled(bool checked)
1937 {
1938     if (checked) {
1939         mouse_drags_ = true;
1940         if (rubber_band_ && rubber_band_->isVisible())
1941             rubber_band_->hide();
1942         ui->streamPlot->setInteractions(
1943                     QCP::iRangeDrag |
1944                     QCP::iRangeZoom
1945                     );
1946     }
1947 }
1948 
1949 void TCPStreamDialog::on_zoomRadioButton_toggled(bool checked)
1950 {
1951     if (checked) {
1952         mouse_drags_ = false;
1953         ui->streamPlot->setInteractions(QCP::Interactions());
1954     }
1955 }
1956 
1957 void TCPStreamDialog::on_bySeqNumberCheckBox_stateChanged(int /* state */)
1958 {
1959     fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
1960 }
1961 
1962 void TCPStreamDialog::on_showSegLengthCheckBox_stateChanged(int state)
1963 {
1964     bool visible = (state != 0);
1965     if (graph_.type == GRAPH_THROUGHPUT && base_graph_ != NULL) {
1966         base_graph_->setVisible(visible);
1967         tracer_->setGraph(visible ? base_graph_ : NULL);
1968         ui->streamPlot->replot();
1969     }
1970 }
1971 
1972 void TCPStreamDialog::on_showThroughputCheckBox_stateChanged(int state)
1973 {
1974     bool visible = (state != 0);
1975     if (graph_.type == GRAPH_THROUGHPUT && tput_graph_ != NULL) {
1976         tput_graph_->setVisible(visible);
1977         ui->streamPlot->replot();
1978     }
1979 }
1980 
1981 void TCPStreamDialog::on_showGoodputCheckBox_stateChanged(int state)
1982 {
1983     bool visible = (state != 0);
1984     if (graph_.type == GRAPH_THROUGHPUT && goodput_graph_ != NULL) {
1985         goodput_graph_->setVisible(visible);
1986         ui->streamPlot->replot();
1987     }
1988 }
1989 
1990 void TCPStreamDialog::on_showRcvWinCheckBox_stateChanged(int state)
1991 {
1992     bool visible = (state != 0);
1993     if (graph_.type == GRAPH_WSCALE && rwin_graph_ != NULL) {
1994         rwin_graph_->setVisible(visible);
1995         ui->streamPlot->replot();
1996     }
1997 }
1998 
1999 void TCPStreamDialog::on_showBytesOutCheckBox_stateChanged(int state)
2000 {
2001     bool visible = (state != 0);
2002     if (graph_.type == GRAPH_WSCALE && base_graph_ != NULL) {
2003         base_graph_->setVisible(visible);
2004         tracer_->setGraph(visible ? base_graph_ : NULL);
2005         ui->streamPlot->replot();
2006     }
2007 }
2008 
2009 void TCPStreamDialog::on_actionZoomIn_triggered()
2010 {
2011     zoomAxes(true);
2012 }
2013 
2014 void TCPStreamDialog::on_actionZoomInX_triggered()
2015 {
2016     zoomXAxis(true);
2017 }
2018 
2019 void TCPStreamDialog::on_actionZoomInY_triggered()
2020 {
2021     zoomYAxis(true);
2022 }
2023 
2024 void TCPStreamDialog::on_actionZoomOut_triggered()
2025 {
2026     zoomAxes(false);
2027 }
2028 
2029 void TCPStreamDialog::on_actionZoomOutX_triggered()
2030 {
2031     zoomXAxis(false);
2032 }
2033 
2034 void TCPStreamDialog::on_actionZoomOutY_triggered()
2035 {
2036     zoomYAxis(false);
2037 }
2038 
2039 void TCPStreamDialog::on_actionReset_triggered()
2040 {
2041     on_resetButton_clicked();
2042 }
2043 
2044 void TCPStreamDialog::on_actionMoveRight10_triggered()
2045 {
2046     panAxes(10, 0);
2047 }
2048 
2049 void TCPStreamDialog::on_actionMoveLeft10_triggered()
2050 {
2051     panAxes(-10, 0);
2052 }
2053 
2054 void TCPStreamDialog::on_actionMoveUp10_triggered()
2055 {
2056     panAxes(0, 10);
2057 }
2058 
2059 void TCPStreamDialog::on_actionMoveDown10_triggered()
2060 {
2061     panAxes(0, -10);
2062 }
2063 
2064 void TCPStreamDialog::on_actionMoveRight1_triggered()
2065 {
2066     panAxes(1, 0);
2067 }
2068 
2069 void TCPStreamDialog::on_actionMoveLeft1_triggered()
2070 {
2071     panAxes(-1, 0);
2072 }
2073 
2074 void TCPStreamDialog::on_actionMoveUp1_triggered()
2075 {
2076     panAxes(0, 1);
2077 }
2078 
2079 void TCPStreamDialog::on_actionMoveDown1_triggered()
2080 {
2081     panAxes(0, -1);
2082 }
2083 
2084 void TCPStreamDialog::on_actionNextStream_triggered()
2085 {
2086     if (int(graph_.stream) < int(get_tcp_stream_count()) - 1) {
2087         ui->streamNumberSpinBox->setValue(graph_.stream + 1);
2088         updateGraph();
2089     }
2090 }
2091 
2092 void TCPStreamDialog::on_actionPreviousStream_triggered()
2093 {
2094     if (graph_.stream > 0) {
2095         ui->streamNumberSpinBox->setValue(graph_.stream - 1);
2096         updateGraph();
2097     }
2098 }
2099 
2100 void TCPStreamDialog::on_actionSwitchDirection_triggered()
2101 {
2102     address tmp_addr;
2103     guint16 tmp_port;
2104 
2105     copy_address(&tmp_addr, &graph_.src_address);
2106     tmp_port = graph_.src_port;
2107     free_address(&graph_.src_address);
2108     copy_address(&graph_.src_address, &graph_.dst_address);
2109     graph_.src_port = graph_.dst_port;
2110     free_address(&graph_.dst_address);
2111     copy_address(&graph_.dst_address, &tmp_addr);
2112     graph_.dst_port = tmp_port;
2113     free_address(&tmp_addr);
2114 
2115     fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
2116 }
2117 
2118 void TCPStreamDialog::on_actionGoToPacket_triggered()
2119 {
2120     if (tracer_->visible() && cap_file_ && packet_num_ > 0) {
2121         emit goToPacket(packet_num_);
2122     }
2123 }
2124 
2125 void TCPStreamDialog::on_actionDragZoom_triggered()
2126 {
2127     if (mouse_drags_) {
2128         ui->zoomRadioButton->toggle();
2129     } else {
2130         ui->dragRadioButton->toggle();
2131     }
2132 }
2133 
2134 void TCPStreamDialog::on_actionToggleSequenceNumbers_triggered()
2135 {
2136     seq_origin_zero_ = seq_origin_zero_ ? false : true;
2137     fillGraph();
2138 }
2139 
2140 void TCPStreamDialog::on_actionToggleTimeOrigin_triggered()
2141 {
2142     ts_origin_conn_ = ts_origin_conn_ ? false : true;
2143     fillGraph();
2144 }
2145 
2146 void TCPStreamDialog::on_actionRoundTripTime_triggered()
2147 {
2148     ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_RTT));
2149 }
2150 
2151 void TCPStreamDialog::on_actionThroughput_triggered()
2152 {
2153     ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_THROUGHPUT));
2154 }
2155 
2156 void TCPStreamDialog::on_actionStevens_triggered()
2157 {
2158     ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_TSEQ_STEVENS));
2159 }
2160 
2161 void TCPStreamDialog::on_actionTcptrace_triggered()
2162 {
2163     ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_TSEQ_TCPTRACE));
2164 }
2165 
2166 void TCPStreamDialog::on_actionWindowScaling_triggered()
2167 {
2168     ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_WSCALE));
2169 }
2170 
2171 void TCPStreamDialog::GraphUpdater::triggerUpdate(int timeout, bool reset_axes)
2172 {
2173     if (!hasPendingUpdate()) {
2174         graph_update_timer_ = new QTimer(dialog_);
2175         graph_update_timer_->setSingleShot(true);
2176         dialog_->connect(graph_update_timer_, SIGNAL(timeout()), dialog_, SLOT(updateGraph()));
2177     }
2178     reset_axes_ = (reset_axes_ || reset_axes);
2179     graph_update_timer_->start(timeout);
2180 }
2181 
2182 void TCPStreamDialog::GraphUpdater::clearPendingUpdate()
2183 {
2184     if (hasPendingUpdate()) {
2185         if (graph_update_timer_->isActive())
2186             graph_update_timer_->stop();
2187         delete graph_update_timer_;
2188         graph_update_timer_ = NULL;
2189         reset_axes_ = false;
2190     }
2191 }
2192 
2193 void TCPStreamDialog::GraphUpdater::doUpdate()
2194 {
2195     if (hasPendingUpdate()) {
2196         bool reset_axes = reset_axes_;
2197         clearPendingUpdate();
2198         // if the stream has changed, update the data here
2199         int new_stream = dialog_->ui->streamNumberSpinBox->value();
2200         if ((int(dialog_->graph_.stream) != new_stream) &&
2201             (new_stream >= 0 && new_stream < int(get_tcp_stream_count()))) {
2202             dialog_->graph_.stream = new_stream;
2203             dialog_->findStream();
2204         }
2205         dialog_->fillGraph(reset_axes, /*set_focus =*/false);
2206     }
2207 }
2208 
2209 void TCPStreamDialog::on_buttonBox_helpRequested()
2210 {
2211     wsApp->helpTopicAction(HELP_STATS_TCP_STREAM_GRAPHS_DIALOG);
2212 }
2213