/* 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 <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()
 : tableName_(DEFAULT_TABLE),
 tableUIName_(DEFAULT_UI_TABLE),
 selectorUint_(0),
 selectorString_(""),
 selectorDCERPC_(NULL),
 default_proto_(DECODE_AS_NONE),
 current_proto_(DECODE_AS_NONE),
 dissector_handle_(NULL)
{
}

DecodeAsItem::~DecodeAsItem()
{
}


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("Current\"Decode As\" behavior");
        case colType:
            return QVariant();
        case colDefault:
            return tr("Default \"Decode As\" behavior");
        case colProtocol:
            return tr("Change behavior when the protocol field matches this value");
        }
        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 (IS_FT_UINT(selector_type)) {
                return entryString(item->tableName_, GUINT_TO_POINTER(item->selectorUint_));
            } else if (IS_FT_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 (IS_FT_STRING(selector_type)) {
                return tr("String");
            } else if (IS_FT_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->default_proto_;
        case colProtocol:
            return item->current_proto_;
        }
        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 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->tableName_ = entry->table_name;
                item->tableUIName_ = get_dissector_table_ui_name(entry->table_name);

                //all other columns affected
                emit dataChanged(index(cur_index.row(), colSelector),
                                 index(cur_index.row(), colProtocol));

            }
        }
        }
        break;
    case DecodeAsModel::colProtocol:
        item->current_proto_ = value.toString();
        break;
    case DecodeAsModel::colSelector:
        {
        ftenum_t selector_type = get_dissector_table_selector_type(item->tableName_);

        if (IS_FT_STRING(selector_type)) {
            item->selectorString_ = value.toString();
        } else if (IS_FT_UINT(selector_type)) {
            item->selectorUint_ = value.toString().toUInt(Q_NULLPTR, 0);
        }
        }
        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 = new DecodeAsItem();
    DecodeAsItem* alternativeItem = NULL;
    bool lastItemIsOk = false;

    if (cap_file_ && cap_file_->edt) {
        //populate the new Decode As item with the last protocol layer
        //that can support Decode As
        wmem_list_frame_t * protos = wmem_list_head(cap_file_->edt->pi.layers);
        gint8 curr_layer_num_saved = cap_file_->edt->pi.curr_layer_num;
        guint8 curr_layer_num = 1;

        while (protos != NULL) {
            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) {
                    dissector_handle_t dissector = NULL;
                    ftenum_t selector_type = get_dissector_table_selector_type(entry->table_name);
                    bool itemOk = false;

                    //reset the default and current protocols in case previous layer
                    //populated it and this layer doesn't have a handle for it
                    item->default_proto_ =  item->current_proto_ = DECODE_AS_NONE;

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

                    //see if there is a default dissector that matches value
                    if (IS_FT_STRING(selector_type)) {

                        //pick the first value in the packet as the default
                        cap_file_->edt->pi.curr_layer_num = curr_layer_num;
                        gpointer selector = entry->values[0].build_values[0](&cap_file_->edt->pi);
                        if (selector != NULL) {
                            item->selectorString_ = entryString(item->tableName_, selector);
                            dissector = dissector_get_default_string_handle(entry->table_name, (const gchar*)selector);
                        } else {
                            item->selectorString_ = "";
                        }
                        itemOk = !item->selectorString_.isEmpty();

                    } else if (IS_FT_UINT(selector_type)) {

                        //pick the first value in the packet as the default
                        cap_file_->edt->pi.curr_layer_num = curr_layer_num;
                        item->selectorUint_ = GPOINTER_TO_UINT(entry->values[0].build_values[0](&cap_file_->edt->pi));
                        itemOk = item->selectorUint_ != 0;

                        dissector = dissector_get_default_uint_handle(entry->table_name, item->selectorUint_);
                    } else if (selector_type == FT_NONE) {
                        // There is no default for an FT_NONE dissector table
                        dissector = NULL;
                        itemOk = true;
                    } else if (selector_type == FT_GUID) {
                        /* Special handling for DCE/RPC dissectors */
                        if (strcmp(entry->name, "dcerpc") == 0) {
                            item->selectorDCERPC_ = (decode_dcerpc_bind_values_t*)entry->values[0].build_values[0](&cap_file_->edt->pi);
                            itemOk = true;
                        }
                    }

                    if (itemOk) {
                        if (!alternativeItem) {
                            alternativeItem = new DecodeAsItem();
                        }
                        *alternativeItem = *item;
                    }
                    lastItemIsOk = itemOk;

                    if (dissector != NULL) {
                        item->default_proto_ = dissector_handle_get_short_name(dissector);
                        //When adding a new record, "default" should equal "current", so the user can
                        //explicitly change it
                        item->current_proto_ = item->default_proto_;
                    }
                }
            }
            protos = wmem_list_frame_next(protos);
            curr_layer_num++;
        }

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

    // If the last item has an empty selector (e.g. an empty port number),
    // prefer an entry that has a valid selector.
    if (alternativeItem) {
        if (lastItemIsOk) {
            delete alternativeItem;
        } else {
            delete item;
            item = alternativeItem;
        }
    }
    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);
    decode_as_items_.removeAt(row);
    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->tableName_ = src->tableName_;
    dst->tableUIName_ = src->tableUIName_;
    dst->selectorUint_ = src->selectorUint_;
    dst->selectorString_ = src->selectorString_;
    dst->selectorDCERPC_ = src->selectorDCERPC_;
    dst->default_proto_ = src->default_proto_;
    dst->current_proto_ = src->current_proto_;
    dst->dissector_handle_ = src->dissector_handle_;

    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 = new DecodeAsItem();

    dissector_table_t dissector_table = find_dissector_table(values[0]);

    QString tableName(values[0]);
    bool tableNameFound = false;
    // 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->tableName_ = entry->table_name;
            item->tableUIName_ = get_dissector_table_ui_name(entry->table_name);
            tableNameFound = true;
            break;
        }
    }

    if (!tableNameFound || !dissector_table) {
        delete item;
        g_strfreev(values);
        return PREFS_SET_SYNTAX_ERR;
    }

    QString selector(values[1]);
    ftenum_t selector_type = get_dissector_table_selector_type(item->tableName_);

    if (IS_FT_STRING(selector_type)) {
        item->selectorString_ = selector;
    } else if (IS_FT_UINT(selector_type)) {
        item->selectorUint_ = selector.toUInt(Q_NULLPTR, 0);
    }

    item->default_proto_ = values[2];
    item->dissector_handle_ = dissector_table_get_dissector_handle(dissector_table, values[3]);
    if (item->dissector_handle_) {
        item->current_proto_ = 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();
    emit beginResetModel();

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

    emit endResetModel();
}

