1 /* interface_tree_cache_model.cpp
2 * Model caching interface changes before sending them to global storage
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
12 #include <ui/qt/models/interface_tree_cache_model.h>
13
14 #include "glib.h"
15
16 #include "epan/prefs.h"
17
18 #include <ui/qt/utils/qt_ui_utils.h>
19 #include "ui/capture_globals.h"
20 #include "wsutil/utf8_entities.h"
21
22 #include "wiretap/wtap.h"
23
24 #include "wireshark_application.h"
25
26 #include <QIdentityProxyModel>
27
InterfaceTreeCacheModel(QObject * parent)28 InterfaceTreeCacheModel::InterfaceTreeCacheModel(QObject *parent) :
29 QIdentityProxyModel(parent)
30 {
31 /* ATTENTION: This cache model is not intended to be used with anything
32 * else then InterfaceTreeModel, and will break with anything else
33 * leading to unintended results. */
34 sourceModel = new InterfaceTreeModel(parent);
35
36 QIdentityProxyModel::setSourceModel(sourceModel);
37 storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
38
39 checkableColumns << IFTREE_COL_HIDDEN << IFTREE_COL_PROMISCUOUSMODE;
40 #ifdef HAVE_PCAP_CREATE
41 checkableColumns << IFTREE_COL_MONITOR_MODE;
42 #endif
43
44 editableColumns << IFTREE_COL_COMMENT << IFTREE_COL_SNAPLEN << IFTREE_COL_PIPE_PATH;
45
46 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
47 editableColumns << IFTREE_COL_BUFFERLEN;
48 #endif
49 }
50
~InterfaceTreeCacheModel()51 InterfaceTreeCacheModel::~InterfaceTreeCacheModel()
52 {
53 #ifdef HAVE_LIBPCAP
54 /* This list should only exist, if the dialog is closed, without calling save first */
55 newDevices.clear();
56 #endif
57
58 delete storage;
59 delete sourceModel;
60 }
61
getColumnContent(int idx,int col,int role)62 QVariant InterfaceTreeCacheModel::getColumnContent(int idx, int col, int role)
63 {
64 return InterfaceTreeCacheModel::data(index(idx, col), role);
65 }
66
67 #ifdef HAVE_LIBPCAP
reset(int row)68 void InterfaceTreeCacheModel::reset(int row)
69 {
70 if (row < 0)
71 {
72 delete storage;
73 storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
74 }
75 else
76 {
77 if (storage->count() > row)
78 storage->remove(storage->keys().at(row));
79 }
80 }
81
saveNewDevices()82 void InterfaceTreeCacheModel::saveNewDevices()
83 {
84 QList<interface_t>::const_iterator it = newDevices.constBegin();
85 /* idx is used for iterating only over the indices of the new devices. As all new
86 * devices are stored with an index higher then sourceModel->rowCount(), we start
87 * only with those storage indices.
88 * it is just the iterator over the new devices. A new device must not necessarily
89 * have storage, which will lead to that device not being stored in global_capture_opts */
90 for (int idx = sourceModel->rowCount(); it != newDevices.constEnd(); ++it, idx++)
91 {
92 interface_t *device = const_cast<interface_t *>(&(*it));
93 bool useDevice = false;
94
95 QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
96 /* When devices are being added, they are added using generic values. So only devices
97 * whose data have been changed should be used from here on out. */
98 if (dataField != 0)
99 {
100 if (device->if_info.type != IF_PIPE)
101 {
102 continue;
103 }
104
105 if (device->if_info.type == IF_PIPE)
106 {
107 QVariant saveValue = dataField->value(IFTREE_COL_PIPE_PATH);
108 if (saveValue.isValid())
109 {
110 g_free(device->if_info.name);
111 device->if_info.name = qstring_strdup(saveValue.toString());
112
113 g_free(device->name);
114 device->name = qstring_strdup(saveValue.toString());
115
116 g_free(device->display_name);
117 device->display_name = qstring_strdup(saveValue.toString());
118 useDevice = true;
119 }
120 }
121
122 if (useDevice)
123 g_array_append_val(global_capture_opts.all_ifaces, *device);
124
125 }
126
127 /* All entries of this new devices have been considered */
128 storage->remove(idx);
129 delete dataField;
130 }
131
132 newDevices.clear();
133 }
134
save()135 void InterfaceTreeCacheModel::save()
136 {
137 if (storage->count() == 0)
138 return;
139
140 QMap<char**, QStringList> prefStorage;
141
142 /* No devices are hidden until checking "Show" state */
143 prefStorage[&prefs.capture_devices_hide] = QStringList();
144
145 /* Storing new devices first including their changed values */
146 saveNewDevices();
147
148
149 for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++)
150 {
151 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
152
153 if (! device->name)
154 continue;
155
156 /* Try to load a saved value row for this index */
157 QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
158
159 /* Handle the storing of values for this device here */
160 if (dataField)
161 {
162 QMap<InterfaceTreeColumns, QVariant>::const_iterator it = dataField->constBegin();
163 while (it != dataField->constEnd())
164 {
165 InterfaceTreeColumns col = it.key();
166 QVariant saveValue = it.value();
167
168 /* Setting the field values for each individual saved value cannot be generic, as the
169 * struct cannot be accessed in a generic way. Therefore below, each individually changed
170 * value has to be handled separately. Comments are stored only in the preference file
171 * and applied to the data name during loading. Therefore comments are not handled here */
172
173 if (col == IFTREE_COL_HIDDEN)
174 {
175 device->hidden = saveValue.toBool();
176 }
177 else if (device->if_info.type == IF_EXTCAP)
178 {
179 /* extcap interfaces do not have the following columns.
180 * ATTENTION: all generic columns must be added, BEFORE this
181 * if-clause, or they will be ignored for extcap interfaces */
182 }
183 else if (col == IFTREE_COL_PROMISCUOUSMODE)
184 {
185 device->pmode = saveValue.toBool();
186 }
187 #ifdef HAVE_PCAP_CREATE
188 else if (col == IFTREE_COL_MONITOR_MODE)
189 {
190 device->monitor_mode_enabled = saveValue.toBool();
191 }
192 #endif
193 else if (col == IFTREE_COL_SNAPLEN)
194 {
195 int iVal = saveValue.toInt();
196 if (iVal != WTAP_MAX_PACKET_SIZE_STANDARD)
197 {
198 device->has_snaplen = true;
199 device->snaplen = iVal;
200 }
201 else
202 {
203 device->has_snaplen = false;
204 device->snaplen = WTAP_MAX_PACKET_SIZE_STANDARD;
205 }
206 }
207 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
208 else if (col == IFTREE_COL_BUFFERLEN)
209 {
210 device->buffer = saveValue.toInt();
211 }
212 #endif
213 ++it;
214 }
215 }
216
217 QVariant content = getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::CheckStateRole);
218 if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Unchecked)
219 prefStorage[&prefs.capture_devices_hide] << QString(device->name);
220
221 content = getColumnContent(idx, IFTREE_COL_COMMENT);
222 if (content.isValid() && content.toString().size() > 0)
223 prefStorage[&prefs.capture_devices_descr] << QString("%1(%2)").arg(device->name).arg(content.toString());
224
225 bool allowExtendedColumns = true;
226
227 if (device->if_info.type == IF_EXTCAP)
228 allowExtendedColumns = false;
229
230 if (allowExtendedColumns)
231 {
232 content = getColumnContent(idx, IFTREE_COL_PROMISCUOUSMODE, Qt::CheckStateRole);
233 if (content.isValid())
234 {
235 bool value = static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked;
236 prefStorage[&prefs.capture_devices_pmode] << QString("%1(%2)").arg(device->name).arg(value ? 1 : 0);
237 }
238
239 #ifdef HAVE_PCAP_CREATE
240 content = getColumnContent(idx, IFTREE_COL_MONITOR_MODE, Qt::CheckStateRole);
241 if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked)
242 prefStorage[&prefs.capture_devices_monitor_mode] << QString(device->name);
243 #endif
244
245 content = getColumnContent(idx, IFTREE_COL_SNAPLEN);
246 if (content.isValid())
247 {
248 int value = content.toInt();
249 prefStorage[&prefs.capture_devices_snaplen] <<
250 QString("%1:%2(%3)").arg(device->name).
251 arg(device->has_snaplen ? 1 : 0).
252 arg(device->has_snaplen ? value : WTAP_MAX_PACKET_SIZE_STANDARD);
253 }
254
255 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
256 content = getColumnContent(idx, IFTREE_COL_BUFFERLEN);
257 if (content.isValid())
258 {
259 int value = content.toInt();
260 if (value != -1)
261 {
262 prefStorage[&prefs.capture_devices_buffersize] <<
263 QString("%1(%2)").arg(device->name).
264 arg(value);
265 }
266 }
267 #endif
268 }
269 }
270
271 QMap<char**, QStringList>::const_iterator it = prefStorage.constBegin();
272 while (it != prefStorage.constEnd())
273 {
274 char ** key = it.key();
275
276 g_free(*key);
277 *key = qstring_strdup(it.value().join(","));
278
279 ++it;
280 }
281
282 wsApp->emitAppSignal(WiresharkApplication::LocalInterfacesChanged);
283 }
284 #endif
285
rowCount(const QModelIndex & parent) const286 int InterfaceTreeCacheModel::rowCount(const QModelIndex & parent) const
287 {
288 int totalCount = sourceModel->rowCount(parent);
289 #ifdef HAVE_LIBPCAP
290 totalCount += newDevices.size();
291 #endif
292 return totalCount;
293 }
294
changeIsAllowed(InterfaceTreeColumns col) const295 bool InterfaceTreeCacheModel::changeIsAllowed(InterfaceTreeColumns col) const
296 {
297 if (editableColumns.contains(col) || checkableColumns.contains(col))
298 return true;
299 return false;
300 }
301
302 #ifdef HAVE_LIBPCAP
lookup(const QModelIndex & index) const303 const interface_t * InterfaceTreeCacheModel::lookup(const QModelIndex &index) const
304 {
305 const interface_t * result = 0;
306
307 if (! index.isValid() || ! global_capture_opts.all_ifaces)
308 return result;
309
310 int idx = index.row();
311
312 if ((unsigned int) idx >= global_capture_opts.all_ifaces->len)
313 {
314 idx = idx - global_capture_opts.all_ifaces->len;
315 if (idx < newDevices.size())
316 result = &newDevices[idx];
317 }
318 else
319 {
320 result = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
321 }
322
323 return result;
324 }
325 #endif
326
327 /* This checks if the column can be edited for the given index. This differs from
328 * isAvailableField in such a way, that it is only used in flags and not any
329 * other method.*/
isAllowedToBeEdited(const QModelIndex & index) const330 bool InterfaceTreeCacheModel::isAllowedToBeEdited(const QModelIndex &index) const
331 {
332 #ifndef HAVE_LIBPCAP
333 Q_UNUSED(index);
334 #else
335 const interface_t * device = lookup(index);
336 if (device == 0)
337 return false;
338
339 InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
340 if (device->if_info.type == IF_EXTCAP)
341 {
342 /* extcap interfaces do not have those settings */
343 if (col == IFTREE_COL_PROMISCUOUSMODE || col == IFTREE_COL_SNAPLEN)
344 return false;
345 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
346 if (col == IFTREE_COL_BUFFERLEN)
347 return false;
348 #endif
349 }
350 #endif
351 return true;
352 }
353
354 // Whether this field is available for modification and display.
isAvailableField(const QModelIndex & index) const355 bool InterfaceTreeCacheModel::isAvailableField(const QModelIndex &index) const
356 {
357 #ifndef HAVE_LIBPCAP
358 Q_UNUSED(index);
359 #else
360 const interface_t * device = lookup(index);
361
362 if (device == 0)
363 return false;
364
365 InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
366 if (col == IFTREE_COL_HIDDEN)
367 {
368 // Do not allow default capture interface to be hidden.
369 if (! g_strcmp0(prefs.capture_device, device->display_name))
370 return false;
371 }
372 #endif
373
374 return true;
375 }
376
flags(const QModelIndex & index) const377 Qt::ItemFlags InterfaceTreeCacheModel::flags(const QModelIndex &index) const
378 {
379 if (! index.isValid())
380 return Qt::ItemFlags();
381
382 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
383
384 InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
385
386 if (changeIsAllowed(col) && isAvailableField(index) && isAllowedToBeEdited(index))
387 {
388 if (checkableColumns.contains(col))
389 {
390 flags |= Qt::ItemIsUserCheckable;
391 }
392 else
393 {
394 flags |= Qt::ItemIsEditable;
395 }
396 }
397
398 return flags;
399 }
400
setData(const QModelIndex & index,const QVariant & value,int role)401 bool InterfaceTreeCacheModel::setData(const QModelIndex &index, const QVariant &value, int role)
402 {
403 if (! index.isValid())
404 return false;
405
406 if (! isAvailableField(index))
407 return false;
408
409 int row = index.row();
410 InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
411
412 if (role == Qt::CheckStateRole || role == Qt::EditRole)
413 {
414 if (changeIsAllowed(col) )
415 {
416 QVariant saveValue = value;
417
418 QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
419 /* obtain the list of already stored changes for this row. If none exist
420 * create a new storage row for this entry */
421 if ((dataField = storage->value(row, 0)) == 0)
422 {
423 dataField = new QMap<InterfaceTreeColumns, QVariant>();
424 storage->insert(row, dataField);
425 }
426
427 dataField->insert(col, saveValue);
428
429 return true;
430 }
431 }
432
433 return false;
434 }
435
data(const QModelIndex & index,int role) const436 QVariant InterfaceTreeCacheModel::data(const QModelIndex &index, int role) const
437 {
438 if (! index.isValid())
439 return QVariant();
440
441 int row = index.row();
442
443 InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
444
445 if (isAvailableField(index) && isAllowedToBeEdited(index))
446 {
447 if (((role == Qt::DisplayRole || role == Qt::EditRole) && editableColumns.contains(col)) ||
448 (role == Qt::CheckStateRole && checkableColumns.contains(col)) )
449 {
450 QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
451 if ((dataField = storage->value(row, 0)) != 0)
452 {
453 if (dataField->contains(col))
454 {
455 return dataField->value(col, QVariant());
456 }
457 }
458 }
459 }
460 else
461 {
462 if (role == Qt::CheckStateRole)
463 return QVariant();
464 else if (role == Qt::DisplayRole)
465 return QString(UTF8_EM_DASH);
466 }
467
468 if (row < sourceModel->rowCount())
469 {
470 return sourceModel->data(index, role);
471 }
472 #ifdef HAVE_LIBPCAP
473 else
474 {
475 /* Handle all fields, which will have to be displayed for new devices. Only pipes
476 * are supported at the moment, so the information to be displayed is pretty limited.
477 * After saving, the devices are stored in global_capture_opts and no longer
478 * classify as new devices. */
479 const interface_t * device = lookup(index);
480
481 if (device != 0)
482 {
483 if (role == Qt::DisplayRole || role == Qt::EditRole)
484 {
485 if (col == IFTREE_COL_PIPE_PATH ||
486 col == IFTREE_COL_NAME ||
487 col == IFTREE_COL_DESCRIPTION)
488 {
489
490 QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
491 if ((dataField = storage->value(row, 0)) != 0 &&
492 dataField->contains(IFTREE_COL_PIPE_PATH))
493 {
494 return dataField->value(IFTREE_COL_PIPE_PATH, QVariant());
495 }
496 else
497 return QString(device->name);
498 }
499 else if (col == IFTREE_COL_TYPE)
500 {
501 return QVariant::fromValue((int)device->if_info.type);
502 }
503 }
504 else if (role == Qt::CheckStateRole)
505 {
506 if (col == IFTREE_COL_HIDDEN)
507 {
508 // Do not allow default capture interface to be hidden.
509 if (! g_strcmp0(prefs.capture_device, device->display_name))
510 return QVariant();
511
512 /* Hidden is a de-selection, therefore inverted logic here */
513 return device->hidden ? Qt::Unchecked : Qt::Checked;
514 }
515 }
516 }
517 }
518 #endif
519
520 return QVariant();
521 }
522
523 #ifdef HAVE_LIBPCAP
index(int row,int column,const QModelIndex & parent) const524 QModelIndex InterfaceTreeCacheModel::index(int row, int column, const QModelIndex &parent) const
525 {
526 if (row >= sourceModel->rowCount() && (row - sourceModel->rowCount()) < newDevices.count())
527 {
528 return createIndex(row, column, (void *)0);
529 }
530
531 return QIdentityProxyModel::index(row, column, parent);
532 }
533
addDevice(const interface_t * newDevice)534 void InterfaceTreeCacheModel::addDevice(const interface_t * newDevice)
535 {
536 emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
537 newDevices << *newDevice;
538 emit endInsertRows();
539 }
540
deleteDevice(const QModelIndex & index)541 void InterfaceTreeCacheModel::deleteDevice(const QModelIndex &index)
542 {
543 if (! index.isValid())
544 return;
545
546 emit beginRemoveRows(QModelIndex(), index.row(), index.row());
547
548 int row = index.row();
549
550 /* device is in newDevices */
551 if (row >= sourceModel->rowCount())
552 {
553 int newDeviceIdx = row - sourceModel->rowCount();
554
555 newDevices.removeAt(newDeviceIdx);
556 if (storage->contains(index.row()))
557 storage->remove(index.row());
558
559 /* The storage array has to be resorted, if the index, that was removed
560 * had been in the middle of the array. Can't start at index.row(), as
561 * it may not be contained in storage
562 * We must iterate using a list, not an iterator, otherwise the change
563 * will fold on itself. */
564 QList<int> storageKeys = storage->keys();
565 for (int i = 0; i < storageKeys.size(); ++i)
566 {
567 int key = storageKeys.at(i);
568 if (key > index.row())
569 {
570 storage->insert(key - 1, storage->value(key));
571 storage->remove(key);
572 }
573 }
574
575 emit endRemoveRows();
576 }
577 else
578 {
579 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row);
580 capture_opts_free_interface_t(device);
581 global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, row);
582 emit endRemoveRows();
583 wsApp->emitAppSignal(WiresharkApplication::LocalInterfacesChanged);
584 }
585 }
586 #endif
587