/* packet-protobuf.c
 * Routines for Google Protocol Buffers dissection
 * Copyright 2017, Huang Qiangxiong <qiangxiong.huang@qq.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
 * The information used comes from:
 * https://developers.google.com/protocol-buffers/docs/encoding
 *
 * This protobuf dissector may be invoked by GRPC dissector or other dissectors.
 * Other dissectors can give protobuf message type info by the data argument or private_table["pb_msg_type"]
 * before call protobuf dissector.
 * For GRPC dissector the data argument format is:
 *    "application/grpc" ["+proto"] "," "/" service-name "/" method-name "," ("request" / "response")
 * For example:
 *    application/grpc,/helloworld.Greeter/SayHello,request
 * In this format, we will try to get real protobuf message type by method (service-name.method-name)
 * and in/out type (request / reponse).
 * For other dissectors can specifies message type directly, like:
 *    "message," message_type_name
 * For example:
 *    message,helloworld.HelloRequest      (helloworld is package, HelloRequest is message type)
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/prefs.h>
#include <epan/uat.h>
#include <epan/strutil.h>
#include <epan/proto_data.h>
#include <wsutil/filesystem.h>
#include <wsutil/file_util.h>
#include <wsutil/pint.h>
#include <wsutil/ws_printf.h>
#include <wsutil/report_message.h>

#include "protobuf-helper.h"
#include "packet-protobuf.h"

/* converting */
static inline gdouble
protobuf_uint64_to_double(guint64 value) {
    union { gdouble f; guint64 i; } double_uint64_union;

    double_uint64_union.i = value;
    return double_uint64_union.f;
}

static inline gfloat
protobuf_uint32_to_float(guint32 value) {
    union { gfloat f; guint32 i; } float_uint32_union;

    float_uint32_union.i = value;
    return float_uint32_union.f;
}

VALUE_STRING_ARRAY_GLOBAL_DEF(protobuf_wire_type);

/* which field type of each wire type could be */
static int protobuf_wire_to_field_type[6][9] = {
    /* PROTOBUF_WIRETYPE_VARINT, 0, "varint") */
    { PROTOBUF_TYPE_INT32, PROTOBUF_TYPE_INT64, PROTOBUF_TYPE_UINT32, PROTOBUF_TYPE_UINT64,
      PROTOBUF_TYPE_SINT32, PROTOBUF_TYPE_SINT64, PROTOBUF_TYPE_BOOL, PROTOBUF_TYPE_ENUM,
      PROTOBUF_TYPE_NONE },

    /* PROTOBUF_WIRETYPE_FIXED64, 1, "64-bit")   */
    { PROTOBUF_TYPE_FIXED64, PROTOBUF_TYPE_SFIXED64, PROTOBUF_TYPE_DOUBLE,
      PROTOBUF_TYPE_NONE },

    /* PROTOBUF_WIRETYPE_LENGTH_DELIMITED, 2, "Length-delimited") */
    { PROTOBUF_TYPE_STRING, PROTOBUF_TYPE_BYTES, PROTOBUF_TYPE_MESSAGE, PROTOBUF_TYPE_GROUP,
      PROTOBUF_TYPE_NONE },

    /* PROTOBUF_WIRETYPE_START_GROUP, 3, "Start group (deprecated)") */
    { PROTOBUF_TYPE_NONE },

    /* PROTOBUF_WIRETYPE_END_GROUP, 4, "End group (deprecated)") */
    { PROTOBUF_TYPE_NONE },

    /* PROTOBUF_WIRETYPE_FIXED32, 5, "32-bit") */
    { PROTOBUF_TYPE_FIXED32, PROTOBUF_TYPE_SINT32, PROTOBUF_TYPE_FLOAT,
      PROTOBUF_TYPE_NONE }
};

void proto_register_protobuf(void);
void proto_reg_handoff_protobuf(void);

#define PREFS_UPDATE_PROTOBUF_SEARCH_PATHS            1
#define PREFS_UPDATE_PROTOBUF_UDP_MESSAGE_TYPES       2
#define PREFS_UPDATE_ALL   (PREFS_UPDATE_PROTOBUF_SEARCH_PATHS | PREFS_UPDATE_PROTOBUF_UDP_MESSAGE_TYPES)

static void protobuf_reinit(int target);

static int proto_protobuf = -1;

static gboolean protobuf_dissector_called = FALSE;

/* information get from *.proto files */
static int hf_protobuf_message_name = -1;
static int hf_protobuf_field_name = -1;
static int hf_protobuf_field_type = -1;

/* field tag */
static int hf_protobuf_field_number = -1;
static int hf_protobuf_wire_type = -1;

/* field value */
static int hf_protobuf_value_length = -1; /* only Length-delimited field has */
static int hf_protobuf_value_data = -1;
static int hf_protobuf_value_double = -1;
static int hf_protobuf_value_float = -1;
static int hf_protobuf_value_int64 = -1;
static int hf_protobuf_value_uint64 = -1;
static int hf_protobuf_value_int32 = -1;
static int hf_protobuf_value_uint32 = -1;
static int hf_protobuf_value_bool = -1;
static int hf_protobuf_value_string = -1;
static int hf_protobuf_value_repeated = -1;

/* expert */
static expert_field ei_protobuf_failed_parse_tag = EI_INIT;
static expert_field ei_protobuf_failed_parse_length_delimited_field = EI_INIT;
static expert_field ei_protobuf_failed_parse_field = EI_INIT;
static expert_field ei_protobuf_wire_type_invalid = EI_INIT;
static expert_field et_protobuf_message_type_not_found = EI_INIT;
static expert_field et_protobuf_wire_type_not_support_packed_repeated = EI_INIT;
static expert_field et_protobuf_failed_parse_packed_repeated_field = EI_INIT;

/* trees */
static int ett_protobuf = -1;
static int ett_protobuf_message = -1;
static int ett_protobuf_field = -1;
static int ett_protobuf_value = -1;
static int ett_protobuf_packed_repeated = -1;

/* preferences */
static gboolean try_dissect_as_string = FALSE;
static gboolean show_all_possible_field_types = FALSE;
static gboolean dissect_bytes_as_string = FALSE;
static gboolean old_dissect_bytes_as_string = FALSE;
static gboolean show_details = FALSE;
static gboolean pbf_as_hf = FALSE; /* dissect protobuf fields as header fields of wireshark */

/* dynamic wireshark header fields for protobuf fields */
static hf_register_info *dynamic_hf = NULL;
static guint dynamic_hf_size = 0;
/* the key is full name of protobuf fields, the value is header field id */
static GHashTable *pbf_hf_hash = NULL;

/* Protobuf field value subdissector table list.
 * Only valid for the value of PROTOBUF_TYPE_BYTES or PROTOBUF_TYPE_STRING fields.
 */
static dissector_table_t protobuf_field_subdissector_table;

static dissector_handle_t protobuf_handle;

/* store varint tvb info */
typedef struct {
    guint offset;
    guint length;
    guint64 value;
} protobuf_varint_tvb_info_t;

static PbwDescriptorPool* pbw_pool = NULL;

/* protobuf source files search paths */
typedef struct {
    char* path; /* protobuf source files searching directory path */
    gboolean load_all; /* load all *.proto files in this directory and its sub directories */
} protobuf_search_path_t;

static protobuf_search_path_t* protobuf_search_paths = NULL;
static guint num_protobuf_search_paths = 0;

static void *
protobuf_search_paths_copy_cb(void* n, const void* o, size_t siz _U_)
{
    protobuf_search_path_t* new_rec = (protobuf_search_path_t*)n;
    const protobuf_search_path_t* old_rec = (const protobuf_search_path_t*)o;

    /* copy interval values like gint */
    memcpy(new_rec, old_rec, sizeof(protobuf_search_path_t));

    if (old_rec->path)
        new_rec->path = g_strdup(old_rec->path);

    return new_rec;
}

static void
protobuf_search_paths_free_cb(void*r)
{
    protobuf_search_path_t* rec = (protobuf_search_path_t*)r;

    g_free(rec->path);
}

UAT_DIRECTORYNAME_CB_DEF(protobuf_search_paths, path, protobuf_search_path_t)
UAT_BOOL_CB_DEF(protobuf_search_paths, load_all, protobuf_search_path_t)

/* the protobuf message type of the data on certain udp ports */
typedef struct {
    range_t  *udp_port_range; /* dissect data on these udp ports as protobuf */
    gchar    *message_type; /* protobuf message type of data on these udp ports */
} protobuf_udp_message_type_t;

static protobuf_udp_message_type_t* protobuf_udp_message_types = NULL;
static guint num_protobuf_udp_message_types = 0;

static void *
protobuf_udp_message_types_copy_cb(void* n, const void* o, size_t siz _U_)
{
    protobuf_udp_message_type_t* new_rec = (protobuf_udp_message_type_t*)n;
    const protobuf_udp_message_type_t* old_rec = (const protobuf_udp_message_type_t*)o;

    /* copy interval values like gint */
    memcpy(new_rec, old_rec, sizeof(protobuf_udp_message_type_t));

    if (old_rec->udp_port_range)
        new_rec->udp_port_range = range_copy(NULL, old_rec->udp_port_range);
    if (old_rec->message_type)
        new_rec->message_type = g_strdup(old_rec->message_type);

    return new_rec;
}

