/* expert_info_dialog.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "expert_info_dialog.h" #include #include "file.h" #include #include #include #include #include #include "wireshark_application.h" #include #include #include #include #include #include // To do: // - Test with custom expert levels (Preferences -> Expert). // - Test with large captures. Add a custom model if needed. // - Promote to a fourth pane in the main window? // - Make colors configurable? In theory we could condense image/expert_indicators.svg, // down to one item, make sure it uses a single (or a few) base color(s), and generate // icons on the fly. enum { severity_col_, summary_col_, group_col_, protocol_col_, count_col_ }; enum { group_type_ = 1000, packet_type_ = 1001 }; static const int auto_expand_threshold_ = 20; // Arbitrary class ExpertGroupTreeWidgetItem : public QTreeWidgetItem { public: ExpertGroupTreeWidgetItem(QTreeWidget *parent, int severity, const QString &summary, int group, const QString &protocol) : QTreeWidgetItem (parent, group_type_) { // XXX We set text and data here, colors in addExpertInfo, and counts // in updateCounts. setData(severity_col_, Qt::UserRole, QVariant(severity)); setData(group_col_, Qt::UserRole, QVariant(group)); setText(severity_col_, val_to_str_const(severity, expert_severity_vals, "Unknown")); QString summary_raw = summary; // Might contain CR, LF, etc. setText(summary_col_, summary_raw.simplified()); setText(group_col_, val_to_str_const(group, expert_group_vals, "Unknown")); setText(protocol_col_, protocol); setText(count_col_, "0"); } void updateCounts() { int tot_children = childCount(); int vis_children = 0; for (int i = 0; i < tot_children; i++) { if (!child(i)->isHidden()) vis_children++; } QString count_str = QString::number(vis_children); if (vis_children != tot_children) { count_str += QString(" / %1").arg(tot_children); } setText(count_col_, count_str); setHidden(vis_children < 1); } bool operator< (const QTreeWidgetItem &other) const { int sort_col = treeWidget()->sortColumn(); switch(sort_col) { case severity_col_: return data(severity_col_, Qt::UserRole).value() < other.data(severity_col_, Qt::UserRole).value(); case count_col_: return text(count_col_).toInt() < other.text(count_col_).toInt(); default: return QTreeWidgetItem::operator<(other); } } }; class ExpertPacketTreeWidgetItem : public QTreeWidgetItem { public: ExpertPacketTreeWidgetItem(expert_info_t *expert_info = NULL, column_info *cinfo = NULL) : QTreeWidgetItem (packet_type_), group_by_summary_(true), packet_num_(0), group_(0), severity_(0), hf_id_(-1) { if (expert_info) { packet_num_ = expert_info->packet_num; group_ = expert_info->group; severity_ = expert_info->severity; hf_id_ = expert_info->hf_index; protocol_ = expert_info->protocol; summary_ = expert_info->summary; if (cinfo) { info_ = col_get_text(cinfo, COL_INFO); } } setTextAlignment(severity_col_, Qt::AlignRight); } virtual QVariant data(int column, int role) const { if (role == Qt::DisplayRole) { switch(column) { case severity_col_: return QString::number(packet_num_); break; case summary_col_: if (group_by_summary_) { return QString(info_).simplified(); } else { return QString(summary_).simplified(); } break; default: break; } } return QTreeWidgetItem::data(column, role); } guint32 packetNum() const { return packet_num_; } int group() const { return group_; } int severity() const { return severity_; } int hfId() const { return hf_id_; } QString protocol() const { return protocol_; } QString summary() const { return summary_; } QString groupKey(bool group_by_summary) { group_by_summary_ = group_by_summary; QString key = QString("%1|%2|%3") .arg(severity_) .arg(group_) .arg(QString(protocol_)); if (group_by_summary) { key += "|"; key += summary_; } return key; } bool operator< (const QTreeWidgetItem &other) const { // Probably not needed. if (other.type() != packet_type_) return QTreeWidgetItem::operator< (other); const ExpertPacketTreeWidgetItem *other_expert = static_cast(&other); // Force ascending. if (treeWidget()->header()->sortIndicatorOrder() == Qt::DescendingOrder) { return packet_num_ > other_expert->packetNum(); } else { return packet_num_ < other_expert->packetNum(); } } private: bool group_by_summary_; guint32 packet_num_; int group_; int severity_; int hf_id_; // Half-hearted attempt at conserving memory. If this isn't sufficient, // PacketListRecord interns column strings in a GStringChunk. QByteArray protocol_; QByteArray summary_; QByteArray info_; }; ExpertInfoDialog::ExpertInfoDialog(QWidget &parent, CaptureFile &capture_file) : WiresharkDialog(parent, capture_file), ui(new Ui::ExpertInfoDialog), need_show_hide_(false), display_filter_(QString()) { ui->setupUi(this); setWindowSubtitle(tr("Expert Information")); // Clicking on an item jumps to its associated packet. Make the dialog // narrow so that we avoid obscuring the packet list. int dlg_width = parent.width() * 3 / 5; if (dlg_width < width()) dlg_width = width(); loadGeometry(dlg_width, parent.height()); int one_em = fontMetrics().height(); ui->expertInfoTreeWidget->setColumnWidth(summary_col_, one_em * 25); // Arbitrary severity_actions_ = QList() << ui->actionShowError << ui->actionShowWarning << ui->actionShowNote << ui->actionShowChat << ui->actionShowComment; QList severities = QList() << PI_ERROR << PI_WARN << PI_NOTE << PI_CHAT << PI_COMMENT; QMenu *severity_menu = new QMenu(ui->severitiesPushButton); // It might be nice to color each menu item to match each severity. It // might also be nice if Qt supported that... foreach (QAction *sa, severity_actions_) { severity_menu->addAction(sa); sa->setData(QVariant(severities.takeFirst())); sa->setChecked(true); connect(sa, SIGNAL(toggled(bool)), this, SLOT(actionShowToggled())); } ui->severitiesPushButton->setMenu(severity_menu); ui->expertInfoTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->expertInfoTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showProtoHierMenu(QPoint))); QMenu *submenu; FilterAction::Action cur_action = FilterAction::ActionApply; submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { FilterAction *fa = new FilterAction(submenu, cur_action, at); submenu->addAction(fa); connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); } cur_action = FilterAction::ActionPrepare; submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { FilterAction *fa = new FilterAction(submenu, cur_action, at); submenu->addAction(fa); connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); } FilterAction *fa; QList extra_actions = QList() << FilterAction::ActionFind << FilterAction::ActionColorize << FilterAction::ActionWebLookup << FilterAction::ActionCopy; foreach (FilterAction::Action extra_action, extra_actions) { fa = new FilterAction(&ctx_menu_, extra_action); ctx_menu_.addAction(fa); connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); } connect(&cap_file_, SIGNAL(captureFileRetapStarted()), this, SLOT(retapStarted())); connect(&cap_file_, SIGNAL(captureFileRetapFinished()), this, SLOT(retapFinished())); setDisplayFilter(); QTimer::singleShot(0, this, SLOT(retapPackets())); } ExpertInfoDialog::~ExpertInfoDialog() { delete ui; } void ExpertInfoDialog::clearAllData() { ui->expertInfoTreeWidget->clear(); error_events_ = 0; warn_events_ = 0; note_events_ = 0; chat_events_ = 0; comment_events_ = 0; need_show_hide_ = false; ei_to_ti_.clear(); gti_packets_.clear(); } void ExpertInfoDialog::setDisplayFilter(const QString &display_filter) { display_filter_ = display_filter; updateWidgets(); } void ExpertInfoDialog::retapPackets() { if (file_closed_) return; clearAllData(); removeTapListeners(); if (!registerTapListener("expert", this, NULL, TL_REQUIRES_COLUMNS, tapReset, tapPacket, tapDraw)) { return; } if (ui->limitCheckBox->isChecked()) { GString *error_string = set_tap_dfilter(this, display_filter_.toUtf8().constData()); if (error_string) { QMessageBox::warning(this, tr("Endpoint expert failed to set filter"), error_string->str); g_string_free(error_string, TRUE); return; } } cap_file_.retapPackets(); } void ExpertInfoDialog::retapStarted() { ui->limitCheckBox->setEnabled(false); } void ExpertInfoDialog::retapFinished() { ui->limitCheckBox->setEnabled(! file_closed_ && ! display_filter_.isEmpty()); addPacketTreeItems(); for (int i = 0; i < ui->expertInfoTreeWidget->topLevelItemCount(); i++) { QTreeWidgetItem *group_ti = ui->expertInfoTreeWidget->topLevelItem(i); if (group_ti->childCount() <= auto_expand_threshold_) { group_ti->setExpanded(true); } } } void ExpertInfoDialog::addExpertInfo(ExpertPacketTreeWidgetItem *packet_ti) { if (!packet_ti) return; QTreeWidgetItem *group_ti; group_ti = ensureGroupTreeWidgetItem(packet_ti); gti_packets_[group_ti] << packet_ti; // XXX Use plain colors until our users demand to be blinded. // if (background.isValid()) { // packet_ti->setBackground(0, background); // packet_ti->setForeground(0, ColorUtils::expert_color_foreground); // } } void ExpertInfoDialog::addExpertInfo(struct expert_info_s *expert_info) { if (!expert_info) return; ExpertPacketTreeWidgetItem *packet_ti = new ExpertPacketTreeWidgetItem(expert_info, &(cap_file_.capFile()->cinfo)); addExpertInfo(packet_ti); } void ExpertInfoDialog::updateCounts() { for (int i = 0; i < ui->expertInfoTreeWidget->topLevelItemCount(); i++) { ExpertGroupTreeWidgetItem *group_ti = dynamic_cast(ui->expertInfoTreeWidget->topLevelItem(i)); if (group_ti) group_ti->updateCounts(); } } void ExpertInfoDialog::tapReset(void *eid_ptr) { ExpertInfoDialog *eid = static_cast(eid_ptr); if (!eid) return; eid->clearAllData(); } gboolean ExpertInfoDialog::tapPacket(void *eid_ptr, struct _packet_info *pinfo, struct epan_dissect *, const void *data) { ExpertInfoDialog *eid = static_cast(eid_ptr); expert_info_t *expert_info = (expert_info_t *) data; gboolean draw_required = FALSE; if (!pinfo || !eid || !expert_info) return FALSE; eid->addExpertInfo(expert_info); switch(expert_info->severity) { case(PI_COMMENT): if (eid->comment_events_ < 1) draw_required = TRUE; eid->comment_events_++; break; case(PI_CHAT): if (eid->chat_events_ < 1) draw_required = TRUE; eid->chat_events_++; break; case(PI_NOTE): if (eid->note_events_ < 1) draw_required = TRUE; eid->note_events_++; break; case(PI_WARN): if (eid->warn_events_ < 1) draw_required = TRUE; eid->warn_events_++; break; case(PI_ERROR): if (eid->error_events_ < 1) draw_required = TRUE; eid->error_events_++; break; default: g_assert_not_reached(); } return draw_required; } void ExpertInfoDialog::tapDraw(void *eid_ptr) { ExpertInfoDialog *eid = static_cast(eid_ptr); if (!eid) return; eid->addPacketTreeItems(); } QTreeWidgetItem *ExpertInfoDialog::ensureGroupTreeWidgetItem(ExpertPacketTreeWidgetItem *packet_ti) { if (!packet_ti) return NULL; QTreeWidgetItem *group_ti; QString key = packet_ti->groupKey(ui->groupBySummaryCheckBox->isChecked()); if (ei_to_ti_.contains(key)) { group_ti = ei_to_ti_[key]; } else { QString summary; if (ui->groupBySummaryCheckBox->isChecked()) summary = packet_ti->summary(); group_ti = new ExpertGroupTreeWidgetItem(ui->expertInfoTreeWidget, packet_ti->severity(), summary, packet_ti->group(), packet_ti->protocol()); QColor background; switch(packet_ti->severity()) { case(PI_COMMENT): background = ColorUtils::expert_color_comment; break; case(PI_CHAT): background = ColorUtils::expert_color_chat; break; case(PI_NOTE): background = ColorUtils::expert_color_note; break; case(PI_WARN): background = ColorUtils::expert_color_warn; break; case(PI_ERROR): background = ColorUtils::expert_color_error; break; default: break; } if (background.isValid()) { for (int i = 0; i < ui->expertInfoTreeWidget->columnCount(); i++) { group_ti->setBackground(i, background); group_ti->setForeground(i, ColorUtils::expert_color_foreground); } } ei_to_ti_[key] = group_ti; gti_packets_[group_ti] = QList(); } return group_ti; } void ExpertInfoDialog::addPacketTreeItems() { setUpdatesEnabled(false); // Adding a list of ExpertPacketTreeWidgetItems is much faster than // adding them individually. We still add ExpertGroupTreeWidgetItems // individually since that gives us a nice progress indicator. for (int i = 0; i < ui->expertInfoTreeWidget->topLevelItemCount(); i++) { QTreeWidgetItem *group_ti = ui->expertInfoTreeWidget->topLevelItem(i); if (gti_packets_.contains(group_ti)) { group_ti->addChildren(gti_packets_[group_ti]); gti_packets_[group_ti].clear(); } } setUpdatesEnabled(true); updateWidgets(); } void ExpertInfoDialog::updateWidgets() { ui->limitCheckBox->setEnabled(! file_closed_ && ! display_filter_.isEmpty()); ui->actionShowError->setEnabled(error_events_ > 0); ui->actionShowWarning->setEnabled(warn_events_ > 0); ui->actionShowNote->setEnabled(note_events_ > 0); ui->actionShowChat->setEnabled(chat_events_ > 0); ui->actionShowComment->setEnabled(comment_events_ > 0); if (need_show_hide_) { for (int i = 0; i < ui->expertInfoTreeWidget->topLevelItemCount(); i++) { QTreeWidgetItem *group_ti = ui->expertInfoTreeWidget->topLevelItem(i); switch (group_ti->data(severity_col_, Qt::UserRole).value()) { case PI_ERROR: group_ti->setHidden(! ui->actionShowError->isChecked()); break; case PI_WARN: group_ti->setHidden(! ui->actionShowWarning->isChecked()); break; case PI_NOTE: group_ti->setHidden(! ui->actionShowNote->isChecked()); break; case PI_CHAT: group_ti->setHidden(! ui->actionShowChat->isChecked()); break; case PI_COMMENT: group_ti->setHidden(! ui->actionShowComment->isChecked()); break; default: break; } } } updateCounts(); QString tooltip; QString hint; if (file_closed_) { tooltip = tr("Capture file closed."); hint = tr("Capture file closed."); } else if (display_filter_.isEmpty()) { tooltip = tr("No display filter"); hint = tr("No display filter set."); } else { tooltip = tr("Limit information to \"%1\".").arg(display_filter_); hint = tr("Display filter: \"%1\"").arg(display_filter_); } ui->limitCheckBox->setToolTip(tooltip); hint.prepend(""); hint.append(""); ui->hintLabel->setText(hint); } void ExpertInfoDialog::actionShowToggled() { need_show_hide_ = true; updateWidgets(); } void ExpertInfoDialog::showProtoHierMenu(QPoint pos) { bool enable = true; ExpertPacketTreeWidgetItem *packet_ti = dynamic_cast(ui->expertInfoTreeWidget->currentItem()); if (!packet_ti || packet_ti->hfId() < 0) { enable = false; } foreach (QMenu *submenu, ctx_menu_.findChildren()) { submenu->setEnabled(enable && !file_closed_); } foreach (QAction *action, ctx_menu_.actions()) { FilterAction *fa = qobject_cast(action); bool action_enable = enable && !file_closed_; if (fa && (fa->action() == FilterAction::ActionWebLookup || fa->action() == FilterAction::ActionCopy)) { action_enable = enable; } action->setEnabled(action_enable); } ctx_menu_.popup(ui->expertInfoTreeWidget->viewport()->mapToGlobal(pos)); } void ExpertInfoDialog::filterActionTriggered() { ExpertPacketTreeWidgetItem *packet_ti = dynamic_cast(ui->expertInfoTreeWidget->currentItem()); FilterAction *fa = qobject_cast(QObject::sender()); if (!fa || !packet_ti) { return; } int hf_index = packet_ti->hfId(); if (hf_index > -1) { QString filter_string; if (fa->action() == FilterAction::ActionWebLookup) { filter_string = QString("%1 %2") .arg(packet_ti->protocol()) .arg(packet_ti->summary()); } else if (fa->action() == FilterAction::ActionCopy) { filter_string = QString("%1 %2: %3") .arg(packet_ti->packetNum()) .arg(packet_ti->protocol()) .arg(packet_ti->summary()); } else { filter_string = proto_registrar_get_abbrev(hf_index); } if (! filter_string.isEmpty()) { emit filterAction(filter_string, fa->action(), fa->actionType()); } } } void ExpertInfoDialog::captureFileClosing() { WiresharkDialog::captureFileClosing(); } void ExpertInfoDialog::on_expertInfoTreeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *) { QString first_col_title = tr("Severity"); if (current && current->type() == packet_type_) { first_col_title = tr("Packet"); } ui->expertInfoTreeWidget->headerItem()->setText(severity_col_, first_col_title); // Ignore top-level items. if (!current || !current->parent() || file_closed_) return; ExpertPacketTreeWidgetItem *packet_ti = dynamic_cast(current); if (!packet_ti) return; emit goToPacket(packet_ti->packetNum(), packet_ti->hfId()); } void ExpertInfoDialog::on_limitCheckBox_toggled(bool) { retapPackets(); } void ExpertInfoDialog::on_groupBySummaryCheckBox_toggled(bool) { QList pending_items; QList group_items = ui->expertInfoTreeWidget->invisibleRootItem()->takeChildren(); foreach (QList gti_list, gti_packets_.values()) { pending_items.append(gti_list); } gti_packets_.clear(); ei_to_ti_.clear(); foreach (QTreeWidgetItem *ti, pending_items) { ExpertPacketTreeWidgetItem *packet_ti = dynamic_cast(ti); addExpertInfo(packet_ti); } addPacketTreeItems(); foreach (QTreeWidgetItem *gti, group_items) { QList packet_items = gti->takeChildren(); foreach (QTreeWidgetItem *ti, packet_items) { ExpertPacketTreeWidgetItem *packet_ti = dynamic_cast(ti); addExpertInfo(packet_ti); } delete gti; addPacketTreeItems(); } retapFinished(); // Expands tree items. } // Show child (packet list) items that match the contents of searchLineEdit. void ExpertInfoDialog::on_searchLineEdit_textChanged(const QString &search_re) { QTreeWidgetItemIterator it(ui->expertInfoTreeWidget, ui->groupBySummaryCheckBox->isChecked() ? QTreeWidgetItemIterator::HasChildren : QTreeWidgetItemIterator::NoChildren); QRegExp regex(search_re, Qt::CaseInsensitive); while (*it) { bool hidden = true; // XXX Check other columns as well? if (search_re.isEmpty() || (*it)->text(summary_col_).contains(regex)) { hidden = false; } (*it)->setHidden(hidden); ++it; } if (!ui->groupBySummaryCheckBox->isChecked()) { updateCounts(); } } void ExpertInfoDialog::on_buttonBox_helpRequested() { wsApp->helpTopicAction(HELP_EXPERT_INFO_DIALOG); } // Stat command + args static void expert_info_init(const char *, void*) { wsApp->emitStatCommandSignal("ExpertInfo", NULL, NULL); } static stat_tap_ui expert_info_stat_ui = { REGISTER_STAT_GROUP_GENERIC, NULL, "expert", expert_info_init, 0, NULL }; extern "C" { void register_tap_listener_qt_expert_info(void) { register_stat_tap_ui(&expert_info_stat_ui, NULL); } } /* * Editor modelines * * Local Variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * ex: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */