1 /* service_response_time_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "service_response_time_dialog.h"
11 
12 #include "file.h"
13 
14 #include <epan/tap.h>
15 #include <wsutil/ws_assert.h>
16 #include <ui/service_response_time.h>
17 
18 #include "rpc_service_response_time_dialog.h"
19 #include "scsi_service_response_time_dialog.h"
20 #include "wireshark_application.h"
21 
22 #include <QTreeWidget>
23 #include <QTreeWidgetItemIterator>
24 
25 static QHash<const QString, register_srt_t *> cfg_str_to_srt_;
26 
27 extern "C" {
28 static void
srt_init(const char * args,void *)29 srt_init(const char *args, void*) {
30     QStringList args_l = QString(args).split(',');
31     if (args_l.length() > 1) {
32         QString srt = QString("%1,%2").arg(args_l[0]).arg(args_l[1]);
33         QString filter;
34         if (args_l.length() > 2) {
35             filter = QStringList(args_l.mid(2)).join(",");
36         }
37         wsApp->emitTapParameterSignal(srt, filter, NULL);
38     }
39 }
40 }
41 
register_service_response_tables(const void *,void * value,void *)42 gboolean register_service_response_tables(const void *, void *value, void*)
43 {
44     register_srt_t *srt = (register_srt_t*)value;
45     const char* short_name = proto_get_protocol_short_name(find_protocol_by_id(get_srt_proto_id(srt)));
46     char *cfg_abbr = srt_table_get_tap_string(srt);
47     tpdCreator tpd_creator = ServiceResponseTimeDialog::createSrtDialog;
48 
49     /* XXX - These dissectors haven't been converted over to due to an "interactive input dialog" for their
50        tap data.  Let those specific dialogs register for themselves */
51     if (strcmp(short_name, "DCERPC") == 0) {
52         short_name = "DCE-RPC";
53         tpd_creator = RpcServiceResponseTimeDialog::createDceRpcSrtDialog;
54     } else if (strcmp(short_name, "RPC") == 0) {
55         short_name = "ONC-RPC";
56         tpd_creator = RpcServiceResponseTimeDialog::createOncRpcSrtDialog;
57     } else if (strcmp(short_name, "SCSI") == 0) {
58         tpd_creator = ScsiServiceResponseTimeDialog::createScsiSrtDialog;
59     }
60 
61     cfg_str_to_srt_[cfg_abbr] = srt;
62     TapParameterDialog::registerDialog(
63                 short_name,
64                 cfg_abbr,
65                 REGISTER_STAT_GROUP_RESPONSE_TIME,
66                 srt_init,
67                 tpd_creator);
68     g_free(cfg_abbr);
69     return FALSE;
70 }
71 
72 enum {
73     srt_table_type_ = 1000,
74     srt_row_type_
75 };
76 
77 class SrtRowTreeWidgetItem : public QTreeWidgetItem
78 {
79 public:
SrtRowTreeWidgetItem(QTreeWidgetItem * parent,const srt_procedure_t * procedure)80     SrtRowTreeWidgetItem(QTreeWidgetItem *parent, const srt_procedure_t *procedure) :
81         QTreeWidgetItem (parent, srt_row_type_),
82         procedure_(procedure)
83     {
84         setText(SRT_COLUMN_PROCEDURE, procedure_->procedure);
85         setHidden(true);
86     }
87 
draw()88     void draw() {
89         setText(SRT_COLUMN_INDEX, QString::number(procedure_->proc_index));
90         setText(SRT_COLUMN_CALLS, QString::number(procedure_->stats.num));
91         setText(SRT_COLUMN_MIN, QString::number(nstime_to_sec(&procedure_->stats.min), 'f', 6));
92         setText(SRT_COLUMN_MAX, QString::number(nstime_to_sec(&procedure_->stats.max), 'f', 6));
93         setText(SRT_COLUMN_AVG, QString::number(get_average(&procedure_->stats.tot, procedure_->stats.num) / 1000.0, 'f', 6));
94         setText(SRT_COLUMN_SUM, QString::number(nstime_to_sec(&procedure_->stats.tot), 'f', 6));
95 
96         for (int col = 0; col < columnCount(); col++) {
97             if (col == SRT_COLUMN_PROCEDURE) continue;
98             setTextAlignment(col, Qt::AlignRight);
99         }
100 
101         setHidden(procedure_->stats.num < 1);
102     }
103 
operator <(const QTreeWidgetItem & other) const104     bool operator< (const QTreeWidgetItem &other) const
105     {
106         if (other.type() != srt_row_type_) return QTreeWidgetItem::operator< (other);
107         const SrtRowTreeWidgetItem *other_row = static_cast<const SrtRowTreeWidgetItem *>(&other);
108 
109         switch (treeWidget()->sortColumn()) {
110         case SRT_COLUMN_INDEX:
111             return procedure_->proc_index < other_row->procedure_->proc_index;
112         case SRT_COLUMN_CALLS:
113             return procedure_->stats.num < other_row->procedure_->stats.num;
114         case SRT_COLUMN_MIN:
115             return nstime_cmp(&procedure_->stats.min, &other_row->procedure_->stats.min) < 0;
116         case SRT_COLUMN_MAX:
117             return nstime_cmp(&procedure_->stats.max, &other_row->procedure_->stats.max) < 0;
118         case SRT_COLUMN_AVG:
119         {
120             double our_avg = get_average(&procedure_->stats.tot, procedure_->stats.num);
121             double other_avg = get_average(&other_row->procedure_->stats.tot, other_row->procedure_->stats.num);
122             return our_avg < other_avg;
123         }
124         case SRT_COLUMN_SUM:
125             return nstime_cmp(&procedure_->stats.tot, &other_row->procedure_->stats.tot) < 0;
126         default:
127             break;
128         }
129 
130         return QTreeWidgetItem::operator< (other);
131     }
rowData()132     QList<QVariant> rowData() {
133         return QList<QVariant>() << QString(procedure_->procedure) << procedure_->proc_index << procedure_->stats.num
134                                  << nstime_to_sec(&procedure_->stats.min) << nstime_to_sec(&procedure_->stats.max)
135                                  << get_average(&procedure_->stats.tot, procedure_->stats.num) / 1000.0
136                                  << nstime_to_sec(&procedure_->stats.tot);
137     }
138 private:
139     const srt_procedure_t *procedure_;
140 };
141 
142 class SrtTableTreeWidgetItem : public QTreeWidgetItem
143 {
144 public:
SrtTableTreeWidgetItem(QTreeWidget * parent,const srt_stat_table * srt_table)145     SrtTableTreeWidgetItem(QTreeWidget *parent, const srt_stat_table *srt_table) :
146         QTreeWidgetItem (parent, srt_table_type_),
147         srt_table_(srt_table)
148     {
149         setText(0, srt_table_->name);
150         setFirstColumnSpanned(true);
151         setExpanded(true);
152 
153         for (int i = 0; i < srt_table_->num_procs; i++) {
154             new SrtRowTreeWidgetItem(this, &srt_table_->procedures[i]);
155         }
156     }
columnTitle()157     const QString columnTitle() { return srt_table_->proc_column_name; }
158 
rowData()159     QList<QVariant> rowData() {
160         return QList<QVariant>() << srt_table_->name;
161     }
filterField()162     const QString filterField() { return srt_table_->filter_string; }
163 
164 private:
165     const srt_stat_table *srt_table_;
166 };
167 
168 
ServiceResponseTimeDialog(QWidget & parent,CaptureFile & cf,register_srt * srt,const QString filter,int help_topic)169 ServiceResponseTimeDialog::ServiceResponseTimeDialog(QWidget &parent, CaptureFile &cf, register_srt *srt, const QString filter, int help_topic) :
170     TapParameterDialog(parent, cf, help_topic),
171     srt_(srt)
172 {
173     QString subtitle = QString("%1 Service Response Time Statistics")
174             .arg(proto_get_protocol_short_name(find_protocol_by_id(get_srt_proto_id(srt))));
175     setWindowSubtitle(subtitle);
176     loadGeometry(0, 0, "ServiceResponseTimeDialog");
177 
178     srt_data_.srt_array = NULL;
179     srt_data_.user_data = NULL;
180 
181     // Add number of columns for this stats_tree
182     QStringList header_labels;
183     for (int col = 0; col < NUM_SRT_COLUMNS; col++) {
184         header_labels.push_back(service_response_time_get_column_name(col));
185     }
186     statsTreeWidget()->setColumnCount(header_labels.count());
187     statsTreeWidget()->setHeaderLabels(header_labels);
188 
189     for (int col = 0; col < statsTreeWidget()->columnCount(); col++) {
190         if (col == SRT_COLUMN_PROCEDURE) continue;
191         statsTreeWidget()->headerItem()->setTextAlignment(col, Qt::AlignRight);
192     }
193 
194     addFilterActions();
195 
196     if (!filter.isEmpty()) {
197         setDisplayFilter(filter);
198     }
199 
200     connect(statsTreeWidget(), SIGNAL(itemChanged(QTreeWidgetItem*,int)),
201             this, SLOT(statsTreeWidgetItemChanged()));
202 }
203 
~ServiceResponseTimeDialog()204 ServiceResponseTimeDialog::~ServiceResponseTimeDialog()
205 {
206     if (srt_data_.srt_array) {
207         free_srt_table(srt_, srt_data_.srt_array);
208         g_array_free(srt_data_.srt_array, TRUE);
209     }
210 }
211 
createSrtDialog(QWidget & parent,const QString cfg_str,const QString filter,CaptureFile & cf)212 TapParameterDialog *ServiceResponseTimeDialog::createSrtDialog(QWidget &parent, const QString cfg_str, const QString filter, CaptureFile &cf)
213 {
214     if (!cfg_str_to_srt_.contains(cfg_str)) {
215         // XXX MessageBox?
216         return NULL;
217     }
218 
219     register_srt_t *srt = cfg_str_to_srt_[cfg_str];
220 
221     return new ServiceResponseTimeDialog(parent, cf, srt, filter);
222 }
223 
addSrtTable(const struct _srt_stat_table * srt_table)224 void ServiceResponseTimeDialog::addSrtTable(const struct _srt_stat_table *srt_table)
225 {
226     new SrtTableTreeWidgetItem(statsTreeWidget(), srt_table);
227 }
228 
tapReset(void * srtd_ptr)229 void ServiceResponseTimeDialog::tapReset(void *srtd_ptr)
230 {
231     srt_data_t *srtd = (srt_data_t*) srtd_ptr;
232     ServiceResponseTimeDialog *srt_dlg = static_cast<ServiceResponseTimeDialog *>(srtd->user_data);
233     if (!srt_dlg) return;
234 
235     reset_srt_table(srtd->srt_array);
236 
237     srt_dlg->statsTreeWidget()->clear();
238 }
239 
tapDraw(void * srtd_ptr)240 void ServiceResponseTimeDialog::tapDraw(void *srtd_ptr)
241 {
242     srt_data_t *srtd = (srt_data_t*) srtd_ptr;
243     ServiceResponseTimeDialog *srt_dlg = static_cast<ServiceResponseTimeDialog *>(srtd->user_data);
244     if (!srt_dlg || !srt_dlg->statsTreeWidget()) return;
245 
246     QTreeWidgetItemIterator it(srt_dlg->statsTreeWidget());
247     while (*it) {
248         if ((*it)->type() == srt_row_type_) {
249             SrtRowTreeWidgetItem *srtr_ti = static_cast<SrtRowTreeWidgetItem *>((*it));
250             srtr_ti->draw();
251         }
252         ++it;
253     }
254 
255     for (int i = 0; i < srt_dlg->statsTreeWidget()->columnCount() - 1; i++) {
256         srt_dlg->statsTreeWidget()->resizeColumnToContents(i);
257     }
258 }
259 
endRetapPackets()260 void ServiceResponseTimeDialog::endRetapPackets()
261 {
262     for (guint i = 0; i < srt_data_.srt_array->len; i++) {
263         srt_stat_table *srt_table = g_array_index(srt_data_.srt_array, srt_stat_table*, i);
264         addSrtTable(srt_table);
265     }
266     WiresharkDialog::endRetapPackets();
267 }
268 
fillTree()269 void ServiceResponseTimeDialog::fillTree()
270 {
271     if (srt_data_.srt_array) {
272         free_srt_table(srt_, srt_data_.srt_array);
273         g_array_free(srt_data_.srt_array, TRUE);
274     }
275     srt_data_.srt_array = g_array_new(FALSE, TRUE, sizeof(srt_stat_table*));
276     srt_data_.user_data = this;
277 
278     provideParameterData();
279 
280     srt_table_dissector_init(srt_, srt_data_.srt_array);
281 
282     QString display_filter = displayFilter();
283     if (!registerTapListener(get_srt_tap_listener_name(srt_),
284                         &srt_data_,
285                         display_filter.toUtf8().constData(),
286                         0,
287                         tapReset,
288                         get_srt_packet_func(srt_),
289                         tapDraw)) {
290         reject(); // XXX Stay open instead?
291         return;
292     }
293 
294     statsTreeWidget()->setSortingEnabled(false);
295 
296     cap_file_.retapPackets();
297 
298     // We only have one table. Move its tree items up one level.
299     if (statsTreeWidget()->invisibleRootItem()->childCount() == 1) {
300         statsTreeWidget()->setRootIndex(statsTreeWidget()->model()->index(0, 0));
301     }
302 
303     tapDraw(&srt_data_);
304 
305     statsTreeWidget()->sortItems(SRT_COLUMN_PROCEDURE, Qt::AscendingOrder);
306     statsTreeWidget()->setSortingEnabled(true);
307 
308     removeTapListeners();
309 }
310 
treeItemData(QTreeWidgetItem * ti) const311 QList<QVariant> ServiceResponseTimeDialog::treeItemData(QTreeWidgetItem *ti) const
312 {
313     QList<QVariant> tid;
314     if (ti->type() == srt_table_type_) {
315         SrtTableTreeWidgetItem *srtt_ti = static_cast<SrtTableTreeWidgetItem *>(ti);
316         if (srtt_ti) {
317             tid << srtt_ti->rowData();
318         }
319     } else if (ti->type() == srt_row_type_) {
320         SrtRowTreeWidgetItem *srtr_ti = static_cast<SrtRowTreeWidgetItem *>(ti);
321         if (srtr_ti) {
322             tid << srtr_ti->rowData();
323         }
324     }
325     return tid;
326 }
327 
filterExpression()328 const QString ServiceResponseTimeDialog::filterExpression()
329 {
330     QString filter_expr;
331     if (statsTreeWidget()->selectedItems().count() > 0) {
332         QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
333         if (ti->type() == srt_row_type_) {
334             SrtTableTreeWidgetItem *srtt_ti = static_cast<SrtTableTreeWidgetItem *>(ti->parent());
335             ws_assert(srtt_ti);
336             QString field = srtt_ti->filterField();
337             QString value = ti->text(SRT_COLUMN_INDEX);
338             if (!field.isEmpty() && !value.isEmpty()) {
339                 filter_expr = QString("%1==%2").arg(field).arg(value);
340             }
341         }
342     }
343     return filter_expr;
344 }
345 
statsTreeWidgetItemChanged()346 void ServiceResponseTimeDialog::statsTreeWidgetItemChanged()
347 {
348     QString procedure_title = service_response_time_get_column_name(SRT_COLUMN_PROCEDURE);
349 
350     if (statsTreeWidget()->selectedItems().count() > 0) {
351         QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
352         SrtTableTreeWidgetItem *srtt_ti = NULL;
353         if (ti->type() == srt_row_type_) {
354             srtt_ti = static_cast<SrtTableTreeWidgetItem *>(ti->parent());
355         } else {
356             srtt_ti = static_cast<SrtTableTreeWidgetItem *>(ti);
357         }
358         if (srtt_ti) {
359             procedure_title = srtt_ti->columnTitle();
360         }
361     }
362     statsTreeWidget()->headerItem()->setText(SRT_COLUMN_PROCEDURE, procedure_title);
363 }
364