diff --git a/.github/labeler.yml b/.github/labeler.yml index e3da18c1eda..e46e15bc5b9 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -95,3 +95,8 @@ CI: grpc: - grpc/**/* - src/idl_gen_grpc.cpp + +wireshark: + - wireshark/**/* + - src/bfbs_gen_wireshark.cpp + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e7038f89d73..e1e120476d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,6 +181,7 @@ set(FlatBuffers_Compiler_SRCS src/flatc_main.cpp src/bfbs_gen.h src/bfbs_gen_lua.h + src/bfbs_gen_wireshark.h src/bfbs_gen_nim.h src/bfbs_namer.h include/codegen/idl_namer.h @@ -193,6 +194,7 @@ set(FlatBuffers_Compiler_SRCS src/annotated_binary_text_gen.h src/annotated_binary_text_gen.cpp src/bfbs_gen_lua.cpp + src/bfbs_gen_wireshark.cpp src/bfbs_gen_nim.cpp src/code_generators.cpp grpc/src/compiler/schema_interface.h diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index c84999a5485..06d1e697e61 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -750,6 +750,7 @@ struct IDLOptions { kNim = 1 << 17, kProto = 1 << 18, kKotlinKmp = 1 << 19, + kWireshark = 1 << 20, kMAX }; diff --git a/src/BUILD.bazel b/src/BUILD.bazel index 4b9fb7a8156..48dc646813a 100644 --- a/src/BUILD.bazel +++ b/src/BUILD.bazel @@ -76,6 +76,8 @@ cc_library( "bfbs_gen_lua.h", "bfbs_gen_nim.cpp", "bfbs_gen_nim.h", + "bfbs_gen_wireshark.cpp", + "bfbs_gen_wireshark.h", "bfbs_namer.h", "binary_annotator.cpp", "binary_annotator.h", @@ -102,6 +104,8 @@ cc_library( "bfbs_gen_lua.h", "bfbs_gen_nim.cpp", "bfbs_gen_nim.h", + "bfbs_gen_wireshark.cpp", + "bfbs_gen_wireshark.h", "bfbs_namer.h", "file_binary_writer.cpp", "file_name_saving_file_manager.cpp", diff --git a/src/bfbs_gen_wireshark.cpp b/src/bfbs_gen_wireshark.cpp new file mode 100644 index 00000000000..f46425dde69 --- /dev/null +++ b/src/bfbs_gen_wireshark.cpp @@ -0,0 +1,927 @@ +/* + * Copyright 2025 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: +// * comment everything +// * squash commits +// * fix vector of unions +// * handle windows paths +// * test optional scalars (should work?) +// * print default vectors (this is just empty vs null) and strings (which have +// non null defaults) + +// Future work: +// * nested_flatbuffer - call Parse if I can get the data out of the +// reflection +// * bitfields - well known attributes are not passed down to us +// * flexbuffers + +#include "bfbs_gen_wireshark.h" + +#include +#include +#include + +// Ensure no includes to flatc internals. bfbs_gen.h and generator.h are OK. +#include "bfbs_gen.h" +#include "bfbs_namer.h" + +// The intermediate representation schema. +#include "flatbuffers/code_generator.h" +#include "flatbuffers/reflection.h" +#include "flatbuffers/reflection_generated.h" + +namespace flatbuffers { +namespace { +// To reduce typing +namespace r = ::reflection; + +std::set LuaKeywords() { + return { "and", "break", "do", "else", "elseif", "end", + "false", "for", "function", "goto", "if", "in", + "local", "nil", "not", "or", "repeat", "return", + "then", "true", "until", "while" }; +} + +Namer::Config WiresharkDefaultConfig() { + return { /*types=*/Case::kUpperCamel, + /*constants=*/Case::kUnknown, + /*methods=*/Case::kUpperCamel, + /*functions=*/Case::kUpperCamel, + /*fields=*/Case::kUpperCamel, + /*variables=*/Case::kLowerCamel, + /*variants=*/Case::kKeep, + /*enum_variant_seperator=*/"", + /*escape_keywords=*/Namer::Config::Escape::AfterConvertingCase, + /*namespaces=*/Case::kKeep, + /*namespace_seperator=*/"__", + /*object_prefix=*/"", + /*object_suffix=*/"", + /*keyword_prefix=*/"", + /*keyword_suffix=*/"_", + /*filenames=*/Case::kKeep, + /*directories=*/Case::kKeep, + /*output_path=*/"", + /*filename_suffix=*/"", + /*filename_extension=*/".lua" }; +} + +// Returns the parser function name (defined in 00_wireshark_numbers.lua) for a +// given scalar type +std::string GetScalarParserFunction(r::BaseType type) { + switch (type) { + case r::Bool: return "Parse_Bool"; + case r::UType: + case r::Byte: + case r::UByte: return "Parse_Uint8"; + case r::Short: return "Parse_Int16"; + case r::UShort: return "Parse_Uint16"; + case r::Int: return "Parse_Int32"; + case r::UInt: return "Parse_Uint32"; + case r::Long: return "Parse_Int64"; + case r::ULong: return "Parse_Uint64"; + case r::Float: return "Parse_Float32"; + case r::Double: return "Parse_Float64"; + default: FLATBUFFERS_ASSERT(false); + } +} + +// Returns the FlatBuffers defined Proto Field type for a given scalar type +std::string GetScalarParserProtoField(r::BaseType type) { + switch (type) { + case r::Bool: return "fb_bool"; + case r::UType: + case r::Byte: + case r::UByte: return "fb_uint8"; + case r::Short: return "fb_int16"; + case r::UShort: return "fb_uint16"; + case r::Int: return "fb_int32"; + case r::UInt: return "fb_uint32"; + case r::Long: return "fb_int64"; + case r::ULong: return "fb_uint64"; + case r::Float: return "fb_float32"; + case r::Double: return "fb_float64"; + default: FLATBUFFERS_ASSERT(false); + } +} + +// helper function to convert named elements to underscore format for use as lua +// variables +void to_underscore(std::string &str, char match = '.') { + std::replace(str.begin(), str.end(), match, '_'); +} + +class WiresharkBfbsGenerator : public BaseBfbsGenerator { + public: + explicit WiresharkBfbsGenerator(const std::string &flatc_version) + : BaseBfbsGenerator(), + schema_(nullptr), + options_(), + flatc_version_(flatc_version), + namer_(WiresharkDefaultConfig(), LuaKeywords()) {} + + Status GenerateFromSchema(const r::Schema *schema, + const CodeGenOptions &options) + FLATBUFFERS_OVERRIDE { + schema_ = schema; + options_ = options; + + // generates 01__enums.lua + // provides lookup tables for enum values to names + if (!GenerateEnums()) { return ERROR; } + // generates 01__lookup.lua + // provides lookup tables for member names and fields for each object + if (!GenerateMemberLookupTables()) { return ERROR; } + // generates 02_.lua + // provides the actual parsing functions for each object in the schema + // and dissectors for root objects + if (!GenerateTableFiles()) { return ERROR; } + + return OK; + } + + using BaseBfbsGenerator::GenerateCode; + + // all of these functions were copied from the lua bfbs generator + Status GenerateCode(const Parser &, const std::string &, + const std::string &) override { + return Status::NOT_IMPLEMENTED; + } + + Status GenerateMakeRule(const Parser &, const std::string &, + const std::string &, std::string &) override { + return Status::NOT_IMPLEMENTED; + } + + Status GenerateGrpcCode(const Parser &, const std::string &, + const std::string &) override { + return Status::NOT_IMPLEMENTED; + } + + Status GenerateRootFile(const Parser &, const std::string &) override { + return Status::NOT_IMPLEMENTED; + } + + bool IsSchemaOnly() const override { return true; } + + bool SupportsBfbsGeneration() const override { return true; } + + bool SupportsRootFileGeneration() const override { return false; } + + IDLOptions::Language Language() const override { + return IDLOptions::kWireshark; + } + + std::string LanguageName() const override { return "Wireshark"; } + + uint64_t SupportedAdvancedFeatures() const FLATBUFFERS_OVERRIDE { + return r::AdvancedArrayFeatures | r::AdvancedUnionFeatures | + r::OptionalScalars | r::DefaultVectorsAndStrings; + } + + private: + // convenience struct for holding all the common strings used in generation + struct names { + std::string name_space; + std::string object_name; + std::string full_name; + std::string full_name_underscore; + std::string declaration_file; + std::string declaration_file_underscore; + + // Constructor to initialize all members + names(const std::string &ns, const std::string &obj_name, + const std::string &full, const std::string &full_underscore, + const std::string &decl_file, const std::string &decl_file_underscore) + : name_space(ns), + object_name(obj_name), + full_name(full), + full_name_underscore(full_underscore), + declaration_file(decl_file), + declaration_file_underscore(decl_file_underscore) {} + + template + names(const T &object) + : names(std::string(), // name_space + std::string(), // object_name + object.name()->str(), // full_name + object.name()->str(), // full_name_underscore + object.declaration_file()->str(), // declaration_file + object.declaration_file()->str() // declaration_file_underscore + ) { + static_assert(std::is_base_of::value || + std::is_base_of::value, + "T must be a reflection object or enum type"); + + to_underscore(full_name_underscore); + + const bool is_absolute_path = + declaration_file.rfind("//", 0) == std::string::npos; + + declaration_file_underscore = + declaration_file_underscore.substr(is_absolute_path ? 1 : 2); + + to_underscore(declaration_file_underscore, '/'); + to_underscore(declaration_file_underscore); + } + }; + + // iterate over every enum in the schema and generate a lookup table for each. + // this allows wireshark to display the enum names instead of just the integer + // values + bool GenerateEnums() { + std::map files; + + ForAllEnums(schema_->enums(), [&](const r::Enum *enum_def) { + // get all strings up front + const names object_names(*enum_def); + + const std::string enum_lookup_table_name = + "ENUM_" + object_names.full_name_underscore; + + // create or access current file contents + std::string &code = files[object_names.declaration_file_underscore]; + + // -- begin actual code generation + + // add header if not present + if (code.empty()) { + code += "--[[\n"; + code += + " Automatically generated by the FlatBuffers compiler, do not " + "modify.\n"; + code += " This file contains Lua tables for enums in the schema.\n"; + code += "--]]\n\n"; + } + + // create table for enum values + code += "-- Enum: " + object_names.full_name + "\n"; + code += "" + enum_lookup_table_name + " = {\n"; + + // add each enum value entry + ForAllEnumValues(enum_def, [&](const r::EnumVal *enum_val) { + const std::string name = enum_val->name()->str(); + const std::string value = NumToString(enum_val->value()); + + code += " [" + value + "] = \"" + name + "\",\n"; + }); + + code += "}\n\n"; + }); + + // write files to disk + for (const auto &file : files) { + const std::string file_name = + options_.output_path + "01_" + file.first + "_enums.lua"; + SaveFile(file_name.c_str(), file.second, false); + } + + return true; + } + + // iterate over every object in the schema and generate lookup tables for + // member names and fields. These are used in the parse functions so that each + // field has a dedicated ProtoField definition so that users can take full + // advantage of wireshark filtering. Two tables are created to enforce + // ordering. + bool GenerateMemberLookupTables() { + std::map files; + + // loop over every object in the schema + ForAllObjects(schema_->objects(), [&](const r::Object *object) { + // get all strings up front + const names object_names(*object); + const std::string member_name_table = + object_names.full_name_underscore + "_member_names"; + const std::string memeber_field_table = + object_names.full_name_underscore + "_member_fields"; + + // -- begin actual code generation + + // create or access current file contents + std::string &code = files[object_names.declaration_file_underscore]; + + // add header if not present + if (code.empty()) { + code += "--[[\n"; + code += + " Automatically generated by the FlatBuffers compiler, do not " + "modify.\n"; + code += " This file contains Lua tables for enums in the schema.\n"; + code += "--]]\n\n"; + } + + code += "-- Object: " + object_names.full_name + "\n"; + + // create a table of field names for each object + code += member_name_table + " = {\n"; + + // add each field to the table + ForAllFields(object, /*reverse=*/false, [&](const r::Field *field) { + code += " [" + NumToString(field->id()) + "] = \"" + + namer_.Variable(field->name()->str()) + "\",\n"; + }); + + code += "}\n\n"; + + // create a lookup table of ProtoFields + code += memeber_field_table + " = {\n"; + + // add the "self" entry - this uses an integer key so that it won't ever + // get confused with the fields themselves + code += " [1] = ProtoField.bytes(\"" + object_names.full_name + + "\", \"" + object_names.full_name + "\", base.NONE, \"" + + object_names.full_name + "\"),\n"; + + try { + // generate the ProtoField definitions for each field in the object + ForAllFields(object, /*reverse=*/false, [&](const r::Field *field) { + code += CreateProtoFieldDefinition(field, + object_names.full_name_underscore); + }); + + code += "}\n\n"; + } catch (const std::runtime_error &) { return; } + }); + + // write lookup files to disk + for (const auto &file : files) { + const std::string file_name = + options_.output_path + "01_" + file.first + "_lookup.lua"; + SaveFile(file_name.c_str(), file.second, false); + } + + return true; + } + + // iterate over every object and create the parse functions for for every + // field of every object. Then create the root table/struct parse function, + // and create wireshark dissectors for root objects. + bool GenerateTableFiles() { + std::map files; + + // loop over every object in the schema + ForAllObjects(schema_->objects(), [&](const r::Object *object) { + // get all strings up front + const names object_names(*object); + const std::string member_name_table = + object_names.full_name_underscore + "_union_type_enum_values"; + const std::string member_lookup_table = + object_names.full_name_underscore + "_member_fields"; + const std::string member_names_table = + object_names.full_name_underscore + "_member_names"; + const std::string member_list_table = + object_names.full_name_underscore + "_member_list"; + const std::string proto_name = + "PROTO_" + object_names.full_name_underscore; + const std::string parse_object_function_name = + "Parse_" + object_names.full_name_underscore; + + // create or access current file contents + auto &code = files[object_names.declaration_file_underscore]; + + // -- begin actual code generation + + // header + if (code.empty()) { + code += "--[[\n"; + code += + " Automatically generated by the FlatBuffers compiler, do not " + "modify.\n"; + code += " This file contains Lua tables for enums in the schema.\n"; + code += "--]]\n\n"; + } + + code += "-- Object: " + object_names.full_name + "\n"; + + // create a lookup table for union types in this object + code += "local " + member_name_table + " = {}\n\n"; + + // create parser functions for each field + ForAllFields(object, /*reverse=*/false, [&](const r::Field *field) { + const std::string field_function_name = + "Parse_" + namer_.Variable(field->name()->str()); + + code += "local function " + field_function_name + + "(buffer, offset, tree)\n"; + code += " local field_name = " + member_names_table + "[" + + NumToString(field->id()) + "]\n"; + code += CreateParserDefinition(field, object, member_lookup_table); + code += "end\n\n"; + }); + + // create the lookup table that matches field names to parse functions + code += "local " + member_list_table + " = {\n"; + + ForAllFields(object, false, [&](const r::Field *field) { + const std::string field_function_name = + "Parse_" + namer_.Variable(field->name()->str()); + + if (object->is_struct()) { + code += " { " + NumToString(field->offset()) + ", " + + member_names_table + "[" + NumToString(field->id()) + "], " + + field_function_name + " },\n"; + } else { + code += " { " + member_names_table + "[" + NumToString(field->id()) + + "], " + field_function_name + " },\n"; + } + }); + + code += "}\n\n"; + + const bool is_root_node = object == schema_->root_table(); + + // collect pointers to every dependency of the root node so we can add the + // ProtoFields to it + const auto deps = CollectDependencies(object, schema_); + + // root nodes get a Proto definition and dissector function + if (is_root_node) { + // create the proto definition + code += proto_name + " = Proto(\"" + object_names.full_name + + "\", \"flatbuffers: " + object_names.full_name + "\")\n"; + code += proto_name + ".fields = Construct_Fields(\n"; + + // add all types to the ProtoField definition so wireshark lookup works + bool first = true; + for (const auto *dep : deps) { + std::string dep_full_name = dep->name()->str(); + to_underscore(dep_full_name); + if (!first) { code += ",\n"; } + code += " " + dep_full_name + "_member_fields"; + first = false; + } + + code += "\n)\n\n"; + + // register the protocol with wireshark + code += "Register_Proto(" + proto_name + ")\n\n"; + + // add the dissector function for the protocol + code += proto_name + ".dissector = function(buffer, info, tree)\n"; + code += " info.cols.protocol = " + proto_name + ".description\n"; + code += + " return " + parse_object_function_name + "(buffer, 0, tree)\n"; + code += "end\n\n"; + } + + // create the parse function for this object + code += + "function " + parse_object_function_name + "(buffer, offset, tree"; + + // if the object is a root node, we have some more code to add. + if (is_root_node) { + code += ", verbose)\n"; + code += " local file_name = \"flatbuffer: " + + object_names.declaration_file + "\"\n\n"; + code += + " assert(buffer.reported_len, \"Please pass the full Tvb into " + "the parser, not a TvbRange (or anything else!)\")\n\n"; + code += " FB_VERBOSE = verbose and verbose or false\n\n"; + code += " local subtree = tree:add(" + proto_name + + ", buffer(offset), buffer(offset):raw(), file_name)\n\n"; + + code += + " offset = offset + Parse_Root_Offset(buffer, offset, " + "subtree).value\n\n"; + + // parse file_ident if present in the schema + if (const std::string ident = schema_->file_ident()->str(); + !ident.empty()) { + code += " local file_ident_buffer = buffer(offset + 4, 4)\n"; + code += " local file_ident_string = file_ident_buffer:string()\n"; + code += + " local ident_match = file_ident_string == \"" + ident + "\"\n"; + code += + " subtree:add(fb_file_ident, file_ident_buffer, " + "file_ident_string)"; + code += + ":append_text(\" [\" .. (ident_match and " + "\"MATCH\" or \"MISMATCH\") .. \"]\")\n\n"; + } + + } else { + code += ")\n"; + code += " local subtree = tree\n"; + } + + // call the derived parse function + if (object->is_struct()) { + code += " return Parse_Struct(buffer, offset, subtree, " + + member_lookup_table + "[1], " + + NumToString(object->bytesize()) + ", " + member_list_table + + ")\n"; + } else { + code += " return Parse_Table(buffer, offset, subtree, " + + member_lookup_table + "[1], " + member_list_table + ")\n"; + } + code += "end\n\n"; + }); + + // write all files to disk + for (const auto &file : files) { + const std::string file_name = + options_.output_path + "02_" + file.first + ".lua"; + SaveFile(file_name.c_str(), file.second, false); + } + + return true; + } + + // helper function which creates a ProtoField definition for a given field in + // the schema. + std::string CreateProtoFieldDefinition(const r::Field *field, + const std::string &object_full_name) { + std::string code; + + // collect basic information + std::string field_name = field->name()->str(); + const std::string field_name_full = object_full_name + "_" + field_name; + const r::Type *type = field->type(); + r::BaseType base_type = type->base_type(); + std::string enum_lookup_table = "nil"; + const int32_t index = type->index(); + + if (field->deprecated()) { field_name += " [DEPRECATED]"; } + + // enum protofield definitions have to generate their lookup table name. + // union enums have to grab their base types + if (IsInteger(base_type) && index >= 0) { + // enum types always have a valid index + std::string enum_name_underscore = + schema_->enums()->Get(index)->name()->str(); + to_underscore(enum_name_underscore); + + // set the lookup table string + enum_lookup_table = "ENUM_" + enum_name_underscore; + + if (base_type == r::UType) { + // union enums have the base_type UType, we need the actual underlying + // type to get the appropriate width + base_type = + schema_->enums()->Get(index)->underlying_type()->base_type(); + } + } + + // generate the key for the table and the prefix of the value + code += " [" + object_full_name + "_member_names[" + + NumToString(field->id()) + "]] = "; + code += "ProtoField."; + + // create the ProtoField type for this field + switch (base_type) { + case r::Bool: + code += "bool(\"" + field_name_full + "\", \"" + field_name + + "\", nil, nil, \"" + field_name; + break; + case r::UType: + case r::Byte: + case r::UByte: + code += "uint8(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, " + enum_lookup_table + ", nil, \"" + field_name; + break; + case r::Short: + code += "int16(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, " + enum_lookup_table + ", nil, \"" + field_name; + break; + case r::UShort: + code += "uint16(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, " + enum_lookup_table + ", nil, \"" + field_name; + break; + case r::Int: + code += "int32(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, " + enum_lookup_table + ", nil, \"" + field_name; + break; + case r::UInt: + code += "uint32(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, " + enum_lookup_table + ", nil, \"" + field_name; + break; + case r::Long: + code += "int64(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, " + enum_lookup_table + ", nil, \"" + field_name; + break; + case r::ULong: + code += "uint64(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, " + enum_lookup_table + ", nil, \"" + field_name; + break; + case r::Double: + code += "double(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, \"" + field_name; + break; + case r::Float: + code += "float(\"" + field_name_full + "\", \"" + field_name + + "\", base.DEC, \"" + field_name; + break; + case r::String: + code += "string(\"" + field_name_full + "\", \"" + field_name + + "\", base.UNICODE, \"" + field_name; + break; + case r::Obj: + case r::Union: + code += "bytes(\"" + field_name_full + "\", \"" + field_name + + "\", base.NONE, \"" + field_name; + break; + case r::Vector: + case r::Array: + case r::Vector64: + code += "bytes(\"" + field_name_full + "\", \"" + field_name + + "\", base.NONE, \"" + field_name; + break; + default: throw std::runtime_error("Unsupported field type"); + } + + code += "\"),\n"; + + return code; + } + + // generate the actual custom parse function for the given field + std::string CreateParserDefinition(const r::Field *field, + const r::Object *object, + const std::string &field_lookup) { + std::string code; + + // we have to regenerate the underscore name here.. :shrug: + std::string full_object_name_underscore = object->name()->str(); + to_underscore(full_object_name_underscore); + + const r::Type *type = field->type(); + r::BaseType base_type = type->base_type(); + + // when the base type is a union, we have to fish out the true underlying + // type + if (base_type == r::UType) { + // UType should always have a valid index + const int32_t index = type->index(); + base_type = schema_->enums()->Get(index)->underlying_type()->base_type(); + } + + // generate the actual parser definition based on type + switch (base_type) { + // scalar types + case r::Bool: + code += " local parsed_data = Parse_Bool(buffer, offset, tree, " + + field_lookup + "[field_name], " + + (field->default_integer() ? "true" : "false") + ")\n"; + break; + case r::UType: + case r::Byte: + case r::UByte: + code += " local parsed_data = Parse_Uint8(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_integer()) + ")\n"; + break; + case r::Short: + code += " local parsed_data = Parse_Int16(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_integer()) + ")\n"; + break; + case r::UShort: + code += " local parsed_data = Parse_Uint16(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_integer()) + ")\n"; + break; + case r::Int: + code += " local parsed_data = Parse_Int32(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_integer()) + ")\n"; + break; + case r::UInt: + code += " local parsed_data = Parse_Uint32(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_integer()) + ")\n"; + break; + case r::Long: + code += " local parsed_data = Parse_Int64(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_integer()) + ")\n"; + break; + case r::ULong: + code += " local parsed_data = Parse_Uint64(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_integer()) + ")\n"; + break; + case r::Float: + code += " local parsed_data = Parse_Float32(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_real()) + ")\n"; + break; + case r::Double: + code += " local parsed_data = Parse_Float64(buffer, offset, tree, " + + field_lookup + "[field_name], " + + NumToString(field->default_real()) + ")\n"; + break; + // non scalar types + case r::String: + code += + " local parsed_data = Parse_Offset_Field(buffer, offset, tree," + + field_lookup + "[field_name], Parse_String)\n"; + break; + case r::Vector64: + // handle just like regular vectors, but the indirect size is 8 and the + // function called is different. + case r::Vector: { + const auto element_base_type = type->element(); + bool is_indirect = false; + std::string object_type; + std::string object_parser; + // handle scalar types differently from object types + // inlined structs and offset types are handled the same here - just + // call their parse function + switch (element_base_type) { + case r::Obj: { + const auto sub_object = schema_->objects()->Get(type->index()); + std::string sub_object_name_underscore = sub_object->name()->str(); + to_underscore(sub_object_name_underscore); + + object_type = sub_object_name_underscore + "_member_fields[1]"; + object_parser = "Parse_" + sub_object_name_underscore; + + if (!sub_object->is_struct()) { is_indirect = true; } + } break; + case r::Union: { + break; + } + default: + // scalar types + object_type = GetScalarParserProtoField(element_base_type); + object_parser = GetScalarParserFunction(element_base_type); + break; + } + + // derived parse function name based on width + const std::string parse_function = + "Parse_Vector" + + std::string((type->base_type() == r::Vector64) ? "64" : ""); + + // ParseVector calls into ParseArray, so we need all the arguments to + // ParseArray + code += " local parsed_data = " + parse_function + + "(buffer, offset, tree, " + + std::string(is_indirect ? "true" : "false") + ", " + + field_lookup + "[field_name], " + object_type + ", " + + NumToString(type->element_size()) + ", " + object_parser + + ")\n"; + } break; + case r::Obj: { + const r::Object *sub_object = schema_->objects()->Get(type->index()); + std::string sub_object_full_name = sub_object->name()->str(); + to_underscore(sub_object_full_name); + + code += " local parsed_data = Parse_" + sub_object_full_name + + "(buffer, offset, tree)\n"; + code += " parsed_data.tree:prepend_text(field_name .. \": \")\n"; + } break; + case r::Union: { + // we could probably just infer that union type is stored at id - 1, but + // just in case, I'll go hunting for it. + uint32_t union_type_index; + const std::string union_type_name = field->name()->str() + "_type"; + + // this currently loops over all fields with no break. could be more + // optimal + ForAllFields(object, false, [&](const r::Field *obj_field) { + if (obj_field->name()->str() == union_type_name) { + union_type_index = obj_field->id(); + } + }); + + // create a local lookup table of union value to parser function + code += " local union_functions = {\n"; + + // add parser functions for each enum value in the union + ForAllEnumValues(schema_->enums()->Get(type->index()), + [&](const r::EnumVal *enum_val) { + // for each enum value, get the target object and its + // parser function + int32_t union_type_index = enum_val->value(); + + if (union_type_index == 0) { + // skip the NONE entry, as it doesn't have a parser + return; + } + + const r::Object *enum_target_object = + schema_->objects()->Get(union_type_index); + + std::string enum_target_name_underscore = + enum_target_object->name()->str(); + to_underscore(enum_target_name_underscore); + + code += " [" + NumToString(union_type_index) + + "] = " + "Parse_" + + enum_target_name_underscore + ",\n"; + }); + + code += " }\n\n"; + + // parse the union type based on the lookup table + code += " local parsed_data = Parse_Union(buffer, offset, tree, " + + field_lookup + "[field_name], union_functions[" + + full_object_name_underscore + "_union_type_enum_values[" + + NumToString(union_type_index) + "]])\n"; + } break; + case r::Array: { + const auto element_base_type = type->element(); + std::string object_type; + std::string object_parser; + + switch (element_base_type) { + case r::Obj: { + const auto sub_object = schema_->objects()->Get(type->index()); + + std::string sub_object_name_underscore = sub_object->name()->str(); + to_underscore(sub_object_name_underscore); + + object_type = sub_object_name_underscore + "_member_fields[1]"; + object_parser = "Parse_" + sub_object_name_underscore; + break; + } + default: + object_type = GetScalarParserProtoField(element_base_type); + object_parser = GetScalarParserFunction(element_base_type); + break; + } + + code += " local parsed_data = Parse_Array(buffer, offset, tree, " + + field_lookup + "[field_name], " + object_type + ", " + + NumToString(type->element_size()) + ", " + + NumToString(type->fixed_length()) + ", " + object_parser + + ")\n"; + } break; + case r::None: + case r::MaxBaseType: FLATBUFFERS_ASSERT(false); + } + + // if this field is a union lookup, it gets more code + if (IsInteger(type->base_type()) && type->index() >= 0 && + schema_->enums()->Get(type->index())->is_union()) { + // set the value of the union field in the local lookup table, so that we + // can reference it when we come accross the union itself + code += " " + full_object_name_underscore + "_union_type_enum_values[" + + NumToString(field->id()) + "] = parsed_data.value\n"; + } + + return code; + } + + // helper function for collecting all directly included tables and structs of + // a root table. this is used to fill in the wireshark protocol fields section + std::vector CollectDependencies(const r::Object *object, + const r::Schema *schema) { + std::vector dependencies; + std::unordered_set visited; + + // recursive caller to collect and return all depdenent objects + std::function collect = [&](const r::Object *obj) { + if (!obj || visited.count(obj)) { return; } + visited.insert(obj); + dependencies.push_back(obj); + + for (const auto field : *obj->fields()) { + const auto type = field->type(); + if (type->base_type() == r::BaseType::Obj || + type->base_type() == r::BaseType::Union) { + const auto *dep = schema->objects()->Get(type->index()); + collect(dep); + } else if (type->base_type() == r::BaseType::Vector && + type->element() == r::BaseType::Obj) { + const auto *dep = schema->objects()->Get(type->index()); + collect(dep); + } + } + }; + + collect(object); + return dependencies; + } + + const r::Schema *schema_; + CodeGenOptions options_; + + const std::string flatc_version_; + const BfbsNamer namer_; +}; + +} // namespace + +std::unique_ptr NewWiresharkBfbsGenerator( + const std::string &flatc_version) { + return std::unique_ptr( + new WiresharkBfbsGenerator(flatc_version)); +} + +} // namespace flatbuffers diff --git a/src/bfbs_gen_wireshark.h b/src/bfbs_gen_wireshark.h new file mode 100644 index 00000000000..4bc4db6f474 --- /dev/null +++ b/src/bfbs_gen_wireshark.h @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_BFBS_GEN_WIRESHARK_H_ +#define FLATBUFFERS_BFBS_GEN_WIRESHARK_H_ + +#include "flatbuffers/code_generator.h" + +namespace flatbuffers { + +std::unique_ptr NewWiresharkBfbsGenerator( + const std::string &flatc_version); + +} // namespace flatbuffers + +#endif // FLATBUFFERS_BFBS_GEN_WIRESHARK_H_ diff --git a/src/flatc_main.cpp b/src/flatc_main.cpp index 45f95a08f73..6b500b5c4ec 100644 --- a/src/flatc_main.cpp +++ b/src/flatc_main.cpp @@ -19,6 +19,7 @@ #include "bfbs_gen_lua.h" #include "bfbs_gen_nim.h" +#include "bfbs_gen_wireshark.h" #include "flatbuffers/base.h" #include "flatbuffers/code_generator.h" #include "flatbuffers/flatc.h" @@ -126,8 +127,9 @@ int main(int argc, const char *argv[]) { flatbuffers::NewKotlinCodeGenerator()); flatc.RegisterCodeGenerator( - flatbuffers::FlatCOption{ "", "kotlin-kmp", "", - "Generate Kotlin multiplatform classes for tables/structs" }, + flatbuffers::FlatCOption{ + "", "kotlin-kmp", "", + "Generate Kotlin multiplatform classes for tables/structs" }, flatbuffers::NewKotlinKMPCodeGenerator()); flatc.RegisterCodeGenerator( @@ -175,6 +177,12 @@ int main(int argc, const char *argv[]) { "Generate TypeScript code for tables/structs" }, flatbuffers::NewTsCodeGenerator()); + flatc.RegisterCodeGenerator( + flatbuffers::FlatCOption{ + "w", "wireshark", "", + "Generate Wireshark dissector for tables/structs" }, + flatbuffers::NewWiresharkBfbsGenerator(flatbuffers_version)); + // Create the FlatC options by parsing the command line arguments. const flatbuffers::FlatCOptions &options = flatc.ParseFromCommandLineArguments(argc, argv); diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index d01e18ef761..6e19e39d99a 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -2682,8 +2682,7 @@ bool Parser::SupportsOptionalScalars(const flatbuffers::IDLOptions &opts) { IDLOptions::kKotlin | IDLOptions::kKotlinKmp | IDLOptions::kCpp | IDLOptions::kJava | IDLOptions::kCSharp | IDLOptions::kTs | IDLOptions::kBinary | IDLOptions::kGo | IDLOptions::kPython | - IDLOptions::kJson | - IDLOptions::kNim; + IDLOptions::kJson | IDLOptions::kNim | IDLOptions::kWireshark; unsigned long langs = opts.lang_to_generate; return (langs > 0 && langs < IDLOptions::kMAX) && !(langs & ~supported_langs); } @@ -2694,7 +2693,8 @@ bool Parser::SupportsOptionalScalars() const { bool Parser::SupportsDefaultVectorsAndStrings() const { static FLATBUFFERS_CONSTEXPR unsigned long supported_langs = - IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kNim; + IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kNim | + IDLOptions::kWireshark; return !(opts.lang_to_generate & ~supported_langs); } @@ -2703,24 +2703,28 @@ bool Parser::SupportsAdvancedUnionFeatures() const { ~(IDLOptions::kCpp | IDLOptions::kTs | IDLOptions::kPhp | IDLOptions::kJava | IDLOptions::kCSharp | IDLOptions::kKotlin | IDLOptions::kBinary | IDLOptions::kSwift | IDLOptions::kNim | - IDLOptions::kJson | IDLOptions::kKotlinKmp)) == 0; + IDLOptions::kJson | IDLOptions::kKotlinKmp | + IDLOptions::kWireshark)) == 0; } bool Parser::SupportsAdvancedArrayFeatures() const { return (opts.lang_to_generate & ~(IDLOptions::kCpp | IDLOptions::kPython | IDLOptions::kJava | IDLOptions::kCSharp | IDLOptions::kJsonSchema | IDLOptions::kJson | - IDLOptions::kBinary | IDLOptions::kRust | IDLOptions::kTs)) == 0; + IDLOptions::kBinary | IDLOptions::kRust | IDLOptions::kTs | + IDLOptions::kWireshark)) == 0; } bool Parser::Supports64BitOffsets() const { return (opts.lang_to_generate & - ~(IDLOptions::kCpp | IDLOptions::kJson | IDLOptions::kBinary)) == 0; + ~(IDLOptions::kCpp | IDLOptions::kJson | IDLOptions::kBinary | + IDLOptions::kWireshark)) == 0; } bool Parser::SupportsUnionUnderlyingType() const { - return (opts.lang_to_generate & ~(IDLOptions::kCpp | IDLOptions::kTs | - IDLOptions::kBinary)) == 0; + return (opts.lang_to_generate & + ~(IDLOptions::kCpp | IDLOptions::kTs | IDLOptions::kBinary | + IDLOptions::kWireshark)) == 0; } Namespace *Parser::UniqueNamespace(Namespace *ns) { diff --git a/wireshark/00_wireshark.lua b/wireshark/00_wireshark.lua new file mode 100755 index 00000000000..f8757ba4b1f --- /dev/null +++ b/wireshark/00_wireshark.lua @@ -0,0 +1,303 @@ +-- can be enabled when calling the root node parse function +FB_VERBOSE = false + +function Register_Proto(protocol) + DissectorTable.get("tcp.port"):add_for_decode_as(protocol) + DissectorTable.get("udp.port"):add_for_decode_as(protocol) +end + +local function Parse_Offset(buffer, offset, tree, field_name, field_type, indirect_offset) + -- assume voffset is the only 2 byte wide variant + local field_size = (field_type == fb_voffset_t) and 2 or 4 + + local offset_buffer = buffer(offset, field_size) + local offset_value = offset_buffer:le_uint() + + local subtree = tree + if FB_VERBOSE then + subtree = tree:add(field_type, offset_buffer, offset_value) + subtree:prepend_text(field_type.name .. ": ") + subtree:append_text(" [0x" .. NumberToHex(buffer:offset() + offset + offset_value, field_size) .. "]") + end + + return { + tree = subtree, + value = offset_value + } +end + +local function Parse_UOffset(buffer, offset, tree, field_type) + return Parse_Offset(buffer, offset, tree, field_type.name, fb_uoffset_t, 0) +end + +local function Parse_VOffset(buffer, offset, tree, field_name, indirect_offset) + return Parse_Offset(buffer, offset, tree, field_name, fb_voffset_t, indirect_offset) +end + +function Parse_Root_Offset(buffer, offset, tree) + local root_buffer = buffer(offset, 4) + local root_offset = root_buffer:le_uint() + + if FB_VERBOSE then + -- we never return this subtree + local subtree = tree:add(fb_uoffset_t, root_buffer, root_offset) + subtree:prepend_text("root offset: ") + subtree:append_text(" [0x" .. NumberToHex(buffer:offset() + root_offset, 4) .. "]") + end + + return { + tree = tree, + value = root_offset + } +end + +function Parse_Offset_Field(buffer, offset, tree, field_type, field_parser) + local offset_data = Parse_UOffset(buffer, offset, tree, field_type) + return field_parser(buffer, offset + offset_data.value, offset_data.tree, field_type) +end + +function Parse_Array(buffer, offset, tree, field_type, object_type, item_size, count, ParseFunction) + local array_buffer = buffer(offset, (count * item_size)) + local array_bytes = array_buffer:raw() + local description = field_type.name .. ": array[" .. count .. "]" + + local subtree = tree:add(fb_array, array_buffer, array_bytes, description) + + for i = 1, count do + local item_offset = offset + ((i - 1) * item_size) + local tree_item = ParseFunction(buffer, item_offset, subtree, object_type) + tree_item.tree:prepend_text((i - 1) .. ": ") + end + + return { + tree = subtree, + value = array_bytes + } +end + +local function Parse_VectorInfo(buffer, offset, tree, field_type, object_size) + local vector_count_buffer = buffer(offset, 4) + local vector_count = vector_count_buffer:le_uint() + + local vector_buffer = buffer(offset, 4 + (vector_count * object_size)) + local vector_bytes = vector_buffer:raw() + local description = field_type.name .. ": vector[" .. vector_count .. "]" + + local subtree = tree + if FB_VERBOSE then + subtree = tree:add(field_type, vector_buffer, vector_bytes, description) + + local count_buffer = buffer(offset, 4) + local count_value = count_buffer:le_uint() + subtree:add(fb_uint32, count_buffer, count_value) + end + + return { + tree = subtree, + value = vector_count, + } +end + +-- TODO ParseVector64 or take an argument is_64 +function Parse_Vector(buffer, offset, tree, is_indirect, field_type, object_type, object_size, Parse_Function) + local offset_info = Parse_UOffset(buffer, offset, tree, field_type) + local vector_object_size = is_indirect and 4 or object_size + local vector_info = Parse_VectorInfo(buffer, offset + offset_info.value, offset_info.tree, field_type, + vector_object_size) + + local Parse_FunctionObject = is_indirect and function(func_buffer, func_offset, func_tree, func_field_type) + return Parse_Offset_Field(func_buffer, func_offset, func_tree, func_field_type, Parse_Function) + end or Parse_Function + + return Parse_Array(buffer, offset + offset_info.value + 4, vector_info.tree, field_type, object_type, + object_size, vector_info.value, Parse_FunctionObject) +end + +function Parse_String(buffer, offset, tree, field_type) + if offset == 0 then + local subtree = tree:add(field_type) + subtree:append_text("null [DEFAULT VALUE]") + return { + tree = subtree, + value = nil + } + end + + local string_length_buffer = buffer(offset, 4) + local string_length = string_length_buffer:le_uint() + 1 -- always null terminated, but not listed in length + + local string_struct_buffer = buffer(offset, 4 + string_length) + local string_struct_bytes = string_struct_buffer:raw() + local description = field_type.name .. ": string[" .. string_length .. "]" + + local subtree = tree + + if FB_VERBOSE then + subtree = tree:add(fb_string, string_struct_buffer, string_struct_bytes, description) + + local length_buffer = buffer(offset, 4) + local length_value = length_buffer:le_uint() + local len_tree = subtree:add(fb_uint32, length_buffer, length_value) + len_tree:prepend_text("length: ") + len_tree:append_text(" (excl \\0)") + end + + local string_buffer = buffer(offset + 4, string_length) + local string_value = string_buffer:string() + subtree:add(field_type, string_buffer, string_value) + + return { + tree = subtree, + value = string_value + } +end + +function Parse_Struct(buffer, offset, tree, field_type, struct_size, member_list) + local struct_buffer = buffer(offset, struct_size) + local struct_bytes = struct_buffer:raw() + + local subtree = tree:add(field_type, struct_buffer, struct_bytes, field_type.name) + + for i = 1, #member_list do + local data_item_offset = member_list[i][1] + -- call this members' parser + member_list[i][3](buffer, offset + data_item_offset, subtree, member_list[i][1]) + end + + return { + tree = subtree, + value = struct_bytes + } +end + +-- I can't figure out how to get width out of field size +function Parse_Enum(buffer, offset, tree, field_width, field_type, default_value) + if offset == 0 then + local subtree = tree + subtree = tree:add(field_type, default_value) + subtree:append_text(" [DEFAULT VALUE]") + return { + tree = subtree, + value = default_value + } + end + + local enum_buffer = buffer(offset, field_width) + local enum_value = enum_buffer:le_uint() + local subtree = tree:add(field_type, enum_buffer, enum_value) + + return { + tree = subtree, + value = enum_value + } +end + +function Parse_Union(buffer, offset, tree, field_type, parse_function) + if offset == 0 then + local subtree = tree:add(field_type) + return { + tree = subtree, + value = nil + } + end + + local union_data = Parse_Offset_Field(buffer, offset, tree, field_type, parse_function) + + union_data.tree:prepend_text(field_type.name .. ": ") + + return union_data +end + +local function Parse_VTable(buffer) + local vtable_size_buffer = buffer(0, 2) + local vtable_size = vtable_size_buffer:le_uint() + + local table_size_buffer = buffer(2, 2) + local table_size = table_size_buffer:le_uint() + + local entries = {} + + for i = 1, ((vtable_size - 4) / 2) do + local entry_buffer = buffer(4 + ((i - 1) * 2), 2) + local entry = entry_buffer:le_uint() + entries[i] = entry + end + + return { + vtable_size = vtable_size, + vtable_size_buffer = vtable_size_buffer, + table_size = table_size, + table_size_buffer = table_size_buffer, + entries = entries + } +end + +local function Parse_TableInfo(buffer, offset, tree, field_type, member_list) + local vtable_offset_buffer = buffer(offset, 4) + local vtable_offset = vtable_offset_buffer:le_int() + + local vtable_data = Parse_VTable(buffer(offset - vtable_offset)) + + -- create table subtree + local table_buffer = buffer(offset, vtable_data.table_size) + local table_bytes = table_buffer:raw() + local subtree = tree:add(field_type, table_buffer, table_bytes, field_type.name) + + -- add vtable info to subtree + if FB_VERBOSE then + -- add vtable offset to tree + local vtable_tree = subtree:add(fb_soffset_t, vtable_offset_buffer, -vtable_offset) + vtable_tree:prepend_text("vtable offset: ") + vtable_tree:append_text(" [0x" .. NumberToHex(buffer:offset() + offset - vtable_offset, 4) .. "]") + + local vtable_buffer = buffer(offset - vtable_offset, vtable_data.vtable_size) + local vtable_bytes = vtable_buffer:raw() + local vtable_description = "vtable[" .. #vtable_data.entries .. "]" + local vtable_subtree = vtable_tree:add(fb_vtable, vtable_buffer, vtable_bytes, vtable_description) + -- add vtable metadata + local vtable_size_buffer = buffer(vtable_data.vtable_size_buffer:offset(), 2) + local vtable_size_value = vtable_size_buffer:le_uint() + local vtable_size_subtree = vtable_subtree:add(fb_uint16, vtable_size_buffer, vtable_size_value) + vtable_size_subtree:prepend_text("VTable Size: ") + + local table_size_buffer = buffer(vtable_data.table_size_buffer:offset(), 2) + local table_size_value = table_size_buffer:le_uint() + local table_size_subtree = vtable_subtree:add(fb_uint16, table_size_buffer, table_size_value) + table_size_subtree:prepend_text("Table Size: ") + + -- add vtable entries + -- skip over vtable size and table size + local entry_start = offset - vtable_offset + 4 + if (#vtable_data.entries ~= 0) then + for i = 1, #vtable_data.entries do + local entry_offset = entry_start + ((i - 1) * 2) + Parse_VOffset(buffer, entry_offset, vtable_subtree, member_list[i][1], buffer:offset() + offset) + end + end + end + + -- give the generated parser the subtree and vtable entries + return { + tree = subtree, + value = vtable_data.entries + } +end + +function Parse_Table(buffer, offset, tree, field_type, member_list) + -- we know its a table, so parse the table info to get the offsets + local entry_list = Parse_TableInfo(buffer, offset, tree, field_type, member_list) + + for i = 1, #entry_list.value do + local table_tree = entry_list.tree + + -- parse the data item here + local data_item_offset = (entry_list.value[i] == 0) and 0 or (offset + entry_list.value[i]) + -- call this members' parser + member_list[i][2](buffer, data_item_offset, table_tree, member_list[i][1]) + end + + return { + tree = entry_list.tree, + value = nil + } +end diff --git a/wireshark/00_wireshark_numbers.lua b/wireshark/00_wireshark_numbers.lua new file mode 100755 index 00000000000..f34f5ca754c --- /dev/null +++ b/wireshark/00_wireshark_numbers.lua @@ -0,0 +1,85 @@ +local function AddFieldToTree(buffer, offset, tree, field_type, scalar_type, field_width, default_value) + if offset == 0 then + local subtree = tree:add(field_type, default_value) + subtree:append_text(" [DEFAULT VALUE]") + return { + tree = subtree, + value = nil + } + end + + local field_buffer = buffer(offset, field_width) + local field_value = field_buffer["le_" .. scalar_type](field_buffer) + return { + tree = tree:add(field_type, field_buffer, field_value), + value = field_value + } +end + +function Parse_Uint8(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "uint", + 1, default_value) +end + +function Parse_Uint16(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "uint", + 2, default_value) +end + +function Parse_Uint32(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "uint", + 4, default_value) +end + +function Parse_Uint64(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "uint64", + 8, default_value) +end + +function Parse_Int8(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, + "int", 1, default_value) +end + +function Parse_Int16(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "int", + 2, default_value) +end + +function Parse_Int32(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "int", + 4, default_value) +end + +function Parse_Int64(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "int64", + 8, default_value) +end + +function Parse_Float32(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "float", + 4, default_value) +end + +function Parse_Float64(buffer, offset, tree, field_type, default_value) + return AddFieldToTree(buffer, offset, tree, field_type, "double", + 8, default_value) +end + +function Parse_Bool(buffer, offset, tree, field_type, default_value) + if offset == 0 then + local subtree = tree:add(field_type, default_value) + subtree:append_text(" [DEFAULT VALUE]") + return { + tree = subtree, + value = default_value + } + end + + local bool_buffer = buffer(offset, 1) + local bool_value = bool_buffer:le_uint() + return { + tree = tree:add(field_type, bool_buffer, bool_value), + value = bool_value + } +end diff --git a/wireshark/00_wireshark_types.lua b/wireshark/00_wireshark_types.lua new file mode 100755 index 00000000000..0fed8376a9b --- /dev/null +++ b/wireshark/00_wireshark_types.lua @@ -0,0 +1,45 @@ +-- internal primitives +fb_uoffset_t = ProtoField.uint32("fb_uoffset_t", "unsigned offset", base.DEC, nil, nil, "Generic Offset Type") +fb_soffset_t = ProtoField.int32("fb_soffset_t", "signed offset", base.DEC, nil, nil, "Generic Signed Offset Type") +fb_voffset_t = ProtoField.uint16("fb_voffset_t", "vtable offset", base.DEC, nil, nil, "Generic VTable Offset Type") + +-- primitives +-- bool +fb_bool = ProtoField.bool("fb_bool", "bool", base.NONE, nil, nil, "Generic Boolean Type") + +-- unsigned +fb_uint8 = ProtoField.uint8("fb_uint8", "uint8", base.DEC, nil, nil, "Generic 8-bit Unsigned Integer") +fb_uint16 = ProtoField.uint16("fb_uint16", "uint16", base.DEC, nil, nil, "Generic 16-bit Unsigned Integer") +fb_uint32 = ProtoField.uint32("fb_uint32", "uint32", base.DEC, nil, nil, "Generic 32-bit Unsigned Integer") +fb_uint64 = ProtoField.uint64("fb_uint64", "uint64", base.DEC, nil, nil, "Generic 64-bit Unsigned Integer") + +-- signed +fb_int8 = ProtoField.int8("fb_int8", "int8", base.DEC, nil, nil, "Generic 8-bit Signed Integer") +fb_int16 = ProtoField.int16("fb_int16", "int16", base.DEC, nil, nil, "Generic 16-bit Signed Integer") +fb_int32 = ProtoField.int32("fb_int32", "int32", base.DEC, nil, nil, "Generic 32-bit Signed Integer") +fb_int64 = ProtoField.int64("fb_int64", "int64", base.DEC, nil, nil, "Generic 64-bit Signed Integer") + +-- floating point +fb_float32 = ProtoField.float("fb_float32", "float32", base.DEC, "Generic 32-bit Float") +fb_float64 = ProtoField.double("fb_float64", "float64", base.DEC, "Generic 64-bit Float") +fb_union = ProtoField.bytes("fb_union", "union", base.NONE, "Generic Union Type ([uint8_t, u_offset_t])") + +fb_struct = ProtoField.bytes("fb_struct", "struct", base.NONE, "Generic Struct Type") +fb_array = ProtoField.bytes("fb_array", "array", base.NONE, "Generic Array Type") +fb_vector = ProtoField.bytes("fb_vector", "vector", base.NONE, "Generic Vector Type") +fb_table = ProtoField.bytes("fb_table", "table", base.NONE, "Generic Table Type") +fb_vtable = ProtoField.bytes("fb_vtable", "vtable", base.NONE, "Generic VTable Type") +fb_string = ProtoField.string("fb_string", "string", base.UNICODE, "Generic String Type") +fb_file_ident = ProtoField.string("fb_file_identifier", "file identifier", base.UNICODE, "File Identifier") + +fb_basic_fields = { + fb_uoffset_t, fb_soffset_t, fb_voffset_t, + fb_bool, + fb_uint8, fb_uint16, fb_uint32, fb_uint64, + fb_int8, fb_int16, fb_int32, fb_int64, + fb_float32, fb_float64, fb_string, fb_file_ident, + fb_union, fb_struct, fb_array, fb_vector, fb_table, fb_vtable +} + +fb_proto = Proto("flatbuffers", "flatbuffers") +fb_proto.fields = fb_basic_fields diff --git a/wireshark/00_wireshark_utilities.lua b/wireshark/00_wireshark_utilities.lua new file mode 100755 index 00000000000..16e7d357486 --- /dev/null +++ b/wireshark/00_wireshark_utilities.lua @@ -0,0 +1,27 @@ +function Construct_Fields(...) + local fields = {} + local args = { ... } + + local function concat_tables(t1, t2) + local t3 = {} + -- Copy all elements from t1 into t3 + for i = 1, #t1 do + t3[#t3 + 1] = t1[i] + end + -- Append all values from t2 into t3 + for _, value in pairs(t2) do + t3[#t3 + 1] = value + end + return t3 + end + + for i, v in ipairs(args) do + fields = concat_tables(fields, v) + end + + return fields +end + +function NumberToHex(value, num_bytes) + return string.format("%0" .. (num_bytes * 2) .. "x", value) +end diff --git a/wireshark/README.md b/wireshark/README.md new file mode 100644 index 00000000000..09182be4d3c --- /dev/null +++ b/wireshark/README.md @@ -0,0 +1,19 @@ +# Loading Flatbuffer Generated Wireshark Plugins +Copy the contents of this directory into your chosen Wireshark plugins folder. make sure to point `flatc` at the same plugins folder when generating with `--wireshark`. + +> **Note:** If you place them in a folder, ensure the folder name appears earlier in ASCII order than any generated folders. + +## Why Are the Wireshark Files Named Strangely? + +According to the [Wireshark documentation](https://www.wireshark.org/docs/wsdg_html_chunked/wsluarm.html), the file load process and order are critical: + +> ... **all files ending with .lua** are loaded from the global plugins directory and its subdirectories. Then all files ending with .lua in the personal Lua plugins directory and its subdirectories are loaded. **The files are processed in ASCIIbetical order (compared byte-by-byte, as strcmp), descending into each subdirectory depth-first in order."** + +To ensure the correct load order, generated files are named according to the below table. + +| File Prefix | Description | +|-------------|---------------------------------------------------------------| +| `00_` | Base and helper FlatBuffers files needed by generated code | +| `10_` | Generated definitions file containing important lookup tables | +| `20_` | Generated table and struct files that are not root tables | +| `30_` | Generated tables marked as root tables | \ No newline at end of file