/* decode_as_model.cpp
 * Data model for Decode As records.
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <errno.h>

#include "decode_as_model.h"
#include <epan/to_str.h>
#include <epan/decode_as.h>
#include <epan/epan_dissect.h>
#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/prefs-int.h>
#include <epan/dissectors/packet-dcerpc.h>

#include <ui/qt/utils/qt_ui_utils.h>
#include <ui/qt/utils/variant_pointer.h>
#include <wsutil/file_util.h>
#include <wsutil/ws_assert.h>

#include <QVector>

static const char *DEFAULT_TABLE = "tcp.port";    // Arbitrary
static const char *DEFAULT_UI_TABLE = "TCP port";    // Arbitrary

DecodeAsItem::DecodeAsItem(const char* table_name, gconstpointer selector) :
 tableName_(DEFAULT_TABLE),
 tableUIName_(DEFAULT_UI_TABLE),
 selectorUint_(0),
 selectorString_(""),
 selectorDCERPC_(NULL),
 default_dissector_(DECODE_AS_NONE),
 current_dissector_(DECODE_AS_NONE),
 dissector_handle_(NULL)
{
    if (table_name == nullptr)
        return;

    init(table_name, selector);
}

DecodeAsItem::DecodeAsItem(const decode_as_t *entry, gconstpointer selector) :
 tableName_(DEFAULT_TABLE),
 tableUIName_(DEFAULT_UI_TABLE),
 selectorUint_(0),
 selectorString_(""),
 selectorDCERPC_(NULL),
 default_dissector_(DECODE_AS_NONE),
 current_dissector_(DECODE_AS_NONE),
 dissector_handle_(NULL)
{
    if (entry == nullptr)
        return;

    init(entry->table_name, selector);
}

DecodeAsItem::~DecodeAsItem()
{
}

void DecodeAsItem::init(const char* table_name, gconstpointer selector)
{
    tableName_ = table_name;
    tableUIName_ = get_dissector_table_ui_name(tableName_);

    dissector_handle_t default_handle = NULL;
    ftenum_t selector_type = get_dissector_table_selector_type(tableName_);
    if (FT_IS_STRING(selector_type)) {
        if (selector != NULL) {
            default_handle = dissector_get_default_string_handle(tableName_, (const gchar*)selector);
            selectorString_ = QString((const char*)selector);
        }
    } else if (FT_IS_UINT(selector_type)) {
        if (selector != NULL) {
            selectorUint_ = GPOINTER_TO_UINT(selector);
            default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_);
        }
    } else if (selector_type == FT_NONE) {
        // There is no default for an FT_NONE dissector table
    } else if (selector_type == FT_GUID) {
        /* Special handling for DCE/RPC dissectors */
        if (strcmp(tableName_, DCERPC_TABLE_NAME) == 0) {
            selectorDCERPC_ = (decode_dcerpc_bind_values_t*)(selector);
        }
    }

    if (default_handle != NULL) {
        default_dissector_ = dissector_handle_get_description(default_handle);
        // When adding a new record, we set the "current" values equal to
        // the default, so the user can easily reset the value.
        // The existing value read from the prefs file should already
        // be added to the table from reading the prefs file.
        // When reading existing values the current dissector should be
        // set explicitly to the actual current value.
        current_dissector_ = default_dissector_;
        dissector_handle_ = default_handle;
    }
}

void DecodeAsItem::setTable(const decode_as_t *entry)
{
    if (entry == nullptr)
        return;

    tableName_ = entry->table_name;
    tableUIName_ = get_dissector_table_ui_name(entry->table_name);

    /* XXX: Should the selector values be reset (e.g., to 0 and "")
     * What if someone tries to change the table to the DCERPC table?
     * That doesn't really work without the DCERPC special handling.
     */

    updateHandles();
}

void DecodeAsItem::setSelector(const QString &value)
{
    ftenum_t selector_type = get_dissector_table_selector_type(tableName_);

    if (FT_IS_STRING(selector_type)) {
        selectorString_ = value;
    } else if (FT_IS_UINT(selector_type)) {
        selectorUint_ = value.toUInt(Q_NULLPTR, 0);
    }

    updateHandles();
}