void DecodeAsModel::setDissectorHandle(const QModelIndex &index, dissector_handle_t  dissector_handle)
{
    DecodeAsItem* item = decode_as_items_[index.row()];
    if (item != NULL)
        item->dissector_handle_ = 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 default_dh, current_dh;
    QString default_proto_name(DECODE_AS_NONE), current_proto_name(DECODE_AS_NONE);
    DecodeAsItem* item = new DecodeAsItem();
    ftenum_t selector_type = get_dissector_table_selector_type(table_name);

    item->tableName_ = table_name;
    item->tableUIName_ = get_dissector_table_ui_name(table_name);
    if (IS_FT_UINT(selector_type)) {
       item->selectorUint_ = GPOINTER_TO_UINT(key);
    } else if (IS_FT_STRING(selector_type)) {
       item->selectorString_ = entryString(table_name, key);
    }

    default_dh = dtbl_entry_get_initial_handle((dtbl_entry_t *)value);
    if (default_dh) {
        default_proto_name = dissector_handle_get_short_name(default_dh);
    }
    item->default_proto_ = default_proto_name;

    current_dh = dtbl_entry_get_handle((dtbl_entry_t *)value);
    if (current_dh) {
        current_proto_name = QString(dissector_handle_get_short_name(current_dh));
    }
    item->current_proto_ = current_proto_name;
    item->dissector_handle_ = 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;
    QString default_proto_name(DECODE_AS_NONE), current_proto_name(DECODE_AS_NONE);

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

    DecodeAsItem* item = new DecodeAsItem();

    item->tableName_ = "dcerpc.uuid";
    item->tableUIName_ = get_dissector_table_ui_name(item->tableName_);

    item->selectorDCERPC_ = binding;

    sub_dissectors = find_dissector_table(item->tableName_);

    guid_val.ver = binding->ver;
    guid_val.guid = binding->uuid;
    item->dissector_handle_ = dissector_get_guid_handle(sub_dissectors, &guid_val);
    if (item->dissector_handle_) {
        current_proto_name = QString(dissector_handle_get_short_name(item->dissector_handle_));
    }
    item->current_proto_ = current_proto_name;
    item->default_proto_ = default_proto_name;

    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->current_proto_.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->current_proto_ == DECODE_AS_NONE) || !item->dissector_handle_) {
                    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 (IS_FT_UINT(dissector_table_get_type(sub_dissectors))) {
                        if (item->dissector_handle_ != NULL) {
                            module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissector_handle_)));
                            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->dissector_handle_, item->current_proto_.toUtf8().constData());
                    sub_dissectors = find_dissector_table(decode_as_entry->table_name);

                    /* For now, only numeric dissector tables can use preferences */
                    if (IS_FT_UINT(dissector_table_get_type(sub_dissectors))) {
                        module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissector_handle_)));
                        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();
}