static gboolean
protobuf_udp_message_types_update_cb(void *r, char **err)
{
    protobuf_udp_message_type_t* rec = (protobuf_udp_message_type_t*)r;
    static range_t *empty;

    empty = range_empty(NULL);
    if (ranges_are_equal(rec->udp_port_range, empty)) {
        *err = g_strdup("Must specify UDP port(s) (like 8000 or 8000,8008-8088)");
        wmem_free(NULL, empty);
        return FALSE;
    }

    wmem_free(NULL, empty);
    return TRUE;
}

static void
protobuf_udp_message_types_free_cb(void*r)
{
    protobuf_udp_message_type_t* rec = (protobuf_udp_message_type_t*)r;

    wmem_free(NULL, rec->udp_port_range);
    g_free(rec->message_type);
}

UAT_RANGE_CB_DEF(protobuf_udp_message_types, udp_port_range, protobuf_udp_message_type_t)
UAT_CSTRING_CB_DEF(protobuf_udp_message_types, message_type, protobuf_udp_message_type_t)

static GSList* old_udp_port_ranges = NULL;

/* If you use int32 or int64 as the type for a negative number, the resulting varint is always
 * ten bytes long - it is, effectively, treated like a very large unsigned integer. If you use
 * one of the signed types, the resulting varint uses ZigZag encoding, which is much more efficient.
 * ZigZag encoding maps signed integers to unsigned integers so that numbers with a small absolute
 * value (for instance, -1) have a small varint encoded value too. (refers to protobuf spec)
 *      sint32 encoded using   (n << 1) ^ (n >> 31)
 */
static gint32
sint32_decode(guint32 sint32) {
    return (sint32 >> 1) ^ ((gint32)sint32 << 31 >> 31);
}

/* sint64 encoded using   (n << 1) ^ (n >> 63) */
static gint64
sint64_decode(guint64 sint64) {
    return (sint64 >> 1) ^ ((gint64)sint64 << 63 >> 63);
}

/* Try to get a protobuf field which has a varint value from the tvb.
 * The field number, wire type and uint64 value will be output.
 * @return the length of this field. Zero if failed.
 */
static guint
tvb_get_protobuf_field_uint(tvbuff_t* tvb, guint offset, guint maxlen,
    guint64* field_number, guint32* wire_type, guint64* value)
{
    guint tag_length, value_length;
    guint64 tag_value;

    /* parsing the tag of the field */
    tag_length = tvb_get_varint(tvb, offset, maxlen, &tag_value, ENC_VARINT_PROTOBUF);
    if (tag_length == 0 || tag_length >= maxlen) {
        return 0;
    }
    *field_number = tag_value >> 3;
    *wire_type = tag_value & 0x07;

    if (*wire_type != PROTOBUF_WIRETYPE_VARINT) {
        return 0;
    }
    /* parsing the value of the field */
    value_length = tvb_get_varint(tvb, offset + tag_length, maxlen - tag_length, value, ENC_VARINT_PROTOBUF);
    return (value_length == 0) ? 0 : (tag_length + value_length);
}

/* Get Protobuf timestamp from the tvb according to the format of google.protobuf.Timestamp.
 * return the length parsed.
 */
static guint
tvb_get_protobuf_time(tvbuff_t* tvb, guint offset, guint maxlen, nstime_t* timestamp)
{
    guint field_length;
    guint64 field_number, value;
    guint32 wire_type;
    guint off = offset;
    guint len = maxlen; /* remain bytes */

    /* Get the seconds and nanos fields from google.protobuf.Timestamp message which defined:
     *
     * message Timestamp {
     *    int64 seconds = 1;
     *    int32 nanos = 2;
     * }
     */
    timestamp->secs = 0;
    timestamp->nsecs = 0;

    while (len > 0) {
        field_length = tvb_get_protobuf_field_uint(tvb, off, len, &field_number, &wire_type, &value);
        if (field_length == 0) {
            break;
        }

        if (field_number == 1) {
            timestamp->secs = (gint64)value;
        } else if (field_number == 2) {
            timestamp->nsecs = (gint32)value;
        }

        off += field_length;
        len -= field_length;
    }

    return maxlen - len;
}


/* declare first because it will be called by dissect_packed_repeated_field_values */
static void
protobuf_dissect_field_value(proto_tree *value_tree, tvbuff_t *tvb, guint offset, guint length, packet_info *pinfo,
    proto_item *ti_field, int field_type, const guint64 value, const gchar* prepend_text, const PbwFieldDescriptor* field_desc, gboolean is_top_level);

static void
dissect_protobuf_message(tvbuff_t *tvb, guint offset, guint length, packet_info *pinfo, proto_tree *protobuf_tree, const PbwDescriptor* message_desc, gboolean is_top_level);

/* Only repeated fields of primitive numeric types (types which use the varint, 32-bit, or 64-bit wire types) can
 * be declared "packed".
 * The format of a packed_repeated field likes: tag + varint + varint + varint ...
 * or likes: tag + fixed64 + fixed64 + fixed64 ...
 * Return consumed bytes
 */
static guint
dissect_packed_repeated_field_values(tvbuff_t *tvb, guint start, guint length, packet_info *pinfo,
    proto_item *ti_field, int wire_type, int field_type, const gchar* prepend_text, const PbwFieldDescriptor* field_desc)
{
    guint64 sub_value;
    guint sub_value_length;
    guint offset = start;
    protobuf_varint_tvb_info_t *info;
    guint max_offset = offset + length;
    wmem_list_frame_t *lframe;
    wmem_list_t* varint_list;
    int value_size = 0;

    if (prepend_text == NULL) {
        prepend_text = "";
    }

    /* prepare subtree */
    proto_item_append_text(ti_field, "%s [", prepend_text);
    proto_item *ti = proto_tree_add_item(proto_item_get_subtree(ti_field), hf_protobuf_value_repeated, tvb, start, length, ENC_NA);
    proto_tree *subtree = proto_item_add_subtree(ti, ett_protobuf_packed_repeated);

    prepend_text = "";

    switch (field_type)
    {
    /* packed for Varint encoded types (int32, int64, uint32, uint64, sint32, sint64, bool, enum) */
    /* format: tag + varint + varint + varint ... */
    case PROTOBUF_TYPE_INT32:
    case PROTOBUF_TYPE_INT64:
    case PROTOBUF_TYPE_UINT32:
    case PROTOBUF_TYPE_UINT64:
    case PROTOBUF_TYPE_SINT32:
    case PROTOBUF_TYPE_SINT64:
    case PROTOBUF_TYPE_BOOL:
    case PROTOBUF_TYPE_ENUM:
        varint_list = wmem_list_new(wmem_packet_scope());

        /* try to test all can parsed as varint */
        while (offset < max_offset) {
            sub_value_length = tvb_get_varint(tvb, offset, max_offset - offset, &sub_value, ENC_VARINT_PROTOBUF);
            if (sub_value_length == 0) {
                /* not a valid packed repeated field */
                wmem_destroy_list(varint_list);
                return 0;
            }

            /* temporarily store varint info in the list */
            info = wmem_new(wmem_packet_scope(), protobuf_varint_tvb_info_t);
            info->offset = offset;
            info->length = sub_value_length;
            info->value = sub_value;
            wmem_list_append(varint_list, info);

            offset += sub_value_length;
        }

        /* all parsed, we add varints into the packed-repeated subtree */
        for (lframe = wmem_list_head(varint_list); lframe != NULL; lframe = wmem_list_frame_next(lframe)) {
            info = (protobuf_varint_tvb_info_t*)wmem_list_frame_data(lframe);
            protobuf_dissect_field_value(subtree, tvb, info->offset, info->length, pinfo,
                ti_field, field_type, info->value, prepend_text, field_desc,  FALSE);
            prepend_text = ",";
        }

        wmem_destroy_list(varint_list);
        break;

    /* packed for 64-bit encoded types (fixed64, sfixed64, double) and 32-bit encoded types (fixed32, sfixed32, float) */
    /* format like: tag + sint32 + sint32 + sint32 ... */
    case PROTOBUF_TYPE_FIXED64:
    case PROTOBUF_TYPE_SFIXED64:
    case PROTOBUF_TYPE_DOUBLE:
        value_size = 8; /* 64-bit */
        /* FALLTHROUGH */
    case PROTOBUF_TYPE_FIXED32:
    case PROTOBUF_TYPE_SFIXED32:
    case PROTOBUF_TYPE_FLOAT:
        if (value_size == 0) {
            value_size = 4; /* 32-bit */
        }

        if (length % value_size != 0) {
            expert_add_info(pinfo, ti_field, &et_protobuf_failed_parse_packed_repeated_field);
            return 0;
        }

        for (offset = start; offset < max_offset; offset += value_size) {
            protobuf_dissect_field_value(subtree, tvb, offset, value_size, pinfo, ti_field, field_type,
                (wire_type == PROTOBUF_WIRETYPE_FIXED32 ? tvb_get_guint32(tvb, offset, ENC_LITTLE_ENDIAN)
                    : tvb_get_guint64(tvb, offset, ENC_LITTLE_ENDIAN)),
                prepend_text, field_desc, FALSE);
            prepend_text = ",";
        }

        break;

    default:
        expert_add_info(pinfo, ti_field, &et_protobuf_wire_type_not_support_packed_repeated);
        return 0; /* prevent dead loop */
    }

    proto_item_append_text(ti_field, "]");
    return length;
}