void DecodeAsItem::setDissectorHandle(dissector_handle_t handle)
{
    dissector_handle_ = handle;
    if (handle == nullptr) {
        current_dissector_ = DECODE_AS_NONE;
    } else {
        current_dissector_ = dissector_handle_get_description(handle);
    }
}

void DecodeAsItem::updateHandles()
{
    ftenum_t selector_type = get_dissector_table_selector_type(tableName_);
    dissector_handle_t default_handle = nullptr;

    if (FT_IS_STRING(selector_type)) {
        default_handle = dissector_get_default_string_handle(tableName_, qUtf8Printable(selectorString_));
    } else if (FT_IS_UINT(selector_type)) {
        default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_);
    }
    if (default_handle != nullptr) {
        default_dissector_ = dissector_handle_get_description(default_handle);
    } else {
        default_dissector_ = DECODE_AS_NONE;
    }
}

DecodeAsModel::DecodeAsModel(QObject *parent, capture_file *cf) :
    QAbstractTableModel(parent),
    cap_file_(cf)
{
}

DecodeAsModel::~DecodeAsModel()
{
    foreach(DecodeAsItem* item, decode_as_items_)
        delete item;
    decode_as_items_.clear();
}

Qt::ItemFlags DecodeAsModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemFlags();

    DecodeAsItem* item = decode_as_items_[index.row()];

    Qt::ItemFlags flags = QAbstractTableModel::flags(index);
    switch(index.column())
    {
    case DecodeAsModel::colTable:
    case DecodeAsModel::colProtocol:
        flags |= Qt::ItemIsEditable;
        break;
    case DecodeAsModel::colSelector:
        {
        ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());
        if ((selector_type != FT_NONE) &&
            (item->selectorDCERPC() == NULL))
            flags |= Qt::ItemIsEditable;
        break;
        }
    }

    return flags;
}

QVariant DecodeAsModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }

    DecodeAsItem* item;

    switch (role)
    {
    case Qt::ToolTipRole:
        switch (index.column())
        {
        case colTable:
            return tr("Match using this field");
        case colSelector:
            return tr("Change behavior when the field matches this value");
        case colType:
            return tr("Field value type (and base, if Integer)");
        case colDefault:
            return tr("Default \"Decode As\" behavior");
        case colProtocol:
            return tr("Current\"Decode As\" behavior");
        }
        return QVariant();
    case Qt::DisplayRole:
    case Qt::EditRole:
        item = decode_as_items_[index.row()];
        if (item == NULL)
            return QVariant();

        switch (index.column())
        {
        case colTable:
            return item->tableUIName();
        case colSelector:
        {
            ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());
            if (FT_IS_UINT(selector_type)) {
                return entryString(item->tableName(), GUINT_TO_POINTER(item->selectorUint()));
            } else if (FT_IS_STRING(selector_type)) {
                return entryString(item->tableName(), (gconstpointer)item->selectorString().toUtf8().constData());
            } else if (selector_type == FT_GUID) {
                if (item->selectorDCERPC() != NULL) {
                    return item->selectorDCERPC()->ctx_id;
                }
            }

            return DECODE_AS_NONE;
        }
        case colType:
        {
            ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());

            if (FT_IS_STRING(selector_type)) {
                return tr("String");
            } else if (FT_IS_UINT(selector_type)) {
                QString type_desc = tr("Integer, base ");
                switch (get_dissector_table_param(item->tableName())) {
                case BASE_OCT:
                    type_desc.append("8");
                    break;
                case BASE_DEC:
                    type_desc.append("10");
                    break;
                case BASE_HEX:
                    type_desc.append("16");
                    break;
                default:
                    type_desc.append(tr("unknown"));
                }
                return type_desc;
            } else if (selector_type == FT_NONE) {
                return tr("<none>");
            } else if (selector_type == FT_GUID) {
                if (item->selectorDCERPC() != NULL) {
                    return QString("ctx_id");
                } else {
                    return tr("GUID");
                }
            }
            break;
        }
        case colDefault:
            return item->defaultDissector();
        case colProtocol:
            return item->currentDissector();
        }
        return QVariant();

    case Qt::UserRole:
        item = decode_as_items_[index.row()];
        return QVariant::fromValue(static_cast<void *>(item));
    }

    return QVariant();
}

