X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=8b93827d1e14a4d28155925dd5660015704aff0e;hb=2d45a224943c79e95cbd4913b44420788bc6c17d;hp=317ed91b078e30ab36d3f96a9e2877c26189b89c;hpb=e0f6420220f67c814d257c59650764a0884f43bc;p=pyderasn.git diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 317ed91..8b93827 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -1,5 +1,5 @@ # coding: utf-8 -# PyDERASN -- Python ASN.1 DER codec with abstract structures +# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures # Copyright (C) 2017-2018 Sergey Matveev # # This program is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ from datetime import datetime from string import ascii_letters +from string import digits from string import printable from string import whitespace from unittest import TestCase @@ -42,12 +43,14 @@ from hypothesis.strategies import sets from hypothesis.strategies import text from hypothesis.strategies import tuples from six import assertRaisesRegex +from six import binary_type from six import byte2int from six import indexbytes from six import int2byte from six import iterbytes from six import PY2 from six import text_type +from six import unichr as six_unichr from pyderasn import _pp from pyderasn import abs_decode_path @@ -57,9 +60,11 @@ from pyderasn import BMPString from pyderasn import Boolean from pyderasn import BoundsError from pyderasn import Choice -from pyderasn import decode_path_defby from pyderasn import DecodeError +from pyderasn import DecodePathDefBy from pyderasn import Enumerated +from pyderasn import EOC +from pyderasn import EOC_LEN from pyderasn import GeneralizedTime from pyderasn import GeneralString from pyderasn import GraphicString @@ -106,11 +111,11 @@ from pyderasn import VideotexString from pyderasn import VisibleString -settings.register_profile('local', settings( +settings.register_profile("local", settings( deadline=5000, perform_health_check=False, )) -settings.load_profile('local') +settings.load_profile("local") LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4 tag_classes = sampled_from(( @@ -283,7 +288,7 @@ class CommonMixin(object): with self.assertRaises(ValueError): self.base_klass(impl=b"whatever", expl=b"whenever") - @given(binary(), integers(), integers(), integers()) + @given(binary(min_size=1), integers(), integers(), integers()) def test_decoded(self, impl, offset, llen, vlen): obj = self.base_klass(impl=impl, _decoded=(offset, llen, vlen)) self.assertEqual(obj.offset, offset) @@ -292,7 +297,7 @@ class CommonMixin(object): self.assertEqual(obj.tlen, len(impl)) self.assertEqual(obj.tlvlen, obj.tlen + obj.llen + obj.vlen) - @given(binary()) + @given(binary(min_size=1)) def test_impl_inherited(self, impl_tag): class Inherited(self.base_klass): impl = impl_tag @@ -529,8 +534,9 @@ class TestBoolean(CommonMixin, TestCase): booleans(), integers(min_value=1).map(tag_ctxc), integers(min_value=0), + binary(max_size=5), ) - def test_symmetric(self, values, value, tag_expl, offset): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk): for klass in (Boolean, BooleanInherited): _, _, _, default, optional, _decoded = values obj = klass( @@ -548,10 +554,13 @@ class TestBoolean(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bool(obj_decoded), bool(obj_expled)) @@ -581,13 +590,71 @@ class TestBoolean(CommonMixin, TestCase): ))) @given(integers(min_value=0 + 1, max_value=255 - 1)) - def test_invalid_value(self, value): + def test_ber_value(self, value): with assertRaisesRegex(self, DecodeError, "unacceptable Boolean value"): Boolean().decode(b"".join(( Boolean.tag_default, len_encode(1), int2byte(value), ))) + obj, _ = Boolean().decode( + b"".join(( + Boolean.tag_default, + len_encode(1), + int2byte(value), + )), + ctx={"bered": True}, + ) + self.assertTrue(bool(obj)) + self.assertTrue(obj.bered) + self.assertFalse(obj.lenindef) + + @given( + integers(min_value=1).map(tag_ctxc), + lists( + booleans(), + min_size=1, + max_size=5, + ), + ) + def test_ber_expl(self, expl, values): + encoded = b"" + for value in values: + encoded += ( + expl + + b"\x80" + + Boolean(value).encode() + + EOC + ) + encoded = SequenceOf.tag_default + len_encode(len(encoded)) + encoded + + class SeqOf(SequenceOf): + schema = Boolean(expl=expl) + seqof, tail = SeqOf().decode(encoded, ctx={"bered": True}) + self.assertSequenceEqual(tail, b"") + self.assertSequenceEqual([bool(v) for v in seqof], values) + self.assertSetEqual( + set( + ( + v.tlvlen, + v.expl_tlvlen, + v.expl_tlen, + v.expl_llen, + v.bered, + v.lenindef, + v.expl_lenindef, + ) for v in seqof + ), + set((( + 3 + EOC_LEN, + len(expl) + 1 + 3 + EOC_LEN, + len(expl), + 1, + False, + False, + True, + ),)), + ) @composite @@ -904,8 +971,9 @@ class TestInteger(CommonMixin, TestCase): integers(), integers(min_value=1).map(tag_ctxc), integers(min_value=0), + binary(max_size=5), ) - def test_symmetric(self, values, value, tag_expl, offset): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk): for klass in (Integer, IntegerInherited): _, _, _, _, default, optional, _, _decoded = values obj = klass( @@ -923,10 +991,13 @@ class TestInteger(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(int(obj_decoded), int(obj_expled)) @@ -1128,8 +1199,8 @@ class TestBitString(CommonMixin, TestCase): @given( tuples(integers(min_value=0), binary()), tuples(integers(min_value=0), binary()), - binary(), - binary(), + binary(min_size=1), + binary(min_size=1), ) def test_comparison(self, value1, value2, tag1, tag2): for klass in (BitString, BitStringInherited): @@ -1279,6 +1350,7 @@ class TestBitString(CommonMixin, TestCase): optional, _decoded, ) = d.draw(bit_string_values_strategy(value_required=True)) + tail_junk = d.draw(binary(max_size=5)) tag_expl = tag_ctxc(d.draw(integers(min_value=1))) offset = d.draw(integers(min_value=0)) for klass in (BitString, BitStringInherited): @@ -1299,10 +1371,13 @@ class TestBitString(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) @@ -1372,6 +1447,91 @@ class TestBitString(CommonMixin, TestCase): self.assertTrue(obj[9]) self.assertFalse(obj[17]) + @given( + integers(min_value=1, max_value=30), + lists( + one_of( + binary(min_size=1, max_size=5), + lists( + binary(min_size=1, max_size=5), + min_size=1, + max_size=3, + ), + ), + min_size=0, + max_size=3, + ), + lists(booleans(), min_size=1), + ) + def test_constructed(self, impl, chunk_inputs, chunk_last_bits): + def chunk_constructed(contents): + return ( + tag_encode(form=TagFormConstructed, num=3) + + b"\x80" + + b"".join(BitString(content).encode() for content in contents) + + EOC + ) + chunks = [] + payload_expected = b"" + bit_len_expected = 0 + for chunk_input in chunk_inputs: + if isinstance(chunk_input, binary_type): + chunks.append(BitString(chunk_input).encode()) + payload_expected += chunk_input + bit_len_expected += len(chunk_input) * 8 + else: + chunks.append(chunk_constructed(chunk_input)) + payload = b"".join(chunk_input) + payload_expected += payload + bit_len_expected += len(payload) * 8 + chunk_last = BitString("'%s'B" % "".join( + "1" if bit else "0" for bit in chunk_last_bits + )) + payload_expected += bytes(chunk_last) + bit_len_expected += chunk_last.bit_len + encoded_indefinite = ( + tag_encode(form=TagFormConstructed, num=impl) + + b"\x80" + + b"".join(chunks) + + chunk_last.encode() + + EOC + ) + encoded_definite = ( + tag_encode(form=TagFormConstructed, num=impl) + + len_encode(len(b"".join(chunks) + chunk_last.encode())) + + b"".join(chunks) + + chunk_last.encode() + ) + with assertRaisesRegex(self, DecodeError, "unallowed BER"): + BitString(impl=tag_encode(impl)).decode(encoded_indefinite) + for lenindef_expected, encoded in ( + (True, encoded_indefinite), + (False, encoded_definite), + ): + obj, tail = BitString(impl=tag_encode(impl)).decode( + encoded, ctx={"bered": True} + ) + self.assertSequenceEqual(tail, b"") + self.assertEqual(obj.bit_len, bit_len_expected) + self.assertSequenceEqual(bytes(obj), payload_expected) + self.assertTrue(obj.bered) + self.assertEqual(obj.lenindef, lenindef_expected) + self.assertEqual(len(encoded), obj.tlvlen) + + def test_x690_vector(self): + vector = BitString("'0A3B5F291CD'H") + obj, tail = BitString().decode(hexdec("0307040A3B5F291CD0")) + self.assertSequenceEqual(tail, b"") + self.assertEqual(obj, vector) + obj, tail = BitString().decode( + hexdec("23800303000A3B0305045F291CD00000"), + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, b"") + self.assertEqual(obj, vector) + self.assertTrue(obj.bered) + self.assertTrue(obj.lenindef) + @composite def octet_string_values_strategy(draw, do_expl=False): @@ -1437,7 +1597,7 @@ class TestOctetString(CommonMixin, TestCase): repr(obj) pprint(obj) - @given(binary(), binary(), binary(), binary()) + @given(binary(), binary(), binary(min_size=1), binary(min_size=1)) def test_comparison(self, value1, value2, tag1, tag2): for klass in (OctetString, OctetStringInherited): obj1 = klass(value1) @@ -1637,8 +1797,9 @@ class TestOctetString(CommonMixin, TestCase): binary(), integers(min_value=1).map(tag_ctxc), integers(min_value=0), + binary(max_size=5), ) - def test_symmetric(self, values, value, tag_expl, offset): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk): for klass in (OctetString, OctetStringInherited): _, _, _, _, default, optional, _decoded = values obj = klass( @@ -1656,10 +1817,13 @@ class TestOctetString(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) @@ -1679,6 +1843,65 @@ class TestOctetString(CommonMixin, TestCase): ) self.assertEqual(obj_decoded.expl_offset, offset) + @given( + integers(min_value=1, max_value=30), + lists( + one_of( + binary(min_size=1, max_size=5), + lists( + binary(min_size=1, max_size=5), + min_size=1, + max_size=3, + ), + ), + min_size=1, + max_size=3, + ), + ) + def test_constructed(self, impl, chunk_inputs): + def chunk_constructed(contents): + return ( + tag_encode(form=TagFormConstructed, num=4) + + b"\x80" + + b"".join(OctetString(content).encode() for content in contents) + + EOC + ) + chunks = [] + payload_expected = b"" + for chunk_input in chunk_inputs: + if isinstance(chunk_input, binary_type): + chunks.append(OctetString(chunk_input).encode()) + payload_expected += chunk_input + else: + chunks.append(chunk_constructed(chunk_input)) + payload = b"".join(chunk_input) + payload_expected += payload + encoded_indefinite = ( + tag_encode(form=TagFormConstructed, num=impl) + + b"\x80" + + b"".join(chunks) + + EOC + ) + encoded_definite = ( + tag_encode(form=TagFormConstructed, num=impl) + + len_encode(len(b"".join(chunks))) + + b"".join(chunks) + ) + with assertRaisesRegex(self, DecodeError, "unallowed BER"): + OctetString(impl=tag_encode(impl)).decode(encoded_indefinite) + for lenindef_expected, encoded in ( + (True, encoded_indefinite), + (False, encoded_definite), + ): + obj, tail = OctetString(impl=tag_encode(impl)).decode( + encoded, ctx={"bered": True} + ) + self.assertSequenceEqual(tail, b"") + self.assertSequenceEqual(bytes(obj), payload_expected) + self.assertTrue(obj.bered) + self.assertEqual(obj.lenindef, lenindef_expected) + self.assertEqual(len(encoded), obj.tlvlen) + @composite def null_values_strategy(draw, do_expl=False): @@ -1816,8 +2039,9 @@ class TestNull(CommonMixin, TestCase): null_values_strategy(), integers(min_value=1).map(tag_ctxc), integers(min_value=0), + binary(max_size=5), ) - def test_symmetric(self, values, tag_expl, offset): + def test_symmetric(self, values, tag_expl, offset, tail_junk): for klass in (Null, NullInherited): _, _, optional, _decoded = values obj = klass(optional=optional, _decoded=_decoded) @@ -1830,10 +2054,13 @@ class TestNull(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) @@ -2136,8 +2363,9 @@ class TestObjectIdentifier(CommonMixin, TestCase): oid_strategy(), integers(min_value=1).map(tag_ctxc), integers(min_value=0), + binary(max_size=5), ) - def test_symmetric(self, values, value, tag_expl, offset): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk): for klass in (ObjectIdentifier, ObjectIdentifierInherited): _, _, _, default, optional, _decoded = values obj = klass( @@ -2155,10 +2383,13 @@ class TestObjectIdentifier(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(tuple(obj_decoded), tuple(obj_expled)) @@ -2214,6 +2445,12 @@ class TestObjectIdentifier(CommonMixin, TestCase): data, ))) + def test_x690_vector(self): + self.assertEqual( + ObjectIdentifier().decode(hexdec("0603883703"))[0], + ObjectIdentifier((2, 999, 3)), + ) + @composite def enumerated_values_strategy(draw, schema=None, do_expl=False): @@ -2413,6 +2650,7 @@ class TestEnumerated(CommonMixin, TestCase): tag_expl = d.draw(integers(min_value=1).map(tag_ctxc)) offset = d.draw(integers(min_value=0)) value = d.draw(sampled_from(sorted([v for _, v in schema_input]))) + tail_junk = d.draw(binary(max_size=5)) class E(Enumerated): schema = schema_input @@ -2431,10 +2669,13 @@ class TestEnumerated(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(int(obj_decoded), int(obj_expled)) @@ -2525,8 +2766,8 @@ class StringMixin(object): def test_comparison(self, d): value1 = d.draw(text(alphabet=self.text_alphabet())) value2 = d.draw(text(alphabet=self.text_alphabet())) - tag1 = d.draw(binary()) - tag2 = d.draw(binary()) + tag1 = d.draw(binary(min_size=1)) + tag2 = d.draw(binary(min_size=1)) obj1 = self.base_klass(value1) obj2 = self.base_klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) @@ -2724,6 +2965,7 @@ class StringMixin(object): value = d.draw(text(alphabet=self.text_alphabet())) tag_expl = tag_ctxc(d.draw(integers(min_value=1))) offset = d.draw(integers(min_value=0)) + tail_junk = d.draw(binary(max_size=5)) _, _, _, _, default, optional, _decoded = values obj = self.base_klass( value=value, @@ -2740,10 +2982,13 @@ class StringMixin(object): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) @@ -2770,35 +3015,135 @@ class TestUTF8String(StringMixin, CommonMixin, TestCase): base_klass = UTF8String +class UnicodeDecodeErrorMixin(object): + @given(text( + alphabet="".join(six_unichr(i) for i in list(range(0x0410, 0x044f + 1))), + min_size=1, + max_size=5, + )) + def test_unicode_decode_error(self, cyrillic_text): + with self.assertRaises(DecodeError): + self.base_klass(cyrillic_text) + + class TestNumericString(StringMixin, CommonMixin, TestCase): base_klass = NumericString + def text_alphabet(self): + return digits + + @given(text(alphabet=ascii_letters, min_size=1, max_size=5)) + def test_non_numeric(self, cyrillic_text): + with assertRaisesRegex(self, DecodeError, "non-numeric"): + self.base_klass(cyrillic_text) -class TestPrintableString(StringMixin, CommonMixin, TestCase): + @given( + sets(integers(min_value=0, max_value=10), min_size=2, max_size=2), + integers(min_value=0), + lists(integers()), + ) + def test_invalid_bounds_while_decoding(self, ints, offset, decode_path): + decode_path = tuple(str(i) for i in decode_path) + value, bound_min = list(sorted(ints)) + + class String(self.base_klass): + bounds = (bound_min, bound_min) + with self.assertRaises(DecodeError) as err: + String().decode( + self.base_klass(b"1" * value).encode(), + offset=offset, + decode_path=decode_path, + ) + repr(err.exception) + self.assertEqual(err.exception.offset, offset) + self.assertEqual(err.exception.decode_path, decode_path) + + +class TestPrintableString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = PrintableString -class TestTeletexString(StringMixin, CommonMixin, TestCase): +class TestTeletexString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = TeletexString -class TestVideotexString(StringMixin, CommonMixin, TestCase): +class TestVideotexString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = VideotexString -class TestIA5String(StringMixin, CommonMixin, TestCase): +class TestIA5String( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = IA5String -class TestGraphicString(StringMixin, CommonMixin, TestCase): +class TestGraphicString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = GraphicString -class TestVisibleString(StringMixin, CommonMixin, TestCase): +class TestVisibleString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = VisibleString + def test_x690_vector(self): + obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573")) + self.assertSequenceEqual(tail, b"") + self.assertEqual(str(obj), "Jones") + self.assertFalse(obj.bered) + self.assertFalse(obj.lenindef) + + obj, tail = VisibleString().decode( + hexdec("3A0904034A6F6E04026573"), + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, b"") + self.assertEqual(str(obj), "Jones") + self.assertTrue(obj.bered) + self.assertFalse(obj.lenindef) + + obj, tail = VisibleString().decode( + hexdec("3A8004034A6F6E040265730000"), + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, b"") + self.assertEqual(str(obj), "Jones") + self.assertTrue(obj.bered) + self.assertTrue(obj.lenindef) + -class TestGeneralString(StringMixin, CommonMixin, TestCase): +class TestGeneralString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = GeneralString @@ -2884,8 +3229,8 @@ class TimeMixin(object): min_value=self.min_datetime, max_value=self.max_datetime, )) - tag1 = d.draw(binary()) - tag2 = d.draw(binary()) + tag1 = d.draw(binary(min_size=1)) + tag2 = d.draw(binary(min_size=1)) if self.omit_ms: value1 = value1.replace(microsecond=0) value2 = value2.replace(microsecond=0) @@ -3008,6 +3353,7 @@ class TimeMixin(object): )) tag_expl = tag_ctxc(d.draw(integers(min_value=1))) offset = d.draw(integers(min_value=0)) + tail_junk = d.draw(binary(max_size=5)) _, _, _, default, optional, _decoded = values obj = self.base_klass( value=value, @@ -3024,10 +3370,13 @@ class TimeMixin(object): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertEqual(obj_decoded.todatetime(), obj_expled.todatetime()) self.assertEqual(obj_decoded.todatetime(), obj.todatetime()) @@ -3326,8 +3675,9 @@ class TestAny(CommonMixin, TestCase): integers().map(lambda x: Integer(x).encode()), integers(min_value=1).map(tag_ctxc), integers(min_value=0), + binary(max_size=5), ) - def test_symmetric(self, values, value, tag_expl, offset): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk): for klass in (Any, AnyInherited): _, _, optional, _decoded = values obj = klass(value=value, optional=optional, _decoded=_decoded) @@ -3340,10 +3690,13 @@ class TestAny(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) self.assertEqual(bytes(obj_decoded), bytes(obj)) @@ -3570,6 +3923,7 @@ class TestChoice(CommonMixin, TestCase): ) tag_expl = tag_ctxc(d.draw(integers(min_value=1))) offset = d.draw(integers(min_value=0)) + tail_junk = d.draw(binary(max_size=5)) class Wahl(self.base_klass): schema = _schema @@ -3588,10 +3942,13 @@ class TestChoice(CommonMixin, TestCase): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertEqual(obj_decoded.choice, obj_expled.choice) self.assertEqual(obj_decoded.value, obj_expled.value) @@ -3651,6 +4008,26 @@ class TestChoice(CommonMixin, TestCase): with self.assertRaises(TagMismatch): obj.decode(int_encoded) + def test_tag_mismatch_underlying(self): + class SeqOfBoolean(SequenceOf): + schema = Boolean() + + class SeqOfInteger(SequenceOf): + schema = Integer() + + class Wahl(Choice): + schema = ( + ("erste", SeqOfBoolean()), + ) + + int_encoded = SeqOfInteger((Integer(123),)).encode() + bool_encoded = SeqOfBoolean((Boolean(False),)).encode() + obj = Wahl() + obj.decode(bool_encoded) + with self.assertRaises(TagMismatch) as err: + obj.decode(int_encoded) + self.assertEqual(err.exception.decode_path, ("erste", "0")) + @composite def seq_values_strategy(draw, seq_klass, do_expl=False): @@ -3826,7 +4203,7 @@ def sequences_strategy(draw, seq_klass): class SeqMixing(object): def test_invalid_value_type(self): with self.assertRaises(InvalidValueType) as err: - self.base_klass((1, 2, 3)) + self.base_klass(123) repr(err.exception) def test_invalid_value_type_set(self): @@ -4057,14 +4434,15 @@ class SeqMixing(object): @given(data_strategy()) def test_symmetric(self, d): seq, expects = d.draw(sequence_strategy(seq_klass=self.base_klass)) + tail_junk = d.draw(binary(max_size=5)) self.assertTrue(seq.ready) self.assertFalse(seq.decoded) self._assert_expects(seq, expects) repr(seq) pprint(seq) seq_encoded = seq.encode() - seq_decoded, tail = seq.decode(seq_encoded) - self.assertEqual(tail, b"") + seq_decoded, tail = seq.decode(seq_encoded + tail_junk) + self.assertEqual(tail, tail_junk) self.assertTrue(seq.ready) self._assert_expects(seq_decoded, expects) self.assertEqual(seq, seq_decoded) @@ -4227,6 +4605,16 @@ class TestSequence(SeqMixing, CommonMixin, TestCase): seq[missing] = Boolean() repr(err.exception) + def test_x690_vector(self): + class Seq(Sequence): + schema = ( + ("name", IA5String()), + ("ok", Boolean()), + ) + seq = Seq().decode(hexdec("300A1605536d6974680101FF"))[0] + self.assertEqual(seq["name"], "Smith") + self.assertEqual(seq["ok"], True) + class TestSet(SeqMixing, CommonMixin, TestCase): base_klass = Set @@ -4592,8 +4980,9 @@ class SeqOfMixing(object): lists(integers().map(Integer)), integers(min_value=1).map(tag_ctxc), integers(min_value=0), + binary(max_size=5), ) - def test_symmetric(self, values, value, tag_expl, offset): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk): _, _, _, _, _, default, optional, _decoded = values class SeqOf(self.base_klass): @@ -4613,10 +5002,13 @@ class SeqOfMixing(object): repr(obj_expled) pprint(obj_expled) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ) repr(obj_decoded) pprint(obj_decoded) - self.assertEqual(tail, b"") + self.assertEqual(tail, tail_junk) self._test_symmetric_compare_objs(obj_decoded, obj_expled) self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl) @@ -5003,6 +5395,9 @@ class TestDefinesByPath(TestCase): self.assertIsNotNone(seq_integered["value"].defined) self.assertEqual(seq_integered["value"].defined[0], type_integered) self.assertEqual(seq_integered["value"].defined[1], Integer(123)) + self.assertTrue(seq_integered_raw[ + seq_integered["value"].defined[1].offset: + ].startswith(Integer(123).encode())) seq_sequenced, _ = Seq().decode( seq_sequenced_raw, @@ -5014,7 +5409,7 @@ class TestDefinesByPath(TestCase): self.assertIsNone(seq_inner["valueInner"].defined) defines_by_path.append(( - ("value", decode_path_defby(type_sequenced), "typeInner"), + ("value", DecodePathDefBy(type_sequenced), "typeInner"), ((("valueInner",), {type_innered: Pairs()}),), )) seq_sequenced, _ = Seq().decode( @@ -5033,9 +5428,9 @@ class TestDefinesByPath(TestCase): defines_by_path.append(( ( "value", - decode_path_defby(type_sequenced), + DecodePathDefBy(type_sequenced), "valueInner", - decode_path_defby(type_innered), + DecodePathDefBy(type_innered), any, "type", ), @@ -5136,3 +5531,42 @@ class TestStrictDefaultExistence(TestCase): seq.decode(raw) with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): seq.decode(raw, ctx={"strict_default_existence": True}) + + +class TestX690PrefixedType(TestCase): + def runTest(self): + self.assertSequenceEqual( + VisibleString("Jones").encode(), + hexdec("1A054A6F6E6573"), + ) + self.assertSequenceEqual( + VisibleString( + "Jones", + impl=tag_encode(3, klass=TagClassApplication), + ).encode(), + hexdec("43054A6F6E6573"), + ) + self.assertSequenceEqual( + Any( + VisibleString( + "Jones", + impl=tag_encode(3, klass=TagClassApplication), + ), + expl=tag_ctxc(2), + ).encode(), + hexdec("A20743054A6F6E6573"), + ) + self.assertSequenceEqual( + OctetString( + VisibleString( + "Jones", + impl=tag_encode(3, klass=TagClassApplication), + ).encode(), + impl=tag_encode(7, form=TagFormConstructed, klass=TagClassApplication), + ).encode(), + hexdec("670743054A6F6E6573"), + ) + self.assertSequenceEqual( + VisibleString("Jones", impl=tag_ctxp(2)).encode(), + hexdec("82054A6F6E6573"), + )