From 05db179231174bb496db86744504fc86d02686c2 Mon Sep 17 00:00:00 2001 From: MohammadHH Date: Thu, 29 Jul 2021 18:42:37 +0300 Subject: [PATCH] Support named members decoding for .ber formats when named_members is set to true and while decoding a .ber file, for integer types with named-numbers, the integer value will be replaced with its corresponding name if found. For bit string types with named-bits, the bit string value will be replaced with list of strings of corresponding names when found. --- .gitignore | 4 +- asn1tools/codecs/ber.py | 74 ++++++++++++++++++++----- asn1tools/codecs/compiler.py | 9 ++- asn1tools/codecs/constraints_checker.py | 4 +- asn1tools/codecs/der.py | 5 +- asn1tools/codecs/gser.py | 6 +- asn1tools/codecs/jer.py | 4 +- asn1tools/codecs/oer.py | 4 +- asn1tools/codecs/per.py | 4 +- asn1tools/codecs/type_checker.py | 4 +- asn1tools/codecs/uper.py | 4 +- asn1tools/codecs/xer.py | 4 +- asn1tools/compiler.py | 42 ++++++++++---- tests/test_ber.py | 59 ++++++++++++++++++++ tests/utils.py | 9 +++ 15 files changed, 189 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 99fa3864..b75e3784 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,6 @@ gmon.out /*.mk Cargo.lock /rust_source.rs -/*.o \ No newline at end of file +/*.o +.idea/ +venv/ \ No newline at end of file diff --git a/asn1tools/codecs/ber.py b/asn1tools/codecs/ber.py index 136ec22d..1f19c8d3 100644 --- a/asn1tools/codecs/ber.py +++ b/asn1tools/codecs/ber.py @@ -25,7 +25,7 @@ from . import add_error_location from .compiler import enum_values_as_dict from .compiler import clean_bit_string_value - +from .compiler import named_numbers_as_dict class Class(object): UNIVERSAL = 0x00 @@ -803,10 +803,13 @@ def __repr__(self): class Integer(Type): - def __init__(self, name): + def __init__(self, name, named_members=None): super(Integer, self).__init__(name, 'INTEGER', Tag.INTEGER) + self.named_members = named_members + if named_members is not None: + self.named_members = named_numbers_as_dict(named_members) @add_error_location def encode(self, data, encoded): @@ -819,8 +822,10 @@ def _decode(self, data, offset): length, offset = decode_length_definite(data, offset) end_offset = offset + length - - return decode_signed_integer(data[offset:end_offset]), end_offset + decoded = decode_signed_integer(data[offset:end_offset]) + if self.named_members: + decoded = self.named_members.get(decoded) or decoded + return decoded, end_offset def __repr__(self): return 'Integer({})'.format(self.name) @@ -871,12 +876,20 @@ def __repr__(self): class BitString(PrimitiveOrConstructedType): - def __init__(self, name, has_named_bits): + def __init__(self, name, has_named_bits, named_members): super(BitString, self).__init__(name, 'BIT STRING', Tag.BIT_STRING, self) self.has_named_bits = has_named_bits + self.named_members = named_members + if has_named_bits and self.named_members: + self.named_members = enum_values_as_dict(self.named_members) + # ensure no gap between named bits + number_of_named_bits = len(self.named_members) + maximum_number_in_named_bits = max([int(key) for key in self.named_members.keys()]) + 1 + if maximum_number_in_named_bits > number_of_named_bits: + self.named_members = None def is_default(self, value): if self.default is None: @@ -886,7 +899,6 @@ def is_default(self, value): self.has_named_bits) clean_default = clean_bit_string_value(self.default, self.has_named_bits) - return clean_value == clean_default @add_error_location @@ -909,12 +921,42 @@ def encode(self, data, encoded): encoded.append(number_of_unused_bits) encoded.extend(data) + def get_string_list_representation(self, bytes_array, bits_length): + number_of_named_bits = len(self.named_members) + + # convert bytes_array into bit string + bit_string = ''.join(["{:08b}".format(byte) for byte in bytes_array])[:bits_length] + current_bit_string_length = len(bit_string) + + if current_bit_string_length < number_of_named_bits: + # insert zeros to left + bit_string = bit_string.rjust(number_of_named_bits, '0') + elif current_bit_string_length > number_of_named_bits: + extra_bits = bit_string[:current_bit_string_length - number_of_named_bits] + is_extra_bits_all_zero = True + for bit in extra_bits: + is_extra_bits_all_zero = is_extra_bits_all_zero and bit == '0' + if is_extra_bits_all_zero: + # clean extra bits + bit_string = bit_string[current_bit_string_length - number_of_named_bits:] + else: + return None + + current_bit_string_length = len(bit_string) + string_list = [] + for key, val in self.named_members.items(): + if bit_string[current_bit_string_length - int(key) - 1] == '1': + string_list.append(val) + return string_list + def decode_primitive_contents(self, data, offset, length): length -= 1 number_of_bits = 8 * length - data[offset] offset += 1 - - return (data[offset:offset + length], number_of_bits) + decoded = data[offset:offset + length] + if self.has_named_bits and self.named_members: + decoded = self.get_string_list_representation(decoded, number_of_bits) or decoded + return decoded, number_of_bits def decode_constructed_segments(self, segments): decoded = bytearray() @@ -923,8 +965,10 @@ def decode_constructed_segments(self, segments): for data, length in segments: decoded.extend(data) number_of_bits += length - - return (bytes(decoded), number_of_bits) + decoded = bytes(decoded) + if self.has_named_bits and self.named_members: + decoded = self.get_string_list_representation(decoded, number_of_bits) or decoded + return decoded, number_of_bits def __repr__(self): return 'BitString({})'.format(self.name) @@ -1606,7 +1650,8 @@ def compile_implicit_type(self, name, type_descriptor, module_name): *self.compile_members(type_descriptor['members'], module_name)) elif type_name == 'INTEGER': - compiled = Integer(name) + named_members = type_descriptor.get('named-numbers') if self.named_members else None + compiled = Integer(name, named_members) elif type_name == 'REAL': compiled = Real(name) elif type_name == 'ENUMERATED': @@ -1652,7 +1697,8 @@ def compile_implicit_type(self, name, type_descriptor, module_name): compiled = DateTime(name) elif type_name == 'BIT STRING': has_named_bits = ('named-bits' in type_descriptor) - compiled = BitString(name, has_named_bits) + named_members = type_descriptor.get('named-bits') if self.named_members else None + compiled = BitString(name, has_named_bits, named_members) elif type_name == 'ANY': compiled = Any(name) elif type_name == 'ANY DEFINED BY': @@ -1764,8 +1810,8 @@ def compile_extension_member(self, additions.append(compiled_member) -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() def decode_length(data): diff --git a/asn1tools/codecs/compiler.py b/asn1tools/codecs/compiler.py index 34483b5c..feb31b66 100644 --- a/asn1tools/codecs/compiler.py +++ b/asn1tools/codecs/compiler.py @@ -183,13 +183,14 @@ def decode(self, data): class Compiler(object): - def __init__(self, specification, numeric_enums=False): + def __init__(self, specification, numeric_enums=False, named_members=False): self._specification = specification self._numeric_enums = numeric_enums self._types_backtrace = [] self.recursive_types = [] self.compiled = {} self.current_type_descriptor = None + self.named_members = named_members def types_backtrace_push(self, type_name): self._types_backtrace.append(type_name) @@ -1179,6 +1180,12 @@ def enum_values_as_dict(values): } +def named_numbers_as_dict(values): + return { + val: key + for key, val in values.items() + } + def enum_values_split(values): if EXTENSION_MARKER in values: index = values.index(EXTENSION_MARKER) diff --git a/asn1tools/codecs/constraints_checker.py b/asn1tools/codecs/constraints_checker.py index dc2d8be4..d8fc5bfe 100644 --- a/asn1tools/codecs/constraints_checker.py +++ b/asn1tools/codecs/constraints_checker.py @@ -457,5 +457,5 @@ def char_range(begin, end): return ''.join(sorted(value)) -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() diff --git a/asn1tools/codecs/der.py b/asn1tools/codecs/der.py index 14c49b62..7ded80a3 100644 --- a/asn1tools/codecs/der.py +++ b/asn1tools/codecs/der.py @@ -135,7 +135,6 @@ def encode(self, data, encoded, values=None): def _decode(self, data, offset): length, offset = decode_length_definite(data, offset) end_offset = offset + length - return decode_signed_integer(data[offset:end_offset]), end_offset def __repr__(self): @@ -459,5 +458,5 @@ def compile_implicit_type(self, name, type_descriptor, module_name): return compiled -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() diff --git a/asn1tools/codecs/gser.py b/asn1tools/codecs/gser.py index abb0436e..87e91d95 100644 --- a/asn1tools/codecs/gser.py +++ b/asn1tools/codecs/gser.py @@ -547,7 +547,7 @@ def compile_type(self, name, type_descriptor, module_name): module_name) compiled = Choice(name, members) elif type_name == 'INTEGER': - compiled = Integer(name) + compiled = Integer(name) elif type_name == 'REAL': compiled = Real(name) elif type_name == 'ENUMERATED': @@ -620,8 +620,8 @@ def compile_type(self, name, type_descriptor, module_name): return compiled -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() def decode_length(_data): diff --git a/asn1tools/codecs/jer.py b/asn1tools/codecs/jer.py index aeee6091..75d0db25 100644 --- a/asn1tools/codecs/jer.py +++ b/asn1tools/codecs/jer.py @@ -660,8 +660,8 @@ def compile_type(self, name, type_descriptor, module_name): return compiled -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() def decode_length(_data): diff --git a/asn1tools/codecs/oer.py b/asn1tools/codecs/oer.py index 5e51b1ec..54835594 100644 --- a/asn1tools/codecs/oer.py +++ b/asn1tools/codecs/oer.py @@ -1502,8 +1502,8 @@ def compile_extension_member(self, additions.append(compiled_member) -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() def decode_length(_data): diff --git a/asn1tools/codecs/per.py b/asn1tools/codecs/per.py index c2e7ef0f..613b3cc3 100644 --- a/asn1tools/codecs/per.py +++ b/asn1tools/codecs/per.py @@ -2287,8 +2287,8 @@ def char_range(begin, end): return PermittedAlphabet(encode_map, decode_map) -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() def decode_length(_data): diff --git a/asn1tools/codecs/type_checker.py b/asn1tools/codecs/type_checker.py index 05b02426..c406b52b 100644 --- a/asn1tools/codecs/type_checker.py +++ b/asn1tools/codecs/type_checker.py @@ -377,5 +377,5 @@ def compile_type(self, name, type_descriptor, module_name): return compiled -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() diff --git a/asn1tools/codecs/uper.py b/asn1tools/codecs/uper.py index e744bd7f..60930a80 100644 --- a/asn1tools/codecs/uper.py +++ b/asn1tools/codecs/uper.py @@ -637,8 +637,8 @@ def compile_type(self, name, type_descriptor, module_name): return compiled -def compile_dict(specification, numeric_enums=False): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums=False, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() def decode_length(_data): diff --git a/asn1tools/codecs/xer.py b/asn1tools/codecs/xer.py index 78b4a6f7..e5c30fe6 100644 --- a/asn1tools/codecs/xer.py +++ b/asn1tools/codecs/xer.py @@ -801,8 +801,8 @@ def compile_type(self, name, type_descriptor, module_name): return compiled -def compile_dict(specification, numeric_enums): - return Compiler(specification, numeric_enums).process() +def compile_dict(specification, numeric_enums, named_members=False): + return Compiler(specification, numeric_enums, named_members).process() def decode_length(_data): diff --git a/asn1tools/compiler.py b/asn1tools/compiler.py index a6127794..9791b7c0 100644 --- a/asn1tools/compiler.py +++ b/asn1tools/compiler.py @@ -243,7 +243,8 @@ def _compile_files_cache(filenames, any_defined_by_choices, encoding, cache_dir, - numeric_enums): + numeric_enums, + named_members=False): key = [codec.encode('ascii')] if isinstance(filenames, str): @@ -262,7 +263,8 @@ def _compile_files_cache(filenames, compiled = compile_dict(parse_files(filenames, encoding), codec, any_defined_by_choices, - numeric_enums) + numeric_enums, + named_members) cache[key] = compiled return compiled @@ -271,7 +273,8 @@ def _compile_files_cache(filenames, def compile_dict(specification, codec='ber', any_defined_by_choices=None, - numeric_enums=False): + numeric_enums=False, + named_members=False): """Compile given ASN.1 specification dictionary and return a :class:`~asn1tools.compiler.Specification` object that can be used to encode and decode data structures with given codec @@ -281,6 +284,9 @@ def compile_dict(specification, Give `numeric_enums` as ``True`` for numeric enumeration values instead of strings. + Give `named_members` as ``True`` for string values of named numbers + instead of integers. + >>> foo = asn1tools.compile_dict(asn1tools.parse_files('foo.asn')) """ @@ -306,18 +312,22 @@ def compile_dict(specification, any_defined_by_choices) return Specification(codec.compile_dict(specification, - numeric_enums), + numeric_enums, + named_members), codec.decode_length, type_checker.compile_dict(specification, - numeric_enums), + numeric_enums, + named_members), constraints_checker.compile_dict(specification, - numeric_enums)) + numeric_enums, + named_members)) def compile_string(string, codec='ber', any_defined_by_choices=None, - numeric_enums=False): + numeric_enums=False, + named_members=False): """Compile given ASN.1 specification string and return a :class:`~asn1tools.compiler.Specification` object that can be used to encode and decode data structures with given codec @@ -327,6 +337,9 @@ def compile_string(string, Give `numeric_enums` as ``True`` for numeric enumeration values instead of strings. + Give `named_members` as ``True`` for string values of named numbers + instead of integers. + >>> with open('foo.asn') as fin: ... foo = asn1tools.compile_string(fin.read()) @@ -335,7 +348,8 @@ def compile_string(string, return compile_dict(parse_string(string), codec, any_defined_by_choices, - numeric_enums) + numeric_enums, + named_members) def compile_files(filenames, @@ -343,7 +357,8 @@ def compile_files(filenames, any_defined_by_choices=None, encoding='utf-8', cache_dir=None, - numeric_enums=False): + numeric_enums=False, + named_members=False): """Compile given ASN.1 specification file(s) and return a :class:`~asn1tools.compiler.Specification` object that can be used to encode and decode data structures with given codec @@ -364,6 +379,9 @@ def compile_files(filenames, Give `numeric_enums` as ``True`` for numeric enumeration values instead of strings. + Give `named_members` as ``True`` for string values of named numbers + instead of integers. + >>> foo = asn1tools.compile_files('foo.asn') Give `cache_dir` as a string to use a cache. @@ -376,14 +394,16 @@ def compile_files(filenames, return compile_dict(parse_files(filenames, encoding), codec, any_defined_by_choices, - numeric_enums) + numeric_enums, + named_members) else: return _compile_files_cache(filenames, codec, any_defined_by_choices, encoding, cache_dir, - numeric_enums) + numeric_enums, + named_members) def pre_process_dict(specification): diff --git a/tests/test_ber.py b/tests/test_ber.py index ff026d6f..70cc88fb 100644 --- a/tests/test_ber.py +++ b/tests/test_ber.py @@ -292,6 +292,45 @@ def test_bit_string_explicit_tags(self): foo = asn1tools.compile_string(spec) self.assert_encode_decode(foo, 'Foo', (b'\x56', 7), b'\xa2\x04\x03\x02\x01\x56') + def test_named_bit_string(self): + foo = """ + Foo DEFINITIONS ::= + BEGIN + A ::= BIT STRING { + a (0), + b (1), + c (2), + d (3) + } + + B ::= BIT STRING { + a (0), + d (9) + } + + C ::= BIT STRING + END + """ + specification = asn1tools.compile_string(foo, named_members=True) + # A type with data '1010' + self.assert_encode_decode_named_member(specification, 'A', (b'\xa0', 4), (['b','d'], 4), b'\x03\x02\x04\xa0') + # A type with data '10' -> '0010' + self.assert_encode_decode_named_member(specification, 'A', (b'\x80', 2), (['b'], 2), b'\x03\x02\x06\x80') + # A type with data '101010' + self.assert_encode_decode_named_member(specification, 'A', (b'\xa8', 6), (b'\xa8', 6), b'\x03\x02\x02\xa8') + # A type with data '001010' + self.assert_encode_decode_named_member(specification, 'A', (b'\x28', 6), (['b','d'], 6), b'\x03\x02\x02\x28') + # A type with data '0000'b that has no corresponding names + self.assert_encode_decode_named_member(specification, 'A', (b'\x00', 4), (b'\x00', 4), b'\x03\x02\x04\x00') + # A type with data '0010100' + self.assert_encode_decode_named_member(specification, 'A', (b'\x28', 7), (b'\x28', 7), b'\x03\x02\x01\x28') + # A type with data '000000' + self.assert_encode_decode_named_member(specification, 'A', (b'\x00', 6), (b'\x00', 6), b'\x03\x02\x02\x00') + # B type with data '0100' with gap between named bits + self.assert_encode_decode_named_member(specification, 'B', (b'\x40', 4), (b'\x40', 4), b'\x03\x02\x04\x40') + # C type with no named bits and data '0101' + self.assert_encode_decode_named_member(specification, 'C', (b'\x50', 4), (b'\x50', 4), b'\x03\x02\x04\x50') + def test_octet_string_explicit_tags(self): """Test explicit tags on octet strings. @@ -3076,6 +3115,26 @@ def test_any_defined_by_integer(self): self.assertEqual(str(cm.exception), "Fie.fum: Bad AnyDefinedBy choice 2. (At offset: 5)") + def test_named_integer_types(self): + foo = """ + Foo DEFINITIONS ::= BEGIN + A ::= INTEGER { + a(0), + b(1), + c(2) + } + + B ::= INTEGER + END + """ + specification = asn1tools.compile_string(foo, named_members=True) + # A type named integer with name b + self.assert_encode_decode_named_member(specification, 'A', 1, 'b',b'\x02\x01\x01') + # A type named integer with value 10 that has no corresponding name + self.assert_encode_decode_named_member(specification, 'A', 10, 10, b'\x02\x01\x0a') + # B type with no named integers when named_members flag is set + self.assert_encode_decode_named_member(specification, 'B', 1, 1, b'\x02\x01\x01') + def test_any_defined_by_object_identifier(self): spec = """ Foo DEFINITIONS ::= BEGIN diff --git a/tests/utils.py b/tests/utils.py index 63cf9dcc..f0855687 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -46,6 +46,15 @@ def assert_encode_decode(self, print('Expected:', decoded_message) raise + def assert_encode_decode_named_member(self, + specification, + type_name, + decoded_message, + decoded_name, + encoded_message): + self.assertEqual(encoded_message, specification.encode(type_name, decoded_message)) + self.assertEqual(decoded_name, specification.decode(type_name, encoded_message)) + def assert_encode_decode_string(self, specification, type_name,