/* Dissect field value based on a specific type. */
static void
protobuf_dissect_field_value(proto_tree *value_tree, tvbuff_t *tvb, guint offset, guint length, packet_info *pinfo,
    proto_item *ti_field, int field_type, const guint64 value, const gchar* prepend_text, const PbwFieldDescriptor* field_desc, gboolean is_top_level)
{
    gdouble double_value;
    gfloat float_value;
    gint64 int64_value;
    gint32 int32_value;
    char* buf;
    gboolean add_datatype = TRUE;
    proto_item* ti = NULL;
    proto_tree* subtree = NULL;
    const char* enum_value_name = NULL;
    const PbwDescriptor* sub_message_desc = NULL;
    const PbwEnumDescriptor* enum_desc = NULL;
    int* hf_id_ptr = NULL;
    const gchar* field_full_name = field_desc ? pbw_FieldDescriptor_full_name(field_desc) : NULL;
    proto_tree* field_tree = proto_item_get_subtree(ti_field);
    proto_tree* field_parent_tree = proto_tree_get_parent_tree(field_tree);
    proto_tree* pbf_tree = field_tree;
    nstime_t timestamp = { 0 };
    dissector_handle_t field_dissector = (field_full_name && (field_type == PROTOBUF_TYPE_BYTES || field_type == PROTOBUF_TYPE_STRING)) ?
        dissector_get_string_handle(protobuf_field_subdissector_table, field_full_name) : NULL;

    if (pbf_as_hf && field_full_name) {
        hf_id_ptr = (int*)g_hash_table_lookup(pbf_hf_hash, field_full_name);
        DISSECTOR_ASSERT_HINT(hf_id_ptr && (*hf_id_ptr) > 0, "hf must have been initialized properly");
    }

    if (pbf_as_hf && hf_id_ptr && !show_details) {
        /* set ti_field (Field(x)) item hidden if there is header_field */
        proto_item_set_hidden(ti_field);
        pbf_tree = field_parent_tree;
    }

    if (prepend_text == NULL) {
        prepend_text = "";
    }

    switch (field_type)
    {
    case PROTOBUF_TYPE_DOUBLE:
        double_value = protobuf_uint64_to_double(value);
        proto_tree_add_double(value_tree, hf_protobuf_value_double, tvb, offset, length, double_value);
        proto_item_append_text(ti_field, "%s %lf", prepend_text, double_value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%lf", double_value);
        }
        if (hf_id_ptr) {
            proto_tree_add_double(pbf_tree, *hf_id_ptr, tvb, offset, length, double_value);
        }
        break;

    case PROTOBUF_TYPE_FLOAT:
        float_value = protobuf_uint32_to_float((guint32) value);
        proto_tree_add_float(value_tree, hf_protobuf_value_float, tvb, offset, length, float_value);
        proto_item_append_text(ti_field, "%s %f", prepend_text, float_value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%f", float_value);
        }
        if (hf_id_ptr) {
            proto_tree_add_float(pbf_tree, *hf_id_ptr, tvb, offset, length, float_value);
        }
        break;

    case PROTOBUF_TYPE_INT64:
    case PROTOBUF_TYPE_SFIXED64:
        int64_value = (gint64) value;
        proto_tree_add_int64(value_tree, hf_protobuf_value_int64, tvb, offset, length, int64_value);
        proto_item_append_text(ti_field, "%s %" G_GINT64_MODIFIER "d", prepend_text, int64_value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%" G_GINT64_MODIFIER "d", int64_value);
        }
        if (hf_id_ptr) {
            proto_tree_add_int64(pbf_tree, *hf_id_ptr, tvb, offset, length, int64_value);
        }
        break;

    case PROTOBUF_TYPE_UINT64:
    case PROTOBUF_TYPE_FIXED64: /* same as UINT64 */
        proto_tree_add_uint64(value_tree, hf_protobuf_value_uint64, tvb, offset, length, value);
        proto_item_append_text(ti_field, "%s %" G_GINT64_MODIFIER "u", prepend_text, value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%" G_GINT64_MODIFIER "u", value);
        }
        if (hf_id_ptr) {
            proto_tree_add_uint64(pbf_tree, *hf_id_ptr, tvb, offset, length, value);
        }
        break;

    case PROTOBUF_TYPE_INT32:
    case PROTOBUF_TYPE_SFIXED32:
        int32_value = (gint32)value;
        proto_tree_add_int(value_tree, hf_protobuf_value_int32, tvb, offset, length, int32_value);
        proto_item_append_text(ti_field, "%s %d", prepend_text, int32_value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%d", int32_value);
        }
        if (hf_id_ptr) {
            proto_tree_add_int(pbf_tree, *hf_id_ptr, tvb, offset, length, int32_value);
        }
        break;

    case PROTOBUF_TYPE_ENUM:
        int32_value = (gint32) value;
        /* get the name of enum value */
        if (field_desc) {
            enum_desc = pbw_FieldDescriptor_enum_type(field_desc);
            if (enum_desc) {
                const PbwEnumValueDescriptor* enum_value_desc = pbw_EnumDescriptor_FindValueByNumber(enum_desc, int32_value);
                if (enum_value_desc) {
                    enum_value_name = pbw_EnumValueDescriptor_name(enum_value_desc);
                }
            }
        }
        ti = proto_tree_add_int(value_tree, hf_protobuf_value_int32, tvb, offset, length, int32_value);
        if (enum_value_name) { /* show enum value name */
            proto_item_append_text(ti_field, "%s %s(%d)", prepend_text, enum_value_name, int32_value);
            proto_item_append_text(ti, " (%s)", enum_value_name);
            if (is_top_level) {
                col_append_fstr(pinfo->cinfo, COL_INFO, "=%s", enum_value_name);
            }
        } else {
            proto_item_append_text(ti_field, "%s %d", prepend_text, int32_value);
            if (is_top_level) {
                col_append_fstr(pinfo->cinfo, COL_INFO, "=%d", int32_value);
            }

        }
        if (hf_id_ptr) {
            proto_tree_add_int(pbf_tree, *hf_id_ptr, tvb, offset, length, int32_value);
        }
        break;

    case PROTOBUF_TYPE_BOOL:
        if (length > 1) break; /* boolean should not use more than one bytes */
        proto_tree_add_boolean(value_tree, hf_protobuf_value_bool, tvb, offset, length, (guint32)value);
        proto_item_append_text(ti_field, "%s %s", prepend_text, value ? "true" : "false");
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%s", value ? "true" : "false");
        }
        if (hf_id_ptr) {
            proto_tree_add_boolean(pbf_tree, *hf_id_ptr, tvb, offset, length, (guint32)value);
        }
        break;

    case PROTOBUF_TYPE_BYTES:
        if (field_dissector) {
            if (!show_details) { /* don't show Value node if there is a subdissector for this field */
                proto_item_set_hidden(proto_tree_get_parent(value_tree));
            }
            if (dissect_bytes_as_string) { /* the type of *hf_id_ptr MUST be FT_STRING now */
                if (hf_id_ptr) {
                    ti = proto_tree_add_string_format_value(pbf_tree, *hf_id_ptr, tvb, offset, length, "", "(%u bytes)", length);
                }
                /* don't try to dissect bytes as string if there is a subdissector for this field */
                break;
            }
        }
        if (!dissect_bytes_as_string) {
            /* the type of *hf_id_ptr MUST be FT_BYTES now */
            if (hf_id_ptr) {
                ti = proto_tree_add_bytes_format_value(pbf_tree, *hf_id_ptr, tvb, offset, length, NULL, "(%u bytes)", length);
            }
            break;
        }
        /* or continue dissect BYTES as STRING */
        proto_item_append_text(ti_field, " =");
        /* FALLTHROUGH */
    case PROTOBUF_TYPE_STRING:
        proto_tree_add_item_ret_display_string(value_tree, hf_protobuf_value_string, tvb, offset, length, ENC_UTF_8|ENC_NA, wmem_packet_scope(), &buf);
        proto_item_append_text(ti_field, "%s %s", prepend_text, buf);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%s", buf);
        }
        if (hf_id_ptr) {
            ti = proto_tree_add_item_ret_display_string(pbf_tree, *hf_id_ptr, tvb, offset, length, ENC_UTF_8|ENC_NA, wmem_packet_scope(), &buf);
        }
        break;

    case PROTOBUF_TYPE_GROUP: /* This feature is deprecated. GROUP is identical to Nested MESSAGE. */
    case PROTOBUF_TYPE_MESSAGE:
        subtree = field_tree;
        if (field_desc) {
            sub_message_desc = pbw_FieldDescriptor_message_type(field_desc);
            if (sub_message_desc == NULL) {
                expert_add_info(pinfo, ti_field, &et_protobuf_message_type_not_found);
            }
        }
        if (hf_id_ptr) {
            if (sub_message_desc
                && strcmp(pbw_Descriptor_full_name(sub_message_desc), "google.protobuf.Timestamp") == 0)
            {   /* parse this field directly as timestamp */
                if (tvb_get_protobuf_time(tvb, offset, length, &timestamp)) {
                    ti = proto_tree_add_time(pbf_tree, *hf_id_ptr, tvb, offset, length, &timestamp);
                    subtree = proto_item_add_subtree(ti, ett_protobuf_message);
                } else {
                    expert_add_info(pinfo, ti_field, &ei_protobuf_failed_parse_field);
                }
            } else {
                ti = proto_tree_add_bytes_format_value(pbf_tree, *hf_id_ptr, tvb, offset, length, NULL, "(%u bytes)", length);
                subtree = proto_item_add_subtree(ti, ett_protobuf_message);
            }
        }
        if (sub_message_desc) {
            dissect_protobuf_message(tvb, offset, length, pinfo, subtree, sub_message_desc, FALSE);
        }
        break;

    case PROTOBUF_TYPE_UINT32:
    case PROTOBUF_TYPE_FIXED32: /* same as UINT32 */
        proto_tree_add_uint(value_tree, hf_protobuf_value_uint32, tvb, offset, length, (guint32)value);
        proto_item_append_text(ti_field, "%s %u", prepend_text, (guint32)value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%u", (guint32)value);
        }
        if (hf_id_ptr) {
            proto_tree_add_uint(pbf_tree, *hf_id_ptr, tvb, offset, length, (guint32)value);
        }
        break;

    case PROTOBUF_TYPE_SINT32:
        int32_value = sint32_decode((guint32)value);
        proto_tree_add_int(value_tree, hf_protobuf_value_int32, tvb, offset, length, int32_value);
        proto_item_append_text(ti_field, "%s %d", prepend_text, int32_value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%d", int32_value);
        }
        if (hf_id_ptr) {
            proto_tree_add_int(pbf_tree, *hf_id_ptr, tvb, offset, length, int32_value);
        }
        break;

    case PROTOBUF_TYPE_SINT64:
        int64_value = sint64_decode(value);
        proto_tree_add_int64(value_tree, hf_protobuf_value_int64, tvb, offset, length, int64_value);
        proto_item_append_text(ti_field, "%s %" G_GINT64_MODIFIER "d", prepend_text, int64_value);
        if (is_top_level) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "=%" G_GINT64_MODIFIER "d", int64_value);
        }
        if (hf_id_ptr) {
            proto_tree_add_int64(pbf_tree, *hf_id_ptr, tvb, offset, length, int64_value);
        }
        break;

    default:
        /* ignore unknown field type */
        add_datatype = FALSE;
        break;
    }

    /* try dissect field value according to protobuf_field dissector table */
    if (field_dissector) {
        /* determine the tree passing to the subdissector */
        subtree = field_tree;
        if (ti) {
            subtree = proto_item_get_subtree(ti);
            if (!subtree) {
                subtree = proto_item_add_subtree(ti, ett_protobuf_value);
            }
        }

        call_dissector(field_dissector, tvb_new_subset_length(tvb, offset, length), pinfo, subtree);
    }

    if (add_datatype)
        proto_item_append_text(ti_field, " (%s)", val_to_str(field_type, protobuf_field_type, "Unknown type (%d)"));

}