QVariant DecodeAsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
        return QVariant();

    switch (section) {
    case colTable:
        return tr("Field");
    case colSelector:
        return tr("Value");
    case colType:
        return tr("Type");
    case colDefault:
        return tr("Default");
    case colProtocol:
        return tr("Current");
    default:
        ws_assert_not_reached();
    }

    return QVariant();
}

int DecodeAsModel::rowCount(const QModelIndex &parent) const
{
    // there are no children
    if (parent.isValid()) {
        return 0;
    }

    return static_cast<int>(decode_as_items_.count());
}

int DecodeAsModel::columnCount(const QModelIndex &parent) const
{
    // there are no children
    if (parent.isValid()) {
        return 0;
    }

    return colDecodeAsMax;
}

bool DecodeAsModel::setData(const QModelIndex &cur_index, const QVariant &value, int role)
{
    if (!cur_index.isValid())
        return false;

    if (role != Qt::EditRole)
        return false;

    if (data(cur_index, role) == value) {
        // Data appears unchanged, do not do additional checks.
        return true;
    }

    DecodeAsItem* item = decode_as_items_[cur_index.row()];

    switch(cur_index.column())
    {
    case DecodeAsModel::colTable:
        {
        QString valueStr = value.toString();
        //grab the table values from the Decode As list because they are persistent
        for (GList *cur = decode_as_list; cur; cur = cur->next) {
            decode_as_t *entry = (decode_as_t *) cur->data;
            if (valueStr.compare(get_dissector_table_ui_name(entry->table_name)) == 0) {
                item->setTable(entry);
                //all other columns affected
                emit dataChanged(index(cur_index.row(), colSelector),
                                 index(cur_index.row(), colProtocol));
                break;
            }
        }
        }
        break;
    case DecodeAsModel::colProtocol:
    {
        dissector_handle_t handle = VariantPointer<dissector_handle>::asPtr(value);
        item->setDissectorHandle(handle);
        break;
    }
    case DecodeAsModel::colSelector:
        item->setSelector(value.toString());
        emit dataChanged(index(cur_index.row(), colDefault),
                         index(cur_index.row(), colProtocol));
        break;
    }

    return true;
}

bool DecodeAsModel::insertRows(int row, int count, const QModelIndex &/*parent*/)
{
    // support insertion of just one item for now.
    if (count != 1 || row < 0 || row > rowCount())
        return false;

    beginInsertRows(QModelIndex(), row, row);

    DecodeAsItem* item = nullptr;
    const decode_as_t *firstEntry = nullptr;

    if (cap_file_ && cap_file_->edt) {
        // Populate the new Decode As item with the last protocol layer
        // that can support Decode As and has a selector field for that
        // present in the frame.
        //
        // XXX: This treats 0 (for UInts) and empty strings the same as
        // the fields for the tables not being present at all.

        wmem_list_frame_t * protos = wmem_list_tail(cap_file_->edt->pi.layers);
        gint8 curr_layer_num_saved = cap_file_->edt->pi.curr_layer_num;
        guint8 curr_layer_num = wmem_list_count(cap_file_->edt->pi.layers);

        while (protos != NULL && item == nullptr) {
            int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos));
            const gchar * proto_name = proto_get_protocol_filter_name(proto_id);
            for (GList *cur = decode_as_list; cur; cur = cur->next) {
                decode_as_t *entry = (decode_as_t *) cur->data;
                if (g_strcmp0(proto_name, entry->name) == 0) {
                    if (firstEntry == nullptr) {
                        firstEntry = entry;
                    }
                    ftenum_t selector_type = get_dissector_table_selector_type(entry->table_name);
                    // Pick the first value in the packet for the current
                    // layer for the table
                    // XXX: What if the Decode As table supports multiple
                    // values, but the first possible one is 0/NULL?
                    cap_file_->edt->pi.curr_layer_num = curr_layer_num;
                    gpointer selector = entry->values[0].build_values[0](&cap_file_->edt->pi);
                    // FT_NONE tables don't need a value
                    if (selector != NULL || selector_type == FT_NONE) {
                        item = new DecodeAsItem(entry, selector);
                        break;
                    }

                }
            }
            protos = wmem_list_frame_prev(protos);
            curr_layer_num--;
        }

        cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved;
    }

    // If we didn't find an entry with a valid selector, create an entry
    // from the last table with an empty selector, or an empty entry.
    if (item == nullptr) {
        item = new DecodeAsItem(firstEntry);
    }
    decode_as_items_ << item;

    endInsertRows();

    return true;
}

