Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / ui / qt / models / packet_list_model.cpp
blobabc0b3ad902c02c13bf52f86cf4ff821d7926037
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
8 */
10 #include <algorithm>
11 #include <cmath>
12 #include <stdexcept>
14 #include "packet_list_model.h"
16 #include "file.h"
18 #include <wsutil/nstime.h>
19 #include <epan/column.h>
20 #include <epan/expert.h>
21 #include <epan/prefs.h>
23 #include "ui/packet_list_utils.h"
24 #include "ui/recent.h"
26 #include <epan/color_filters.h>
27 #include "frame_tvbuff.h"
29 #include <ui/qt/utils/color_utils.h>
30 #include <ui/qt/utils/qt_ui_utils.h>
31 #include "main_application.h"
32 #include <ui/qt/main_window.h>
33 #include <ui/qt/main_status_bar.h>
34 #include <ui/qt/widgets/wireless_timeline.h>
36 #include <QColor>
37 #include <QElapsedTimer>
38 #include <QFontMetrics>
39 #include <QModelIndex>
40 #include <QElapsedTimer>
42 // Print timing information
43 //#define DEBUG_PACKET_LIST_MODEL 1
45 #ifdef DEBUG_PACKET_LIST_MODEL
46 #include <wsutil/time_util.h>
47 #endif
49 class SortAbort : public std::runtime_error
51 using std::runtime_error::runtime_error;
54 static PacketListModel * glbl_plist_model = Q_NULLPTR;
55 static const int reserved_packets_ = 100000;
57 unsigned
58 packet_list_append(column_info *, frame_data *fdata)
60 if (!glbl_plist_model)
61 return 0;
63 /* fdata should be filled with the stuff we need
64 * strings are built at display time.
66 return glbl_plist_model->appendPacket(fdata);
69 void
70 packet_list_recreate_visible_rows(void)
72 if (glbl_plist_model)
73 glbl_plist_model->recreateVisibleRows();
76 PacketListModel::PacketListModel(QObject *parent, capture_file *cf) :
77 QAbstractItemModel(parent),
78 number_to_row_(QVector<int>()),
79 max_row_height_(0),
80 max_line_count_(1),
81 idle_dissection_row_(0)
83 Q_ASSERT(glbl_plist_model == Q_NULLPTR);
84 glbl_plist_model = this;
85 setCaptureFile(cf);
87 physical_rows_.reserve(reserved_packets_);
88 visible_rows_.reserve(reserved_packets_);
89 new_visible_rows_.reserve(1000);
90 number_to_row_.reserve(reserved_packets_);
92 if (qobject_cast<MainWindow *>(mainApp->mainWindow()))
94 MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
95 QWidget * wtWidget = mw->findChild<WirelessTimeline *>();
96 if (wtWidget && qobject_cast<WirelessTimeline *>(wtWidget))
98 WirelessTimeline * wt = qobject_cast<WirelessTimeline *>(wtWidget);
99 connect(this, &PacketListModel::bgColorizationProgress,
100 wt, &WirelessTimeline::bgColorizationProgress);
105 connect(this, &PacketListModel::maxLineCountChanged,
106 this, &PacketListModel::emitItemHeightChanged,
107 Qt::QueuedConnection);
108 idle_dissection_timer_ = new QElapsedTimer();
111 PacketListModel::~PacketListModel()
113 delete idle_dissection_timer_;
116 void PacketListModel::setCaptureFile(capture_file *cf)
118 cap_file_ = cf;
121 // Packet list records have no children (for now, at least).
122 QModelIndex PacketListModel::index(int row, int column, const QModelIndex &) const
124 if (row >= visible_rows_.count() || row < 0 || !cap_file_ || column >= prefs.num_cols)
125 return QModelIndex();
127 PacketListRecord *record = visible_rows_[row];
129 return createIndex(row, column, record);
132 // Everything is under the root.
133 QModelIndex PacketListModel::parent(const QModelIndex &) const
135 return QModelIndex();
138 int PacketListModel::packetNumberToRow(int packet_num) const
140 // map 1-based values to 0-based row numbers. Invisible rows are stored as
141 // the default value (0) and should map to -1.
142 return number_to_row_.value(packet_num) - 1;
145 unsigned PacketListModel::recreateVisibleRows()
147 beginResetModel();
148 visible_rows_.resize(0);
149 number_to_row_.fill(0);
150 endResetModel();
152 foreach (PacketListRecord *record, physical_rows_) {
153 frame_data *fdata = record->frameData();
155 if (fdata->passed_dfilter || fdata->ref_time) {
156 visible_rows_ << record;
157 if (static_cast<uint32_t>(number_to_row_.size()) <= fdata->num) {
158 number_to_row_.resize(fdata->num + 10000);
160 number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
163 if (!visible_rows_.isEmpty()) {
164 beginInsertRows(QModelIndex(), 0, static_cast<int>(visible_rows_.count()) - 1);
165 endInsertRows();
167 idle_dissection_row_ = 0;
168 return static_cast<unsigned>(visible_rows_.count());
171 void PacketListModel::clear() {
172 beginResetModel();
173 qDeleteAll(physical_rows_);
174 PacketListRecord::invalidateAllRecords();
175 physical_rows_.resize(0);
176 visible_rows_.resize(0);
177 new_visible_rows_.resize(0);
178 number_to_row_.resize(0);
179 endResetModel();
180 max_row_height_ = 0;
181 max_line_count_ = 1;
182 idle_dissection_timer_->invalidate();
183 idle_dissection_row_ = 0;
186 void PacketListModel::invalidateAllColumnStrings()
188 // https://bugreports.qt.io/browse/QTBUG-58580
189 // https://bugreports.qt.io/browse/QTBUG-124173
190 // https://codereview.qt-project.org/c/qt/qtbase/+/285280
192 // In Qt 6, QAbstractItemView::dataChanged determines how much of the
193 // viewport rectangle is covered by the changed indices and only updates
194 // that much. Unfortunately, if the number of indices is very large,
195 // computing the union of the intersecting rectangle takes much longer
196 // than unconditionally updating the entire viewport. It increases linearly
197 // with the total number of packets in the list, unlike updating the
198 // viewport, which scales with the size of the viewport but is unaffected
199 // by undisplayed packets.
201 // In particular, if the data for all of the model is invalidated, we
202 // know we want to update the entire viewport and very much do not
203 // want to waste time calculating the affected area. (This can take
204 // 1 s with 1.4 M packets, 9 s with 12 M packets.)
206 // Issuing layoutAboutToBeChanged() and layoutChanged() causes the
207 // QTreeView to clear all the information for each of the view items,
208 // but without clearing the current and selected items (unlike
209 // [begin|end]ResetModel.)
211 // Theoretically this is less efficient because dataChanged() has a list
212 // of what roles changed and the other signals do not; in practice,
213 // neither QTreeView::dataChanged nor QAbstractItemView::dataChanged
214 // actually use the roles parameter, and just reset everything.
215 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
216 emit layoutAboutToBeChanged();
217 #endif
218 PacketListRecord::invalidateAllRecords();
219 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
220 emit layoutChanged();
221 #else
222 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
223 QVector<int>() << Qt::DisplayRole);
224 #endif
227 void PacketListModel::resetColumns()
229 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
230 emit layoutAboutToBeChanged();
231 #endif
232 if (cap_file_) {
233 PacketListRecord::resetColumns(&cap_file_->cinfo);
236 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
237 emit layoutChanged();
238 #else
239 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
240 #endif
241 emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
244 void PacketListModel::resetColorized()
246 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
247 emit layoutAboutToBeChanged();
248 #endif
249 PacketListRecord::resetColorization();
250 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
251 emit layoutChanged();
252 #else
253 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
254 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
255 #endif
258 void PacketListModel::toggleFrameMark(const QModelIndexList &indeces)
260 if (!cap_file_ || indeces.count() <= 0)
261 return;
263 int sectionMax = columnCount() - 1;
265 foreach (QModelIndex index, indeces) {
266 if (! index.isValid())
267 continue;
269 PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
270 if (!record)
271 continue;
273 frame_data *fdata = record->frameData();
274 if (!fdata)
275 continue;
277 if (fdata->marked)
278 cf_unmark_frame(cap_file_, fdata);
279 else
280 cf_mark_frame(cap_file_, fdata);
282 emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
283 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
287 void PacketListModel::setDisplayedFrameMark(bool set)
289 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
290 emit layoutAboutToBeChanged();
291 #endif
292 foreach (PacketListRecord *record, visible_rows_) {
293 if (set) {
294 cf_mark_frame(cap_file_, record->frameData());
295 } else {
296 cf_unmark_frame(cap_file_, record->frameData());
299 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
300 emit layoutChanged();
301 #else
302 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
303 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
304 #endif
307 void PacketListModel::toggleFrameIgnore(const QModelIndexList &indeces)
309 if (!cap_file_ || indeces.count() <= 0)
310 return;
312 int sectionMax = columnCount() - 1;
314 foreach (QModelIndex index, indeces) {
315 if (! index.isValid())
316 continue;
318 PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
319 if (!record)
320 continue;
322 frame_data *fdata = record->frameData();
323 if (!fdata)
324 continue;
326 if (fdata->ignored)
327 cf_unignore_frame(cap_file_, fdata);
328 else
329 cf_ignore_frame(cap_file_, fdata);
331 emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
332 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
336 void PacketListModel::setDisplayedFrameIgnore(bool set)
338 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
339 emit layoutAboutToBeChanged();
340 #endif
341 foreach (PacketListRecord *record, visible_rows_) {
342 if (set) {
343 cf_ignore_frame(cap_file_, record->frameData());
344 } else {
345 cf_unignore_frame(cap_file_, record->frameData());
348 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
349 emit layoutChanged();
350 #else
351 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
352 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
353 #endif
356 void PacketListModel::toggleFrameRefTime(const QModelIndex &rt_index)
358 if (!cap_file_ || !rt_index.isValid()) return;
360 PacketListRecord *record = static_cast<PacketListRecord*>(rt_index.internalPointer());
361 if (!record) return;
363 frame_data *fdata = record->frameData();
364 if (!fdata) return;
366 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
367 emit layoutAboutToBeChanged();
368 #endif
369 if (fdata->ref_time) {
370 fdata->ref_time=0;
371 cap_file_->ref_time_count--;
372 } else {
373 fdata->ref_time=1;
374 cap_file_->ref_time_count++;
376 cf_reftime_packets(cap_file_);
377 if (!fdata->ref_time && !fdata->passed_dfilter) {
378 cap_file_->displayed_count--;
380 record->resetColumns(&cap_file_->cinfo);
381 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
384 void PacketListModel::unsetAllFrameRefTime()
386 if (!cap_file_) return;
388 /* XXX: we might need a progressbar here */
390 foreach (PacketListRecord *record, physical_rows_) {
391 frame_data *fdata = record->frameData();
392 if (fdata->ref_time) {
393 fdata->ref_time = 0;
396 cap_file_->ref_time_count = 0;
397 cf_reftime_packets(cap_file_);
398 PacketListRecord::resetColumns(&cap_file_->cinfo);
399 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
400 emit layoutChanged();
401 #else
402 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
403 #endif
406 void PacketListModel::addFrameComment(const QModelIndexList &indices, const QByteArray &comment)
408 int sectionMax = columnCount() - 1;
409 frame_data *fdata;
410 if (!cap_file_) return;
412 for (const auto &index : indices) {
413 if (!index.isValid()) continue;
415 PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
416 if (!record) continue;
418 fdata = record->frameData();
419 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
420 wtap_block_add_string_option(pkt_block, OPT_COMMENT, comment.data(), comment.size());
422 if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
423 cap_file_->packet_comment_count++;
424 expert_update_comment_count(cap_file_->packet_comment_count);
427 // In case there are coloring rules or columns related to comments.
428 // (#12519)
430 // XXX: "Does any active coloring rule relate to frame data"
431 // could be an optimization. For columns, note that
432 // "col_based_on_frame_data" only applies to built in columns,
433 // not custom columns based on frame data. (Should we prevent
434 // custom columns based on frame data from being created,
435 // substituting them with the other columns?)
437 // Note that there are not currently any fields that depend on
438 // whether other frames have comments, unlike with time references
439 // and time shifts ("frame.time_relative", "frame.offset_shift", etc.)
440 // If there were, then we'd need to reset data for all frames instead
441 // of just the frames changed.
442 record->invalidateColorized();
443 record->invalidateRecord();
444 emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
445 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
449 void PacketListModel::setFrameComment(const QModelIndex &index, const QByteArray &comment, unsigned c_number)
451 int sectionMax = columnCount() - 1;
452 frame_data *fdata;
453 if (!cap_file_) return;
455 if (!index.isValid()) return;
457 PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
458 if (!record) return;
460 fdata = record->frameData();
462 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
463 if (comment.isEmpty()) {
464 wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, c_number);
465 if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
466 cap_file_->packet_comment_count--;
467 expert_update_comment_count(cap_file_->packet_comment_count);
469 } else {
470 wtap_block_set_nth_string_option_value(pkt_block, OPT_COMMENT, c_number, comment.data(), comment.size());
471 cf_set_modified_block(cap_file_, fdata, pkt_block);
474 record->invalidateColorized();
475 record->invalidateRecord();
476 emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
477 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
480 void PacketListModel::deleteFrameComments(const QModelIndexList &indices)
482 int sectionMax = columnCount() - 1;
483 frame_data *fdata;
484 if (!cap_file_) return;
486 for (const auto &index : indices) {
487 if (!index.isValid()) continue;
489 PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
490 if (!record) continue;
492 fdata = record->frameData();
493 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
494 unsigned n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
496 if (n_comments) {
497 for (unsigned i = 0; i < n_comments; i++) {
498 wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
500 if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
501 cap_file_->packet_comment_count -= n_comments;
502 expert_update_comment_count(cap_file_->packet_comment_count);
505 record->invalidateColorized();
506 record->invalidateRecord();
507 emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
508 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
513 void PacketListModel::deleteAllFrameComments()
515 int row;
516 int sectionMax = columnCount() - 1;
517 if (!cap_file_) return;
519 /* XXX: we might need a progressbar here */
521 foreach (PacketListRecord *record, physical_rows_) {
522 frame_data *fdata = record->frameData();
523 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
524 unsigned n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
526 if (n_comments) {
527 for (unsigned i = 0; i < n_comments; i++) {
528 wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
530 cf_set_modified_block(cap_file_, fdata, pkt_block);
532 record->invalidateColorized();
533 record->invalidateRecord();
534 row = packetNumberToRow(fdata->num);
535 if (row > -1) {
536 emit dataChanged(index(row, 0), index(row, sectionMax),
537 QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
541 cap_file_->packet_comment_count = 0;
542 expert_update_comment_count(cap_file_->packet_comment_count);
545 void PacketListModel::setMaximumRowHeight(int height)
547 max_row_height_ = height;
548 // As the QTreeView uniformRowHeights documentation says,
549 // "The height is obtained from the first item in the view. It is
550 // updated when the data changes on that item."
551 emit dataChanged(index(0, 0), index(0, columnCount() - 1));
554 int PacketListModel::sort_column_;
555 int PacketListModel::sort_column_is_numeric_;
556 int PacketListModel::text_sort_column_;
557 Qt::SortOrder PacketListModel::sort_order_;
558 capture_file *PacketListModel::sort_cap_file_;
559 bool PacketListModel::stop_flag_;
560 ProgressFrame *PacketListModel::progress_frame_;
561 double PacketListModel::comps_;
562 double PacketListModel::exp_comps_;
564 QElapsedTimer busy_timer_;
565 const int busy_timeout_ = 65; // ms, approximately 15 fps
566 void PacketListModel::sort(int column, Qt::SortOrder order)
568 if (!cap_file_ || visible_rows_.count() < 1) return;
569 if (column < 0) return;
571 if (physical_rows_.count() < 1)
572 return;
574 sort_column_ = column;
575 text_sort_column_ = PacketListRecord::textColumn(column);
576 sort_order_ = order;
577 sort_cap_file_ = cap_file_;
579 QString col_title = get_column_title(column);
581 if (text_sort_column_ >= 0 && (unsigned)visible_rows_.count() > prefs.gui_packet_list_cached_rows_max) {
582 /* Column not based on frame data but by column text that requires
583 * dissection, so to sort in a reasonable amount of time the column
584 * text needs to be cached.
586 /* If the sort is being triggered because the columns were already
587 * sorted and the filter is being cleared (or changed to something
588 * else with more rows than fit in the cache), then the temporary
589 * message will be immediately overwritten with the standard capture
590 * statistics by the packets_bar_update() call after thawing the rows.
591 * It will still blink yellow, and the user will get the message if
592 * they then click on the header file (wondering why it didn't sort.)
594 if (col_title.isEmpty()) {
595 col_title = tr("Column");
597 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);
598 mainApp->pushStatus(MainApplication::TemporaryStatus, temp_msg);
599 return;
602 /* If we are currently in the middle of reading the capture file, don't
603 * sort. PacketList::captureFileReadFinished invalidates all the cached
604 * column strings and then tries to sort again.
605 * Similarly, claim the read lock because we don't want the file to
606 * change out from under us while sorting, which can segfault. (Previously
607 * we ignored user input, but now in order to cancel sorting we don't.)
609 if (sort_cap_file_->read_lock) {
610 ws_info("Refusing to sort because capture file is being read");
611 /* We shouldn't have to tell the user because we're just deferring
612 * the sort until PacketList::captureFileReadFinished
614 return;
616 sort_cap_file_->read_lock = true;
618 QString busy_msg;
619 if (!col_title.isEmpty()) {
620 busy_msg = tr("Sorting \"%1\"…").arg(col_title);
621 } else {
622 busy_msg = tr("Sorting …");
624 stop_flag_ = false;
625 comps_ = 0;
626 /* XXX: The expected number of comparisons is O(N log N), but this could
627 * be a pretty significant overestimate of the amount of time it takes,
628 * if there are lots of identical entries. (Especially with string
629 * comparisons, some comparisons are faster than others.) Better to
630 * overestimate?
632 exp_comps_ = log2(visible_rows_.count()) * visible_rows_.count();
633 progress_frame_ = nullptr;
634 if (qobject_cast<MainWindow *>(mainApp->mainWindow())) {
635 MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
636 progress_frame_ = mw->findChild<ProgressFrame *>();
637 if (progress_frame_) {
638 progress_frame_->showProgress(busy_msg, true, false, &stop_flag_, 0);
639 connect(progress_frame_, &ProgressFrame::stopLoading,
640 this, &PacketListModel::stopSorting);
644 busy_timer_.start();
645 sort_column_is_numeric_ = isNumericColumn(sort_column_);
646 QVector<PacketListRecord *> sorted_visible_rows_ = visible_rows_;
647 try {
648 std::sort(sorted_visible_rows_.begin(), sorted_visible_rows_.end(), recordLessThan);
650 beginResetModel();
651 visible_rows_.resize(0);
652 number_to_row_.fill(0);
653 foreach (PacketListRecord *record, sorted_visible_rows_) {
654 frame_data *fdata = record->frameData();
656 if (fdata->passed_dfilter || fdata->ref_time) {
657 visible_rows_ << record;
658 if (number_to_row_.size() <= (int)fdata->num) {
659 number_to_row_.resize(fdata->num + 10000);
661 number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
664 endResetModel();
665 } catch (const SortAbort& e) {
666 mainApp->pushStatus(MainApplication::TemporaryStatus, e.what());
669 if (progress_frame_ != nullptr) {
670 progress_frame_->hide();
671 disconnect(progress_frame_, &ProgressFrame::stopLoading,
672 this, &PacketListModel::stopSorting);
674 sort_cap_file_->read_lock = false;
676 if (cap_file_->current_frame) {
677 emit goToPacket(cap_file_->current_frame->num);
681 void PacketListModel::stopSorting()
683 stop_flag_ = true;
686 bool PacketListModel::isNumericColumn(int column)
688 /* XXX - Should this and ui/packet_list_utils.c right_justify_column()
689 * be the same list of columns?
691 if (column < 0) {
692 return false;
694 switch (sort_cap_file_->cinfo.columns[column].col_fmt) {
695 case COL_CUMULATIVE_BYTES: /**< 3) Cumulative number of bytes */
696 case COL_DELTA_TIME: /**< 5) Delta time */
697 case COL_DELTA_TIME_DIS: /**< 8) Delta time displayed*/
698 case COL_UNRES_DST_PORT: /**< 10) Unresolved dest port */
699 case COL_FREQ_CHAN: /**< 15) IEEE 802.11 (and WiMax?) - Channel */
700 case COL_RSSI: /**< 22) IEEE 802.11 - received signal strength */
701 case COL_TX_RATE: /**< 23) IEEE 802.11 - TX rate in Mbps */
702 case COL_NUMBER: /**< 32) Packet list item number */
703 case COL_NUMBER_DIS: /**< 33) Packet list item number */
704 case COL_PACKET_LENGTH: /**< 34) Packet length in bytes */
705 case COL_UNRES_SRC_PORT: /**< 42) Unresolved source port */
706 return true;
709 * Try to sort port numbers as number, if the numeric comparison fails (due
710 * to name resolution), it will fallback to string comparison.
711 * */
712 case COL_RES_DST_PORT: /**< 10) Resolved dest port */
713 case COL_DEF_DST_PORT: /**< 12) Destination port */
714 case COL_DEF_SRC_PORT: /**< 38) Source port */
715 case COL_RES_SRC_PORT: /**< 41) Resolved source port */
716 return true;
718 case COL_CUSTOM:
719 /* handle custom columns below. */
720 break;
722 default:
723 return false;
726 unsigned num_fields = g_slist_length(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids);
727 col_custom_t *col_custom;
728 for (unsigned i = 0; i < num_fields; i++) {
729 col_custom = (col_custom_t *) g_slist_nth_data(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids, i);
730 if (col_custom->field_id == 0) {
731 ftenum_t type = dfilter_get_return_type(col_custom->dfilter);
732 if (FT_IS_INTEGER(type) || FT_IS_FLOATING(type) || type == FT_BOOLEAN || type == FT_RELATIVE_TIME) {
733 return true;
735 return false;
737 header_field_info *hfi = proto_registrar_get_nth(col_custom->field_id);
740 * Reject a field when there is no numeric field type or when:
741 * - there are (value_string) "strings"
742 * (but do accept fields which have a unit suffix).
743 * - BASE_HEX or BASE_HEX_DEC (these have a constant width, string
744 * comparison is faster than conversion to double).
745 * - BASE_CUSTOM (these can be formatted in any way).
747 if (!hfi ||
748 (hfi->strings != NULL && !(hfi->display & BASE_UNIT_STRING)) ||
749 !(((FT_IS_INT(hfi->type) || FT_IS_UINT(hfi->type)) &&
750 ((FIELD_DISPLAY(hfi->display) == BASE_DEC) ||
751 (FIELD_DISPLAY(hfi->display) == BASE_OCT) ||
752 (FIELD_DISPLAY(hfi->display) == BASE_DEC_HEX))) ||
753 (hfi->type == FT_DOUBLE) || (hfi->type == FT_FLOAT) ||
754 (hfi->type == FT_BOOLEAN) || (hfi->type == FT_FRAMENUM) ||
755 (hfi->type == FT_RELATIVE_TIME))) {
756 return false;
760 return true;
763 bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2)
765 int cmp_val = 0;
766 comps_++;
768 // Wherein we try to cram the logic of packet_list_compare_records,
769 // _packet_list_compare_records, and packet_list_compare_custom from
770 // gtk/packet_list_store.c into one function
772 if (busy_timer_.elapsed() > busy_timeout_) {
773 if (progress_frame_) {
774 progress_frame_->setValue(static_cast<int>(comps_/exp_comps_ * 100));
776 // What's the least amount of processing that we can do which will draw
777 // the busy indicator?
778 mainApp->processEvents(QEventLoop::ExcludeSocketNotifiers, 1);
779 if (stop_flag_) {
780 throw SortAbort("Sorting aborted");
782 busy_timer_.restart();
784 if (sort_column_ < 0) {
785 // No column.
786 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
787 } else if (text_sort_column_ < 0) {
788 // Column comes directly from frame data
789 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), sort_cap_file_->cinfo.columns[sort_column_].col_fmt);
790 } else {
791 QString r1String = r1->columnString(sort_cap_file_, sort_column_);
792 QString r2String = r2->columnString(sort_cap_file_, sort_column_);
793 // XXX: The naive string comparison compares Unicode code points.
794 // Proper collation is more expensive
795 cmp_val = r1String.compare(r2String);
796 if (cmp_val != 0 && sort_column_is_numeric_) {
797 // Custom column with numeric data (or something like a port number).
798 // Attempt to convert to numbers.
799 // XXX This is slow. Can we avoid doing this? Perhaps the actual
800 // values used for sorting should be cached too as QVariant[List].
801 // If so, we could consider using QCollatorSortKeys or similar
802 // for strings as well.
803 bool ok_r1, ok_r2;
804 double num_r1 = parseNumericColumn(r1String, &ok_r1);
805 double num_r2 = parseNumericColumn(r2String, &ok_r2);
807 if (!ok_r1 && !ok_r2) {
808 cmp_val = 0;
809 } else if (!ok_r1 || (ok_r2 && num_r1 < num_r2)) {
810 // either r1 is invalid (and sort it before others) or both
811 // r1 and r2 are valid (sort normally)
812 cmp_val = -1;
813 } else if (!ok_r2 || (num_r1 > num_r2)) {
814 cmp_val = 1;
818 if (cmp_val == 0) {
819 // All else being equal, compare column numbers.
820 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
824 if (sort_order_ == Qt::AscendingOrder) {
825 return cmp_val < 0;
826 } else {
827 return cmp_val > 0;
831 // Parses a field as a double. Handle values with suffixes ("12ms"), negative
832 // values ("-1.23") and fields with multiple occurrences ("1,2"). Marks values
833 // that do not contain any numeric value ("Unknown") as invalid.
834 double PacketListModel::parseNumericColumn(const QString &val, bool *ok)
836 QByteArray ba = val.toUtf8();
837 const char *strval = ba.constData();
838 char *end = NULL;
839 double num = g_ascii_strtod(strval, &end);
840 *ok = strval != end;
841 return num;
844 // ::data is const so we have to make changes here.
845 void PacketListModel::emitItemHeightChanged(const QModelIndex &ih_index)
847 if (!ih_index.isValid()) return;
849 PacketListRecord *record = static_cast<PacketListRecord*>(ih_index.internalPointer());
850 if (!record) return;
852 if (record->lineCount() > max_line_count_) {
853 max_line_count_ = record->lineCount();
854 emit itemHeightChanged(ih_index);
858 int PacketListModel::rowCount(const QModelIndex &) const
860 return static_cast<int>(visible_rows_.count());
863 int PacketListModel::columnCount(const QModelIndex &) const
865 return prefs.num_cols;
868 QVariant PacketListModel::data(const QModelIndex &d_index, int role) const
870 if (!d_index.isValid())
871 return QVariant();
873 PacketListRecord *record = static_cast<PacketListRecord*>(d_index.internalPointer());
874 if (!record)
875 return QVariant();
876 const frame_data *fdata = record->frameData();
877 if (!fdata)
878 return QVariant();
880 switch (role) {
881 case Qt::TextAlignmentRole:
882 switch(recent_get_column_xalign(d_index.column())) {
883 case COLUMN_XALIGN_RIGHT:
884 return Qt::AlignRight;
885 case COLUMN_XALIGN_CENTER:
886 return Qt::AlignCenter;
887 case COLUMN_XALIGN_LEFT:
888 return Qt::AlignLeft;
889 case COLUMN_XALIGN_DEFAULT:
890 default:
891 if (right_justify_column(d_index.column(), cap_file_)) {
892 return Qt::AlignRight;
894 break;
896 return Qt::AlignLeft;
898 case Qt::BackgroundRole:
899 const color_t *color;
900 if (fdata->ignored) {
901 color = &prefs.gui_ignored_bg;
902 } else if (fdata->marked) {
903 color = &prefs.gui_marked_bg;
904 } else if (fdata->color_filter && recent.packet_list_colorize) {
905 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
906 color = &color_filter->bg_color;
907 } else {
908 return QVariant();
910 return ColorUtils::fromColorT(color);
911 case Qt::ForegroundRole:
912 if (fdata->ignored) {
913 color = &prefs.gui_ignored_fg;
914 } else if (fdata->marked) {
915 color = &prefs.gui_marked_fg;
916 } else if (fdata->color_filter && recent.packet_list_colorize) {
917 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
918 color = &color_filter->fg_color;
919 } else {
920 return QVariant();
922 return ColorUtils::fromColorT(color);
923 case Qt::DisplayRole:
925 int column = d_index.column();
926 QString column_string = record->columnString(cap_file_, column, true);
927 // We don't know an item's sizeHint until we fetch its text here.
928 // Assume each line count is 1. If the line count changes, emit
929 // itemHeightChanged which triggers another redraw (including a
930 // fetch of SizeHintRole and DisplayRole) in the next event loop.
931 if (column == 0 && record->lineCountChanged() && record->lineCount() > max_line_count_) {
932 emit maxLineCountChanged(d_index);
934 return column_string;
936 case Qt::SizeHintRole:
938 // If this is the first row and column, return the maximum row height...
939 if (d_index.row() < 1 && d_index.column() < 1 && max_row_height_ > 0) {
940 QSize size = QSize(-1, max_row_height_);
941 return size;
943 // ...otherwise punt so that the item delegate can correctly calculate the item width.
944 return QVariant();
946 default:
947 return QVariant();
951 QVariant PacketListModel::headerData(int section, Qt::Orientation orientation,
952 int role) const
954 if (!cap_file_) return QVariant();
956 if (orientation == Qt::Horizontal && section < prefs.num_cols) {
957 switch (role) {
958 case Qt::DisplayRole:
959 return QVariant::fromValue(QString(get_column_title(section)));
960 case Qt::ToolTipRole:
961 return QVariant::fromValue(gchar_free_to_qstring(get_column_tooltip(section)));
962 case PacketListModel::HEADER_CAN_DISPLAY_STRINGS:
963 return (bool)display_column_strings(section, cap_file_);
964 case PacketListModel::HEADER_CAN_DISPLAY_DETAILS:
965 return (bool)display_column_details(section, cap_file_);
966 default:
967 break;
971 return QVariant();
974 void PacketListModel::flushVisibleRows()
976 int pos = static_cast<int>(visible_rows_.count());
978 if (new_visible_rows_.count() > 0) {
979 beginInsertRows(QModelIndex(), pos, pos + static_cast<int>(new_visible_rows_.count()));
980 foreach (PacketListRecord *record, new_visible_rows_) {
981 frame_data *fdata = record->frameData();
983 visible_rows_ << record;
984 if (static_cast<unsigned int>(number_to_row_.size()) <= fdata->num) {
985 number_to_row_.resize(fdata->num + 10000);
987 number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
989 endInsertRows();
990 new_visible_rows_.resize(0);
994 // Fill our column string and colorization cache while the application is
995 // idle. Try to be as conservative with the CPU and disk as possible.
996 static const int idle_dissection_interval_ = 5; // ms
997 void PacketListModel::dissectIdle(bool reset)
999 if (reset) {
1000 // qDebug() << "=di reset" << idle_dissection_row_;
1001 idle_dissection_row_ = 0;
1002 } else if (!idle_dissection_timer_->isValid()) {
1003 return;
1006 idle_dissection_timer_->restart();
1008 if (!cap_file_ || cap_file_->read_lock) {
1009 // File is in use (at worst, being rescanned). Try again later.
1010 QTimer::singleShot(idle_dissection_interval_, this, [=]() { dissectIdle(); });
1011 return;
1014 int first = idle_dissection_row_;
1015 while (idle_dissection_timer_->elapsed() < idle_dissection_interval_
1016 && idle_dissection_row_ < physical_rows_.count()) {
1017 ensureRowColorized(idle_dissection_row_);
1018 idle_dissection_row_++;
1019 // if (idle_dissection_row_ % 1000 == 0) qDebug() << "=di row" << idle_dissection_row_;
1022 if (idle_dissection_row_ < physical_rows_.count()) {
1023 QTimer::singleShot(0, this, [=]() { dissectIdle(); });
1024 } else {
1025 idle_dissection_timer_->invalidate();
1028 // report colorization progress
1029 emit bgColorizationProgress(first+1, idle_dissection_row_+1);
1032 // XXX Pass in cinfo from packet_list_append so that we can fill in
1033 // line counts?
1034 int PacketListModel::appendPacket(frame_data *fdata)
1036 PacketListRecord *record = new PacketListRecord(fdata);
1037 qsizetype pos = -1;
1039 #ifdef DEBUG_PACKET_LIST_MODEL
1040 if (fdata->num % 10000 == 1) {
1041 log_resource_usage(fdata->num == 1, "%u packets", fdata->num);
1043 #endif
1045 physical_rows_ << record;
1047 if (fdata->passed_dfilter || fdata->ref_time) {
1048 new_visible_rows_ << record;
1049 if (new_visible_rows_.count() < 2) {
1050 // This is the first queued packet. Schedule an insertion for
1051 // the next UI update.
1052 QTimer::singleShot(0, this, &PacketListModel::flushVisibleRows);
1054 pos = static_cast<int>( visible_rows_.count() + new_visible_rows_.count() ) - 1;
1057 emit packetAppended(cap_file_, fdata, physical_rows_.size() - 1);
1059 return static_cast<int>(pos);
1062 frame_data *PacketListModel::getRowFdata(QModelIndex idx) const
1064 if (!idx.isValid())
1065 return Q_NULLPTR;
1066 return getRowFdata(idx.row());
1069 frame_data *PacketListModel::getRowFdata(int row) const {
1070 if (row < 0 || row >= visible_rows_.count())
1071 return NULL;
1072 PacketListRecord *record = visible_rows_[row];
1073 if (!record)
1074 return NULL;
1075 return record->frameData();
1078 void PacketListModel::ensureRowColorized(int row)
1080 if (row < 0 || row >= visible_rows_.count())
1081 return;
1082 PacketListRecord *record = visible_rows_[row];
1083 if (!record)
1084 return;
1085 if (!record->colorized()) {
1086 record->ensureColorized(cap_file_);
1090 int PacketListModel::visibleIndexOf(frame_data *fdata) const
1092 if (fdata == nullptr) {
1093 return -1;
1095 return packetNumberToRow(fdata->num);