/* add all possible values according to field types. */
static void
protobuf_try_dissect_field_value_on_multi_types(proto_tree *value_tree, tvbuff_t *tvb, guint offset, guint length,
    packet_info *pinfo, proto_item *ti_field, int* field_types, const guint64 value, const gchar* prepend_text)
{
    int i;

    if (prepend_text == NULL) {
        prepend_text = "";
    }

    for (i = 0; field_types[i] != PROTOBUF_TYPE_NONE; ++i) {
        protobuf_dissect_field_value(value_tree, tvb, offset, length, pinfo, ti_field, field_types[i], value, prepend_text, NULL, FALSE);
        prepend_text = ",";
    }
}

static guint
dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_info *pinfo, proto_tree *protobuf_tree,
    const PbwDescriptor* message_desc, gboolean is_top_level)
{
    guint64 tag_value; /* tag value = (field_number << 3) | wire_type */
    guint tag_length; /* how many bytes this tag has */
    guint64 field_number;
    guint32 wire_type;
    guint64 value_uint64; /* uint64 value of numeric field (type of varint, 64-bit, 32-bit */
    guint value_length;
    guint value_length_size = 0; /* only Length-delimited field has it */
    proto_item *ti_field, *ti_field_number, *ti_wire, *ti_value_length = NULL;
    proto_item *ti_value, *ti_field_name, *ti_field_type = NULL;
    proto_tree *field_tree;
    proto_tree *value_tree;
    const gchar* field_name = NULL;
    int field_type = -1;
    gboolean is_packed_repeated = FALSE;
    const PbwFieldDescriptor* field_desc = NULL;

    /* A protocol buffer message is a series of key-value pairs. The binary version of a message just uses
     * the field's number as the key. a wire type that provides just enough information to find the length of
     * the following value.
     * Format of protobuf is:
     *       protobuf field -> tag value
     *       tag -> (field_number << 3) | wire_type  (the last three bits of the number store the wire type)
     *       value -> according to wiret_type, value may be
     *                 - varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum),
     *                 - 64-bit number (fixed64, sfixed64, double)
     *                 - Length-delimited (string, bytes, embedded messages, packed repeated fields)
     *                 - deprecated 'Start group' or 'End group' (we stop dissecting when encountered them)
     *                 - 32-bit (fixed32, sfixed32, float)
     * All numbers in protobuf are stored in little-endian byte order.
     */

    field_tree = proto_tree_add_subtree(protobuf_tree, tvb, *offset, 0, ett_protobuf_field, &ti_field, "Field");

    /* parsing Tag */
    tag_length = tvb_get_varint(tvb, *offset, maxlen, &tag_value, ENC_VARINT_PROTOBUF);

    if (tag_length == 0) { /* not found a valid varint */
        expert_add_info(pinfo, ti_field, &ei_protobuf_failed_parse_tag);
        return FALSE;
    }

    ti_field_number = proto_tree_add_item_ret_uint64(field_tree, hf_protobuf_field_number, tvb, *offset, tag_length, ENC_LITTLE_ENDIAN|ENC_VARINT_PROTOBUF, &field_number);
    ti_wire = proto_tree_add_item_ret_uint(field_tree, hf_protobuf_wire_type, tvb, *offset, 1, ENC_LITTLE_ENDIAN|ENC_VARINT_PROTOBUF, &wire_type);
    (*offset) += tag_length;

    /* try to find field_info first */
    if (message_desc) {
        /* find field descriptor according to field number from message descriptor */
        field_desc = pbw_Descriptor_FindFieldByNumber(message_desc, (int) field_number);
        if (field_desc) {
            field_name = pbw_FieldDescriptor_name(field_desc);
            field_type = pbw_FieldDescriptor_type(field_desc);
            is_packed_repeated = pbw_FieldDescriptor_is_packed(field_desc)
                && pbw_FieldDescriptor_is_repeated(field_desc);
        }
    }

    proto_item_append_text(ti_field, "(%" G_GINT64_MODIFIER "u):", field_number);

    /* support filtering with field name */
    ti_field_name = proto_tree_add_string(field_tree, hf_protobuf_field_name, tvb, *offset, 1,
        (field_name ? field_name : "<UNKNOWN>"));
    proto_item_set_generated(ti_field_name);
    if (field_name) {
        proto_item_append_text(ti_field, " %s %s", field_name,
            (field_type == PROTOBUF_TYPE_MESSAGE || field_type == PROTOBUF_TYPE_GROUP
                || field_type == PROTOBUF_TYPE_BYTES)
            ? "" : "="
        );
        if (field_type > 0) {
            ti_field_type = proto_tree_add_int(field_tree, hf_protobuf_field_type, tvb, *offset, 1, field_type);
            proto_item_set_generated(ti_field_type);
        }

        if (is_top_level) {
            /* Show field name in Info column */
            col_append_fstr(pinfo->cinfo, COL_INFO, " %s", field_name);
        }
    }

    /* move ti_field_number and ti_wire after ti_field_type (or field_type) for good look */
    proto_tree_move_item(field_tree, (ti_field_type ? ti_field_type : ti_field_name), ti_wire);
    proto_tree_move_item(field_tree, (ti_field_type ? ti_field_type : ti_field_name), ti_field_number);

    /* determine value_length, uint of numeric value and maybe value_length_size according to wire_type */
    switch (wire_type)
    {
    case PROTOBUF_WIRETYPE_VARINT: /* varint, format: tag + varint */
        /* get value length and real value */
        value_length = tvb_get_varint(tvb, *offset, maxlen - tag_length, &value_uint64, ENC_VARINT_PROTOBUF);
        if (value_length == 0) {
            expert_add_info(pinfo, ti_wire, &ei_protobuf_failed_parse_field);
            return FALSE;
        }
        break;

    case PROTOBUF_WIRETYPE_FIXED64: /* fixed 64-bit type, format: tag + 64-bit-value */
        /* get value length and real value */
        value_length = 8;
        value_uint64 = tvb_get_letoh64(tvb, *offset);
        break;

    case PROTOBUF_WIRETYPE_FIXED32: /* fixed 32-bit type, format: tag + 32-bit-value */
        value_length = 4;
        value_uint64 = tvb_get_letohl(tvb, *offset);
        break;

    case PROTOBUF_WIRETYPE_LENGTH_DELIMITED: /* Length-delimited, format: tag + length(varint) + bytes_value */
        /* this time value_uint64 is the length of following value bytes */
        value_length_size = tvb_get_varint(tvb, *offset, maxlen - tag_length, &value_uint64, ENC_VARINT_PROTOBUF);
        if (value_length_size == 0) {
            expert_add_info(pinfo, ti_field, &ei_protobuf_failed_parse_length_delimited_field);
            return FALSE;
        }

        ti_value_length = proto_tree_add_uint64(field_tree, hf_protobuf_value_length, tvb, *offset, value_length_size, value_uint64);
        (*offset) += value_length_size;

        /* we believe the length of following value will not be bigger than guint */
        value_length = (guint) value_uint64;
        break;

    default:
        expert_add_info(pinfo, ti_wire, &ei_protobuf_wire_type_invalid);
        return FALSE;
    }

    proto_item_set_len(ti_field, tag_length + value_length_size + value_length);
    proto_item_set_len(ti_field_name, tag_length + value_length_size + value_length);
    if (ti_field_type) {
        proto_item_set_len(ti_field_type, tag_length + value_length_size + value_length);
    }

    /* add value as bytes first */
    ti_value = proto_tree_add_item(field_tree, hf_protobuf_value_data, tvb, *offset, value_length, ENC_NA);

    /* add value subtree. we add uint value for numeric field or string for length-delimited at least. */
    value_tree = proto_item_add_subtree(ti_value, ett_protobuf_value);

    if (field_desc) {
        if (is_packed_repeated) {
            dissect_packed_repeated_field_values(tvb, *offset, value_length, pinfo, ti_field,
                wire_type, field_type, "", field_desc);
        } else {
            protobuf_dissect_field_value(value_tree, tvb, *offset, value_length, pinfo, ti_field, field_type, value_uint64, "", field_desc,
                                         is_top_level);
        }
    } else {
        if (show_all_possible_field_types) {
            /* try dissect every possible field type */
            protobuf_try_dissect_field_value_on_multi_types(value_tree, tvb, *offset, value_length, pinfo,
                ti_field, protobuf_wire_to_field_type[wire_type], value_uint64, "");
        } else {
            field_type = (wire_type == PROTOBUF_WIRETYPE_LENGTH_DELIMITED)
                /* print string at least for length-delimited */
                ? (try_dissect_as_string ? PROTOBUF_TYPE_STRING : PROTOBUF_TYPE_NONE)
                /* use uint32 or uint64 */
                : (value_uint64 <= 0xFFFFFFFF ? PROTOBUF_TYPE_UINT32 : PROTOBUF_TYPE_UINT64);
            int field_types[] = { field_type, PROTOBUF_TYPE_NONE };

            protobuf_try_dissect_field_value_on_multi_types(value_tree, tvb, *offset, value_length, pinfo,
                ti_field, field_types, value_uint64, "");
        }
    }

    if (field_desc && !show_details) {
        proto_item_set_hidden(ti_field_number);
        proto_item_set_hidden(ti_wire);
        proto_item_set_hidden(ti_value_length);
        proto_item_set_hidden(ti_field_name);
        proto_item_set_hidden(ti_field_type);
        if (field_type != PROTOBUF_TYPE_BYTES && field_type != PROTOBUF_TYPE_GROUP) {
            proto_item_set_hidden(ti_value);
        }
    }

    (*offset) += value_length;
    return TRUE;
}

