laforge has submitted this change. ( https://gerrit.osmocom.org/c/python/pyosmocom/+/38272?usp=email )
Change subject: osmocom.construct.Asn1DerInteger ......................................................................
osmocom.construct.Asn1DerInteger
This is a 'construct' type which can be used for encoding/decoding integer values according to ASN.1 DER encoding rules.
Related: SYS#7094 Change-Id: I0cfe97daf957919de86453d6d44f9c99ab3075ac --- M src/osmocom/construct.py M tests/test_construct.py 2 files changed, 73 insertions(+), 0 deletions(-)
Approvals: laforge: Looks good to me, approved Jenkins Builder: Verified
diff --git a/src/osmocom/construct.py b/src/osmocom/construct.py index 7c0f034..5a78246 100644 --- a/src/osmocom/construct.py +++ b/src/osmocom/construct.py @@ -600,6 +600,19 @@ stream_write(stream, data, length, path) return obj
+class Asn1DerInteger(Construct): + """A signed integer value using ASN.1 DER encoding rules (see also ITU-T X.690 8.3)""" + def _parse(self, stream, context, path): + data = stream_read_entire(stream, path) + return int.from_bytes(data, byteorder='big', signed=True) + + def _build(self, obj, stream, context, path): + if not isinstance(obj, integertypes): + raise IntegerError(f"value {obj} is not an integer", path=path) + val = obj.to_bytes(int_bytes_required(obj, signed=True), byteorder='big', signed=True) + stream_write(stream, val, len(val), path) + return obj + # merged definitions of 24.008 + 23.040 TypeOfNumber = Enum(BitsInteger(3), unknown=0, international=1, national=2, network_specific=3, short_code=4, alphanumeric=5, abbreviated=6, reserved_for_extension=7) diff --git a/tests/test_construct.py b/tests/test_construct.py index c2aeece..ba4f8f4 100755 --- a/tests/test_construct.py +++ b/tests/test_construct.py @@ -114,6 +114,66 @@ self.assertEqual(re_enc, enc)
+class TestAsn1DerInteger(unittest.TestCase): + + tests = [ + # positive numbers + ( b'\x00', 0 ), + ( b'\x01', 1 ), + ( b'\x02', 2 ), + ( b'\x7f', 127 ), + ( b'\x00\x80', 128 ), + ( b'\x00\x81', 129 ), + ( b'\x00\xfe', 254 ), + ( b'\x01\x00', 256 ), + ( b'\x01\x01', 257 ), + ( b'\x7f\xff', 32767 ), + ( b'\x00\x80\x00', 32768 ), + ( b'\x00\x80\x01', 32769 ), + ( b'\x00\xff\xfe', 65534 ), + ( b'\x00\xff\xff', 65535 ), + ( b'\x01\x00\x00', 65536 ), + + # negative numbers + ( b'\x00', -0 ), + ( b'\xff', -1 ), + ( b'\xfe', -2 ), + ( b'\x81', -127 ), + ( b'\x80', -128 ), + ( b'\xff\x7f', -129 ), + ( b'\xff\x02', -254 ), + ( b'\xff\x00', -256 ), + ( b'\xfe\xff', -257 ), + ( b'\x80\x01', -32767 ), + ( b'\x80\x00', -32768 ), + ( b'\xff\x7f\xff', -32769 ), + ( b'\xff\x00\x02', -65534 ), + ( b'\xff\x00\x01', -65535 ), + ( b'\xff\x00\x00', -65536 ), + ] + + def test_encode(self): + adi = Asn1DerInteger() + + # Verfiy with chosen numbers + for t in self.tests: + self.assertEqual(t[0], adi.build(t[1])) + + # Verify that ITU-T X.690 8.3.2 is always complied with (for standard two's + # complement that should always be the case) + for i in range(-100000,100000): + res = adi.build(i) + if len(res) > 1: + self.assertFalse(int(res[0]) == 0xff and int(res[1]) & 0x80 == 0x80) + self.assertFalse(int(res[0]) == 0x00 and int(res[1]) & 0x80 == 0x00) + + def test_decode(self): + adi = Asn1DerInteger() + + # Verfiy with chosen numbers + for t in self.tests: + self.assertEqual(t[1], adi.parse(t[0])) +
if __name__ == "__main__": unittest.main()