X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=c730633c5a8433bd1badd2a05f7ca20d5baeed43;hb=eeee170c0c7f0c89370c074c6e39592b1d7eca1d;hp=23127261feb9175309eea0f8b6e41356f0e7cf56;hpb=7aed684a10179b2e57e81369e956ac6df4fb135e;p=pyderasn.git diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 2312726..c730633 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -1,6 +1,6 @@ # coding: utf-8 -# PyDERASN -- Python ASN.1 DER codec with abstract structures -# Copyright (C) 2017 Sergey Matveev +# 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 # it under the terms of the GNU Lesser General Public License as @@ -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,23 +43,28 @@ 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 from pyderasn import Any from pyderasn import BitString 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 @@ -71,6 +77,7 @@ from pyderasn import InvalidOID from pyderasn import InvalidValueType from pyderasn import len_decode from pyderasn import len_encode +from pyderasn import LENINDEF from pyderasn import NotEnoughData from pyderasn import Null from pyderasn import NumericString @@ -105,11 +112,10 @@ 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(( @@ -119,6 +125,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): @@ -282,7 +291,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) @@ -291,7 +300,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 @@ -457,10 +466,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], @@ -474,10 +482,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], @@ -491,10 +498,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], @@ -508,10 +514,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], @@ -528,8 +533,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( @@ -547,10 +553,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)) @@ -580,13 +589,86 @@ 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), + 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.assertSequenceEqual(tail, junk) + + @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 + + LENINDEF + + 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 @@ -845,10 +927,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], @@ -862,10 +943,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], @@ -879,10 +959,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): @@ -903,8 +982,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( @@ -922,10 +1002,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)) @@ -1127,8 +1210,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): @@ -1235,10 +1318,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], @@ -1252,10 +1334,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], @@ -1278,6 +1359,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): @@ -1298,10 +1380,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)) @@ -1371,6 +1456,209 @@ 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), + binary(), + ) + def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk): + def chunk_constructed(contents): + return ( + tag_encode(form=TagFormConstructed, num=3) + + LENINDEF + + 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) + + LENINDEF + + 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 + junk, + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, junk) + 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) + + @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")) + 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): @@ -1436,7 +1724,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) @@ -1578,10 +1866,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], @@ -1595,10 +1882,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], @@ -1612,10 +1898,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): @@ -1636,8 +1921,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( @@ -1655,10 +1941,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)) @@ -1678,6 +1967,122 @@ 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, + ), + binary(), + ) + def test_constructed(self, impl, chunk_inputs, junk): + def chunk_constructed(contents): + return ( + tag_encode(form=TagFormConstructed, num=4) + + LENINDEF + + 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) + + LENINDEF + + 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 + junk, + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, junk) + self.assertSequenceEqual(bytes(obj), payload_expected) + self.assertTrue(obj.bered) + self.assertEqual(obj.lenindef, lenindef_expected) + 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): @@ -1774,10 +2179,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], @@ -1791,10 +2195,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], @@ -1815,8 +2218,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) @@ -1829,10 +2233,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) @@ -2042,10 +2449,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], @@ -2059,10 +2465,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], @@ -2135,8 +2540,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( @@ -2154,10 +2560,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)) @@ -2213,6 +2622,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): @@ -2412,6 +2827,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 @@ -2430,10 +2846,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)) @@ -2524,8 +2943,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) @@ -2663,10 +3082,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], @@ -2680,10 +3098,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], @@ -2697,10 +3114,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): @@ -2723,6 +3139,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, @@ -2739,10 +3156,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)) @@ -2769,35 +3189,134 @@ 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), + decode_path_strat, + ) + def test_invalid_bounds_while_decoding(self, ints, offset, 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) -class TestGeneralString(StringMixin, CommonMixin, TestCase): + 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( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = GeneralString @@ -2883,8 +3402,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) @@ -3007,6 +3526,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, @@ -3023,10 +3543,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()) @@ -3288,10 +3811,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], @@ -3305,10 +3827,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], @@ -3325,8 +3846,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) @@ -3339,10 +3861,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)) @@ -3364,6 +3889,40 @@ 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)) + 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): @@ -3569,6 +4128,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 @@ -3587,10 +4147,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) @@ -3650,6 +4213,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): @@ -3825,7 +4408,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): @@ -4006,10 +4589,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], @@ -4023,10 +4605,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], @@ -4056,31 +4637,55 @@ 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"") 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(), - ) + seq_encoded = seq.encode() + seq_decoded, tail = seq.decode(seq_encoded + tail_junk) + self.assertFalse(seq_decoded.lenindef) + + 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) + 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()) @@ -4226,6 +4831,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 @@ -4548,10 +5163,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], @@ -4565,10 +5179,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], @@ -4591,8 +5204,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): @@ -4612,10 +5226,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) @@ -4641,6 +5258,22 @@ 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) + 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}) + class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase): class SeqOf(SequenceOf): @@ -4902,9 +5535,9 @@ class TestOIDDefines(TestCase): max_size=len(value_names), )) _schema = [ - ("type", ObjectIdentifier(defines=(value_name_chosen, { + ("type", ObjectIdentifier(defines=(((value_name_chosen,), { oid: Integer() for oid in oids[:-1] - }))), + }),))), ] for i, value_name in enumerate(value_names): _schema.append((value_name, Any(expl=tag_ctxp(i)))) @@ -4927,7 +5560,7 @@ class TestOIDDefines(TestCase): class TestDefinesByPath(TestCase): - def runTest(self): + def test_generated(self): class Seq(Sequence): schema = ( ("type", ObjectIdentifier()), @@ -4990,27 +5623,39 @@ class TestDefinesByPath(TestCase): seq_integered, _ = Seq().decode(seq_integered_raw) self.assertIsNone(seq_integered["value"].defined) defines_by_path.append( - (("type",), ("value", { + (("type",), ((("value",), { type_integered: Integer(), type_sequenced: SeqInner(), - })) + }),)) + ) + seq_integered, _ = Seq().decode( + seq_integered_raw, + ctx={"defines_by_path": defines_by_path}, ) - seq_integered, _ = Seq().decode(seq_integered_raw, defines_by_path=defines_by_path) 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, defines_by_path=defines_by_path) + seq_sequenced, _ = Seq().decode( + seq_sequenced_raw, + ctx={"defines_by_path": defines_by_path}, + ) self.assertIsNotNone(seq_sequenced["value"].defined) self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced) seq_inner = seq_sequenced["value"].defined[1] self.assertIsNone(seq_inner["valueInner"].defined) defines_by_path.append(( - ("value", decode_path_defby(type_sequenced), "typeInner"), - ("valueInner", {type_innered: Pairs()}), + ("value", DecodePathDefBy(type_sequenced), "typeInner"), + ((("valueInner",), {type_innered: Pairs()}),), )) - seq_sequenced, _ = Seq().decode(seq_sequenced_raw, defines_by_path=defines_by_path) + seq_sequenced, _ = Seq().decode( + seq_sequenced_raw, + ctx={"defines_by_path": defines_by_path}, + ) self.assertIsNotNone(seq_sequenced["value"].defined) self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced) seq_inner = seq_sequenced["value"].defined[1] @@ -5023,18 +5668,21 @@ 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", ), - ("value", { + ((("value",), { type_integered: Integer(), type_octet_stringed: OctetString(), - }), + }),), )) - seq_sequenced, _ = Seq().decode(seq_sequenced_raw, defines_by_path=defines_by_path) + seq_sequenced, _ = Seq().decode( + seq_sequenced_raw, + ctx={"defines_by_path": defines_by_path}, + ) self.assertIsNotNone(seq_sequenced["value"].defined) self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced) seq_inner = seq_sequenced["value"].defined[1] @@ -5044,3 +5692,121 @@ class TestDefinesByPath(TestCase): for pair_input, pair_got in zip(pairs_input, pairs_got): self.assertEqual(pair_got["value"][0].defined[0], pair_input[0]) self.assertEqual(pair_got["value"][0].defined[1], pair_input[1]) + + @given(oid_strategy(), integers()) + def test_simple(self, oid, tgt): + class Inner(Sequence): + schema = ( + ("oid", ObjectIdentifier(defines=((("..", "tgt"), { + ObjectIdentifier(oid): Integer(), + }),))), + ) + + class Outer(Sequence): + schema = ( + ("inner", Inner()), + ("tgt", OctetString()), + ) + + inner = Inner() + inner["oid"] = ObjectIdentifier(oid) + outer = Outer() + outer["inner"] = inner + outer["tgt"] = OctetString(Integer(tgt).encode()) + decoded, _ = Outer().decode(outer.encode()) + self.assertEqual(decoded["tgt"].defined[1], Integer(tgt)) + + +class TestAbsDecodePath(TestCase): + @given( + lists(text(alphabet=ascii_letters, min_size=1)).map(tuple), + lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple), + ) + def test_concat(self, decode_path, rel_path): + self.assertSequenceEqual( + abs_decode_path(decode_path, rel_path), + decode_path + rel_path, + ) + + @given( + lists(text(alphabet=ascii_letters, min_size=1)).map(tuple), + lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple), + ) + def test_abs(self, decode_path, rel_path): + self.assertSequenceEqual( + abs_decode_path(decode_path, ("/",) + rel_path), + rel_path, + ) + + @given( + lists(text(alphabet=ascii_letters, min_size=1), min_size=5).map(tuple), + integers(min_value=1, max_value=3), + lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple), + ) + def test_dots(self, decode_path, number_of_dots, rel_path): + self.assertSequenceEqual( + abs_decode_path(decode_path, tuple([".."] * number_of_dots) + rel_path), + decode_path[:-number_of_dots] + rel_path, + ) + + +class TestStrictDefaultExistence(TestCase): + @given(data_strategy()) + def runTest(self, d): + count = d.draw(integers(min_value=1, max_value=10)) + chosen = d.draw(integers(min_value=0, max_value=count - 1)) + _schema = [ + ("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}) + + +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"), + )