def test_from_bytearray(self): self.assert_length_value(8, b'\x00', BitString.from_bytearray(b'\x00')) self.assert_length_value(16, b'ab', BitString.from_bytearray(b'ab')) self.assert_length_value( 16, b'ab', BitString.from_bytearray(bytearray(b'ab')))
def test_from_sequence(self): self.assert_length_value( 8, b'\x01', BitString.from_sequence([1], BitString.from_byte)) self.assert_error( lambda: BitString.from_sequence([256], BitString.from_byte)) self.assert_length_value( 16, b'\x01\x02', BitString.from_sequence([1, 2], BitString.from_byte))
def dispatch_table(big_endian=True, encoding=None, errors=STRICT): ''' Convert types appropriately. ''' # pylint: disable-msg=W0108 # consistency return {int: lambda n: BitString.from_int(n, ordered=big_endian), str: lambda s: BitString.from_str(s, encoding, errors), bytes: lambda b: BitString.from_bytearray(b), bytearray: lambda b: BitString.from_bytearray(b), BitString: lambda x: x}
def test_add(self): acc = BitString() for i in range(8): acc += BitString.from_int('0o' + str(i)) # >>> hex(0o76543210) # '0xfac688' self.assert_length_value(24, b'\x88\xc6\xfa', acc) acc = BitString() for i in range(7): acc += BitString.from_int('0o' + str(i)) self.assert_length_value(21, b'\x88\xc6\x1a', acc)
def dispatch_table(big_endian=True, encoding=None, errors=STRICT): ''' Convert types appropriately. ''' # pylint: disable-msg=W0108 # consistency return { int: lambda n: BitString.from_int(n, ordered=big_endian), str: lambda s: BitString.from_str(s, encoding, errors), bytes: lambda b: BitString.from_bytearray(b), bytearray: lambda b: BitString.from_bytearray(b), BitString: lambda x: x }
def assert_round_trip(self, start, stop=None, length=None): if stop is None: stop = start result = BitString.from_int(start, length=length).to_int() assert result == stop, (result, stop) if length is not None: assert len(result) == length, (result, length)
def matcher(value=None): """ Generate the matcher, given a value. """ if value is None: return LEnd(length) else: return _Constant(BitString.from_int(value, length=length, big_endian=False))
def Byte(value=None): ''' Match or read a byte (if a value is given, it must match). ''' if value is None: return BEnd(8) else: return _Constant(BitString.from_byte(value))
def String(value, encoding=None, errors=STRICT): """ Match or read a string (to read a value, give the number of bytes). """ if isinstance(value, int): return _String(value, encoding=encoding, errors=errors) else: return _Constant(BitString.from_str(value, encoding=encoding, errors=errors))
def ByteArray(value): ''' Match or read an array of bytes (to read a value, give the number of bytes). ''' if isinstance(value, int): return _ByteArray(value) else: return _Constant(BitString.from_bytearray(value))
def matcher(value=None): ''' Generate the matcher, given a value. ''' if value is None: return LEnd(length) else: return _Constant( BitString.from_int(value, length=length, big_endian=False))
def String(value, encoding=None, errors=STRICT): ''' Match or read a string (to read a value, give the number of bytes). ''' if isinstance(value, int): return _String(value, encoding=encoding, errors=errors) else: return _Constant( BitString.from_str(value, encoding=encoding, errors=errors))
def matcher(value=None): ''' Generate the matcher, given a value. ''' if value is None: return BEnd(length) else: return _Constant(BitString.from_int(value, length=length, big_endian=True))
def test_get_item(self): a = BitString.from_int('01001100011100001111b0') b = a[:] assert a == b, (a, b) b = a[0:] assert a == b, (a, b) b = a[-1::-1] assert BitString.from_int('11110000111000110010b0') == b, b b = a[0] assert BitString.from_int('0b0') == b, (b, str(b), BitString.from_int('0b0')) b = a[1] assert BitString.from_int('1b0') == b, b b = a[0:2] assert BitString.from_int('01b0') == b, b b = a[0:2] assert BitString.from_int('0b10') == b, b b = a[-5:] assert BitString.from_int('01111b0') == b, b b = a[-1:-6:-1] assert BitString.from_int('11110b0') == b, b b = a[1:-1] assert BitString.from_int('100110001110000111b0') == b, b
def test_encode(self): mac = parse(''' Frame( Header( preamble = 0b10101010*7, start = 0b10101011, destn = 010203040506x0, source = 0708090a0b0cx0, ethertype = 0800x0 ), Data(1/8,2/8,3/8,4/8), CRC(234d0/4.) ) ''') serial = simple_serialiser(mac, dispatch_table()) bs = serial.bytes() for _index in range(7): b = next(bs) assert b == BitString.from_int('0b10101010').to_int(), b b = next(bs) assert b == BitString.from_int('0b10101011').to_int(), b
def test_invert(self): #basicConfig(level=DEBUG) self.assert_length_value(12, b'\x00\x0c', ~BitString.from_int('0x3ff'))
def test_str(self): b = BitString.from_int32(0xabcd1234) assert str(b) == '00101100 01001000 10110011 11010101b0/32', str(b) b = BitString.from_int('0b110') assert str(b) == '011b0/3', str(b)
def test_from_int_with_length(self): self.assert_error(lambda: BitString.from_int(1, 0)) self.assert_error(lambda: BitString.from_int(0, 1)) self.assert_error(lambda: BitString.from_int(0, 7)) self.assert_length_value(8, b'\x00', BitString.from_int(0, 8)) self.assert_error(lambda: BitString.from_int(0, 0.1)) self.assert_length_value(8, b'\x00', BitString.from_int(0, 1.)) self.assert_length_value(1, b'\x00', BitString.from_int('0x0', 1)) self.assert_length_value(7, b'\x00', BitString.from_int('0x0', 7)) self.assert_length_value(8, b'\x00', BitString.from_int('0x0', 8)) self.assert_length_value(1, b'\x00', BitString.from_int('0x0', 0.1)) self.assert_length_value(8, b'\x00', BitString.from_int('0x0', 1.)) self.assert_length_value(16, b'\x34\x12', BitString.from_int(0x1234, 16)) self.assert_length_value(16, b'\x34\x12', BitString.from_int('0x1234', 16)) self.assert_length_value(16, b'\x12\x34', BitString.from_int('1234x0', 16)) self.assert_length_value(16, b'\x34\x12', BitString.from_int('4660', 16)) self.assert_length_value(16, b'\x34\x12', BitString.from_int('0d4660', 16)) self.assert_length_value(16, b'\x12\x34', BitString.from_int('4660d0', 16))
def bassert(self, value, expected, length=None): x = BitString.from_int(expected, length) assert value == x, (value, x)
def make_binary_parser(): ''' Create a parser for binary data. ''' # avoid import loops from lepl import Word, Letter, Digit, UnsignedInteger, \ Regexp, DfaRegexp, Drop, Separator, Delayed, Optional, Any, First, \ args, Trace, TraceVariables from lepl.bin.bits import BitString from lepl.support.node import Node classes = {} def named_class(name, *args): ''' Given a name and some args, create a sub-class of Binary and create an instance with the given content. ''' if name not in classes: classes[name] = type(name, (Node,), {}) return classes[name](*args) with TraceVariables(False): mult = lambda l, n: BitString.from_sequence([l] * int(n, 0)) # an attribute or class name name = Word(Letter(), Letter() | Digit() | '_') # lengths can be integers (bits) or floats (bytes.bits) # but if we have a float, we do not want to parse as an int # (or we will get a conversion error due to too small length) length = First(UnsignedInteger() + '.' + Optional(UnsignedInteger()), UnsignedInteger()) # a literal decimal decimal = UnsignedInteger() # a binary number (without pre/postfix) binary = Any('01')[1:] # an octal number (without pre/postfix) octal = Any('01234567')[1:] # a hex number (without pre/postfix) hex_ = Regexp('[a-fA-F0-9]')[1:] # the letters used for binary, octal and hex values #(eg the 'x' in 0xffee) # pylint: disable-msg=C0103 b, o, x, d = Any('bB'), Any('oO'), Any('xX'), Any('dD') # a decimal with optional pre/postfix dec = '0' + d + decimal | decimal + d + '0' | decimal # little-endian literals have normal prefix syntax (eg 0xffee) little = decimal | '0' + (b + binary | o + octal | x + hex_) # big-endian literals have postfix (eg ffeex0) big = (binary + b | octal + o | hex_ + x) + '0' # optional spaces - will be ignored # (use DFA here because it's multi-line, so \n will match ok) spaces = Drop(DfaRegexp('[ \t\n\r]*')) with Separator(spaces): # the grammar is recursive - expressions can contain expressions - # so we use a delayed matcher here as a placeholder, so that we can # use them before they are defined. expr = Delayed() # an implicit length value can be big or little-endian ivalue = big | little > args(BitString.from_int) # a value with a length can also be decimal lvalue = (big | little | dec) & Drop('/') & length \ > args(BitString.from_int) value = lvalue | ivalue repeat = value & Drop('*') & little > args(mult) # a named value is also a tuple named = name & Drop('=') & (expr | value | repeat) > tuple # an entry in the expression could be any of these entry = named | value | repeat | expr # and an expression itself consists of a comma-separated list of # one or more entries, surrounded by paremtheses entries = Drop('(') & entry[1:, Drop(',')] & Drop(')') # the Binary node may be explicit or implicit and takes the list of # entries as an argument list node = Optional(Drop('Node')) & entries > Node # alternatively, we can give a name and create a named sub-class other = name & entries > args(named_class) # and finally, we "tie the knot" by giving a definition for the # delayed matcher we introduced earlier, which is either a binary # node or a subclass expr += spaces & (node | other) & spaces #expr = Trace(expr) # this changes order, making 0800x0 parse as binary expr.config.no_compile_to_regexp() # use sequence to force regexp over multiple lines return expr.get_parse_sequence()
def _assert(self, repr_, value): try: b = BitString.from_int(repr_) assert str(b) == value + 'b0/' + str(len(b)), str(b) except ValueError: assert value is None
def __init__(self, value, length=None): if not isinstance(value, BitString): value = BitString.from_int(value, length) super(Const, self).__init__(value)
def test_from_int(self): self.assert_length_value(3, b'\x00', BitString.from_int('0o0')) self.assert_error(lambda: BitString.from_int('1o0')) self.assert_error(lambda: BitString.from_int('00o0')) self.assert_error(lambda: BitString.from_int('100o0')) self.assert_error(lambda: BitString.from_int('777o0')) self.assert_length_value(9, b'\x40\x00', BitString.from_int('0o100')) self.assert_length_value(9, b'\xfe\x01', BitString.from_int('0o776')) self.assert_length_value(12, b'\xff\x03', BitString.from_int('0x3ff')) self.assert_length_value(12, b'\xff\x03', BitString.from_int('0o1777')) self.assert_length_value(16, b'\x03\xff', BitString.from_int('03ffx0')) self.assert_length_value(3, b'\x04', BitString.from_int('0b100')) self.assert_length_value(1, b'\x01', BitString.from_int('1b0')) self.assert_length_value(2, b'\x02', BitString.from_int('01b0')) self.assert_length_value(9, b'\x00\x01', BitString.from_int('000000001b0')) self.assert_length_value(9, b'\x01\x01', BitString.from_int('100000001b0')) self.assert_length_value(16, b'\x0f\x33', BitString.from_int('1111000011001100b0'))
def test_from_sequence(self): self.assert_length_value(8, b'\x01', BitString.from_sequence([1], BitString.from_byte)) self.assert_error(lambda: BitString.from_sequence([256], BitString.from_byte)) self.assert_length_value(16, b'\x01\x02', BitString.from_sequence([1,2], BitString.from_byte))
def test_from_bytearray(self): self.assert_length_value(8, b'\x00', BitString.from_bytearray(b'\x00')) self.assert_length_value(16, b'ab', BitString.from_bytearray(b'ab')) self.assert_length_value(16, b'ab', BitString.from_bytearray(bytearray(b'ab')))
def make_binary_parser(): ''' Create a parser for binary data. ''' # avoid import loops from lepl import Word, Letter, Digit, UnsignedInteger, \ Regexp, DfaRegexp, Drop, Separator, Delayed, Optional, Any, First, \ args, Trace, TraceVariables from lepl.bin.bits import BitString from lepl.support.node import Node classes = {} def named_class(name, *args): ''' Given a name and some args, create a sub-class of Binary and create an instance with the given content. ''' if name not in classes: classes[name] = type(name, (Node, ), {}) return classes[name](*args) with TraceVariables(False): mult = lambda l, n: BitString.from_sequence([l] * int(n, 0)) # an attribute or class name name = Word(Letter(), Letter() | Digit() | '_') # lengths can be integers (bits) or floats (bytes.bits) # but if we have a float, we do not want to parse as an int # (or we will get a conversion error due to too small length) length = First( UnsignedInteger() + '.' + Optional(UnsignedInteger()), UnsignedInteger()) # a literal decimal decimal = UnsignedInteger() # a binary number (without pre/postfix) binary = Any('01')[1:] # an octal number (without pre/postfix) octal = Any('01234567')[1:] # a hex number (without pre/postfix) hex_ = Regexp('[a-fA-F0-9]')[1:] # the letters used for binary, octal and hex values #(eg the 'x' in 0xffee) # pylint: disable-msg=C0103 b, o, x, d = Any('bB'), Any('oO'), Any('xX'), Any('dD') # a decimal with optional pre/postfix dec = '0' + d + decimal | decimal + d + '0' | decimal # little-endian literals have normal prefix syntax (eg 0xffee) little = decimal | '0' + (b + binary | o + octal | x + hex_) # big-endian literals have postfix (eg ffeex0) big = (binary + b | octal + o | hex_ + x) + '0' # optional spaces - will be ignored # (use DFA here because it's multi-line, so \n will match ok) spaces = Drop(DfaRegexp('[ \t\n\r]*')) with Separator(spaces): # the grammar is recursive - expressions can contain expressions - # so we use a delayed matcher here as a placeholder, so that we can # use them before they are defined. expr = Delayed() # an implicit length value can be big or little-endian ivalue = big | little > args(BitString.from_int) # a value with a length can also be decimal lvalue = (big | little | dec) & Drop('/') & length \ > args(BitString.from_int) value = lvalue | ivalue repeat = value & Drop('*') & little > args(mult) # a named value is also a tuple named = name & Drop('=') & (expr | value | repeat) > tuple # an entry in the expression could be any of these entry = named | value | repeat | expr # and an expression itself consists of a comma-separated list of # one or more entries, surrounded by paremtheses entries = Drop('(') & entry[1:, Drop(',')] & Drop(')') # the Binary node may be explicit or implicit and takes the list of # entries as an argument list node = Optional(Drop('Node')) & entries > Node # alternatively, we can give a name and create a named sub-class other = name & entries > args(named_class) # and finally, we "tie the knot" by giving a definition for the # delayed matcher we introduced earlier, which is either a binary # node or a subclass expr += spaces & (node | other) & spaces #expr = Trace(expr) # this changes order, making 0800x0 parse as binary expr.config.no_compile_to_regexp() # use sequence to force regexp over multiple lines return expr.get_parse_sequence()
def test_from_byte(self): self.assert_error(lambda: BitString.from_byte(-1)) self.assert_length_value(8, b'\x00', BitString.from_byte(0)) self.assert_length_value(8, b'\x01', BitString.from_byte(1)) self.assert_length_value(8, b'\xff', BitString.from_byte(255)) self.assert_error(lambda: BitString.from_byte(256))