static void
dissect_protobuf_message(tvbuff_t *tvb, guint offset, guint length, packet_info *pinfo, proto_tree *protobuf_tree,
    const PbwDescriptor* message_desc, gboolean is_top_level)
{
    proto_tree *message_tree;
    proto_item *ti_message, *ti;
    const gchar* message_name = "<UNKNOWN> Message Type";
    guint max_offset = offset + length;

    if (message_desc) {
        message_name = pbw_Descriptor_full_name(message_desc);
    }

    if (pbf_as_hf && message_desc) {
        /* support filtering with message name as wireshark field name */
        int *hf_id_ptr = (int*)g_hash_table_lookup(pbf_hf_hash, message_name);
        DISSECTOR_ASSERT_HINT(hf_id_ptr && (*hf_id_ptr) > 0, "hf of message should initialized properly");
        ti_message = proto_tree_add_item(protobuf_tree, *hf_id_ptr, tvb, offset, length, ENC_NA);
        proto_item_set_text(ti_message, "Message: %s", message_name);

        if (show_details) {
            /* show "Message" item and add its fields under this item */
            message_tree = proto_item_add_subtree(ti_message, ett_protobuf_message);
        } else {
            /* hidden "Message" item (but still can be filtered by wireshark field name with "pbm.xxx" prefix),
             * and add its fields under the parent (field or protobuf protocol) item directly */
            proto_item_set_hidden(ti_message);
            message_tree = protobuf_tree;
            ti_message = proto_tree_get_parent(message_tree);
            proto_item_append_text(ti_message, " (Message: %s)", message_name);
        }
    } else {
        message_tree = proto_tree_add_subtree_format(protobuf_tree, tvb, offset, length, ett_protobuf_message,
            &ti_message, "Message: %s", message_name);
    }

    if (is_top_level) {
        col_clear(pinfo->cinfo, COL_PROTOCOL);
        col_append_fstr(pinfo->cinfo, COL_PROTOCOL, "PB(%s)", message_name);
        /* Top-level info will get written into Info column. */
        col_clear(pinfo->cinfo, COL_INFO);
    }

    /* support filtering with message name */
    ti = proto_tree_add_string(message_tree, hf_protobuf_message_name, tvb, offset, length, message_name);
    proto_item_set_generated(ti);
    if (!show_details) {
        proto_item_set_hidden(ti);
    }

    /* each time we dissect one protobuf field. */
    while (offset < max_offset)
    {
        if (!dissect_one_protobuf_field(tvb, &offset, max_offset - offset, pinfo, message_tree, message_desc, is_top_level))
            break;
    }
}

/* try to find message type by UDP port */
static const PbwDescriptor*
find_message_type_by_udp_port(packet_info *pinfo)
{
    range_t* udp_port_range;
    const gchar* message_type;
    guint i;
    for (i = 0; i < num_protobuf_udp_message_types; ++i) {
        udp_port_range = protobuf_udp_message_types[i].udp_port_range;
        if (value_is_in_range(udp_port_range, pinfo->srcport)
            || value_is_in_range(udp_port_range, pinfo->destport))
        {
            message_type = protobuf_udp_message_types[i].message_type;
            if (message_type && strlen(message_type) > 0) {
                return pbw_DescriptorPool_FindMessageTypeByName(pbw_pool, message_type);
            }
        }
    }
    return NULL;
}

