Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wireshark dissector support #7

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on: [push, pull_request]
jobs:
run-tests:
name: Run tests
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- name: Install prerequisites
run: |
Expand Down
40 changes: 26 additions & 14 deletions bin/bragi
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,37 @@
import argparse

from bragi.parser import CompilationUnit
from bragi.cpp_generator import CodeGenerator
from bragi.cpp_generator import CodeGenerator as CppCodeGenerator
from bragi.wireshark_generator import CodeGenerator as WiresharkCodeGenerator

parser = argparse.ArgumentParser(prog = 'bragi', description = 'Bragi IDL to C++ compiler')
parser.add_argument('input', nargs=1, help='input file')
parser.add_argument('output', nargs=1, help='output file')
parser.add_argument('-l', '--lib', nargs=1, help='C++ library to use', choices=['frigg', 'stdc++'], default='libc++')
parser.add_argument('--protobuf', help='Generate protobuf compatibilty methods (SerializeAsString/ParseFromArray)', action='store_true')
parser.add_argument('input', nargs='+', help='input file', type=argparse.FileType('r'))
parser.add_argument('-o', '--output', help='output file', type=str)
subparsers = parser.add_subparsers(required=True, dest='language')

cpp_parser = subparsers.add_parser('cpp')
cpp_parser.add_argument('-l', '--lib', nargs=1, help='C++ library to use', choices=['frigg', 'stdc++'], default='libc++')
cpp_parser.add_argument('--protobuf', help='Generate protobuf compatibilty methods (SerializeAsString/ParseFromArray)', action='store_true')

ws_parser = subparsers.add_parser('wireshark')

args = parser.parse_args()

source = args.input[0]
output = args.output[0]
lib = args.lib[0]
inputs = []
output = args.output

with open(source, "r") as f:
code = f.read()
unit = CompilationUnit(source, code)
for source in args.input:
code = source.read()
unit = CompilationUnit(source.name, code)
unit.process()
unit.verify()
inputs.append(unit)

if(args.language == "cpp"):
lib = args.lib[0]
generator = CppCodeGenerator(inputs, lib, protobuf_compat = args.protobuf)
else:
generator = WiresharkCodeGenerator(inputs)

generator = CodeGenerator(unit, lib, protobuf_compat = args.protobuf)
with open(output, "w") as o:
o.write(generator.generate())
with open(output, "w") as o:
o.write(generator.generate())
49 changes: 27 additions & 22 deletions bragi/cpp_generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .tokens import *
from .types import *

import hashlib

class StdlibTraits:
def needs_allocator(self):
return False
Expand Down Expand Up @@ -41,7 +43,7 @@ def includes(self):

class CodeGenerator:
def __init__(self, unit, stdlib, protobuf_compat = False):
self.unit = unit
self.units = unit
self.protobuf_compat = protobuf_compat
self.stdlib_traits = None
self.indent_depth = 0
Expand Down Expand Up @@ -75,22 +77,25 @@ def generate(self):

out += '#include <bragi/internals.hpp>\n\n'

for thing in self.unit.tokens:
if type(thing) == NamespaceTag:
out += self.switch_ns(thing)
if type(thing) == UsingTag:
out += self.generate_using(thing)
if type(thing) == Enum and thing.mode == "enum":
out += self.generate_enum(thing)
if type(thing) == Enum and thing.mode == "consts":
out += self.generate_consts(thing)
if type(thing) == Message:
out += self.generate_message(thing)
if type(thing) == Struct:
out += self.generate_struct(thing)
if type(thing) == Group:
for m in thing.members:
out += self.generate_message(m)
for unit in self.units:
for thing in unit.tokens:
if type(thing) == NamespaceTag:
out += self.switch_ns(thing)
protohash = hashlib.shake_128(thing.name.encode('ascii')).hexdigest(4)
out += f'{self.indent}const char protocol_hash[] = "0x{protohash}";\n\n'
if type(thing) == UsingTag:
out += self.generate_using(thing)
if type(thing) == Enum and thing.mode == "enum":
out += self.generate_enum(thing)
if type(thing) == Enum and thing.mode == "consts":
out += self.generate_consts(thing)
if type(thing) == Message:
out += self.generate_message(thing)
if type(thing) == Struct:
out += self.generate_struct(thing)
if type(thing) == Group:
for m in thing.members:
out += self.generate_message(m)

out += self.finalize_ns()

Expand Down Expand Up @@ -737,14 +742,14 @@ def emit_struct_decoder(self, parent, members):
return out

# Protobuf compatibilty code
def emit_serialize_as_string(self, parent):
def emit_serialize_as_string(self):
out = ''

if type(self.stdlib_traits) is FriggTraits:
out = f'{self.indent}void SerializeToString(frg::string<Allocator> *str) {{\n'
self.enter_indent()

out += f'{self.indent}str->resize({parent.head.size});\n'
out += f'{self.indent}str->resize(size_of_head());\n'
out += f'{self.indent}bragi::limited_writer wr{{str->data(), str->size()}};\n\n'
out += self.emit_assert_that('encode_head(wr)')

Expand All @@ -754,7 +759,7 @@ def emit_serialize_as_string(self, parent):
out = f'{self.indent}std::string SerializeAsString() {{\n'
self.enter_indent()

out += f'{self.indent}std::string str(size_t({parent.head.size}), \'\\0\');\n'
out += f'{self.indent}std::string str(size_of_head(), \'\\0\');\n'
out += f'{self.indent}bragi::limited_writer wr{{str.data(), str.size()}};\n\n'
out += self.emit_assert_that('encode_head(wr)') + '\n'
out += f'{self.indent}return str;\n'
Expand Down Expand Up @@ -862,7 +867,7 @@ def emit_class_members(self, members):
out += f'{self.indent}{self.generate_type(m.type)} m_{m.name}; bool p_{m.name};\n'