bool DecodeAsModel::removeRows(int row, int count, const QModelIndex &/*parent*/)
{
    if (count != 1 || row < 0 || row >= rowCount())
        return false;

    beginRemoveRows(QModelIndex(), row, row);
    DecodeAsItem* item = decode_as_items_.takeAt(row);
    delete item;
    endRemoveRows();

    return true;
}

void DecodeAsModel::clearAll()
{
    if (rowCount() < 1)
        return;

    beginResetModel();
    foreach(DecodeAsItem* item, decode_as_items_)
        delete item;
    decode_as_items_.clear();
    endResetModel();
}

bool DecodeAsModel::copyRow(int dst_row, int src_row)
{
    if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) {
        return false;
    }

    DecodeAsItem* src = decode_as_items_[src_row];
    DecodeAsItem* dst = decode_as_items_[dst_row];

    *dst = *src;

    QVector<int> roles;
    roles << Qt::EditRole << Qt::BackgroundRole;
    emit dataChanged(index(dst_row, 0), index(dst_row, columnCount()), roles);

    return true;
}

prefs_set_pref_e DecodeAsModel::readDecodeAsEntry(gchar *key, const gchar *value, void *private_data, gboolean)
{
    DecodeAsModel *model = (DecodeAsModel*)private_data;
    if (model == NULL)
        return PREFS_SET_OK;

    if (strcmp(key, DECODE_AS_ENTRY) != 0) {
        return PREFS_SET_NO_SUCH_PREF;
    }

    /* Parse into table, selector, initial, current */
    gchar **values = g_strsplit_set(value, ",", 4);
    DecodeAsItem *item = nullptr;

    dissector_table_t dissector_table = find_dissector_table(values[0]);

    QString tableName(values[0]);
    // Get the table values from the Decode As list because they are persistent
    for (GList *cur = decode_as_list; cur; cur = cur->next) {
        decode_as_t *entry = (decode_as_t *) cur->data;
        if (tableName.compare(entry->table_name) == 0) {
            item = new DecodeAsItem(entry);
            break;
        }
    }

    if (item == nullptr) {
        g_strfreev(values);
        return PREFS_SET_SYNTAX_ERR;
    }

    QString selector(values[1]);
    item->setSelector(selector);

    /* The value for the default dissector in the decode_as_entries file
     * has no effect other than perhaps making the config file more
     * informative when edited manually.
     * We will actually display and reset to the programmatic default value.
     */
    item->setDissectorHandle(dissector_table_get_dissector_handle(dissector_table, values[3]));

    model->decode_as_items_ << item;
    g_strfreev(values);

    return PREFS_SET_OK;
}

bool DecodeAsModel::copyFromProfile(QString filename, const gchar **err)
{
    FILE *fp = ws_fopen(filename.toUtf8().constData(), "r");

    if (fp == NULL) {
        *err = g_strerror(errno);
        return false;
    }

    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    read_prefs_file(filename.toUtf8().constData(), fp, readDecodeAsEntry, this);
    endInsertRows();

    fclose(fp);

    return true;
}

