X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=dd3506b88c3216a221fe15096ccdc1d8c8fbf9ff;hb=529183bf80e4796b3970d18e0fac490cd5865781;hp=a07c495ebd5d838219b6b2be69cc011911f734cf;hpb=3906f3ed9567496401ada4ba71a342215b8785f1;p=pyderasn.git diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index a07c495..dd3506b 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 @@ -77,6 +77,10 @@ from pyderasn import InvalidOID from pyderasn import InvalidValueType from pyderasn import len_decode from pyderasn import len_encode +from pyderasn import LEN_YYMMDDHHMMSSZ +from pyderasn import LEN_YYYYMMDDHHMMSSDMZ +from pyderasn import LEN_YYYYMMDDHHMMSSZ +from pyderasn import LENINDEF from pyderasn import NotEnoughData from pyderasn import Null from pyderasn import NumericString @@ -113,7 +117,6 @@ from pyderasn import VisibleString settings.register_profile("local", settings( deadline=5000, - perform_health_check=False, )) settings.load_profile("local") LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4 @@ -125,6 +128,9 @@ tag_classes = sampled_from(( TagClassUniversal, )) tag_forms = sampled_from((TagFormConstructed, TagFormPrimitive)) +decode_path_strat = lists(integers(), max_size=3).map( + lambda decode_path: tuple(str(dp) for dp in decode_path) +) class TestHex(TestCase): @@ -463,10 +469,9 @@ class TestBoolean(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Boolean().decode( tag_encode(tag)[:-1], @@ -480,10 +485,9 @@ class TestBoolean(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_expl_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Boolean(expl=Boolean.tag_default).decode( tag_encode(tag)[:-1], @@ -497,10 +501,9 @@ class TestBoolean(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Boolean().decode( Boolean.tag_default + len_encode(l)[:-1], @@ -514,10 +517,9 @@ class TestBoolean(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_expl_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Boolean(expl=Boolean.tag_default).decode( Boolean.tag_default + len_encode(l)[:-1], @@ -606,8 +608,27 @@ class TestBoolean(CommonMixin, TestCase): ctx={"bered": True}, ) self.assertTrue(bool(obj)) + self.assertTrue(obj.ber_encoded) + self.assertFalse(obj.lenindef) self.assertTrue(obj.bered) + + @given( + integers(min_value=1).map(tag_ctxc), + binary().filter(lambda x: not x.startswith(EOC)), + ) + def test_ber_expl_no_eoc(self, expl, junk): + encoded = expl + LENINDEF + Boolean(False).encode() + with assertRaisesRegex(self, DecodeError, "no EOC"): + Boolean(expl=expl).decode(encoded + junk, ctx={"bered": True}) + obj, tail = Boolean(expl=expl).decode( + encoded + EOC + junk, + ctx={"bered": True}, + ) + self.assertTrue(obj.expl_lenindef) self.assertFalse(obj.lenindef) + self.assertFalse(obj.ber_encoded) + self.assertTrue(obj.bered) + self.assertSequenceEqual(tail, junk) @given( integers(min_value=1).map(tag_ctxc), @@ -622,7 +643,7 @@ class TestBoolean(CommonMixin, TestCase): for value in values: encoded += ( expl + - b"\x80" + + LENINDEF + Boolean(value).encode() + EOC ) @@ -640,9 +661,10 @@ class TestBoolean(CommonMixin, TestCase): v.expl_tlvlen, v.expl_tlen, v.expl_llen, - v.bered, + v.ber_encoded, v.lenindef, v.expl_lenindef, + v.bered, ) for v in seqof ), set((( @@ -653,6 +675,7 @@ class TestBoolean(CommonMixin, TestCase): False, False, True, + True, ),)), ) @@ -792,9 +815,19 @@ class TestInteger(CommonMixin, TestCase): with self.assertRaises(BoundsError) as err: Integer(value=values[0], bounds=(values[1], values[2])) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + Integer(bounds=(values[1], values[2])).decode( + Integer(values[0]).encode() + ) + repr(err.exception) with self.assertRaises(BoundsError) as err: Integer(value=values[2], bounds=(values[0], values[1])) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + Integer(bounds=(values[0], values[1])).decode( + Integer(values[2]).encode() + ) + repr(err.exception) @given(data_strategy()) def test_call(self, d): @@ -913,10 +946,9 @@ class TestInteger(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Integer().decode( tag_encode(tag)[:-1], @@ -930,10 +962,9 @@ class TestInteger(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Integer().decode( Integer.tag_default + len_encode(l)[:-1], @@ -947,10 +978,9 @@ class TestInteger(CommonMixin, TestCase): @given( sets(integers(), min_size=2, max_size=2), integers(min_value=0), - lists(integers()), + decode_path_strat, ) 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 Int(Integer): @@ -1307,10 +1337,9 @@ class TestBitString(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: BitString().decode( tag_encode(tag)[:-1], @@ -1324,10 +1353,9 @@ class TestBitString(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: BitString().decode( BitString.tag_default + len_encode(l)[:-1], @@ -1462,12 +1490,13 @@ class TestBitString(CommonMixin, TestCase): max_size=3, ), lists(booleans(), min_size=1), + binary(), ) - def test_constructed(self, impl, chunk_inputs, chunk_last_bits): + def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk): def chunk_constructed(contents): return ( tag_encode(form=TagFormConstructed, num=3) + - b"\x80" + + LENINDEF + b"".join(BitString(content).encode() for content in contents) + EOC ) @@ -1491,7 +1520,7 @@ class TestBitString(CommonMixin, TestCase): bit_len_expected += chunk_last.bit_len encoded_indefinite = ( tag_encode(form=TagFormConstructed, num=impl) + - b"\x80" + + LENINDEF + b"".join(chunks) + chunk_last.encode() + EOC @@ -1509,15 +1538,133 @@ class TestBitString(CommonMixin, TestCase): (False, encoded_definite), ): obj, tail = BitString(impl=tag_encode(impl)).decode( - encoded, ctx={"bered": True} + encoded + junk, + ctx={"bered": True}, ) - self.assertSequenceEqual(tail, b"") + self.assertSequenceEqual(tail, junk) self.assertEqual(obj.bit_len, bit_len_expected) self.assertSequenceEqual(bytes(obj), payload_expected) - self.assertTrue(obj.bered) + self.assertTrue(obj.ber_encoded) self.assertEqual(obj.lenindef, lenindef_expected) + self.assertTrue(obj.bered) self.assertEqual(len(encoded), obj.tlvlen) + @given( + integers(min_value=0), + decode_path_strat, + ) + def test_ber_definite_too_short(self, offset, decode_path): + with assertRaisesRegex(self, DecodeError, "longer than data") as err: + BitString().decode( + tag_encode(3, form=TagFormConstructed) + len_encode(1), + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path) + self.assertEqual(err.exception.offset, offset) + + @given( + integers(min_value=0), + decode_path_strat, + ) + def test_ber_definite_no_data(self, offset, decode_path): + with assertRaisesRegex(self, DecodeError, "zero length") as err: + BitString().decode( + tag_encode(3, form=TagFormConstructed) + len_encode(0), + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path) + self.assertEqual(err.exception.offset, offset) + + @given( + integers(min_value=0), + decode_path_strat, + integers(min_value=1, max_value=3), + ) + def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks): + bs = BitString(b"data").encode() + with self.assertRaises(NotEnoughData) as err: + BitString().decode( + tag_encode(3, form=TagFormConstructed) + LENINDEF + chunks * bs, + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),)) + self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs)) + + @given( + integers(min_value=0), + decode_path_strat, + integers(min_value=1, max_value=3), + ) + def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks): + bs = BitString(b"data").encode() + bs_longer = BitString(b"data-longer").encode() + with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err: + BitString().decode( + ( + tag_encode(3, form=TagFormConstructed) + + len_encode((chunks + 1) * len(bs)) + + chunks * bs + + bs_longer + ), + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),)) + self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs)) + + @given( + integers(min_value=0), + decode_path_strat, + ) + def test_ber_indefinite_no_chunks(self, offset, decode_path): + with assertRaisesRegex(self, DecodeError, "no chunks") as err: + BitString().decode( + tag_encode(3, form=TagFormConstructed) + LENINDEF + EOC, + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path) + self.assertEqual(err.exception.offset, offset) + + @given(data_strategy()) + def test_ber_indefinite_not_multiple(self, d): + bs_short = BitString("'A'H").encode() + bs_full = BitString("'AA'H").encode() + chunks = [bs_full for _ in range(d.draw(integers(min_value=0, max_value=3)))] + chunks.append(bs_short) + d.draw(permutations(chunks)) + chunks.append(bs_short) + offset = d.draw(integers(min_value=0)) + decode_path = d.draw(decode_path_strat) + with assertRaisesRegex(self, DecodeError, "multiple of 8 bits") as err: + BitString().decode( + ( + tag_encode(3, form=TagFormConstructed) + + LENINDEF + + b"".join(chunks) + + EOC + ), + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual( + err.exception.decode_path, + decode_path + (str(chunks.index(bs_short)),), + ) + self.assertEqual( + err.exception.offset, + offset + 1 + 1 + chunks.index(bs_short) * len(bs_full), + ) + def test_x690_vector(self): vector = BitString("'0A3B5F291CD'H") obj, tail = BitString().decode(hexdec("0307040A3B5F291CD0")) @@ -1529,8 +1676,9 @@ class TestBitString(CommonMixin, TestCase): ) self.assertSequenceEqual(tail, b"") self.assertEqual(obj, vector) - self.assertTrue(obj.bered) + self.assertTrue(obj.ber_encoded) self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) @composite @@ -1632,10 +1780,20 @@ class TestOctetString(CommonMixin, TestCase): with self.assertRaises(BoundsError) as err: OctetString(value=value, bounds=(bound_min, bound_max)) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + OctetString(bounds=(bound_min, bound_max)).decode( + OctetString(value).encode() + ) + repr(err.exception) value = d.draw(binary(min_size=bound_max + 1)) with self.assertRaises(BoundsError) as err: OctetString(value=value, bounds=(bound_min, bound_max)) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + OctetString(bounds=(bound_min, bound_max)).decode( + OctetString(value).encode() + ) + repr(err.exception) @given(data_strategy()) def test_call(self, d): @@ -1739,10 +1897,9 @@ class TestOctetString(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: OctetString().decode( tag_encode(tag)[:-1], @@ -1756,10 +1913,9 @@ class TestOctetString(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: OctetString().decode( OctetString.tag_default + len_encode(l)[:-1], @@ -1773,10 +1929,9 @@ class TestOctetString(CommonMixin, TestCase): @given( sets(integers(min_value=0, max_value=10), min_size=2, max_size=2), integers(min_value=0), - lists(integers()), + decode_path_strat, ) 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(OctetString): @@ -1857,12 +2012,13 @@ class TestOctetString(CommonMixin, TestCase): min_size=1, max_size=3, ), + binary(), ) - def test_constructed(self, impl, chunk_inputs): + def test_constructed(self, impl, chunk_inputs, junk): def chunk_constructed(contents): return ( tag_encode(form=TagFormConstructed, num=4) + - b"\x80" + + LENINDEF + b"".join(OctetString(content).encode() for content in contents) + EOC ) @@ -1878,7 +2034,7 @@ class TestOctetString(CommonMixin, TestCase): payload_expected += payload encoded_indefinite = ( tag_encode(form=TagFormConstructed, num=impl) + - b"\x80" + + LENINDEF + b"".join(chunks) + EOC ) @@ -1894,14 +2050,71 @@ class TestOctetString(CommonMixin, TestCase): (False, encoded_definite), ): obj, tail = OctetString(impl=tag_encode(impl)).decode( - encoded, ctx={"bered": True} + encoded + junk, + ctx={"bered": True}, ) - self.assertSequenceEqual(tail, b"") + self.assertSequenceEqual(tail, junk) self.assertSequenceEqual(bytes(obj), payload_expected) - self.assertTrue(obj.bered) + self.assertTrue(obj.ber_encoded) self.assertEqual(obj.lenindef, lenindef_expected) + self.assertTrue(obj.bered) self.assertEqual(len(encoded), obj.tlvlen) + @given( + integers(min_value=0), + decode_path_strat, + ) + def test_ber_definite_too_short(self, offset, decode_path): + with assertRaisesRegex(self, DecodeError, "longer than data") as err: + OctetString().decode( + tag_encode(4, form=TagFormConstructed) + len_encode(1), + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path) + self.assertEqual(err.exception.offset, offset) + + @given( + integers(min_value=0), + decode_path_strat, + integers(min_value=1, max_value=3), + ) + def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks): + bs = OctetString(b"data").encode() + with self.assertRaises(NotEnoughData) as err: + OctetString().decode( + tag_encode(4, form=TagFormConstructed) + LENINDEF + chunks * bs, + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),)) + self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs)) + + @given( + integers(min_value=0), + decode_path_strat, + integers(min_value=1, max_value=3), + ) + def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks): + bs = OctetString(b"data").encode() + bs_longer = OctetString(b"data-longer").encode() + with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err: + OctetString().decode( + ( + tag_encode(4, form=TagFormConstructed) + + len_encode((chunks + 1) * len(bs)) + + chunks * bs + + bs_longer + ), + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),)) + self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs)) + @composite def null_values_strategy(draw, do_expl=False): @@ -1998,10 +2211,9 @@ class TestNull(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Null().decode( tag_encode(tag)[:-1], @@ -2015,10 +2227,9 @@ class TestNull(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Null().decode( Null.tag_default + len_encode(l)[:-1], @@ -2270,10 +2481,9 @@ class TestObjectIdentifier(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: ObjectIdentifier().decode( tag_encode(tag)[:-1], @@ -2287,10 +2497,9 @@ class TestObjectIdentifier(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: ObjectIdentifier().decode( ObjectIdentifier.tag_default + len_encode(l)[:-1], @@ -2798,10 +3007,20 @@ class StringMixin(object): with self.assertRaises(BoundsError) as err: self.base_klass(value=value, bounds=(bound_min, bound_max)) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + self.base_klass(bounds=(bound_min, bound_max)).decode( + self.base_klass(value).encode() + ) + repr(err.exception) value = d.draw(text(alphabet=self.text_alphabet(), min_size=bound_max + 1)) with self.assertRaises(BoundsError) as err: self.base_klass(value=value, bounds=(bound_min, bound_max)) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + self.base_klass(bounds=(bound_min, bound_max)).decode( + self.base_klass(value).encode() + ) + repr(err.exception) @given(data_strategy()) def test_call(self, d): @@ -2905,10 +3124,9 @@ class StringMixin(object): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: self.base_klass().decode( tag_encode(tag)[:-1], @@ -2922,10 +3140,9 @@ class StringMixin(object): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: self.base_klass().decode( self.base_klass.tag_default + len_encode(l)[:-1], @@ -2939,10 +3156,9 @@ class StringMixin(object): @given( sets(integers(min_value=0, max_value=10), min_size=2, max_size=2), integers(min_value=0), - lists(integers()), + decode_path_strat, ) 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): @@ -3040,10 +3256,9 @@ class TestNumericString(StringMixin, CommonMixin, TestCase): @given( sets(integers(min_value=0, max_value=10), min_size=2, max_size=2), integers(min_value=0), - lists(integers()), + decode_path_strat, ) 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): @@ -3116,8 +3331,9 @@ class TestVisibleString( obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573")) self.assertSequenceEqual(tail, b"") self.assertEqual(str(obj), "Jones") - self.assertFalse(obj.bered) + self.assertFalse(obj.ber_encoded) self.assertFalse(obj.lenindef) + self.assertFalse(obj.bered) obj, tail = VisibleString().decode( hexdec("3A0904034A6F6E04026573"), @@ -3125,8 +3341,9 @@ class TestVisibleString( ) self.assertSequenceEqual(tail, b"") self.assertEqual(str(obj), "Jones") - self.assertTrue(obj.bered) + self.assertTrue(obj.ber_encoded) self.assertFalse(obj.lenindef) + self.assertTrue(obj.bered) obj, tail = VisibleString().decode( hexdec("3A8004034A6F6E040265730000"), @@ -3134,8 +3351,9 @@ class TestVisibleString( ) self.assertSequenceEqual(tail, b"") self.assertEqual(str(obj), "Jones") - self.assertTrue(obj.bered) + self.assertTrue(obj.ber_encoded) self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) class TestGeneralString( @@ -3436,6 +3654,48 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): datetime(2010, 1, 2, 3, 4, 5, 0), ) + @given( + binary( + min_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2, + max_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2, + ), + binary(min_size=1, max_size=1), + binary( + min_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2, + max_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2, + ), + ) + def test_junk(self, part0, part1, part2): + junk = part0 + part1 + part2 + assume(not (set(junk) <= set(digits.encode("ascii")))) + with self.assertRaises(DecodeError): + GeneralizedTime().decode( + GeneralizedTime.tag_default + + len_encode(len(junk)) + + junk + ) + + @given( + binary( + min_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2, + max_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2, + ), + binary(min_size=1, max_size=1), + binary( + min_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2, + max_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2, + ), + ) + def test_junk_dm(self, part0, part1, part2): + junk = part0 + part1 + part2 + assume(not (set(junk) <= set(digits.encode("ascii")))) + with self.assertRaises(DecodeError): + GeneralizedTime().decode( + GeneralizedTime.tag_default + + len_encode(len(junk)) + + junk + ) + class TestUTCTime(TimeMixin, CommonMixin, TestCase): base_klass = UTCTime @@ -3500,6 +3760,27 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): 1900 + year, ) + @given( + binary( + min_size=(LEN_YYMMDDHHMMSSZ - 1) // 2, + max_size=(LEN_YYMMDDHHMMSSZ - 1) // 2, + ), + binary(min_size=1, max_size=1), + binary( + min_size=(LEN_YYMMDDHHMMSSZ - 1) // 2, + max_size=(LEN_YYMMDDHHMMSSZ - 1) // 2, + ), + ) + def test_junk(self, part0, part1, part2): + junk = part0 + part1 + part2 + assume(not (set(junk) <= set(digits.encode("ascii")))) + with self.assertRaises(DecodeError): + UTCTime().decode( + UTCTime.tag_default + + len_encode(len(junk)) + + junk + ) + @composite def any_values_strategy(draw, do_expl=False): @@ -3638,10 +3919,9 @@ class TestAny(CommonMixin, TestCase): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Any().decode( tag_encode(tag)[:-1], @@ -3655,10 +3935,9 @@ class TestAny(CommonMixin, TestCase): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: Any().decode( Any.tag_default + len_encode(l)[:-1], @@ -3718,17 +3997,60 @@ class TestAny(CommonMixin, TestCase): self.assertEqual(obj_decoded.llen, 0) self.assertEqual(obj_decoded.vlen, len(value)) + @given( + integers(min_value=1).map(tag_ctxc), + integers(min_value=0, max_value=3), + integers(min_value=0), + decode_path_strat, + binary(), + ) + def test_indefinite(self, expl, chunks, offset, decode_path, junk): + chunk = Boolean(False, expl=expl).encode() + encoded = ( + OctetString.tag_default + + LENINDEF + + b"".join([chunk] * chunks) + + EOC + ) + obj, tail = Any().decode( + encoded + junk, + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, junk) + self.assertEqual(obj.offset, offset) + self.assertEqual(obj.tlvlen, len(encoded)) + self.assertTrue(obj.lenindef) + self.assertFalse(obj.ber_encoded) + self.assertTrue(obj.bered) + with self.assertRaises(NotEnoughData) as err: + Any().decode( + encoded[:-1], + offset=offset, + decode_path=decode_path, + ctx={"bered": True}, + ) + self.assertEqual(err.exception.offset, offset + 1 + 1 + len(chunk) * chunks) + self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),)) + @composite def choice_values_strategy(draw, value_required=False, schema=None, do_expl=False): if schema is None: names = list(draw(sets(text_letters(), min_size=1, max_size=5))) - tags = [tag_encode(tag) for tag in draw(sets( - integers(min_value=0), + tags = [{tag_type: tag_value} for tag_type, tag_value in draw(sets( + one_of( + tuples(just("impl"), integers(min_value=0).map(tag_encode)), + tuples(just("expl"), integers(min_value=0).map(tag_ctxp)), + ), min_size=len(names), max_size=len(names), ))] - schema = [(name, Integer(impl=tag)) for name, tag in zip(names, tags)] + schema = [ + (name, Integer(**tag_kwargs)) + for name, tag_kwargs in zip(names, tags) + ] value = None if value_required or draw(booleans()): value = draw(tuples( @@ -3970,8 +4292,8 @@ class TestChoice(CommonMixin, TestCase): self.assertEqual(obj_decoded.expl_offset, offset) self.assertSequenceEqual( obj_expled_encoded[ - obj_decoded.value.offset - offset: - obj_decoded.value.offset + obj_decoded.value.tlvlen - offset + obj_decoded.value.fulloffset - offset: + obj_decoded.value.fulloffset + obj_decoded.value.fulllen - offset ], obj_encoded, ) @@ -4384,10 +4706,9 @@ class SeqMixing(object): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: self.base_klass().decode( tag_encode(tag)[:-1], @@ -4401,10 +4722,9 @@ class SeqMixing(object): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: self.base_klass().decode( self.base_klass.tag_default + len_encode(l)[:-1], @@ -4440,26 +4760,52 @@ class SeqMixing(object): self._assert_expects(seq, expects) repr(seq) pprint(seq) + self.assertTrue(seq.ready) seq_encoded = seq.encode() 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) - self.assertEqual(seq_decoded.encode(), seq_encoded) - for expect in expects: - if not expect["presented"]: - self.assertNotIn(expect["name"], seq_decoded) - continue - self.assertIn(expect["name"], seq_decoded) - obj = seq_decoded[expect["name"]] - self.assertTrue(obj.decoded) - offset = obj.expl_offset if obj.expled else obj.offset - tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen - self.assertSequenceEqual( - seq_encoded[offset:offset + tlvlen], - obj.encode(), - ) + self.assertFalse(seq_decoded.lenindef) + self.assertFalse(seq_decoded.ber_encoded) + self.assertFalse(seq_decoded.bered) + + t, _, lv = tag_strip(seq_encoded) + _, _, v = len_decode(lv) + seq_encoded_lenindef = t + LENINDEF + v + EOC + seq_decoded_lenindef, tail_lenindef = seq.decode( + seq_encoded_lenindef + tail_junk, + ctx={"bered": True}, + ) + self.assertTrue(seq_decoded_lenindef.lenindef) + self.assertTrue(seq_decoded_lenindef.bered) + with self.assertRaises(DecodeError): + seq.decode(seq_encoded_lenindef[:-1], ctx={"bered": True}) + with self.assertRaises(DecodeError): + seq.decode(seq_encoded_lenindef[:-2], ctx={"bered": True}) + repr(seq_decoded_lenindef) + pprint(seq_decoded_lenindef) + self.assertTrue(seq_decoded_lenindef.ready) + + for decoded, decoded_tail, encoded in ( + (seq_decoded, tail, seq_encoded), + (seq_decoded_lenindef, tail_lenindef, seq_encoded_lenindef), + ): + self.assertEqual(decoded_tail, tail_junk) + self._assert_expects(decoded, expects) + self.assertEqual(seq, decoded) + self.assertEqual(decoded.encode(), seq_encoded) + self.assertEqual(decoded.tlvlen, len(encoded)) + for expect in expects: + if not expect["presented"]: + self.assertNotIn(expect["name"], decoded) + continue + self.assertIn(expect["name"], decoded) + obj = decoded[expect["name"]] + self.assertTrue(obj.decoded) + offset = obj.expl_offset if obj.expled else obj.offset + tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen + self.assertSequenceEqual( + seq_encoded[offset:offset + tlvlen], + obj.encode(), + ) @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given(data_strategy()) @@ -4512,7 +4858,7 @@ class SeqMixing(object): self.assertSequenceEqual(seq.encode(), empty_seq) @given(data_strategy()) - def test_encoded_default_accepted(self, d): + def test_encoded_default_not_accepted(self, d): _schema = list(d.draw(dictionaries( text_letters(), integers(), @@ -4540,10 +4886,15 @@ class SeqMixing(object): for (n, v), t in zip(_schema, tags) ] seq_with_default = SeqWithDefault() - seq_decoded, _ = seq_with_default.decode(seq_encoded) - for name, value in _schema: - self.assertEqual(seq_decoded[name], seq_with_default[name]) - self.assertEqual(seq_decoded[name], value) + with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): + seq_with_default.decode(seq_encoded) + for ctx in ({"bered": True}, {"allow_default_values": True}): + seq_decoded, _ = seq_with_default.decode(seq_encoded, ctx=ctx) + self.assertTrue(seq_decoded.ber_encoded) + self.assertTrue(seq_decoded.bered) + for name, value in _schema: + self.assertEqual(seq_decoded[name], seq_with_default[name]) + self.assertEqual(seq_decoded[name], value) @given(data_strategy()) def test_missing_from_spec(self, d): @@ -4569,6 +4920,31 @@ class SeqMixing(object): with self.assertRaises(TagMismatch): seq_missing.decode(seq_encoded) + @given(data_strategy()) + def test_bered(self, d): + class Seq(self.base_klass): + schema = (("underlying", Boolean()),) + encoded = Boolean.tag_default + len_encode(1) + b"\x01" + encoded = Seq.tag_default + len_encode(len(encoded)) + encoded + decoded, _ = Seq().decode(encoded, ctx={"bered": True}) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + + class Seq(self.base_klass): + schema = (("underlying", OctetString()),) + encoded = ( + tag_encode(form=TagFormConstructed, num=4) + + LENINDEF + + OctetString(b"whatever").encode() + + EOC + ) + encoded = Seq.tag_default + len_encode(len(encoded)) + encoded + decoded, _ = Seq().decode(encoded, ctx={"bered": True}) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + class TestSequence(SeqMixing, CommonMixin, TestCase): base_klass = Sequence @@ -4639,6 +5015,36 @@ class TestSet(SeqMixing, CommonMixin, TestCase): b"".join(sorted([seq[name].encode() for name, _ in Seq.schema])), ) + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(data_strategy()) + def test_unsorted(self, d): + tags = [ + tag_encode(tag) for tag in + d.draw(sets(integers(min_value=1), min_size=2, max_size=5)) + ] + tags = d.draw(permutations(tags)) + assume(tags != sorted(tags)) + encoded = b"".join(OctetString(t, impl=t).encode() for t in tags) + seq_encoded = b"".join(( + Set.tag_default, + len_encode(len(encoded)), + encoded, + )) + + class Seq(Set): + schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)] + seq = Seq() + with assertRaisesRegex(self, DecodeError, "unordered SET"): + seq.decode(seq_encoded) + for ctx in ({"bered": True}, {"allow_unordered_set": True}): + seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx) + self.assertTrue(seq_decoded.ber_encoded) + self.assertTrue(seq_decoded.bered) + self.assertSequenceEqual( + [bytes(seq_decoded[str(i)]) for i, t in enumerate(tags)], + [t for t in tags], + ) + @composite def seqof_values_strategy(draw, schema=None, do_expl=False): @@ -4782,17 +5188,27 @@ class SeqOfMixing(object): schema = Boolean() bound_min = d.draw(integers(min_value=1, max_value=1 << 7)) bound_max = d.draw(integers(min_value=bound_min, max_value=1 << 7)) - value = [Boolean()] * d.draw(integers(max_value=bound_min - 1)) + value = [Boolean(False)] * d.draw(integers(max_value=bound_min - 1)) with self.assertRaises(BoundsError) as err: SeqOf(value=value, bounds=(bound_min, bound_max)) repr(err.exception) - value = [Boolean()] * d.draw(integers( + with assertRaisesRegex(self, DecodeError, "bounds") as err: + SeqOf(bounds=(bound_min, bound_max)).decode( + SeqOf(value).encode() + ) + repr(err.exception) + value = [Boolean(True)] * d.draw(integers( min_value=bound_max + 1, max_value=bound_max + 10, )) with self.assertRaises(BoundsError) as err: SeqOf(value=value, bounds=(bound_min, bound_max)) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + SeqOf(bounds=(bound_min, bound_max)).decode( + SeqOf(value).encode() + ) + repr(err.exception) @given(integers(min_value=1, max_value=10)) def test_out_of_bounds(self, bound_max): @@ -4937,10 +5353,9 @@ class SeqOfMixing(object): @given( integers(min_value=31), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_tag(self, tag, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: self.base_klass().decode( tag_encode(tag)[:-1], @@ -4954,10 +5369,9 @@ class SeqOfMixing(object): @given( integers(min_value=128), integers(min_value=0), - lists(integers()), + decode_path_strat, ) def test_bad_len(self, l, offset, decode_path): - decode_path = tuple(str(i) for i in decode_path) with self.assertRaises(DecodeError) as err: self.base_klass().decode( self.base_klass.tag_default + len_encode(l)[:-1], @@ -5034,6 +5448,50 @@ class SeqOfMixing(object): ], ) + t, _, lv = tag_strip(obj_encoded) + _, _, v = len_decode(lv) + obj_encoded_lenindef = t + LENINDEF + v + EOC + obj_decoded_lenindef, tail_lenindef = obj.decode( + obj_encoded_lenindef + tail_junk, + ctx={"bered": True}, + ) + self.assertTrue(obj_decoded_lenindef.lenindef) + self.assertTrue(obj_decoded_lenindef.bered) + repr(obj_decoded_lenindef) + pprint(obj_decoded_lenindef) + self.assertEqual(obj_decoded_lenindef.tlvlen, len(obj_encoded_lenindef)) + with self.assertRaises(DecodeError): + obj.decode(obj_encoded_lenindef[:-1], ctx={"bered": True}) + with self.assertRaises(DecodeError): + obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True}) + + @given(data_strategy()) + def test_bered(self, d): + class SeqOf(self.base_klass): + schema = Boolean() + encoded = Boolean(False).encode() + encoded += Boolean.tag_default + len_encode(1) + b"\x01" + encoded = SeqOf.tag_default + len_encode(len(encoded)) + encoded + decoded, _ = SeqOf().decode(encoded, ctx={"bered": True}) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + + class SeqOf(self.base_klass): + schema = OctetString() + encoded = OctetString(b"whatever").encode() + encoded += ( + tag_encode(form=TagFormConstructed, num=4) + + LENINDEF + + OctetString(b"whatever").encode() + + EOC + ) + encoded = SeqOf.tag_default + len_encode(len(encoded)) + encoded + decoded, _ = SeqOf().decode(encoded, ctx={"bered": True}) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase): class SeqOf(SequenceOf): @@ -5071,6 +5529,38 @@ class TestSetOf(SeqOfMixing, CommonMixin, TestCase): b"".join(sorted([v.encode() for v in values])), ) + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(data_strategy()) + def test_unsorted(self, d): + values = [OctetString(v).encode() for v in d.draw(sets( + binary(min_size=1, max_size=5), + min_size=2, + max_size=5, + ))] + values = d.draw(permutations(values)) + assume(values != sorted(values)) + encoded = b"".join(values) + seq_encoded = b"".join(( + SetOf.tag_default, + len_encode(len(encoded)), + encoded, + )) + + class Seq(SetOf): + schema = OctetString() + seq = Seq() + with assertRaisesRegex(self, DecodeError, "unordered SET OF"): + seq.decode(seq_encoded) + + for ctx in ({"bered": True}, {"allow_unordered_set": True}): + seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx) + self.assertTrue(seq_decoded.ber_encoded) + self.assertTrue(seq_decoded.bered) + self.assertSequenceEqual( + [obj.encode() for obj in seq_decoded], + values, + ) + class TestGoMarshalVectors(TestCase): def runTest(self): @@ -5519,18 +6009,23 @@ class TestStrictDefaultExistence(TestCase): ("int%d" % i, Integer(expl=tag_ctxc(i + 1))) for i in range(count) ] - - class Seq(Sequence): - schema = _schema - seq = Seq() - for i in range(count): - seq["int%d" % i] = Integer(123) - raw = seq.encode() - chosen = "int%d" % chosen - seq.specs[chosen] = seq.specs[chosen](default=123) - seq.decode(raw) - with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): - seq.decode(raw, ctx={"strict_default_existence": True}) + for klass in (Sequence, Set): + class Seq(klass): + schema = _schema + seq = Seq() + for i in range(count): + seq["int%d" % i] = Integer(123) + raw = seq.encode() + chosen_choice = "int%d" % chosen + seq.specs[chosen_choice] = seq.specs[chosen_choice](default=123) + with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): + seq.decode(raw) + decoded, _ = seq.decode(raw, ctx={"allow_default_values": True}) + self.assertTrue(decoded.ber_encoded) + self.assertTrue(decoded.bered) + decoded, _ = seq.decode(raw, ctx={"bered": True}) + self.assertTrue(decoded.ber_encoded) + self.assertTrue(decoded.bered) class TestX690PrefixedType(TestCase): @@ -5570,3 +6065,13 @@ class TestX690PrefixedType(TestCase): VisibleString("Jones", impl=tag_ctxp(2)).encode(), hexdec("82054A6F6E6573"), ) + + +class TestExplOOB(TestCase): + def runTest(self): + expl = tag_ctxc(123) + raw = Integer(123).encode() + Integer(234).encode() + raw = b"".join((expl, len_encode(len(raw)), raw)) + with assertRaisesRegex(self, DecodeError, "explicit tag out-of-bound"): + Integer(expl=expl).decode(raw) + Integer(expl=expl).decode(raw, ctx={"allow_expl_oob": True})