if self.stdlib_traits.needs_allocator():
out += f'{self.indent}Allocator allocator;'
out += f'{self.indent}Allocator allocator;\n'

return out

Expand Down Expand Up @@ -902,7 +907,7 @@ def generate_message(self, message):
out += self.emit_part_decoder('tail', None, None)

if self.protobuf_compat:
out += self.emit_serialize_as_string(message)
out += self.emit_serialize_as_string()
out += self.emit_parse_from_array(message)

out += self.emit_class_members(all_members)
Expand Down
43 changes: 32 additions & 11 deletions bragi/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
start: (message | enum | consts | ns | struct | using | group)+

tag: "tag" "(" INT ")"
attributes: tag?
format: "@format" "(" NAME ")"
attributes: tag? format?
enum_attributes: format?

message: "message" NAME INT message_block
enum: "enum" NAME enum_block
consts: "consts" NAME type_name enum_block
enum: enum_attributes "enum" NAME enum_block
consts: enum_attributes "consts" NAME type_name enum_block
ns: "namespace" ESCAPED_STRING ";"
struct: "struct" NAME "{" message_member* "}"
using: "using" ESCAPED_STRING "=" ESCAPED_STRING ";"
Expand Down Expand Up @@ -90,19 +92,40 @@ def tags_block(self, items, meta):
return TagsBlock(meta.line, meta.column, items)

def attributes(self, items):
return items[0] if len(items) > 0 else None
ret = {}

for i in items:
if type(i) == Tag:
ret['tag'] = i
elif type(i) == Format:
ret['format'] = i

return ret

@v_args(meta = True)
def tag(self, items, meta):
return Tag(meta.line, meta.column, items[0])
return Tag(meta.line, meta.column, items[-1])

@v_args(meta = True)
def format(self, items, meta):
return Format(meta.line, meta.column, items[0])

@v_args(meta = True)
def enum(self, items, meta):
return Enum(meta.line, meta.column, items[0], 'enum', TypeName(0, 0, 'int32'), flatten(items[1:]))
return Enum(meta.line, meta.column, items[1], 'enum', TypeName(0, 0, 'int32'), items[0], flatten(items[2:]))

@v_args(meta = True)
def consts(self, items, meta):
return Enum(meta.line, meta.column, items[0], 'consts', items[1], flatten(items[2:]))
return Enum(meta.line, meta.column, items[1], 'consts', items[2], items[0], flatten(items[3:]))

def enum_attributes(self, items):
ret = {}

for i in items:
if type(i) == Format:
ret['format'] = i

return ret

@v_args(meta = True)
def using(self, items, meta):
Expand Down Expand Up @@ -133,9 +156,6 @@ def type_name(self, items, meta):
def type_size(self, items, meta):
return '[' + ''.join(items) + ']'

def attributes(self, items):
return items[0] if len(items) > 0 else None

@v_args(meta = True)
def group(self, items, meta):
return Group(meta.line, meta.column, items)
Expand Down Expand Up @@ -257,7 +277,8 @@ def process(self):
TypeIdentity.CONSTS if t.mode == 'consts' else TypeIdentity.ENUM,
fixed_size = subtype.fixed_size,
signed = subtype.signed,
subtype = subtype)
subtype = subtype,
attributes = t.attributes)
)

t.type = self.type_registry.get_type(t.name)
Expand Down
19 changes: 16 additions & 3 deletions bragi/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def __init__(self, line, column, name, id, body):
self.id = id
self.head = None
self.tail = None
self.body = body

for m in body:
if self.head is None and type(m) is HeadSection:
self.head = m
Expand All @@ -45,10 +47,11 @@ def __repr__(self):
return 'Struct(' + self.name + ') { ' + str(self.members) + ' }'

class MessageMember:
def __init__(self, line, column, tag, typename, name):
def __init__(self, line, column, attributes, typename, name):
self.line = line
self.column = column
self.tag = tag
self.tag = attributes.get('tag', None)
self.format = attributes.get('format', None)
self.typename = typename
self.type = None
self.name = name
Expand Down Expand Up @@ -85,14 +88,24 @@ def __init__(self, line, column, value):
def __repr__(self):
return 'tag(' + str(self.value) + ')'

class Format:
def __init__(self, line, column, value):
self.line = line
self.column = column
self.value = value

def __repr__(self):
return 'format(' + str(self.value) + ')'

class Enum:
def __init__(self, line, column, name, mode, typename, members):
def __init__(self, line, column, name, mode, typename, attributes, members):
self.line = line
self.column = column
self.name = name
self.mode = mode
self.type = typename
self.members = members
self.attributes = attributes

def __repr__(self):
return 'Enum(' + self.name + ') { ' + str(self.members) + ' }'
Expand Down
6 changes: 5 additions & 1 deletion bragi/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,25 @@ class TypeIdentity(enum.Enum):
STRING = 6

class Type:
def __init__(self, name, identity, fixed_size = None, dynamic = False, subtype = None, signed = False, n_elements = None):
def __init__(self, name, identity, fixed_size = None, dynamic = False, subtype = None, signed = False, n_elements = None, attributes = {}):
self.name = name
self.identity = identity
self.fixed_size = fixed_size
self.dynamic = dynamic
self.subtype = subtype
self.n_elements = None
self.signed = None
self.attributes = attributes

if self.identity is TypeIdentity.ARRAY and n_elements:
self.n_elements = n_elements

if self.identity is TypeIdentity.INTEGER:
self.signed = signed

def __repr__(self):
return f'{self.name}'

class TypeRegistry:
def __init__(self):
self.types = {
Expand Down
Loading