1 /* decode_as_delegate.cpp
2 * Delegates for editing various field types in a Decode As record.
3 *
4 * Wireshark - Network traffic analyzer
5 * By Gerald Combs <gerald@wireshark.org>
6 * Copyright 1998 Gerald Combs
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "decode_as_delegate.h"
12
13 #include "epan/decode_as.h"
14 #include "epan/epan_dissect.h"
15
16 #include <ui/qt/utils/variant_pointer.h>
17
18 #include <QComboBox>
19 #include <QEvent>
20 #include <QLineEdit>
21 #include <QTreeView>
22
23 typedef struct _dissector_info_t {
24 QString proto_name;
25 dissector_handle_t dissector_handle;
26 } dissector_info_t;
27
Q_DECLARE_METATYPE(dissector_info_t *)28 Q_DECLARE_METATYPE(dissector_info_t *)
29
30 DecodeAsDelegate::DecodeAsDelegate(QObject *parent, capture_file *cf)
31 : QStyledItemDelegate(parent),
32 cap_file_(cf)
33 {
34 cachePacketProtocols();
35 }
36
indexToField(const QModelIndex & index) const37 DecodeAsItem* DecodeAsDelegate::indexToField(const QModelIndex &index) const
38 {
39 const QVariant v = index.model()->data(index, Qt::UserRole);
40 return static_cast<DecodeAsItem*>(v.value<void *>());
41 }
42
cachePacketProtocols()43 void DecodeAsDelegate::cachePacketProtocols()
44 {
45 //cache the list of potential decode as protocols in the current packet
46 if (cap_file_ && cap_file_->edt) {
47
48 wmem_list_frame_t * protos = wmem_list_head(cap_file_->edt->pi.layers);
49 guint8 curr_layer_num = 1;
50
51 while (protos != NULL) {
52 int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos));
53 const gchar * proto_name = proto_get_protocol_filter_name(proto_id);
54 for (GList *cur = decode_as_list; cur; cur = cur->next) {
55 decode_as_t *entry = (decode_as_t *) cur->data;
56 if (g_strcmp0(proto_name, entry->name) == 0) {
57 packet_proto_data_t proto_data;
58
59 proto_data.table_ui_name = get_dissector_table_ui_name(entry->table_name);
60 proto_data.proto_name = proto_name;
61 proto_data.curr_layer_num = curr_layer_num;
62
63 packet_proto_list_.append(proto_data);
64 }
65 }
66 protos = wmem_list_frame_next(protos);
67 curr_layer_num++;
68 }
69 }
70 }
71
collectDAProtocols(QSet<QString> & all_protocols,QList<QString> & current_list) const72 void DecodeAsDelegate::collectDAProtocols(QSet<QString>& all_protocols, QList<QString>& current_list) const
73 {
74 // If a packet is selected group its tables at the top in order
75 // from last-dissected to first.
76
77 //gather the initial list
78 for (GList *cur = decode_as_list; cur; cur = cur->next) {
79 decode_as_t *entry = (decode_as_t *) cur->data;
80 const char *table_name = get_dissector_table_ui_name(entry->table_name);
81 if (table_name) {
82 all_protocols.insert(get_dissector_table_ui_name(entry->table_name));
83 }
84 }
85
86 //filter out those in selected packet
87 foreach(packet_proto_data_t proto, packet_proto_list_)
88 {
89 current_list.append(proto.table_ui_name);
90 all_protocols.remove(proto.table_ui_name);
91 }
92 }
93
94 //Determine if there are multiple values in the selector field that would
95 //correspond to using a combo box
isSelectorCombo(DecodeAsItem * item) const96 bool DecodeAsDelegate::isSelectorCombo(DecodeAsItem* item) const
97 {
98 const gchar *proto_name = NULL;
99
100 foreach(packet_proto_data_t proto, packet_proto_list_)
101 {
102 if (g_strcmp0(proto.table_ui_name, item->tableUIName_) == 0) {
103 proto_name = proto.proto_name;
104 break;
105 }
106 }
107
108 for (GList *cur = decode_as_list; cur; cur = cur->next) {
109 decode_as_t *entry = (decode_as_t *) cur->data;
110 if ((g_strcmp0(proto_name, entry->name) == 0) &&
111 (g_strcmp0(item->tableName_, entry->table_name) == 0) &&
112 (cap_file_ && cap_file_->edt) &&
113 (entry->num_items > 1)) {
114 return true;
115 }
116 }
117
118 return false;
119 }
120
decodeAddProtocol(const gchar *,const gchar * proto_name,gpointer value,gpointer user_data)121 void DecodeAsDelegate::decodeAddProtocol(const gchar *, const gchar *proto_name, gpointer value, gpointer user_data)
122 {
123 QMap<QString, dissector_info_t*>* proto_list = (QMap<QString, dissector_info_t*>*)user_data;
124
125 if (!proto_list)
126 return;
127
128 dissector_info_t *dissector_info = new dissector_info_t();
129 dissector_info->proto_name = proto_name;
130 dissector_info->dissector_handle = (dissector_handle_t) value;
131
132 proto_list->insert(proto_name, dissector_info);
133 }
134
createEditor(QWidget * parentWidget,const QStyleOptionViewItem & option,const QModelIndex & index) const135 QWidget* DecodeAsDelegate::createEditor(QWidget *parentWidget, const QStyleOptionViewItem &option,
136 const QModelIndex &index) const
137 {
138 DecodeAsItem* item = indexToField(index);
139 QWidget *editor = nullptr;
140
141 switch(index.column())
142 {
143 case DecodeAsModel::colTable:
144 {
145 QComboBox *cb_editor = new QComboBox(parentWidget);
146 QSet<QString> da_set;
147 QList<QString> packet_list;
148 QString table_ui_name;
149
150 collectDAProtocols(da_set, packet_list);
151
152 cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
153
154 //put the protocols from the packet first in the combo box
155 foreach (table_ui_name, packet_list) {
156 cb_editor->addItem(table_ui_name, table_ui_name);
157 }
158 if (packet_list.count() > 0) {
159 cb_editor->insertSeparator(packet_list.count());
160 }
161
162 //put the rest of the protocols in the combo box
163 QList<QString> da_list = da_set.values();
164 std::sort(da_list.begin(), da_list.end());
165
166 foreach (table_ui_name, da_list) {
167 cb_editor->addItem(table_ui_name, table_ui_name);
168 }
169
170 //Make sure the combo box is at least as wide as the column
171 QTreeView* parentTree = (QTreeView*)parent();
172 int protoColWidth = parentTree->columnWidth(index.column());
173 if (protoColWidth > cb_editor->size().width())
174 cb_editor->setFixedWidth(protoColWidth);
175
176 editor = cb_editor;
177 break;
178 }
179 case DecodeAsModel::colSelector:
180 {
181 QComboBox *cb_editor = NULL;
182 const gchar *proto_name = NULL;
183 bool edt_present = cap_file_ && cap_file_->edt;
184 gint8 curr_layer_num_saved = edt_present ? cap_file_->edt->pi.curr_layer_num : 0;
185
186 foreach(packet_proto_data_t proto, packet_proto_list_)
187 {
188 if (g_strcmp0(proto.table_ui_name, item->tableUIName_) == 0) {
189 if (edt_present) {
190 cap_file_->edt->pi.curr_layer_num = proto.curr_layer_num;
191 }
192 proto_name = proto.proto_name;
193 //XXX - break? Or do we always want the last layer of tunnelled protocols?
194 }
195 }
196
197 for (GList *cur = decode_as_list; cur; cur = cur->next) {
198 decode_as_t *entry = (decode_as_t *) cur->data;
199 if ((g_strcmp0(proto_name, entry->name) == 0) &&
200 (g_strcmp0(item->tableName_, entry->table_name) == 0)) {
201 if (edt_present) {
202 if (entry->num_items > 1)
203 {
204 //only create a combobox if there is a choice of values, otherwise it looks funny
205 cb_editor = new QComboBox(parentWidget);
206
207 //Don't limit user to just what's in combo box
208 cb_editor->setEditable(true);
209
210 cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
211
212 //add the current value of the column
213 const QString& current_value = index.model()->data(index, Qt::EditRole).toString();
214 if (!current_value.isEmpty())
215 cb_editor->addItem(current_value);
216
217 //get the value(s) from the packet
218 for (uint ni = 0; ni < entry->num_items; ni++) {
219 if (entry->values[ni].num_values == 1) { // Skip over multi-value ("both") entries
220 QString entryStr = DecodeAsModel::entryString(entry->table_name,
221 entry->values[ni].build_values[0](&cap_file_->edt->pi));
222 //don't duplicate entries
223 if (cb_editor->findText(entryStr) < 0)
224 cb_editor->addItem(entryStr);
225 }
226 }
227 cb_editor->setCurrentIndex(entry->default_index_value);
228
229 //Make sure the combo box is at least as wide as the column
230 QTreeView* parentTree = (QTreeView*)parent();
231 int protoColWidth = parentTree->columnWidth(index.column());
232 if (protoColWidth > cb_editor->size().width())
233 cb_editor->setFixedWidth(protoColWidth);
234
235 }
236 }
237 break;
238 }
239 }
240
241 if (edt_present) {
242 cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved;
243 }
244
245 //if there isn't a need for a combobox, just let user have a text box for direct edit
246 if (cb_editor) {
247 editor = cb_editor;
248 } else {
249 editor = QStyledItemDelegate::createEditor(parentWidget, option, index);
250 }
251 break;
252 }
253
254 case DecodeAsModel::colProtocol:
255 {
256 QComboBox *cb_editor = new QComboBox(parentWidget);
257 QMap<QString, dissector_info_t*> protocols;
258
259 cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
260
261 for (GList *cur = decode_as_list; cur; cur = cur->next) {
262 decode_as_t *entry = (decode_as_t *) cur->data;
263 if (g_strcmp0(item->tableName_, entry->table_name) == 0) {
264 entry->populate_list(entry->table_name, decodeAddProtocol, &protocols);
265 break;
266 }
267 }
268
269 cb_editor->addItem(DECODE_AS_NONE);
270 cb_editor->insertSeparator(cb_editor->count());
271
272 //QMap already sorts the keys (protocols) alphabetically
273 QMap<QString, dissector_info_t*>::iterator protocol;
274 for (protocol = protocols.begin(); protocol != protocols.end(); ++protocol)
275 {
276 cb_editor->addItem(protocol.key(), VariantPointer<dissector_info_t>::asQVariant(protocol.value()));
277 }
278
279 //Make sure the combo box is at least as wide as the column
280 QTreeView* parentTree = (QTreeView*)parent();
281 int protoColWidth = parentTree->columnWidth(index.column());
282 if (protoColWidth > cb_editor->size().width())
283 cb_editor->setFixedWidth(protoColWidth);
284
285 editor = cb_editor;
286 break;
287 }
288 }
289
290 if (editor) {
291 editor->setAutoFillBackground(true);
292 }
293 return editor;
294 }
295
setEditorData(QWidget * editor,const QModelIndex & index) const296 void DecodeAsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
297 {
298 DecodeAsItem* item = indexToField(index);
299
300 switch(index.column())
301 {
302 case DecodeAsModel::colTable:
303 case DecodeAsModel::colProtocol:
304 {
305 QComboBox *combobox = static_cast<QComboBox *>(editor);
306 const QString &data = index.model()->data(index, Qt::EditRole).toString();
307 combobox->setCurrentText(data);
308 }
309 break;
310 case DecodeAsModel::colSelector:
311 if (isSelectorCombo(item)) {
312 QComboBox *combobox = static_cast<QComboBox *>(editor);
313 const QString &data = index.model()->data(index, Qt::EditRole).toString();
314 combobox->setCurrentText(data);
315 }
316 else {
317 QStyledItemDelegate::setEditorData(editor, index);
318 }
319 break;
320 default:
321 QStyledItemDelegate::setEditorData(editor, index);
322 break;
323 }
324 }
325
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const326 void DecodeAsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
327 const QModelIndex &index) const
328 {
329 DecodeAsItem* item = indexToField(index);
330
331 switch(index.column())
332 {
333 case DecodeAsModel::colTable:
334 {
335 QComboBox *combobox = static_cast<QComboBox *>(editor);
336 const QString &data = combobox->currentText();
337 model->setData(index, data, Qt::EditRole);
338 break;
339 }
340 case DecodeAsModel::colSelector:
341 if (isSelectorCombo(item)) {
342 QComboBox *combobox = static_cast<QComboBox *>(editor);
343 const QString &data = combobox->currentText();
344 model->setData(index, data, Qt::EditRole);
345 } else {
346 QStyledItemDelegate::setModelData(editor, model, index);
347 }
348 break;
349 case DecodeAsModel::colProtocol:
350 {
351 QComboBox *combobox = static_cast<QComboBox *>(editor);
352 const QString &data = combobox->currentText();
353 model->setData(index, data, Qt::EditRole);
354
355 //set the dissector handle
356 QVariant var = combobox->itemData(combobox->currentIndex());
357 dissector_info_t* dissector_info = VariantPointer<dissector_info_t>::asPtr(var);
358 if (dissector_info != NULL) {
359 ((DecodeAsModel*)model)->setDissectorHandle(index, dissector_info->dissector_handle);
360 } else {
361 ((DecodeAsModel*)model)->setDissectorHandle(index, NULL);
362 }
363 break;
364 }
365 default:
366 QStyledItemDelegate::setModelData(editor, model, index);
367 break;
368 }
369 }
370
371 #if 0
372 // Qt docs suggest overriding updateEditorGeometry, but the defaults seem sane.
373 void UatDelegate::updateEditorGeometry(QWidget *editor,
374 const QStyleOptionViewItem &option, const QModelIndex &index) const
375 {
376 QStyledItemDelegate::updateEditorGeometry(editor, option, index);
377 }
378 #endif
379