static int
dissect_protobuf(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
    proto_item *ti;
    proto_tree *protobuf_tree;
    guint offset = 0;
    guint i;
    const PbwDescriptor* message_desc = NULL;
    const gchar* data_str = NULL;

    /* initialize only the first time the protobuf dissector is called */
    if (!protobuf_dissector_called) {
        protobuf_dissector_called = TRUE;
        protobuf_reinit(PREFS_UPDATE_ALL);
    }

    /* may set col_set_str(pinfo->cinfo, COL_PROTOCOL, "PROTOBUF"); */
    col_append_str(pinfo->cinfo, COL_INFO, " (PROTOBUF)");

    ti = proto_tree_add_item(tree, proto_protobuf, tvb, 0, -1, ENC_NA);
    protobuf_tree = proto_item_add_subtree(ti, ett_protobuf);

    /* The dissectors written in Lua are not able to specify the message type by data
       parameter when calling protobuf dissector. But they can tell Protobuf dissector
       the message type by the value of pinfo->private_table["pb_msg_type"]. */
    if (data) {
        data_str = (const gchar*)data;
    } else if (pinfo->private_table) {
        data_str = (const gchar*)g_hash_table_lookup(pinfo->private_table, "pb_msg_type");
    }

    if (data_str) {
        /* The data_str has two formats:
        * (1) Come from GRPC dissector like:
        *    http2_content_type "," http2_path "," ("request" / "response")
        * Acording to grpc wire format guide, it will be:
        *    "application/grpc" ["+proto"] "," "/" service-name "/" method-name "," ("request" / "response")
        * For example:
        *    application/grpc,/helloworld.Greeter/SayHello,request
        * In this format, we will try to get real protobuf message type by method (service-name.method-name)
        * and in/out type (request / reponse).
        * (2) Come from other dissector which specifies message type directly, like:
        *    "message," message_type_name
        * For example:
        *    message,helloworld.HelloRequest      (helloworld is package, HelloRequest is message type)
        */
        const gchar* message_info = strchr(data_str, ',');

        if (message_info) {
            message_info++; /* ignore ',' */
            proto_item_append_text(ti, ": %s", message_info);  /* append to proto item */

            if (g_str_has_prefix(data_str, "message,")) {
                /* find message type by name directly */
                message_desc = pbw_DescriptorPool_FindMessageTypeByName(pbw_pool, message_info);
            } else /* if (g_str_has_prefix(data_str, "application/grpc,") */ {
                /* get long method-name like: helloworld.Greeter.SayHello */
                if (message_info[0] == '/') {
                    message_info++; /* ignore first '/' */
                }

                gchar** tmp_names = wmem_strsplit(wmem_packet_scope(), message_info, ",", 2);
                gchar* method_name = (tmp_names[0]) ? tmp_names[0] : NULL;
                gchar* direction_type = (method_name && tmp_names[1]) ? tmp_names[1] : NULL;

                /* replace all '/' to '.', so helloworld.Greeter/SayHello converted to helloworld.Greeter.SayHello */
                if (method_name) {
                    for (i = 0; method_name[i] != 0; i++) {
                        if (method_name[i] == '/') {
                            method_name[i] = '.';
                        }
                    }
                }

                /* find message type according to method descriptor */
                if (direction_type) {
                    const PbwMethodDescriptor* method_desc = pbw_DescriptorPool_FindMethodByName(pbw_pool, method_name);
                    if (method_desc) {
                        message_desc = strcmp(direction_type, "request") == 0
                            ? pbw_MethodDescriptor_input_type(method_desc)
                            : pbw_MethodDescriptor_output_type(method_desc);
                    }
                }
            }

            if (message_desc) {
                const char* message_full_name = pbw_Descriptor_full_name(message_desc);
                if (message_full_name) {
                    col_append_fstr(pinfo->cinfo, COL_INFO, " %s", message_full_name);
                }
            }
        }

    } else if (pinfo->ptype == PT_UDP) {
        message_desc = find_message_type_by_udp_port(pinfo);
    }

    dissect_protobuf_message(tvb, offset, tvb_reported_length_remaining(tvb, offset), pinfo,
        protobuf_tree, message_desc, pinfo->ptype == PT_UDP);

    return tvb_captured_length(tvb);
}

static gboolean
load_all_files_in_dir(PbwDescriptorPool* pool, const gchar* dir_path)
{
    WS_DIR        *dir;             /* scanned directory */
    WS_DIRENT     *file;            /* current file */
    const gchar   *dot;
    const gchar   *name;            /* current file or dir name (without parent dir path) */
    gchar         *path;            /* sub file or dir path of dir_path */

    if (g_file_test(dir_path, G_FILE_TEST_IS_DIR)) {
        if ((dir = ws_dir_open(dir_path, 0, NULL)) != NULL) {
            while ((file = ws_dir_read_name(dir)) != NULL) {
                /* load all files with '.proto' suffix */
                name = ws_dir_get_name(file);
                path = g_build_filename(dir_path, name, NULL);
                dot = strrchr(name, '.');
                if (dot && g_ascii_strcasecmp(dot + 1, "proto") == 0) {
                    /* Note: pbw_load_proto_file support absolute or relative (to one of search paths) path */
                    if (pbw_load_proto_file(pool, path) != 0) {
                        g_free(path);
                        ws_dir_close(dir);
                        return FALSE;
                    }
                } else {
                    if (!load_all_files_in_dir(pool, path)) {
                        g_free(path);
                        ws_dir_close(dir);
                        return FALSE;
                    }
                }
                g_free(path);
            }
            ws_dir_close(dir);
        }
    }
    return TRUE;
}

/* There might be a lot of errors to be found during parsing .proto files.
   We buffer the errors first, and print them in one list finally. */
static wmem_strbuf_t* err_msg_buf = NULL;
#define MIN_ERR_STR_BUF_SIZE 512
#define MAX_ERR_STR_BUF_SIZE 1024

static void
buffer_error(const gchar *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);

    if (err_msg_buf == NULL)
        err_msg_buf = wmem_strbuf_sized_new(wmem_epan_scope(), MIN_ERR_STR_BUF_SIZE, MAX_ERR_STR_BUF_SIZE);

    wmem_strbuf_append_vprintf(err_msg_buf, fmt, ap);

    va_end(ap);
}

static void
flush_and_report_error(void)
{
    char* str;
    if (err_msg_buf) {
        str = wmem_strbuf_finalize(err_msg_buf);
        err_msg_buf = NULL;
        report_failure("Protobuf: Error(s):\n%s", str);
        wmem_free(wmem_epan_scope(), str);
    }
}

static void
update_protobuf_search_paths(void)
{
    protobuf_reinit(PREFS_UPDATE_PROTOBUF_SEARCH_PATHS);
}

static void
update_protobuf_udp_message_types(void)
{
    protobuf_reinit(PREFS_UPDATE_PROTOBUF_UDP_MESSAGE_TYPES);
}

static void
deregister_header_fields(void)
{
    if (dynamic_hf) {
        /* Deregister all fields */
        for (guint i = 0; i < dynamic_hf_size; i++) {
            proto_deregister_field(proto_protobuf, *(dynamic_hf[i].p_id));
            g_free(dynamic_hf[i].p_id);
            /* dynamic_hf[i].name and .abbrev will be freed by proto_add_deregistered_data */
        }

        proto_add_deregistered_data(dynamic_hf);
        dynamic_hf = NULL;
        dynamic_hf_size = 0;
    }

    if (pbf_hf_hash) {
        g_hash_table_destroy(pbf_hf_hash);
        pbf_hf_hash = NULL;
    }
}

/* convert the names of the enum's values to value_string array */
static value_string*
enum_to_value_string(const PbwEnumDescriptor* enum_desc)
{
    value_string* vals;
    int i, value_count;
    if (enum_desc == NULL || (value_count = pbw_EnumDescriptor_value_count(enum_desc)) == 0) {
        return NULL;
    }

    vals = g_new0(value_string, value_count + 1);
    for (i = 0; i < value_count; i++) {
        const PbwEnumValueDescriptor* enum_value_desc = pbw_EnumDescriptor_value(enum_desc, i);
        vals[i].value = pbw_EnumValueDescriptor_number(enum_value_desc);
        vals[i].strptr = g_strdup(pbw_EnumValueDescriptor_name(enum_value_desc));
    }
    /* the strptr of last element of vals must be NULL */
    return vals;
}

/* create wireshark header fields according to each message's fields
 * and add them into pbf_as_hf hash table */
