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