QString DecodeAsModel::entryString(const gchar *table_name, gconstpointer value)
{
    QString entry_str;
    ftenum_t selector_type = get_dissector_table_selector_type(table_name);

    switch (selector_type) {

    case FT_UINT8:
    case FT_UINT16:
    case FT_UINT24:
    case FT_UINT32:
    {
        uint num_val = GPOINTER_TO_UINT(value);
        switch (get_dissector_table_param(table_name)) {

        case BASE_DEC:
            entry_str = QString::number(num_val);
            break;

        case BASE_HEX:
            int width;
            switch (selector_type) {
            case FT_UINT8:
                width = 2;
                break;
            case FT_UINT16:
                width = 4;
                break;
            case FT_UINT24:
                width = 6;
                break;
            case FT_UINT32:
                width = 8;
                break;

            default:
                ws_assert_not_reached();
                break;
            }
            entry_str = QString("%1").arg(int_to_qstring(num_val, width, 16));
            break;

        case BASE_OCT:
            entry_str = "0" + QString::number(num_val, 8);
            break;
        }
        break;
    }

    case FT_STRING:
    case FT_STRINGZ:
    case FT_UINT_STRING:
    case FT_STRINGZPAD:
    case FT_STRINGZTRUNC:
        entry_str = (const char *)value;
        break;

    case FT_GUID:
        //avoid the assert for now
        break;

    case FT_NONE:
        //doesn't really matter, just avoiding the assert
        return "0";

    default:
        ws_assert_not_reached();
        break;
    }
    return entry_str;
}

void DecodeAsModel::fillTable()
{
    decode_as_items_.clear();
    beginResetModel();

    dissector_all_tables_foreach_changed(buildChangedList, this);
    decode_dcerpc_add_show_list(buildDceRpcChangedList, this);

    endResetModel();
}

void DecodeAsModel::setDissectorHandle(const QModelIndex &index, dissector_handle_t  dissector_handle)
{
    DecodeAsItem* item = decode_as_items_[index.row()];
    if (item != NULL)
        item->setDissectorHandle(dissector_handle);
}

void DecodeAsModel::buildChangedList(const gchar *table_name, ftenum_t, gpointer key, gpointer value, gpointer user_data)
{
    DecodeAsModel *model = (DecodeAsModel*)user_data;
    if (model == NULL)
        return;

    dissector_handle_t current_dh;
    DecodeAsItem* item = new DecodeAsItem(table_name, key);

    current_dh = dtbl_entry_get_handle((dtbl_entry_t *)value);
    item->setDissectorHandle(current_dh);

    model->decode_as_items_ << item;
}

void DecodeAsModel::buildDceRpcChangedList(gpointer data, gpointer user_data)
{
    dissector_table_t sub_dissectors;
    guid_key guid_val;
    decode_dcerpc_bind_values_t *binding = (decode_dcerpc_bind_values_t *)data;

    DecodeAsModel *model = (DecodeAsModel*)user_data;
    if (model == NULL)
        return;

    DecodeAsItem* item = new DecodeAsItem(DCERPC_TABLE_NAME, binding);

    sub_dissectors = find_dissector_table(DCERPC_TABLE_NAME);

    guid_val.ver = binding->ver;
    guid_val.guid = binding->uuid;
    item->setDissectorHandle(dissector_get_guid_handle(sub_dissectors, &guid_val));

    model->decode_as_items_ << item;
}

typedef QPair<const char *, guint32> UintPair;
typedef QPair<const char *, const char *> CharPtrPair;

void DecodeAsModel::gatherChangedEntries(const gchar *table_name,
        ftenum_t selector_type, gpointer key, gpointer, gpointer user_data)
{
    DecodeAsModel *model = qobject_cast<DecodeAsModel*>((DecodeAsModel*)user_data);
    if (model == NULL)
        return;

    switch (selector_type) {
    case FT_UINT8:
    case FT_UINT16:
    case FT_UINT24:
    case FT_UINT32:
        model->changed_uint_entries_ << UintPair(table_name, GPOINTER_TO_UINT(key));
        break;
    case FT_NONE:
        //need to reset dissector table, so this needs to be in a changed list,
        //might as well be the uint one.
        model->changed_uint_entries_ << UintPair(table_name, 0);
        break;

    case FT_STRING:
    case FT_STRINGZ:
    case FT_UINT_STRING:
    case FT_STRINGZPAD:
    case FT_STRINGZTRUNC:
        model->changed_string_entries_ << CharPtrPair(table_name, (const char *) key);
        break;
    default:
        break;
    }
}