static void
collect_fields(const PbwDescriptor* message, void* userdata)
{
    wmem_list_t* hf_list = (wmem_list_t*) userdata;
    hf_register_info* hf;
    const PbwFieldDescriptor* field_desc;
    const PbwEnumDescriptor* enum_desc;
    const PbwDescriptor* sub_msg_desc;
    int i, field_type, total_num = pbw_Descriptor_field_count(message);

    /* add message as field */
    hf = g_new0(hf_register_info, 1);
    hf->p_id = g_new(gint, 1);
    *(hf->p_id) = -1;
    hf->hfinfo.name = g_strdup(pbw_Descriptor_name(message));
    hf->hfinfo.abbrev = g_strdup_printf("pbm.%s", pbw_Descriptor_full_name(message));
    hf->hfinfo.type = FT_BYTES;
    hf->hfinfo.display = BASE_NONE;
    wmem_list_append(hf_list, hf);
    g_hash_table_insert(pbf_hf_hash, g_strdup(pbw_Descriptor_full_name(message)), hf->p_id);

    /* add fields of this message as fields */
    for (i = 0; i < total_num; i++) {
        field_desc = pbw_Descriptor_field(message, i);
        field_type = pbw_FieldDescriptor_type(field_desc);
        if (field_type <= PROTOBUF_TYPE_NONE ||field_type > PROTOBUF_MAX_FIELD_TYPE) {
            /* not a valid field type */
            continue;
        }
        hf = g_new0(hf_register_info, 1);
        hf->p_id = g_new(gint, 1);
        *(hf->p_id) = -1;

        hf->hfinfo.name = g_strdup(pbw_FieldDescriptor_name(field_desc));
        hf->hfinfo.abbrev = g_strdup_printf("pbf.%s", pbw_FieldDescriptor_full_name(field_desc));
        switch (field_type) {
        case PROTOBUF_TYPE_DOUBLE:
            hf->hfinfo.type = FT_DOUBLE;
            hf->hfinfo.display = BASE_NONE;
            break;

        case PROTOBUF_TYPE_FLOAT:
            hf->hfinfo.type = FT_FLOAT;
            hf->hfinfo.display = BASE_NONE;
            break;

        case PROTOBUF_TYPE_INT64:
        case PROTOBUF_TYPE_SFIXED64:
        case PROTOBUF_TYPE_SINT64:
            hf->hfinfo.type = FT_INT64;
            hf->hfinfo.display = BASE_DEC;
            break;

        case PROTOBUF_TYPE_UINT64:
        case PROTOBUF_TYPE_FIXED64:
            hf->hfinfo.type = FT_UINT64;
            hf->hfinfo.display = BASE_DEC;
            break;

        case PROTOBUF_TYPE_INT32:
        case PROTOBUF_TYPE_SFIXED32:
        case PROTOBUF_TYPE_SINT32:
            hf->hfinfo.type = FT_INT32;
            hf->hfinfo.display = BASE_DEC;
            break;

        case PROTOBUF_TYPE_UINT32:
        case PROTOBUF_TYPE_FIXED32:
            hf->hfinfo.type = FT_UINT32;
            hf->hfinfo.display = BASE_DEC;
            break;

        case PROTOBUF_TYPE_ENUM:
            hf->hfinfo.type = FT_INT32;
            hf->hfinfo.display = BASE_DEC;
            enum_desc = pbw_FieldDescriptor_enum_type(field_desc);
            if (enum_desc) {
                hf->hfinfo.strings = enum_to_value_string(enum_desc);
            }
            break;

        case PROTOBUF_TYPE_BOOL:
            hf->hfinfo.type = FT_BOOLEAN;
            hf->hfinfo.display = BASE_NONE;
            break;

        case PROTOBUF_TYPE_BYTES:
            hf->hfinfo.type = dissect_bytes_as_string ? FT_STRING : FT_BYTES;
            hf->hfinfo.display = BASE_NONE;
            break;

        case PROTOBUF_TYPE_STRING:
            hf->hfinfo.type = FT_STRING;
            hf->hfinfo.display = BASE_NONE;
            break;

        case PROTOBUF_TYPE_GROUP:
        case PROTOBUF_TYPE_MESSAGE:
            sub_msg_desc = pbw_FieldDescriptor_message_type(field_desc);
            if (sub_msg_desc && strcmp(pbw_Descriptor_full_name(sub_msg_desc), "google.protobuf.Timestamp") == 0) {
                hf->hfinfo.type = FT_ABSOLUTE_TIME;
                hf->hfinfo.display = ABSOLUTE_TIME_LOCAL;
            } else {
                hf->hfinfo.type = FT_BYTES;
                hf->hfinfo.display = BASE_NONE;
            }
            break;

        default:
            /* should not happen */
            break;
        }

        wmem_list_append(hf_list, hf);
        g_hash_table_insert(pbf_hf_hash, g_strdup(pbw_FieldDescriptor_full_name(field_desc)), hf->p_id);
    }
}

static void
update_header_fields(gboolean force_reload)
{
    if (!force_reload && pbf_as_hf && dynamic_hf) {
        /* If initialized, do nothing. */
        return;
    }
    deregister_header_fields();

    if (pbf_as_hf) {
        int i;
        wmem_list_frame_t *it;
        wmem_list_t* hf_list = wmem_list_new(NULL);
        pbf_hf_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
        DISSECTOR_ASSERT(pbw_pool);
        pbw_foreach_message(pbw_pool, collect_fields, hf_list);
        dynamic_hf_size = wmem_list_count(hf_list);
        if (dynamic_hf_size == 0) {
            deregister_header_fields();
            return;
        }
        dynamic_hf = g_new0(hf_register_info, dynamic_hf_size);

        for (it = wmem_list_head(hf_list), i = 0; it; it = wmem_list_frame_next(it), i++) {
            hf_register_info* hf = (hf_register_info*) wmem_list_frame_data(it);
            /* copy hf_register_info structure */
            dynamic_hf[i] = *hf;
            g_free(hf);
            HFILL_INIT(dynamic_hf[i]);
        }

        wmem_destroy_list(hf_list);
        proto_register_field_array(proto_protobuf, dynamic_hf, dynamic_hf_size);
    }
}

static void
protobuf_reinit(int target)
{
    guint i;
    gchar **source_paths;
    GSList* it;
    range_t* udp_port_range;
    const gchar* message_type;
    gboolean loading_completed = TRUE;

    if (target & PREFS_UPDATE_PROTOBUF_UDP_MESSAGE_TYPES) {
        /* delete protobuf dissector from old udp ports */
        for (it = old_udp_port_ranges; it; it = it->next) {
            udp_port_range = (range_t*) it->data;
            dissector_delete_uint_range("udp.port", udp_port_range, protobuf_handle);
            wmem_free(NULL, udp_port_range);
        }

        if (old_udp_port_ranges) {
            g_slist_free(old_udp_port_ranges);
            old_udp_port_ranges = NULL;
        }

        /* add protobuf dissector to new udp ports */
        for (i = 0; i < num_protobuf_udp_message_types; ++i) {
            udp_port_range = protobuf_udp_message_types[i].udp_port_range;
            if (udp_port_range) {
                udp_port_range = range_copy(NULL, udp_port_range);
                old_udp_port_ranges = g_slist_append(old_udp_port_ranges, udp_port_range);
                dissector_add_uint_range("udp.port", udp_port_range, protobuf_handle);
            }
        }
    }

    /* loading .proto files and checking message types of UDP port will be done only after dissector is called */
    if (!protobuf_dissector_called) {
        return;
    }

    if (target & PREFS_UPDATE_PROTOBUF_SEARCH_PATHS) {
        /* convert protobuf_search_path_t array to char* array. should release by g_free(). */
        source_paths = g_new0(char *, num_protobuf_search_paths + 1);

        for (i = 0; i < num_protobuf_search_paths; ++i) {
            source_paths[i] = protobuf_search_paths[i].path;
        }

        /* init DescriptorPool of protobuf */
        pbw_reinit_DescriptorPool(&pbw_pool, (const char**)source_paths, buffer_error);

        /* load all .proto files in the marked search paths, we can invoke FindMethodByName etc later. */
        for (i = 0; i < num_protobuf_search_paths; ++i) {
            if (protobuf_search_paths[i].load_all) {
                if (!load_all_files_in_dir(pbw_pool, protobuf_search_paths[i].path)) {
                    buffer_error("Protobuf: Loading .proto files action stopped!\n");
                    loading_completed = FALSE;
                    break; /* stop loading when error occurs */
                }
            }
        }

        g_free(source_paths);
        update_header_fields(TRUE);
    }

    /* check if the message types of UDP port exist */
    for (i = 0; i < num_protobuf_udp_message_types; ++i) {
        message_type = protobuf_udp_message_types[i].message_type;
        if (loading_completed && message_type && strlen(message_type) > 0
            && pbw_DescriptorPool_FindMessageTypeByName(pbw_pool, message_type) == NULL) {
            buffer_error("Protobuf: the message type \"%s\" of UDP Message Type preferences does not exist!\n", message_type);
        }
    }

    /* report error if encountered */
    flush_and_report_error();
}

