def test_same_param_from_parent_and_siblings(self): # Test that we ask the parent for a parameter even when there is a # child with the same name (but _after_ the entry that needs it). a = Sequence('a', [ Sequence('expected', [], value=Constant(2)), Sequence('b', [ # This should come from the parent (ie: a.expected) Field('c', length=8, constraints=[Equals(ValueResult('expected'))]), Sequence('expected', [], value=Constant(1)), # This should come from the sibling (ie: b.expected) Field('d', length=8, constraints=[Equals(ValueResult('expected'))]), ]) ]) b = a.children[1].entry lookup = ExpressionParameters([a]) self.assertEqual([Param('expected', Param.IN, _Integer())], list(lookup.get_passed_variables(a, a.children[1]))) self.assertEqual([Param('expected', Param.OUT, _Integer())], list(lookup.get_passed_variables(b, b.children[1]))) self.assertEqual([Param('expected', Param.IN, _Integer())], list(lookup.get_passed_variables(b, b.children[2]))) self.assertRaises(bdec.DecodeError, list, a.decode(Data('\x01\x01'))) self.assertRaises(bdec.DecodeError, list, a.decode(Data('\x02\x00'))) list(a.decode(Data('\x02\x01')))
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_sub_children(self): a2 = Field('a2', 8) a1 = Sequence('a1', [a2]) a = Sequence('a', [a1]) value = ValueResult('a.a1.a2') b1 = Field('b1', value) b = Sequence('b', [b1]) spec = Sequence('blah', [a,b]) vars = ExpressionParameters([spec]) self.assertEqual([Local('a.a1.a2', _Integer())], vars.get_locals(spec)) # Note that despite containing a referenced entry, it isn't a local (as # it is passed up to the parent entry). self.assertEqual([], vars.get_locals(a)) # Now check what parameters are passed in and out. Note that we check # that the name is correct for the context of the parameter. self.assertEqual([], vars.get_params(spec)) self.assertEqual([Param('a2', Param.OUT, _Integer())], vars.get_params(a2)) self.assertEqual([Param('a2', Param.OUT, _Integer())], vars.get_params(a1)) self.assertEqual([Param('a1.a2', Param.OUT, _Integer())], vars.get_params(a)) self.assertEqual([Param('a1.a2', Param.OUT, _Integer())], list(vars.get_passed_variables(a, a.children[0]))) self.assertEqual([Param('a.a1.a2', Param.IN, _Integer())], vars.get_params(b)) self.assertEqual([Param('a.a1.a2', Param.IN, _Integer())], vars.get_params(b1)) self.assertEqual([Param('a.a1.a2', Param.IN, _Integer())], list(vars.get_passed_variables(b, b.children[0])))
def test_sequence_value(self): # Define an integer with a custom byte ordering lower = Field('lower byte', 8) lower_value = ValueResult('lower byte') ignored = Field('ignored', 8) upper = Field('upper byte', 8) upper_value = ValueResult('upper byte') value = ArithmeticExpression(operator.__add__, ArithmeticExpression(operator.__mul__, upper_value, Constant(256)), lower_value) length = Sequence('length', [lower, ignored, upper], value) header = Sequence('header', [length]) int_value = ValueResult('length') data = Field('data', int_value) spec = Sequence('blah', [length, data]) vars = ExpressionParameters([spec]) self.assertEquals([], vars.get_params(spec)) self.assertTrue(vars.is_value_referenced(lower)) self.assertFalse(vars.is_value_referenced(ignored)) self.assertTrue(vars.is_value_referenced(upper)) self.assertEqual([Local('lower byte', _Integer()), Local('upper byte', _Integer())], vars.get_locals(length)) self.assertEqual([Param('lower byte', Param.OUT, _Integer())], vars.get_params(lower)) self.assertEqual([Param('upper byte', Param.OUT, _Integer())], vars.get_params(upper)) self.assertEqual([Param('length', Param.OUT, _Integer())], vars.get_params(length)) self.assertEqual([Local('length', _Integer())], vars.get_locals(spec)) self.assertEqual([Param('length', Param.IN, _Integer())], vars.get_params(data))
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_direct_children(self): a = Field('a', 8) value = ValueResult('a') b = Field('b', value) spec = Sequence('blah', [a,b]) vars = ExpressionParameters([spec]) self.assertEqual([Local('a', _Integer())], vars.get_locals(spec)) self.assertTrue(vars.is_value_referenced(a)) self.assertFalse(vars.is_value_referenced(b)) self.assertEqual([], vars.get_locals(a))
def test_common_entry_with_input_parameter(self): # Test that we correctly resolve a common entry that has an input # parameter that resolves to mulitiple (different) entries. a = Field('a', length=parse('${b}')) # Here the common entry 'a' is used into two locations, each time it # resolves to an entry with a different length. c = Sequence('c', [Field('b', 8), a]) d = Sequence('d', [Field('b', 16), a]) lookup = ExpressionParameters([a, c, d]) self.assertEqual([Param('b', Param.OUT, _Integer())], list(lookup.get_passed_variables(c, c.children[0]))) self.assertEqual([Param('b', Param.OUT, _Integer())], list(lookup.get_passed_variables(d, d.children[0])))
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_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_unused_parameters_with_same_name(self): # Test that when we have multiple 'unused' parameters with the same # name we don't duplicate the same local variable. This happened with # the vfat specification (the different bootsector types all had the # same output parameters). a1 = Field('a', length=16) a2 = Field('a', length=8) # C doesn't use the outputs from a1 and a2, so should have a single # local variable. c = Sequence('c', [a1, a2]) # Now create a couple of other entries that actually use a1 & a2 d1 = Sequence('d1', [a1, Field('e1', length=parse('${a}'))]) d2 = Sequence('d2', [a2, Field('e2', length=parse('${a}'))]) lookup = ExpressionParameters([a1, a2, c, d1, d2]) self.assertEqual([Param('unused a', Param.OUT, _Integer())], list(lookup.get_passed_variables(c, c.children[0]))) self.assertEqual([Param('unused a', Param.OUT, _Integer())], list(lookup.get_passed_variables(c, c.children[1]))) self.assertEqual([Local('unused a', _Integer())], lookup.get_locals(c))
def test_name_ends_in_length(self): a = Field('data length', 8, Field.INTEGER) b = Field('data', parse('${data length} * 8')) c = Sequence('c', [a, b]) params = ExpressionParameters([c]) self.assertEqual([Local('data length', _Integer())], params.get_locals(c)) self.assertEqual([], params.get_params(c)) self.assertEqual([Param('data length', Param.OUT, _Integer())], params.get_params(a)) self.assertEqual([Param('data length', Param.IN, _Integer())], params.get_params(b)) self.assertEqual(True, params.is_value_referenced(a)) self.assertEqual(False, params.is_length_referenced(a))
def test_param_ordering(self): # Test that we order the parameters consistently a = Field('a', 8) b = Field('b', 8) c = Field('c', 8) d = Sequence('d', [a,b,c]) e = Field('e', parse('${d.a} + ${d.b} + ${d.c}')) f = Sequence('f', [d, e]) params = ExpressionParameters([f]) self.assertEqual([Local('d.a', _Integer()), Local('d.b', _Integer()), Local('d.c', _Integer())], params.get_locals(f)) self.assertEqual([Param('a', Param.OUT, _Integer()), Param('b', Param.OUT, _Integer()), Param('c', Param.OUT, _Integer())], params.get_params(d)) self.assertEqual([Param('d.a', Param.OUT, _Integer()), Param('d.b', Param.OUT, _Integer()), Param('d.c', Param.OUT, _Integer())], list(params.get_passed_variables(f, f.children[0])))
def test_param_length_postfix(self): # Test that a 'length:' postfix doesn't confuse the parameter # detection. There was a bug where the types would get confused if # both the length and value of an entry ending in ' length:' were # referenced. a = Sequence('a', [ Field('b length:', 8), Field('b', length=parse('${b length:}'), format=Field.TEXT), Sequence('c', [], value=parse('len{b length:} + len{b}')), ]) lookup = ExpressionParameters([a]) self.assertEqual([ Param('b length:', Param.OUT, EntryValueType(a.children[0].entry)), Param('b length: length', Param.OUT, EntryLengthType(a.children[0].entry))], list(lookup.get_passed_variables(a, a.children[0]))) self.assertEqual([ Param('b length', Param.OUT, EntryLengthType(a.children[1].entry)), Param('b length:', Param.IN, EntryValueType(a.children[0].entry))], list(lookup.get_passed_variables(a, a.children[1]))) self.assertEqual([ Param('b length', Param.IN, EntryLengthType(a.children[1].entry)), Param('b length: length', Param.IN, EntryLengthType(a.children[0].entry))], list(lookup.get_passed_variables(a, a.children[2])))
def test_recursive_entry_with_input_param(self): # There was a bug with passing input parameters to recursive entries, # where the embedded (recursed) entry wouldn't have the parameter # correctly passed. This technique is used in the asn.1 decoder. a = Choice('a', [ Field('not recursive', length=8, constraints=[Equals(ValueResult('zero'))]), Sequence('recursive', [ Field('unused', length=8) ]) ]) a.children[1].entry.children.append(Child('a', a)) b = Sequence('b', [ Sequence('zero', [], value=Constant(0)), a ]) lookup = ExpressionParameters([a, b]) self.assertEqual([ Param('zero', Param.IN, _Integer())], list(lookup.get_passed_variables(a, a.children[1]))) self.assertEqual([ Param('zero', Param.IN, _Integer())], list(lookup.get_passed_variables(a.children[1].entry, a.children[1].entry.children[1])))
def test_two_recursive_entries_with_input_parameters(self): # The previous fix for recursive parameters didn't handle two # structures that were recursive using the same intermediate instance. # # In this case, 'a' is recursive through both 'recursive x' and # 'recursive y', and the bug was that any input parameters to # 'recursive y' weren't being correctly detected. a = Choice('a', [ Field('not recursive', length=8, constraints=[Equals(ValueResult('zero'))]), Sequence('recursive x', [ Field('unused', length=8, constraints=[Maximum(10)]) ]), Sequence('recursive y', [ Field('unused', length=8) ]), ]) a.children[1].entry.children.append(Child('a1', a)) a.children[2].entry.children.append(Child('a2', a)) b = Sequence('b', [ Sequence('zero', [], value=Constant(0)), a ]) lookup = ExpressionParameters([a, b]) self.assertEqual([ Param('zero', Param.IN, _Integer())], list(lookup.get_passed_variables(a, a.children[1]))) self.assertEqual([ Param('zero', Param.IN, _Integer())], list(lookup.get_passed_variables(a.children[1].entry, a.children[1].entry.children[1]))) self.assertEqual([ Param('zero', Param.IN, _Integer())], list(lookup.get_passed_variables(a, a.children[2]))) self.assertEqual([ Param('zero', Param.IN, _Integer())], list(lookup.get_passed_variables(a.children[2].entry, a.children[2].entry.children[1])))
def test_length_reference(self): a1 = Field('a1', 8) a = Sequence('a', [a1]) b1 = Field('b1', LengthResult('a')) b = Sequence('b', [b1]) spec = Sequence('blah', [a,b]) vars = ExpressionParameters([spec]) self.assertEqual([Local('a length', _Integer())], vars.get_locals(spec)) self.assertFalse(vars.is_length_referenced(a1)) self.assertTrue(vars.is_length_referenced(a)) self.assertEqual([Param('a length', Param.OUT, EntryLengthType(a))], vars.get_params(a)) self.assertEqual([Param('a length', Param.IN, EntryLengthType(a))], vars.get_params(b))
def test_choice_reference(self): """ Test the parameter names when we have items selected under a choice. """ byte = Sequence('8 bit:', [Field('id', 8, constraints=[Equals(Data('\x00'))]), Field('length', 8)]) word = Sequence('16 bit:', [Field('id', 8, constraints=[Equals(Data('\x01'))]), Field('length', 16)]) length = Choice('variable integer', [byte, word]) length_value = ValueResult('variable integer.length') data = Field('data', length_value) spec = Sequence('spec', [length, data]) vars = ExpressionParameters([spec]) self.assertFalse(vars.is_value_referenced(byte)) self.assertTrue(vars.is_value_referenced(byte.children[1].entry)) self.assertFalse(vars.is_value_referenced(word)) self.assertTrue(vars.is_value_referenced(word.children[1].entry)) self.assertEqual([Param('length', Param.OUT, _Integer())], vars.get_params(byte)) self.assertEqual([Param('length', Param.OUT, _Integer())], vars.get_params(word)) self.assertEqual([Param('length', Param.OUT, _Integer())], vars.get_params(length)) self.assertEqual([], vars.get_locals(length)) self.assertEqual([Param('length', Param.OUT, _Integer())], list(vars.get_passed_variables(length, length.children[0]))) self.assertEqual([Local('variable integer.length', _Integer())], vars.get_locals(spec)) self.assertEqual([Param('variable integer.length', Param.IN, _Integer())], vars.get_params(data))
def test_reference_outside_of_choice(self): """ Test passing in a parameter into choice options. """ # Note that the 'integer' option has a fixed length... length = Field('length:', 8) length_value = ValueResult('length:') text = Sequence('text', [ Field('id:', 8, constraints=[Equals(Data('\x00'))]), Field('value', length_value, Field.TEXT)]) integer = Sequence('integer', [ Field('id:', 8, constraints=[Equals(Data('\x01'))]), Field('value', 16, Field.INTEGER)]) spec = Sequence('spec', [length, Choice('data', [text, integer])]) vars = ExpressionParameters([spec]) self.assertTrue(vars.is_value_referenced(length)) self.assertEqual([], vars.get_params(spec)) self.assertEqual([Local('length:', _Integer())], vars.get_locals(spec)) self.assertEqual([Param('length:', Param.OUT, _Integer())], vars.get_params(length)) self.assertEqual([Param('length:', Param.OUT, _Integer())], list(vars.get_passed_variables(spec, spec.children[0]))) self.assertEqual([Param('length:', Param.IN, _Integer())], vars.get_params(spec.children[1].entry)) self.assertEqual([Param('length:', Param.IN, _Integer())], list(vars.get_passed_variables(spec, spec.children[1]))) self.assertEqual([Param('length:', Param.IN, _Integer())], vars.get_params(text)) self.assertEqual([Param('length:', Param.IN, _Integer())], list(vars.get_passed_variables(spec.children[1].entry, spec.children[1].entry.children[0]))) self.assertEqual([Param('length:', Param.IN, _Integer())], vars.get_params(text.children[1].entry)) self.assertEqual([], vars.get_params(integer)) self.assertEqual([], list(vars.get_passed_variables(spec.children[1].entry, spec.children[1].entry.children[1])))
def test_unused_parameters(self): # Test detection of re-use of a common entry, where not all output parameters are used. length = Field('length', 8) shared = Sequence('shared', [length]) length_value = ValueResult('shared.length') # Now we'll reference that common component twice, but only once # referencing an actual value. In 'b' we use it twice, to detect that # it only turns up in the locals list once. a = Sequence('a', [shared, Field('a data', length_value)]) b = Sequence('b', [shared, shared]) spec = Sequence('spec', [a,b]) vars = ExpressionParameters([spec]) self.assertEqual([], list(vars.get_params(spec))) self.assertTrue(vars.is_value_referenced(length)) self.assertFalse(vars.is_value_referenced(shared)) self.assertEqual([], vars.get_locals(spec)) # Test that the 'length' and 'shared' entries pass out their value self.assertEqual([Param('length', Param.OUT, _Integer())], list(vars.get_params(length))) self.assertEqual([Param('length', Param.OUT, _Integer())], vars.get_params(shared)) # First validate the passing of the length value within entry 'a' self.assertEqual([], vars.get_params(a)) self.assertEqual([Local('shared.length', _Integer())], vars.get_locals(a)) self.assertEqual([Param('shared.length', Param.OUT, _Integer())], list(vars.get_passed_variables(a, a.children[0]))) self.assertEqual([Param('shared.length', Param.IN, _Integer())], list(vars.get_passed_variables(a, a.children[1]))) self.assertEqual([], list(vars.get_passed_variables(spec, spec.children[0]))) # Now test the passing out (and ignoring) of the length value within 'b' self.assertEqual([], vars.get_params(b)) self.assertEqual([Local('unused length', _Integer())], vars.get_locals(b)) self.assertEqual([Param('unused length', Param.OUT, _Integer())], list(vars.get_passed_variables(b, b.children[0]))) self.assertEqual([], list(vars.get_passed_variables(spec, spec.children[0])))
def test_choice_reference(self): # Test that we can correctly reference a choice (which in effect # references each of its children). # # We test this by creating a choice where each child has a value type, # and attempt to reference the top level choice. len = Field('len', length=8) a = Field('a', length=32) b = Field('b', length=16) c = Field('c', length=8) var_len = Choice('var_len', [a, b, c], length=parse('${len}')) data = Field('data', length=parse('${var_len}')) spec = Sequence('spec', [len, var_len, data]) # Check the parameters passed in and out of each entry lookup = ExpressionParameters([spec]) self.assertEqual([], lookup.get_params(spec)) self.assertEqual([Param('len', Param.OUT, _Integer())], lookup.get_params(len)) self.assertEqual([Param('len', Param.IN, _Integer()), Param('var_len', Param.OUT, _Integer())], lookup.get_params(var_len)) 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('var_len', Param.IN, _Integer())], lookup.get_params(data)) # Test the mapping of the parameters for the choice to the option # entries. self.assertEqual([Param('var_len', Param.OUT, _Integer())], list(lookup.get_passed_variables(var_len, var_len.children[0]))) self.assertEqual([Param('var_len', Param.OUT, _Integer())], list(lookup.get_passed_variables(var_len, var_len.children[1]))) self.assertEqual([Param('var_len', Param.OUT, _Integer())], list(lookup.get_passed_variables(var_len, var_len.children[2]))) # And validate the locals... self.assertEqual([Local('len', _Integer()), Local('var_len', _Integer())], lookup.get_locals(spec)) self.assertEqual([], lookup.get_locals(len)) self.assertEqual([], lookup.get_locals(var_len)) self.assertEqual([], lookup.get_locals(a)) self.assertEqual([], lookup.get_locals(data))