aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHuang Qiangxiong <qiangxiong.huang@qq.com>2017-09-23 09:05:21 -0400
committerAnders Broman <a.broman58@gmail.com>2019-10-07 10:35:52 +0000
commit5750c4981c56464ef1dbb8d7cfb0446cdb4b12ec (patch)
tree83e99a97609a3b8e12653d2041b4722004c87021
parent0d8bebc327cd71736c152e8e428294f826e51561 (diff)
protobuf: add support for Protocol Buffers Language (*.proto) files
1. A C-style Protocol Buffers Language (PBL) parser for *.proto file is added. It contains protobuf_lang_scanner.l (lex scanner), epan/protobuf_lang.y (grammar parser), and protobuf_lang_tree.h/c (grammar tree implementation). 2. The protobuf-helper.h/cpp is an interface wrapper layer. If one day C++ is allowed, we can create a protobuf-helper.cpp file, which using offical protobuf C++ library, to replace protobuf-helper.c. That keeps packet-protobuf.c unchanged. 3. User can specify protobuf search paths, and the UDP ports to protobuf message type maps at the Protobuf protocol preferences. 4. Other dissectors can pass the message type to Protobuf dissector by data parameter or pinfo->private_table["pb_msg_type"] (pinfo.private["pb_msg_type"] in lua). Some Sample of GRPC with Protobuf captures can be found in Bug: 13932. Bug: 13932 Change-Id: Ife16c2f7b381296f8db4740dabe5f8362a456f48 Reviewed-on: https://code.wireshark.org/review/22892 Petri-Dish: Anders Broman <a.broman58@gmail.com> Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman <a.broman58@gmail.com>
-rw-r--r--docbook/release-notes.adoc1
-rw-r--r--docbook/wsug_src/WSUG_chapter_customize.adoc78
-rw-r--r--epan/CMakeLists.txt7
-rw-r--r--epan/dissectors/packet-protobuf.c813
-rw-r--r--epan/protobuf-helper.c215
-rw-r--r--epan/protobuf-helper.h209
-rw-r--r--epan/protobuf_lang.y621
-rw-r--r--epan/protobuf_lang_scanner.l199
-rw-r--r--epan/protobuf_lang_tree.c850
-rw-r--r--epan/protobuf_lang_tree.h340
10 files changed, 3174 insertions, 159 deletions
diff --git a/docbook/release-notes.adoc b/docbook/release-notes.adoc
index ab46876c82..8d3a59e858 100644
--- a/docbook/release-notes.adoc
+++ b/docbook/release-notes.adoc
@@ -61,6 +61,7 @@ since version 3.0.0:
.exe installers are still dual-signed using SHA-1 and SHA-2.
* The “Enabled Protocols” Dialog now only enables, disables and inverts protocols based on the set filter selection. The protocol type (standard or heuristic) may also be choosen as a filter value.
* The “Analyze -> Apply as Filter” and “Analyze -> Prepare a Filter” packet list and detail popup menus now show a preview of their respective filters.
+* Protobuf files (*.proto) can now be configured to enable more precise parsing of serialized Protobuf data (such as gRPC).
// === Removed Features and Support
diff --git a/docbook/wsug_src/WSUG_chapter_customize.adoc b/docbook/wsug_src/WSUG_chapter_customize.adoc
index 612b465279..419c32b22b 100644
--- a/docbook/wsug_src/WSUG_chapter_customize.adoc
+++ b/docbook/wsug_src/WSUG_chapter_customize.adoc
@@ -738,6 +738,10 @@ Configuration files stored in each profile include:
* User DLTs Table (user_dlts) (<<ChUserDLTsSection>>)
* IKEv2 decryption table (ikev2_decryption_table) (<<ChIKEv2DecryptionSection>>)
+
+* Protobuf Search Paths (protobuf_search_paths) (<<ChProtobufSearchPaths>>)
+
+* Protobuf UDP Message Types (protobuf_udp_message_types) (<<ChProtobufUDPMessageTypes>>)
--
* Changed dissector assignments (__decode_as_entries__), which can be set in the “Decode
@@ -1148,4 +1152,78 @@ size this trailer is. A value of 0 disables the trailer protocol.
Trailer protocol::
The name of the trailer protocol to be used (uses “data” as default).
+[[ChProtobufSearchPaths]]
+
+=== Protobuf Search Paths
+
+The
+https://developers.google.com/protocol-buffers/docs/encoding[binary wire format]
+of Protocol Buffers (Protobuf) messages are not self-described protocol. For
+example, the `varint` wire type in protobuf packet may be converted to int32, int64,
+uint32, uint64, sint32, sint64, bool or enum field types of
+https://developers.google.com/protocol-buffers/docs/proto3[protocol buffers language].
+Wireshark should be configured with Protocol Buffers language files (*.proto) to
+enable proper dissection of protobuf data (which may be payload of
+https://grpc.io/[gRPC]) based on the message, enum and field definitions.
+
+You can specify protobuf search paths at the Protobuf protocol preferences.
+For example, if you defined a proto file with path `d:/my_proto_files/helloworld.proto`
+and the `helloworld.proto` contains a line of `import "google/protobuf/any.proto";`
+because the `any` type of offical protobuf library is used. And the real path of
+`any.proto` is `d:/protobuf-3.4.1/include/google/protobuf/any.proto`. You should
+add the `d:/protobuf-3.4.1/include/` and `d:/my_proto_files` paths into protobuf
+search paths.
+
+The configuration for the protobuf search paths is a user table, as described
+in <<ChUserTable>>, with the following fields:
+
+Protobuf source directory::
+This specifies a directory containing protobuf source files. For example,
+`d:/protobuf-3.4.1/include/` and `d:/my_proto_files` in Windows, or
+`/usr/include/` and `/home/alice/my_proto_files` in Linux/UNIX.
+
+Load all files::
+If this option is enabled, Wireshark will load all *.proto files in this directory
+and its subdirectories when Wireshark startup or protobuf search paths preferences
+changed. Note that the source directories that configured to protobuf offical or third
+libraries path (like `d:/protobuf-3.4.1/include/`) should not be set to load all
+files, that may cause memory unnecessary occuption.
+
+[[ChProtobufUDPMessageTypes]]
+
+=== Protobuf UDP Message Types
+
+If the payload of UDP on certain ports is Protobuf encoding, Wireshark use this table
+to know which Protobuf message type should be used to parsing the data on the specified
+UDP port(s).
+
+The configuration for UDP Port(s) to Protobuf message type maps is a user table, as
+described in <<ChUserTable>>, with the following fields:
+
+UDP Ports::
+The range of UDP ports. The format may be "8000" or "8000,8008-8088,9080".
+
+Message Type::
+The Protobuf message type as which the data on the specified udp port(s) should be parsed.
+The message type is allowed to be empty, that means let Protobuf to dissect the data on
+specified UDP ports as normal wire type without precise definitions.
+
+Tips: You can create your own dissector to call Protobuf dissector. If your dissector is
+written in C language, you can pass the message type to Protobuf dissector by `data`
+parameter of call_dissector_with_data() function. If your dissector is written in Lua, you
+can pass the message type to Protobuf dissector by `pinfo.private["pb_msg_type"]`. The format
+of `data` and `pinfo.private["pb_msg_type"]` is
+
+----
+ "message," message_type_name
+----
+
+For example:
+
+----
+ message,helloworld.HelloRequest
+----
+
+the `helloworld` is package name, `HelloRequest` is message type.
+
// End of WSUG Chapter Customizing
diff --git a/epan/CMakeLists.txt b/epan/CMakeLists.txt
index 03f4939d2f..b08a1a7167 100644
--- a/epan/CMakeLists.txt
+++ b/epan/CMakeLists.txt
@@ -276,6 +276,8 @@ set(LIBWIRESHARK_NONGENERATED_FILES
value_string.c
unit_strings.c
xdlc.c
+ protobuf-helper.c
+ protobuf_lang_tree.c
${CMAKE_CURRENT_BINARY_DIR}/ps.c
)
@@ -287,12 +289,17 @@ add_lex_files(LEX_FILES LIBWIRESHARK_FILES
dtd_preparse.l
radius_dict.l
uat_load.l
+ protobuf_lang_scanner.l
)
add_lemon_files(LEMON_FILES LIBWIRESHARK_FILES
dtd_grammar.lemon
)
+add_yacc_files(YACC_FILES LIBWIRESHARK_FILES
+ protobuf_lang.y
+)
+
set_source_files_properties(
${LIBWIRESHARK_FILES}
PROPERTIES
diff --git a/epan/dissectors/packet-protobuf.c b/epan/dissectors/packet-protobuf.c
index de1112519b..2d247b34b2 100644
--- a/epan/dissectors/packet-protobuf.c
+++ b/epan/dissectors/packet-protobuf.c
@@ -11,13 +11,21 @@
/*
* The information used comes from:
- * https://developers.google.cn/protocol-buffers/docs/encoding
+ * https://developers.google.com/protocol-buffers/docs/encoding
*
- * This protobuf dissector may be invoked by GRPC dissector.
- *
- * TODO
- * Support custom preference settings for embedded messages.
- * Dissect message according to '*.proto' files.
+ * 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"
@@ -25,13 +33,18 @@
#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"
-#include "wsutil/pint.h"
-
/* converting */
static inline gdouble
protobuf_uint64_to_double(guint64 value) {
@@ -51,33 +64,6 @@ protobuf_uint32_to_float(guint32 value) {
VALUE_STRING_ARRAY_GLOBAL_DEF(protobuf_wire_type);
-/* Protobuf field type. Must be kept in sync with FieldType of protobuf wire_format_lite.h */
-#define protobuf_field_type_VALUE_STRING_LIST(XXX) \
- XXX(PROTOBUF_TYPE_NONE, 0, "") \
- XXX(PROTOBUF_TYPE_DOUBLE, 1, "double") \
- XXX(PROTOBUF_TYPE_FLOAT, 2, "float") \
- XXX(PROTOBUF_TYPE_INT64, 3, "int64") \
- XXX(PROTOBUF_TYPE_UINT64, 4, "uint64") \
- XXX(PROTOBUF_TYPE_INT32, 5, "int32") \
- XXX(PROTOBUF_TYPE_FIXED64, 6, "fixed64") \
- XXX(PROTOBUF_TYPE_FIXED32, 7, "fixed32") \
- XXX(PROTOBUF_TYPE_BOOL, 8, "bool") \
- XXX(PROTOBUF_TYPE_STRING, 9, "string") \
- XXX(PROTOBUF_TYPE_GROUP, 10, "group(packed_repeated)") \
- XXX(PROTOBUF_TYPE_MESSAGE, 11, "message") \
- XXX(PROTOBUF_TYPE_BYTES, 12, "bytes") \
- XXX(PROTOBUF_TYPE_UINT32, 13, "uint32") \
- XXX(PROTOBUF_TYPE_ENUM, 14, "enum") \
- XXX(PROTOBUF_TYPE_SFIXED32, 15, "sfixed32") \
- XXX(PROTOBUF_TYPE_SFIXED64, 16, "sfixed64") \
- XXX(PROTOBUF_TYPE_SINT32, 17, "sint32") \
- XXX(PROTOBUF_TYPE_SINT64, 18, "sint64")
-
-#define PROTOBUF_MAX_FIELD_TYPE 18
-
-VALUE_STRING_ENUM(protobuf_field_type);
-VALUE_STRING_ARRAY(protobuf_field_type);
-
/* which field type of each wire type could be */
static int protobuf_wire_to_field_type[6][9] = {
/* PROTOBUF_WIRETYPE_VARINT, 0, "varint") */
@@ -109,6 +95,11 @@ void proto_reg_handoff_protobuf(void);
static int proto_protobuf = -1;
+/* 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;
@@ -131,41 +122,126 @@ 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 */
gboolean try_dissect_as_string = FALSE;
-gboolean try_dissect_as_repeated = FALSE;
gboolean show_all_possible_field_types = FALSE;
+gboolean dissect_bytes_as_string = FALSE;
static dissector_handle_t protobuf_handle;
-/* Stuff for generation/handling of fields */
+/* store varint tvb info */
typedef struct {
- gchar* call_path; /* equals to grpc :path, for example: "/helloworld.Greeter/SayHello" */
- guint direction_type; /* 0: request, 1: response */
- guint field_type; /* type of field, refer to protobuf_field_type vals. */
- gchar* field_name; /* field name, will display in tree */
- guint field_number; /* field number in .proto file*/
-} protobuf_field_t;
-
-static GHashTable* protobuf_fields_hash = NULL;
-
-/* get field info according to call_path, direction_type and field_number
-* user should free the returned struct. */
-static protobuf_field_t*
-protobuf_find_field_info(const gchar* call_path_direction_type, int field_number)
+ guint offset;
+ guint length;
+ guint64 value;
+} protobuf_varint_tvb_info_t;
+
+/* preferences */
+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_)
{
- gchar* key = wmem_strdup_printf(wmem_packet_scope(), "%s,%d", call_path_direction_type, field_number);
- protobuf_field_t* p = (protobuf_field_t*)g_hash_table_lookup(protobuf_fields_hash, key);
- return p;
+ 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.
@@ -184,73 +260,145 @@ sint64_decode(guint64 sint64) {
return (sint64 >> 1) ^ ((gint64)sint64 << 63 >> 63);
}
+
/* declare first because it will be called by dissect_packed_repeated_field_values */
static void
-protobuf_try_dissect_field_value_on_multi_types(proto_tree *value_tree, tvbuff_t *tvb, guint offset, guint length,
- packet_info *pinfo, void *data, proto_item *ti_field, int wire_type, int* field_types, const guint64 value);
+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);
-/* format tag + varint + varint + varint ...
-return consumed bytes */
+static void
+dissect_protobuf_message(tvbuff_t *tvb, guint offset, guint length, packet_info *pinfo, proto_tree *protobuf_tree, const PbwDescriptor* message_desc);
+
+/* 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(proto_tree *value_tree, tvbuff_t *tvb, guint start, guint length, packet_info *pinfo,
- void *data, proto_item *ti_field, int wire_type, int* field_types, const gchar* prepend_text)
+ 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;
- int i;
+ wmem_list_frame_t *lframe;
+ wmem_list_t* varint_list;
+ int value_size = 0;
- for (i = 0; field_types[i] != PROTOBUF_TYPE_NONE; ++i) {
- if (field_types[i] == PROTOBUF_TYPE_GROUP) {
- return 0; /* prevent dead loop */
- }
+ if (prepend_text == NULL) {
+ prepend_text = "";
}
- proto_item_append_text(ti_field, "%s Repeated: [", (prepend_text ? prepend_text : ""));
+ /* prepare subtree */
+ proto_item_append_text(ti_field, "%s [", prepend_text);
proto_item *ti = proto_tree_add_item(value_tree, hf_protobuf_value_repeated, tvb, start, length, ENC_NA);
proto_tree *subtree = proto_item_add_subtree(ti, ett_protobuf_packed_repeated);
- /* add varints into the packed-repeated subtree */
- 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 */
+ 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);
+ 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;
}
- protobuf_try_dissect_field_value_on_multi_types(subtree, tvb, offset, sub_value_length, pinfo, data,
- ti_field, wire_type, field_types, sub_value);
+ 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);
+ prepend_text = ",";
+ }
+
+ break;
- offset += sub_value_length;
+ 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. if pfield_info is given, we use pfield_info->field_type
- * insteadof field_type parameter and we will show field_name.
- */
+/* 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,
- void *data, proto_item *ti_field, int wire_type, int field_type, const guint64 value, protobuf_field_t *pfield_info)
+ proto_item *ti_field, int field_type, const guint64 value, const gchar* prepend_text, const PbwFieldDescriptor* field_desc)
{
gdouble double_value;
gfloat float_value;
gint64 int64_value;
gint32 int32_value;
- char* buf;
+ char* buf;
gboolean add_datatype = TRUE;
+ proto_item* ti;
+ const char* enum_value_name = NULL;
+ const PbwDescriptor* sub_message_desc = NULL;
+ const PbwEnumDescriptor* enum_desc = NULL;
- gchar* prepend_text;
-
- if (pfield_info && pfield_info->field_name && strlen(pfield_info->field_name) >0) {
- field_type = pfield_info->field_type;
- prepend_text = wmem_strdup_printf(wmem_packet_scope(), ", %s =", pfield_info->field_name);
- } else {
- prepend_text = ",";
+ if (prepend_text == NULL) {
+ prepend_text = "";
}
switch (field_type)
@@ -282,42 +430,64 @@ protobuf_dissect_field_value(proto_tree *value_tree, tvbuff_t *tvb, guint offset
case PROTOBUF_TYPE_INT32:
case PROTOBUF_TYPE_SFIXED32:
- int32_value = (gint32) value;
+ 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);
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);
+ } else {
+ proto_item_append_text(ti_field, "%s %d", prepend_text, 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");
break;
+ case PROTOBUF_TYPE_BYTES:
+ if (!dissect_bytes_as_string) {
+ break;
+ }
+ /* or continue dissect BYTES as STRING */
+ /* 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);
break;
- case PROTOBUF_TYPE_GROUP:
- if (try_dissect_as_repeated) {
- int field_types[] = { PROTOBUF_TYPE_UINT64, PROTOBUF_TYPE_NONE };
- dissect_packed_repeated_field_values(value_tree, tvb, offset, length, pinfo, data, ti_field, wire_type,
- field_types, prepend_text);
- }
- add_datatype = FALSE;
- break;
-
+ case PROTOBUF_TYPE_GROUP: /* This feature is deprecated. GROUP is identical to Nested MESSAGE. */
case PROTOBUF_TYPE_MESSAGE:
- /* may call dissect_protobuf(tvb, pinfo, value_tree, data); */
- add_datatype = FALSE;
- break;
-
- case PROTOBUF_TYPE_BYTES:
- /* do nothing */
- add_datatype = FALSE;
+ if (field_desc) {
+ sub_message_desc = pbw_FieldDescriptor_message_type(field_desc);
+ if (sub_message_desc) {
+ dissect_protobuf_message(tvb, offset, length, pinfo,
+ proto_item_get_subtree(ti_field), sub_message_desc);
+ } else {
+ expert_add_info(pinfo, ti_field, &et_protobuf_message_type_not_found);
+ }
+ } else {
+ /* we don't continue with unknown mssage type */
+ }
break;
- case PROTOBUF_TYPE_ENUM:
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);
@@ -350,18 +520,23 @@ protobuf_dissect_field_value(proto_tree *value_tree, tvbuff_t *tvb, guint offset
/* 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, void *data, proto_item *ti_field, int wire_type, int* field_types, const guint64 value)
+ 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, data, ti_field, wire_type, field_types[i],
- value, NULL);
+ protobuf_dissect_field_value(value_tree, tvb, offset, length, pinfo, ti_field, field_types[i], value, prepend_text, NULL);
+ prepend_text = ",";
}
}
-static gboolean
-dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_info *pinfo,
- proto_tree *protobuf_tree, void *data)
+static guint
+dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_info *pinfo, proto_tree *protobuf_tree,
+ const PbwDescriptor* message_desc)
{
guint64 tag_value; /* tag value = (field_number << 3) | wire_type */
guint tag_length; /* how many bytes this tag has */
@@ -371,10 +546,13 @@ dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_in
guint value_length;
guint value_length_size = 0; /* only Length-delimited field has it */
proto_item *ti_field, *ti_wire;
- proto_item *ti_value;
+ proto_item *ti_value, *ti_field_name, *ti_field_type = NULL;
proto_tree *field_tree;
proto_tree *value_tree;
- protobuf_field_t* pfield_info = NULL;
+ 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
@@ -391,7 +569,7 @@ dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_in
* 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");
+ 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);
@@ -405,7 +583,35 @@ dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_in
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;
- proto_item_append_text(ti_field, "[%" G_GINT64_MODIFIER "u]", field_number);
+ /* 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 && !dissect_bytes_as_string))
+ ? "" : "="
+ );
+ 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);
+ }
+ }
/* determine value_length, uint of numeric value and maybe value_length_size according to wire_type */
switch (wire_type)
@@ -451,6 +657,10 @@ dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_in
}
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);
@@ -458,41 +668,28 @@ dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_in
/* 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);
- /* try to find field_info first */
- if (data) {
- const gchar* message_info = (const gchar*)data;
- /* find call_path + request or response part from format:
- * http2_content_type "," http2_path "," ("request" / "response")
- * Acording to grpc wire format guide, it will be:
- * "application/grpc" [("+proto" / "+json" / {custom})] "," "/" service-name "/" method-name "/" "," ("request" / "response")
- * For example:
- * application/grpc,/helloworld.Greeter/SayHello,request
- */
- message_info = strchr(message_info, ',');
- if (message_info) {
- message_info++;
+ if (field_desc) {
+ if (is_packed_repeated) {
+ dissect_packed_repeated_field_values(value_tree, 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);
}
- pfield_info = protobuf_find_field_info(message_info, (gint) field_number);
- }
-
- if (pfield_info) {
- protobuf_dissect_field_value(value_tree, tvb, *offset, value_length, pinfo, data, ti_field, wire_type,
- pfield_info->field_type, value_uint64, pfield_info);
} 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, data,
- ti_field, wire_type, protobuf_wire_to_field_type[wire_type], value_uint64);
+ 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 {
- int field_type = (wire_type == PROTOBUF_WIRETYPE_LENGTH_DELIMITED)
+ 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, data,
- ti_field, wire_type, field_types, value_uint64);
+ protobuf_try_dissect_field_value_on_multi_types(value_tree, tvb, *offset, value_length, pinfo,
+ ti_field, field_types, value_uint64, "");
}
}
@@ -500,12 +697,63 @@ dissect_one_protobuf_field(tvbuff_t *tvb, guint* offset, guint maxlen, packet_in
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)
+{
+ proto_tree *message_tree;
+ proto_item *ti_message, *ti;
+ const gchar* message_name = "<UNKNOWN> Message Type";
+ guint max_offset = offset + length;
+
+ message_tree = proto_tree_add_subtree(protobuf_tree, tvb, offset, length, ett_protobuf_message, &ti_message, "Message");
+ if (message_desc) {
+ message_name = pbw_Descriptor_full_name(message_desc);
+ }
+
+ proto_item_append_text(ti_message, ": %s", message_name);
+ /* 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);
+
+ /* each time we dissec one protobuf field. */
+ while (offset < max_offset)
+ {
+ if (!dissect_one_protobuf_field(tvb, &offset, max_offset - offset, pinfo, message_tree, message_desc))
+ 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;
/* may set col_set_str(pinfo->cinfo, COL_PROTOCOL, "PROTOBUF"); */
col_append_str(pinfo->cinfo, COL_INFO, " (PROTOBUF)");
@@ -513,25 +761,202 @@ dissect_protobuf(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data
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) {
- /* append to proto item */
- proto_item_append_text(ti, ": %s", (const gchar*)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");
}
- /* each time we dissect one protobuf field. */
- while (tvb_reported_length_remaining(tvb, offset) > 0)
- {
- if (!dissect_one_protobuf_field(tvb, &offset, tvb_reported_length_remaining(tvb, offset), pinfo, protobuf_tree, data))
- break;
+ 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);
+
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)) {
+ return FALSE;
+ }
+ } else {
+ if (!load_all_files_in_dir(pool, path)) {
+ return FALSE;
+ }
+ }
+ g_free(path);
+ }
+ ws_dir_close(dir);
+ }
+ }
+ return TRUE;
+}
+
+void
+protobuf_reinit(void)
+{
+ guint i;
+ gchar **source_paths;
+ GSList* it;
+ range_t* udp_port_range;
+ const gchar* message_type;
+ gboolean loading_completed = TRUE;
+
+ /* 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, report_failure);
+
+ /* 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)) {
+ report_failure("Protobuf: Loading .proto files action stopped!");
+ loading_completed = FALSE;
+ break; /* stop loading when error occurs */
+ }
+ }
+ }
+
+ g_free(source_paths);
+
+ /* 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);
+ }
+
+ 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) {
+ report_failure("Protobuf: the message type \"%s\" of UDP Message Type preferences does not exist!", message_type);
+ }
+ }
+}
+
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),
@@ -601,6 +1026,7 @@ proto_register_protobuf(void)
static gint *ett[] = {
&ett_protobuf,
+ &ett_protobuf_message,
&ett_protobuf_field,
&ett_protobuf_value,
&ett_protobuf_packed_repeated
@@ -624,31 +1050,100 @@ proto_register_protobuf(void)
{ "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, NULL);
-
- prefs_register_bool_preference(protobuf_module, "try_dissect_all_length_delimited_field_as_string",
- "Try to dissect all length-delimited field as string.",
- "Try to dissect all length-delimited field as string.",
+ protobuf_module = prefs_register_protocol(proto_protobuf, protobuf_reinit);
+
+ 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,
+ protobuf_reinit,
+ 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, "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,
+ protobuf_reinit,
+ 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, "try_dissect_length_delimited_field_as_repeated",
- "Try to dissect length-delimited field as repeated.",
- "Try to dissect length-delimited field as repeated.",
- &try_dissect_as_repeated);
-
- prefs_register_bool_preference(protobuf_module, "show_all_possible_field_types",
- "Try to show all possible field types for each field.",
- "Try to show all possible field types for each field according to wire type.",
+ 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);
expert_protobuf = expert_register_protocol(proto_protobuf);
@@ -660,10 +1155,10 @@ proto_register_protobuf(void)
void
proto_reg_handoff_protobuf(void)
{
- dissector_add_uint_range_with_preference("udp.port", "", protobuf_handle);
-
dissector_add_string("grpc_message_type", "application/grpc", protobuf_handle);
dissector_add_string("grpc_message_type", "application/grpc+proto", protobuf_handle);
+
+ protobuf_reinit();
}
/*
diff --git a/epan/protobuf-helper.c b/epan/protobuf-helper.c
new file mode 100644
index 0000000000..02a4470b28
--- /dev/null
+++ b/epan/protobuf-helper.c
@@ -0,0 +1,215 @@
+/* protobuf-helper.c
+ *
+ * Wrapper of Protocol Buffers Language library which generated by protobuf_lang.y and protobuf_lang_scanner.l.
+ * Copyright 2019, 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 packet-protobuf dissector needs get information from *.proto files for dissecting
+ * protobuf packet correctly. The information includes:
+ * - The names of MESSAGE, ENUM, FIELD, ENUM_VALUE;
+ * - The data type of FIELD which assuring the value of protobuf field of packet can be dissected correctly.
+ *
+ * At present, we use C Protocol Buffers Language Parser which generated by protobuf_lang.y and protobuf_lang_scanner.l.
+ * Because wireshark is mainly implemented in plain ANSI C but the offical protobuf library is implemented in C++ language.
+ *
+ * One day, if C++ library is allowd, we can create a protobuf-helper.cpp file, that invoking offical protobuf C++ library directly,
+ * to replace protobuf-helper.c. The packet-protobuf.c can keep unchanged.
+ */
+
+#include "config.h"
+
+#include "protobuf-helper.h"
+#include "protobuf_lang_tree.h"
+
+VALUE_STRING_ARRAY_GLOBAL_DEF(protobuf_field_type);
+
+void
+pbw_reinit_DescriptorPool(PbwDescriptorPool** pool, const char** directories, pbw_report_error_cb_t error_cb)
+{
+ pbl_reinit_descriptor_pool((pbl_descriptor_pool_t**) pool, directories, (pbl_report_error_cb_t) error_cb);
+}
+
+/* load a proto file, return 0 if successed */
+int
+pbw_load_proto_file(PbwDescriptorPool* pool, const char* filename) {
+ if (pbl_add_proto_file_to_be_parsed((pbl_descriptor_pool_t*) pool, filename)) {
+ return run_pbl_parser((pbl_descriptor_pool_t*) pool, FALSE);
+ } else {
+ return 2;
+ }
+}
+
+/* like DescriptorPool::FindMethodByName */
+const PbwMethodDescriptor*
+pbw_DescriptorPool_FindMethodByName(const PbwDescriptorPool* pool, const char* name) {
+ return (const PbwMethodDescriptor*)pbl_message_descriptor_pool_FindMethodByName((pbl_descriptor_pool_t*) pool, name);
+}
+
+/* like MethodDescriptor::name() */
+const char*
+pbw_MethodDescriptor_name(const PbwMethodDescriptor* method) {
+ return pbl_method_descriptor_name((const pbl_method_descriptor_t*) method);
+}
+
+/* like MethodDescriptor::full_name() */
+const char*
+pbw_MethodDescriptor_full_name(const PbwMethodDescriptor* method) {
+ return pbl_method_descriptor_full_name((const pbl_method_descriptor_t*) method);
+}
+
+/* like MethodDescriptor::input_type() */
+const PbwDescriptor*
+pbw_MethodDescriptor_input_type(const PbwMethodDescriptor* method) {
+ return (const PbwDescriptor*)pbl_method_descriptor_input_type((const pbl_method_descriptor_t*) method);
+}
+
+/* like MethodDescriptor::output_type() */
+const PbwDescriptor*
+pbw_MethodDescriptor_output_type(const PbwMethodDescriptor* method) {
+ return (const PbwDescriptor*)pbl_method_descriptor_output_type((const pbl_method_descriptor_t*) method);
+}
+
+/* like DescriptorPool::FindMessageTypeByName() */
+const PbwDescriptor*
+pbw_DescriptorPool_FindMessageTypeByName(const PbwDescriptorPool* pool, const char* name) {
+ return (const PbwDescriptor*)pbl_message_descriptor_pool_FindMessageTypeByName((const pbl_descriptor_pool_t*) pool, name);
+}
+
+/* like Descriptor::full_name() */
+const char*
+pbw_Descriptor_full_name(const PbwDescriptor* message) {
+ return pbl_message_descriptor_full_name((const pbl_message_descriptor_t*) message);
+}
+
+/* like Descriptor::name() */
+const char*
+pbw_Descriptor_name(const PbwDescriptor* message) {
+ return pbl_message_descriptor_name((const pbl_message_descriptor_t*) message);
+}
+
+/* like Descriptor::field_count() */
+int
+pbw_Descriptor_field_count(const PbwDescriptor* message) {
+ return pbl_message_descriptor_field_count((const pbl_message_descriptor_t*) message);
+}
+
+/* like Descriptor::field() */
+const PbwFieldDescriptor*
+pbw_Descriptor_field(const PbwDescriptor* message, int field_index) {
+ return (const PbwFieldDescriptor*)pbl_message_descriptor_field((const pbl_message_descriptor_t*) message, field_index);
+}
+
+/* like Descriptor::FindFieldByNumber() */
+const PbwFieldDescriptor*
+pbw_Descriptor_FindFieldByNumber(const PbwDescriptor* message, int number) {
+ return (const PbwFieldDescriptor*)pbl_message_descriptor_FindFieldByNumber((const pbl_message_descriptor_t*) message, number);
+}
+
+/* like Descriptor::FindFieldByName() */
+const PbwFieldDescriptor*
+pbw_Descriptor_FindFieldByName(const PbwDescriptor* message, const char* name) {
+ return (const PbwFieldDescriptor*)pbl_message_descriptor_FindFieldByName((const pbl_message_descriptor_t*) message, name);
+}
+
+/* like FieldDescriptor::full_name() */
+const char*
+pbw_FieldDescriptor_full_name(const PbwFieldDescriptor* field) {
+ return pbl_field_descriptor_full_name((const pbl_field_descriptor_t*) field);
+}
+
+/* like FieldDescriptor::name() */
+const char*
+pbw_FieldDescriptor_name(const PbwFieldDescriptor* field) {
+ return pbl_field_descriptor_name((const pbl_field_descriptor_t*) field);
+}
+
+/* like FieldDescriptor::number() */
+int
+pbw_FieldDescriptor_number(const PbwFieldDescriptor* field) {
+ return pbl_field_descriptor_number((const pbl_field_descriptor_t*) field);
+}
+
+/* like FieldDescriptor::type() */
+int
+pbw_FieldDescriptor_type(const PbwFieldDescriptor* field) {
+ return pbl_field_descriptor_type((const pbl_field_descriptor_t*) field);
+}
+
+/* like FieldDescriptor::is_repeated() */
+int
+pbw_FieldDescriptor_is_repeated(const PbwFieldDescriptor* field) {
+ return pbl_field_descriptor_is_repeated((const pbl_field_descriptor_t*) field);;
+}
+
+/* like FieldDescriptor::is_packed() */
+int
+pbw_FieldDescriptor_is_packed(const PbwFieldDescriptor* field) {
+ return pbl_field_descriptor_is_packed((const pbl_field_descriptor_t*) field);;
+}
+
+/* like FieldDescriptor::typeName() */
+const char*
+pbw_FieldDescriptor_typeName(int field_type) {
+ return pbl_field_descriptor_TypeName(field_type);
+}
+
+/* like FieldDescriptor::message_type() */
+const PbwDescriptor*
+pbw_FieldDescriptor_message_type(const PbwFieldDescriptor* field) {
+ return (const PbwDescriptor*)pbl_field_descriptor_message_type((const pbl_field_descriptor_t*) field);
+}
+
+/* like FieldDescriptor::enum_type() */
+const PbwEnumDescriptor*
+pbw_FieldDescriptor_enum_type(const PbwFieldDescriptor* field) {
+ return (const PbwEnumDescriptor*)pbl_field_descriptor_enum_type((const pbl_field_descriptor_t*) field);
+}
+
+/* like EnumDescriptor::name() */
+const char*
+pbw_EnumDescriptor_name(const PbwEnumDescriptor* anEnum) {
+ return pbl_enum_descriptor_name((const pbl_enum_descriptor_t*) anEnum);
+}
+
+/* like EnumDescriptor::full_name() */
+const char*
+pbw_EnumDescriptor_full_name(const PbwEnumDescriptor* anEnum) {
+ return pbl_enum_descriptor_full_name((const pbl_enum_descriptor_t*) anEnum);
+}
+
+/* like EnumDescriptor::FindValueByNumber() */
+const PbwEnumValueDescriptor*
+pbw_EnumDescriptor_FindValueByNumber(const PbwEnumDescriptor* anEnum, int number) {
+ return (const PbwEnumValueDescriptor*)pbl_enum_descriptor_FindValueByNumber((const pbl_enum_descriptor_t*) anEnum, number);
+}
+
+/* like EnumValueDescriptor::name() */
+const char*
+pbw_EnumValueDescriptor_name(const PbwEnumValueDescriptor* enumValue) {
+ return pbl_enum_value_descriptor_name((const pbl_enum_value_descriptor_t*) enumValue);
+}
+
+/* like EnumValueDescriptor::full_name() */
+const char*
+pbw_EnumValueDescriptor_full_name(const PbwEnumValueDescriptor* enumValue) {
+ return pbl_enum_value_descriptor_full_name((const pbl_enum_value_descriptor_t*) enumValue);
+}
+
+/*
+ * 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:
+ */
diff --git a/epan/protobuf-helper.h b/epan/protobuf-helper.h
new file mode 100644
index 0000000000..3dada793eb
--- /dev/null
+++ b/epan/protobuf-helper.h
@@ -0,0 +1,209 @@
+/* protobuf-helper.h
+ *
+ * C Wrapper Layer of Protocol Buffers Language library.
+ * Copyright 2019, 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 packet-protobuf dissector needs get information from *.proto files for dissecting
+ * protobuf packet correctly. The information includes:
+ * - The names of MESSAGE, ENUM, FIELD, ENUM_VALUE;
+ * - The data type of FIELD which assuring the value of protobuf field of packet can be dissected correctly.
+ *
+ * At present, we use C Protocol Buffers Language Parser which generated by protobuf_lang.y and protobuf_lang_scanner.l.
+ * Because wireshark is mainly implemented in plain ANSI C but the offical protobuf library is implemented in C++ language.
+ *
+ * One day, if C++ library is allowd, we can create a protobuf-helper.cpp file, that invoking offical protobuf C++ library directly,
+ * to replace protobuf-helper.c. The packet-protobuf.c can keep unchanged.
+ */
+
+#ifndef __PROTOBUF_HELPER_H__
+#define __PROTOBUF_HELPER_H__
+
+#include <epan/value_string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Protobuf field type. Must be kept in sync with FieldType of protobuf wire_format_lite.h */
+#define protobuf_field_type_VALUE_STRING_LIST(XXX) \
+ XXX(PROTOBUF_TYPE_NONE, 0, "") \
+ XXX(PROTOBUF_TYPE_DOUBLE, 1, "double") \
+ XXX(PROTOBUF_TYPE_FLOAT, 2, "float") \
+ XXX(PROTOBUF_TYPE_INT64, 3, "int64") \
+ XXX(PROTOBUF_TYPE_UINT64, 4, "uint64") \
+ XXX(PROTOBUF_TYPE_INT32, 5, "int32") \
+ XXX(PROTOBUF_TYPE_FIXED64, 6, "fixed64") \
+ XXX(PROTOBUF_TYPE_FIXED32, 7, "fixed32") \
+ XXX(PROTOBUF_TYPE_BOOL, 8, "bool") \
+ XXX(PROTOBUF_TYPE_STRING, 9, "string") \
+ XXX(PROTOBUF_TYPE_GROUP, 10, "group") \
+ XXX(PROTOBUF_TYPE_MESSAGE, 11, "message") \
+ XXX(PROTOBUF_TYPE_BYTES, 12, "bytes") \
+ XXX(PROTOBUF_TYPE_UINT32, 13, "uint32") \
+ XXX(PROTOBUF_TYPE_ENUM, 14, "enum") \
+ XXX(PROTOBUF_TYPE_SFIXED32, 15, "sfixed32") \
+ XXX(PROTOBUF_TYPE_SFIXED64, 16, "sfixed64") \
+ XXX(PROTOBUF_TYPE_SINT32, 17, "sint32") \
+ XXX(PROTOBUF_TYPE_SINT64, 18, "sint64")
+
+#define PROTOBUF_MAX_FIELD_TYPE 18
+
+VALUE_STRING_ENUM(protobuf_field_type);
+VALUE_STRING_ARRAY_GLOBAL_DCL(protobuf_field_type);
+
+/* like google::protobuf::DescriptorPool of protobuf cpp library */
+typedef struct PbwDescriptorPool PbwDescriptorPool;
+/* like google::protobuf::MethodDescriptor of protobuf cpp library */
+typedef struct PbwMethodDescriptor PbwMethodDescriptor;
+/* like google::protobuf::Descriptor of protobuf cpp library */
+typedef struct PbwDescriptor PbwDescriptor;
+/* like google::protobuf::FieldDescriptor of protobuf cpp library */
+typedef struct PbwFieldDescriptor PbwFieldDescriptor;
+/* like google::protobuf::EnumDescriptor of protobuf cpp library */
+typedef struct PbwEnumDescriptor PbwEnumDescriptor;
+/* like google::protobuf::EnumValueDescriptor of protobuf cpp library */
+typedef struct PbwEnumValueDescriptor PbwEnumValueDescriptor;
+
+typedef void(*pbw_report_error_cb_t)(const char *msg_format, ...);
+
+/**
+ Reinitialize PbwDescriptorPool according to proto files directories.
+ @param pool The output DescriptorPool will be created. If *pool is not NULL, it will free it first.
+ @param directories The root directories containing proto files. Must end with NULL element.
+ @param error_cb The error reporter callback function. */
+void
+pbw_reinit_DescriptorPool(PbwDescriptorPool** pool, const char** directories, pbw_report_error_cb_t error_cb);
+
+/* load a proto file, return 0 if successed */
+int
+pbw_load_proto_file(PbwDescriptorPool* pool, const char* filename);
+
+/* like DescriptorPool::FindMethodByName */
+const PbwMethodDescriptor*
+pbw_DescriptorPool_FindMethodByName(const PbwDescriptorPool* pool, const char* name);
+
+/* like MethodDescriptor::name() */
+const char*
+pbw_MethodDescriptor_name(const PbwMethodDescriptor* method);
+
+/* like MethodDescriptor::full_name() */
+const char*
+pbw_MethodDescriptor_full_name(const PbwMethodDescriptor* method);
+
+/* like MethodDescriptor::input_type() */
+const PbwDescriptor*
+pbw_MethodDescriptor_input_type(const PbwMethodDescriptor* method);
+
+/* like MethodDescriptor::output_type() */
+const PbwDescriptor*
+pbw_MethodDescriptor_output_type(const PbwMethodDescriptor* method);
+
+/* like DescriptorPool::FindMessageTypeByName() */
+const PbwDescriptor*
+pbw_DescriptorPool_FindMessageTypeByName(const PbwDescriptorPool* pool, const char* name);
+
+/* like Descriptor::name() */
+const char*
+pbw_Descriptor_name(const PbwDescriptor* message);
+
+/* like Descriptor::full_name() */
+const char*
+pbw_Descriptor_full_name(const PbwDescriptor* message);
+
+/* like Descriptor::field_count() */
+int
+pbw_Descriptor_field_count(const PbwDescriptor* message);
+
+/* like Descriptor::field() */
+const PbwFieldDescriptor*
+pbw_Descriptor_field(const PbwDescriptor* message, int field_index);
+
+/* like Descriptor::FindFieldByNumber() */
+const PbwFieldDescriptor*
+pbw_Descriptor_FindFieldByNumber(const PbwDescriptor* message, int number);
+
+/* like Descriptor::FindFieldByName() */
+const PbwFieldDescriptor*
+pbw_Descriptor_FindFieldByName(const PbwDescriptor* message, const char* name);
+
+/* like FieldDescriptor::full_name() */
+const char*
+pbw_FieldDescriptor_full_name(const PbwFieldDescriptor* field);
+
+/* like FieldDescriptor::name() */
+const char*
+pbw_FieldDescriptor_name(const PbwFieldDescriptor* field);
+
+/* like FieldDescriptor::number() */
+int
+pbw_FieldDescriptor_number(const PbwFieldDescriptor* field);
+
+/* like FieldDescriptor::type() */
+int
+pbw_FieldDescriptor_type(const PbwFieldDescriptor* field);
+
+/* like FieldDescriptor::is_repeated() */
+int
+pbw_FieldDescriptor_is_repeated(const PbwFieldDescriptor* field);
+
+/* like FieldDescriptor::is_packed() */
+int
+pbw_FieldDescriptor_is_packed(const PbwFieldDescriptor* field);
+
+/* like FieldDescriptor::typeName() */
+const char*
+pbw_FieldDescriptor_typeName(int field_type);
+
+/* like FieldDescriptor::message_type() */
+const PbwDescriptor*
+pbw_FieldDescriptor_message_type(const PbwFieldDescriptor* field);
+
+/* like FieldDescriptor::enum_type() */
+const PbwEnumDescriptor*
+pbw_FieldDescriptor_enum_type(const PbwFieldDescriptor* field);
+
+/* like EnumDescriptor::name() */
+const char*
+pbw_EnumDescriptor_name(const PbwEnumDescriptor* anEnum);
+
+/* like EnumDescriptor::full_name() */
+const char*
+pbw_EnumDescriptor_full_name(const PbwEnumDescriptor* anEnum);
+
+/* like EnumDescriptor::FindValueByNumber() */
+const PbwEnumValueDescriptor*
+pbw_EnumDescriptor_FindValueByNumber(const PbwEnumDescriptor* anEnum, int number);
+
+/* like EnumValueDescriptor::name() */
+const char*
+pbw_EnumValueDescriptor_name(const PbwEnumValueDescriptor* enumValue);
+
+/* like EnumValueDescriptor::full_name() */
+const char*
+pbw_EnumValueDescriptor_full_name(const PbwEnumValueDescriptor* enumValue);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __PROTOBUF_HELPER_H__ */
+
+/*
+ * 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:
+ */
diff --git a/epan/protobuf_lang.y b/epan/protobuf_lang.y
new file mode 100644
index 0000000000..77fdf2f5de
--- /dev/null
+++ b/epan/protobuf_lang.y
@@ -0,0 +1,621 @@
+/*
+ * We want a reentrant parser.
+ */
+%pure-parser
+
+/*
+ * We also want a reentrant scanner, so we have to pass the
+ * handle for the reentrant scanner to the parser, and the
+ * parser has to pass it to the lexical analyzer.
+ *
+ * We use void * rather than yyscan_t because, at least with some
+ * versions of Flex and Bison, if you use yyscan_t in %parse-param and
+ * %lex-param, you have to include the protobuf_lang_scanner_lex.h before
+ * protobuf_lang.h to get yyscan_t declared, and you have to include protobuf_lang.h
+ * before protobuf_lang_scanner_lex.h to get YYSTYPE declared. Using void *
+ * breaks the cycle; the Flex documentation says yyscan_t is just a void *.
+ */
+%parse-param {void *yyscanner}
+%lex-param {void *yyscanner}
+
+/*
+ * And we need to pass the parser/scanner state to the parser.
+ */
+%parse-param {protobuf_lang_state_t *state}
+
+%{
+/* protobuf_lang.y
+ *
+ * C Protocol Buffers Language (PBL) Parser (for *.proto files)
+ * Copyright 2019, Huang Qiangxiong <qiangxiong.huang@qq.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/* This parser is mainly to get MESSAGE, ENUM, and FIELD information from *.proto files.
+ * There are two formats of *.proto files:
+ * 1) Protocol Buffers Version 3 Language Specification:
+ * https://developers.google.com/protocol-buffers/docs/reference/proto3-spec
+ * 2) Protocol Buffers Version 2 Language Specification:
+ * https://developers.google.com/protocol-buffers/docs/reference/proto2-spec
+ * There are some errors about 'proto', 'option' (value) and 'reserved' (fieldName) definitions on the site.
+ * This parser is created because Wireshark is mainly implemented in plain ANSI C but the offical
+ * Protocol Buffers Language parser is implemented in C++.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <glib.h>
+#include <wsutil/file_util.h>
+#include "protobuf_lang_tree.h"
+DIAG_OFF_BYACC
+#include "protobuf_lang.h"
+#include "protobuf_lang_scanner_lex.h"
+DIAG_ON_BYACC
+
+#define NAME_TO_BE_SET "<NAME_TO_BE_SET>"
+#define NEED_NOT_NAME "<NEED_NOT_NAME>"
+
+/* Error handling function for bison */
+void protobuf_langerror(void* yyscanner, protobuf_lang_state_t *state, const char *msg);
+
+/* Extended error handling function */
+void protobuf_langerrorv(void* yyscanner, protobuf_lang_state_t *state, const char *fmt, ...);
+
+DIAG_OFF_BYACC
+%}
+
+%debug
+%expect 23 /* suppress the warning about these conflicts */
+
+%union {
+ char* sval;
+ pbl_node_t* node;
+ int ival;
+};
+
+/* operations or symbols tokens */
+%token PT_QUOTE PT_LPAREN PT_RPAREN PT_LBRACKET PT_RBRACKET PT_LCURLY PT_RCURLY PT_EQUAL PT_NOTEQUAL PT_NOTEQUAL2
+%token PT_GEQUAL PT_LEQUAL PT_ASSIGN_PLUS PT_ASSIGN PT_PLUS PT_MINUS PT_MULTIPLY PT_DIV PT_LOGIC_OR PT_OR PT_LOGIC_AND
+%token PT_AND PT_NOT PT_NEG PT_XOR PT_SHL PT_SHR PT_PERCENT PT_DOLLAR PT_COND PT_SEMICOLON PT_DOT PT_COMMA PT_COLON PT_LESS PT_GREATER
+
+/* key words tokens */
+%right <sval> PT_SYNTAX PT_IMPORT PT_WEAK PT_PUBLIC PT_PACKAGE PT_OPTION PT_REQUIRED PT_OPTIONAL
+%right <sval> PT_REPEATED PT_ONEOF PT_MAP PT_RESERVED PT_ENUM PT_GROUP PT_EXTEND PT_EXTENSIONS
+%right <sval> PT_MESSAGE PT_SERVICE PT_RPC PT_STREAM PT_RETURNS PT_TO PT_PROTO2 PT_PROTO3 PT_IDENT PT_STRLIT
+
+%token <ival> PT_DECIMALLIT PT_OCTALLIT PT_HEXLIT
+
+%type <sval> optionName label type keyType messageName enumName
+%type <sval> streamName fieldName oneofName mapName serviceName rpcName messageType
+%type <sval> groupName constant exIdent strLit
+
+%type <node> protoBody topLevelDef message messageBody rpc rpcDecl field oneofField
+%type <node> enum enumBody enumField service serviceBody stream streamDecl
+%type <node> fieldOptions fieldOption oneof oneofBody mapField group extend extendBody
+
+%type <ival> intLit fieldNumber
+
+/* We don't care about following nodes:
+syntax import package option enumValueOptions enumValueOption rpcBody streamBody
+extensions reserved ranges range quoteFieldNames emptyStatement
+*/
+
+%start proto
+
+%%
+
+/* v2/v3: proto = syntax { import | package | option | topLevelDef | emptyStatement } */
+/* Offical PBL bugfix: proto = { syntax } { import | package | option | topLevelDef | emptyStatement }
+ The default syntax version is "proto2". */
+proto:
+ syntax wholeProtoBody
+| wholeProtoBody
+;
+
+wholeProtoBody: protoBody
+ {
+ /* set real package name */
+ pbl_set_node_name($1, state->file->package_name);
+ /* use the allocate mem of the name of the package node */
+ state->file->package_name = pbl_get_node_name($1);
+ /* put this file data into package tables */
+ pbl_node_t* packnode = (pbl_node_t*)g_hash_table_lookup(state->pool->packages, state->file->package_name);
+ if (packnode) {
+ pbl_merge_children(packnode, $1);
+ pbl_free_node($1);
+ } else {
+ g_hash_table_insert(state->pool->packages, g_strdup(state->file->package_name), $1);
+ }
+ }
+;
+
+/* v2: syntax = "syntax" "=" quote "proto2" quote ";" */
+/* v3: syntax = "syntax" "=" quote "proto3" quote ";" */
+syntax:
+ PT_SYNTAX PT_ASSIGN PT_PROTO2 PT_SEMICOLON { state->file->syntax_version = 2; }
+| PT_SYNTAX PT_ASSIGN PT_PROTO3 PT_SEMICOLON { state->file->syntax_version = 3; }
+;
+
+protoBody:
+ /* empty */ { $$ = pbl_create_node(state->file, PBL_PACKAGE, NAME_TO_BE_SET); } /* create am empty package node */
+| protoBody import /* default action is { $$ = $1; } */
+| protoBody package
+| protoBody option
+| protoBody topLevelDef { $$ = $1; pbl_add_child($$, $2); }
+| protoBody emptyStatement
+;
+
+/* v2/v3: import = "import" [ "weak" | "public" ] strLit ";" */
+import:
+ PT_IMPORT strLit PT_SEMICOLON { pbl_add_proto_file_to_be_parsed(state->pool, $2); } /* append file to todo list */
+| PT_IMPORT PT_PUBLIC strLit PT_SEMICOLON { pbl_add_proto_file_to_be_parsed(state->pool, $3); }
+| PT_IMPORT PT_WEAK strLit PT_SEMICOLON { pbl_add_proto_file_to_be_parsed(state->pool, $3); }
+;
+
+/* v2/v3: package = "package" fullIdent ";" */
+package: PT_PACKAGE exIdent PT_SEMICOLON
+ { /* The memory of $2 will be freed after parsing, but the package_name will
+ be replaced by the new-allocated name of package node late */
+ state->file->package_name = $2;
+ }
+;
+
+/* v2/v3: option = "option" optionName "=" constant ";" */
+/* Offical PBL bugfix: option = "option" optionName "=" ( constant | customOptionValue ) ";" */
+option:
+ PT_OPTION optionName PT_ASSIGN constant PT_SEMICOLON
+| PT_OPTION optionName PT_ASSIGN customOptionValue PT_SEMICOLON
+;
+
+/* v2/v3: optionName = ( ident | "(" fullIdent ")" ) { "." ident } */
+optionName:
+ exIdent
+| PT_LPAREN exIdent PT_RPAREN { $$ = pbl_store_string_token(state, g_strconcat("(", $2, ")", NULL)); }
+| PT_LPAREN exIdent PT_RPAREN exIdent { $$ = pbl_store_string_token(state, g_strconcat("(", $2, ")", $4, NULL)); } /* exIdent contains "." */
+;
+
+/* Allow format which not defined in offical PBL specification like:
+ option (google.api.http) = { post: "/v3alpha/kv/put" body: "*" };
+ option (google.api.http) = { post: "/v3alpha/kv/put", body: "*" };
+ option (google.api.http) = { post: "/v3alpha/kv/put" { any format } body: "*" };
+*/
+customOptionValue: PT_LCURLY customOptionBody PT_RCURLY
+;
+
+customOptionBody:
+ /* empty */
+| customOptionBody exIdent
+| customOptionBody strLit
+| customOptionBody symbolsWithoutCurly
+| customOptionBody intLit
+| customOptionBody customOptionValue
+;
+
+symbolsWithoutCurly:
+ PT_LPAREN | PT_RPAREN | PT_LBRACKET | PT_RBRACKET | PT_EQUAL | PT_NOTEQUAL | PT_NOTEQUAL2 | PT_GEQUAL
+| PT_LEQUAL | PT_ASSIGN_PLUS | PT_ASSIGN | PT_PLUS | PT_MINUS | PT_MULTIPLY | PT_DIV | PT_LOGIC_OR | PT_OR
+| PT_LOGIC_AND | PT_AND | PT_NOT | PT_NEG | PT_XOR | PT_SHL | PT_SHR | PT_PERCENT | PT_DOLLAR | PT_COND
+| PT_SEMICOLON | PT_DOT | PT_COMMA | PT_COLON | PT_LESS | PT_GREATER
+;
+
+/* v2: topLevelDef = message | enum | extend | service */
+/* v3: topLevelDef = message | enum | service */
+topLevelDef:
+ message
+| enum
+| extend /*v2 only */
+| service
+;
+
+/* v2/v3: message = "message" messageName messageBody */
+message: PT_MESSAGE messageName PT_LCURLY messageBody PT_RCURLY { $$ = $4; pbl_set_node_name($$, $2); }
+;
+
+/* v2: messageBody = "{" { field | enum | message | extend | extensions | group | option | oneof | mapField | reserved | emptyStatement } "}" */
+/* v3: messageBody = "{" { field | enum | message | option | oneof | mapField | reserved | emptyStatement } "}" */
+messageBody:
+ /* empty */ { $$ = pbl_create_node(state->file, PBL_MESSAGE, NAME_TO_BE_SET); }
+| messageBody field { $$ = $1; pbl_add_child($$, $2); }
+| messageBody enum { $$ = $1; pbl_add_child($$, $2); }
+| messageBody message { $$ = $1; pbl_add_child($$, $2); }
+| messageBody extend /* v2 only */
+| messageBody extensions /* v2 only */
+| messageBody group /* v2 only */ { $$ = $1; pbl_add_child($$, $2); }
+| messageBody option
+| messageBody oneof { $$ = $1; pbl_merge_children($$, $2); pbl_free_node($2); }
+| messageBody mapField { $$ = $1; pbl_add_child($$, $2); }
+| messageBody reserved
+| messageBody emptyStatement
+;
+
+/* v2/v3: enum = "enum" enumName enumBody */
+/* 1 2 3 4 5 */
+enum: PT_ENUM enumName PT_LCURLY enumBody PT_RCURLY { $$ = $4; pbl_set_node_name($$, $2); }
+;
+
+/* v2/v3: enumBody = "{" { option | enumField | emptyStatement } "}" */
+enumBody:
+ /* empty */ { $$ = pbl_create_node(state->file, PBL_ENUM, NAME_TO_BE_SET); }
+| enumBody option
+| enumBody enumField { $$ = $1; pbl_add_child($$, $2); }
+| enumBody emptyStatement
+;
+
+/* v2/v3: enumField = ident "=" intLit [ "[" enumValueOption { "," enumValueOption } "]" ]";" */
+enumField:
+ exIdent PT_ASSIGN intLit PT_LBRACKET enumValueOptions PT_RBRACKET PT_SEMICOLON
+ { $$ = pbl_create_enum_value_node(state->file, $1, $3); }
+| exIdent PT_ASSIGN intLit
+ { $$ = pbl_create_enum_value_node(state->file, $1, $3); }
+;
+
+/* v2/v3: enumValueOption { "," enumValueOption } */
+enumValueOptions:
+ enumValueOption
+| enumValueOptions PT_COMMA enumValueOption
+
+/* v2/v3: enumValueOption = optionName "=" constant */
+enumValueOption: optionName PT_ASSIGN constant
+;
+
+/* v2: service = "service" serviceName "{" { option | rpc | stream | emptyStatement } "}" */
+/* v3: service = "service" serviceName "{" { option | rpc | emptyStatement } "}" */
+service: PT_SERVICE serviceName PT_LCURLY serviceBody PT_RCURLY { $$ = $4; pbl_set_node_name($$, $2); }
+;
+
+serviceBody:
+ /* empty */ { $$ = pbl_create_node(state->file, PBL_SERVICE, NAME_TO_BE_SET); }
+| serviceBody option
+| serviceBody rpc { $$ = $1; pbl_add_child($$, $2); }
+| serviceBody emptyStatement
+| serviceBody stream /* v2 only */ { $$ = $1; pbl_add_child($$, $2); }
+;
+
+/* v2/v3: rpc = "rpc" rpcName "(" [ "stream" ] messageType ")" "returns" "(" [ "stream" ] messageType ")" (( "{" {option | emptyStatement } "}" ) | ";") */
+rpc:
+ rpcDecl PT_SEMICOLON
+| rpcDecl PT_LCURLY rpcBody PT_RCURLY
+;
+
+/* "rpc" rpcName "(" [ "stream" ] messageType ")" "returns" "(" [ "stream" ] messageType ")" */
+rpcDecl:
+/* 1 2 3 4 5 6 7 8 9 */
+ PT_RPC rpcName PT_LPAREN messageType PT_RPAREN PT_RETURNS PT_LPAREN messageType PT_RPAREN
+ { $$ = pbl_create_method_node(state->file, $2, $4, FALSE, $8, FALSE); }
+/* 1 2 3 4 5 6 7 8 9 10 */
+| PT_RPC rpcName PT_LPAREN PT_STREAM messageType PT_RPAREN PT_RETURNS PT_LPAREN messageType PT_RPAREN
+ { $$ = pbl_create_method_node(state->file, $2, $5, TRUE, $9, FALSE); }
+/* 1 2 3 4 5 6 7 8 9 10 */
+| PT_RPC rpcName PT_LPAREN messageType PT_RPAREN PT_RETURNS PT_LPAREN PT_STREAM messageType PT_RPAREN
+ { $$ = pbl_create_method_node(state->file, $2, $4, FALSE, $9, TRUE); }
+/* 1 2 3 4 5 6 7 8 9 10 11 */
+| PT_RPC rpcName PT_LPAREN PT_STREAM messageType PT_RPAREN PT_RETURNS PT_LPAREN PT_STREAM messageType PT_RPAREN
+ { $$ = pbl_create_method_node(state->file, $2, $5, TRUE, $10, TRUE); }
+;
+
+rpcBody:
+ /* empty */
+| rpcBody option
+| rpcBody emptyStatement
+;
+
+/* v2: stream = "stream" streamName "(" messageType "," messageType ")" (( "{" { option | emptyStatement } "}") | ";" ) */
+stream:
+ streamDecl PT_SEMICOLON
+| streamDecl PT_LCURLY streamBody PT_RCURLY
+;
+
+/* v2 only */
+/* 1 2 3 4 5 6 7 */
+streamDecl: PT_STREAM streamName PT_LPAREN messageType PT_COMMA messageType PT_RPAREN
+ { $$ = pbl_create_method_node(state->file, $2, $4, TRUE, $6, TRUE); }
+;
+
+/* v2 only */
+streamBody:
+ /* empty */
+| streamBody option
+| streamBody emptyStatement
+;
+
+/* v2: label type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" */
+/* v3: field = [ "repeated" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" */
+field:
+/* 1 2 3 4 5 */
+ type fieldName PT_ASSIGN fieldNumber PT_SEMICOLON
+ { $$ = pbl_create_field_node(state->file, NULL, $1, $2, $4, NULL); }
+/* 1 2 3 4 5 6 7 8 */
+| type fieldName PT_ASSIGN fieldNumber PT_LBRACKET fieldOptions PT_RBRACKET PT_SEMICOLON
+ { $$ = pbl_create_field_node(state->file, NULL, $1, $2, $4, $6); }
+/* 1 2 3 4 5 6 */
+| label type fieldName PT_ASSIGN fieldNumber PT_SEMICOLON
+ { $$ = pbl_create_field_node(state->file, $1, $2, $3, $5, NULL); }
+/* 1 2 3 4 5 6 7 8 9 */
+| label type fieldName PT_ASSIGN fieldNumber PT_LBRACKET fieldOptions PT_RBRACKET PT_SEMICOLON
+ { $$ = pbl_create_field_node(state->file, $1, $2, $3, $5, $7); }
+;
+
+/* v2: label = "required" | "optional" | "repeated" */
+label: PT_REQUIRED | PT_OPTIONAL | PT_REPEATED;
+
+/* v2/v3: type = "double" | "float" | "int32" | "int64" | "uint32" | "uint64"
+ | "sint32" | "sint64" | "fixed32" | "fixed64" | "sfixed32" | "sfixed64"
+ | "bool" | "string" | "bytes" | messageType | enumType
+*/
+type: exIdent;
+
+fieldNumber: intLit;
+
+/* v2/v3: fieldOptions = fieldOption { "," fieldOption } */
+fieldOptions:
+ fieldOption
+ { $$ = pbl_create_node(state->file, PBL_OPTIONS, NEED_NOT_NAME); pbl_add_child($$, $1); }
+| fieldOptions PT_COMMA fieldOption
+ { $$ = $1; pbl_add_child($$, $3); }
+;
+
+/* v2/v3: fieldOption = optionName "=" constant */
+fieldOption: optionName PT_ASSIGN constant
+ { $$ = pbl_create_option_node(state->file, $1, $3); }
+;
+
+/* v2 only: group = label "group" groupName "=" fieldNumber messageBody */
+/* 1 2 3 4 5 6 7 8 */
+group: label PT_GROUP groupName PT_ASSIGN fieldNumber PT_LCURLY messageBody PT_RCURLY
+ { $$ = $7; pbl_set_node_name($$, $3); }
+;
+
+groupName: exIdent;
+
+/* v2/v3: oneof = "oneof" oneofName "{" { oneofField | emptyStatement } "}" */
+/* 1 2 3 4 5 */
+oneof: PT_ONEOF oneofName PT_LCURLY oneofBody PT_RCURLY { $$ = $4; pbl_set_node_name($$, $2); }
+;
+
+oneofBody:
+ /* empty */ { $$ = pbl_create_node(state->file, PBL_ONEOF, NAME_TO_BE_SET); }
+| oneofBody oneofField { $$ = $1; pbl_add_child($$, $2); }
+| oneofBody emptyStatement
+;
+
+/* v2/v3: oneofField = type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" */
+oneofField:
+/* 1 2 3 4 5 6 7 8 */
+ type fieldName PT_ASSIGN fieldNumber PT_LBRACKET fieldOptions PT_RBRACKET PT_SEMICOLON
+ { $$ = pbl_create_field_node(state->file, NULL, $1, $2, $4, $6); }
+/* 1 2 3 4 5 */
+| type fieldName PT_ASSIGN fieldNumber PT_SEMICOLON
+ { $$ = pbl_create_field_node(state->file, NULL, $1, $2, $4, NULL); }
+;
+
+/* v2/v3: mapField = "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";" */
+mapField:
+/* 1 2 3 4 5 6 7 8 9 10 11 12 13 */
+ PT_MAP PT_LESS keyType PT_COMMA type PT_GREATER mapName PT_ASSIGN fieldNumber PT_LBRACKET fieldOptions PT_RBRACKET PT_SEMICOLON
+ {
+ $$ = pbl_create_map_field_node(state->file, $7, $9, $11);
+ pbl_add_child($$, pbl_create_field_node(state->file, NULL, $3, "key", 1, NULL)); /* add key field */
+ pbl_add_child($$, pbl_create_field_node(state->file, NULL, $5, "value", 2, NULL)); /* add value field */
+ }
+/* 1 2 3 4 5 6 7 8 9 10 */
+| PT_MAP PT_LESS keyType PT_COMMA type PT_GREATER mapName PT_ASSIGN fieldNumber PT_SEMICOLON
+ {
+ $$ = pbl_create_map_field_node(state->file, $7, $9, NULL);
+ pbl_add_child($$, pbl_create_field_node(state->file, NULL, $3, "key", 1, NULL)); /* add key field */
+ pbl_add_child($$, pbl_create_field_node(state->file, NULL, $5, "value", 2, NULL)); /* add value field */
+ }
+;
+
+/* keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" |
+ "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string" */
+keyType: exIdent
+;
+
+/* v2 only: extensions = "extensions" ranges ";" */
+extensions: PT_EXTENSIONS ranges PT_SEMICOLON
+;
+
+/* v2/v3: reserved = "reserved" ( ranges | fieldNames ) ";" */
+reserved:
+ PT_RESERVED ranges PT_SEMICOLON
+| PT_RESERVED quoteFieldNames PT_SEMICOLON
+;
+
+/* v2/v3: ranges = range { "," range } */
+ranges:
+ range
+| ranges PT_COMMA range
+;
+
+/* v2/v3: range = intLit [ "to" ( intLit | "max" ) ] */
+range:
+ intLit
+| intLit PT_TO intLit
+| intLit PT_TO exIdent
+;
+
+/* v2/v3: fieldNames = fieldName { "," fieldName }
+Note: There is an error in BNF definition about reserved fieldName. It's strLit not ident.
+*/
+quoteFieldNames:
+ strLit
+| quoteFieldNames PT_COMMA strLit
+;
+
+/* v2 only: extend = "extend" messageType "{" {field | group | emptyStatement} "}" */
+/* 1 2 3 4 5 */
+extend: PT_EXTEND messageType PT_LCURLY extendBody PT_RCURLY
+ { $$ = $4; pbl_set_node_name($$, pbl_store_string_token(state, g_strconcat($2, "Extend", NULL))); }
+;
+
+/* v2 only */
+extendBody:
+ /* empty */ { $$ = pbl_create_node(state->file, PBL_MESSAGE, NAME_TO_BE_SET); }
+| extendBody field { $$ = $1; pbl_add_child($$, $2); }
+| extendBody group { $$ = $1; pbl_add_child($$, $2); }
+| extendBody emptyStatement
+;
+
+messageName: exIdent;
+enumName: exIdent;
+streamName: exIdent;
+fieldName: exIdent;
+oneofName: exIdent;
+mapName: exIdent;
+serviceName: exIdent;
+rpcName: exIdent;
+
+/* messageType = [ "." ] { ident "." } messageName */
+messageType: exIdent
+;
+
+/* enumType = [ "." ] { ident "." } enumName */
+/*enumType: exIdent*/
+;
+
+/* intLit = decimalLit | octalLit | hexLit */
+intLit: PT_DECIMALLIT | PT_OCTALLIT | PT_HEXLIT
+;
+
+/* emptyStatement = ";" */
+emptyStatement: PT_SEMICOLON;
+
+/* constant = fullIdent | ( [ "-" | "+" ] intLit ) | ( [ "-" | "+" ] floatLit ) | strLit | boolLit */
+constant: exIdent | strLit
+;
+
+exIdent: PT_IDENT
+| PT_SYNTAX | PT_IMPORT | PT_WEAK | PT_PUBLIC | PT_PACKAGE | PT_OPTION
+| PT_ONEOF | PT_MAP | PT_RESERVED | PT_ENUM | PT_GROUP | PT_EXTEND | PT_EXTENSIONS
+| PT_MESSAGE | PT_SERVICE | PT_RPC | PT_STREAM | PT_RETURNS | PT_TO | label
+;
+
+strLit: PT_STRLIT | PT_PROTO2 | PT_PROTO3
+;
+
+%%
+
+DIAG_ON_BYACC
+
+void
+protobuf_langerror(void* yyscanner, protobuf_lang_state_t *state, const char *msg)
+{
+ int lineno;
+ void(*error_cb)(const char *format, ...);
+ const char* filepath = (state && state->file) ?
+ state->file->filename : "UNKNOWN";
+
+ error_cb = (state && state->pool->error_cb) ?
+ state->pool->error_cb : pbl_printf;
+
+ lineno = yyscanner ? protobuf_langget_lineno(yyscanner) : -1;
+
+ if (lineno > -1) {
+ error_cb("Protobuf: Parsing file [%s:%d] faield: %s\n", filepath, lineno, msg);
+ } else {
+ error_cb("Protobuf: Parsing file [%s] faield: %s\n", filepath, msg);
+ }
+}
+
+void
+protobuf_langerrorv(void* yyscanner, protobuf_lang_state_t *state, const char *fmt, ...)
+{
+ char* msg;
+ va_list ap;
+ va_start(ap, fmt);
+ msg = g_strdup_vprintf(fmt, ap);
+ protobuf_langerror(yyscanner, state, msg);
+ va_end(ap);
+ g_free(msg);
+}
+
+static void
+pbl_clear_state(protobuf_lang_state_t *state)
+{
+ if (state == NULL) {
+ return;
+ }
+
+ state->pool = NULL;
+ state->file = NULL;
+
+ if (state->lex_string_tokens) {
+ g_slist_free_full(state->lex_string_tokens, g_free);
+ }
+ state->lex_string_tokens = NULL;
+}
+
+static void
+pbl_reinit_state(protobuf_lang_state_t *state, pbl_descriptor_pool_t* pool, const char* filepath)
+{
+ if (state == NULL) {
+ return;
+ }
+
+ state->pool = pool;
+ state->file = (pbl_file_descriptor_t*) g_hash_table_lookup(pool->proto_files, filepath);
+
+ if (state->lex_string_tokens) {
+ g_slist_free_full(state->lex_string_tokens, g_free);
+ }
+ state->lex_string_tokens = NULL;
+}
+
+int run_pbl_parser(pbl_descriptor_pool_t* pool, gboolean debug)
+{
+ protobuf_lang_state_t state = {0};
+ yyscan_t scanner;
+ GSList* it;
+ FILE * fp;
+ int status;
+ const char* filepath;
+
+ protobuf_langdebug = debug ? 1 : 0;
+
+ it = pool->proto_files_to_be_parsed;
+ while (it) {
+ filepath = (const char*) it->data;
+ /* reinit state and scanner */
+ pbl_reinit_state(&state, pool, filepath);
+ scanner = NULL;
+
+ /* Note that filepath is absolute path in proto_files */
+ fp = ws_fopen(filepath, "r");
+ if (fp == NULL) {
+ protobuf_langerrorv(NULL, &state, "File does not exists!");
+ pbl_clear_state(&state);
+ return -1;
+ }
+
+ status = protobuf_langlex_init(&scanner);
+ if (status != 0) {
+ protobuf_langerrorv(NULL, &state, "Initialize Protocol Buffers Languange scanner failed!\n");
+ fclose(fp);
+ pbl_clear_state(&state);
+ return status;
+ }
+
+ /* associate the parser state with the lexical analyzer state */
+ protobuf_langset_extra(&state, scanner);
+
+ protobuf_langrestart(fp, scanner);
+ status = protobuf_langparse(scanner, &state);
+ fclose(fp);
+ if (status != 0) {
+ protobuf_langerrorv(NULL, &state, "Parsing grammers failed!\n");
+ pbl_clear_state(&state);
+ return status;
+ }
+
+ /* remove the parsed file from list */
+ pool->proto_files_to_be_parsed = it = g_slist_delete_link(pool->proto_files_to_be_parsed, it);
+ }
+
+ return 0;
+}
+
+DIAG_OFF_BYACC
diff --git a/epan/protobuf_lang_scanner.l b/epan/protobuf_lang_scanner.l
new file mode 100644
index 0000000000..7efba58d70
--- /dev/null
+++ b/epan/protobuf_lang_scanner.l
@@ -0,0 +1,199 @@
+%top {
+/* Include this before everything else, for various large-file definitions */
+#include "config.h"
+}
+
+/*
+ * We want a reentrant scanner.
+ */
+%option reentrant
+
+/*
+ * We want to generate code that can be used by a reentrant parser
+ * generated by Bison or Berkeley YACC.
+ */
+%option bison-bridge
+
+/*
+ * We don't read interactively from the terminal.
+ */
+%option never-interactive
+
+/*
+ * We want to stop processing when we get to the end of the input.
+ */
+%option noyywrap
+
+/*
+ * The type for the state we keep for the scanner (and parser).
+ */
+%option extra-type="protobuf_lang_state_t *"
+
+/*
+ * Prefix scanner routines with "protobuf_lang" rather than "yy", so this scanner
+ * can coexist with other scanners.
+ */
+%option prefix="protobuf_lang"
+
+/*
+ * We have to override the memory allocators so that we don't get
+ * "unused argument" warnings from the yyscanner argument (which
+ * we don't use, as we have a global memory allocator).
+ *
+ * We provide, as macros, our own versions of the routines generated by Flex,
+ * which just call malloc()/realloc()/free() (as the Flex versions do),
+ * discarding the extra argument.
+ */
+%option noyyalloc
+%option noyyrealloc
+%option noyyfree
+%option yylineno
+%option noinput
+%option nounput
+
+%{
+/* protobuf_lang_scanner.l
+ *
+ * C Protocol Buffers Language Lexer (for *.proto files)
+ * Copyright 2019, Huang Qiangxiong <qiangxiong.huang@qq.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include "protobuf_lang_tree.h"
+#include "protobuf_lang.h"
+
+/*
+ * Disable diagnostics in the code generated by Flex.
+ */
+DIAG_OFF_FLEX
+
+/*
+ * Sleazy hack to suppress compiler warnings in yy_fatal_error().
+ */
+#define YY_EXIT_FAILURE ((void)yyscanner, 2)
+
+/*
+ * Macros for the allocators, to discard the extra argument.
+ */
+#define protobuf_langalloc(size, yyscanner) (void *)malloc(size)
+#define protobuf_langrealloc(ptr, size, yyscanner) (void *)realloc((char *)(ptr), (size))
+#define protobuf_langfree(ptr, yyscanner) free((char *)ptr)
+
+int old_status;
+
+/* error handling function defined in bison (*.y) file */
+extern void
+protobuf_langerrorv(void* yyscanner, protobuf_lang_state_t *state, const char *fmt, ...);
+
+/* duplicate the text and keep the pointer in parser state for freeing later automatically */
+static gchar*
+strdup_and_store(void* yyscanner, const char* text);
+
+%}
+
+%x COMMENT
+
+%%
+ /* operations or symbols (PT_ means PBL Token) */
+\" return PT_QUOTE;
+"(" return PT_LPAREN;
+")" return PT_RPAREN;
+"[" return PT_LBRACKET;
+"]" return PT_RBRACKET;
+"{" return PT_LCURLY;
+"}" return PT_RCURLY;
+"==" return PT_EQUAL;
+"!=" return PT_NOTEQUAL;
+"<>" return PT_NOTEQUAL2;
+">=" return PT_GEQUAL;
+"<=" return PT_LEQUAL;
+"+=" return PT_ASSIGN_PLUS;
+"=" return PT_ASSIGN;
+"+" return PT_PLUS;
+"-" return PT_MINUS;
+"*" return PT_MULTIPLY;
+"/" return PT_DIV;
+"||" return PT_LOGIC_OR;
+"|" return PT_OR;
+"&&" return PT_LOGIC_AND;
+"&" return PT_AND;
+"!" return PT_NOT;
+"~" return PT_NEG;
+"^" return PT_XOR;
+"<<" return PT_SHL;
+">>" return PT_SHR;
+"%" return PT_PERCENT;
+"$" return PT_DOLLAR;
+"?" return PT_COND;
+";" return PT_SEMICOLON;
+"." return PT_DOT;
+"," return PT_COMMA;
+":" return PT_COLON;
+"<" return PT_LESS;
+">" return PT_GREATER;
+
+ /* key words */
+syntax yylval->sval = strdup_and_store(yyscanner, yytext); return PT_SYNTAX;
+import yylval->sval = strdup_and_store(yyscanner, yytext); return PT_IMPORT;
+weak yylval->sval = strdup_and_store(yyscanner, yytext); return PT_WEAK;
+public yylval->sval = strdup_and_store(yyscanner, yytext); return PT_PUBLIC;
+package yylval->sval = strdup_and_store(yyscanner, yytext); return PT_PACKAGE;
+option yylval->sval = strdup_and_store(yyscanner, yytext); return PT_OPTION;
+required yylval->sval = strdup_and_store(yyscanner, yytext); return PT_REQUIRED;
+optional yylval->sval = strdup_and_store(yyscanner, yytext); return PT_OPTIONAL;
+repeated yylval->sval = strdup_and_store(yyscanner, yytext); return PT_REPEATED;
+oneof yylval->sval = strdup_and_store(yyscanner, yytext); return PT_ONEOF;
+map yylval->sval = strdup_and_store(yyscanner, yytext); return PT_MAP;
+reserved yylval->sval = strdup_and_store(yyscanner, yytext); return PT_RESERVED;
+enum yylval->sval = strdup_and_store(yyscanner, yytext); return PT_ENUM;
+group yylval->sval = strdup_and_store(yyscanner, yytext); return PT_GROUP;
+extend yylval->sval = strdup_and_store(yyscanner, yytext); return PT_EXTEND;
+extensions yylval->sval = strdup_and_store(yyscanner, yytext); return PT_EXTENSIONS;
+message yylval->sval = strdup_and_store(yyscanner, yytext); return PT_MESSAGE;
+service yylval->sval = strdup_and_store(yyscanner, yytext); return PT_SERVICE;
+rpc yylval->sval = strdup_and_store(yyscanner, yytext); return PT_RPC;
+stream yylval->sval = strdup_and_store(yyscanner, yytext); return PT_STREAM;
+returns yylval->sval = strdup_and_store(yyscanner, yytext); return PT_RETURNS;
+to yylval->sval = strdup_and_store(yyscanner, yytext); return PT_TO;
+
+ /* key values */
+["']proto2["'] yylval->sval = strdup_and_store(yyscanner, yytext); return PT_PROTO2;
+["']proto3["'] yylval->sval = strdup_and_store(yyscanner, yytext); return PT_PROTO3;
+
+ /* intLit values */
+0|[1-9][0-9]* yylval->ival = atoi(yytext); return PT_DECIMALLIT;
+0[0-7]* sscanf(yytext+1, "%o", &yylval->ival); return PT_OCTALLIT;
+0[xX][0-9a-fA-F]+ sscanf(yytext+2, "%x", &yylval->ival); return PT_HEXLIT;
+
+ /* Using extended identifier because we care only about position */
+[a-zA-Z0-9_.+-]+ yylval->sval = strdup_and_store(yyscanner, yytext); return PT_IDENT;
+\"(\\.|\"\"|[^"\n"])*\" yylval->sval = g_strndup(yytext + 1, strlen(yytext) - 2); return PT_STRLIT;
+\'(\\.|\'\'|[^"\n"])*\' yylval->sval = g_strndup(yytext + 1, strlen(yytext) - 2); return PT_STRLIT;
+
+ /* comments */
+"//".*\n
+"/*" { old_status = YY_START; BEGIN COMMENT; }
+<COMMENT>"*/" { BEGIN old_status; }
+<COMMENT>([^*]|\n)+|.
+
+ /* space & tab */
+[ \t\n]
+ /* prevent flex jam */
+. { protobuf_langerrorv(yyscanner, protobuf_langget_extra(yyscanner), "unexpected token in proto file!\n"); }
+
+%%
+
+static gchar*
+strdup_and_store(void* yyscanner, const char* text) {
+ return pbl_store_string_token(protobuf_langget_extra(yyscanner), g_strdup(text));
+}
+
+/*
+ * Turn diagnostics back on, so we check the code that we've written.
+ */
+DIAG_ON_FLEX \ No newline at end of file
diff --git a/epan/protobuf_lang_tree.c b/epan/protobuf_lang_tree.c
new file mode 100644
index 0000000000..e51debe4c0
--- /dev/null
+++ b/epan/protobuf_lang_tree.c
@@ -0,0 +1,850 @@
+/* protobuf_lang_tree.c
+ *
+ * Routines of building and reading Protocol Buffers Language grammer tree.
+ * Copyright 2019, 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
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "protobuf_lang_tree.h"
+#include "protobuf-helper.h" /* only for PROTOBUF_TYPE_XXX enumeration */
+
+/**
+ Reinitialize the protocol buffers pool according to proto files directories.
+ @param ppool The output descriptor_pool will be created. If *pool is not NULL, it will free it first.
+ @param directories The root directories containing proto files. Must end with NULL element.
+ @param error_cb The error reporter callback function.
+ */
+void
+pbl_reinit_descriptor_pool(pbl_descriptor_pool_t** ppool, const char** directories, pbl_report_error_cb_t error_cb)
+{
+ guint i;
+
+ pbl_free_pool(*ppool);
+ pbl_descriptor_pool_t* p = g_new0(pbl_descriptor_pool_t, 1);
+
+ for (i = 0; directories[i] != NULL; i++) {
+ p->source_paths = g_slist_append(p->source_paths, g_strdup(directories[i]));
+ }
+
+ p->error_cb = error_cb ? error_cb : pbl_printf;
+ p->packages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, pbl_free_node);
+ p->proto_files = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ p->proto_files_to_be_parsed = NULL;
+
+ *ppool = p;
+}
+
+/* free all memory used by this protocol buffers languange pool */
+void
+pbl_free_pool(pbl_descriptor_pool_t* pool)
+{
+ if (pool == NULL) return;
+
+ g_slist_free_full(pool->source_paths, g_free);
+ g_hash_table_destroy(pool->packages);
+ g_slist_free(pool->proto_files_to_be_parsed); /* elements will be removed in p->proto_files */
+ g_hash_table_destroy(pool->proto_files);
+
+ g_free(pool);
+}
+
+/* Canonicalize absolute file path. We only accept path like:
+ * /home/test/protos/example.proto
+ * D:/mydir/test/example.proto
+ * d:\mydir\test\example.proto
+ * This function will replace all '\' to '/', and change '//' to '/'.
+ * May use _fullpath() in windows or realpath() in *NIX.
+ * Return a newly-allocated path, NULL if failed (file
+ * does not exist, or file path contains '/../').
+ */
+static char*
+pbl_canonicalize_absolute_filepath(const char* path)
+{
+ int i, j;
+ char* canon_path = g_new(char, strlen(path) + 1);
+ /* replace all '\' to '/', and change '//' to '/' */
+ for (i = 0, j = 0; path[i] != '\0'; i++) {
+ if (path[i] == '\\' || path[i] == '/') {
+ if (j > 0 && canon_path[j-1] == '/') {
+ /* ignore redundant slash */
+ } else {
+ canon_path[j++] = '/';
+ }
+ } else {
+#ifdef _WIN32
+ canon_path[j++] = g_ascii_tolower(path[i]);
+#else
+ canon_path[j++] = path[i];
+#endif
+ }
+ }
+ canon_path[j] = '\0';
+
+ if (g_path_is_absolute(canon_path)
+ && g_file_test(canon_path, G_FILE_TEST_IS_REGULAR)
+ && strstr(canon_path, "/../") == NULL) {
+ return canon_path;
+ } else {
+ g_free(canon_path);
+ return NULL;
+ }
+}
+
+/* Add a file into to do list */
+gboolean
+pbl_add_proto_file_to_be_parsed(pbl_descriptor_pool_t* pool, const char* filepath)
+{
+ char* path = NULL;
+ GSList* it = NULL;
+ char* concat_path = NULL;
+
+ /* Try to get the absolute path of the file */
+ if (g_path_is_absolute(filepath)) {
+ path = pbl_canonicalize_absolute_filepath(filepath);
+ }
+
+ if (path == NULL) {
+ /* try to concat with source directories */
+ for (it = pool->source_paths; it; it = it->next) {
+ concat_path = g_build_filename((char*)it->data, filepath, NULL);
+ path = pbl_canonicalize_absolute_filepath(concat_path);
+ g_free(concat_path);
+ if (path) break;
+ }
+ }
+
+ if (path == NULL) {
+ pool->error_cb("Protobuf: file %s does not exist!\n", filepath);
+ return FALSE;
+ }
+
+ if (!g_hash_table_lookup(pool->proto_files, path)) {
+ /* create file descriptor info */
+ pbl_file_descriptor_t* file = g_new0(pbl_file_descriptor_t, 1);
+ file->filename = path;
+ file->syntax_version = 2;
+ file->package_name = PBL_DEFAULT_PACKAGE_NAME;
+ file->pool = pool;
+
+ /* store in hash table and list */
+ g_hash_table_insert(pool->proto_files, path, file);
+ pool->proto_files_to_be_parsed = g_slist_append(pool->proto_files_to_be_parsed, path);
+ } else {
+ /* The file is already in the proto_files */
+ g_free(path);
+ }
+ return TRUE;
+}
+
+/* find node according to full_name */
+static pbl_node_t*
+pbl_find_node_in_pool(const pbl_descriptor_pool_t* pool, const char* full_name, pbl_node_type_t nodetype)
+{
+ char* full_name_buf;
+ int len, i;
+ pbl_node_t* package;
+ pbl_node_t* node = NULL;
+ GSList* names = NULL; /* NULL terminated name retrieved from full_name */
+ GSList* it = NULL;
+
+ if (pool == NULL || full_name == NULL || pool->packages == NULL) {
+ return NULL;
+ }
+
+ full_name_buf = g_strdup(full_name);
+ len = (int)strlen(full_name_buf);
+ /* scan from end to begin, and replace '.' to '\0' */
+ for (i = len-1; i >= 0; i--) {
+ if (full_name_buf[i] == '.' || i == 0) {
+ if (i == 0) {
+ /* no dot any more, we search in default package */
+ names = g_slist_prepend(names, full_name_buf);
+ package = (pbl_node_t*) g_hash_table_lookup(pool->packages, PBL_DEFAULT_PACKAGE_NAME);
+ } else { /* replace middle dot with '\0' */
+ /* push name at top of names */
+ names = g_slist_prepend(names, full_name_buf + i + 1);
+ full_name_buf[i] = 0;
+ /* take 0~i of full_name_buf as package name */
+ package = (pbl_node_t*) g_hash_table_lookup(pool->packages, full_name_buf);
+ }
+ if (package) {
+ node = package;
+ /* search node in this package */
+ for (it = names; (it && node && node->children_by_name); it = it->next) {
+ node = (pbl_node_t*) g_hash_table_lookup(node->children_by_name, it->data);
+ }
+
+ if (it == NULL && node && node->nodetype == nodetype) {
+ break; /* found */
+ }
+ node = NULL;
+ }
+ }
+ }
+
+ if (names) {
+ g_slist_free(names);
+ }
+ g_free(full_name_buf);
+ return node;
+}
+
+/* get the full name of node. if it is NULL, it will be built. */
+const char*
+pbl_get_node_full_name(pbl_node_t* node)
+{
+ const char* parent_full_name;
+ if (node == NULL
+ || node->nodetype == PBL_UNKNOWN
+ || node->nodetype == PBL_OPTIONS
+ || node->nodetype == PBL_OPTION) {
+ return NULL;
+ }
+
+ if (node->full_name) {
+ return node->full_name;
+ }
+
+ if (node->nodetype == PBL_ONEOF) {
+ return pbl_get_node_full_name(node->parent);
+ }
+
+ if (node->nodetype == PBL_PACKAGE) {
+ node->full_name = g_strdup(node->name);
+ } else {
+ parent_full_name = pbl_get_node_full_name(node->parent);
+ if (parent_full_name && parent_full_name[0] != 0) {
+ node->full_name = g_strconcat(parent_full_name, ".", node->name, NULL);
+ } else {
+ node->full_name = g_strdup(node->name);
+ }
+ }
+
+ return node->full_name;
+}
+
+/* try to find node globally or in the package of given context */
+static const pbl_node_t*
+pbl_find_node_in_context(const pbl_node_t* context, const char* name, pbl_node_type_t nodetype)
+{
+ pbl_node_t* package = NULL;
+ const pbl_node_t* node = NULL;
+ pbl_descriptor_pool_t* pool = NULL;
+ const char* pack_name;
+ char* full_name;
+
+ if (context == NULL || name == NULL) {
+ return NULL;
+ }
+
+ /* try find node in context first */
+ if (context->children_by_name) {
+ node = (pbl_node_t*) g_hash_table_lookup(context->children_by_name, name);
+ if (node && node->nodetype == nodetype) {
+ return node;
+ }
+ }
+
+ /* find package node */
+ for (node = context; node; node = node->parent) {
+ if (node->nodetype == PBL_PACKAGE) {
+ package = (pbl_node_t*)node;
+ break;
+ }
+ }
+ /* find pool */
+ if (context && context->file) {
+ pool = context->file->pool;
+ }
+
+ /* try find node in package */
+ if (package && pool) {
+ pack_name = pbl_get_node_full_name(package);
+ full_name = (pack_name[0] == 0) ? g_strdup(name) : g_strconcat(pack_name, ".", name, NULL);
+ node = pbl_find_node_in_pool(pool, full_name, nodetype);
+ g_free(full_name);
+ if (node) {
+ return node;
+ }
+ }
+
+ /* try find node in pool directly */
+ if (pool) {
+ return pbl_find_node_in_pool(pool, name, nodetype);
+ }
+
+ return NULL;
+}
+
+/* like descriptor_pool::FindMethodByName */
+const pbl_method_descriptor_t*
+pbl_message_descriptor_pool_FindMethodByName(const pbl_descriptor_pool_t* pool, const char* full_name)
+{
+ pbl_node_t* n = pbl_find_node_in_pool(pool, full_name, PBL_METHOD);
+ return n ? (pbl_method_descriptor_t*)n : NULL;
+}
+
+/* like MethodDescriptor::name() */
+const char*
+pbl_method_descriptor_name(const pbl_method_descriptor_t* method)
+{
+ return pbl_get_node_name((pbl_node_t*)method);
+}
+
+/* like MethodDescriptor::full_name() */
+const char*
+pbl_method_descriptor_full_name(const pbl_method_descriptor_t* method)
+{
+ return pbl_get_node_full_name((pbl_node_t*)method);
+}
+
+/* like MethodDescriptor::input_type() */
+const pbl_message_descriptor_t*
+pbl_method_descriptor_input_type(const pbl_method_descriptor_t* method)
+{
+ const pbl_node_t* n = pbl_find_node_in_context((pbl_node_t*)method, method->in_msg_type, PBL_MESSAGE);
+ return n ? (const pbl_message_descriptor_t*)n : NULL;
+}
+
+/* like MethodDescriptor::output_type() */
+const pbl_message_descriptor_t*
+pbl_method_descriptor_output_type(const pbl_method_descriptor_t* method)
+{
+ const pbl_node_t* n = pbl_find_node_in_context((pbl_node_t*)method, method->out_msg_type, PBL_MESSAGE);
+ return n ? (const pbl_message_descriptor_t*)n : NULL;
+}
+
+/* like descriptor_pool::FindMessageTypeByName() */
+const pbl_message_descriptor_t*
+pbl_message_descriptor_pool_FindMessageTypeByName(const pbl_descriptor_pool_t* pool, const char* name)
+{
+ pbl_node_t* n = pbl_find_node_in_pool(pool, name, PBL_MESSAGE);
+ return n ? (pbl_message_descriptor_t*)n : NULL;
+}
+
+/* like Descriptor::name() */
+const char*
+pbl_message_descriptor_name(const pbl_message_descriptor_t* message)
+{
+ return pbl_get_node_name((pbl_node_t*)message);
+}
+
+/* like Descriptor::full_name() */
+const char*
+pbl_message_descriptor_full_name(const pbl_message_descriptor_t* message)
+{
+ return pbl_get_node_full_name((pbl_node_t*)message);
+}
+
+/* like Descriptor::field_count() */
+int
+pbl_message_descriptor_field_count(const pbl_message_descriptor_t* message)
+{
+ return (message && message->fields) ? g_slist_length(message->fields) : 0;
+}
+
+/* like Descriptor::field() */
+const pbl_field_descriptor_t*
+pbl_message_descriptor_field(const pbl_message_descriptor_t* message, int field_index)
+{
+ return (message && message->fields) ? (pbl_field_descriptor_t*) g_slist_nth_data(message->fields, field_index) : NULL;
+}
+
+/* like Descriptor::FindFieldByNumber() */
+const pbl_field_descriptor_t*
+pbl_message_descriptor_FindFieldByNumber(const pbl_message_descriptor_t* message, int number)
+{
+ if (message && message->fields_by_number) {
+ return (pbl_field_descriptor_t*) g_hash_table_lookup(message->fields_by_number, GINT_TO_POINTER(number));
+ } else {
+ return NULL;
+ }
+}
+
+/* like Descriptor::FindFieldByName() */
+const pbl_field_descriptor_t*
+pbl_message_descriptor_FindFieldByName(const pbl_message_descriptor_t* message, const char* name)
+{
+ if (message && ((pbl_node_t*)message)->children_by_name) {
+ return (pbl_field_descriptor_t*) g_hash_table_lookup(((pbl_node_t*)message)->children_by_name, name);
+ } else {
+ return NULL;
+ }
+}
+
+/* like FieldDescriptor::full_name() */
+const char*
+pbl_field_descriptor_full_name(const pbl_field_descriptor_t* field)
+{
+ return pbl_get_node_full_name((pbl_node_t*)field);
+}
+
+/* like FieldDescriptor::name() */
+const char*
+pbl_field_descriptor_name(const pbl_field_descriptor_t* field)
+{
+ return pbl_get_node_name((pbl_node_t*)field);
+}
+
+/* like FieldDescriptor::number() */
+int
+pbl_field_descriptor_number(const pbl_field_descriptor_t* field)
+{
+ return GPOINTER_TO_INT(field->number);
+}
+
+/* like FieldDescriptor::type() */
+int
+pbl_field_descriptor_type(const pbl_field_descriptor_t* field)
+{
+ const pbl_node_t* node;
+ if (field->type == PROTOBUF_TYPE_NONE) {
+ /* try to lookup as ENUM */
+ node = pbl_find_node_in_context(((pbl_node_t*)field)->parent, field->type_name, PBL_ENUM);
+ if (node) {
+ ((pbl_field_descriptor_t*)field)->type = PROTOBUF_TYPE_ENUM;
+ } else {
+ /* try to lookup as MESSAGE */
+ node = pbl_find_node_in_context(((pbl_node_t*)field)->parent, field->type_name, PBL_MESSAGE);
+ if (node) {
+ ((pbl_field_descriptor_t*)field)->type = PROTOBUF_TYPE_MESSAGE;
+ }
+ }
+ }
+ return field->type;
+}
+
+/* like FieldDescriptor::is_repeated() */
+int
+pbl_field_descriptor_is_repeated(const pbl_field_descriptor_t* field)
+{
+ return field->is_repeated ? 1 : 0;
+}
+
+/* like FieldDescriptor::is_packed() */
+int
+pbl_field_descriptor_is_packed(const pbl_field_descriptor_t* field)
+{
+ gboolean has_packed_option;
+ gboolean packed_option_value;
+ int syntax_version = ((pbl_node_t*)field)->file->syntax_version;
+
+ /* determine packed flag */
+ if (field->is_repeated == FALSE) {
+ return FALSE;
+ }
+ /* note: field->type may be undetermined until calling pbl_field_descriptor_type() */
+ switch (pbl_field_descriptor_type(field)) {
+ case PROTOBUF_TYPE_STRING:
+ case PROTOBUF_TYPE_GROUP:
+ case PROTOBUF_TYPE_MESSAGE:
+ case PROTOBUF_TYPE_BYTES:
+ return FALSE;
+ default: /* only repeated fields of primitive numeric types can be declared "packed". */
+ has_packed_option = field->options_node
+ && field->options_node->children_by_name
+ && g_hash_table_lookup(field->options_node->children_by_name, "packed");
+
+ packed_option_value = (has_packed_option ?
+ g_strcmp0(
+ ((pbl_option_descriptor_t*)g_hash_table_lookup(
+ field->options_node->children_by_name, "packed"))->value, "true") == 0
+ : FALSE);
+
+ if (syntax_version == 2) {
+ return packed_option_value;
+ } else { /* packed default in syntax_version = 3 */
+ return has_packed_option ? packed_option_value : TRUE;
+ }
+ }
+}
+
+/* like FieldDescriptor::TypeName() */
+const char*
+pbl_field_descriptor_TypeName(int field_type)
+{
+ return val_to_str(field_type, protobuf_field_type, "UNKNOWN_FIELD_TYPE(%d)");
+}
+
+/* like FieldDescriptor::message_type() type = TYPE_MESSAGE or TYPE_GROUP */
+const pbl_message_descriptor_t*
+pbl_field_descriptor_message_type(const pbl_field_descriptor_t* field)
+{
+ const pbl_node_t* n;
+ if (field->type == PROTOBUF_TYPE_MESSAGE || field->type == PROTOBUF_TYPE_GROUP) {
+ n = pbl_find_node_in_context(((pbl_node_t*)field)->parent, field->type_name, PBL_MESSAGE);
+ return n ? (const pbl_message_descriptor_t*)n : NULL;
+ }
+ return NULL;
+}
+
+/* like FieldDescriptor::enum_type() type = TYPE_ENUM */
+const pbl_enum_descriptor_t*
+pbl_field_descriptor_enum_type(const pbl_field_descriptor_t* field)
+{
+ const pbl_node_t* n;
+ if (field->type == PROTOBUF_TYPE_ENUM) {
+ n = pbl_find_node_in_context(((pbl_node_t*)field)->parent, field->type_name, PBL_ENUM);
+ return n ? (const pbl_enum_descriptor_t*)n : NULL;
+ }
+ return NULL;
+}
+
+/* like EnumDescriptor::name() */
+const char*
+pbl_enum_descriptor_name(const pbl_enum_descriptor_t* anEnum)
+{
+ return pbl_get_node_name((pbl_node_t*)anEnum);
+}
+
+/* like EnumDescriptor::full_name() */
+const char*
+pbl_enum_descriptor_full_name(const pbl_enum_descriptor_t* anEnum)
+{
+ return pbl_get_node_full_name((pbl_node_t*)anEnum);
+}
+
+/* like EnumDescriptor::FindValueByNumber() */
+const pbl_enum_value_descriptor_t*
+pbl_enum_descriptor_FindValueByNumber(const pbl_enum_descriptor_t* anEnum, int number)
+{
+ if (anEnum && anEnum->values_by_number) {
+ return (pbl_enum_value_descriptor_t*) g_hash_table_lookup(anEnum->values_by_number, GINT_TO_POINTER(number));
+ } else {
+ return NULL;
+ }
+}
+
+/* like EnumValueDescriptor::name() */
+const char*
+pbl_enum_value_descriptor_name(const pbl_enum_value_descriptor_t* enumValue)
+{
+ return pbl_get_node_name((pbl_node_t*)enumValue);
+}
+
+/* like EnumValueDescriptor::full_name() */
+const char*
+pbl_enum_value_descriptor_full_name(const pbl_enum_value_descriptor_t* enumValue)
+{
+ return pbl_get_node_full_name((pbl_node_t*)enumValue);
+}
+
+/*
+ * Following are tree building functions that should only be invoked by protobuf_lang parser.
+ */
+
+static void
+pbl_init_node(pbl_node_t* node, pbl_file_descriptor_t* file, pbl_node_type_t nodetype, const char* name)
+{
+ node->nodetype = nodetype;
+ node->name = g_strdup(name);
+ node->file = file;
+}
+
+/* create a normal node */
+pbl_node_t*
+pbl_create_node(pbl_file_descriptor_t* file, pbl_node_type_t nodetype, const char* name)
+{
+ pbl_node_t* node = NULL;
+
+ switch (nodetype) {
+ case PBL_METHOD: /* should use pbl_create_method_node() */
+ case PBL_FIELD: /* should use pbl_create_field_node() */
+ case PBL_MAP_FIELD: /* should use pbl_create_map_field_node() */
+ case PBL_ENUM_VALUE: /* should use pbl_create_enum_value_node() */
+ case PBL_OPTION: /* should use pbl_create_option_node() */
+ return NULL;
+ case PBL_MESSAGE:
+ node = (pbl_node_t*) g_malloc0(sizeof(pbl_message_descriptor_t));
+ break;
+ case PBL_ENUM:
+ node = (pbl_node_t*) g_malloc0(sizeof(pbl_enum_descriptor_t));
+ break;
+ default:
+ node = g_new0(pbl_node_t, 1);
+ }
+ pbl_init_node(node, file, nodetype, name);
+ return node;
+}
+
+pbl_node_t*
+pbl_set_node_name(pbl_node_t* node, const char* newname)
+{
+ g_free(node->name);
+ node->name = g_strdup(newname);
+ return node;
+}
+
+/* create a method (rpc or stream of service) node */
+pbl_node_t* pbl_create_method_node(pbl_file_descriptor_t* file,
+ const char* name, const char* in_msg_type,
+ gboolean in_is_stream, const char* out_msg_type, gboolean out_is_stream)
+{
+ pbl_method_descriptor_t* node = g_new0(pbl_method_descriptor_t, 1);
+ pbl_init_node(&node->basic_info, file, PBL_METHOD, name);
+
+ node->in_msg_type = g_strdup(in_msg_type);
+ node->in_is_stream = in_is_stream;
+ node->out_msg_type = g_strdup(out_msg_type);
+ node->out_is_stream = out_is_stream;
+
+ return (pbl_node_t*)node;
+}
+
+/* Get type simple type enum value according to the type name.
+ Return 0 means undetermined. */
+static int
+pbl_get_simple_type_enum_value_by_typename(const char* type_name)
+{
+ int i = str_to_val(type_name, protobuf_field_type, 0);
+ if (i == PROTOBUF_TYPE_GROUP || i == PROTOBUF_TYPE_MESSAGE || i == PROTOBUF_TYPE_ENUM) {
+ i = PROTOBUF_TYPE_NONE; /* complex type will find after parsing */
+ }
+
+ return i;
+}
+
+/* create a field node */
+pbl_node_t* pbl_create_field_node(pbl_file_descriptor_t* file, const char* label,
+ const char* type_name, const char* name, int number, pbl_node_t* options)
+{
+ pbl_field_descriptor_t* node = g_new0(pbl_field_descriptor_t, 1);
+ pbl_init_node(&node->basic_info, file, PBL_FIELD, name);
+
+ node->number = number;
+ node->options_node = options;
+ node->is_repeated = (g_strcmp0(label, "repeated") == 0);
+ node->type_name = g_strdup(type_name);
+ /* type 0 means undetermined, it will be determined on
+ calling pbl_field_descriptor_type() later */
+ node->type = pbl_get_simple_type_enum_value_by_typename(type_name);
+
+ return (pbl_node_t*)node;
+}
+
+/* create a map field node */
+pbl_node_t* pbl_create_map_field_node(pbl_file_descriptor_t* file,
+ const char* name, int number, pbl_node_t* options)
+{
+ pbl_field_descriptor_t* node = g_new0(pbl_field_descriptor_t, 1);
+ pbl_init_node(&node->basic_info, file, PBL_MAP_FIELD, name);
+
+ node->number = number;
+ node->type_name = g_strconcat(name, "MapEntry", NULL);
+ node->type = PROTOBUF_TYPE_MESSAGE;
+ node->is_repeated = TRUE;
+ node->options_node = options;
+
+ return (pbl_node_t*)node;
+}
+
+/* create an enumeration field node */
+pbl_node_t*
+pbl_create_enum_value_node(pbl_file_descriptor_t* file, const char* name, int number)
+{
+ pbl_enum_value_descriptor_t* node = g_new0(pbl_enum_value_descriptor_t, 1);
+ pbl_init_node(&node->basic_info, file, PBL_ENUM_VALUE, name);
+
+ node->number = number;
+ return (pbl_node_t*)node;
+}
+
+/* create an option node */
+pbl_node_t* pbl_create_option_node(pbl_file_descriptor_t* file,
+ const char* name, const char* value)
+{
+ pbl_option_descriptor_t* node = g_new0(pbl_option_descriptor_t, 1);
+ pbl_init_node(&node->basic_info, file, PBL_OPTION, name);
+
+ if (value)
+ node->value = g_strdup(value);
+ return (pbl_node_t*)node;
+}
+
+/* add a node as a child of parent node, and return the parent pointer */
+pbl_node_t*
+pbl_add_child(pbl_node_t* parent, pbl_node_t* child)
+{
+ pbl_node_t* map_msg = NULL;
+ if (child == NULL || parent == NULL) {
+ return parent;
+ }
+
+ /* add a message node for mapField first */
+ if (child->nodetype == PBL_MAP_FIELD) {
+ map_msg = pbl_create_node(child->file, PBL_MESSAGE, ((pbl_field_descriptor_t*)child)->type_name);
+ pbl_merge_children(map_msg, child);
+ pbl_add_child(parent, map_msg);
+ }
+
+ child->parent = parent;
+
+ /* add child to children list */
+ parent->children = g_slist_append(parent->children, child);
+
+ /* add child to children_by_name table */
+ if (parent->children_by_name == NULL) {
+ parent->children_by_name = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+ }
+
+ if (g_hash_table_lookup(parent->children_by_name, child->name) && child->file && parent->file
+ && child->file->pool && child->file->pool->error_cb) {
+ child->file->pool->error_cb(
+ "Protobuf: Warning: \"%s\" of \"%s\" is already defined in file \"%s\".\n",
+ child->name, child->file->filename, parent->file->filename);
+ }
+
+ g_hash_table_insert(parent->children_by_name, child->name, child);
+
+ if (parent->nodetype == PBL_MESSAGE) {
+ pbl_message_descriptor_t* msg = (pbl_message_descriptor_t*) parent;
+ /* add child to fields_by_number table */
+ if (child->nodetype == PBL_FIELD || child->nodetype == PBL_MAP_FIELD) {
+ msg->fields = g_slist_append(msg->fields, child);
+ if (msg->fields_by_number == NULL) {
+ msg->fields_by_number = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
+ }
+ g_hash_table_insert(msg->fields_by_number,
+ GINT_TO_POINTER(((pbl_field_descriptor_t*)child)->number), child);
+ }
+ } else if (parent->nodetype == PBL_ENUM && child->nodetype == PBL_ENUM_VALUE) {
+ pbl_enum_descriptor_t* anEnum = (pbl_enum_descriptor_t*) parent;
+ /* add child to values_by_number table */
+ if (anEnum->values_by_number == NULL) {
+ anEnum->values_by_number = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
+ }
+ g_hash_table_insert(anEnum->values_by_number,
+ GINT_TO_POINTER(((pbl_enum_value_descriptor_t*)child)->number), child);
+ }
+
+ return parent;
+}
+
+/* merge one('from') node's children to another('to') node, and return the 'to' pointer */
+pbl_node_t*
+pbl_merge_children(pbl_node_t* to, pbl_node_t* from)
+{
+ GSList* it;
+ pbl_node_t* child;
+
+ if (to == NULL || from == NULL) {
+ return to;
+ }
+
+ if (from->children) {
+ for (it = from->children; it; it = it->next) {
+ child = (pbl_node_t*)it->data;
+ pbl_add_child(to, child);
+ }
+
+ g_slist_free(from->children);
+ from->children = NULL;
+ if (from->children_by_name) {
+ g_hash_table_destroy(from->children_by_name);
+ }
+ from->children_by_name = NULL;
+
+ if (from->nodetype == PBL_MESSAGE) {
+ pbl_message_descriptor_t* msg = (pbl_message_descriptor_t*) from;
+ if (msg->fields) {
+ g_slist_free(msg->fields);
+ msg->fields = NULL;
+ }
+ if (msg->fields_by_number) {
+ g_hash_table_destroy(msg->fields_by_number);
+ msg->fields_by_number = NULL;
+ }
+ } else if (from->nodetype == PBL_ENUM) {
+ pbl_enum_descriptor_t* anEnum = (pbl_enum_descriptor_t*) from;
+ if (anEnum->values_by_number) {
+ g_hash_table_destroy(anEnum->values_by_number);
+ anEnum->values_by_number = NULL;
+ }
+ }
+ }
+
+ return to;
+}
+
+/* free a pbl_node_t and its children. */
+void
+pbl_free_node(gpointer anode)
+{
+ pbl_method_descriptor_t* method_node;
+ pbl_message_descriptor_t* message_node;
+ pbl_field_descriptor_t* field_node;
+ pbl_enum_descriptor_t* enum_node;
+ pbl_option_descriptor_t* option_node;
+ pbl_node_t* node = (pbl_node_t*) anode;
+
+ if (node == NULL) return;
+
+ switch (node->nodetype) {
+ case PBL_METHOD:
+ method_node = (pbl_method_descriptor_t*) node;
+ g_free(method_node->in_msg_type);
+ g_free(method_node->out_msg_type);
+ break;
+ case PBL_MESSAGE:
+ message_node = (pbl_message_descriptor_t*) node;
+ if (message_node->fields) {
+ g_slist_free(message_node->fields);
+ }
+ if (message_node->fields_by_number) {
+ g_hash_table_destroy(message_node->fields_by_number);
+ }
+ break;
+ case PBL_FIELD:
+ case PBL_MAP_FIELD:
+ field_node = (pbl_field_descriptor_t*) node;
+ g_free(field_node->type_name);
+ if (field_node->options_node) {
+ pbl_free_node(field_node->options_node);
+ }
+ break;
+ case PBL_ENUM:
+ enum_node = (pbl_enum_descriptor_t*) node;
+ if (enum_node->values_by_number) {
+ g_hash_table_destroy(enum_node->values_by_number);
+ }
+ break;
+ case PBL_OPTION:
+ option_node = (pbl_option_descriptor_t*) node;
+ g_free(option_node->value);
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+
+ g_free(node->name);
+ g_free(node->full_name);
+ if (node->children) {
+ g_slist_free_full(node->children, pbl_free_node);
+ }
+ if (node->children_by_name) {
+ g_hash_table_destroy(node->children_by_name);
+ }
+}
+
+/*
+ * 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:
+ */
diff --git a/epan/protobuf_lang_tree.h b/epan/protobuf_lang_tree.h
new file mode 100644
index 0000000000..aee9ebcf02
--- /dev/null
+++ b/epan/protobuf_lang_tree.h
@@ -0,0 +1,340 @@
+/* protobuf_lang_tree.h
+ *
+ * Routines of building and reading Protocol Buffers Language grammer tree.
+ * Copyright 2019, 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
+ */
+
+#ifndef __PROTOBUF_LANG_TREE_H__
+#define __PROTOBUF_LANG_TREE_H__
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#define PBL_DEFAULT_PACKAGE_NAME ""
+
+typedef void(*pbl_report_error_cb_t)(const char *msg_format, ...);
+
+/* Node types of protocol buffers language */
+typedef enum {
+ PBL_UNKNOWN = 0,
+ PBL_PACKAGE,
+ PBL_MESSAGE,
+ PBL_FIELD,
+ PBL_ONEOF,
+ PBL_MAP_FIELD,
+ PBL_ENUM,
+ PBL_ENUM_VALUE,
+ PBL_SERVICE,
+ PBL_METHOD, /* contains the rpc and stream node of service */
+ PBL_OPTIONS,
+ PBL_OPTION
+} pbl_node_type_t;
+
+/* like google::protobuf::descriptor_pool of protobuf cpp library */
+typedef struct {
+ GSList* source_paths; /* the directories in which to search for proto file */
+ pbl_report_error_cb_t error_cb; /* error call back function */
+ GHashTable* packages; /* all packages parsed from proto files */
+ GHashTable* proto_files; /* all proto files that are parsed or to be parsed */
+ GSList* proto_files_to_be_parsed; /* files is to be parsed */
+} pbl_descriptor_pool_t;
+
+/* file descriptor */
+typedef struct {
+ const char* filename;
+ int syntax_version;
+ const char* package_name;
+ pbl_descriptor_pool_t* pool;
+} pbl_file_descriptor_t;
+
+/* Basic information of node */
+typedef struct pbl_node_t{
+ pbl_node_type_t nodetype;
+ gchar* name;
+ gchar* full_name; /* constructed during first access */
+ struct pbl_node_t* parent;
+ GSList* children; /* child is a pbl_node_t */
+ GHashTable* children_by_name; /* take children names as keys */
+ pbl_file_descriptor_t* file;
+} pbl_node_t;
+
+/* like google::protobuf::MethodDescriptor of protobuf cpp library */
+typedef struct {
+ pbl_node_t basic_info;
+ gchar* in_msg_type;
+ gboolean in_is_stream;
+ gchar* out_msg_type;
+ gboolean out_is_stream;
+} pbl_method_descriptor_t;
+
+/* like google::protobuf::Descriptor of protobuf cpp library */
+typedef struct {
+ pbl_node_t basic_info;
+ GSList* fields;
+ GHashTable* fields_by_number;
+} pbl_message_descriptor_t;
+
+/* like google::protobuf::FieldDescriptor of protobuf cpp library */
+typedef struct {
+ pbl_node_t basic_info;
+ int number;
+ int type; /* refer to PROTOBUF_TYPE_XXX of protobuf-helper.h */
+ gchar* type_name;
+ pbl_node_t* options_node;
+ gboolean is_repeated;
+} pbl_field_descriptor_t;
+
+/* like google::protobuf::EnumDescriptor of protobuf cpp library */
+typedef struct {
+ pbl_node_t basic_info;
+ GHashTable* values_by_number;
+} pbl_enum_descriptor_t;
+
+/* like google::protobuf::EnumValueDescriptor of protobuf cpp library */
+typedef struct {
+ pbl_node_t basic_info;
+ int number;
+} pbl_enum_value_descriptor_t;
+
+/* Option node. The name of basic_info is optionName.
+ Now, we only care about fieldOption. */
+typedef struct {
+ pbl_node_t basic_info;
+ char* value;
+} pbl_option_descriptor_t;
+
+/* parser state */
+typedef struct {
+ pbl_descriptor_pool_t* pool; /* pool will keep the parsing result */
+ pbl_file_descriptor_t* file; /* info of current parsing file */
+ GSList* lex_string_tokens;
+} protobuf_lang_state_t;
+
+/* Store chars created by strdup or g_strconcat into protobuf_lang_state_t temporarily,
+ and return back the input chars pointer.
+ It will be freed when protobuf_lang_state_t is released */
+static inline gchar*
+pbl_store_string_token(protobuf_lang_state_t* parser_state, char* dupstr)
+{
+ parser_state->lex_string_tokens = g_slist_append(parser_state->lex_string_tokens, dupstr);
+ return dupstr;
+}
+
+/* default error_cb */
+static inline void
+pbl_printf(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+}
+
+/**
+ Reinitialize the protocol buffers pool according to proto files directories.
+ @param ppool The output descriptor_pool will be created. If *pool is not NULL, it will free it first.
+ @param directories The root directories containing proto files. Must end with NULL element.
+ @param error_cb The error reporter callback function. */
+void
+pbl_reinit_descriptor_pool(pbl_descriptor_pool_t** ppool, const char** directories, pbl_report_error_cb_t error_cb);
+
+/* free all memory used by this protocol buffers languange pool */
+void
+pbl_free_pool(pbl_descriptor_pool_t* pool);
+
+/* add a proto file to pool. this file will not be parsed until run_pbl_parser function is invoked. */
+gboolean
+pbl_add_proto_file_to_be_parsed(pbl_descriptor_pool_t* pool, const char* filepath);
+
+/* run C protocol buffers languange parser, return 0 if successed */
+int run_pbl_parser(pbl_descriptor_pool_t* pool, gboolean debug);
+
+/* like descriptor_pool::FindMethodByName */
+const pbl_method_descriptor_t*
+pbl_message_descriptor_pool_FindMethodByName(const pbl_descriptor_pool_t* pool, const char* name);
+
+/* like MethodDescriptor::name() */
+const char*
+pbl_method_descriptor_name(const pbl_method_descriptor_t* method);
+
+/* like MethodDescriptor::full_name() */
+const char*
+pbl_method_descriptor_full_name(const pbl_method_descriptor_t* method);
+
+/* like MethodDescriptor::input_type() */
+const pbl_message_descriptor_t*
+pbl_method_descriptor_input_type(const pbl_method_descriptor_t* method);
+
+/* like MethodDescriptor::output_type() */
+const pbl_message_descriptor_t*
+pbl_method_descriptor_output_type(const pbl_method_descriptor_t* method);
+
+/* like descriptor_pool::FindMessageTypeByName() */
+const pbl_message_descriptor_t*
+pbl_message_descriptor_pool_FindMessageTypeByName(const pbl_descriptor_pool_t* pool, const char* name);
+
+/* like Descriptor::name() */
+const char*
+pbl_message_descriptor_name(const pbl_message_descriptor_t* message);
+
+/* like Descriptor::full_name() */
+const char*
+pbl_message_descriptor_full_name(const pbl_message_descriptor_t* message);
+
+/* like Descriptor::field_count() */
+int
+pbl_message_descriptor_field_count(const pbl_message_descriptor_t* message);
+
+/* like Descriptor::field() */
+const pbl_field_descriptor_t*
+pbl_message_descriptor_field(const pbl_message_descriptor_t* message, int field_index);
+
+/* like Descriptor::FindFieldByNumber() */
+const pbl_field_descriptor_t*
+pbl_message_descriptor_FindFieldByNumber(const pbl_message_descriptor_t* message, int number);
+
+/* like Descriptor::FindFieldByName() */
+const pbl_field_descriptor_t*
+pbl_message_descriptor_FindFieldByName(const pbl_message_descriptor_t* message, const char* name);
+
+/* like FieldDescriptor::full_name() */
+const char*
+pbl_field_descriptor_full_name(const pbl_field_descriptor_t* field);
+
+/* like FieldDescriptor::name() */
+const char*
+pbl_field_descriptor_name(const pbl_field_descriptor_t* field);
+
+/* like FieldDescriptor::number() */
+int
+pbl_field_descriptor_number(const pbl_field_descriptor_t* field);
+
+/* like FieldDescriptor::type() */
+int
+pbl_field_descriptor_type(const pbl_field_descriptor_t* field);
+
+/* like FieldDescriptor::is_repeated() */
+int
+pbl_field_descriptor_is_repeated(const pbl_field_descriptor_t* field);
+
+/* like FieldDescriptor::is_packed() */
+int
+pbl_field_descriptor_is_packed(const pbl_field_descriptor_t* field);
+
+/* like FieldDescriptor::TypeName() */
+const char*
+pbl_field_descriptor_TypeName(int field_type);
+
+/* like FieldDescriptor::message_type() */
+const pbl_message_descriptor_t*
+pbl_field_descriptor_message_type(const pbl_field_descriptor_t* field);
+
+/* like FieldDescriptor::enum_type() */
+const pbl_enum_descriptor_t*
+pbl_field_descriptor_enum_type(const pbl_field_descriptor_t* field);
+
+/* like EnumDescriptor::name() */
+const char*
+pbl_enum_descriptor_name(const pbl_enum_descriptor_t* anEnum);
+
+/* like EnumDescriptor::full_name() */
+const char*
+pbl_enum_descriptor_full_name(const pbl_enum_descriptor_t* anEnum);
+
+/* like EnumDescriptor::FindValueByNumber() */
+const pbl_enum_value_descriptor_t*
+pbl_enum_descriptor_FindValueByNumber(const pbl_enum_descriptor_t* anEnum, int number);
+
+/* like EnumValueDescriptor::name() */
+const char*
+pbl_enum_value_descriptor_name(const pbl_enum_value_descriptor_t* enumValue);
+
+/* like EnumValueDescriptor::full_name() */
+const char*
+pbl_enum_value_descriptor_full_name(const pbl_enum_value_descriptor_t* enumValue);
+
+/*
+ * Following are tree building functions.
+ */
+
+/* create a normal node */
+pbl_node_t*
+pbl_create_node(pbl_file_descriptor_t* file, pbl_node_type_t nodetype, const char* name);
+
+/* change the name of node */
+pbl_node_t*
+pbl_set_node_name(pbl_node_t* node, const char* newname);
+
+/* get the name of node */
+static inline const char*
+pbl_get_node_name(pbl_node_t* node)
+{
+ return node->name;
+}
+
+/* get the full name of node. if it is NULL, it will be built. */
+const char*
+pbl_get_node_full_name(pbl_node_t* node);
+
+/* append a node as a child of the parent node, and return the parent pointer */
+pbl_node_t*
+pbl_add_child(pbl_node_t* parent, pbl_node_t* child);
+
+/* create an enumeration field node */
+pbl_node_t*
+pbl_create_enum_value_node(pbl_file_descriptor_t* file, const char* name, int number);
+
+/* merge one('from') node's children to another('to') node, and return the 'to' pointer */
+pbl_node_t*
+pbl_merge_children(pbl_node_t* to, pbl_node_t* from);
+
+/* create a field node */
+pbl_node_t*
+pbl_create_field_node(pbl_file_descriptor_t* file, const char* label, const char* type_name, const char* name, int number, pbl_node_t* options);
+
+/* create a map field node */
+pbl_node_t*
+pbl_create_map_field_node(pbl_file_descriptor_t* file, const char* name, int number, pbl_node_t* options);
+
+/* create a method (rpc or stream of service) node */
+pbl_node_t*
+pbl_create_method_node(pbl_file_descriptor_t* file, const char* name, const char* in_msg_type, gboolean in_is_stream, const char* out_msg_type, gboolean out_is_stream);
+
+/* create an option node */
+pbl_node_t*
+pbl_create_option_node(pbl_file_descriptor_t* file, const char* name, const char* value);
+
+/* free a pbl_node_t and its children. */
+void
+pbl_free_node(gpointer anode);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __PROTOBUF_LANG_TREE_H__ */
+
+/*
+ * 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:
+ */