1 /* rtp_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 "rtp_stream_dialog.h"
11 #include <ui_rtp_stream_dialog.h>
12 
13 #include "file.h"
14 
15 #include "epan/addr_resolv.h"
16 #include <epan/rtp_pt.h>
17 
18 #include <wsutil/utf8_entities.h>
19 
20 #include <ui/qt/utils/qt_ui_utils.h>
21 #include "rtp_analysis_dialog.h"
22 #include "wireshark_application.h"
23 #include "ui/qt/widgets/wireshark_file_dialog.h"
24 
25 #include <QAction>
26 #include <QClipboard>
27 #include <QKeyEvent>
28 #include <QPushButton>
29 #include <QTextStream>
30 #include <QTreeWidgetItem>
31 #include <QTreeWidgetItemIterator>
32 #include <QDateTime>
33 
34 #include <ui/qt/utils/color_utils.h>
35 
36 /*
37  * @file RTP stream dialog
38  *
39  * Displays a list of RTP streams with the following information:
40  * - UDP 4-tuple
41  * - SSRC
42  * - Payload type
43  * - Stats: Packets, lost, max delta, max jitter, mean jitter
44  * - Problems
45  *
46  * Finds reverse streams
47  * "Save As" rtpdump
48  * Mark packets
49  * Go to the setup frame
50  * Prepare filter
51  * Copy As CSV and YAML
52  * Analyze
53  */
54 
55 // To do:
56 // - Add more statistics to the hint text (e.g. lost packets).
57 // - Add more statistics to the main list (e.g. stream duration)
58 
59 const int src_addr_col_    =  0;
60 const int src_port_col_    =  1;
61 const int dst_addr_col_    =  2;
62 const int dst_port_col_    =  3;
63 const int ssrc_col_        =  4;
64 const int start_time_col_  =  5;
65 const int duration_col_    =  6;
66 const int payload_col_     =  7;
67 const int packets_col_     =  8;
68 const int lost_col_        =  9;
69 const int min_delta_col_   = 10;
70 const int mean_delta_col_  = 11;
71 const int max_delta_col_   = 12;
72 const int min_jitter_col_  = 13;
73 const int mean_jitter_col_ = 14;
74 const int max_jitter_col_  = 15;
75 const int status_col_      = 16;
76 const int ssrc_fmt_col_    = 17;
77 const int lost_perc_col_   = 18;
78 
79 enum { rtp_stream_type_ = 1000 };
80 
81 bool operator==(rtpstream_id_t const& a, rtpstream_id_t const& b);
82 
83 class RtpStreamTreeWidgetItem : public QTreeWidgetItem
84 {
85 public:
RtpStreamTreeWidgetItem(QTreeWidget * tree,rtpstream_info_t * stream_info)86     RtpStreamTreeWidgetItem(QTreeWidget *tree, rtpstream_info_t *stream_info) :
87         QTreeWidgetItem(tree, rtp_stream_type_),
88         stream_info_(stream_info),
89         tod_(0)
90     {
91         drawData();
92     }
93 
streamInfo() const94     rtpstream_info_t *streamInfo() const { return stream_info_; }
95 
drawData()96     void drawData() {
97         rtpstream_info_calc_t calc;
98 
99         if (!stream_info_) {
100             return;
101         }
102         rtpstream_info_calculate(stream_info_, &calc);
103 
104         setText(src_addr_col_, calc.src_addr_str);
105         setText(src_port_col_, QString::number(calc.src_port));
106         setText(dst_addr_col_, calc.dst_addr_str);
107         setText(dst_port_col_, QString::number(calc.dst_port));
108         setText(ssrc_col_, QString("0x%1").arg(calc.ssrc, 0, 16));
109         if (tod_) {
110             QDateTime abs_dt = QDateTime::fromMSecsSinceEpoch(nstime_to_msec(&stream_info_->start_fd->abs_ts));
111             setText(start_time_col_, QString("%1")
112                 .arg(abs_dt.toString("yyyy-MM-dd hh:mm:ss.zzz")));
113         } else {
114           setText(start_time_col_, QString::number(calc.start_time_ms, 'f', 6));
115         }
116         setText(duration_col_, QString::number(calc.duration_ms, 'f', prefs.gui_decimal_places1));
117         setText(payload_col_, calc.all_payload_type_names);
118         setText(packets_col_, QString::number(calc.packet_count));
119         setText(lost_col_, QObject::tr("%1 (%L2%)").arg(calc.lost_num).arg(QString::number(calc.lost_perc, 'f', 1)));
120         setText(min_delta_col_, QString::number(calc.min_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
121         setText(mean_delta_col_, QString::number(calc.mean_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
122         setText(max_delta_col_, QString::number(calc.max_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
123         setText(min_jitter_col_, QString::number(calc.min_jitter, 'f', prefs.gui_decimal_places3));
124         setText(mean_jitter_col_, QString::number(calc.mean_jitter, 'f', prefs.gui_decimal_places3));
125         setText(max_jitter_col_, QString::number(calc.max_jitter, 'f', prefs.gui_decimal_places3));
126 
127         if (calc.problem) {
128             setText(status_col_, UTF8_BULLET);
129             setTextAlignment(status_col_, Qt::AlignCenter);
130             QColor bgColor(ColorUtils::warningBackground());
131             QColor textColor(QApplication::palette().text().color());
132             for (int i = 0; i < columnCount(); i++) {
133                 QBrush bgBrush = background(i);
134                 bgBrush.setColor(bgColor);
135                 setBackground(i, bgBrush);
136                 QBrush fgBrush = foreground(i);
137                 fgBrush.setColor(textColor);
138                 setForeground(i, fgBrush);
139             }
140         }
141 
142         rtpstream_info_calc_free(&calc);
143     }
144     // Return a QString, int, double, or invalid QVariant representing the raw column data.
colData(int col) const145     QVariant colData(int col) const {
146         rtpstream_info_calc_t calc;
147         if (!stream_info_) {
148             return QVariant();
149         }
150 
151         rtpstream_info_calculate(stream_info_, &calc);
152 
153         switch(col) {
154         case src_addr_col_:
155             return text(col);
156         case src_port_col_:
157             return calc.src_port;
158         case dst_addr_col_:
159             return text(col);
160         case dst_port_col_:
161             return calc.dst_port;
162         case ssrc_col_:
163             return calc.ssrc;
164         case start_time_col_:
165             return calc.start_time_ms;
166         case duration_col_:
167             return calc.duration_ms;
168         case payload_col_:
169             return text(col);
170         case packets_col_:
171             return calc.packet_count;
172         case lost_col_:
173             return calc.lost_num;
174         case min_delta_col_:
175             return calc.min_delta;
176         case mean_delta_col_:
177             return calc.mean_delta;
178         case max_delta_col_:
179             return calc.max_delta;
180         case min_jitter_col_:
181             return calc.min_jitter;
182         case mean_jitter_col_:
183             return calc.mean_jitter;
184         case max_jitter_col_:
185             return calc.max_jitter;
186         case status_col_:
187             return calc.problem ? "Problem" : "";
188         case ssrc_fmt_col_:
189             return QString("0x%1").arg(calc.ssrc, 0, 16);
190         case lost_perc_col_:
191             return QString::number(calc.lost_perc, 'f', prefs.gui_decimal_places1);
192         default:
193             break;
194         }
195         return QVariant();
196     }
197 
operator <(const QTreeWidgetItem & other) const198     bool operator< (const QTreeWidgetItem &other) const
199     {
200         rtpstream_info_calc_t calc1;
201         rtpstream_info_calc_t calc2;
202 
203         if (other.type() != rtp_stream_type_) return QTreeWidgetItem::operator <(other);
204         const RtpStreamTreeWidgetItem &other_rstwi = dynamic_cast<const RtpStreamTreeWidgetItem&>(other);
205 
206         switch (treeWidget()->sortColumn()) {
207         case src_addr_col_:
208             return cmp_address(&(stream_info_->id.src_addr), &(other_rstwi.stream_info_->id.src_addr)) < 0;
209         case src_port_col_:
210             return stream_info_->id.src_port < other_rstwi.stream_info_->id.src_port;
211         case dst_addr_col_:
212             return cmp_address(&(stream_info_->id.dst_addr), &(other_rstwi.stream_info_->id.dst_addr)) < 0;
213         case dst_port_col_:
214             return stream_info_->id.dst_port < other_rstwi.stream_info_->id.dst_port;
215         case ssrc_col_:
216             return stream_info_->id.ssrc < other_rstwi.stream_info_->id.ssrc;
217         case start_time_col_:
218             rtpstream_info_calculate(stream_info_, &calc1);
219             rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
220             return calc1.start_time_ms < calc2.start_time_ms;
221         case duration_col_:
222             rtpstream_info_calculate(stream_info_, &calc1);
223             rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
224             return calc1.duration_ms < calc2.duration_ms;
225         case payload_col_:
226             return g_strcmp0(stream_info_->all_payload_type_names, other_rstwi.stream_info_->all_payload_type_names);
227         case packets_col_:
228             return stream_info_->packet_count < other_rstwi.stream_info_->packet_count;
229         case lost_col_:
230             return lost_ < other_rstwi.lost_;
231         case min_delta_col_:
232             return stream_info_->rtp_stats.min_delta < other_rstwi.stream_info_->rtp_stats.min_delta;
233         case mean_delta_col_:
234             return stream_info_->rtp_stats.mean_delta < other_rstwi.stream_info_->rtp_stats.mean_delta;
235         case max_delta_col_:
236             return stream_info_->rtp_stats.max_delta < other_rstwi.stream_info_->rtp_stats.max_delta;
237         case min_jitter_col_:
238             return stream_info_->rtp_stats.min_jitter < other_rstwi.stream_info_->rtp_stats.min_jitter;
239         case mean_jitter_col_:
240             return stream_info_->rtp_stats.mean_jitter < other_rstwi.stream_info_->rtp_stats.mean_jitter;
241         case max_jitter_col_:
242             return stream_info_->rtp_stats.max_jitter < other_rstwi.stream_info_->rtp_stats.max_jitter;
243         default:
244             break;
245         }
246 
247         // Fall back to string comparison
248         return QTreeWidgetItem::operator <(other);
249     }
250 
setTOD(gboolean tod)251     void setTOD(gboolean tod)
252     {
253       tod_ = tod;
254     }
255 
256 private:
257     rtpstream_info_t *stream_info_;
258     guint32 lost_;
259     gboolean tod_;
260 };
261 
262 
263 RtpStreamDialog *RtpStreamDialog::pinstance_{nullptr};
264 std::mutex RtpStreamDialog::mutex_;
265 
openRtpStreamDialog(QWidget & parent,CaptureFile & cf,QObject * packet_list)266 RtpStreamDialog *RtpStreamDialog::openRtpStreamDialog(QWidget &parent, CaptureFile &cf, QObject *packet_list)
267 {
268     std::lock_guard<std::mutex> lock(mutex_);
269     if (pinstance_ == nullptr)
270     {
271         pinstance_ = new RtpStreamDialog(parent, cf);
272         connect(pinstance_, SIGNAL(packetsMarked()),
273                 packet_list, SLOT(redrawVisiblePackets()));
274         connect(pinstance_, SIGNAL(goToPacket(int)),
275                 packet_list, SLOT(goToPacket(int)));
276     }
277     return pinstance_;
278 }
279 
RtpStreamDialog(QWidget & parent,CaptureFile & cf)280 RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) :
281     WiresharkDialog(parent, cf),
282     ui(new Ui::RtpStreamDialog),
283     need_redraw_(false)
284 {
285     ui->setupUi(this);
286     loadGeometry(parent.width() * 4 / 5, parent.height() * 2 / 3);
287     setWindowSubtitle(tr("RTP Streams"));
288     ui->streamTreeWidget->installEventFilter(this);
289 
290     ctx_menu_.addMenu(ui->menuSelect);
291     ctx_menu_.addMenu(ui->menuFindReverse);
292     ctx_menu_.addAction(ui->actionGoToSetup);
293     ctx_menu_.addAction(ui->actionMarkPackets);
294     ctx_menu_.addAction(ui->actionPrepareFilter);
295     ctx_menu_.addAction(ui->actionExportAsRtpDump);
296     ctx_menu_.addAction(ui->actionCopyAsCsv);
297     ctx_menu_.addAction(ui->actionCopyAsYaml);
298     ctx_menu_.addAction(ui->actionAnalyze);
299     set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
300 
301     ui->streamTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
302     ui->streamTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
303     connect(ui->streamTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
304                 SLOT(showStreamMenu(QPoint)));
305 
306     find_reverse_button_ = new QToolButton();
307     ui->buttonBox->addButton(find_reverse_button_, QDialogButtonBox::ActionRole);
308     find_reverse_button_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
309     find_reverse_button_->setPopupMode(QToolButton::MenuButtonPopup);
310 
311     connect(ui->actionFindReverse, SIGNAL(triggered()), this, SLOT(on_actionFindReverseNormal_triggered()));
312     find_reverse_button_->setDefaultAction(ui->actionFindReverse);
313     // Overrides text striping of shortcut undercode in QAction
314     find_reverse_button_->setText(ui->actionFindReverseNormal->text());
315     find_reverse_button_->setMenu(ui->menuFindReverse);
316 
317     analyze_button_ = RtpAnalysisDialog::addAnalyzeButton(ui->buttonBox, this);
318     prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
319     prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
320     connect(prepare_button_, SIGNAL(pressed()), this, SLOT(on_actionPrepareFilter_triggered()));
321     player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
322     copy_button_ = ui->buttonBox->addButton(ui->actionCopyButton->text(), QDialogButtonBox::ActionRole);
323     copy_button_->setToolTip(ui->actionCopyButton->toolTip());
324     export_button_ = ui->buttonBox->addButton(ui->actionExportAsRtpDump->text(), QDialogButtonBox::ActionRole);
325     export_button_->setToolTip(ui->actionExportAsRtpDump->toolTip());
326     connect(export_button_, SIGNAL(pressed()), this, SLOT(on_actionExportAsRtpDump_triggered()));
327 
328     QMenu *copy_menu = new QMenu(copy_button_);
329     QAction *ca;
330     ca = copy_menu->addAction(tr("as CSV"));
331     ca->setToolTip(ui->actionCopyAsCsv->toolTip());
332     connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsCsv_triggered()));
333     ca = copy_menu->addAction(tr("as YAML"));
334     ca->setToolTip(ui->actionCopyAsYaml->toolTip());
335     connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsYaml_triggered()));
336     copy_button_->setMenu(copy_menu);
337     connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
338             this, SLOT(captureEvent(CaptureEvent)));
339 
340     /* Register the tap listener */
341     memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
342     tapinfo_.tap_reset = tapReset;
343     tapinfo_.tap_draw = tapDraw;
344     tapinfo_.tap_mark_packet = tapMarkPacket;
345     tapinfo_.tap_data = this;
346     tapinfo_.mode = TAP_ANALYSE;
347 
348     register_tap_listener_rtpstream(&tapinfo_, NULL, show_tap_registration_error);
349     if (cap_file_.isValid() && cap_file_.capFile()->dfilter) {
350         // Activate display filter checking
351         tapinfo_.apply_display_filter = true;
352         ui->displayFilterCheckBox->setChecked(true);
353     }
354 
355     connect(this, SIGNAL(updateFilter(QString, bool)),
356             &parent, SLOT(filterPackets(QString, bool)));
357     connect(&parent, SIGNAL(displayFilterSuccess(bool)),
358             this, SLOT(displayFilterSuccess(bool)));
359     connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
360             &parent, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
361     connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
362             &parent, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
363     connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
364             &parent, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
365     connect(this, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
366             &parent, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
367     connect(this, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
368             &parent, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
369     connect(this, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
370             &parent, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
371 
372     /* Scan for RTP streams (redissect all packets) */
373     rtpstream_scan(&tapinfo_, cf.capFile(), NULL);
374 
375     updateWidgets();
376 }
377 
~RtpStreamDialog()378 RtpStreamDialog::~RtpStreamDialog()
379 {
380     std::lock_guard<std::mutex> lock(mutex_);
381     freeLastSelected();
382     delete ui;
383     remove_tap_listener_rtpstream(&tapinfo_);
384     pinstance_ = nullptr;
385 }
386 
setRtpStreamSelection(rtpstream_id_t * id,bool state)387 void RtpStreamDialog::setRtpStreamSelection(rtpstream_id_t *id, bool state)
388 {
389     QTreeWidgetItemIterator iter(ui->streamTreeWidget);
390     while (*iter) {
391         RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
392         rtpstream_info_t *stream_info = rsti->streamInfo();
393         if (stream_info) {
394             if (rtpstream_id_equal(id,&stream_info->id,RTPSTREAM_ID_EQUAL_SSRC)) {
395                 (*iter)->setSelected(state);
396             }
397         }
398         ++iter;
399     }
400 }
401 
selectRtpStream(QVector<rtpstream_id_t * > stream_ids)402 void RtpStreamDialog::selectRtpStream(QVector<rtpstream_id_t *> stream_ids)
403 {
404     std::lock_guard<std::mutex> lock(mutex_);
405     foreach(rtpstream_id_t *id, stream_ids) {
406         setRtpStreamSelection(id, true);
407     }
408 }
409 
deselectRtpStream(QVector<rtpstream_id_t * > stream_ids)410 void RtpStreamDialog::deselectRtpStream(QVector<rtpstream_id_t *> stream_ids)
411 {
412     std::lock_guard<std::mutex> lock(mutex_);
413     foreach(rtpstream_id_t *id, stream_ids) {
414         setRtpStreamSelection(id, false);
415     }
416 }
417 
eventFilter(QObject *,QEvent * event)418 bool RtpStreamDialog::eventFilter(QObject *, QEvent *event)
419 {
420     if (ui->streamTreeWidget->hasFocus() && event->type() == QEvent::KeyPress) {
421         QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
422         switch(keyEvent.key()) {
423             case Qt::Key_G:
424                 on_actionGoToSetup_triggered();
425                 return true;
426             case Qt::Key_M:
427                 on_actionMarkPackets_triggered();
428                 return true;
429             case Qt::Key_P:
430                 on_actionPrepareFilter_triggered();
431                 return true;
432             case Qt::Key_R:
433                 if (keyEvent.modifiers() == Qt::ShiftModifier) {
434                     on_actionFindReversePair_triggered();
435                 } else if (keyEvent.modifiers() == Qt::ControlModifier) {
436                     on_actionFindReverseSingle_triggered();
437                 } else {
438                     on_actionFindReverseNormal_triggered();
439                 }
440                 return true;
441             case Qt::Key_I:
442                 if (keyEvent.modifiers() == Qt::ControlModifier) {
443                     // Ctrl+I
444                     on_actionSelectInvert_triggered();
445                     return true;
446                 }
447                 break;
448             case Qt::Key_A:
449                 if (keyEvent.modifiers() == Qt::ControlModifier) {
450                     // Ctrl+A
451                     on_actionSelectAll_triggered();
452                     return true;
453                 } else if (keyEvent.modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
454                     // Ctrl+Shift+A
455                     on_actionSelectNone_triggered();
456                     return true;
457                 } else if (keyEvent.modifiers() == Qt::NoModifier) {
458                     on_actionAnalyze_triggered();
459                 }
460                 break;
461             default:
462                 break;
463         }
464     }
465     return false;
466 }
467 
captureEvent(CaptureEvent e)468 void RtpStreamDialog::captureEvent(CaptureEvent e)
469 {
470     if (e.captureContext() == CaptureEvent::Retap)
471     {
472         switch (e.eventType())
473         {
474         case CaptureEvent::Started:
475             ui->displayFilterCheckBox->setEnabled(false);
476             break;
477         case CaptureEvent::Finished:
478             ui->displayFilterCheckBox->setEnabled(true);
479             break;
480         default:
481             break;
482         }
483     }
484 
485 }
486 
tapReset(rtpstream_tapinfo_t * tapinfo)487 void RtpStreamDialog::tapReset(rtpstream_tapinfo_t *tapinfo)
488 {
489     RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
490     if (rtp_stream_dialog) {
491         rtp_stream_dialog->freeLastSelected();
492         /* Copy currently selected rtpstream_ids */
493         QTreeWidgetItemIterator iter(rtp_stream_dialog->ui->streamTreeWidget);
494         while (*iter) {
495             RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
496             rtpstream_info_t *stream_info = rsti->streamInfo();
497             if ((*iter)->isSelected()) {
498                 rtpstream_id_t *i = (rtpstream_id_t *)g_malloc0(sizeof(rtpstream_id_t));
499                 rtpstream_id_copy(&stream_info->id, i);
500                 rtp_stream_dialog->last_selected_.append(*i);
501             }
502             ++iter;
503         }
504         /* invalidate items which refer to old strinfo_list items. */
505         rtp_stream_dialog->ui->streamTreeWidget->clear();
506     }
507 }
508 
tapDraw(rtpstream_tapinfo_t * tapinfo)509 void RtpStreamDialog::tapDraw(rtpstream_tapinfo_t *tapinfo)
510 {
511     RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
512     if (rtp_stream_dialog) {
513         rtp_stream_dialog->updateStreams();
514     }
515 }
516 
tapMarkPacket(rtpstream_tapinfo_t * tapinfo,frame_data * fd)517 void RtpStreamDialog::tapMarkPacket(rtpstream_tapinfo_t *tapinfo, frame_data *fd)
518 {
519     if (!tapinfo) return;
520 
521     RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
522     if (rtp_stream_dialog) {
523         cf_mark_frame(rtp_stream_dialog->cap_file_.capFile(), fd);
524         rtp_stream_dialog->need_redraw_ = true;
525     }
526 }
527 
528 /* Operator == for rtpstream_id_t */
operator ==(rtpstream_id_t const & a,rtpstream_id_t const & b)529 bool operator==(rtpstream_id_t const& a, rtpstream_id_t const& b)
530 {
531     return rtpstream_id_equal(&a, &b, RTPSTREAM_ID_EQUAL_SSRC);
532 }
533 
updateStreams()534 void RtpStreamDialog::updateStreams()
535 {
536     // string_list is reverse ordered, so we must add
537     // just first "to_insert_count" of streams
538     GList *cur_stream = g_list_first(tapinfo_.strinfo_list);
539     guint tap_len = g_list_length(tapinfo_.strinfo_list);
540     guint tree_len = static_cast<guint>(ui->streamTreeWidget->topLevelItemCount());
541     guint to_insert_count = tap_len - tree_len;
542 
543     // Add any missing items
544     while (cur_stream && cur_stream->data && to_insert_count) {
545         rtpstream_info_t *stream_info = gxx_list_data(rtpstream_info_t*, cur_stream);
546         RtpStreamTreeWidgetItem *rsti = new RtpStreamTreeWidgetItem(ui->streamTreeWidget, stream_info);
547         cur_stream = gxx_list_next(cur_stream);
548         to_insert_count--;
549 
550         // Check if item was selected last time. If so, select it
551         if (-1 != last_selected_.indexOf(stream_info->id)) {
552            rsti->setSelected(true);
553         }
554     }
555 
556     // Recalculate values
557     QTreeWidgetItemIterator iter(ui->streamTreeWidget);
558     while (*iter) {
559         RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
560         rsti->drawData();
561         ++iter;
562     }
563 
564     // Resize columns
565     for (int i = 0; i < ui->streamTreeWidget->columnCount(); i++) {
566         ui->streamTreeWidget->resizeColumnToContents(i);
567     }
568 
569     ui->streamTreeWidget->setSortingEnabled(true);
570 
571     updateWidgets();
572 
573     if (need_redraw_) {
574         emit packetsMarked();
575         need_redraw_ = false;
576     }
577 }
578 
updateWidgets()579 void RtpStreamDialog::updateWidgets()
580 {
581     bool selected = ui->streamTreeWidget->selectedItems().count() > 0;
582 
583     QString hint = "<small><i>";
584     hint += tr("%1 streams").arg(ui->streamTreeWidget->topLevelItemCount());
585 
586     if (selected) {
587         int tot_packets = 0;
588         foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
589             RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
590             if (rsti->streamInfo()) {
591                 tot_packets += rsti->streamInfo()->packet_count;
592             }
593         }
594         hint += tr(", %1 selected, %2 total packets")
595                 .arg(ui->streamTreeWidget->selectedItems().count())
596                 .arg(tot_packets);
597     }
598 
599     hint += ". Right-click for more options.";
600     hint += "</i></small>";
601     ui->hintLabel->setText(hint);
602 
603     bool enable = selected && !file_closed_;
604     bool has_data = ui->streamTreeWidget->topLevelItemCount() > 0;
605 
606     find_reverse_button_->setEnabled(has_data);
607     prepare_button_->setEnabled(enable);
608     export_button_->setEnabled(enable);
609     copy_button_->setEnabled(has_data);
610     analyze_button_->setEnabled(enable);
611 
612     ui->actionFindReverseNormal->setEnabled(enable);
613     ui->actionFindReversePair->setEnabled(has_data);
614     ui->actionFindReverseSingle->setEnabled(has_data);
615     ui->actionGoToSetup->setEnabled(enable);
616     ui->actionMarkPackets->setEnabled(enable);
617     ui->actionPrepareFilter->setEnabled(enable);
618     ui->actionExportAsRtpDump->setEnabled(enable);
619     ui->actionCopyAsCsv->setEnabled(has_data);
620     ui->actionCopyAsYaml->setEnabled(has_data);
621     ui->actionAnalyze->setEnabled(enable);
622 
623 #if defined(QT_MULTIMEDIA_LIB)
624     player_button_->setEnabled(enable);
625 #endif
626 
627     WiresharkDialog::updateWidgets();
628 }
629 
streamRowData(int row) const630 QList<QVariant> RtpStreamDialog::streamRowData(int row) const
631 {
632     QList<QVariant> row_data;
633 
634     if (row >= ui->streamTreeWidget->topLevelItemCount()) {
635         return row_data;
636     }
637 
638     for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
639         if (row < 0) {
640             row_data << ui->streamTreeWidget->headerItem()->text(col);
641         } else {
642             RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row));
643             if (rsti) {
644                 row_data << rsti->colData(col);
645             }
646         }
647     }
648 
649     // Add additional columns to export
650     if (row < 0) {
651         row_data << QString("SSRC formatted");
652         row_data << QString("Lost percentage");
653     } else {
654         RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row));
655         if (rsti) {
656             row_data << rsti->colData(ssrc_fmt_col_);
657             row_data << rsti->colData(lost_perc_col_);
658         }
659     }
660     return row_data;
661 }
662 
freeLastSelected()663 void RtpStreamDialog::freeLastSelected()
664 {
665     /* Free old IDs */
666     for(int i=0; i<last_selected_.length(); i++) {
667         rtpstream_id_t id = last_selected_.at(i);
668         rtpstream_id_free(&id);
669     }
670     /* Clear list and reuse it */
671     last_selected_.clear();
672 }
673 
captureFileClosing()674 void RtpStreamDialog::captureFileClosing()
675 {
676     remove_tap_listener_rtpstream(&tapinfo_);
677 
678     WiresharkDialog::captureFileClosing();
679 }
680 
captureFileClosed()681 void RtpStreamDialog::captureFileClosed()
682 {
683     ui->todCheckBox->setEnabled(false);
684     ui->displayFilterCheckBox->setEnabled(false);
685 
686     WiresharkDialog::captureFileClosed();
687 }
688 
showStreamMenu(QPoint pos)689 void RtpStreamDialog::showStreamMenu(QPoint pos)
690 {
691     ui->actionGoToSetup->setEnabled(!file_closed_);
692     ui->actionMarkPackets->setEnabled(!file_closed_);
693     ui->actionPrepareFilter->setEnabled(!file_closed_);
694     ui->actionExportAsRtpDump->setEnabled(!file_closed_);
695     ui->actionAnalyze->setEnabled(!file_closed_);
696     ctx_menu_.popup(ui->streamTreeWidget->viewport()->mapToGlobal(pos));
697 }
698 
on_actionCopyAsCsv_triggered()699 void RtpStreamDialog::on_actionCopyAsCsv_triggered()
700 {
701     QString csv;
702     QTextStream stream(&csv, QIODevice::Text);
703     for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
704         QStringList rdsl;
705         foreach (QVariant v, streamRowData(row)) {
706             if (!v.isValid()) {
707                 rdsl << "\"\"";
708             } else if (v.type() == QVariant::String) {
709                 rdsl << QString("\"%1\"").arg(v.toString());
710             } else {
711                 rdsl << v.toString();
712             }
713         }
714         stream << rdsl.join(",") << '\n';
715     }
716     wsApp->clipboard()->setText(stream.readAll());
717 }
718 
on_actionCopyAsYaml_triggered()719 void RtpStreamDialog::on_actionCopyAsYaml_triggered()
720 {
721     QString yaml;
722     QTextStream stream(&yaml, QIODevice::Text);
723     stream << "---" << '\n';
724     for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row ++) {
725         stream << "-" << '\n';
726         foreach (QVariant v, streamRowData(row)) {
727             stream << " - " << v.toString() << '\n';
728         }
729     }
730     wsApp->clipboard()->setText(stream.readAll());
731 }
732 
on_actionExportAsRtpDump_triggered()733 void RtpStreamDialog::on_actionExportAsRtpDump_triggered()
734 {
735     if (file_closed_ || ui->streamTreeWidget->selectedItems().count() < 1) return;
736 
737     // XXX If the user selected multiple frames is this the one we actually want?
738     QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
739     RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
740     rtpstream_info_t *stream_info = rsti->streamInfo();
741     if (stream_info) {
742         QString file_name;
743         QDir path(wsApp->lastOpenDir());
744         QString save_file = path.canonicalPath() + "/" + cap_file_.fileBaseName();
745         QString extension;
746         file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save RTPDump As…")),
747                                                  save_file, "RTPDump Format (*.rtpdump)", &extension);
748 
749         if (file_name.length() > 0) {
750             gchar *dest_file = qstring_strdup(file_name);
751             gboolean save_ok = rtpstream_save(&tapinfo_, cap_file_.capFile(), stream_info, dest_file);
752             g_free(dest_file);
753             // else error dialog?
754             if (save_ok) {
755                 wsApp->setLastOpenDirFromFilename(file_name);
756             }
757         }
758 
759     }
760 }
761 
762 // Search for reverse stream of every selected stream
on_actionFindReverseNormal_triggered()763 void RtpStreamDialog::on_actionFindReverseNormal_triggered()
764 {
765     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
766 
767     ui->streamTreeWidget->blockSignals(true);
768 
769     // Traverse all items and if stream is selected, search reverse from
770     // current position till last item (NxN/2)
771     for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
772         RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
773         rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
774         if (fwd_stream && fwd_rsti->isSelected()) {
775             for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
776                 RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
777                 rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
778                 if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
779                     rev_rsti->setSelected(true);
780                     break;
781                 }
782             }
783         }
784     }
785     ui->streamTreeWidget->blockSignals(false);
786     updateWidgets();
787 }
788 
789 // Select all pairs of forward/reverse streams
on_actionFindReversePair_triggered()790 void RtpStreamDialog::on_actionFindReversePair_triggered()
791 {
792     ui->streamTreeWidget->blockSignals(true);
793     ui->streamTreeWidget->clearSelection();
794 
795     // Traverse all items and search reverse from current position till last
796     // item (NxN/2)
797     for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
798         RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
799         rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
800         if (fwd_stream) {
801             for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
802                 RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
803                 rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
804                 if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
805                     fwd_rsti->setSelected(true);
806                     rev_rsti->setSelected(true);
807                     break;
808                 }
809             }
810         }
811     }
812     ui->streamTreeWidget->blockSignals(false);
813     updateWidgets();
814 }
815 
816 // Select all streams which don't have reverse stream
on_actionFindReverseSingle_triggered()817 void RtpStreamDialog::on_actionFindReverseSingle_triggered()
818 {
819     ui->streamTreeWidget->blockSignals(true);
820     ui->streamTreeWidget->selectAll();
821 
822     // Traverse all items and search reverse from current position till last
823     // item (NxN/2)
824     for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
825         RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
826         rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
827         if (fwd_stream) {
828             for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
829                 RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
830                 rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
831                 if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
832                     fwd_rsti->setSelected(false);
833                     rev_rsti->setSelected(false);
834                     break;
835                 }
836             }
837         }
838     }
839     ui->streamTreeWidget->blockSignals(false);
840     updateWidgets();
841 }
842 
on_actionGoToSetup_triggered()843 void RtpStreamDialog::on_actionGoToSetup_triggered()
844 {
845     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
846     // XXX If the user selected multiple frames is this the one we actually want?
847     QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
848     RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
849     rtpstream_info_t *stream_info = rsti->streamInfo();
850     if (stream_info) {
851         emit goToPacket(stream_info->setup_frame_number);
852     }
853 }
854 
on_actionMarkPackets_triggered()855 void RtpStreamDialog::on_actionMarkPackets_triggered()
856 {
857     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
858     rtpstream_info_t *stream_a, *stream_b = NULL;
859 
860     QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
861     RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
862     stream_a = rsti->streamInfo();
863     if (ui->streamTreeWidget->selectedItems().count() > 1) {
864         ti = ui->streamTreeWidget->selectedItems()[1];
865         rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
866         stream_b = rsti->streamInfo();
867     }
868 
869     if (stream_a == NULL && stream_b == NULL) return;
870 
871     // XXX Mark the setup frame as well?
872     need_redraw_ = false;
873     rtpstream_mark(&tapinfo_, cap_file_.capFile(), stream_a, stream_b);
874     updateWidgets();
875 }
876 
on_actionPrepareFilter_triggered()877 void RtpStreamDialog::on_actionPrepareFilter_triggered()
878 {
879     QVector<rtpstream_id_t *> ids = getSelectedRtpIds();
880     QString filter = make_filter_based_on_rtpstream_id(ids);
881     if (filter.length() > 0) {
882         remove_tap_listener_rtpstream(&tapinfo_);
883         emit updateFilter(filter);
884     }
885 }
886 
on_streamTreeWidget_itemSelectionChanged()887 void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged()
888 {
889     updateWidgets();
890 }
891 
on_buttonBox_helpRequested()892 void RtpStreamDialog::on_buttonBox_helpRequested()
893 {
894     wsApp->helpTopicAction(HELP_TELEPHONY_RTP_STREAMS_DIALOG);
895 }
896 
on_displayFilterCheckBox_toggled(bool checked _U_)897 void RtpStreamDialog::on_displayFilterCheckBox_toggled(bool checked _U_)
898 {
899     if (!cap_file_.isValid()) {
900         return;
901     }
902 
903     tapinfo_.apply_display_filter = checked;
904 
905     cap_file_.retapPackets();
906 }
907 
on_todCheckBox_toggled(bool checked)908 void RtpStreamDialog::on_todCheckBox_toggled(bool checked)
909 {
910     QTreeWidgetItemIterator iter(ui->streamTreeWidget);
911     while (*iter) {
912         RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
913         rsti->setTOD(checked);
914         rsti->drawData();
915         ++iter;
916     }
917     ui->streamTreeWidget->resizeColumnToContents(start_time_col_);
918 }
919 
on_actionSelectAll_triggered()920 void RtpStreamDialog::on_actionSelectAll_triggered()
921 {
922     ui->streamTreeWidget->selectAll();
923 }
924 
on_actionSelectInvert_triggered()925 void RtpStreamDialog::on_actionSelectInvert_triggered()
926 {
927     invertSelection();
928 }
929 
on_actionSelectNone_triggered()930 void RtpStreamDialog::on_actionSelectNone_triggered()
931 {
932     ui->streamTreeWidget->clearSelection();
933 }
934 
getSelectedRtpIds()935 QVector<rtpstream_id_t *>RtpStreamDialog::getSelectedRtpIds()
936 {
937     // Gather up our selected streams...
938     QVector<rtpstream_id_t *> stream_ids;
939     foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
940         RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
941         rtpstream_info_t *selected_stream = rsti->streamInfo();
942         if (selected_stream) {
943             stream_ids << &(selected_stream->id);
944         }
945     }
946 
947     return stream_ids;
948 }
949 
rtpPlayerReplace()950 void RtpStreamDialog::rtpPlayerReplace()
951 {
952     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
953 
954     emit rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
955 }
956 
rtpPlayerAdd()957 void RtpStreamDialog::rtpPlayerAdd()
958 {
959     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
960 
961     emit rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
962 }
963 
rtpPlayerRemove()964 void RtpStreamDialog::rtpPlayerRemove()
965 {
966     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
967 
968     emit rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
969 }
970 
rtpAnalysisReplace()971 void RtpStreamDialog::rtpAnalysisReplace()
972 {
973     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
974 
975     emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpIds());
976 }
977 
rtpAnalysisAdd()978 void RtpStreamDialog::rtpAnalysisAdd()
979 {
980     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
981 
982     emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpIds());
983 }
984 
rtpAnalysisRemove()985 void RtpStreamDialog::rtpAnalysisRemove()
986 {
987     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
988 
989     emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpIds());
990 }
991 
displayFilterSuccess(bool success)992 void RtpStreamDialog::displayFilterSuccess(bool success)
993 {
994     if (success && ui->displayFilterCheckBox->isChecked()) {
995         cap_file_.retapPackets();
996     }
997 }
998 
invertSelection()999 void RtpStreamDialog::invertSelection()
1000 {
1001     ui->streamTreeWidget->blockSignals(true);
1002     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
1003         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1004         ti->setSelected(!ti->isSelected());
1005     }
1006     ui->streamTreeWidget->blockSignals(false);
1007     updateWidgets();
1008 }
1009 
on_actionAnalyze_triggered()1010 void RtpStreamDialog::on_actionAnalyze_triggered()
1011 {
1012     RtpStreamDialog::rtpAnalysisAdd();
1013 }
1014 
1015