summaryrefslogtreecommitdiffstats
path: root/ui/qt/voip_calls_dialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/qt/voip_calls_dialog.cpp')
-rw-r--r--ui/qt/voip_calls_dialog.cpp842
1 files changed, 842 insertions, 0 deletions
diff --git a/ui/qt/voip_calls_dialog.cpp b/ui/qt/voip_calls_dialog.cpp
new file mode 100644
index 00000000..b8a54bd7
--- /dev/null
+++ b/ui/qt/voip_calls_dialog.cpp
@@ -0,0 +1,842 @@
+/* voip_calls_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "voip_calls_dialog.h"
+#include <ui_voip_calls_dialog.h>
+
+#include "file.h"
+
+#include "epan/addr_resolv.h"
+#include "epan/dissectors/packet-h225.h"
+
+#include "ui/rtp_stream.h"
+#include "ui/rtp_stream_id.h"
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include "rtp_player_dialog.h"
+#include "sequence_dialog.h"
+#include <ui/qt/utils/stock_icon.h>
+#include "progress_frame.h"
+#include "main_application.h"
+#include <ui/qt/models/voip_calls_info_model.h>
+
+#include <QClipboard>
+#include <QContextMenuEvent>
+#include <QToolButton>
+
+// To do:
+// - More context menu items
+// - Don't select on right click
+// - Player
+// - Add a screenshot to the user's guide
+// - Add filter for quickly searching through list?
+
+// Bugs:
+// - Preparing a filter overwrites the existing filter. The GTK+ UI appends.
+// We'll probably have to add an "append" parameter to MainWindow::filterPackets.
+
+enum { voip_calls_type_ = 1000 };
+
+VoipCallsDialog *VoipCallsDialog::pinstance_voip_{nullptr};
+VoipCallsDialog *VoipCallsDialog::pinstance_sip_{nullptr};
+std::mutex VoipCallsDialog::init_mutex_;
+
+VoipCallsDialog *VoipCallsDialog::openVoipCallsDialogVoip(QWidget &parent, CaptureFile &cf, QObject *packet_list)
+{
+ std::lock_guard<std::mutex> lock(init_mutex_);
+ if (pinstance_voip_ == nullptr)
+ {
+ pinstance_voip_ = new VoipCallsDialog(parent, cf, false);
+ connect(pinstance_voip_, SIGNAL(goToPacket(int)),
+ packet_list, SLOT(goToPacket(int)));
+ }
+ return pinstance_voip_;
+}
+
+VoipCallsDialog *VoipCallsDialog::openVoipCallsDialogSip(QWidget &parent, CaptureFile &cf, QObject *packet_list)
+{
+ std::lock_guard<std::mutex> lock(init_mutex_);
+ if (pinstance_sip_ == nullptr)
+ {
+ pinstance_sip_ = new VoipCallsDialog(parent, cf, true);
+ connect(pinstance_sip_, SIGNAL(goToPacket(int)),
+ packet_list, SLOT(goToPacket(int)));
+ }
+ return pinstance_sip_;
+}
+
+VoipCallsDialog::VoipCallsDialog(QWidget &parent, CaptureFile &cf, bool all_flows) :
+ WiresharkDialog(parent, cf),
+ all_flows_(all_flows),
+ ui(new Ui::VoipCallsDialog),
+ parent_(parent),
+ voip_calls_tap_listeners_removed_(false)
+{
+ ui->setupUi(this);
+ loadGeometry(parent.width() * 4 / 5, parent.height() * 2 / 3);
+ ui->callTreeView->installEventFilter(this);
+
+ // Create the model that stores the actual data and the proxy model that is
+ // responsible for sorting and filtering data in the display.
+ call_infos_model_ = new VoipCallsInfoModel(this);
+ cache_model_ = new CacheProxyModel(this);
+ cache_model_->setSourceModel(call_infos_model_);
+ sorted_model_ = new VoipCallsInfoSortedModel(this);
+ sorted_model_->setSourceModel(cache_model_);
+ ui->callTreeView->setModel(sorted_model_);
+
+ connect(ui->callTreeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
+ this, SLOT(updateWidgets()));
+ ui->callTreeView->sortByColumn(VoipCallsInfoModel::StartTime, Qt::AscendingOrder);
+ setWindowSubtitle(all_flows_ ? tr("SIP Flows") : tr("VoIP Calls"));
+
+ sequence_button_ = ui->buttonBox->addButton(ui->actionFlowSequence->text(), QDialogButtonBox::ActionRole);
+ sequence_button_->setToolTip(ui->actionFlowSequence->toolTip());
+ prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
+ prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
+ player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
+
+ connect (ui->todCheckBox, &QAbstractButton::toggled, this, &VoipCallsDialog::switchTimeOfDay);
+
+ copy_button_ = ui->buttonBox->addButton(ui->actionCopyButton->text(), QDialogButtonBox::ActionRole);
+ copy_button_->setToolTip(ui->actionCopyButton->toolTip());
+ QMenu *copy_menu = new QMenu(copy_button_);
+ QAction *ca;
+ ca = copy_menu->addAction(tr("as CSV"));
+ connect(ca, SIGNAL(triggered()), this, SLOT(copyAsCSV()));
+ ca = copy_menu->addAction(tr("as YAML"));
+ connect(ca, SIGNAL(triggered()), this, SLOT(copyAsYAML()));
+ copy_button_->setMenu(copy_menu);
+ connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
+ this, SLOT(captureEvent(CaptureEvent)));
+
+ connect(this, SIGNAL(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)));
+
+ memset (&tapinfo_, 0, sizeof(tapinfo_));
+ tapinfo_.tap_packet = tapPacket;
+ tapinfo_.tap_reset = tapReset;
+ tapinfo_.tap_draw = tapDraw;
+ tapinfo_.tap_data = this;
+ tapinfo_.callsinfos = g_queue_new();
+ tapinfo_.h225_cstype = H225_OTHER;
+ tapinfo_.fs_option = all_flows_ ? FLOW_ALL : FLOW_ONLY_INVITES; /* flow show option */
+ tapinfo_.graph_analysis = sequence_analysis_info_new();
+ tapinfo_.graph_analysis->name = "voip";
+ sequence_info_ = new SequenceInfo(tapinfo_.graph_analysis);
+ shown_callsinfos_ = g_queue_new();
+
+ voip_calls_init_all_taps(&tapinfo_);
+ if (cap_file_.isValid() && cap_file_.capFile()->dfilter) {
+ // Activate display filter checking
+ tapinfo_.apply_display_filter = true;
+ ui->displayFilterCheckBox->setChecked(true);
+ }
+
+ connect(ui->displayFilterCheckBox, &QCheckBox::toggled,
+ this, &VoipCallsDialog::displayFilterCheckBoxToggled);
+ connect(this, SIGNAL(updateFilter(QString, bool)),
+ &parent, SLOT(filterPackets(QString, bool)));
+ connect(&parent, SIGNAL(displayFilterSuccess(bool)),
+ this, SLOT(displayFilterSuccess(bool)));
+ connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
+
+ ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
+
+ updateWidgets();
+
+ if (cap_file_.isValid()) {
+ tapinfo_.session = cap_file_.capFile()->epan;
+ cap_file_.delayedRetapPackets();
+ }
+}
+
+bool VoipCallsDialog::eventFilter(QObject *, QEvent *event)
+{
+ if (ui->callTreeView->hasFocus() && event->type() == QEvent::KeyPress) {
+ QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
+ switch(keyEvent.key()) {
+ case Qt::Key_I:
+ if (keyEvent.modifiers() == Qt::ControlModifier) {
+ // Ctrl+I
+ on_actionSelectInvert_triggered();
+ return true;
+ }
+ break;
+ case Qt::Key_A:
+ if (keyEvent.modifiers() == Qt::ControlModifier) {
+ // Ctrl+A
+ on_actionSelectAll_triggered();
+ return true;
+ } else if (keyEvent.modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
+ // Ctrl+Shift+A
+ on_actionSelectNone_triggered();
+ return true;
+ }
+ break;
+ case Qt::Key_S:
+ on_actionSelectRtpStreams_triggered();
+ break;
+ case Qt::Key_D:
+ on_actionDeselectRtpStreams_triggered();
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+VoipCallsDialog::~VoipCallsDialog()
+{
+ std::lock_guard<std::mutex> lock(init_mutex_);
+ if ((all_flows_ && (pinstance_sip_ != nullptr))
+ || (!all_flows_ && (pinstance_voip_ != nullptr))
+ ) {
+ delete ui;
+
+ voip_calls_reset_all_taps(&tapinfo_);
+ if (!voip_calls_tap_listeners_removed_) {
+ voip_calls_remove_all_tap_listeners(&tapinfo_);
+ voip_calls_tap_listeners_removed_ = true;
+ }
+ sequence_info_->unref();
+ g_queue_free(tapinfo_.callsinfos);
+ // We don't need to clear shown_callsinfos_ data, it was shared
+ // with tapinfo_.callsinfos and was cleared
+ // during voip_calls_reset_all_taps
+ g_queue_free(shown_callsinfos_);
+ if (all_flows_) {
+ pinstance_sip_ = nullptr;
+ } else {
+ pinstance_voip_ = nullptr;
+ }
+ }
+}
+
+void VoipCallsDialog::removeTapListeners()
+{
+ if (!voip_calls_tap_listeners_removed_) {
+ voip_calls_remove_all_tap_listeners(&tapinfo_);
+ voip_calls_tap_listeners_removed_ = true;
+ }
+ WiresharkDialog::removeTapListeners();
+}
+
+void VoipCallsDialog::captureFileClosing()
+{
+ // The time formatting is currently provided by VoipCallsInfoModel, but when
+ // the cache is active, the ToD cannot be modified.
+ cache_model_->setSourceModel(NULL);
+ if (!voip_calls_tap_listeners_removed_) {
+ voip_calls_remove_all_tap_listeners(&tapinfo_);
+ voip_calls_tap_listeners_removed_ = true;
+ }
+ tapinfo_.session = NULL;
+
+ WiresharkDialog::captureFileClosing();
+}
+
+void VoipCallsDialog::captureFileClosed()
+{
+ // The time formatting is currently provided by VoipCallsInfoModel, but when
+ // the cache is active, the ToD cannot be modified.
+ ui->todCheckBox->setEnabled(false);
+ ui->displayFilterCheckBox->setEnabled(false);
+
+ WiresharkDialog::captureFileClosed();
+}
+
+void VoipCallsDialog::contextMenuEvent(QContextMenuEvent *event)
+{
+ bool selected = ui->callTreeView->selectionModel()->hasSelection();
+
+ if (! selected)
+ return;
+
+ QMenu *popupMenu = new QMenu(this);
+ QAction *action;
+
+ popupMenu->setAttribute(Qt::WA_DeleteOnClose);
+ popupMenu->addMenu(ui->menuSelect);
+ action = popupMenu->addAction(tr("Display time as time of day"), this, SLOT(switchTimeOfDay()));
+ action->setCheckable(true);
+ action->setChecked(call_infos_model_->timeOfDay());
+ action->setEnabled(!file_closed_);
+ popupMenu->addSeparator();
+ action = popupMenu->addAction(tr("Copy as CSV"), this, SLOT(copyAsCSV()));
+ action->setToolTip(tr("Copy stream list as CSV."));
+ action = popupMenu->addAction(tr("Copy as YAML"), this, SLOT(copyAsYAML()));
+ action->setToolTip(tr("Copy stream list as YAML."));
+ popupMenu->addSeparator();
+ popupMenu->addAction(ui->actionSelectRtpStreams);
+ popupMenu->addAction(ui->actionDeselectRtpStreams);
+
+ popupMenu->popup(event->globalPos());
+}
+
+void VoipCallsDialog::changeEvent(QEvent *event)
+{
+ if (0 != event)
+ {
+ switch (event->type())
+ {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+ }
+ QDialog::changeEvent(event);
+}
+
+void VoipCallsDialog::captureEvent(CaptureEvent e)
+{
+ if (e.captureContext() == CaptureEvent::Retap)
+ {
+ switch (e.eventType())
+ {
+ case CaptureEvent::Started:
+ ui->displayFilterCheckBox->setEnabled(false);
+ break;
+ case CaptureEvent::Finished:
+ ui->displayFilterCheckBox->setEnabled(true);
+ break;
+ default:
+ break;
+ }
+ }
+
+}
+
+void VoipCallsDialog::tapReset(void *tapinfo_ptr)
+{
+ voip_calls_tapinfo_t *tapinfo = static_cast<voip_calls_tapinfo_t *>(tapinfo_ptr);
+ VoipCallsDialog *voip_calls_dialog = static_cast<VoipCallsDialog *>(tapinfo->tap_data);
+
+ // Create new callsinfos queue in tapinfo. Current callsinfos are
+ // in shown_callsinfos_, so don't free the [shared] data stored in
+ // the queue, but do free the queue itself. (Do this before calling
+ // voip_calls_reset_all_taps(), as that frees the data in the queue.)
+ g_queue_free(voip_calls_dialog->tapinfo_.callsinfos);
+ voip_calls_dialog->tapinfo_.callsinfos = g_queue_new();
+ voip_calls_reset_all_taps(tapinfo);
+
+ // Leave old graph_analysis as is and allocate new one
+ voip_calls_dialog->sequence_info_->unref();
+ voip_calls_dialog->tapinfo_.graph_analysis = sequence_analysis_info_new();
+ voip_calls_dialog->tapinfo_.graph_analysis->name = "voip";
+ voip_calls_dialog->sequence_info_ = new SequenceInfo(voip_calls_dialog->tapinfo_.graph_analysis);
+}
+
+tap_packet_status VoipCallsDialog::tapPacket(void *, packet_info *, epan_dissect_t *, const void *, tap_flags_t)
+{
+#ifdef QT_MULTIMEDIA_LIB
+// voip_calls_tapinfo_t *tapinfo = (voip_calls_tapinfo_t *) tapinfo_ptr;
+ // add_rtp_packet for voip player.
+// return TAP_PACKET_REDRAW;
+#endif
+ return TAP_PACKET_DONT_REDRAW;
+}
+
+void VoipCallsDialog::tapDraw(void *tapinfo_ptr)
+{
+ voip_calls_tapinfo_t *tapinfo = static_cast<voip_calls_tapinfo_t *>(tapinfo_ptr);
+
+ if (!tapinfo || !tapinfo->redraw) {
+ return;
+ }
+
+ GList *graph_item = g_queue_peek_nth_link(tapinfo->graph_analysis->items, 0);
+ for (; graph_item; graph_item = gxx_list_next(graph_item)) {
+ for (GList *rsi_entry = g_list_first(tapinfo->rtpstream_list); rsi_entry; rsi_entry = gxx_list_next(rsi_entry)) {
+ seq_analysis_item_t * sai = gxx_list_data(seq_analysis_item_t *, graph_item);
+ rtpstream_info_t *rsi = gxx_list_data(rtpstream_info_t *, rsi_entry);
+
+ if (rsi->start_fd->num == sai->frame_number) {
+ rsi->call_num = sai->conv_num;
+ // VOIP_CALLS_DEBUG("setting conv num %u for frame %u", sai->conv_num, sai->frame_number);
+ }
+ }
+ }
+
+ VoipCallsDialog *voip_calls_dialog = static_cast<VoipCallsDialog *>(tapinfo->tap_data);
+ if (voip_calls_dialog) {
+ voip_calls_dialog->updateCalls();
+ }
+}
+
+gint VoipCallsDialog::compareCallNums(gconstpointer a, gconstpointer b)
+{
+ const voip_calls_info_t *call_a = (const voip_calls_info_t *)a;
+ const voip_calls_info_t *call_b = (const voip_calls_info_t *)b;
+
+ return (call_a->call_num != call_b->call_num);
+}
+
+void VoipCallsDialog::updateCalls()
+{
+ voip_calls_info_t *new_callsinfo;
+ voip_calls_info_t *old_callsinfo;
+ GList *found;
+
+ ui->callTreeView->setSortingEnabled(false);
+
+ // Merge new callsinfos with old ones
+ // It keeps list of calls visible including selected items
+ GList *list = g_queue_peek_nth_link(tapinfo_.callsinfos, 0);
+ while (list) {
+ // Find new callsinfo
+ new_callsinfo = gxx_list_data(voip_calls_info_t*, list);
+ found = g_queue_find_custom(shown_callsinfos_, new_callsinfo, VoipCallsDialog::compareCallNums);
+ if (!found) {
+ // New call, add it to list for show
+ g_queue_push_tail(shown_callsinfos_, new_callsinfo);
+ } else {
+ // Existing call
+ old_callsinfo = (voip_calls_info_t *)found->data;
+ if (new_callsinfo != old_callsinfo) {
+ // Replace it
+ voip_calls_free_callsinfo(old_callsinfo);
+ found->data = new_callsinfo;
+ }
+ }
+
+ list = gxx_list_next(list);
+ }
+
+ // Update model
+ call_infos_model_->updateCalls(shown_callsinfos_);
+
+ // Resize columns
+ for (int i = 0; i < call_infos_model_->columnCount(); i++) {
+ ui->callTreeView->resizeColumnToContents(i);
+ }
+
+ ui->callTreeView->setSortingEnabled(true);
+
+ updateWidgets();
+}
+
+void VoipCallsDialog::updateWidgets()
+{
+ bool selected = ui->callTreeView->selectionModel()->hasSelection();
+ bool have_ga_items = false;
+
+ if (tapinfo_.graph_analysis && tapinfo_.graph_analysis->items) {
+ have_ga_items = true;
+ }
+
+ bool enable = selected && have_ga_items && !file_closed_;
+
+ prepare_button_->setEnabled(enable);
+ sequence_button_->setEnabled(enable);
+ ui->actionSelectRtpStreams->setEnabled(enable);
+ ui->actionDeselectRtpStreams->setEnabled(enable);
+#if defined(QT_MULTIMEDIA_LIB)
+ player_button_->setEnabled(enable);
+#endif
+
+ WiresharkDialog::updateWidgets();
+}
+
+void VoipCallsDialog::prepareFilter()
+{
+ if (!ui->callTreeView->selectionModel()->hasSelection() || !tapinfo_.graph_analysis) {
+ return;
+ }
+
+ QString filter_str;
+ QSet<guint16> selected_calls;
+ QString frame_numbers;
+ QList<int> rows;
+
+ /* Build a new filter based on frame numbers */
+ foreach (QModelIndex index, ui->callTreeView->selectionModel()->selectedIndexes()) {
+ if (index.isValid() && ! rows.contains(index.row()))
+ {
+ voip_calls_info_t *call_info = VoipCallsInfoModel::indexToCallInfo(index);
+ if (!call_info) {
+ return;
+ }
+
+ selected_calls << call_info->call_num;
+ rows << index.row();
+ }
+ }
+
+ GList *cur_ga_item = g_queue_peek_nth_link(tapinfo_.graph_analysis->items, 0);
+ while (cur_ga_item && cur_ga_item->data) {
+ seq_analysis_item_t *ga_item = gxx_list_data(seq_analysis_item_t*, cur_ga_item);
+ if (selected_calls.contains(ga_item->conv_num)) {
+ frame_numbers += QString("%1,").arg(ga_item->frame_number);
+ }
+ cur_ga_item = gxx_list_next(cur_ga_item);
+ }
+
+ if (!frame_numbers.isEmpty()) {
+ frame_numbers.chop(1);
+ filter_str = QString("frame.number in {%1} or rtp.setup-frame in {%1}").arg(frame_numbers);
+ }
+
+#if 0
+ // XXX The GTK+ UI falls back to building a filter based on protocols if the filter
+ // length is too long. Leaving this here for the time being in case we need to do
+ // the same in the Qt UI.
+ const sip_calls_info_t *sipinfo;
+ const isup_calls_info_t *isupinfo;
+ const h323_calls_info_t *h323info;
+ const h245_address_t *h245_add = NULL;
+ const gcp_ctx_t* ctx;
+ char *guid_str;
+
+ if (filter_length < max_filter_length) {
+ gtk_editable_insert_text(GTK_EDITABLE(main_display_filter_widget), filter_string_fwd->str, -1, &pos);
+ } else {
+ g_string_free(filter_string_fwd, TRUE);
+ filter_string_fwd = g_string_new(filter_prepend);
+
+ g_string_append_printf(filter_string_fwd, "(");
+ is_first = TRUE;
+ /* Build a new filter based on protocol fields */
+ lista = g_queue_peek_nth_link(voip_calls_get_info()->callsinfos, 0);
+ while (lista) {
+ listinfo = gxx_list_data(voip_calls_info_t *, lista);
+ if (listinfo->selected) {
+ if (!is_first)
+ g_string_append_printf(filter_string_fwd, " or ");
+ switch (listinfo->protocol) {
+ case VOIP_SIP:
+ sipinfo = (sip_calls_info_t *)listinfo->prot_info;
+ g_string_append_printf(filter_string_fwd,
+ "(sip.Call-ID == \"%s\")",
+ sipinfo->call_identifier
+ );
+ break;
+ case VOIP_ISUP:
+ isupinfo = (isup_calls_info_t *)listinfo->prot_info;
+ g_string_append_printf(filter_string_fwd,
+ "(isup.cic == %i and frame.number >= %i and frame.number <= %i and mtp3.network_indicator == %i and ((mtp3.dpc == %i) and (mtp3.opc == %i)) or ((mtp3.dpc == %i) and (mtp3.opc == %i)))",
+ isupinfo->cic, listinfo->start_fd->num,
+ listinfo->stop_fd->num,
+ isupinfo->ni, isupinfo->dpc, isupinfo->opc,
+ isupinfo->opc, isupinfo->dpc
+ );
+ break;
+ case VOIP_H323:
+ {
+ h323info = (h323_calls_info_t *)listinfo->prot_info;
+ guid_str = guid_to_str(NULL, &h323info->guid[0]);
+ g_string_append_printf(filter_string_fwd,
+ "((h225.guid == %s || q931.call_ref == %x:%x || q931.call_ref == %x:%x)",
+ guid_str,
+ (guint8) (h323info->q931_crv & 0x00ff),
+ (guint8)((h323info->q931_crv & 0xff00)>>8),
+ (guint8) (h323info->q931_crv2 & 0x00ff),
+ (guint8)((h323info->q931_crv2 & 0xff00)>>8));
+ listb = g_list_first(h323info->h245_list);
+ wmem_free(NULL, guid_str);
+ while (listb) {
+ h245_add = gxx_list_data(h245_address_t *, listb);
+ g_string_append_printf(filter_string_fwd,
+ " || (ip.addr == %s && tcp.port == %d && h245)",
+ address_to_qstring(&h245_add->h245_address), h245_add->h245_port);
+ listb = gxx_list_next(listb);
+ }
+ g_string_append_printf(filter_string_fwd, ")");
+ }
+ break;
+ case TEL_H248:
+ ctx = (gcp_ctx_t *)listinfo->prot_info;
+ g_string_append_printf(filter_string_fwd,
+ "(h248.ctx == 0x%x)", ctx->id);
+ break;
+ default:
+ /* placeholder to assure valid display filter expression */
+ g_string_append_printf(filter_string_fwd,
+ "(frame)");
+ break;
+ }
+ is_first = FALSE;
+ }
+ lista = gxx_list_next(lista);
+ }
+
+ g_string_append_printf(filter_string_fwd, ")");
+ gtk_editable_insert_text(GTK_EDITABLE(main_display_filter_widget), filter_string_fwd->str, -1, &pos);
+ }
+#endif
+
+ emit updateFilter(filter_str);
+}
+
+void VoipCallsDialog::showSequence()
+{
+ if (file_closed_) return;
+
+ QSet<guint16> selected_calls;
+ foreach (QModelIndex index, ui->callTreeView->selectionModel()->selectedIndexes()) {
+ voip_calls_info_t *call_info = VoipCallsInfoModel::indexToCallInfo(index);
+ if (!call_info) {
+ return;
+ }
+ selected_calls << call_info->call_num;
+ }
+
+ sequence_analysis_list_sort(tapinfo_.graph_analysis);
+ GList *cur_ga_item = g_queue_peek_nth_link(tapinfo_.graph_analysis->items, 0);
+ while (cur_ga_item && cur_ga_item->data) {
+ seq_analysis_item_t *ga_item = gxx_list_data(seq_analysis_item_t*, cur_ga_item);
+ ga_item->display = selected_calls.contains(ga_item->conv_num);
+ cur_ga_item = gxx_list_next(cur_ga_item);
+ }
+
+ SequenceDialog *sequence_dialog = new SequenceDialog(parent_, cap_file_, sequence_info_);
+ // Bypass this dialog and forward signals to parent
+ connect(sequence_dialog, SIGNAL(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(sequence_dialog, SIGNAL(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(sequence_dialog, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(sequence_dialog, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(sequence_dialog, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
+
+ sequence_dialog->setAttribute(Qt::WA_DeleteOnClose);
+ sequence_dialog->enableVoIPFeatures();
+ sequence_dialog->show();
+}
+
+QVector<rtpstream_id_t *>VoipCallsDialog::getSelectedRtpIds()
+{
+ QVector<rtpstream_id_t *> stream_ids;
+ foreach (QModelIndex index, ui->callTreeView->selectionModel()->selectedIndexes()) {
+ voip_calls_info_t *vci = VoipCallsInfoModel::indexToCallInfo(index);
+ if (!vci) continue;
+
+ for (GList *rsi_entry = g_list_first(tapinfo_.rtpstream_list); rsi_entry; rsi_entry = gxx_list_next(rsi_entry)) {
+ rtpstream_info_t *rsi = gxx_list_data(rtpstream_info_t *, rsi_entry);
+ if (!rsi) continue;
+
+ //VOIP_CALLS_DEBUG("checking call %u, start frame %u == stream call %u, start frame %u, setup frame %u",
+ // vci->call_num, vci->start_fd->num,
+ // rsi->call_num, rsi->start_fd->num, rsi->setup_frame_number);
+ if (vci->call_num == static_cast<guint>(rsi->call_num)) {
+ //VOIP_CALLS_DEBUG("adding call number %u", vci->call_num);
+ if (-1 == stream_ids.indexOf(&(rsi->id))) {
+ // Add only new stream
+ stream_ids << &(rsi->id);
+ }
+ }
+ }
+ }
+
+ return stream_ids;
+}
+
+void VoipCallsDialog::rtpPlayerReplace()
+{
+ if (ui->callTreeView->selectionModel()->selectedIndexes().count() < 1) return;
+
+ emit rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
+}
+
+void VoipCallsDialog::rtpPlayerAdd()
+{
+ if (ui->callTreeView->selectionModel()->selectedIndexes().count() < 1) return;
+
+ emit rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
+}
+
+void VoipCallsDialog::rtpPlayerRemove()
+{
+ if (ui->callTreeView->selectionModel()->selectedIndexes().count() < 1) return;
+
+ emit rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
+}
+
+QList<QVariant> VoipCallsDialog::streamRowData(int row) const
+{
+ QList<QVariant> row_data;
+
+ if (row >= sorted_model_->rowCount()) {
+ return row_data;
+ }
+
+ for (int col = 0; col < sorted_model_->columnCount(); col++) {
+ if (row < 0) {
+ row_data << sorted_model_->headerData(col, Qt::Horizontal);
+ } else {
+ row_data << sorted_model_->index(row, col).data();
+ }
+ }
+ return row_data;
+}
+
+void VoipCallsDialog::on_callTreeView_activated(const QModelIndex &index)
+{
+ voip_calls_info_t *call_info = VoipCallsInfoModel::indexToCallInfo(index);
+ if (!call_info) {
+ return;
+ }
+ emit goToPacket(call_info->start_fd->num);
+}
+
+void VoipCallsDialog::selectAll()
+{
+ ui->callTreeView->selectAll();
+}
+
+void VoipCallsDialog::selectNone()
+{
+ ui->callTreeView->clearSelection();
+}
+
+void VoipCallsDialog::copyAsCSV()
+{
+ QString csv;
+ QTextStream stream(&csv, QIODevice::Text);
+ for (int row = -1; row < sorted_model_->rowCount(); row++) {
+ QStringList rdsl;
+ foreach (QVariant v, streamRowData(row)) {
+ QString strval = v.toString();
+ // XXX should quotes (") in strval be stripped/sanitized?
+ rdsl << QString("\"%1\"").arg(strval);
+ }
+ stream << rdsl.join(",") << '\n';
+ }
+ mainApp->clipboard()->setText(stream.readAll());
+}
+
+void VoipCallsDialog::copyAsYAML()
+{
+ QString yaml;
+ QTextStream stream(&yaml, QIODevice::Text);
+ stream << "---" << '\n';
+ for (int row = -1; row < sorted_model_->rowCount(); row++) {
+ stream << "-" << '\n';
+ foreach (QVariant v, streamRowData(row)) {
+ stream << " - " << v.toString() << '\n';
+ }
+ }
+ mainApp->clipboard()->setText(stream.readAll());
+}
+
+void VoipCallsDialog::on_buttonBox_clicked(QAbstractButton *button)
+{
+ if (button == prepare_button_) {
+ prepareFilter();
+ } else if (button == sequence_button_) {
+ showSequence();
+ }
+}
+
+void VoipCallsDialog::removeAllCalls()
+{
+ voip_calls_info_t *callsinfo;
+ GList *list = NULL;
+
+ call_infos_model_->removeAllCalls();
+
+ /* Free shown callsinfos */
+ list = g_queue_peek_nth_link(shown_callsinfos_, 0);
+ while (list)
+ {
+ callsinfo = (voip_calls_info_t *)list->data;
+ voip_calls_free_callsinfo(callsinfo);
+ list = g_list_next(list);
+ }
+ g_queue_clear(shown_callsinfos_);
+}
+
+void VoipCallsDialog::displayFilterCheckBoxToggled(bool checked)
+{
+ if (!cap_file_.isValid()) {
+ return;
+ }
+
+ tapinfo_.apply_display_filter = checked;
+ removeAllCalls();
+
+ cap_file_.retapPackets();
+}
+
+void VoipCallsDialog::on_buttonBox_helpRequested()
+{
+ mainApp->helpTopicAction(HELP_TELEPHONY_VOIP_CALLS_DIALOG);
+}
+
+void VoipCallsDialog::switchTimeOfDay()
+{
+ bool checked = ! call_infos_model_->timeOfDay();
+
+ ui->todCheckBox->setChecked(checked);
+ call_infos_model_->setTimeOfDay(checked);
+ ui->callTreeView->resizeColumnToContents(VoipCallsInfoModel::StartTime);
+ ui->callTreeView->resizeColumnToContents(VoipCallsInfoModel::StopTime);
+}
+
+void VoipCallsDialog::displayFilterSuccess(bool success)
+{
+ if (success && ui->displayFilterCheckBox->isChecked()) {
+ removeAllCalls();
+ cap_file_.retapPackets();
+ }
+}
+
+void VoipCallsDialog::invertSelection()
+{
+ QModelIndex rootIndex = ui->callTreeView->rootIndex();
+ QModelIndex first = sorted_model_->index(0, 0, QModelIndex());
+ int numOfItems = sorted_model_->rowCount(rootIndex);
+ int numOfCols = sorted_model_->columnCount(rootIndex);
+ QModelIndex last = sorted_model_->index(numOfItems - 1, numOfCols - 1, QModelIndex());
+
+ QItemSelection selection(first, last);
+ ui->callTreeView->selectionModel()->select(selection, QItemSelectionModel::Toggle);
+}
+
+void VoipCallsDialog::on_actionSelectAll_triggered()
+{
+ ui->callTreeView->selectAll();
+}
+
+void VoipCallsDialog::on_actionSelectInvert_triggered()
+{
+ invertSelection();
+}
+
+void VoipCallsDialog::on_actionSelectNone_triggered()
+{
+ ui->callTreeView->clearSelection();
+}
+
+void VoipCallsDialog::on_actionSelectRtpStreams_triggered()
+{
+ QVector<rtpstream_id_t *>stream_ids = qvector_rtpstream_ids_copy(getSelectedRtpIds());
+
+ emit rtpStreamsDialogSelectRtpStreams(stream_ids);
+
+ qvector_rtpstream_ids_free(stream_ids);
+ raise();
+}
+
+void VoipCallsDialog::on_actionDeselectRtpStreams_triggered()
+{
+ QVector<rtpstream_id_t *>stream_ids = qvector_rtpstream_ids_copy(getSelectedRtpIds());
+
+ emit rtpStreamsDialogDeselectRtpStreams(stream_ids);
+
+ qvector_rtpstream_ids_free(stream_ids);
+ raise();
+}
+