void DecodeAsModel::applyChanges()
{
    dissector_table_t sub_dissectors;
    module_t *module;
    pref_t* pref_value;
    dissector_handle_t handle;
    // Reset all dissector tables, then apply all rules from model.

    // We can't call g_hash_table_removed from g_hash_table_foreach, which
    // means we can't call dissector_reset_{string,uint} from
    // dissector_all_tables_foreach_changed. Collect changed entries in
    // lists and remove them separately.
    //
    // If dissector_all_tables_remove_changed existed we could call it
    // instead.
    dissector_all_tables_foreach_changed(gatherChangedEntries, this);
    foreach (UintPair uint_entry, changed_uint_entries_) {
        /* Set "Decode As preferences" to default values */
        sub_dissectors = find_dissector_table(uint_entry.first);
        handle = dissector_get_uint_handle(sub_dissectors, uint_entry.second);
        if (handle != NULL) {
            module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(handle)));
            pref_value = prefs_find_preference(module, uint_entry.first);
            if (pref_value != NULL) {
                module->prefs_changed_flags |= prefs_get_effect_flags(pref_value);
                reset_pref(pref_value);
            }
        }

        dissector_reset_uint(uint_entry.first, uint_entry.second);
    }
    changed_uint_entries_.clear();
    foreach (CharPtrPair char_ptr_entry, changed_string_entries_) {
        dissector_reset_string(char_ptr_entry.first, char_ptr_entry.second);
    }
    changed_string_entries_.clear();

    foreach(DecodeAsItem *item, decode_as_items_) {
        decode_as_t       *decode_as_entry;

        if (item->currentDissector().isEmpty()) {
            continue;
        }

        for (GList *cur = decode_as_list; cur; cur = cur->next) {
            decode_as_entry = (decode_as_t *) cur->data;

            if (!g_strcmp0(decode_as_entry->table_name, item->tableName())) {

                ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());
                gconstpointer  selector_value;
                QByteArray byteArray;

                switch (selector_type) {
                case FT_UINT8:
                case FT_UINT16:
                case FT_UINT24:
                case FT_UINT32:
                    selector_value = GUINT_TO_POINTER(item->selectorUint());
                    break;
                case FT_STRING:
                case FT_STRINGZ:
                case FT_UINT_STRING:
                case FT_STRINGZPAD:
                case FT_STRINGZTRUNC:
                    byteArray = item->selectorString().toUtf8();
                    selector_value = (gconstpointer) byteArray.constData();
                    break;
                case FT_NONE:
                    //selector value is ignored, but dissector table needs to happen
                    selector_value = NULL;
                    break;
                case FT_GUID:
                    if (item->selectorDCERPC() != NULL) {
                        selector_value = (gconstpointer)item->selectorDCERPC();
                    } else {
                        //TODO: Support normal GUID dissector tables
                        selector_value = NULL;
                    }
                    break;
                default:
                    continue;
                }

                if ((item->currentDissector() == item->defaultDissector())) {
                    decode_as_entry->reset_value(decode_as_entry->table_name, selector_value);
                    sub_dissectors = find_dissector_table(decode_as_entry->table_name);

                    /* For now, only numeric dissector tables can use preferences */
                    if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) {
                        if (item->dissectorHandle() != NULL) {
                            module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle())));
                            pref_value = prefs_find_preference(module, decode_as_entry->table_name);
                            if (pref_value != NULL) {
                                module->prefs_changed_flags |= prefs_get_effect_flags(pref_value);
                                prefs_remove_decode_as_value(pref_value, item->selectorUint(), TRUE);
                            }
                        }
                    }
                    break;
                } else {
                    decode_as_entry->change_value(decode_as_entry->table_name, selector_value, item->dissectorHandle(), item->currentDissector().toUtf8().constData());
                    sub_dissectors = find_dissector_table(decode_as_entry->table_name);

                    /* For now, only numeric dissector tables can use preferences */
                    if (item->dissectorHandle() != NULL) {
                        if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) {
                            module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle())));
                            pref_value = prefs_find_preference(module, decode_as_entry->table_name);
                            if (pref_value != NULL) {
                                module->prefs_changed_flags |= prefs_get_effect_flags(pref_value);
                                prefs_add_decode_as_value(pref_value, item->selectorUint(), FALSE);
                            }
                        }
                    }
                    break;
                }
            }
        }
    }
    prefs_apply_all();
}
