#!/usr/bin/python import sys, re, signal, tempfile, os, os.path, shutil, atexit basedir=os.path.abspath(os.path.split(sys.argv[0])[0]) charsPerBit = 1.4 TEX_HEADER = r"""\documentclass{scrartcl} \usepackage[german]{babel} \usepackage[latin1]{inputenc} \usepackage[T1]{fontenc} \usepackage{ae,aecompl} \usepackage[active]{srcltx} \usepackage{color} \usepackage{bytefield} \pagestyle{empty} \begin{document} \sffamily """ PACKET_HEADER=r"""\begin{bytefield}{32} \bitheader{0-31} \\ """ PACKET_FOOTER=r"""\end{bytefield} \bigskip """ TEX_FOOTER = r"""\end{document} """ def formatField(width, start, size): areas = [] sz = width - start while size > 0: if sz > size: areas.append({'start': start, 'size': size}) size = 0 else: areas.append({'start': start, 'size': sz}) size -= sz sz = width start = 0 for i in range(len(areas)-1): if areas[i]['start'] < areas[i+1]['start']+areas[i+1]['size']: areas[i]['bottom'] = False areas[i+1]['top'] = False return areas def formatSimpleField(width, start, field): areas = formatField(width, start, field['size']) nameix = 0 namesz = 0 for i in range(len(areas)): if areas[i]['size'] > namesz: namesz = areas[i]['size'] nameix = i areas[nameix]['name'] = field['name'][:int(areas[nameix]['size'] * charsPerBit)] if len(areas) == 2 and areas[0].get('bottom',True): if areas[0].get('name','') : ix = 1 else : ix = 0 if 6 <= int(areas[ix]['size'] * charsPerBit): areas[ix]['name'] = '(cont)' return areas def formatPacket(width, fields): rows = [ [] ] start = 0 for field in fields: areas = [] if field.get('repeat', False): if start > 0 and start < width: areas.append({ 'start': start, 'size': width-start, 'bottom': False, 'right': False}) start = 0 if field.get('size',None): areas.extend(formatSimpleField(width, start, field)) start = areas[-1]['start'] + areas[-1]['size'] elif field.get('minsize', None): f = dict(field) f['size'] = field['minsize'] areas.extend(formatSimpleField(width, start, f)) start = areas[-1]['start'] + areas[-1]['size'] if start >= width : start = 0 addareas = formatField(width, start, field['maxsize'] - field['minsize']) for area in addareas: area['filled'] = True areas += addareas start = areas[-1]['start'] + areas[-1]['size'] if start > 0 and start < width: areas.append({ 'start': start, 'size': width-start, 'bottom': False, 'right': False}) start = 0 else: if start > 0 and start < width: areas.append({ 'start': start, 'size': width-start, 'bottom': False, 'right': False}) areas.extend([ { 'start': 0, 'size': width, 'bottom': False, 'name': field['name'] }, { 'start': 0, 'size': width, 'skip': True }, { 'start': 0, 'size': width, 'top': False } ]) start = 0 if field.get('optional', False): for area in areas: area['optional'] = True if start > 0 and start < width: areas.append({ 'start': start, 'size': width-start, 'bottom': False, 'right': False}) start = 0 if field.get('repeat'): if start > 0 and start < width: areas.append({ 'start': start, 'size': width-start, 'bottom': False, 'right': False}) start = 0 areas.append({ 'start': 0, 'size': width, 'dots': True }) da = areas[(areas[0].get('right', True) and (0,) or (1,))[0]:-1] for i in range(len(da)): if da[i].get('name','') : da[i] = dict(da[i]) del da[i]['name'] areas.extend(da) if start == width : start = 0 while areas: while areas and (not(rows[-1]) or rows[-1][-1]['start'] + rows[-1][-1]['size'] < width): if areas[0].get('right', True) == False: # This is a fillup field. Check, wether to draw top: if len(rows) <= 1: areas[0]['top'] = False elif rows[-2][-1].get('bottom', True) or not rows[-2][-1].get('right', True): areas[0]['top'] = False rows[-1].append(areas.pop(0)) if areas: rows.append([]) return rows def texquote(s): s = s.replace('_', '\\_') return s def makeTex(rows): lines = [] for row in rows: line = [] for area in row: sides="" if area.get('left', True) : sides += "l" if area.get('right', True) : sides += "r" if area.get('top', True) : sides += "t" if area.get('bottom', True) : sides += "b" if sides == "lrtb" : sides = "" else : sides = "[%s]" % sides if area.get('filled', False): line.append(r"\bitbox%s{%s}{\color[gray]{0.93}\rule{\width}{\height}}" % (sides, area['size'])) elif area.get('skip', False): line.append(r"\skippedwords") elif area.get('dots', False): line.append(r"\wordbox[]{1}{$\vdots$\\[1ex]}") else: name = texquote(area.get('name','')) if name and area.get('optional', False): name = "[%s]" % name line.append(r"\bitbox%s{%s}{\strut %s}" % (sides, area['size'], name)) lines.append(" & ".join(line)) return " \\\\\n".join(lines) + "\n" COMMENT_RE = re.compile(r'//.*?$|/\*.*?\*/|"(?:\\.|[^\\"])*"', re.S | re.M) def stripComments(text): def replacer(match): s = match.group(0) if s.startswith('//pkgdraw:'): return "@@" + s[2:] if s.startswith('/'): return "" return s return COMMENT_RE.sub(replacer, text) SENF_INCLUDE_RE = re.compile(r"#\s*include\s*SENF_") def quoteMacros(text): return SENF_INCLUDE_RE.sub("PKGDRAW_", text).replace("SENF_PARSER_","PKGDRAW_PARSER_") def cppExpand(text, cppopts, dir): tmpf = tempfile.NamedTemporaryFile(dir=dir) tmpf.write(text) tmpf.flush() cmd = "gcc %s -E -o - -x c++-header %s" % (" ".join(cppopts), tmpf.name) return os.popen(cmd).read() FIELD_TYPES = { 'UInt8Parser' : {'size': 8 }, 'UInt16Parser' : {'size': 16 }, 'UInt24Parser' : {'size': 24 }, 'UInt32Parser' : {'size': 32 }, 'UInt64Parser' : {'size': 64 }, 'Int8Parser' : {'size': 8 }, 'Int16Parser' : {'size': 16 }, 'Int24Parser' : {'size': 24 }, 'Int32Parser' : {'size': 32 }, 'Int64Parser' : {'size': 64 }, 'UInt16LSBParser' : {'size': 16 }, 'UInt24LSBParser' : {'size': 24 }, 'UInt32LSBParser' : {'size': 32 }, 'UInt64LSBParser' : {'size': 64 }, 'Int16LSBParser' : {'size': 16 }, 'Int24LSBParser' : {'size': 24 }, 'Int32LSBParser' : {'size': 32 }, 'Int64LSBParser' : {'size': 64 }, 'MACAddressParser': {'size': 48 }, 'INet4AddressParser' : {'size': 32 }, 'INet6AddressParser' : {'size': 128 }, 'VoidPacketParser' : {'size': 0 }, } def parse_FIELD(args, flags): args = [ arg.strip() for arg in args.split(',') ] if len(args) != 2: sys.stderr.write("Failed to parse FIELD: %s\n" % args) return None field = dict(FIELD_TYPES.get(args[1].split(':')[-1], {})) field['name'] = args[0] return field def parse_PRIVATE_FIELD(args, flags): return parse_FIELD(args, flags) def parse_FIELD_RO(args, flags): return parse_FIELD(args, flags) def parse_BITFIELD(args, flags): args = [ arg.strip() for arg in args.split(',') ] if len(args) != 3: sys.stderr.write("Failed to parse BITFIELD: %s\n" % args) return None try: size = int(args[1]) except ValueError: sys.stderr.write("Failed to parse BITFIELD: %s\n" % args) return None return { 'size' : size, 'name' : args[0] } def parse_PRIVATE_BITFIELD(args, flags): return parse_BITFIELD(args, flags) def parse_BITFIELD_RO(args, flags): return parse_BITFIELD(args, flags) def parse_SKIP(args, flags): args = args.split(',')[0] try: bytes = int(args.strip()) except ValueError: sys.stderr.write("Failed to parse SKIP: %s\n" % args) return None return { 'size': 8*bytes, 'name': '' } def parse_SKIP_BITS(args, flags): try: bits = int(args.strip()) except ValueError: sys.stderr.write("Failed to parse SKIP_BITS: %s\n" % args) return None return { 'size': bits, 'name': '' } def parse_VECTOR(args, flags): args = [ arg.strip() for arg in args.split(',') ] if len(args) < 3: sys.stderr.write("Failed to aprse VECTOR: %s\n" % args) return None field = dict(FIELD_TYPES.get(args[-1].split(':')[-1], {})) field['name'] = args[0] field['repeat'] = True return field def parse_LIST(args, flags): return parse_VECTOR(args, flags) VARIANT_FIELD_RE_STR = r""" \(\s*(?: ([a-zA-Z0-9_:]+) | id\(\s* [a-zA-Z0-9_]+\s*,\s* (?: ([a-zA-Z0-9_:]+) | key\(\s*[^,]*,\s*([a-zA-Z0-9_:]+)\s*\) )\s*\) | ids\(\s* [a-zA-Z0-9_]+\s*,\s* [a-zA-Z0-9_]+\s*,\s* [a-zA-Z0-9_]+\s*,\s* (?: ([a-zA-Z0-9_:]+) | key\(\s*[^,]*,\s*([a-zA-Z0-9_:]+)\s*\) )\s*\) | novalue\(\s* [a-zA-Z0-9_]+\s*,\s* (?: ([a-zA-Z0-9_:]+) | key\(\s*[^,]*,\s*([a-zA-Z0-9_:]+)\s*\) )\s*\) )\s*\) """ VARIANT_FIELD_RE = re.compile(VARIANT_FIELD_RE_STR, re.X) VARIANT_FIELDS_RE = re.compile(",\s*((?:%s\s*)+)$" % VARIANT_FIELD_RE_STR, re.X) def parse_VARIANT(args, flags): name = args.split(',',1)[0].strip() fields_match = VARIANT_FIELDS_RE.search(args) if not fields_match: return { 'name': name } fields_str = fields_match.group(1) optional = False minsize = None maxsize = None for field_match in VARIANT_FIELD_RE.finditer(fields_str): parser = ([ group for group in field_match.groups() if group ] + [ None ])[0] field = dict(FIELD_TYPES.get(parser.split(':')[-1], {})) if field.has_key('minsize'): if minsize is None or field['minsize'] < minsize: minsize = field['minsize'] if maxsize is None or field['maxsize'] > maxsize: maxsize = field['maxsize'] elif field.has_key('size'): if field['size'] == 0: optional = True else: if minsize is None or field['size'] < minsize: minsize = field['size'] if maxsize is None or field['size'] > maxsize: maxsize = field['size'] if minsize is not None and minsize == maxsize: return { 'name': name, 'size': minsize, 'optional': optional } elif minsize is not None: return { 'name': name, 'minsize': minsize, 'maxsize': maxsize, 'optional': optional } else: return { 'name': name, 'optional': optional } def parse_PRIVATE_VARIANT(args, flags): return parse_VARIANT(args, flags) def parse_INIT(args, flags): return None PARSER_START_RE = re.compile(r"PKGDRAW_(FIXED_)?PARSER\s*\(\s*\)") PARSER_END_RE = re.compile(r"PKGDRAW_PARSER_FINALIZE\s*\(([^)]*)\)\s*;") PARSER_FIELD_RE = re.compile(r"(?:@@>pkgdraw:(.*)$\s*)?PKGDRAW_PARSER_([A-Z_]+)\s*\(([^;]*)\)\s*;(?:\s*@@ [...] [-- ...]\n" % sys.argv[0]) sys.exit(1) source = args.pop(0) target = args.pop(0) while args and args[0] != '--' : names.append(args.pop(0)) if args : gccopts = args[1:] data, order = scanPackets(cppExpand(quoteMacros(stripComments(file(source).read())), gccopts, os.path.dirname(source))) texf = file(os.path.join(tmpdir, "fields.tex"),"w") texf.write(TEX_HEADER) if not names: order.reverse() names = order for name in names: texf.write("\\textbf{%s}\n\\bigskip\\par\n" % texquote(name)) texf.write(PACKET_HEADER) texf.write(makeTex(formatPacket(32, data[name]))) texf.write(PACKET_FOOTER) texf.write(TEX_FOOTER) texf.close() if os.system("cd %s; %s/textogif -png -dpi 80 -res 0.25 fields >pkgdraw.log 2>&1" % (tmpdir, basedir)) != 0: sys.stderr.write("Conversion failed. See %s\n" % tmpdir) os._exit(1) file(target,"w").write(file(os.path.join(tmpdir, "fields.png")).read())