/* interface_tree_cache_model.cpp * Model caching interface changes before sending them to global storage * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "epan/prefs.h" #include #include "ui/capture_globals.h" #include "wsutil/utf8_entities.h" #include "wiretap/wtap.h" #include "main_application.h" #include InterfaceTreeCacheModel::InterfaceTreeCacheModel(QObject *parent) : QIdentityProxyModel(parent) { /* ATTENTION: This cache model is not intended to be used with anything * else then InterfaceTreeModel, and will break with anything else * leading to unintended results. */ sourceModel = new InterfaceTreeModel(parent); QIdentityProxyModel::setSourceModel(sourceModel); storage = new QMap *>(); checkableColumns << IFTREE_COL_HIDDEN << IFTREE_COL_PROMISCUOUSMODE; #ifdef HAVE_PCAP_CREATE checkableColumns << IFTREE_COL_MONITOR_MODE; #endif editableColumns << IFTREE_COL_COMMENT << IFTREE_COL_SNAPLEN << IFTREE_COL_PIPE_PATH; #ifdef CAN_SET_CAPTURE_BUFFER_SIZE editableColumns << IFTREE_COL_BUFFERLEN; #endif } InterfaceTreeCacheModel::~InterfaceTreeCacheModel() { #ifdef HAVE_LIBPCAP /* This list should only exist, if the dialog is closed, without calling save first */ newDevices.clear(); #endif delete storage; delete sourceModel; } QVariant InterfaceTreeCacheModel::getColumnContent(int idx, int col, int role) { return InterfaceTreeCacheModel::data(index(idx, col), role); } #ifdef HAVE_LIBPCAP void InterfaceTreeCacheModel::reset(int row) { if (row < 0) { delete storage; storage = new QMap *>(); } else { if (storage->count() > row) storage->remove(storage->keys().at(row)); } } void InterfaceTreeCacheModel::saveNewDevices() { QList::const_iterator it = newDevices.constBegin(); /* idx is used for iterating only over the indices of the new devices. As all new * devices are stored with an index higher then sourceModel->rowCount(), we start * only with those storage indices. * it is just the iterator over the new devices. A new device must not necessarily * have storage, which will lead to that device not being stored in global_capture_opts */ for (int idx = sourceModel->rowCount(); it != newDevices.constEnd(); ++it, idx++) { interface_t *device = const_cast(&(*it)); bool useDevice = false; QMap * dataField = storage->value(idx, 0); /* When devices are being added, they are added using generic values. So only devices * whose data have been changed should be used from here on out. */ if (dataField != 0) { if (device->if_info.type != IF_PIPE) { continue; } if (device->if_info.type == IF_PIPE) { QVariant saveValue = dataField->value(IFTREE_COL_PIPE_PATH); if (saveValue.isValid()) { g_free(device->if_info.name); device->if_info.name = qstring_strdup(saveValue.toString()); g_free(device->name); device->name = qstring_strdup(saveValue.toString()); g_free(device->display_name); device->display_name = qstring_strdup(saveValue.toString()); useDevice = true; } } if (useDevice) g_array_append_val(global_capture_opts.all_ifaces, *device); } /* All entries of this new devices have been considered */ storage->remove(idx); delete dataField; } newDevices.clear(); } void InterfaceTreeCacheModel::save() { if (storage->count() == 0) return; QMap prefStorage; /* No devices are hidden until checking "Show" state */ prefStorage[&prefs.capture_devices_hide] = QStringList(); /* Storing new devices first including their changed values */ saveNewDevices(); for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++) { interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); if (! device->name) continue; /* Try to load a saved value row for this index */ QMap * dataField = storage->value(idx, 0); /* Handle the storing of values for this device here */ if (dataField) { QMap::const_iterator it = dataField->constBegin(); while (it != dataField->constEnd()) { InterfaceTreeColumns col = it.key(); QVariant saveValue = it.value(); /* Setting the field values for each individual saved value cannot be generic, as the * struct cannot be accessed in a generic way. Therefore below, each individually changed * value has to be handled separately. Comments are stored only in the preference file * and applied to the data name during loading. Therefore comments are not handled here */ if (col == IFTREE_COL_HIDDEN) { device->hidden = saveValue.toBool(); } else if (device->if_info.type == IF_EXTCAP) { /* extcap interfaces do not have the following columns. * ATTENTION: all generic columns must be added, BEFORE this * if-clause, or they will be ignored for extcap interfaces */ } else if (col == IFTREE_COL_PROMISCUOUSMODE) { device->pmode = saveValue.toBool(); } #ifdef HAVE_PCAP_CREATE else if (col == IFTREE_COL_MONITOR_MODE) { device->monitor_mode_enabled = saveValue.toBool(); } #endif else if (col == IFTREE_COL_SNAPLEN) { int iVal = saveValue.toInt(); if (iVal != WTAP_MAX_PACKET_SIZE_STANDARD) { device->has_snaplen = true; device->snaplen = iVal; } else { device->has_snaplen = false; device->snaplen = WTAP_MAX_PACKET_SIZE_STANDARD; } } #ifdef CAN_SET_CAPTURE_BUFFER_SIZE else if (col == IFTREE_COL_BUFFERLEN) { device->buffer = saveValue.toInt(); } #endif ++it; } } QVariant content = getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::CheckStateRole); if (content.isValid() && static_cast(content.toInt()) == Qt::Unchecked) prefStorage[&prefs.capture_devices_hide] << QString(device->name); content = getColumnContent(idx, IFTREE_COL_COMMENT); if (content.isValid() && content.toString().size() > 0) prefStorage[&prefs.capture_devices_descr] << QString("%1(%2)").arg(device->name).arg(content.toString()); bool allowExtendedColumns = true; if (device->if_info.type == IF_EXTCAP) allowExtendedColumns = false; if (allowExtendedColumns) { content = getColumnContent(idx, IFTREE_COL_PROMISCUOUSMODE, Qt::CheckStateRole); if (content.isValid()) { bool value = static_cast(content.toInt()) == Qt::Checked; prefStorage[&prefs.capture_devices_pmode] << QString("%1(%2)").arg(device->name).arg(value ? 1 : 0); } #ifdef HAVE_PCAP_CREATE content = getColumnContent(idx, IFTREE_COL_MONITOR_MODE, Qt::CheckStateRole); if (content.isValid() && static_cast(content.toInt()) == Qt::Checked) prefStorage[&prefs.capture_devices_monitor_mode] << QString(device->name); #endif content = getColumnContent(idx, IFTREE_COL_SNAPLEN); if (content.isValid()) { int value = content.toInt(); prefStorage[&prefs.capture_devices_snaplen] << QString("%1:%2(%3)").arg(device->name). arg(device->has_snaplen ? 1 : 0). arg(device->has_snaplen ? value : WTAP_MAX_PACKET_SIZE_STANDARD); } #ifdef CAN_SET_CAPTURE_BUFFER_SIZE content = getColumnContent(idx, IFTREE_COL_BUFFERLEN); if (content.isValid()) { int value = content.toInt(); if (value != -1) { prefStorage[&prefs.capture_devices_buffersize] << QString("%1(%2)").arg(device->name). arg(value); } } #endif } } QMap::const_iterator it = prefStorage.constBegin(); while (it != prefStorage.constEnd()) { char ** key = it.key(); g_free(*key); *key = qstring_strdup(it.value().join(",")); ++it; } mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged); } #endif int InterfaceTreeCacheModel::rowCount(const QModelIndex & parent) const { int totalCount = sourceModel->rowCount(parent); #ifdef HAVE_LIBPCAP totalCount += newDevices.size(); #endif return totalCount; } bool InterfaceTreeCacheModel::changeIsAllowed(InterfaceTreeColumns col) const { if (editableColumns.contains(col) || checkableColumns.contains(col)) return true; return false; } #ifdef HAVE_LIBPCAP const interface_t * InterfaceTreeCacheModel::lookup(const QModelIndex &index) const { const interface_t * result = 0; if (! index.isValid() || ! global_capture_opts.all_ifaces) return result; int idx = index.row(); if ((unsigned int) idx >= global_capture_opts.all_ifaces->len) { idx = idx - global_capture_opts.all_ifaces->len; if (idx < newDevices.size()) result = &newDevices[idx]; } else { result = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx); } return result; } #endif /* This checks if the column can be edited for the given index. This differs from * isAvailableField in such a way, that it is only used in flags and not any * other method.*/ bool InterfaceTreeCacheModel::isAllowedToBeEdited(const QModelIndex &index) const { #ifndef HAVE_LIBPCAP Q_UNUSED(index); #else const interface_t * device = lookup(index); if (device == 0) return false; InterfaceTreeColumns col = (InterfaceTreeColumns) index.column(); if (device->if_info.type == IF_EXTCAP) { /* extcap interfaces do not have those settings */ if (col == IFTREE_COL_PROMISCUOUSMODE || col == IFTREE_COL_SNAPLEN) return false; #ifdef CAN_SET_CAPTURE_BUFFER_SIZE if (col == IFTREE_COL_BUFFERLEN) return false; #endif } #endif return true; } // Whether this field is available for modification and display. bool InterfaceTreeCacheModel::isAvailableField(const QModelIndex &index) const { #ifndef HAVE_LIBPCAP Q_UNUSED(index); #else const interface_t * device = lookup(index); if (device == 0) return false; InterfaceTreeColumns col = (InterfaceTreeColumns) index.column(); if (col == IFTREE_COL_HIDDEN) { // Do not allow default capture interface to be hidden. if (! g_strcmp0(prefs.capture_device, device->display_name)) return false; } #endif return true; } Qt::ItemFlags InterfaceTreeCacheModel::flags(const QModelIndex &index) const { if (! index.isValid()) return Qt::ItemFlags(); Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; InterfaceTreeColumns col = (InterfaceTreeColumns) index.column(); if (changeIsAllowed(col) && isAvailableField(index) && isAllowedToBeEdited(index)) { if (checkableColumns.contains(col)) { flags |= Qt::ItemIsUserCheckable; } else { flags |= Qt::ItemIsEditable; } } return flags; } bool InterfaceTreeCacheModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (! index.isValid()) return false; if (! isAvailableField(index)) return false; int row = index.row(); InterfaceTreeColumns col = (InterfaceTreeColumns)index.column(); if (role == Qt::CheckStateRole || role == Qt::EditRole) { if (changeIsAllowed(col) ) { QVariant saveValue = value; QMap * dataField = 0; /* obtain the list of already stored changes for this row. If none exist * create a new storage row for this entry */ if ((dataField = storage->value(row, 0)) == 0) { dataField = new QMap(); storage->insert(row, dataField); } dataField->insert(col, saveValue); return true; } } return false; } QVariant InterfaceTreeCacheModel::data(const QModelIndex &index, int role) const { if (! index.isValid()) return QVariant(); int row = index.row(); InterfaceTreeColumns col = (InterfaceTreeColumns)index.column(); if (isAvailableField(index) && isAllowedToBeEdited(index)) { if (((role == Qt::DisplayRole || role == Qt::EditRole) && editableColumns.contains(col)) || (role == Qt::CheckStateRole && checkableColumns.contains(col)) ) { QMap * dataField = 0; if ((dataField = storage->value(row, 0)) != 0) { if (dataField->contains(col)) { return dataField->value(col, QVariant()); } } } } else { if (role == Qt::CheckStateRole) return QVariant(); else if (role == Qt::DisplayRole) return QString(UTF8_EM_DASH); } if (row < sourceModel->rowCount()) { return sourceModel->data(index, role); } #ifdef HAVE_LIBPCAP else { /* Handle all fields, which will have to be displayed for new devices. Only pipes * are supported at the moment, so the information to be displayed is pretty limited. * After saving, the devices are stored in global_capture_opts and no longer * classify as new devices. */ const interface_t * device = lookup(index); if (device != 0) { if (role == Qt::DisplayRole || role == Qt::EditRole) { if (col == IFTREE_COL_PIPE_PATH || col == IFTREE_COL_NAME || col == IFTREE_COL_DESCRIPTION) { QMap * dataField = 0; if ((dataField = storage->value(row, 0)) != 0 && dataField->contains(IFTREE_COL_PIPE_PATH)) { return dataField->value(IFTREE_COL_PIPE_PATH, QVariant()); } else return QString(device->name); } else if (col == IFTREE_COL_TYPE) { return QVariant::fromValue((int)device->if_info.type); } } else if (role == Qt::CheckStateRole) { if (col == IFTREE_COL_HIDDEN) { // Do not allow default capture interface to be hidden. if (! g_strcmp0(prefs.capture_device, device->display_name)) return QVariant(); /* Hidden is a de-selection, therefore inverted logic here */ return device->hidden ? Qt::Unchecked : Qt::Checked; } } } } #endif return QVariant(); } #ifdef HAVE_LIBPCAP QModelIndex InterfaceTreeCacheModel::index(int row, int column, const QModelIndex &parent) const { if (row >= sourceModel->rowCount() && (row - sourceModel->rowCount()) < newDevices.count()) { return createIndex(row, column, (void *)0); } return QIdentityProxyModel::index(row, column, parent); } void InterfaceTreeCacheModel::addDevice(const interface_t * newDevice) { emit beginInsertRows(QModelIndex(), rowCount(), rowCount()); newDevices << *newDevice; emit endInsertRows(); } void InterfaceTreeCacheModel::deleteDevice(const QModelIndex &index) { if (! index.isValid()) return; emit beginRemoveRows(QModelIndex(), index.row(), index.row()); int row = index.row(); /* device is in newDevices */ if (row >= sourceModel->rowCount()) { int newDeviceIdx = row - sourceModel->rowCount(); newDevices.removeAt(newDeviceIdx); if (storage->contains(index.row())) storage->remove(index.row()); /* The storage array has to be resorted, if the index, that was removed * had been in the middle of the array. Can't start at index.row(), as * it may not be contained in storage * We must iterate using a list, not an iterator, otherwise the change * will fold on itself. */ QList storageKeys = storage->keys(); for (int i = 0; i < storageKeys.size(); ++i) { int key = storageKeys.at(i); if (key > index.row()) { storage->insert(key - 1, storage->value(key)); storage->remove(key); } } emit endRemoveRows(); } else { interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row); capture_opts_free_interface_t(device); global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, row); emit endRemoveRows(); mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged); } } #endif