summaryrefslogtreecommitdiffstats
path: root/ui/qt/models/packet_list_model.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/qt/models/packet_list_model.cpp')
-rw-r--r--ui/qt/models/packet_list_model.cpp1014
1 files changed, 1014 insertions, 0 deletions
diff --git a/ui/qt/models/packet_list_model.cpp b/ui/qt/models/packet_list_model.cpp
new file mode 100644
index 0000000..0ed61d7
--- /dev/null
+++ b/ui/qt/models/packet_list_model.cpp
@@ -0,0 +1,1014 @@
+/* packet_list_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <algorithm>
+#include <glib.h>
+#include <cmath>
+#include <stdexcept>
+
+#include "packet_list_model.h"
+
+#include "file.h"
+
+#include <wsutil/nstime.h>
+#include <epan/column.h>
+#include <epan/expert.h>
+#include <epan/prefs.h>
+
+#include "ui/packet_list_utils.h"
+#include "ui/recent.h"
+
+#include <epan/color_filters.h>
+#include "frame_tvbuff.h"
+
+#include <ui/qt/utils/color_utils.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+#include "main_application.h"
+#include <ui/qt/main_window.h>
+#include <ui/qt/main_status_bar.h>
+#include <ui/qt/widgets/wireless_timeline.h>
+
+#include <QColor>
+#include <QElapsedTimer>
+#include <QFontMetrics>
+#include <QModelIndex>
+#include <QElapsedTimer>
+
+// Print timing information
+//#define DEBUG_PACKET_LIST_MODEL 1
+
+#ifdef DEBUG_PACKET_LIST_MODEL
+#include <wsutil/time_util.h>
+#endif
+
+class SortAbort : public std::runtime_error
+{
+ using std::runtime_error::runtime_error;
+};
+
+static PacketListModel * glbl_plist_model = Q_NULLPTR;
+static const int reserved_packets_ = 100000;
+
+guint
+packet_list_append(column_info *, frame_data *fdata)
+{
+ if (!glbl_plist_model)
+ return 0;
+
+ /* fdata should be filled with the stuff we need
+ * strings are built at display time.
+ */
+ return glbl_plist_model->appendPacket(fdata);
+}
+
+void
+packet_list_recreate_visible_rows(void)
+{
+ if (glbl_plist_model)
+ glbl_plist_model->recreateVisibleRows();
+}
+
+PacketListModel::PacketListModel(QObject *parent, capture_file *cf) :
+ QAbstractItemModel(parent),
+ number_to_row_(QVector<int>()),
+ max_row_height_(0),
+ max_line_count_(1),
+ idle_dissection_row_(0)
+{
+ Q_ASSERT(glbl_plist_model == Q_NULLPTR);
+ glbl_plist_model = this;
+ setCaptureFile(cf);
+
+ physical_rows_.reserve(reserved_packets_);
+ visible_rows_.reserve(reserved_packets_);
+ new_visible_rows_.reserve(1000);
+ number_to_row_.reserve(reserved_packets_);
+
+ if (qobject_cast<MainWindow *>(mainApp->mainWindow()))
+ {
+ MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
+ QWidget * wtWidget = mw->findChild<WirelessTimeline *>();
+ if (wtWidget && qobject_cast<WirelessTimeline *>(wtWidget))
+ {
+ WirelessTimeline * wt = qobject_cast<WirelessTimeline *>(wtWidget);
+ connect(this, &PacketListModel::bgColorizationProgress,
+ wt, &WirelessTimeline::bgColorizationProgress);
+ }
+
+ }
+
+ connect(this, &PacketListModel::maxLineCountChanged,
+ this, &PacketListModel::emitItemHeightChanged,
+ Qt::QueuedConnection);
+ idle_dissection_timer_ = new QElapsedTimer();
+}
+
+PacketListModel::~PacketListModel()
+{
+ delete idle_dissection_timer_;
+}
+
+void PacketListModel::setCaptureFile(capture_file *cf)
+{
+ cap_file_ = cf;
+}
+
+// Packet list records have no children (for now, at least).
+QModelIndex PacketListModel::index(int row, int column, const QModelIndex &) const
+{
+ if (row >= visible_rows_.count() || row < 0 || !cap_file_ || column >= prefs.num_cols)
+ return QModelIndex();
+
+ PacketListRecord *record = visible_rows_[row];
+
+ return createIndex(row, column, record);
+}
+
+// Everything is under the root.
+QModelIndex PacketListModel::parent(const QModelIndex &) const
+{
+ return QModelIndex();
+}
+
+int PacketListModel::packetNumberToRow(int packet_num) const
+{
+ // map 1-based values to 0-based row numbers. Invisible rows are stored as
+ // the default value (0) and should map to -1.
+ return number_to_row_.value(packet_num) - 1;
+}
+
+guint PacketListModel::recreateVisibleRows()
+{
+ beginResetModel();
+ visible_rows_.resize(0);
+ number_to_row_.fill(0);
+ endResetModel();
+
+ foreach (PacketListRecord *record, physical_rows_) {
+ frame_data *fdata = record->frameData();
+
+ if (fdata->passed_dfilter || fdata->ref_time) {
+ visible_rows_ << record;
+ if (static_cast<guint32>(number_to_row_.size()) <= fdata->num) {
+ number_to_row_.resize(fdata->num + 10000);
+ }
+ number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
+ }
+ }
+ if (!visible_rows_.isEmpty()) {
+ beginInsertRows(QModelIndex(), 0, static_cast<int>(visible_rows_.count()) - 1);
+ endInsertRows();
+ }
+ idle_dissection_row_ = 0;
+ return static_cast<guint>(visible_rows_.count());
+}
+
+void PacketListModel::clear() {
+ beginResetModel();
+ qDeleteAll(physical_rows_);
+ PacketListRecord::invalidateAllRecords();
+ physical_rows_.resize(0);
+ visible_rows_.resize(0);
+ new_visible_rows_.resize(0);
+ number_to_row_.resize(0);
+ endResetModel();
+ max_row_height_ = 0;
+ max_line_count_ = 1;
+ idle_dissection_timer_->invalidate();
+ idle_dissection_row_ = 0;
+}
+
+void PacketListModel::invalidateAllColumnStrings()
+{
+ PacketListRecord::invalidateAllRecords();
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::DisplayRole);
+}
+
+void PacketListModel::resetColumns()
+{
+ if (cap_file_) {
+ PacketListRecord::resetColumns(&cap_file_->cinfo);
+ }
+
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
+ emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
+}
+
+void PacketListModel::resetColorized()
+{
+ PacketListRecord::resetColorization();
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
+}
+
+void PacketListModel::toggleFrameMark(const QModelIndexList &indeces)
+{
+ if (!cap_file_ || indeces.count() <= 0)
+ return;
+
+ int sectionMax = columnCount() - 1;
+
+ foreach (QModelIndex index, indeces) {
+ if (! index.isValid())
+ continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record)
+ continue;
+
+ frame_data *fdata = record->frameData();
+ if (!fdata)
+ continue;
+
+ if (fdata->marked)
+ cf_unmark_frame(cap_file_, fdata);
+ else
+ cf_mark_frame(cap_file_, fdata);
+
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
+ }
+}
+
+void PacketListModel::setDisplayedFrameMark(gboolean set)
+{
+ foreach (PacketListRecord *record, visible_rows_) {
+ if (set) {
+ cf_mark_frame(cap_file_, record->frameData());
+ } else {
+ cf_unmark_frame(cap_file_, record->frameData());
+ }
+ }
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
+}
+
+void PacketListModel::toggleFrameIgnore(const QModelIndexList &indeces)
+{
+ if (!cap_file_ || indeces.count() <= 0)
+ return;
+
+ int sectionMax = columnCount() - 1;
+
+ foreach (QModelIndex index, indeces) {
+ if (! index.isValid())
+ continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record)
+ continue;
+
+ frame_data *fdata = record->frameData();
+ if (!fdata)
+ continue;
+
+ if (fdata->ignored)
+ cf_unignore_frame(cap_file_, fdata);
+ else
+ cf_ignore_frame(cap_file_, fdata);
+
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+}
+
+void PacketListModel::setDisplayedFrameIgnore(gboolean set)
+{
+ foreach (PacketListRecord *record, visible_rows_) {
+ if (set) {
+ cf_ignore_frame(cap_file_, record->frameData());
+ } else {
+ cf_unignore_frame(cap_file_, record->frameData());
+ }
+ }
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+}
+
+void PacketListModel::toggleFrameRefTime(const QModelIndex &rt_index)
+{
+ if (!cap_file_ || !rt_index.isValid()) return;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(rt_index.internalPointer());
+ if (!record) return;
+
+ frame_data *fdata = record->frameData();
+ if (!fdata) return;
+
+ if (fdata->ref_time) {
+ fdata->ref_time=0;
+ cap_file_->ref_time_count--;
+ } else {
+ fdata->ref_time=1;
+ cap_file_->ref_time_count++;
+ }
+ cf_reftime_packets(cap_file_);
+ if (!fdata->ref_time && !fdata->passed_dfilter) {
+ cap_file_->displayed_count--;
+ }
+ record->resetColumns(&cap_file_->cinfo);
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
+}
+
+void PacketListModel::unsetAllFrameRefTime()
+{
+ if (!cap_file_) return;
+
+ /* XXX: we might need a progressbar here */
+
+ foreach (PacketListRecord *record, physical_rows_) {
+ frame_data *fdata = record->frameData();
+ if (fdata->ref_time) {
+ fdata->ref_time = 0;
+ }
+ }
+ cap_file_->ref_time_count = 0;
+ cf_reftime_packets(cap_file_);
+ PacketListRecord::resetColumns(&cap_file_->cinfo);
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
+}
+
+void PacketListModel::addFrameComment(const QModelIndexList &indices, const QByteArray &comment)
+{
+ int sectionMax = columnCount() - 1;
+ frame_data *fdata;
+ if (!cap_file_) return;
+
+ for (const auto &index : indices) {
+ if (!index.isValid()) continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record) continue;
+
+ fdata = record->frameData();
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ wtap_block_add_string_option(pkt_block, OPT_COMMENT, comment.data(), comment.size());
+
+ if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
+ cap_file_->packet_comment_count++;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+ }
+
+ // In case there are coloring rules or columns related to comments.
+ // (#12519)
+ //
+ // XXX: "Does any active coloring rule relate to frame data"
+ // could be an optimization. For columns, note that
+ // "col_based_on_frame_data" only applies to built in columns,
+ // not custom columns based on frame data. (Should we prevent
+ // custom columns based on frame data from being created,
+ // substituting them with the other columns?)
+ //
+ // Note that there are not currently any fields that depend on
+ // whether other frames have comments, unlike with time references
+ // and time shifts ("frame.time_relative", "frame.offset_shift", etc.)
+ // If there were, then we'd need to reset data for all frames instead
+ // of just the frames changed.
+ record->invalidateColorized();
+ record->invalidateRecord();
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+}
+
+void PacketListModel::setFrameComment(const QModelIndex &index, const QByteArray &comment, guint c_number)
+{
+ int sectionMax = columnCount() - 1;
+ frame_data *fdata;
+ if (!cap_file_) return;
+
+ if (!index.isValid()) return;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record) return;
+
+ fdata = record->frameData();
+
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ if (comment.isEmpty()) {
+ wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, c_number);
+ if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
+ cap_file_->packet_comment_count--;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+ }
+ } else {
+ wtap_block_set_nth_string_option_value(pkt_block, OPT_COMMENT, c_number, comment.data(), comment.size());
+ cf_set_modified_block(cap_file_, fdata, pkt_block);
+ }
+
+ record->invalidateColorized();
+ record->invalidateRecord();
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+}
+
+void PacketListModel::deleteFrameComments(const QModelIndexList &indices)
+{
+ int sectionMax = columnCount() - 1;
+ frame_data *fdata;
+ if (!cap_file_) return;
+
+ for (const auto &index : indices) {
+ if (!index.isValid()) continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record) continue;
+
+ fdata = record->frameData();
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
+
+ if (n_comments) {
+ for (guint i = 0; i < n_comments; i++) {
+ wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
+ }
+ if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
+ cap_file_->packet_comment_count -= n_comments;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+ }
+
+ record->invalidateColorized();
+ record->invalidateRecord();
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+ }
+}
+
+void PacketListModel::deleteAllFrameComments()
+{
+ int row;
+ int sectionMax = columnCount() - 1;
+ if (!cap_file_) return;
+
+ /* XXX: we might need a progressbar here */
+
+ foreach (PacketListRecord *record, physical_rows_) {
+ frame_data *fdata = record->frameData();
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
+
+ if (n_comments) {
+ for (guint i = 0; i < n_comments; i++) {
+ wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
+ }
+ cf_set_modified_block(cap_file_, fdata, pkt_block);
+
+ record->invalidateColorized();
+ record->invalidateRecord();
+ row = packetNumberToRow(fdata->num);
+ if (row > -1) {
+ emit dataChanged(index(row, 0), index(row, sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+ }
+ }
+ cap_file_->packet_comment_count = 0;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+}
+
+void PacketListModel::setMaximumRowHeight(int height)
+{
+ max_row_height_ = height;
+ // As the QTreeView uniformRowHeights documentation says,
+ // "The height is obtained from the first item in the view. It is
+ // updated when the data changes on that item."
+ emit dataChanged(index(0, 0), index(0, columnCount() - 1));
+}
+
+int PacketListModel::sort_column_;
+int PacketListModel::sort_column_is_numeric_;
+int PacketListModel::text_sort_column_;
+Qt::SortOrder PacketListModel::sort_order_;
+capture_file *PacketListModel::sort_cap_file_;
+gboolean PacketListModel::stop_flag_;
+ProgressFrame *PacketListModel::progress_frame_;
+double PacketListModel::comps_;
+double PacketListModel::exp_comps_;
+
+QElapsedTimer busy_timer_;
+const int busy_timeout_ = 65; // ms, approximately 15 fps
+void PacketListModel::sort(int column, Qt::SortOrder order)
+{
+ if (!cap_file_ || visible_rows_.count() < 1) return;
+ if (column < 0) return;
+
+ if (physical_rows_.count() < 1)
+ return;
+
+ sort_column_ = column;
+ text_sort_column_ = PacketListRecord::textColumn(column);
+ sort_order_ = order;
+ sort_cap_file_ = cap_file_;
+
+ QString col_title = get_column_title(column);
+
+ if (text_sort_column_ >= 0 && (guint)visible_rows_.count() > prefs.gui_packet_list_cached_rows_max) {
+ /* Column not based on frame data but by column text that requires
+ * dissection, so to sort in a reasonable amount of time the column
+ * text needs to be cached.
+ */
+ /* If the sort is being triggered because the columns were already
+ * sorted and the filter is being cleared (or changed to something
+ * else with more rows than fit in the cache), then the temporary
+ * message will be immediately overwritten with the standard capture
+ * statistics by the packets_bar_update() call after thawing the rows.
+ * It will still blink yellow, and the user will get the message if
+ * they then click on the header file (wondering why it didn't sort.)
+ */
+ if (col_title.isEmpty()) {
+ col_title = tr("Column");
+ }
+ QString temp_msg = tr("%1 can only be sorted with %2 or fewer visible rows; increase cache size in Layout preferences").arg(col_title).arg(prefs.gui_packet_list_cached_rows_max);
+ mainApp->pushStatus(MainApplication::TemporaryStatus, temp_msg);
+ return;
+ }
+
+ /* If we are currently in the middle of reading the capture file, don't
+ * sort. PacketList::captureFileReadFinished invalidates all the cached
+ * column strings and then tries to sort again.
+ * Similarly, claim the read lock because we don't want the file to
+ * change out from under us while sorting, which can segfault. (Previously
+ * we ignored user input, but now in order to cancel sorting we don't.)
+ */
+ if (sort_cap_file_->read_lock) {
+ ws_info("Refusing to sort because capture file is being read");
+ /* We shouldn't have to tell the user because we're just deferring
+ * the sort until PacketList::captureFileReadFinished
+ */
+ return;
+ }
+ sort_cap_file_->read_lock = TRUE;
+
+ QString busy_msg;
+ if (!col_title.isEmpty()) {
+ busy_msg = tr("Sorting \"%1\"…").arg(col_title);
+ } else {
+ busy_msg = tr("Sorting …");
+ }
+ stop_flag_ = FALSE;
+ comps_ = 0;
+ /* XXX: The expected number of comparisons is O(N log N), but this could
+ * be a pretty significant overestimate of the amount of time it takes,
+ * if there are lots of identical entries. (Especially with string
+ * comparisons, some comparisons are faster than others.) Better to
+ * overestimate?
+ */
+ exp_comps_ = log2(visible_rows_.count()) * visible_rows_.count();
+ progress_frame_ = nullptr;
+ if (qobject_cast<MainWindow *>(mainApp->mainWindow())) {
+ MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
+ progress_frame_ = mw->findChild<ProgressFrame *>();
+ if (progress_frame_) {
+ progress_frame_->showProgress(busy_msg, true, false, &stop_flag_, 0);
+ connect(progress_frame_, &ProgressFrame::stopLoading,
+ this, &PacketListModel::stopSorting);
+ }
+ }
+
+ busy_timer_.start();
+ sort_column_is_numeric_ = isNumericColumn(sort_column_);
+ QVector<PacketListRecord *> sorted_visible_rows_ = visible_rows_;
+ try {
+ std::sort(sorted_visible_rows_.begin(), sorted_visible_rows_.end(), recordLessThan);
+
+ beginResetModel();
+ visible_rows_.resize(0);
+ number_to_row_.fill(0);
+ foreach (PacketListRecord *record, sorted_visible_rows_) {
+ frame_data *fdata = record->frameData();
+
+ if (fdata->passed_dfilter || fdata->ref_time) {
+ visible_rows_ << record;
+ if (number_to_row_.size() <= (int)fdata->num) {
+ number_to_row_.resize(fdata->num + 10000);
+ }
+ number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
+ }
+ }
+ endResetModel();
+ } catch (const SortAbort& e) {
+ mainApp->pushStatus(MainApplication::TemporaryStatus, e.what());
+ }
+
+ if (progress_frame_ != nullptr) {
+ progress_frame_->hide();
+ disconnect(progress_frame_, &ProgressFrame::stopLoading,
+ this, &PacketListModel::stopSorting);
+ }
+ sort_cap_file_->read_lock = FALSE;
+
+ if (cap_file_->current_frame) {
+ emit goToPacket(cap_file_->current_frame->num);
+ }
+}
+
+void PacketListModel::stopSorting()
+{
+ stop_flag_ = TRUE;
+}
+
+bool PacketListModel::isNumericColumn(int column)
+{
+ if (column < 0) {
+ return false;
+ }
+ switch (sort_cap_file_->cinfo.columns[column].col_fmt) {
+ case COL_CUMULATIVE_BYTES: /**< 3) Cumulative number of bytes */
+ case COL_DELTA_TIME: /**< 5) Delta time */
+ case COL_DELTA_TIME_DIS: /**< 8) Delta time displayed*/
+ case COL_UNRES_DST_PORT: /**< 10) Unresolved dest port */
+ case COL_FREQ_CHAN: /**< 15) IEEE 802.11 (and WiMax?) - Channel */
+ case COL_RSSI: /**< 22) IEEE 802.11 - received signal strength */
+ case COL_TX_RATE: /**< 23) IEEE 802.11 - TX rate in Mbps */
+ case COL_NUMBER: /**< 32) Packet list item number */
+ case COL_PACKET_LENGTH: /**< 33) Packet length in bytes */
+ case COL_UNRES_SRC_PORT: /**< 41) Unresolved source port */
+ return true;
+
+ /*
+ * Try to sort port numbers as number, if the numeric comparison fails (due
+ * to name resolution), it will fallback to string comparison.
+ * */
+ case COL_RES_DST_PORT: /**< 10) Resolved dest port */
+ case COL_DEF_DST_PORT: /**< 12) Destination port */
+ case COL_DEF_SRC_PORT: /**< 37) Source port */
+ case COL_RES_SRC_PORT: /**< 40) Resolved source port */
+ return true;
+
+ case COL_CUSTOM:
+ /* handle custom columns below. */
+ break;
+
+ default:
+ return false;
+ }
+
+ guint num_fields = g_slist_length(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids);
+ for (guint i = 0; i < num_fields; i++) {
+ guint *field_idx = (guint *) g_slist_nth_data(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids, i);
+ header_field_info *hfi = proto_registrar_get_nth(*field_idx);
+
+ /*
+ * Reject a field when there is no numeric field type or when:
+ * - there are (value_string) "strings"
+ * (but do accept fields which have a unit suffix).
+ * - BASE_HEX or BASE_HEX_DEC (these have a constant width, string
+ * comparison is faster than conversion to double).
+ * - BASE_CUSTOM (these can be formatted in any way).
+ */
+ if (!hfi ||
+ (hfi->strings != NULL && !(hfi->display & BASE_UNIT_STRING)) ||
+ !(((FT_IS_INT(hfi->type) || FT_IS_UINT(hfi->type)) &&
+ ((FIELD_DISPLAY(hfi->display) == BASE_DEC) ||
+ (FIELD_DISPLAY(hfi->display) == BASE_OCT) ||
+ (FIELD_DISPLAY(hfi->display) == BASE_DEC_HEX))) ||
+ (hfi->type == FT_DOUBLE) || (hfi->type == FT_FLOAT) ||
+ (hfi->type == FT_BOOLEAN) || (hfi->type == FT_FRAMENUM) ||
+ (hfi->type == FT_RELATIVE_TIME))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2)
+{
+ int cmp_val = 0;
+ comps_++;
+
+ // Wherein we try to cram the logic of packet_list_compare_records,
+ // _packet_list_compare_records, and packet_list_compare_custom from
+ // gtk/packet_list_store.c into one function
+
+ if (busy_timer_.elapsed() > busy_timeout_) {
+ if (progress_frame_) {
+ progress_frame_->setValue(static_cast<int>(comps_/exp_comps_ * 100));
+ }
+ // What's the least amount of processing that we can do which will draw
+ // the busy indicator?
+ mainApp->processEvents(QEventLoop::ExcludeSocketNotifiers, 1);
+ if (stop_flag_) {
+ throw SortAbort("Sorting aborted");
+ }
+ busy_timer_.restart();
+ }
+ if (sort_column_ < 0) {
+ // No column.
+ cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
+ } else if (text_sort_column_ < 0) {
+ // Column comes directly from frame data
+ cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), sort_cap_file_->cinfo.columns[sort_column_].col_fmt);
+ } else {
+ QString r1String = r1->columnString(sort_cap_file_, sort_column_);
+ QString r2String = r2->columnString(sort_cap_file_, sort_column_);
+ // XXX: The naive string comparison compares Unicode code points.
+ // Proper collation is more expensive
+ cmp_val = r1String.compare(r2String);
+ if (cmp_val != 0 && sort_column_is_numeric_) {
+ // Custom column with numeric data (or something like a port number).
+ // Attempt to convert to numbers.
+ // XXX This is slow. Can we avoid doing this? Perhaps the actual
+ // values used for sorting should be cached too as QVariant[List].
+ // If so, we could consider using QCollatorSortKeys or similar
+ // for strings as well.
+ bool ok_r1, ok_r2;
+ double num_r1 = parseNumericColumn(r1String, &ok_r1);
+ double num_r2 = parseNumericColumn(r2String, &ok_r2);
+
+ if (!ok_r1 && !ok_r2) {
+ cmp_val = 0;
+ } else if (!ok_r1 || (ok_r2 && num_r1 < num_r2)) {
+ // either r1 is invalid (and sort it before others) or both
+ // r1 and r2 are valid (sort normally)
+ cmp_val = -1;
+ } else if (!ok_r2 || (num_r1 > num_r2)) {
+ cmp_val = 1;
+ }
+ }
+
+ if (cmp_val == 0) {
+ // All else being equal, compare column numbers.
+ cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
+ }
+ }
+
+ if (sort_order_ == Qt::AscendingOrder) {
+ return cmp_val < 0;
+ } else {
+ return cmp_val > 0;
+ }
+}
+
+// Parses a field as a double. Handle values with suffixes ("12ms"), negative
+// values ("-1.23") and fields with multiple occurrences ("1,2"). Marks values
+// that do not contain any numeric value ("Unknown") as invalid.
+double PacketListModel::parseNumericColumn(const QString &val, bool *ok)
+{
+ QByteArray ba = val.toUtf8();
+ const char *strval = ba.constData();
+ gchar *end = NULL;
+ double num = g_ascii_strtod(strval, &end);
+ *ok = strval != end;
+ return num;
+}
+
+// ::data is const so we have to make changes here.
+void PacketListModel::emitItemHeightChanged(const QModelIndex &ih_index)
+{
+ if (!ih_index.isValid()) return;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(ih_index.internalPointer());
+ if (!record) return;
+
+ if (record->lineCount() > max_line_count_) {
+ max_line_count_ = record->lineCount();
+ emit itemHeightChanged(ih_index);
+ }
+}
+
+int PacketListModel::rowCount(const QModelIndex &) const
+{
+ return static_cast<int>(visible_rows_.count());
+}
+
+int PacketListModel::columnCount(const QModelIndex &) const
+{
+ return prefs.num_cols;
+}
+
+QVariant PacketListModel::data(const QModelIndex &d_index, int role) const
+{
+ if (!d_index.isValid())
+ return QVariant();
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(d_index.internalPointer());
+ if (!record)
+ return QVariant();
+ const frame_data *fdata = record->frameData();
+ if (!fdata)
+ return QVariant();
+
+ switch (role) {
+ case Qt::TextAlignmentRole:
+ switch(recent_get_column_xalign(d_index.column())) {
+ case COLUMN_XALIGN_RIGHT:
+ return Qt::AlignRight;
+ break;
+ case COLUMN_XALIGN_CENTER:
+ return Qt::AlignCenter;
+ break;
+ case COLUMN_XALIGN_LEFT:
+ return Qt::AlignLeft;
+ break;
+ case COLUMN_XALIGN_DEFAULT:
+ default:
+ if (right_justify_column(d_index.column(), cap_file_)) {
+ return Qt::AlignRight;
+ }
+ break;
+ }
+ return Qt::AlignLeft;
+
+ case Qt::BackgroundRole:
+ const color_t *color;
+ if (fdata->ignored) {
+ color = &prefs.gui_ignored_bg;
+ } else if (fdata->marked) {
+ color = &prefs.gui_marked_bg;
+ } else if (fdata->color_filter && recent.packet_list_colorize) {
+ const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
+ color = &color_filter->bg_color;
+ } else {
+ return QVariant();
+ }
+ return ColorUtils::fromColorT(color);
+ case Qt::ForegroundRole:
+ if (fdata->ignored) {
+ color = &prefs.gui_ignored_fg;
+ } else if (fdata->marked) {
+ color = &prefs.gui_marked_fg;
+ } else if (fdata->color_filter && recent.packet_list_colorize) {
+ const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
+ color = &color_filter->fg_color;
+ } else {
+ return QVariant();
+ }
+ return ColorUtils::fromColorT(color);
+ case Qt::DisplayRole:
+ {
+ int column = d_index.column();
+ QString column_string = record->columnString(cap_file_, column, true);
+ // We don't know an item's sizeHint until we fetch its text here.
+ // Assume each line count is 1. If the line count changes, emit
+ // itemHeightChanged which triggers another redraw (including a
+ // fetch of SizeHintRole and DisplayRole) in the next event loop.
+ if (column == 0 && record->lineCountChanged() && record->lineCount() > max_line_count_) {
+ emit maxLineCountChanged(d_index);
+ }
+ return column_string;
+ }
+ case Qt::SizeHintRole:
+ {
+ // If this is the first row and column, return the maximum row height...
+ if (d_index.row() < 1 && d_index.column() < 1 && max_row_height_ > 0) {
+ QSize size = QSize(-1, max_row_height_);
+ return size;
+ }
+ // ...otherwise punt so that the item delegate can correctly calculate the item width.
+ return QVariant();
+ }
+ default:
+ return QVariant();
+ }
+}
+
+QVariant PacketListModel::headerData(int section, Qt::Orientation orientation,
+ int role) const
+{
+ if (!cap_file_) return QVariant();
+
+ if (orientation == Qt::Horizontal && section < prefs.num_cols) {
+ switch (role) {
+ case Qt::DisplayRole:
+ return QVariant::fromValue(QString(get_column_title(section)));
+ case Qt::ToolTipRole:
+ return QVariant::fromValue(gchar_free_to_qstring(get_column_tooltip(section)));
+ case PacketListModel::HEADER_CAN_RESOLVE:
+ return (bool)resolve_column(section, cap_file_);
+ default:
+ break;
+ }
+ }
+
+ return QVariant();
+}
+
+void PacketListModel::flushVisibleRows()
+{
+ int pos = static_cast<int>(visible_rows_.count());
+
+ if (new_visible_rows_.count() > 0) {
+ beginInsertRows(QModelIndex(), pos, pos + static_cast<int>(new_visible_rows_.count()));
+ foreach (PacketListRecord *record, new_visible_rows_) {
+ frame_data *fdata = record->frameData();
+
+ visible_rows_ << record;
+ if (static_cast<unsigned int>(number_to_row_.size()) <= fdata->num) {
+ number_to_row_.resize(fdata->num + 10000);
+ }
+ number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
+ }
+ endInsertRows();
+ new_visible_rows_.resize(0);
+ }
+}
+
+// Fill our column string and colorization cache while the application is
+// idle. Try to be as conservative with the CPU and disk as possible.
+static const int idle_dissection_interval_ = 5; // ms
+void PacketListModel::dissectIdle(bool reset)
+{
+ if (reset) {
+// qDebug() << "=di reset" << idle_dissection_row_;
+ idle_dissection_row_ = 0;
+ } else if (!idle_dissection_timer_->isValid()) {
+ return;
+ }
+
+ idle_dissection_timer_->restart();
+
+ int first = idle_dissection_row_;
+ while (idle_dissection_timer_->elapsed() < idle_dissection_interval_
+ && idle_dissection_row_ < physical_rows_.count()) {
+ ensureRowColorized(idle_dissection_row_);
+ idle_dissection_row_++;
+// if (idle_dissection_row_ % 1000 == 0) qDebug() << "=di row" << idle_dissection_row_;
+ }
+
+ if (idle_dissection_row_ < physical_rows_.count()) {
+ QTimer::singleShot(0, this, [=]() { dissectIdle(); });
+ } else {
+ idle_dissection_timer_->invalidate();
+ }
+
+ // report colorization progress
+ emit bgColorizationProgress(first+1, idle_dissection_row_+1);
+}
+
+// XXX Pass in cinfo from packet_list_append so that we can fill in
+// line counts?
+gint PacketListModel::appendPacket(frame_data *fdata)
+{
+ PacketListRecord *record = new PacketListRecord(fdata);
+ qsizetype pos = -1;
+
+#ifdef DEBUG_PACKET_LIST_MODEL
+ if (fdata->num % 10000 == 1) {
+ log_resource_usage(fdata->num == 1, "%u packets", fdata->num);
+ }
+#endif
+
+ physical_rows_ << record;
+
+ if (fdata->passed_dfilter || fdata->ref_time) {
+ new_visible_rows_ << record;
+ if (new_visible_rows_.count() < 2) {
+ // This is the first queued packet. Schedule an insertion for
+ // the next UI update.
+ QTimer::singleShot(0, this, &PacketListModel::flushVisibleRows);
+ }
+ pos = static_cast<int>( visible_rows_.count() + new_visible_rows_.count() ) - 1;
+ }
+
+ return static_cast<gint>(pos);
+}
+
+frame_data *PacketListModel::getRowFdata(QModelIndex idx)
+{
+ if (!idx.isValid())
+ return Q_NULLPTR;
+ return getRowFdata(idx.row());
+}
+
+frame_data *PacketListModel::getRowFdata(int row) {
+ if (row < 0 || row >= visible_rows_.count())
+ return NULL;
+ PacketListRecord *record = visible_rows_[row];
+ if (!record)
+ return NULL;
+ return record->frameData();
+}
+
+void PacketListModel::ensureRowColorized(int row)
+{
+ if (row < 0 || row >= visible_rows_.count())
+ return;
+ PacketListRecord *record = visible_rows_[row];
+ if (!record)
+ return;
+ if (!record->colorized()) {
+ record->ensureColorized(cap_file_);
+ }
+}
+
+int PacketListModel::visibleIndexOf(frame_data *fdata) const
+{
+ int row = 0;
+ foreach (PacketListRecord *record, visible_rows_) {
+ if (record->frameData() == fdata) {
+ return row;
+ }
+ row++;
+ }
+
+ return -1;
+}