Kerberos: add kerberos_inject_longterm_key() helper function
[wireshark-sm.git] / ui / qt / rtp_stream_dialog.cpp
blobc0ad1534a26a46f414a053cc2b32f43ef7fc0ad6
1 /* rtp_stream_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
10 #include "rtp_stream_dialog.h"
11 #include <ui_rtp_stream_dialog.h>
13 #include "file.h"
15 #include "epan/addr_resolv.h"
16 #include <epan/rtp_pt.h>
18 #include <wsutil/utf8_entities.h>
20 #include <ui/qt/utils/qt_ui_utils.h>
21 #include "rtp_analysis_dialog.h"
22 #include "progress_frame.h"
23 #include "main_application.h"
24 #include "ui/qt/widgets/wireshark_file_dialog.h"
26 #include <QAction>
27 #include <QClipboard>
28 #include <QKeyEvent>
29 #include <QPushButton>
30 #include <QTextStream>
31 #include <QTreeWidgetItem>
32 #include <QTreeWidgetItemIterator>
33 #include <QDateTime>
35 #include <ui/qt/utils/color_utils.h>
38 * @file RTP stream dialog
40 * Displays a list of RTP streams with the following information:
41 * - UDP 4-tuple
42 * - SSRC
43 * - Payload type
44 * - Stats: Packets, lost, max delta, max jitter, mean jitter
45 * - Problems
47 * Finds reverse streams
48 * "Save As" rtpdump
49 * Mark packets
50 * Go to the setup frame
51 * Prepare filter
52 * Copy As CSV and YAML
53 * Analyze
56 // To do:
57 // - Add more statistics to the hint text (e.g. lost packets).
58 // - Add more statistics to the main list (e.g. stream duration)
60 const int src_addr_col_ = 0;
61 const int src_port_col_ = 1;
62 const int dst_addr_col_ = 2;
63 const int dst_port_col_ = 3;
64 const int ssrc_col_ = 4;
65 const int start_time_col_ = 5;
66 const int duration_col_ = 6;
67 const int payload_col_ = 7;
68 const int packets_col_ = 8;
69 const int lost_col_ = 9;
70 const int min_delta_col_ = 10;
71 const int mean_delta_col_ = 11;
72 const int max_delta_col_ = 12;
73 const int min_jitter_col_ = 13;
74 const int mean_jitter_col_ = 14;
75 const int max_jitter_col_ = 15;
76 const int status_col_ = 16;
77 const int ssrc_fmt_col_ = 17;
78 const int lost_perc_col_ = 18;
80 enum { rtp_stream_type_ = 1000 };
82 bool operator==(rtpstream_id_t const& a, rtpstream_id_t const& b);
84 class RtpStreamTreeWidgetItem : public QTreeWidgetItem
86 public:
87 RtpStreamTreeWidgetItem(QTreeWidget *tree, rtpstream_info_t *stream_info) :
88 QTreeWidgetItem(tree, rtp_stream_type_),
89 stream_info_(stream_info),
90 tod_(0)
92 drawData();
95 rtpstream_info_t *streamInfo() const { return stream_info_; }
97 void drawData() {
98 rtpstream_info_calc_t calc;
100 if (!stream_info_) {
101 return;
103 rtpstream_info_calculate(stream_info_, &calc);
105 setText(src_addr_col_, calc.src_addr_str);
106 setText(src_port_col_, QString::number(calc.src_port));
107 setText(dst_addr_col_, calc.dst_addr_str);
108 setText(dst_port_col_, QString::number(calc.dst_port));
109 setText(ssrc_col_, QStringLiteral("0x%1").arg(calc.ssrc, 0, 16));
110 if (tod_) {
111 QDateTime abs_dt = QDateTime::fromMSecsSinceEpoch(nstime_to_msec(&stream_info_->start_fd->abs_ts));
112 setText(start_time_col_, QStringLiteral("%1")
113 .arg(abs_dt.toString("yyyy-MM-dd hh:mm:ss.zzz")));
114 } else {
115 setText(start_time_col_, QString::number(calc.start_time_ms, 'f', 6));
117 setText(duration_col_, QString::number(calc.duration_ms, 'f', prefs.gui_decimal_places1));
118 setText(payload_col_, calc.all_payload_type_names);
119 setText(packets_col_, QString::number(calc.packet_count));
120 setText(lost_col_, QObject::tr("%1 (%L2%)").arg(calc.lost_num).arg(QString::number(calc.lost_perc, 'f', 1)));
121 setText(min_delta_col_, QString::number(calc.min_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
122 setText(mean_delta_col_, QString::number(calc.mean_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
123 setText(max_delta_col_, QString::number(calc.max_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
124 setText(min_jitter_col_, QString::number(calc.min_jitter, 'f', prefs.gui_decimal_places3));
125 setText(mean_jitter_col_, QString::number(calc.mean_jitter, 'f', prefs.gui_decimal_places3));
126 setText(max_jitter_col_, QString::number(calc.max_jitter, 'f', prefs.gui_decimal_places3));
128 if (calc.problem) {
129 setText(status_col_, UTF8_BULLET);
130 setTextAlignment(status_col_, Qt::AlignCenter);
131 QColor bgColor(ColorUtils::warningBackground());
132 QColor textColor(QApplication::palette().text().color());
133 for (int i = 0; i < columnCount(); i++) {
134 QBrush bgBrush = background(i);
135 bgBrush.setColor(bgColor);
136 bgBrush.setStyle(Qt::SolidPattern);
137 setBackground(i, bgBrush);
138 QBrush fgBrush = foreground(i);
139 fgBrush.setColor(textColor);
140 fgBrush.setStyle(Qt::SolidPattern);
141 setForeground(i, fgBrush);
145 rtpstream_info_calc_free(&calc);
147 // Return a QString, int, double, or invalid QVariant representing the raw column data.
148 QVariant colData(int col) const {
149 rtpstream_info_calc_t calc;
150 if (!stream_info_) {
151 return QVariant();
154 QVariant ret;
155 rtpstream_info_calculate(stream_info_, &calc);
157 switch(col) {
158 case src_addr_col_:
159 ret = QVariant(text(col));
160 break;
161 case src_port_col_:
162 ret = calc.src_port;
163 break;
164 case dst_addr_col_:
165 ret = text(col);
166 break;
167 case dst_port_col_:
168 ret = calc.dst_port;
169 break;
170 case ssrc_col_:
171 ret = calc.ssrc;
172 break;
173 case start_time_col_:
174 ret = calc.start_time_ms;
175 break;
176 case duration_col_:
177 ret = calc.duration_ms;
178 break;
179 case payload_col_:
180 ret = text(col);
181 break;
182 case packets_col_:
183 ret = calc.packet_count;
184 break;
185 case lost_col_:
186 ret = calc.lost_num;
187 break;
188 case min_delta_col_:
189 ret = calc.min_delta;
190 break;
191 case mean_delta_col_:
192 ret = calc.mean_delta;
193 break;
194 case max_delta_col_:
195 ret = calc.max_delta;
196 break;
197 case min_jitter_col_:
198 ret = calc.min_jitter;
199 break;
200 case mean_jitter_col_:
201 ret = calc.mean_jitter;
202 break;
203 case max_jitter_col_:
204 ret = calc.max_jitter;
205 break;
206 case status_col_:
207 ret = calc.problem ? "Problem" : "";
208 break;
209 case ssrc_fmt_col_:
210 ret = QStringLiteral("0x%1").arg(calc.ssrc, 0, 16);
211 break;
212 case lost_perc_col_:
213 ret = QString::number(calc.lost_perc, 'f', prefs.gui_decimal_places1);
214 break;
215 default:
216 ret = QVariant();
217 break;
219 rtpstream_info_calc_free(&calc);
220 return ret;
223 bool operator< (const QTreeWidgetItem &other) const
225 rtpstream_info_calc_t calc1;
226 rtpstream_info_calc_t calc2;
227 bool ret;
229 if (other.type() != rtp_stream_type_) return QTreeWidgetItem::operator <(other);
230 const RtpStreamTreeWidgetItem &other_rstwi = dynamic_cast<const RtpStreamTreeWidgetItem&>(other);
232 switch (treeWidget()->sortColumn()) {
233 case src_addr_col_:
234 return cmp_address(&(stream_info_->id.src_addr), &(other_rstwi.stream_info_->id.src_addr)) < 0;
235 case src_port_col_:
236 return stream_info_->id.src_port < other_rstwi.stream_info_->id.src_port;
237 case dst_addr_col_:
238 return cmp_address(&(stream_info_->id.dst_addr), &(other_rstwi.stream_info_->id.dst_addr)) < 0;
239 case dst_port_col_:
240 return stream_info_->id.dst_port < other_rstwi.stream_info_->id.dst_port;
241 case ssrc_col_:
242 return stream_info_->id.ssrc < other_rstwi.stream_info_->id.ssrc;
243 case start_time_col_:
244 rtpstream_info_calculate(stream_info_, &calc1);
245 rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
246 ret = calc1.start_time_ms < calc2.start_time_ms;
247 rtpstream_info_calc_free(&calc1);
248 rtpstream_info_calc_free(&calc2);
249 return ret;
250 case duration_col_:
251 rtpstream_info_calculate(stream_info_, &calc1);
252 rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
253 ret = calc1.duration_ms < calc2.duration_ms;
254 rtpstream_info_calc_free(&calc1);
255 rtpstream_info_calc_free(&calc2);
256 return ret;
257 case payload_col_:
258 return g_strcmp0(stream_info_->all_payload_type_names, other_rstwi.stream_info_->all_payload_type_names);
259 case packets_col_:
260 return stream_info_->packet_count < other_rstwi.stream_info_->packet_count;
261 case lost_col_:
262 rtpstream_info_calculate(stream_info_, &calc1);
263 rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
264 /* XXX: Should this sort on the total number or the percentage?
265 * lost_num is displayed first and lost_perc in parenthesis,
266 * so let's use the total number.
268 ret = calc1.lost_num < calc2.lost_num;
269 rtpstream_info_calc_free(&calc1);
270 rtpstream_info_calc_free(&calc2);
271 return ret;
272 case min_delta_col_:
273 return stream_info_->rtp_stats.min_delta < other_rstwi.stream_info_->rtp_stats.min_delta;
274 case mean_delta_col_:
275 return stream_info_->rtp_stats.mean_delta < other_rstwi.stream_info_->rtp_stats.mean_delta;
276 case max_delta_col_:
277 return stream_info_->rtp_stats.max_delta < other_rstwi.stream_info_->rtp_stats.max_delta;
278 case min_jitter_col_:
279 return stream_info_->rtp_stats.min_jitter < other_rstwi.stream_info_->rtp_stats.min_jitter;
280 case mean_jitter_col_:
281 return stream_info_->rtp_stats.mean_jitter < other_rstwi.stream_info_->rtp_stats.mean_jitter;
282 case max_jitter_col_:
283 return stream_info_->rtp_stats.max_jitter < other_rstwi.stream_info_->rtp_stats.max_jitter;
284 default:
285 break;
288 // Fall back to string comparison
289 return QTreeWidgetItem::operator <(other);
292 void setTOD(bool tod)
294 tod_ = tod;
297 private:
298 rtpstream_info_t *stream_info_;
299 bool tod_;
303 RtpStreamDialog *RtpStreamDialog::pinstance_{nullptr};
304 std::mutex RtpStreamDialog::mutex_;
306 RtpStreamDialog *RtpStreamDialog::openRtpStreamDialog(QWidget &parent, CaptureFile &cf, QObject *packet_list)
308 std::lock_guard<std::mutex> lock(mutex_);
309 if (pinstance_ == nullptr)
311 pinstance_ = new RtpStreamDialog(parent, cf);
312 connect(pinstance_, SIGNAL(packetsMarked()),
313 packet_list, SLOT(redrawVisiblePackets()));
314 connect(pinstance_, SIGNAL(goToPacket(int)),
315 packet_list, SLOT(goToPacket(int)));
317 return pinstance_;
320 RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) :
321 WiresharkDialog(parent, cf),
322 ui(new Ui::RtpStreamDialog),
323 need_redraw_(false)
325 ui->setupUi(this);
326 loadGeometry(parent.width() * 4 / 5, parent.height() * 2 / 3);
327 setWindowSubtitle(tr("RTP Streams"));
328 ui->streamTreeWidget->installEventFilter(this);
330 ctx_menu_.addMenu(ui->menuSelect);
331 ctx_menu_.addMenu(ui->menuFindReverse);
332 ctx_menu_.addAction(ui->actionGoToSetup);
333 ctx_menu_.addAction(ui->actionMarkPackets);
334 ctx_menu_.addAction(ui->actionPrepareFilter);
335 ctx_menu_.addAction(ui->actionExportAsRtpDump);
336 ctx_menu_.addAction(ui->actionCopyAsCsv);
337 ctx_menu_.addAction(ui->actionCopyAsYaml);
338 ctx_menu_.addAction(ui->actionAnalyze);
339 set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
341 ui->streamTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
342 ui->streamTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
343 connect(ui->streamTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
344 SLOT(showStreamMenu(QPoint)));
346 find_reverse_button_ = new QToolButton();
347 ui->buttonBox->addButton(find_reverse_button_, QDialogButtonBox::ActionRole);
348 find_reverse_button_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
349 find_reverse_button_->setPopupMode(QToolButton::MenuButtonPopup);
351 connect(ui->actionFindReverse, &QAction::triggered, this, &RtpStreamDialog::on_actionFindReverseNormal_triggered);
352 find_reverse_button_->setDefaultAction(ui->actionFindReverse);
353 // Overrides text striping of shortcut undercode in QAction
354 find_reverse_button_->setText(ui->actionFindReverseNormal->text());
355 find_reverse_button_->setMenu(ui->menuFindReverse);
357 analyze_button_ = RtpAnalysisDialog::addAnalyzeButton(ui->buttonBox, this);
358 prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
359 prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
360 connect(prepare_button_, &QPushButton::pressed, this, &RtpStreamDialog::on_actionPrepareFilter_triggered);
361 player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
362 copy_button_ = ui->buttonBox->addButton(ui->actionCopyButton->text(), QDialogButtonBox::ActionRole);
363 copy_button_->setToolTip(ui->actionCopyButton->toolTip());
364 export_button_ = ui->buttonBox->addButton(ui->actionExportAsRtpDump->text(), QDialogButtonBox::ActionRole);
365 export_button_->setToolTip(ui->actionExportAsRtpDump->toolTip());
366 connect(export_button_, &QPushButton::pressed, this, &RtpStreamDialog::on_actionExportAsRtpDump_triggered);
368 QMenu *copy_menu = new QMenu(copy_button_);
369 QAction *ca;
370 ca = copy_menu->addAction(tr("as CSV"));
371 ca->setToolTip(ui->actionCopyAsCsv->toolTip());
372 connect(ca, &QAction::triggered, this, &RtpStreamDialog::on_actionCopyAsCsv_triggered);
373 ca = copy_menu->addAction(tr("as YAML"));
374 ca->setToolTip(ui->actionCopyAsYaml->toolTip());
375 connect(ca, &QAction::triggered, this, &RtpStreamDialog::on_actionCopyAsYaml_triggered);
376 copy_button_->setMenu(copy_menu);
377 connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
378 this, SLOT(captureEvent(CaptureEvent)));
380 /* Register the tap listener */
381 memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
382 tapinfo_.tap_reset = tapReset;
383 tapinfo_.tap_draw = tapDraw;
384 tapinfo_.tap_mark_packet = tapMarkPacket;
385 tapinfo_.tap_data = this;
386 tapinfo_.mode = TAP_ANALYSE;
388 register_tap_listener_rtpstream(&tapinfo_, NULL, show_tap_registration_error);
389 if (cap_file_.isValid() && cap_file_.capFile()->dfilter) {
390 // Activate display filter checking
391 tapinfo_.apply_display_filter = true;
392 ui->displayFilterCheckBox->setChecked(true);
395 connect(ui->displayFilterCheckBox, &QCheckBox::toggled,
396 this, &RtpStreamDialog::displayFilterCheckBoxToggled);
397 connect(this, SIGNAL(updateFilter(QString, bool)),
398 &parent, SLOT(filterPackets(QString, bool)));
399 connect(&parent, SIGNAL(displayFilterSuccess(bool)),
400 this, SLOT(displayFilterSuccess(bool)));
401 connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
402 &parent, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
403 connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
404 &parent, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
405 connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
406 &parent, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
407 connect(this, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
408 &parent, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
409 connect(this, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
410 &parent, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
411 connect(this, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
412 &parent, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
414 ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
416 updateWidgets();
418 if (cap_file_.isValid()) {
419 cap_file_.delayedRetapPackets();
423 RtpStreamDialog::~RtpStreamDialog()
425 std::lock_guard<std::mutex> lock(mutex_);
426 freeLastSelected();
427 delete ui;
428 rtpstream_reset(&tapinfo_);
429 remove_tap_listener_rtpstream(&tapinfo_);
430 pinstance_ = nullptr;
433 void RtpStreamDialog::setRtpStreamSelection(rtpstream_id_t *id, bool state)
435 QTreeWidgetItemIterator iter(ui->streamTreeWidget);
436 while (*iter) {
437 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
438 rtpstream_info_t *stream_info = rsti->streamInfo();
439 if (stream_info) {
440 if (rtpstream_id_equal(id,&stream_info->id,RTPSTREAM_ID_EQUAL_SSRC)) {
441 (*iter)->setSelected(state);
444 ++iter;
448 void RtpStreamDialog::selectRtpStream(QVector<rtpstream_id_t *> stream_ids)
450 std::lock_guard<std::mutex> lock(mutex_);
451 foreach(rtpstream_id_t *id, stream_ids) {
452 setRtpStreamSelection(id, true);
456 void RtpStreamDialog::deselectRtpStream(QVector<rtpstream_id_t *> stream_ids)
458 std::lock_guard<std::mutex> lock(mutex_);
459 foreach(rtpstream_id_t *id, stream_ids) {
460 setRtpStreamSelection(id, false);
464 bool RtpStreamDialog::eventFilter(QObject *, QEvent *event)
466 if (ui->streamTreeWidget->hasFocus() && event->type() == QEvent::KeyPress) {
467 QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
468 switch(keyEvent.key()) {
469 case Qt::Key_G:
470 on_actionGoToSetup_triggered();
471 return true;
472 case Qt::Key_M:
473 on_actionMarkPackets_triggered();
474 return true;
475 case Qt::Key_P:
476 on_actionPrepareFilter_triggered();
477 return true;
478 case Qt::Key_R:
479 if (keyEvent.modifiers() == Qt::ShiftModifier) {
480 on_actionFindReversePair_triggered();
481 } else if (keyEvent.modifiers() == Qt::ControlModifier) {
482 on_actionFindReverseSingle_triggered();
483 } else {
484 on_actionFindReverseNormal_triggered();
486 return true;
487 case Qt::Key_I:
488 if (keyEvent.modifiers() == Qt::ControlModifier) {
489 // Ctrl+I
490 on_actionSelectInvert_triggered();
491 return true;
493 break;
494 case Qt::Key_A:
495 if (keyEvent.modifiers() == Qt::ControlModifier) {
496 // Ctrl+A
497 on_actionSelectAll_triggered();
498 return true;
499 } else if (keyEvent.modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
500 // Ctrl+Shift+A
501 on_actionSelectNone_triggered();
502 return true;
503 } else if (keyEvent.modifiers() == Qt::NoModifier) {
504 on_actionAnalyze_triggered();
506 break;
507 default:
508 break;
511 return false;
514 void RtpStreamDialog::captureEvent(CaptureEvent e)
516 if (e.captureContext() == CaptureEvent::Retap)
518 switch (e.eventType())
520 case CaptureEvent::Started:
521 ui->displayFilterCheckBox->setEnabled(false);
522 break;
523 case CaptureEvent::Finished:
524 ui->displayFilterCheckBox->setEnabled(true);
525 break;
526 default:
527 break;
533 void RtpStreamDialog::tapReset(rtpstream_tapinfo_t *tapinfo)
535 RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
536 if (rtp_stream_dialog) {
537 rtp_stream_dialog->freeLastSelected();
538 /* Copy currently selected rtpstream_ids */
539 QTreeWidgetItemIterator iter(rtp_stream_dialog->ui->streamTreeWidget);
540 rtpstream_id_t selected_id;
541 while (*iter) {
542 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
543 rtpstream_info_t *stream_info = rsti->streamInfo();
544 if ((*iter)->isSelected()) {
545 /* QList.append() does a member by member copy, so allocate new
546 * addresses. rtpstream_id_copy() overwrites all struct members.
548 rtpstream_id_copy(&stream_info->id, &selected_id);
549 rtp_stream_dialog->last_selected_.append(selected_id);
551 ++iter;
553 /* invalidate items which refer to old strinfo_list items. */
554 rtp_stream_dialog->ui->streamTreeWidget->clear();
558 void RtpStreamDialog::tapDraw(rtpstream_tapinfo_t *tapinfo)
560 RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
561 if (rtp_stream_dialog) {
562 rtp_stream_dialog->updateStreams();
566 void RtpStreamDialog::tapMarkPacket(rtpstream_tapinfo_t *tapinfo, frame_data *fd)
568 if (!tapinfo) return;
570 RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
571 if (rtp_stream_dialog) {
572 cf_mark_frame(rtp_stream_dialog->cap_file_.capFile(), fd);
573 rtp_stream_dialog->need_redraw_ = true;
577 /* Operator == for rtpstream_id_t */
578 bool operator==(rtpstream_id_t const& a, rtpstream_id_t const& b)
580 return rtpstream_id_equal(&a, &b, RTPSTREAM_ID_EQUAL_SSRC);
583 void RtpStreamDialog::updateStreams()
585 // string_list is reverse ordered, so we must add
586 // just first "to_insert_count" of streams
587 GList *cur_stream = g_list_first(tapinfo_.strinfo_list);
588 unsigned tap_len = g_list_length(tapinfo_.strinfo_list);
589 unsigned tree_len = static_cast<unsigned>(ui->streamTreeWidget->topLevelItemCount());
590 unsigned to_insert_count = tap_len - tree_len;
592 // Add any missing items
593 while (cur_stream && cur_stream->data && to_insert_count) {
594 rtpstream_info_t *stream_info = gxx_list_data(rtpstream_info_t*, cur_stream);
595 RtpStreamTreeWidgetItem *rsti = new RtpStreamTreeWidgetItem(ui->streamTreeWidget, stream_info);
596 cur_stream = gxx_list_next(cur_stream);
597 to_insert_count--;
599 // Check if item was selected last time. If so, select it
600 if (-1 != last_selected_.indexOf(stream_info->id)) {
601 rsti->setSelected(true);
605 // Recalculate values
606 QTreeWidgetItemIterator iter(ui->streamTreeWidget);
607 while (*iter) {
608 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
609 rsti->drawData();
610 ++iter;
613 // Resize columns
614 for (int i = 0; i < ui->streamTreeWidget->columnCount(); i++) {
615 ui->streamTreeWidget->resizeColumnToContents(i);
618 ui->streamTreeWidget->setSortingEnabled(true);
620 updateWidgets();
622 if (need_redraw_) {
623 emit packetsMarked();
624 need_redraw_ = false;
628 void RtpStreamDialog::updateWidgets()
630 bool selected = ui->streamTreeWidget->selectedItems().count() > 0;
632 QString hint = "<small><i>";
633 hint += tr("%1 streams").arg(ui->streamTreeWidget->topLevelItemCount());
635 if (selected) {
636 int tot_packets = 0;
637 foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
638 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
639 if (rsti->streamInfo()) {
640 tot_packets += rsti->streamInfo()->packet_count;
643 hint += tr(", %1 selected, %2 total packets")
644 .arg(ui->streamTreeWidget->selectedItems().count())
645 .arg(tot_packets);
648 hint += ". Right-click for more options.";
649 hint += "</i></small>";
650 ui->hintLabel->setText(hint);
652 bool enable = selected && !file_closed_;
653 bool has_data = ui->streamTreeWidget->topLevelItemCount() > 0;
655 find_reverse_button_->setEnabled(has_data);
656 prepare_button_->setEnabled(enable);
657 export_button_->setEnabled(enable);
658 copy_button_->setEnabled(has_data);
659 analyze_button_->setEnabled(enable);
661 ui->actionFindReverseNormal->setEnabled(enable);
662 ui->actionFindReversePair->setEnabled(has_data);
663 ui->actionFindReverseSingle->setEnabled(has_data);
664 ui->actionGoToSetup->setEnabled(enable);
665 ui->actionMarkPackets->setEnabled(enable);
666 ui->actionPrepareFilter->setEnabled(enable);
667 ui->actionExportAsRtpDump->setEnabled(enable);
668 ui->actionCopyAsCsv->setEnabled(has_data);
669 ui->actionCopyAsYaml->setEnabled(has_data);
670 ui->actionAnalyze->setEnabled(enable);
672 #if defined(QT_MULTIMEDIA_LIB)
673 player_button_->setEnabled(enable);
674 #endif
676 WiresharkDialog::updateWidgets();
679 QList<QVariant> RtpStreamDialog::streamRowData(int row) const
681 QList<QVariant> row_data;
683 if (row >= ui->streamTreeWidget->topLevelItemCount()) {
684 return row_data;
687 for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
688 if (row < 0) {
689 row_data << ui->streamTreeWidget->headerItem()->text(col);
690 } else {
691 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row));
692 if (rsti) {
693 row_data << rsti->colData(col);
698 // Add additional columns to export
699 if (row < 0) {
700 row_data << QStringLiteral("SSRC formatted");
701 row_data << QStringLiteral("Lost percentage");
702 } else {
703 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row));
704 if (rsti) {
705 row_data << rsti->colData(ssrc_fmt_col_);
706 row_data << rsti->colData(lost_perc_col_);
709 return row_data;
712 void RtpStreamDialog::freeLastSelected()
714 /* Free old IDs */
715 for(int i=0; i<last_selected_.length(); i++) {
716 rtpstream_id_t id = last_selected_.at(i);
717 rtpstream_id_free(&id);
719 /* Clear list and reuse it */
720 last_selected_.clear();
723 void RtpStreamDialog::captureFileClosing()
725 remove_tap_listener_rtpstream(&tapinfo_);
727 WiresharkDialog::captureFileClosing();
730 void RtpStreamDialog::captureFileClosed()
732 ui->todCheckBox->setEnabled(false);
733 ui->displayFilterCheckBox->setEnabled(false);
735 WiresharkDialog::captureFileClosed();
738 void RtpStreamDialog::showStreamMenu(QPoint pos)
740 ui->actionGoToSetup->setEnabled(!file_closed_);
741 ui->actionMarkPackets->setEnabled(!file_closed_);
742 ui->actionPrepareFilter->setEnabled(!file_closed_);
743 ui->actionExportAsRtpDump->setEnabled(!file_closed_);
744 ui->actionAnalyze->setEnabled(!file_closed_);
745 ctx_menu_.popup(ui->streamTreeWidget->viewport()->mapToGlobal(pos));
748 void RtpStreamDialog::on_actionCopyAsCsv_triggered()
750 QString csv;
751 QTextStream stream(&csv, QIODevice::Text);
752 for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
753 QStringList rdsl;
754 foreach (QVariant v, streamRowData(row)) {
755 if (!v.isValid()) {
756 rdsl << "\"\"";
757 } else if (v.userType() == QMetaType::QString) {
758 rdsl << QStringLiteral("\"%1\"").arg(v.toString());
759 } else {
760 rdsl << v.toString();
763 stream << rdsl.join(",") << '\n';
765 mainApp->clipboard()->setText(stream.readAll());
768 void RtpStreamDialog::on_actionCopyAsYaml_triggered()
770 QString yaml;
771 QTextStream stream(&yaml, QIODevice::Text);
772 stream << "---" << '\n';
773 for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row ++) {
774 stream << "-" << '\n';
775 foreach (QVariant v, streamRowData(row)) {
776 stream << " - " << v.toString() << '\n';
779 mainApp->clipboard()->setText(stream.readAll());
782 void RtpStreamDialog::on_actionExportAsRtpDump_triggered()
784 if (file_closed_ || ui->streamTreeWidget->selectedItems().count() < 1) return;
786 // XXX If the user selected multiple frames is this the one we actually want?
787 QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
788 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
789 rtpstream_info_t *stream_info = rsti->streamInfo();
790 if (stream_info) {
791 QString file_name;
792 QDir path(mainApp->openDialogInitialDir());
793 QString save_file = path.canonicalPath() + "/" + cap_file_.fileBaseName();
794 QString extension;
795 file_name = WiresharkFileDialog::getSaveFileName(this, mainApp->windowTitleString(tr("Save RTPDump As…")),
796 save_file, "RTPDump Format (*.rtp)", &extension);
798 if (file_name.length() > 0) {
799 char *dest_file = qstring_strdup(file_name);
800 bool save_ok = rtpstream_save(&tapinfo_, cap_file_.capFile(), stream_info, dest_file);
801 g_free(dest_file);
802 // else error dialog?
803 if (save_ok) {
804 mainApp->setLastOpenDirFromFilename(file_name);
811 // Search for reverse stream of every selected stream
812 void RtpStreamDialog::on_actionFindReverseNormal_triggered()
814 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
816 ui->streamTreeWidget->blockSignals(true);
818 // Traverse all items and if stream is selected, search reverse from
819 // current position till last item (NxN/2)
820 for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
821 RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
822 rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
823 if (fwd_stream && fwd_rsti->isSelected()) {
824 for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
825 RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
826 rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
827 if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
828 rev_rsti->setSelected(true);
829 break;
834 ui->streamTreeWidget->blockSignals(false);
835 updateWidgets();
838 // Select all pairs of forward/reverse streams
839 void RtpStreamDialog::on_actionFindReversePair_triggered()
841 ui->streamTreeWidget->blockSignals(true);
842 ui->streamTreeWidget->clearSelection();
844 // Traverse all items and search reverse from current position till last
845 // item (NxN/2)
846 for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
847 RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
848 rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
849 if (fwd_stream) {
850 for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
851 RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
852 rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
853 if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
854 fwd_rsti->setSelected(true);
855 rev_rsti->setSelected(true);
856 break;
861 ui->streamTreeWidget->blockSignals(false);
862 updateWidgets();
865 // Select all streams which don't have reverse stream
866 void RtpStreamDialog::on_actionFindReverseSingle_triggered()
868 ui->streamTreeWidget->blockSignals(true);
869 ui->streamTreeWidget->selectAll();
871 // Traverse all items and search reverse from current position till last
872 // item (NxN/2)
873 for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
874 RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
875 rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
876 if (fwd_stream) {
877 for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
878 RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
879 rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
880 if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
881 fwd_rsti->setSelected(false);
882 rev_rsti->setSelected(false);
883 break;
888 ui->streamTreeWidget->blockSignals(false);
889 updateWidgets();
892 void RtpStreamDialog::on_actionGoToSetup_triggered()
894 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
895 // XXX If the user selected multiple frames is this the one we actually want?
896 QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
897 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
898 rtpstream_info_t *stream_info = rsti->streamInfo();
899 if (stream_info) {
900 emit goToPacket(stream_info->setup_frame_number);
904 void RtpStreamDialog::on_actionMarkPackets_triggered()
906 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
907 rtpstream_info_t *stream_a, *stream_b = NULL;
909 QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
910 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
911 stream_a = rsti->streamInfo();
912 if (ui->streamTreeWidget->selectedItems().count() > 1) {
913 ti = ui->streamTreeWidget->selectedItems()[1];
914 rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
915 stream_b = rsti->streamInfo();
918 if (stream_a == NULL && stream_b == NULL) return;
920 // XXX Mark the setup frame as well?
921 need_redraw_ = false;
922 rtpstream_mark(&tapinfo_, cap_file_.capFile(), stream_a, stream_b);
923 updateWidgets();
926 void RtpStreamDialog::on_actionPrepareFilter_triggered()
928 QVector<rtpstream_id_t *> ids = getSelectedRtpIds();
929 QString filter = make_filter_based_on_rtpstream_id(ids);
930 if (filter.length() > 0) {
931 remove_tap_listener_rtpstream(&tapinfo_);
932 emit updateFilter(filter);
936 void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged()
938 updateWidgets();
941 void RtpStreamDialog::on_buttonBox_helpRequested()
943 mainApp->helpTopicAction(HELP_TELEPHONY_RTP_STREAMS_DIALOG);
946 void RtpStreamDialog::displayFilterCheckBoxToggled(bool checked)
948 if (!cap_file_.isValid()) {
949 return;
952 tapinfo_.apply_display_filter = checked;
954 cap_file_.retapPackets();
957 void RtpStreamDialog::on_todCheckBox_toggled(bool checked)
959 QTreeWidgetItemIterator iter(ui->streamTreeWidget);
960 while (*iter) {
961 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
962 rsti->setTOD(checked);
963 rsti->drawData();
964 ++iter;
966 ui->streamTreeWidget->resizeColumnToContents(start_time_col_);
969 void RtpStreamDialog::on_actionSelectAll_triggered()
971 ui->streamTreeWidget->selectAll();
974 void RtpStreamDialog::on_actionSelectInvert_triggered()
976 invertSelection();
979 void RtpStreamDialog::on_actionSelectNone_triggered()
981 ui->streamTreeWidget->clearSelection();
984 QVector<rtpstream_id_t *>RtpStreamDialog::getSelectedRtpIds()
986 // Gather up our selected streams...
987 QVector<rtpstream_id_t *> stream_ids;
988 foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
989 RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
990 rtpstream_info_t *selected_stream = rsti->streamInfo();
991 if (selected_stream) {
992 stream_ids << &(selected_stream->id);
996 return stream_ids;
999 void RtpStreamDialog::rtpPlayerReplace()
1001 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
1003 emit rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
1006 void RtpStreamDialog::rtpPlayerAdd()
1008 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
1010 emit rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
1013 void RtpStreamDialog::rtpPlayerRemove()
1015 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
1017 emit rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
1020 void RtpStreamDialog::rtpAnalysisReplace()
1022 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
1024 emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpIds());
1027 void RtpStreamDialog::rtpAnalysisAdd()
1029 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
1031 emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpIds());
1034 void RtpStreamDialog::rtpAnalysisRemove()
1036 if (ui->streamTreeWidget->selectedItems().count() < 1) return;
1038 emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpIds());
1041 void RtpStreamDialog::displayFilterSuccess(bool success)
1043 if (success && ui->displayFilterCheckBox->isChecked()) {
1044 cap_file_.retapPackets();
1048 void RtpStreamDialog::invertSelection()
1050 ui->streamTreeWidget->blockSignals(true);
1051 for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
1052 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1053 ti->setSelected(!ti->isSelected());
1055 ui->streamTreeWidget->blockSignals(false);
1056 updateWidgets();
1059 void RtpStreamDialog::on_actionAnalyze_triggered()
1061 RtpStreamDialog::rtpAnalysisAdd();