def test_length_and_value_reference(self): # Test a length reference and a value reference to the same entry. a = Field('a', length=8) c = Field('c', length=parse('len{a}')) d = Field('d', length=parse('${a}')) b = Sequence('b', [a, c, d]) # Lets just try a quick decode to make sure we've specified it ok... #list(b.decode(Data('\x08cd'))) # Now test the parameters being passed around. lookup = ExpressionParameters([b]) self.assertEqual([Param('a', Param.OUT, _Integer()), Param('a length', Param.OUT, _Integer())], lookup.get_params(a)) self.assertEqual([Param('a', Param.OUT, _Integer()), Param('a length', Param.OUT, _Integer())], list(lookup.get_passed_variables(b, b.children[0]))) self.assertEqual([Param('a length', Param.IN, _Integer())], list(lookup.get_passed_variables(b, b.children[1]))) self.assertEqual([Param('a length', Param.IN, _Integer())], lookup.get_params(c)) self.assertEqual([Local('a', _Integer()), Local('a length', _Integer())], lookup.get_locals(b)) self.assertTrue(lookup.is_length_referenced(a))
def test_in_and_out_parameters(self): # Test what happens when we have a parameter that is to be passed out # of an entry, but also into a child (issue122). # # ___ e ___ # __c__ d(len=a) # a b(len=a) a = Field('a', length=8) b = Field('b', length=parse('${a}')) c = Sequence('c', [a, b]) d = Field('d', length=parse('${c.a}')) e = Sequence('e', [c, d]) lookup = ExpressionParameters([e]) self.assertEqual([], lookup.get_params(e)) self.assertEqual([Param('a', Param.OUT, _Integer())], lookup.get_params(a)) self.assertEqual([Param('a', Param.OUT, _Integer())], lookup.get_params(c)) self.assertEqual([Param('a', Param.IN, _Integer())], lookup.get_params(b)) self.assertEqual([Param('c.a', Param.IN, _Integer())], lookup.get_params(d)) self.assertEqual([Local('c.a', _Integer())], lookup.get_locals(e)) self.assertEqual([], lookup.get_locals(c)) self.assertTrue(lookup.is_value_referenced(a)) self.assertEqual([Param('a', Param.IN, _Integer())], list(lookup.get_passed_variables(c, c.children[1]))) self.assertEqual([Param('c.a', Param.IN, _Integer())], list(lookup.get_passed_variables(e, e.children[1])))
def test_integer_with_variable_length(self): a = Sequence('a', [ Field('length:', length=8 ), Field('b:', format=Field.INTEGER, length=parse('${length:} * 8'))], value=parse('${b:}')) self.assertEqual('\x01\x00', encode(a, 0).bytes()) self.assertEqual('\x01\xff', encode(a, 255).bytes()) self.assertEqual('\x02\xff\xff', encode(a, 65535).bytes())
def test_length_reference(self): # Test that we can correctly encode entries that use length references a = Sequence('a', [ Field('packet length:', length=8), Field('data length:', length=8), Field('data', length=parse('${data length:} * 8'), format=Field.TEXT), Field('unused', length=parse('${packet length:} * 8 - len{data}'), format=Field.TEXT)]) self.assertEqual('\x05\x03aaabb', encode(a, {'data':'aaa', 'unused':'bb'}).bytes())
def test_two_digits(self): # Tests a common case of representing text digits digit = Sequence('digit', [Field('char:', length=8, constraints=[Minimum(48), Maximum(57)])], value = parse('${char:} - 48')) two_digits = Sequence('two digits', [Child('digit 1:', digit), Child('digit 2:', digit)], value=parse('${digit 1:} * 10 + ${digit 2:}')) self.assertEqual({'${digit 1:}':6, '${digit 2:}' : 7}, _solve(two_digits, None, 67))
def test_subtract(self): a = Sequence('a', [ Field('total length:', length=8), Field('partial length:', length=8), Field('data:', length=parse('${partial length:} * 8')), Sequence('unused', [], value=parse('${total length:} * 8 - len{data:}')), ]) self.assertEqual({'len{data:}': 800}, _solve(a, 3, 0, {'total length:':100}))
def test_complex_length_reference(self): # Here we try to encode a complex length reference that includes a # length reference a = Sequence('a', [ Field('packet length:', length=8, format=Field.INTEGER), Field('header length:', length=8, format=Field.INTEGER), Field('header', length=parse('${header length:} * 8'), format=Field.TEXT), Field('packet', length=parse('${packet length:} * 8 - len{header}'), format=Field.TEXT)]) self.assertEqual('\x06\x02hhpppp', encode(a, {'header':'hh', 'packet':'pppp'}).bytes())
def test_integer_with_fixed_length_reference(self): # This sort of a construct is used in the presense specifications a = Sequence('a', [ Sequence('length:', [], value=parse('16')), Field('b:', format=Field.INTEGER, length=parse('${length:}'))], value=parse('${b:}')) self.assertEqual('\x00\x00', encode(a, 0).bytes()) self.assertEqual('\x00\xff', encode(a, 255).bytes()) self.assertEqual('\xff\xff', encode(a, 65535).bytes())
def test_referencing_implicit_length(self): # There was a problem when encoding length references to entries that # didn't have an explicit length. Test this. a = Sequence('a', [ Field('length:', length=8), Sequence('b', [ Field('b1 length:', length=8), Field('b1', length=parse('${b1 length:} * 8'), format=Field.TEXT)]), Field('unused:', length=parse('${length:} * 8 - len{b}')) ]) self.assertEqual('\x05\x04abcd', encode(a, {'b':{'b1':'abcd'}}).bytes())
def test_sequence_with_referenced_value(self): a = Field('a', length=8) b = Sequence('b', [Child('b:', a)], value=parse('${b:}')) c = Field('c', length=parse('${b} * 8')) d = Sequence('d', [a, b, c]) lookup = ExpressionParameters([a, d]) self.assertEqual([Local('b:', _Integer())], lookup.get_locals(b)) self.assertEqual([Param('a', Param.OUT, _Integer())], lookup.get_params(a)) self.assertEqual([Param('b', Param.OUT, _Integer())], lookup.get_params(b)) self.assertEqual([Param('b', Param.IN, _Integer())], lookup.get_params(c)) self.assertEqual([], lookup.get_params(d))
def test_value_of_variable_length_binary_field(self): number = Sequence('b:', [ Field('len:', length=8), #Field('value:', length=parse('${len:} * 8'), format=Field.INTEGER) ], Field('value:', length=parse('${len:} * 8'))], value=parse('${value:}')) a = Sequence('a', [ number, Sequence('c', [], value=parse('${b:}')) ]) self.assertEqual('\x01\x00', encode(a, {'c':0}).bytes()) self.assertEqual('\x02\x10\xff', encode(a, {'c':0x10ff}).bytes())
def test_secondary_dependency(self): # Test that when A depends on C, and B depends on A, we don't attempt # to encode B before A. blah = Sequence('blah', [ Sequence('a', [ Field('a1', 8, format=Field.INTEGER), Field('a2:', 8, format=Field.INTEGER)]), Field('b', parse('${a.a1} * 8'), format=Field.TEXT), Field('c', parse('${a.a2:} * 8'), format=Field.TEXT)]) self.assertEqual('\x03\x02xyzst', encode(blah, { 'a' : {'a1' : 3}, 'b' : 'xyz', 'c' : 'st'}).bytes())
def test_hidden_detection(self): # Test the parameter passing when the common entry is first found # in a hidden context. There was a bug if a common entry was initially # found 'hidden', it would always be treated as hidden. This tests it # by creating a common entry that is referenced in two places, once # hidden, once not. For the test to pass the code must be dealing # correctly with the parameters. a = Field('a', length=8, format=Field.INTEGER) b = Sequence('b', [ Sequence('c:', [a]), Field('d', length=parse('${c:.a} * 8'), format=Field.TEXT), a, Field('e', length=parse('${a} * 8'), format=Field.TEXT)]) self.assertEqual('\x02dd\x03eee', encode(b, {'d':'dd', 'a':3, 'e':'eee'}).bytes())
def test_renamed_common_reference(self): text_digit = Field('text digit', 8, constraints=[Minimum(48), Maximum(58)]) digit = Sequence('digit', [text_digit], value=parse("${text digit} - 48")) b = Sequence('b', [ Child('length', digit), Field('data', length=parse("${length} * 8"))]) lookup = ExpressionParameters([b]) self.assertEqual([], lookup.get_params(b)) self.assertEqual([Param('digit', Param.OUT, _Integer())], lookup.get_params(digit)) self.assertEqual([Param('length', Param.OUT, _Integer())], list(lookup.get_passed_variables(b, b.children[0])))
def test_multi_digit_encode(self): # Test encoding a multiple text digit entry digit = Sequence('digit', [Field('char:', length=8, constraints=[Minimum(48), Maximum(57)])], value=parse('${char:} - 48')) two_digits = Sequence('two digits', [ Child('digit 1:', digit), Child('digit 2:', digit)], value=parse('${digit 1:} * 10 + ${digit 2:}')) four_digits = Sequence('four digits', [ Child('digits 1:', two_digits), Child('digits 2:', two_digits)], value=parse('${digits 1:} * 100 + ${digits 2:}')) self.assertEqual('12', encode(two_digits, 12).bytes()) self.assertEqual('1234', encode(four_digits, 1234).bytes()) self.assertEqual('7632', encode(four_digits, 7632).bytes())
def test_single_value_multiply(self): # Test when the expression is the value of an entry multipled by something a = Sequence('a', [ Field('b', length=8), Sequence('c', [], value=parse('${b} * 2')), ]) self.assertEqual({'${b}':4}, _solve(a, 1, 8))
def test_single_value(self): # Test when the expression is the value of an entry a = Sequence('a', [ Field('b', length=8), Sequence('c', [], value=parse('${b}')), ]) self.assertEqual({'${b}':5}, _solve(a, 1, 5))
def test_encode_hidden_count(self): # Test that we correctly encode a hidden count a = Sequence('a', [ Field('count:', length=8), SequenceOf('c', Field('d', length=8, format=Field.TEXT), count=parse("${count:}")), ]) self.assertEqual('\x03abc', encode(a, {'c' : ['a', 'b', 'c']}).bytes())
def test_length_reference(self): a = Sequence('a', [ Field('data length:', length=8), Field('b', length=16), Sequence('footer length', [], value=parse('${data length:} * 8 - len{b}'))]) # We now try to solve 'data length:' given that we know the value for 'b length' self.assertEqual({'${data length:}' : 5}, _solve(a, 2, 24, {'b length':16}))
def test_unicode_field(self): a = Sequence('a', [ Field('b:', length=8), Field('c', format=Field.TEXT, length=parse('${b:} * 8'), encoding='utf8') ]) self.assertEqual('\x0c\xe3\x83\xad\xe3\x82\xb0\xe3\x82\xa4\xe3\x83\xb3', encode(a, {'c':u'ログイン'}).bytes())
def test_encode_zero_length_hex_field(self): a = Sequence('a', [ Field('b:', length=8), Field('c', format=Field.HEX, length=parse('${b:} * 8')) ]) self.assertEqual('\x02ab', encode(a, {'c':'6162'}).bytes()) self.assertEqual('\x00', encode(a, {'c':''}).bytes())
def test_hidden_sequence_with_input_param(self): # Here we have a hidden entry that will have to be mocked, but still # requires that data is passed in. c = Sequence('a', [ Child('b:', Sequence('b', [Field('c', length=8)])), Sequence('d', [], value=parse('${b:.c}'))]) self.assertEqual('\x09', encode(c, {'d':9}).bytes())
def test_encode_reference(self): # Test that we correctly encode a reference a = Sequence('a', [ Field("b:", 8, format=Field.INTEGER), Sequence('c', [], value=parse('${b:}'))]) self.assertEqual("\x01", encode(a, {"c" : 0x01}).bytes())
def test_referenced_field_length(self): a = Field('a', length=4) b = Sequence('b', [], value=parse('len{a} * 8 + 4')) c = Sequence('c', [a, b]) params = ExpressionParameters([c]) range = EntryValueType(b).range(params) self.assertEqual(36, range.min) self.assertEqual(36, range.max)
def test_recursive_type(self): variable_length_integer = Choice('variable length integer', []) variable_length_integer.children = [ Sequence('final byte:', [ Field(None, length=1, constraints=[Equals(0)]), Field('value:', length=7)], value=parse('${value:}')), Sequence('intermediate byte:', [ Field(None, length=1, constraints=[Equals(1)]), Field('value:', length=7), Child('next:', variable_length_integer)], value=parse('(${value:} << 7) + ${next:}'))] params = ExpressionParameters([variable_length_integer]) range = EntryValueType(variable_length_integer).range(params) self.assertEqual(None, range.min) self.assertEqual(None, range.max)
def test_little_endian(self): # Test that we can break apart a little endian style number a = Sequence('a', [ Field('b1', length=8), Field('b2', length=8), Sequence('c', [], value=parse('(${b2} << 8) + ${b1} ')), ]) self.assertEqual({'${b1}':0x34, '${b2}':0x12}, _solve(a, 2, 0x1234))
def test_single_value_bimdas(self): # Test that we correctly resolve the value when there are addition # and multiplication involved. a = Sequence('a', [ Field('b', length=8), Sequence('c', [], value=parse('(${b} + 3) * 8')), ]) self.assertEqual({'${b}':5}, _solve(a, 1, 64))
def test_single_value_addition(self): # Test that we correctly resolve the correct value when there is an # addition involved a = Sequence('a', [ Field('b', length=8), Sequence('c', [], value=parse('${b} + 3')), ]) self.assertEqual({'${b}':7}, _solve(a, 1, 10))
def test_divide(self): a = Sequence('a', [ Field('b', length=8), Sequence('c', [], value=parse('${b} / 2')), ]) # TODO: What should we do in this case? This is a lossy conversion... See issue246. #self.assertEqual({'${b}':20}, _solve(a, 1, 10)) self.assertRaises(SolverError, _solve, a, 1, 10)
def test_same_reference_multiple_times(self): a = Sequence('a', [ Field('b', length=8), Sequence('c', [], value=parse('${b} + ${b}')), ]) # TODO: We should correctly be able to invert 'c = b + b'. See issue245. #self.assertEqual({'${b}':7}, _solve(a, 1, 14)) self.assertRaises(SolverError, _solve, a, 1, 14)