void
proto_register_protobuf(void)
{
    static hf_register_info hf[] = {
        { &hf_protobuf_message_name,
            { "Message Name", "protobuf.message.name",
               FT_STRING, BASE_NONE, NULL, 0x0,
              "The name of the protobuf message", HFILL }
        },
        { &hf_protobuf_field_name,
            { "Field Name", "protobuf.field.name",
               FT_STRING, BASE_NONE, NULL, 0x0,
              "The name of the field", HFILL }
        },
        { &hf_protobuf_field_type,
            { "Field Type", "protobuf.field.type",
               FT_INT32, BASE_DEC, VALS(protobuf_field_type), 0x0,
              "The type of the field", HFILL }
        },
        { &hf_protobuf_field_number,
            { "Field Number", "protobuf.field.number",
               FT_UINT64, BASE_DEC, NULL, G_GUINT64_CONSTANT(0xFFFFFFFFFFFFFFF8),
              "Field number encoded in varint", HFILL }
        },
        { &hf_protobuf_wire_type,
            { "Wire Type", "protobuf.field.wiretype",
               FT_UINT8, BASE_DEC, VALS(protobuf_wire_type), 0x07,
              "The Wire Type of the field.", HFILL }
        },
        { &hf_protobuf_value_length,
            { "Value Length", "protobuf.field.value.length",
               FT_UINT64, BASE_DEC, NULL, 0x0,
              "The length of length-delimited field value.", HFILL }
        },
        { &hf_protobuf_value_data,
            { "Value", "protobuf.field.value",
               FT_BYTES, BASE_NONE, NULL, 0x0,
              "The wire type determines value format", HFILL }
        },
        { &hf_protobuf_value_double,
            { "Double", "protobuf.field.value.double",
               FT_DOUBLE, BASE_NONE, NULL, 0x0,
              "Dissect value as double", HFILL }
        },
        { &hf_protobuf_value_float,
            { "Float", "protobuf.field.value.float",
               FT_FLOAT, BASE_NONE, NULL, 0x0,
              "Dissect value as float", HFILL }
        },
        { &hf_protobuf_value_int64,
            { "Int64", "protobuf.field.value.int64",
               FT_INT64, BASE_DEC, NULL, 0x0,
              "Dissect value as int64", HFILL }
        },
        { &hf_protobuf_value_uint64,
            { "Uint64", "protobuf.field.value.uint64",
               FT_UINT64, BASE_DEC, NULL, 0x0,
              "Dissect value as uint64", HFILL }
        },
        { &hf_protobuf_value_int32,
            { "Int32", "protobuf.field.value.int32",
               FT_INT32, BASE_DEC, NULL, 0x0,
              "Dissect value as int32", HFILL }
        },
        { &hf_protobuf_value_uint32,
            { "Uint32", "protobuf.field.value.uint32",
               FT_UINT32, BASE_DEC, NULL, 0x0,
              "Dissect value as uint32", HFILL }
        },
        { &hf_protobuf_value_bool,
            { "Bool", "protobuf.field.value.bool",
               FT_BOOLEAN, BASE_DEC, NULL, 0x0,
              "Dissect value as bool", HFILL }
        },
        { &hf_protobuf_value_string,
            { "String", "protobuf.field.value.string",
               FT_STRING, BASE_NONE, NULL, 0x0,
              "Dissect value as string", HFILL }
        },
        { &hf_protobuf_value_repeated,
            { "Repeated", "protobuf.field.value.repeated",
               FT_BYTES, BASE_NONE, NULL, 0x0,
              "Dissect value as repeated", HFILL }
        }
    };

    static gint *ett[] = {
        &ett_protobuf,
        &ett_protobuf_message,
        &ett_protobuf_field,
        &ett_protobuf_value,
        &ett_protobuf_packed_repeated
    };

    /* Setup protocol expert items */
    static ei_register_info ei[] = {
        { &ei_protobuf_failed_parse_tag,
          { "protobuf.failed_parse_tag", PI_MALFORMED, PI_ERROR,
            "Failed to parse tag field", EXPFILL }
        },
        { &ei_protobuf_wire_type_invalid,
          { "protobuf.field.wiretype.invalid", PI_PROTOCOL, PI_WARN,
            "Unknown or unsupported wiretype", EXPFILL }
        },
        { &ei_protobuf_failed_parse_length_delimited_field,
          { "protobuf.field.failed_parse_length_delimited_field", PI_MALFORMED, PI_ERROR,
            "Failed to parse length delimited field", EXPFILL }
        },
        { &ei_protobuf_failed_parse_field,
          { "protobuf.field.failed_parse_field", PI_MALFORMED, PI_ERROR,
            "Failed to parse value field", EXPFILL }
        },
        { &et_protobuf_message_type_not_found,
          { "protobuf.field.message_type_not_found", PI_PROTOCOL, PI_WARN,
            "Failed to find message type of a field", EXPFILL }
        },
        { &et_protobuf_wire_type_not_support_packed_repeated,
          { "protobuf.field.wire_type_not_support_packed_repeated", PI_MALFORMED, PI_ERROR,
            "The wire type does not support protobuf packed repeated field", EXPFILL }
        },
        { &et_protobuf_failed_parse_packed_repeated_field,
          { "protobuf.field.failed_parse_packed_repeated_field", PI_MALFORMED, PI_ERROR,
            "Failed to parse packed repeated field", EXPFILL }
        },
    };

    module_t *protobuf_module;
    expert_module_t *expert_protobuf;

    static uat_field_t protobuf_search_paths_table_columns[] = {
        UAT_FLD_DIRECTORYNAME(protobuf_search_paths, path, "Protobuf source directory", "Directory of the root of protobuf source files"),
        UAT_FLD_BOOL(protobuf_search_paths, load_all, "Load all files", "Load all .proto files from this directory and its subdirectories"),
        UAT_END_FIELDS
    };
    uat_t* protobuf_search_paths_uat;

    static uat_field_t protobuf_udp_message_types_table_columns[] = {
        UAT_FLD_RANGE(protobuf_udp_message_types, udp_port_range, "UDP Ports", 0xFFFF, "UDP ports on which data will be dissected as protobuf"),
        UAT_FLD_CSTRING(protobuf_udp_message_types, message_type, "Message Type", "Protobuf message type of data on these udp ports"),
        UAT_END_FIELDS
    };
    uat_t* protobuf_udp_message_types_uat;

    proto_protobuf = proto_register_protocol("Protocol Buffers", "ProtoBuf", "protobuf");

    proto_register_field_array(proto_protobuf, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

    protobuf_module = prefs_register_protocol(proto_protobuf, proto_reg_handoff_protobuf);

    protobuf_search_paths_uat = uat_new("Protobuf Search Paths",
        sizeof(protobuf_search_path_t),
        "protobuf_search_paths",
        TRUE,
        &protobuf_search_paths,
        &num_protobuf_search_paths,
        UAT_AFFECTS_DISSECTION | UAT_AFFECTS_FIELDS,
        "ChProtobufSearchPaths",
        protobuf_search_paths_copy_cb,
        NULL,
        protobuf_search_paths_free_cb,
        update_protobuf_search_paths,
        NULL,
        protobuf_search_paths_table_columns
    );

    prefs_register_uat_preference(protobuf_module, "search_paths", "Protobuf search paths",
        "Specify the directories where .proto files are recursively loaded from, or in which to search for imports.",
        protobuf_search_paths_uat);

    prefs_register_bool_preference(protobuf_module, "pbf_as_hf",
        "Dissect Protobuf fields as Wireshark fields.",
        "If Protobuf messages and fields are defined in loaded .proto files,"
        " they will be dissected as wireshark fields if this option is turned on."
        " The names of all these wireshark fields will be prefixed with \"pbf.\" (for fields)"
        " or \"pbm.\" (for messages) followed by their full names in the .proto files.",
        &pbf_as_hf);

    prefs_set_preference_effect_fields(protobuf_module, "pbf_as_hf");

    prefs_register_bool_preference(protobuf_module, "show_details",
        "Show details of message, fields and enums.",
        "Show the names of message, field, enum and enum_value."
        " Show the wire type and field number format of field."
        " Show value nodes of field and enum_value.",
        &show_details);

    prefs_register_bool_preference(protobuf_module, "bytes_as_string",
        "Show all fields of bytes type as string.",
        "Show all fields of bytes type as string. For example ETCD string",
        &dissect_bytes_as_string);

    protobuf_udp_message_types_uat = uat_new("Protobuf UDP Message Types",
        sizeof(protobuf_udp_message_type_t),
        "protobuf_udp_message_types",
        TRUE,
        &protobuf_udp_message_types,
        &num_protobuf_udp_message_types,
        UAT_AFFECTS_DISSECTION | UAT_AFFECTS_FIELDS,
        "ChProtobufUDPMessageTypes",
        protobuf_udp_message_types_copy_cb,
        protobuf_udp_message_types_update_cb,
        protobuf_udp_message_types_free_cb,
        update_protobuf_udp_message_types,
        NULL,
        protobuf_udp_message_types_table_columns
    );

    prefs_register_uat_preference(protobuf_module, "udp_message_types", "Protobuf UDP message types",
        "Specify the Protobuf message type of data on certain UDP ports.",
        protobuf_udp_message_types_uat);

    /* Following preferences are for undefined fields, that happened while message type is not specified
       when calling dissect_protobuf(), or message type or field information is not found in search paths
    */
    prefs_register_bool_preference(protobuf_module, "try_dissect_as_string",
        "Try to dissect all undefined length-delimited fields as string.",
        "Try to dissect all undefined length-delimited fields as string.",
        &try_dissect_as_string);

    prefs_register_bool_preference(protobuf_module, "show_all_types",
        "Try to show all possible field types for each undefined field.",
        "Try to show all possible field types for each undefined field according to wire type.",
        &show_all_possible_field_types);

    prefs_register_static_text_preference(protobuf_module, "field_dissector_table_note",
        "Subdissector can register itself in \"protobuf_field\" dissector table for parsing"
        " the value of the field of bytes or string type.",
        "The key of \"protobuf_field\" table is the full name of field.");

    protobuf_field_subdissector_table =
        register_dissector_table("protobuf_field", "Protobuf field subdissector table",
            proto_protobuf, FT_STRING, BASE_NONE);

    expert_protobuf = expert_register_protocol(proto_protobuf);
    expert_register_field_array(expert_protobuf, ei, array_length(ei));

    protobuf_handle = register_dissector("protobuf", dissect_protobuf, proto_protobuf);
}

void
proto_reg_handoff_protobuf(void)
{
    if (protobuf_dissector_called) {
        update_header_fields( /* if bytes_as_string preferences changed, we force reload header fields */
            (old_dissect_bytes_as_string && !dissect_bytes_as_string) || (!old_dissect_bytes_as_string && dissect_bytes_as_string)
        );
    }
    old_dissect_bytes_as_string = dissect_bytes_as_string;
    dissector_add_string("grpc_message_type", "application/grpc", protobuf_handle);
    dissector_add_string("grpc_message_type", "application/grpc+proto", protobuf_handle);
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */
