1 /* packet_list_model.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
12 #include "packet_list_model.h"
16 #include <wsutil/nstime.h>
17 #include <epan/column.h>
18 #include <epan/prefs.h>
20 #include "ui/packet_list_utils.h"
21 #include "ui/recent.h"
23 #include <epan/color_filters.h>
24 #include "frame_tvbuff.h"
26 #include <ui/qt/utils/color_utils.h>
27 #include "wireshark_application.h"
30 #include <QElapsedTimer>
31 #include <QFontMetrics>
32 #include <QModelIndex>
33 #include <QElapsedTimer>
35 // Print timing information
36 //#define DEBUG_PACKET_LIST_MODEL 1
38 #ifdef DEBUG_PACKET_LIST_MODEL
39 #include <wsutil/time_util.h>
42 static const int reserved_packets_ = 100000;
44 PacketListModel::PacketListModel(QObject *parent, capture_file *cf) :
45 QAbstractItemModel(parent),
46 number_to_row_(QVector<int>()),
49 idle_dissection_row_(0)
52 PacketListRecord::clearStringPool();
54 physical_rows_.reserve(reserved_packets_);
55 visible_rows_.reserve(reserved_packets_);
56 new_visible_rows_.reserve(1000);
57 number_to_row_.reserve(reserved_packets_);
59 connect(this, SIGNAL(maxLineCountChanged(QModelIndex)),
60 this, SLOT(emitItemHeightChanged(QModelIndex)),
61 Qt::QueuedConnection);
62 idle_dissection_timer_ = new QElapsedTimer();
65 PacketListModel::~PacketListModel()
67 delete idle_dissection_timer_;
70 void PacketListModel::setCaptureFile(capture_file *cf)
76 // Packet list records have no children (for now, at least).
77 QModelIndex PacketListModel::index(int row, int column, const QModelIndex &) const
79 if (row >= visible_rows_.count() || row < 0 || !cap_file_ || column >= prefs.num_cols)
82 PacketListRecord *record = visible_rows_[row];
84 return createIndex(row, column, record);
87 // Everything is under the root.
88 QModelIndex PacketListModel::parent(const QModelIndex &) const
93 int PacketListModel::packetNumberToRow(int packet_num) const
95 // map 1-based values to 0-based row numbers. Invisible rows are stored as
96 // the default value (0) and should map to -1.
97 return number_to_row_.value(packet_num) - 1;
100 guint PacketListModel::recreateVisibleRows()
103 visible_rows_.resize(0);
104 number_to_row_.fill(0);
107 foreach (PacketListRecord *record, physical_rows_) {
108 frame_data *fdata = record->frameData();
110 if (fdata->flags.passed_dfilter || fdata->flags.ref_time) {
111 visible_rows_ << record;
112 if (number_to_row_.size() <= (int)fdata->num) {
113 number_to_row_.resize(fdata->num + 10000);
115 number_to_row_[fdata->num] = visible_rows_.count();
118 if (!visible_rows_.isEmpty()) {
119 beginInsertRows(QModelIndex(), 0, visible_rows_.count() - 1);
122 idle_dissection_row_ = 0;
123 return visible_rows_.count();
126 void PacketListModel::clear() {
128 qDeleteAll(physical_rows_);
129 physical_rows_.resize(0);
130 visible_rows_.resize(0);
131 new_visible_rows_.resize(0);
132 number_to_row_.resize(0);
133 PacketListRecord::clearStringPool();
137 idle_dissection_row_ = 0;
140 void PacketListModel::invalidateAllColumnStrings()
142 PacketListRecord::invalidateAllRecords();
143 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
144 headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
147 void PacketListModel::resetColumns()
150 PacketListRecord::resetColumns(&cap_file_->cinfo);
152 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
153 headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
156 void PacketListModel::resetColorized()
158 foreach (PacketListRecord *record, physical_rows_) {
159 record->resetColorized();
161 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
164 void PacketListModel::toggleFrameMark(const QModelIndex &fm_index)
166 if (!cap_file_ || !fm_index.isValid()) return;
168 PacketListRecord *record = static_cast<PacketListRecord*>(fm_index.internalPointer());
171 frame_data *fdata = record->frameData();
174 if (fdata->flags.marked)
175 cf_unmark_frame(cap_file_, fdata);
177 cf_mark_frame(cap_file_, fdata);
179 dataChanged(fm_index, fm_index);
182 void PacketListModel::setDisplayedFrameMark(gboolean set)
184 foreach (PacketListRecord *record, visible_rows_) {
186 cf_mark_frame(cap_file_, record->frameData());
188 cf_unmark_frame(cap_file_, record->frameData());
191 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
194 void PacketListModel::toggleFrameIgnore(const QModelIndex &i_index)
196 if (!cap_file_ || !i_index.isValid()) return;
198 PacketListRecord *record = static_cast<PacketListRecord*>(i_index.internalPointer());
201 frame_data *fdata = record->frameData();
204 if (fdata->flags.ignored)
205 cf_unignore_frame(cap_file_, fdata);
207 cf_ignore_frame(cap_file_, fdata);
210 void PacketListModel::setDisplayedFrameIgnore(gboolean set)
212 foreach (PacketListRecord *record, visible_rows_) {
214 cf_ignore_frame(cap_file_, record->frameData());
216 cf_unignore_frame(cap_file_, record->frameData());
219 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
222 void PacketListModel::toggleFrameRefTime(const QModelIndex &rt_index)
224 if (!cap_file_ || !rt_index.isValid()) return;
226 PacketListRecord *record = static_cast<PacketListRecord*>(rt_index.internalPointer());
229 frame_data *fdata = record->frameData();
232 if (fdata->flags.ref_time) {
233 fdata->flags.ref_time=0;
234 cap_file_->ref_time_count--;
236 fdata->flags.ref_time=1;
237 cap_file_->ref_time_count++;
239 cf_reftime_packets(cap_file_);
240 if (!fdata->flags.ref_time && !fdata->flags.passed_dfilter) {
241 cap_file_->displayed_count--;
243 record->resetColumns(&cap_file_->cinfo);
244 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
247 void PacketListModel::unsetAllFrameRefTime()
249 if (!cap_file_) return;
251 /* XXX: we might need a progressbar here */
253 foreach (PacketListRecord *record, physical_rows_) {
254 frame_data *fdata = record->frameData();
255 if (fdata->flags.ref_time) {
256 fdata->flags.ref_time = 0;
259 cap_file_->ref_time_count = 0;
260 cf_reftime_packets(cap_file_);
261 PacketListRecord::resetColumns(&cap_file_->cinfo);
262 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
265 void PacketListModel::applyTimeShift()
268 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
271 void PacketListModel::setMaximiumRowHeight(int height)
273 max_row_height_ = height;
274 // As the QTreeView uniformRowHeights documentation says,
275 // "The height is obtained from the first item in the view. It is
276 // updated when the data changes on that item."
277 dataChanged(index(0, 0), index(0, columnCount() - 1));
280 //void PacketListModel::setMonospaceFont(const QFont &mono_font, int row_height)
282 // QFontMetrics fm(mono_font_);
283 // mono_font_ = mono_font;
284 // row_height_ = row_height;
285 // line_spacing_ = fm.lineSpacing();
288 // The Qt MVC documentation suggests using QSortFilterProxyModel for sorting
289 // and filtering. That seems like overkill but it might be something we want
290 // to do in the future.
292 int PacketListModel::sort_column_;
293 int PacketListModel::sort_column_is_numeric_;
294 int PacketListModel::text_sort_column_;
295 Qt::SortOrder PacketListModel::sort_order_;
296 capture_file *PacketListModel::sort_cap_file_;
298 QElapsedTimer busy_timer_;
299 const int busy_timeout_ = 65; // ms, approximately 15 fps
300 void PacketListModel::sort(int column, Qt::SortOrder order)
302 // packet_list_store.c:packet_list_dissect_and_cache_all
303 if (!cap_file_ || visible_rows_.count() < 1) return;
304 if (column < 0) return;
306 sort_column_ = column;
307 text_sort_column_ = PacketListRecord::textColumn(column);
309 sort_cap_file_ = cap_file_;
311 gboolean stop_flag = FALSE;
312 QString col_title = get_column_title(column);
315 emit pushProgressStatus(tr("Dissecting"), true, true, &stop_flag);
317 foreach (PacketListRecord *row, physical_rows_) {
318 row->columnString(sort_cap_file_, column);
320 if (busy_timer_.elapsed() > busy_timeout_) {
322 emit popProgressStatus();
325 emit updateProgressStatus(row_num * 100 / physical_rows_.count());
326 // What's the least amount of processing that we can do which will draw
327 // the progress indicator?
328 wsApp->processEvents(QEventLoop::AllEvents, 1);
329 busy_timer_.restart();
332 emit popProgressStatus();
334 // XXX Use updateProgress instead. We'd have to switch from std::sort to
335 // something we can interrupt.
336 if (!col_title.isEmpty()) {
337 QString busy_msg = tr("Sorting \"%1\"").arg(col_title);
338 emit pushBusyStatus(busy_msg);
341 busy_timer_.restart();
342 sort_column_is_numeric_ = isNumericColumn(sort_column_);
343 std::sort(physical_rows_.begin(), physical_rows_.end(), recordLessThan);
346 visible_rows_.resize(0);
347 number_to_row_.fill(0);
348 foreach (PacketListRecord *record, physical_rows_) {
349 frame_data *fdata = record->frameData();
351 if (fdata->flags.passed_dfilter || fdata->flags.ref_time) {
352 visible_rows_ << record;
353 if (number_to_row_.size() <= (int)fdata->num) {
354 number_to_row_.resize(fdata->num + 10000);
356 number_to_row_[fdata->num] = visible_rows_.count();
361 if (!col_title.isEmpty()) {
362 emit popBusyStatus();
365 if (cap_file_->current_frame) {
366 emit goToPacket(cap_file_->current_frame->num);
370 bool PacketListModel::isNumericColumn(int column)
375 switch (sort_cap_file_->cinfo.columns[column].col_fmt) {
376 case COL_8021Q_VLAN_ID: /**< 0) 802.1Q vlan ID */
377 case COL_CUMULATIVE_BYTES: /**< 5) Cumulative number of bytes */
378 case COL_DELTA_TIME: /**< 8) Delta time */
379 case COL_DELTA_TIME_DIS: /**< 9) Delta time displayed*/
380 case COL_UNRES_DST_PORT: /**< 13) Unresolved dest port */
381 case COL_FREQ_CHAN: /**< 18) IEEE 802.11 (and WiMax?) - Channel */
382 case COL_RSSI: /**< 25) IEEE 802.11 - received signal strength */
383 case COL_TX_RATE: /**< 26) IEEE 802.11 - TX rate in Mbps */
384 case COL_NUMBER: /**< 35) Packet list item number */
385 case COL_PACKET_LENGTH: /**< 36) Packet length in bytes */
386 case COL_UNRES_SRC_PORT: /**< 44) Unresolved source port */
387 case COL_TEI: /**< 45) Q.921 TEI */
391 * Try to sort port numbers as number, if the numeric comparison fails (due
392 * to name resolution), it will fallback to string comparison.
394 case COL_RES_DST_PORT: /**< 12) Resolved dest port */
395 case COL_DEF_DST_PORT: /**< 15) Destination port */
396 case COL_DEF_SRC_PORT: /**< 40) Source port */
397 case COL_RES_SRC_PORT: /**< 43) Resolved source port */
401 /* handle custom columns below. */
408 guint num_fields = g_slist_length(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids);
409 for (guint i = 0; i < num_fields; i++) {
410 guint *field_idx = (guint *) g_slist_nth_data(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids, i);
411 header_field_info *hfi = proto_registrar_get_nth(*field_idx);
414 * Reject a field when there is no numeric field type or when:
415 * - there are (value_string) "strings"
416 * (but do accept fields which have a unit suffix).
417 * - BASE_HEX or BASE_HEX_DEC (these have a constant width, string
418 * comparison is faster than conversion to double).
419 * - BASE_CUSTOM (these can be formatted in any way).
422 (hfi->strings != NULL && !(hfi->display & BASE_UNIT_STRING)) ||
423 !(((IS_FT_INT(hfi->type) || IS_FT_UINT(hfi->type)) &&
424 ((FIELD_DISPLAY(hfi->display) == BASE_DEC) ||
425 (FIELD_DISPLAY(hfi->display) == BASE_OCT) ||
426 (FIELD_DISPLAY(hfi->display) == BASE_DEC_HEX))) ||
427 (hfi->type == FT_DOUBLE) || (hfi->type == FT_FLOAT) ||
428 (hfi->type == FT_BOOLEAN) || (hfi->type == FT_FRAMENUM) ||
429 (hfi->type == FT_RELATIVE_TIME))) {
437 bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2)
441 // Wherein we try to cram the logic of packet_list_compare_records,
442 // _packet_list_compare_records, and packet_list_compare_custom from
443 // gtk/packet_list_store.c into one function
445 if (busy_timer_.elapsed() > busy_timeout_) {
446 // What's the least amount of processing that we can do which will draw
447 // the busy indicator?
448 wsApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers, 1);
449 busy_timer_.restart();
451 if (sort_column_ < 0) {
453 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
454 } else if (text_sort_column_ < 0) {
455 // Column comes directly from frame data
456 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), sort_cap_file_->cinfo.columns[sort_column_].col_fmt);
458 if (r1->columnString(sort_cap_file_, sort_column_).constData() == r2->columnString(sort_cap_file_, sort_column_).constData()) {
460 } else if (sort_column_is_numeric_) {
461 // Custom column with numeric data (or something like a port number).
462 // Attempt to convert to numbers.
463 // XXX This is slow. Can we avoid doing this?
465 double num_r1 = parseNumericColumn(r1->columnString(sort_cap_file_, sort_column_), &ok_r1);
466 double num_r2 = parseNumericColumn(r2->columnString(sort_cap_file_, sort_column_), &ok_r2);
468 if (!ok_r1 && !ok_r2) {
470 } else if (!ok_r1 || (ok_r2 && num_r1 < num_r2)) {
471 // either r1 is invalid (and sort it before others) or both
472 // r1 and r2 are valid (sort normally)
474 } else if (!ok_r2 || (ok_r1 && num_r1 > num_r2)) {
478 cmp_val = strcmp(r1->columnString(sort_cap_file_, sort_column_).constData(), r2->columnString(sort_cap_file_, sort_column_).constData());
482 // All else being equal, compare column numbers.
483 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
487 if (sort_order_ == Qt::AscendingOrder) {
494 // Parses a field as a double. Handle values with suffixes ("12ms"), negative
495 // values ("-1.23") and fields with multiple occurrences ("1,2"). Marks values
496 // that do not contain any numeric value ("Unknown") as invalid.
497 double PacketListModel::parseNumericColumn(const QString &val, bool *ok)
499 QByteArray ba = val.toUtf8();
500 const char *strval = ba.constData();
502 double num = g_ascii_strtod(strval, &end);
507 // ::data is const so we have to make changes here.
508 void PacketListModel::emitItemHeightChanged(const QModelIndex &ih_index)
510 if (!ih_index.isValid()) return;
512 PacketListRecord *record = static_cast<PacketListRecord*>(ih_index.internalPointer());
515 if (record->lineCount() > max_line_count_) {
516 max_line_count_ = record->lineCount();
517 emit itemHeightChanged(ih_index);
521 int PacketListModel::rowCount(const QModelIndex &parent) const
523 if (parent.column() >= prefs.num_cols)
526 return visible_rows_.count();
529 int PacketListModel::columnCount(const QModelIndex &) const
531 return prefs.num_cols;
534 QVariant PacketListModel::data(const QModelIndex &d_index, int role) const
536 if (!d_index.isValid())
539 PacketListRecord *record = static_cast<PacketListRecord*>(d_index.internalPointer());
542 const frame_data *fdata = record->frameData();
547 case Qt::TextAlignmentRole:
548 switch(recent_get_column_xalign(d_index.column())) {
549 case COLUMN_XALIGN_RIGHT:
550 return Qt::AlignRight;
552 case COLUMN_XALIGN_CENTER:
553 return Qt::AlignCenter;
555 case COLUMN_XALIGN_LEFT:
556 return Qt::AlignLeft;
558 case COLUMN_XALIGN_DEFAULT:
560 if (right_justify_column(d_index.column(), cap_file_)) {
561 return Qt::AlignRight;
565 return Qt::AlignLeft;
567 case Qt::BackgroundRole:
568 const color_t *color;
569 if (fdata->flags.ignored) {
570 color = &prefs.gui_ignored_bg;
571 } else if (fdata->flags.marked) {
572 color = &prefs.gui_marked_bg;
573 } else if (fdata->color_filter && recent.packet_list_colorize) {
574 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
575 color = &color_filter->bg_color;
579 return ColorUtils::fromColorT(color);
580 case Qt::ForegroundRole:
581 if (fdata->flags.ignored) {
582 color = &prefs.gui_ignored_fg;
583 } else if (fdata->flags.marked) {
584 color = &prefs.gui_marked_fg;
585 } else if (fdata->color_filter && recent.packet_list_colorize) {
586 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
587 color = &color_filter->fg_color;
591 return ColorUtils::fromColorT(color);
592 case Qt::DisplayRole:
594 int column = d_index.column();
595 QByteArray column_string = record->columnString(cap_file_, column, true);
596 // We don't know an item's sizeHint until we fetch its text here.
597 // Assume each line count is 1. If the line count changes, emit
598 // itemHeightChanged which triggers another redraw (including a
599 // fetch of SizeHintRole and DisplayRole) in the next event loop.
600 if (column == 0 && record->lineCountChanged() && record->lineCount() > max_line_count_) {
601 emit maxLineCountChanged(d_index);
603 return column_string;
605 case Qt::SizeHintRole:
607 // If this is the first row and column, return the maximum row height...
608 if (d_index.row() < 1 && d_index.column() < 1 && max_row_height_ > 0) {
609 QSize size = QSize(-1, max_row_height_);
612 // ...otherwise punt so that the item delegate can correctly calculate the item width.
620 QVariant PacketListModel::headerData(int section, Qt::Orientation orientation,
623 if (!cap_file_) return QVariant();
625 if (orientation == Qt::Horizontal && section < prefs.num_cols) {
627 case Qt::DisplayRole:
628 return get_column_title(section);
629 case Qt::ToolTipRole:
631 gchar *tooltip = get_column_tooltip(section);
632 QVariant data(tooltip);
644 void PacketListModel::flushVisibleRows()
646 gint pos = visible_rows_.count();
648 if (new_visible_rows_.count() > 0) {
649 beginInsertRows(QModelIndex(), pos, pos + new_visible_rows_.count());
650 foreach (PacketListRecord *record, new_visible_rows_) {
651 frame_data *fdata = record->frameData();
653 visible_rows_ << record;
654 if (number_to_row_.size() <= (int)fdata->num) {
655 number_to_row_.resize(fdata->num + 10000);
657 number_to_row_[fdata->num] = visible_rows_.count();
660 new_visible_rows_.resize(0);
664 // Fill our column string and colorization cache while the application is
665 // idle. Try to be as conservative with the CPU and disk as possible.
666 static const int idle_dissection_interval_ = 5; // ms
667 void PacketListModel::dissectIdle(bool reset)
670 // qDebug() << "=di reset" << idle_dissection_row_;
671 idle_dissection_row_ = 0;
672 } else if (!idle_dissection_timer_->isValid()) {
676 idle_dissection_timer_->restart();
678 int first = idle_dissection_row_;
679 while (idle_dissection_timer_->elapsed() < idle_dissection_interval_
680 && idle_dissection_row_ < physical_rows_.count()) {
681 ensureRowColorized(idle_dissection_row_);
682 idle_dissection_row_++;
683 // if (idle_dissection_row_ % 1000 == 0) qDebug() << "=di row" << idle_dissection_row_;
686 if (idle_dissection_row_ < physical_rows_.count()) {
687 QTimer::singleShot(idle_dissection_interval_, this, SLOT(dissectIdle()));
689 idle_dissection_timer_->invalidate();
692 // report colorization progress
693 bgColorizationProgress(first+1, idle_dissection_row_+1);
696 // XXX Pass in cinfo from packet_list_append so that we can fill in
698 gint PacketListModel::appendPacket(frame_data *fdata)
700 PacketListRecord *record = new PacketListRecord(fdata);
703 #ifdef DEBUG_PACKET_LIST_MODEL
704 if (fdata->num % 10000 == 1) {
705 log_resource_usage(fdata->num == 1, "%u packets", fdata->num);
709 physical_rows_ << record;
711 if (fdata->flags.passed_dfilter || fdata->flags.ref_time) {
712 new_visible_rows_ << record;
713 if (new_visible_rows_.count() < 2) {
714 // This is the first queued packet. Schedule an insertion for
715 // the next UI update.
716 QTimer::singleShot(0, this, SLOT(flushVisibleRows()));
718 pos = visible_rows_.count() + new_visible_rows_.count() - 1;
724 frame_data *PacketListModel::getRowFdata(int row) {
725 if (row < 0 || row >= visible_rows_.count())
727 PacketListRecord *record = visible_rows_[row];
730 return record->frameData();
733 void PacketListModel::ensureRowColorized(int row)
735 if (row < 0 || row >= visible_rows_.count())
737 PacketListRecord *record = visible_rows_[row];
740 if (!record->colorized()) {
741 record->columnString(cap_file_, 1, true);
745 int PacketListModel::visibleIndexOf(frame_data *fdata) const
748 foreach (PacketListRecord *record, visible_rows_) {
749 if (record->frameData() == fdata) {
764 * indent-tabs-mode: nil
767 * ex: set shiftwidth=4 tabstop=8 expandtab:
768 * :indentSize=4:tabSize=8:noTabs=true: