from . import codegen import types _types_by_name = {} _types = [] default_pointer_size = 4 def type_exists(name): return name in _types_by_name def lookup_type(name): return _types_by_name[name] def get_named_types(): return _types class FixedSize: def __init__(self, val = 0, minor = 0): if isinstance(val, FixedSize): self.vals = val.vals else: self.vals = [0] * (minor + 1) self.vals[minor] = val def __add__(self, other): if isinstance(other, int): other = FixedSize(other) new = FixedSize() l = max(len(self.vals), len(other.vals)) shared = min(len(self.vals), len(other.vals)) new.vals = [0] * l for i in range(shared): new.vals[i] = self.vals[i] + other.vals[i] for i in range(shared,len(self.vals)): new.vals[i] = self.vals[i] for i in range(shared,len(other.vals)): new.vals[i] = new.vals[i] + other.vals[i] return new def __radd__(self, other): return self.__add__(other) def __str__(self): s = "%d" % (self.vals[0]) for i in range(1,len(self.vals)): if self.vals[i] > 0: s = s + " + ((minor >= %d)?%d:0)" % (i, self.vals[i]) return s # Some attribute are propagated from member to the type as they really # are part of the type definition, rather than the member. This applies # only to attributes that affect pointer or array attributes, as these # are member local types, unlike e.g. a Struct that may be used by # other members propagated_attributes=["ptr_array", "nonnull", "chunk"] valid_attributes={ # embedded/appended at the end of the structure 'end', # the C structure contains a pointer to data # for instance we want to write an array to an allocated array 'to_ptr', # write output to this C structure 'ctype', # prefix for flags/values enumerations 'prefix', # used in demarshaller to use directly data from message without a copy 'nocopy', # store member array in a pointer # similar to to_ptr but has an additional argument which is the name of a C # field which will store the array length 'as_ptr', # do not generate marshall code # used for last members to be able to marshall them manually 'nomarshal', # ??? not used by python code 'zero_terminated', 'marshall', # this pointer member cannot be null 'nonnull', # this flag member contains only a single flag 'unique_flag', 'ptr_array', 'outvar', # C structure has an anonymous member (used in switch) 'anon', 'chunk', # this channel is contained in an #ifdef section # the argument specifies the preprocessor define to check 'ifdef', # write this member as zero on network 'zero', # specify minor version required for these members 'minor', # this member contains the byte count for an array. # the argument is the member name for item count (not bytes) 'bytes_count', # this attribute does not exist on the network, fill just structure with the value 'virtual', # for a switch this indicates that on network # it will occupy always the same size (maximum size required for all members) 'fixedsize', } attributes_with_arguments={ 'ctype', 'prefix', 'as_ptr', 'outvar', 'ifdef', 'minor', 'bytes_count', 'virtual', } def fix_attributes(attribute_list): attrs = {} for attr in attribute_list: name = attr[0][1:] lst = attr[1:] if not name in valid_attributes: raise Exception("Attribute %s not recognized" % name) if not name in attributes_with_arguments: if len(lst) > 0: raise Exception("Attribute %s specified with options" % name) elif len(lst) > 1: raise Exception("Attribute %s has more than 1 argument" % name) attrs[name] = lst return attrs class Type: def __init__(self): self.attributes = {} self.registred = False self.name = None def has_name(self): return self.name != None def get_type(self, recursive=False): return self def is_primitive(self): return False def is_fixed_sizeof(self): return True def is_extra_size(self): return False def contains_extra_size(self): return False def is_fixed_nw_size(self): return True def is_array(self): return isinstance(self, ArrayType) def contains_member(self, member): return False def is_struct(self): return isinstance(self, StructType) def is_pointer(self): return isinstance(self, PointerType) def get_num_pointers(self): return 0 def get_pointer_names(self, marshalled): return [] def sizeof(self): return "sizeof(%s)" % (self.c_type()) def __repr__(self): return self.__str__() def __str__(self): if self.name != None: return self.name return "anonymous type" def resolve(self): return self def register(self): if self.registred or self.name == None: return self.registred = True if self.name in _types_by_name: raise Exception("Type %s already defined" % self.name) _types.append(self) _types_by_name[self.name] = self def has_attr(self, name): if not name in valid_attributes: raise Exception('attribute %s not expected' % name) return name in self.attributes class TypeRef(Type): def __init__(self, name): Type.__init__(self) self.name = name def __str__(self): return "ref to %s" % (self.name) def resolve(self): if self.name not in _types_by_name: raise Exception("Unknown type %s" % self.name) return _types_by_name[self.name] def register(self): assert True, "Can't register TypeRef!" class IntegerType(Type): def __init__(self, bits, signed): Type.__init__(self) self.bits = bits self.signed = signed if signed: self.name = "int%d" % bits else: self.name = "uint%d" % bits def primitive_type(self): return self.name def c_type(self): return self.name + "_t" def get_fixed_nw_size(self): return self.bits // 8 def is_primitive(self): return True class TypeAlias(Type): def __init__(self, name, the_type, attribute_list): Type.__init__(self) self.name = name self.the_type = the_type self.attributes = fix_attributes(attribute_list) def get_type(self, recursive=False): if recursive: return self.the_type.get_type(True) else: return self.the_type def primitive_type(self): return self.the_type.primitive_type() def resolve(self): self.the_type = self.the_type.resolve() return self def __str__(self): return "alias %s" % self.name def is_primitive(self): return self.the_type.is_primitive() def is_fixed_sizeof(self): return self.the_type.is_fixed_sizeof() def is_fixed_nw_size(self): return self.the_type.is_fixed_nw_size() def get_fixed_nw_size(self): return self.the_type.get_fixed_nw_size() def get_num_pointers(self): return self.the_type.get_num_pointers() def get_pointer_names(self, marshalled): return self.the_type.get_pointer_names(marshalled) def c_type(self): if self.has_attr("ctype"): return self.attributes["ctype"][0] return self.name class EnumBaseType(Type): def is_enum(self): return isinstance(self, EnumType) def primitive_type(self): return "uint%d" % (self.bits) def c_type(self): return "uint%d_t" % (self.bits) def c_name(self): return codegen.prefix_camel(self.name) def c_enumname(self, value): return self.c_enumname_by_name(self.names[value]) def c_enumname_by_name(self, name): if self.has_attr("prefix"): return self.attributes["prefix"][0] + name return codegen.prefix_underscore_upper(self.name.upper(), name) def is_primitive(self): return True def get_fixed_nw_size(self): return self.bits // 8 # generates a value-name table suitable for use with the wireshark protocol # dissector def c_describe(self, writer): writer.write("static const value_string %s_vs[] = " % codegen.prefix_underscore_lower(self.name)) writer.begin_block() values = list(self.names.keys()) values.sort() for i in values: writer.write("{ ") writer.write(self.c_enumname(i)) writer.write(", \"%s\" }," % self.names[i]) writer.newline() writer.write("{ 0, NULL }") writer.end_block(semicolon=True) writer.newline() class EnumType(EnumBaseType): def __init__(self, bits, name, enums, attribute_list): Type.__init__(self) self.bits = bits self.name = name last = -1 names = {} values = {} for v in enums: name = v[0] if len(v) > 1: value = v[1] else: value = last + 1 last = value assert value not in names names[value] = name values[name] = value self.names = names self.values = values self.attributes = fix_attributes(attribute_list) def __str__(self): return "enum %s" % self.name def c_define(self, writer): writer.write("typedef enum ") writer.write(self.c_name()) writer.begin_block() values = list(self.names.keys()) values.sort() current_default = 0 for i in values: writer.write(self.c_enumname(i)) if i != current_default: writer.write(" = %d" % (i)) writer.write(",") writer.newline() current_default = i + 1 writer.newline() writer.write(codegen.prefix_underscore_upper(self.name.upper(), "ENUM_END")) writer.newline() writer.end_block(newline=False) writer.write(" ") writer.write(self.c_name()) writer.write(";") writer.newline() writer.newline() class FlagsType(EnumBaseType): def __init__(self, bits, name, flags, attribute_list): Type.__init__(self) self.bits = bits self.name = name last = -1 names = {} values = {} for v in flags: name = v[0] if len(v) > 1: value = v[1] else: value = last + 1 last = value assert value not in names names[value] = name values[name] = value self.names = names self.values = values self.attributes = fix_attributes(attribute_list) def __str__(self): return "flags %s" % self.name def c_define(self, writer): writer.write("typedef enum ") writer.write(self.c_name()) writer.begin_block() values = list(self.names.keys()) values.sort() mask = 0 for i in values: writer.write(self.c_enumname(i)) mask = mask | (1< 0 def is_image_size_length(self): if isinstance(self.size, int) or isinstance(self.size, str): return False return self.size[0] == "image_size" def is_bytes_length(self): if isinstance(self.size, int) or isinstance(self.size, str): return False return self.size[0] == "bytes" def is_cstring_length(self): if isinstance(self.size, int) or isinstance(self.size, str): return False return self.size[0] == "cstring" def is_fixed_sizeof(self): return self.is_constant_length() and self.element_type.is_fixed_sizeof() def is_fixed_nw_size(self): return self.is_constant_length() and self.element_type.is_fixed_nw_size() def get_fixed_nw_size(self): if not self.is_fixed_nw_size(): raise Exception("Not a fixed size type") return self.element_type.get_fixed_nw_size() * self.size def get_num_pointers(self): element_count = self.element_type.get_num_pointers() if element_count == 0: return 0 if self.is_constant_length(self): return element_count * self.size raise Exception("Pointers in dynamic arrays not supported") def get_pointer_names(self, marshalled): element_count = self.element_type.get_num_pointers() if element_count == 0: return [] raise Exception("Pointer names in arrays not supported") def is_extra_size(self): return self.has_attr("ptr_array") def contains_extra_size(self): return self.element_type.contains_extra_size() or self.has_attr("chunk") def sizeof(self): return "%s * %s" % (self.element_type.sizeof(), self.size) def c_type(self): return self.element_type.c_type() class PointerType(Type): def __init__(self, target_type): Type.__init__(self) self.name = None self.target_type = target_type self.pointer_size = default_pointer_size def __str__(self): return "%s*" % (str(self.target_type)) def resolve(self): self.target_type = self.target_type.resolve() return self def set_ptr_size(self, new_size): self.pointer_size = new_size def is_fixed_nw_size(self): return True def is_primitive(self): return True def primitive_type(self): if self.pointer_size == 4: return "uint32" else: return "uint64" def get_fixed_nw_size(self): return self.pointer_size def c_type(self): if self.pointer_size == 4: return "uint32_t" else: return "uint64_t" def contains_extra_size(self): return True def get_num_pointers(self): return 1 class Containee: def __init__(self): self.attributes = {} def is_switch(self): return False def is_pointer(self): return not self.is_switch() and self.member_type.is_pointer() def is_array(self): return not self.is_switch() and self.member_type.is_array() def is_struct(self): return not self.is_switch() and self.member_type.is_struct() def is_primitive(self): return not self.is_switch() and self.member_type.is_primitive() def has_attr(self, name): if not name in valid_attributes: raise Exception('attribute %s not expected' % name) return name in self.attributes def has_minor_attr(self): return self.has_attr("minor") def has_end_attr(self): return self.has_attr("end") def get_minor_attr(self): return self.attributes["minor"][0] class Member(Containee): def __init__(self, name, member_type, attribute_list): Containee.__init__(self) self.name = name self.member_type = member_type self.attributes = fix_attributes(attribute_list) def resolve(self, container): self.container = container self.member_type = self.member_type.resolve() self.member_type.register() for i in propagated_attributes: if self.has_attr(i): self.member_type.attributes[i] = self.attributes[i] return self def contains_member(self, member): return self.member_type.contains_member(member) def is_primitive(self): return self.member_type.is_primitive() def is_fixed_sizeof(self): if self.has_end_attr(): return False return self.member_type.is_fixed_sizeof() def is_extra_size(self): return self.has_end_attr() or self.has_attr("to_ptr") or self.member_type.is_extra_size() def is_fixed_nw_size(self): if self.has_attr("virtual"): return True return self.member_type.is_fixed_nw_size() def get_fixed_nw_size(self): if self.has_attr("virtual"): return 0 size = self.member_type.get_fixed_nw_size() if self.has_minor_attr(): minor = self.get_minor_attr() size = FixedSize(size, minor) return size def contains_extra_size(self): return self.member_type.contains_extra_size() def sizeof(self): return self.member_type.sizeof() def __repr__(self): return "%s (%s)" % (str(self.name), str(self.member_type)) def get_num_pointers(self): if self.has_attr("to_ptr"): return 1 return self.member_type.get_num_pointers() def get_pointer_names(self, marshalled): if self.member_type.is_pointer(): if self.has_attr("marshall") == marshalled: names = [self.name] else: names = [] else: names = self.member_type.get_pointer_names(marshalled) if self.has_attr("outvar"): prefix = self.attributes["outvar"][0] names = [prefix + "_" + name for name in names] return names class SwitchCase: def __init__(self, values, member): self.values = values self.member = member self.members = [member] def get_check(self, var_cname, var_type): checks = [] for v in self.values: if v == None: return "1" elif var_type.is_enum(): checks.append("%s == %s" % (var_cname, var_type.c_enumname_by_name(v[1]))) else: checks.append("%s(%s & %s)" % (v[0], var_cname, var_type.c_enumname_by_name(v[1]))) return " || ".join(checks) def resolve(self, container): self.switch = container self.member = self.member.resolve(self) return self def get_num_pointers(self): return self.member.get_num_pointers() def get_pointer_names(self, marshalled): return self.member.get_pointer_names(marshalled) class Switch(Containee): def __init__(self, variable, cases, name, attribute_list): Containee.__init__(self) self.variable = variable self.name = name self.cases = cases self.attributes = fix_attributes(attribute_list) def is_switch(self): return True def lookup_case_member(self, name): for c in self.cases: if c.member.name == name: return c.member return None def has_switch_member(self, member): for c in self.cases: if c.member == member: return True return False def resolve(self, container): self.container = container self.cases = [c.resolve(self) for c in self.cases] return self def __repr__(self): return "switch on %s %s" % (str(self.variable),str(self.name)) def is_fixed_sizeof(self): # Kinda weird, but we're unlikely to have a real struct if there is an @end if self.has_end_attr(): return False return True def is_fixed_nw_size(self): if self.has_attr("fixedsize"): return True size = None has_default = False for c in self.cases: for v in c.values: if v == None: has_default = True if not c.member.is_fixed_nw_size(): return False if size == None: size = c.member.get_fixed_nw_size() elif size != c.member.get_fixed_nw_size(): return False # Fixed size if all elements listed, or has default if has_default: return True key = self.container.lookup_member(self.variable) return len(self.cases) == len(key.member_type.values) def is_extra_size(self): return self.has_end_attr() def contains_extra_size(self): for c in self.cases: if c.member.is_extra_size(): return True if c.member.contains_extra_size(): return True return False def get_fixed_nw_size(self): if not self.is_fixed_nw_size(): raise Exception("Not a fixed size type") size = 0 for c in self.cases: size = max(size, c.member.get_fixed_nw_size()) return size def sizeof(self): return "sizeof(((%s *)NULL)->%s)" % (self.container.c_type(), self.name) def contains_member(self, member): return False # TODO: Don't support switch deep member lookup yet def get_num_pointers(self): count = 0 for c in self.cases: count = max(count, c.get_num_pointers()) return count def get_pointer_names(self, marshalled): names = [] for c in self.cases: names = names + c.get_pointer_names(marshalled) return names class ContainerType(Type): def is_fixed_sizeof(self): for m in self.members: if not m.is_fixed_sizeof(): return False return True def contains_extra_size(self): for m in self.members: if m.is_extra_size(): return True if m.contains_extra_size(): return True return False def is_fixed_nw_size(self): for i in self.members: if not i.is_fixed_nw_size(): return False return True def get_fixed_nw_size(self): size = 0 for i in self.members: size = size + i.get_fixed_nw_size() return size def contains_member(self, member): for m in self.members: if m == member or m.contains_member(member): return True return False def get_fixed_nw_offset(self, member): size = 0 for i in self.members: if i == member: break if i.contains_member(member): size = size + i.member_type.get_fixed_nw_offset(member) break if i.is_fixed_nw_size(): size = size + i.get_fixed_nw_size() return size def resolve(self): self.members = [m.resolve(self) for m in self.members] return self def get_num_pointers(self): count = 0 for m in self.members: count = count + m.get_num_pointers() return count def get_pointer_names(self, marshalled): names = [] for m in self.members: names = names + m.get_pointer_names(marshalled) return names def get_nw_offset(self, member, prefix = "", postfix = ""): fixed = self.get_fixed_nw_offset(member) v = [] container = self while container != None: members = container.members container = None for m in members: if m == member: break if m.contains_member(member): container = m.member_type break if m.is_switch() and m.has_switch_member(member): break if not m.is_fixed_nw_size(): v.append(prefix + m.name + postfix) if len(v) > 0: return str(fixed) + " + " + (" + ".join(v)) else: return str(fixed) def lookup_member(self, name): dot = name.find('.') rest = None if dot >= 0: rest = name[dot+1:] name = name[:dot] member = None if name in self.members_by_name: member = self.members_by_name[name] else: for m in self.members: if m.is_switch(): member = m.lookup_case_member(name) if member != None: break if member != None: break if member == None: raise Exception("No member called %s found" % name) if rest != None: return member.member_type.lookup_member(rest) return member class StructType(ContainerType): def __init__(self, name, members, attribute_list): Type.__init__(self) self.name = name self.members = members self.members_by_name = {} for m in members: self.members_by_name[m.name] = m self.attributes = fix_attributes(attribute_list) def __str__(self): if self.name == None: return "anonymous struct" else: return "struct %s" % self.name def c_type(self): if self.has_attr("ctype"): return self.attributes["ctype"][0] return codegen.prefix_camel(self.name) class MessageType(ContainerType): def __init__(self, name, members, attribute_list): Type.__init__(self) self.name = name self.members = members self.members_by_name = {} for m in members: self.members_by_name[m.name] = m self.reverse_members = {} # ChannelMembers referencing this message self.attributes = fix_attributes(attribute_list) def __str__(self): if self.name == None: return "anonymous message" else: return "message %s" % self.name def c_name(self): if self.name == None: cms = list(self.reverse_members.keys()) if len(cms) != 1: raise "Unknown typename for message" cm = cms[0] channelname = cm.channel.member_name if channelname == None: channelname = "" else: channelname = channelname + "_" if cm.is_server: return "msg_" + channelname + cm.name else: return "msgc_" + channelname + cm.name else: return codegen.prefix_camel("Msg", self.name) def c_type(self): if self.has_attr("ctype"): return self.attributes["ctype"][0] if self.name == None: cms = list(self.reverse_members.keys()) if len(cms) != 1: raise "Unknown typename for message" cm = cms[0] channelname = cm.channel.member_name if channelname == None: channelname = "" if cm.is_server: return codegen.prefix_camel("Msg", channelname, cm.name) else: return codegen.prefix_camel("Msgc", channelname, cm.name) else: return codegen.prefix_camel("Msg", self.name) class ChannelMember(Containee): def __init__(self, name, message_type, value): Containee.__init__(self) self.name = name self.message_type = message_type self.value = value def resolve(self, channel): self.channel = channel self.message_type = self.message_type.resolve() self.message_type.reverse_members[self] = 1 return self def __repr__(self): return "%s (%s)" % (str(self.name), str(self.message_type)) class ChannelType(Type): def __init__(self, name, base, members, attribute_list): Type.__init__(self) self.name = name self.base = base self.member_name = None self.members = members self.attributes = fix_attributes(attribute_list) def __str__(self): if self.name == None: return "anonymous channel" else: return "channel %s" % self.name def is_fixed_nw_size(self): return False def get_client_message(self, name): return self.client_messages_byname[name] def get_server_message(self, name): return self.server_messages_byname[name] def resolve(self): if self.base != None: self.base = self.base.resolve() server_messages = self.base.server_messages[:] server_messages_byname = self.base.server_messages_byname.copy() client_messages = self.base.client_messages[:] client_messages_byname = self.base.client_messages_byname.copy() # Set default member_name, FooChannel -> foo self.member_name = self.name[:-7].lower() else: server_messages = [] server_messages_byname = {} client_messages = [] client_messages_byname = {} server_count = 1 client_count = 1 server = True for m in self.members: if m == "server": server = True elif m == "client": server = False elif server: m.is_server = True m = m.resolve(self) if m.value: server_count = m.value + 1 else: m.value = server_count server_count = server_count + 1 server_messages.append(m) server_messages_byname[m.name] = m else: m.is_server = False m = m.resolve(self) if m.value: client_count = m.value + 1 else: m.value = client_count client_count = client_count + 1 client_messages.append(m) client_messages_byname[m.name] = m self.server_messages = server_messages self.server_messages_byname = server_messages_byname self.client_messages = client_messages self.client_messages_byname = client_messages_byname return self class ProtocolMember: def __init__(self, name, channel_type, value): self.name = name self.channel_type = channel_type self.value = value def resolve(self, protocol): self.channel_type = self.channel_type.resolve() self.channel_type.member_name = self.name return self def __repr__(self): return "%s (%s)" % (str(self.name), str(self.channel_type)) class ProtocolType(Type): def __init__(self, name, channels): Type.__init__(self) self.name = name self.channels = channels def __str__(self): if self.name == None: return "anonymous protocol" else: return "protocol %s" % self.name def is_fixed_nw_size(self): return False def resolve(self): count = 1 for m in self.channels: m = m.resolve(self) if m.value: count = m.value + 1 else: m.value = count count = count + 1 return self int8 = IntegerType(8, True) uint8 = IntegerType(8, False) int16 = IntegerType(16, True) uint16 = IntegerType(16, False) int32 = IntegerType(32, True) uint32 = IntegerType(32, False) int64 = IntegerType(64, True) uint64 = IntegerType(64, False)