X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=aaac6cd56957f2bff73496750b953d7565d58697;hb=de2299f02a411f3b805058afa84118cf361c99c8;hp=2fa22ab34daa594c3799b2ad12580ae124e86bd6;hpb=664822cb0fc85360a5f77d93ed54714c42557c74;p=pyderasn.git diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 2fa22ab..aaac6cd 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -1,11 +1,10 @@ # 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-2020 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 -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# published by the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,8 +15,10 @@ # License along with this program. If not, see # . +from copy import deepcopy 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,14 +43,17 @@ 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 @@ -57,7 +61,11 @@ from pyderasn import Boolean from pyderasn import BoundsError from pyderasn import Choice from pyderasn import DecodeError +from pyderasn import DecodePathDefBy from pyderasn import Enumerated +from pyderasn import EOC +from pyderasn import EOC_LEN +from pyderasn import ExceedingData from pyderasn import GeneralizedTime from pyderasn import GeneralString from pyderasn import GraphicString @@ -70,6 +78,11 @@ 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 LenIndefForm from pyderasn import NotEnoughData from pyderasn import Null from pyderasn import NumericString @@ -85,6 +98,7 @@ from pyderasn import SequenceOf from pyderasn import Set from pyderasn import SetOf from pyderasn import tag_ctxc +from pyderasn import tag_ctxp from pyderasn import tag_decode from pyderasn import tag_encode from pyderasn import tag_strip @@ -103,11 +117,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(( @@ -117,6 +130,16 @@ 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) +) +ctx_dummy = dictionaries(integers(), integers(), min_size=2, max_size=4).example() + + +def assert_exceeding_data(self, call, junk): + if len(junk) > 0: + with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)): + call() class TestHex(TestCase): @@ -280,7 +303,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) @@ -289,10 +312,9 @@ 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): - __slots__ = () impl = impl_tag obj = Inherited() self.assertSequenceEqual(obj.impl, impl_tag) @@ -301,7 +323,6 @@ class CommonMixin(object): @given(binary()) def test_expl_inherited(self, expl_tag): class Inherited(self.base_klass): - __slots__ = () expl = expl_tag obj = Inherited() self.assertSequenceEqual(obj.expl, expl_tag) @@ -319,7 +340,7 @@ class CommonMixin(object): @composite -def boolean_values_strat(draw, do_expl=False): +def boolean_values_strategy(draw, do_expl=False): value = draw(one_of(none(), booleans())) impl = None expl = None @@ -338,7 +359,7 @@ def boolean_values_strat(draw, do_expl=False): class BooleanInherited(Boolean): - __slots__ = () + pass class TestBoolean(CommonMixin, TestCase): @@ -359,14 +380,16 @@ class TestBoolean(CommonMixin, TestCase): obj = Boolean() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) obj = Boolean(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given(booleans(), booleans(), binary(), binary()) def test_comparison(self, value1, value2, tag1, tag2): @@ -374,10 +397,12 @@ class TestBoolean(CommonMixin, TestCase): obj1 = klass(value1) obj2 = klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == bool(obj2), value1 == value2) obj1 = klass(value1, impl=tag1) obj2 = klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(data_strategy()) def test_call(self, d): @@ -389,7 +414,7 @@ class TestBoolean(CommonMixin, TestCase): default_initial, optional_initial, _decoded_initial, - ) = d.draw(boolean_values_strat()) + ) = d.draw(boolean_values_strategy()) obj_initial = klass( value_initial, impl_initial, @@ -405,7 +430,7 @@ class TestBoolean(CommonMixin, TestCase): default, optional, _decoded, - ) = d.draw(boolean_values_strat(do_expl=impl_initial is None)) + ) = d.draw(boolean_values_strategy(do_expl=impl_initial is None)) obj = obj_initial(value, impl, expl, default, optional) if obj.ready: value_expected = default if value is None else value @@ -427,7 +452,7 @@ class TestBoolean(CommonMixin, TestCase): optional = True self.assertEqual(obj.optional, optional) - @given(boolean_values_strat()) + @given(boolean_values_strategy()) def test_copy(self, values): for klass in (Boolean, BooleanInherited): obj = klass(*values) @@ -455,10 +480,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], @@ -472,10 +496,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], @@ -489,10 +512,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], @@ -506,10 +528,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], @@ -522,12 +543,13 @@ class TestBoolean(CommonMixin, TestCase): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( - boolean_values_strat(), + boolean_values_strategy(), 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( @@ -537,18 +559,27 @@ class TestBoolean(CommonMixin, TestCase): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bool(obj_decoded), bool(obj_expled)) @@ -567,6 +598,11 @@ class TestBoolean(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given(integers(min_value=2)) def test_invalid_len(self, l): @@ -578,17 +614,115 @@ 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.ber_encoded) + self.assertFalse(obj.lenindef) + self.assertTrue(obj.bered) + obj = obj.copy() + 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 self.assertRaises(LenIndefForm): + Boolean(expl=expl).decode(encoded + junk) + 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) + obj = obj.copy() + self.assertTrue(obj.expl_lenindef) + self.assertFalse(obj.lenindef) + self.assertFalse(obj.ber_encoded) + self.assertTrue(obj.bered) + self.assertSequenceEqual(tail, junk) + repr(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) + + @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) + with self.assertRaises(LenIndefForm): + SeqOf().decode(encoded) + 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.ber_encoded, + v.lenindef, + v.expl_lenindef, + v.bered, + ) for v in seqof + ), + set((( + 3 + EOC_LEN, + len(expl) + 1 + 3 + EOC_LEN, + len(expl), + 1, + False, + False, + True, + True, + ),)), + ) + repr(seqof) + list(seqof.pps()) + pprint(seqof, big_blobs=True, with_decode_path=True) @composite -def integer_values_strat(draw, do_expl=False): +def integer_values_strategy(draw, do_expl=False): bound_min, value, default, bound_max = sorted(draw(sets( integers(), min_size=4, @@ -626,7 +760,7 @@ def integer_values_strat(draw, do_expl=False): class IntegerInherited(Integer): - __slots__ = () + pass class TestInteger(CommonMixin, TestCase): @@ -642,7 +776,6 @@ class TestInteger(CommonMixin, TestCase): missing = names_input.pop() class Int(Integer): - __slots__ = () schema = [(n, 123) for n in names_input] with self.assertRaises(ObjUnknown) as err: Int(missing) @@ -651,7 +784,6 @@ class TestInteger(CommonMixin, TestCase): @given(sets(text_letters(), min_size=2)) def test_known_name(self, names_input): class Int(Integer): - __slots__ = () schema = [(n, 123) for n in names_input] Int(names_input.pop()) @@ -665,14 +797,16 @@ class TestInteger(CommonMixin, TestCase): obj = Integer() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) obj = Integer(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) hash(obj) @given(integers(), integers(), binary(), binary()) @@ -681,10 +815,12 @@ class TestInteger(CommonMixin, TestCase): obj1 = klass(value1) obj2 = klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == int(obj2), value1 == value2) obj1 = klass(value1, impl=tag1) obj2 = klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(lists(integers())) def test_sorted_works(self, values): @@ -705,7 +841,6 @@ class TestInteger(CommonMixin, TestCase): names_input = dict(zip(names_input, values_input)) class Int(Integer): - __slots__ = () schema = names_input _int = Int(chosen_name) self.assertEqual(_int.named, chosen_name) @@ -723,9 +858,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): @@ -739,7 +884,7 @@ class TestInteger(CommonMixin, TestCase): optional_initial, _specs_initial, _decoded_initial, - ) = d.draw(integer_values_strat()) + ) = d.draw(integer_values_strategy()) obj_initial = klass( value_initial, bounds_initial, @@ -759,7 +904,7 @@ class TestInteger(CommonMixin, TestCase): optional, _, _decoded, - ) = d.draw(integer_values_strat(do_expl=impl_initial is None)) + ) = d.draw(integer_values_strategy(do_expl=impl_initial is None)) if (default is None) and (obj_initial.default is not None): bounds = None if ( @@ -805,7 +950,7 @@ class TestInteger(CommonMixin, TestCase): {} if _specs_initial is None else dict(_specs_initial), ) - @given(integer_values_strat()) + @given(integer_values_strategy()) def test_copy(self, values): for klass in (Integer, IntegerInherited): obj = klass(*values) @@ -844,10 +989,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], @@ -861,10 +1005,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], @@ -878,10 +1021,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): @@ -898,12 +1040,13 @@ class TestInteger(CommonMixin, TestCase): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( - integer_values_strat(), + integer_values_strategy(), 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( @@ -913,18 +1056,27 @@ class TestInteger(CommonMixin, TestCase): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(int(obj_decoded), int(obj_expled)) @@ -943,6 +1095,11 @@ class TestInteger(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) def test_go_vectors_valid(self): for data, expect in (( @@ -983,7 +1140,7 @@ class TestInteger(CommonMixin, TestCase): @composite -def bit_string_values_strat(draw, schema=None, value_required=False, do_expl=False): +def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl=False): if schema is None: schema = () if draw(booleans()): @@ -997,7 +1154,7 @@ def bit_string_values_strat(draw, schema=None, value_required=False, do_expl=Fal def _value(value_required): if not value_required and draw(booleans()): - return + return None generation_choice = 0 if value_required: generation_choice = draw(sampled_from((1, 2, 3))) @@ -1006,9 +1163,9 @@ def bit_string_values_strat(draw, schema=None, value_required=False, do_expl=Fal sampled_from(("0", "1")), max_size=len(schema), ))) - elif generation_choice == 2 or draw(booleans()): + if generation_choice == 2 or draw(booleans()): return draw(binary(max_size=len(schema) // 8)) - elif generation_choice == 3 or draw(booleans()): + if generation_choice == 3 or draw(booleans()): return tuple(draw(lists(sampled_from([name for name, _ in schema])))) return None value = _value(value_required) @@ -1029,7 +1186,7 @@ def bit_string_values_strat(draw, schema=None, value_required=False, do_expl=Fal class BitStringInherited(BitString): - __slots__ = () + pass class TestBitString(CommonMixin, TestCase): @@ -1063,7 +1220,6 @@ class TestBitString(CommonMixin, TestCase): self.assertGreater(len(obj.encode()), (leading_zeros + 1 + trailing_zeros) // 8) class BS(BitString): - __slots__ = () schema = (("whatever", 0),) obj = BS("'%s1%s'B" % (("0" * leading_zeros), ("0" * trailing_zeros))) self.assertEqual(obj.bit_len, leading_zeros + 1) @@ -1089,7 +1245,7 @@ class TestBitString(CommonMixin, TestCase): BitString(b"whatever")["whenever"] repr(err.exception) - def test_get_invalid_typ(self): + def test_get_invalid_type(self): with self.assertRaises(InvalidValueType) as err: BitString(b"whatever")[(1, 2, 3)] repr(err.exception) @@ -1100,7 +1256,6 @@ class TestBitString(CommonMixin, TestCase): missing = _schema.pop() class BS(BitString): - __slots__ = () schema = [(n, i) for i, n in enumerate(_schema)] with self.assertRaises(ObjUnknown) as err: BS((missing,)) @@ -1116,30 +1271,34 @@ class TestBitString(CommonMixin, TestCase): obj = BitString() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) obj = BitString(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @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): obj1 = klass(value1) obj2 = klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == bytes(obj2), value1[1] == value2[1]) obj1 = klass(value1, impl=tag1) obj2 = klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(data_strategy()) def test_call(self, d): @@ -1152,10 +1311,9 @@ class TestBitString(CommonMixin, TestCase): default_initial, optional_initial, _decoded_initial, - ) = d.draw(bit_string_values_strat()) + ) = d.draw(bit_string_values_strategy()) class BS(klass): - __slots__ = () schema = schema_initial obj_initial = BS( value=value_initial, @@ -1173,7 +1331,7 @@ class TestBitString(CommonMixin, TestCase): default, optional, _decoded, - ) = d.draw(bit_string_values_strat( + ) = d.draw(bit_string_values_strategy( schema=schema_initial, do_expl=impl_initial is None, )) @@ -1194,13 +1352,12 @@ class TestBitString(CommonMixin, TestCase): self.assertEqual(obj.optional, optional) self.assertEqual(obj.specs, obj_initial.specs) - @given(bit_string_values_strat()) + @given(bit_string_values_strategy()) def test_copy(self, values): for klass in (BitString, BitStringInherited): _schema, value, impl, expl, default, optional, _decoded = values class BS(klass): - __slots__ = () schema = _schema obj = BS( value=value, @@ -1236,10 +1393,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], @@ -1253,10 +1409,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,12 +1433,12 @@ class TestBitString(CommonMixin, TestCase): default, optional, _decoded, - ) = d.draw(bit_string_values_strat(value_required=True)) + ) = 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): class BS(klass): - __slots__ = () schema = _schema obj = BS( value=value, @@ -1292,18 +1447,27 @@ class TestBitString(CommonMixin, TestCase): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) @@ -1326,6 +1490,11 @@ class TestBitString(CommonMixin, TestCase): self.assertSetEqual(set(value), set(obj_decoded.named)) for name in value: obj_decoded[name] + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given(integers(min_value=1, max_value=255)) def test_bad_zero_value(self, pad_size): @@ -1373,9 +1542,222 @@ 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.ber_encoded) + self.assertEqual(obj.lenindef, lenindef_expected) + self.assertTrue(obj.bered) + obj = obj.copy() + 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")) + 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.ber_encoded) + self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) + obj = obj.copy() + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) + @composite -def octet_string_values_strat(draw, do_expl=False): +def octet_string_values_strategy(draw, do_expl=False): bound_min, bound_max = sorted(draw(sets( integers(min_value=0, max_value=1 << 7), min_size=2, @@ -1408,7 +1790,7 @@ def octet_string_values_strat(draw, do_expl=False): class OctetStringInherited(OctetString): - __slots__ = () + pass class TestOctetString(CommonMixin, TestCase): @@ -1429,25 +1811,36 @@ class TestOctetString(CommonMixin, TestCase): obj = OctetString() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) obj = OctetString(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) - @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) obj2 = klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == bytes(obj2), value1 == value2) obj1 = klass(value1, impl=tag1) obj2 = klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) + + @given(lists(binary())) + def test_sorted_works(self, values): + self.assertSequenceEqual( + [bytes(v) for v in sorted(OctetString(v) for v in values)], + sorted(values), + ) @given(data_strategy()) def test_bounds_satisfied(self, d): @@ -1464,10 +1857,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): @@ -1480,7 +1883,7 @@ class TestOctetString(CommonMixin, TestCase): default_initial, optional_initial, _decoded_initial, - ) = d.draw(octet_string_values_strat()) + ) = d.draw(octet_string_values_strategy()) obj_initial = klass( value_initial, bounds_initial, @@ -1498,7 +1901,7 @@ class TestOctetString(CommonMixin, TestCase): default, optional, _decoded, - ) = d.draw(octet_string_values_strat(do_expl=impl_initial is None)) + ) = d.draw(octet_string_values_strategy(do_expl=impl_initial is None)) if (default is None) and (obj_initial.default is not None): bounds = None if ( @@ -1540,7 +1943,7 @@ class TestOctetString(CommonMixin, TestCase): bounds or bounds_initial or (0, float("+inf")), ) - @given(octet_string_values_strat()) + @given(octet_string_values_strategy()) def test_copy(self, values): for klass in (OctetString, OctetStringInherited): obj = klass(*values) @@ -1571,10 +1974,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], @@ -1588,10 +1990,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], @@ -1605,10 +2006,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): @@ -1625,12 +2025,13 @@ class TestOctetString(CommonMixin, TestCase): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( - octet_string_values_strat(), + octet_string_values_strategy(), 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( @@ -1640,18 +2041,27 @@ class TestOctetString(CommonMixin, TestCase): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) @@ -1670,10 +2080,136 @@ class TestOctetString(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) + + @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.ber_encoded) + self.assertEqual(obj.lenindef, lenindef_expected) + self.assertTrue(obj.bered) + obj = obj.copy() + 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_strat(draw, do_expl=False): +def null_values_strategy(draw, do_expl=False): impl = None expl = None if do_expl: @@ -1690,7 +2226,7 @@ def null_values_strat(draw, do_expl=False): class NullInherited(Null): - __slots__ = () + pass class TestNull(CommonMixin, TestCase): @@ -1700,7 +2236,8 @@ class TestNull(CommonMixin, TestCase): obj = Null() self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given(binary(), binary()) def test_comparison(self, tag1, tag2): @@ -1708,6 +2245,7 @@ class TestNull(CommonMixin, TestCase): obj1 = klass(impl=tag1) obj2 = klass(impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) self.assertNotEqual(obj1, tag2) @given(data_strategy()) @@ -1718,7 +2256,7 @@ class TestNull(CommonMixin, TestCase): expl_initial, optional_initial, _decoded_initial, - ) = d.draw(null_values_strat()) + ) = d.draw(null_values_strategy()) obj_initial = klass( impl=impl_initial, expl=expl_initial, @@ -1730,7 +2268,7 @@ class TestNull(CommonMixin, TestCase): expl, optional, _decoded, - ) = d.draw(null_values_strat(do_expl=impl_initial is None)) + ) = d.draw(null_values_strategy(do_expl=impl_initial is None)) obj = obj_initial(impl=impl, expl=expl, optional=optional) self.assertEqual(obj.tag, impl or impl_initial or obj.tag_default) self.assertEqual(obj.expl_tag, expl or expl_initial) @@ -1738,7 +2276,7 @@ class TestNull(CommonMixin, TestCase): optional = False if optional is None else optional self.assertEqual(obj.optional, optional) - @given(null_values_strat()) + @given(null_values_strategy()) def test_copy(self, values): for klass in (Null, NullInherited): impl, expl, optional, _decoded = values @@ -1766,10 +2304,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], @@ -1783,10 +2320,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], @@ -1804,27 +2340,37 @@ class TestNull(CommonMixin, TestCase): Null(impl=impl).decode(Null().encode()) @given( - null_values_strat(), + 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) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) @@ -1841,6 +2387,11 @@ class TestNull(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given(integers(min_value=1)) def test_invalid_len(self, l): @@ -1864,7 +2415,7 @@ def oid_strategy(draw): @composite -def oid_values_strat(draw, do_expl=False): +def oid_values_strategy(draw, do_expl=False): value = draw(one_of(none(), oid_strategy())) impl = None expl = None @@ -1883,7 +2434,7 @@ def oid_values_strat(draw, do_expl=False): class ObjectIdentifierInherited(ObjectIdentifier): - __slots__ = () + pass class TestObjectIdentifier(CommonMixin, TestCase): @@ -1904,14 +2455,17 @@ class TestObjectIdentifier(CommonMixin, TestCase): obj = ObjectIdentifier() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) obj = ObjectIdentifier(value) self.assertTrue(obj.ready) + self.assertFalse(obj.ber_encoded) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) hash(obj) @given(oid_strategy(), oid_strategy(), binary(), binary()) @@ -1920,11 +2474,13 @@ class TestObjectIdentifier(CommonMixin, TestCase): obj1 = klass(value1) obj2 = klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == tuple(obj2), value1 == value2) self.assertEqual(str(obj1) == str(obj2), value1 == value2) obj1 = klass(value1, impl=tag1) obj2 = klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(lists(oid_strategy())) def test_sorted_works(self, values): @@ -1943,14 +2499,14 @@ class TestObjectIdentifier(CommonMixin, TestCase): default_initial, optional_initial, _decoded_initial, - ) = d.draw(oid_values_strat()) + ) = d.draw(oid_values_strategy()) obj_initial = klass( - value_initial, - impl_initial, - expl_initial, - default_initial, - optional_initial or False, - _decoded_initial, + value=value_initial, + impl=impl_initial, + expl=expl_initial, + default=default_initial, + optional=optional_initial or False, + _decoded=_decoded_initial, ) ( value, @@ -1959,8 +2515,14 @@ class TestObjectIdentifier(CommonMixin, TestCase): default, optional, _decoded, - ) = d.draw(oid_values_strat(do_expl=impl_initial is None)) - obj = obj_initial(value, impl, expl, default, optional) + ) = d.draw(oid_values_strategy(do_expl=impl_initial is None)) + obj = obj_initial( + value=value, + impl=impl, + expl=expl, + default=default, + optional=optional, + ) if obj.ready: value_expected = default if value is None else value value_expected = ( @@ -1981,10 +2543,25 @@ class TestObjectIdentifier(CommonMixin, TestCase): optional = True self.assertEqual(obj.optional, optional) - @given(oid_values_strat()) + @given(oid_values_strategy()) def test_copy(self, values): for klass in (ObjectIdentifier, ObjectIdentifierInherited): - obj = klass(*values) + ( + value, + impl, + expl, + default, + optional, + _decoded, + ) = values + obj = klass( + value=value, + impl=impl, + expl=expl, + default=default, + optional=optional, + _decoded=_decoded, + ) obj_copied = obj.copy() self.assert_copied_basic_fields(obj, obj_copied) self.assertEqual(obj._value, obj_copied._value) @@ -2011,10 +2588,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], @@ -2028,10 +2604,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], @@ -2096,16 +2671,17 @@ class TestObjectIdentifier(CommonMixin, TestCase): self.assertEqual(obj, ObjectIdentifier(".".join(str(arc) for arc in oid))) str(obj) repr(obj) - pprint(obj) + pprint(obj, big_blobs=True, with_decode_path=True) @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( - oid_values_strat(), + oid_values_strategy(), 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( @@ -2115,18 +2691,27 @@ class TestObjectIdentifier(CommonMixin, TestCase): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(tuple(obj_decoded), tuple(obj_expled)) @@ -2145,6 +2730,11 @@ class TestObjectIdentifier(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given( oid_strategy().map(ObjectIdentifier), @@ -2182,9 +2772,64 @@ class TestObjectIdentifier(CommonMixin, TestCase): data, ))) + def test_x690_vector(self): + self.assertEqual( + ObjectIdentifier().decode(hexdec("0603883703"))[0], + ObjectIdentifier((2, 999, 3)), + ) + + def test_nonnormalized_first_arc(self): + tampered = ( + ObjectIdentifier.tag_default + + len_encode(2) + + b'\x80' + + ObjectIdentifier((1, 0)).encode()[-1:] + ) + obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True}) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + obj = obj.copy() + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"): + ObjectIdentifier().decode(tampered) + + @given(data_strategy()) + def test_nonnormalized_arcs(self, d): + arcs = d.draw(lists( + integers(min_value=0, max_value=100), + min_size=1, + max_size=5, + )) + dered = ObjectIdentifier((1, 0) + tuple(arcs)).encode() + _, _, lv = tag_strip(dered) + _, _, v = len_decode(lv) + v_no_first_arc = v[1:] + idx_for_tamper = d.draw(integers( + min_value=0, + max_value=len(v_no_first_arc) - 1, + )) + tampered = list(bytearray(v_no_first_arc)) + for _ in range(d.draw(integers(min_value=1, max_value=3))): + tampered.insert(idx_for_tamper, 0x80) + tampered = bytes(bytearray(tampered)) + tampered = ( + ObjectIdentifier.tag_default + + len_encode(len(tampered)) + + tampered + ) + obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True}) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + obj = obj.copy() + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"): + ObjectIdentifier().decode(tampered) + @composite -def enumerated_values_strat(draw, schema=None, do_expl=False): +def enumerated_values_strategy(draw, schema=None, do_expl=False): if schema is None: schema = list(draw(sets(text_printable, min_size=1, max_size=3))) values = list(draw(sets( @@ -2212,7 +2857,6 @@ def enumerated_values_strat(draw, schema=None, do_expl=False): class TestEnumerated(CommonMixin, TestCase): class EWhatever(Enumerated): - __slots__ = () schema = (("whatever", 0),) base_klass = EWhatever @@ -2231,7 +2875,6 @@ class TestEnumerated(CommonMixin, TestCase): missing = schema_input.pop() class E(Enumerated): - __slots__ = () schema = [(n, 123) for n in schema_input] with self.assertRaises(ObjUnknown) as err: E(missing) @@ -2247,7 +2890,6 @@ class TestEnumerated(CommonMixin, TestCase): _input = list(zip(schema_input, values_input)) class E(Enumerated): - __slots__ = () schema = _input with self.assertRaises(DecodeError) as err: E(missing_value) @@ -2262,34 +2904,37 @@ class TestEnumerated(CommonMixin, TestCase): obj = self.base_klass() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) obj = self.base_klass("whatever") self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given(integers(), integers(), binary(), binary()) def test_comparison(self, value1, value2, tag1, tag2): class E(Enumerated): - __slots__ = () schema = ( ("whatever0", value1), ("whatever1", value2), ) class EInherited(E): - __slots__ = () + pass for klass in (E, EInherited): obj1 = klass(value1) obj2 = klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == int(obj2), value1 == value2) obj1 = klass(value1, impl=tag1) obj2 = klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(data_strategy()) def test_call(self, d): @@ -2301,10 +2946,9 @@ class TestEnumerated(CommonMixin, TestCase): default_initial, optional_initial, _decoded_initial, - ) = d.draw(enumerated_values_strat()) + ) = d.draw(enumerated_values_strategy()) class E(Enumerated): - __slots__ = () schema = schema_initial obj_initial = E( value=value_initial, @@ -2322,7 +2966,7 @@ class TestEnumerated(CommonMixin, TestCase): default, optional, _decoded, - ) = d.draw(enumerated_values_strat( + ) = d.draw(enumerated_values_strategy( schema=schema_initial, do_expl=impl_initial is None, )) @@ -2357,12 +3001,11 @@ class TestEnumerated(CommonMixin, TestCase): self.assertEqual(obj.optional, optional) self.assertEqual(obj.specs, dict(schema_initial)) - @given(enumerated_values_strat()) + @given(enumerated_values_strategy()) def test_copy(self, values): schema_input, value, impl, expl, default, optional, _decoded = values class E(Enumerated): - __slots__ = () schema = schema_input obj = E( value=value, @@ -2380,14 +3023,14 @@ class TestEnumerated(CommonMixin, TestCase): @given(data_strategy()) def test_symmetric(self, d): schema_input, _, _, _, default, optional, _decoded = d.draw( - enumerated_values_strat(), + enumerated_values_strategy(), ) 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): - __slots__ = () schema = schema_input obj = E( value=value, @@ -2396,18 +3039,27 @@ class TestEnumerated(CommonMixin, TestCase): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(int(obj_decoded), int(obj_expled)) @@ -2426,10 +3078,15 @@ class TestEnumerated(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @composite -def string_values_strat(draw, alphabet, do_expl=False): +def string_values_strategy(draw, alphabet, do_expl=False): bound_min, bound_max = sorted(draw(sets( integers(min_value=0, max_value=1 << 7), min_size=2, @@ -2482,7 +3139,8 @@ class StringMixin(object): obj = self.base_klass() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) text_type(obj) with self.assertRaises(ObjNotReady) as err: obj.encode() @@ -2491,23 +3149,26 @@ class StringMixin(object): obj = self.base_klass(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) text_type(obj) @given(data_strategy()) 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) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == bytes(obj2), value1 == value2) self.assertEqual(obj1 == text_type(obj2), value1 == value2) obj1 = self.base_klass(value1, impl=tag1) obj2 = self.base_klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(data_strategy()) def test_bounds_satisfied(self, d): @@ -2528,10 +3189,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): @@ -2543,7 +3214,7 @@ class StringMixin(object): default_initial, optional_initial, _decoded_initial, - ) = d.draw(string_values_strat(self.text_alphabet())) + ) = d.draw(string_values_strategy(self.text_alphabet())) obj_initial = self.base_klass( value_initial, bounds_initial, @@ -2561,7 +3232,7 @@ class StringMixin(object): default, optional, _decoded, - ) = d.draw(string_values_strat( + ) = d.draw(string_values_strategy( self.text_alphabet(), do_expl=impl_initial is None, )) @@ -2608,7 +3279,7 @@ class StringMixin(object): @given(data_strategy()) def test_copy(self, d): - values = d.draw(string_values_strat(self.text_alphabet())) + values = d.draw(string_values_strategy(self.text_alphabet())) obj = self.base_klass(*values) obj_copied = obj.copy() self.assert_copied_basic_fields(obj, obj_copied) @@ -2635,10 +3306,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], @@ -2652,10 +3322,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], @@ -2669,10 +3338,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): @@ -2691,10 +3359,11 @@ class StringMixin(object): @given(data_strategy()) def test_symmetric(self, d): - values = d.draw(string_values_strat(self.text_alphabet())) + values = d.draw(string_values_strategy(self.text_alphabet())) 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, @@ -2703,18 +3372,27 @@ class StringMixin(object): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + self.assertEqual(tail, tail_junk) self.assertEqual(obj_decoded, obj_expled) self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) @@ -2735,41 +3413,202 @@ class StringMixin(object): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) class TestUTF8String(StringMixin, CommonMixin, TestCase): base_klass = UTF8String -class TestNumericString(StringMixin, CommonMixin, TestCase): - base_klass = NumericString +cyrillic_letters = text( + alphabet="".join(six_unichr(i) for i in list(range(0x0410, 0x044f + 1))), + min_size=1, + max_size=5, +) -class TestPrintableString(StringMixin, CommonMixin, TestCase): - base_klass = PrintableString +class UnicodeDecodeErrorMixin(object): + @given(cyrillic_letters) + def test_unicode_decode_error(self, cyrillic_text): + with self.assertRaises(DecodeError): + self.base_klass(cyrillic_text) -class TestTeletexString(StringMixin, CommonMixin, TestCase): - base_klass = TeletexString +class TestNumericString(StringMixin, CommonMixin, TestCase): + base_klass = NumericString + def text_alphabet(self): + return digits + " " -class TestVideotexString(StringMixin, CommonMixin, TestCase): - base_klass = VideotexString + @given(text(alphabet=ascii_letters, min_size=1, max_size=5)) + def test_non_numeric(self, non_numeric_text): + with assertRaisesRegex(self, DecodeError, "non-numeric"): + self.base_klass(non_numeric_text) + @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 + + def text_alphabet(self): + return ascii_letters + digits + " '()+,-./:=?" + + @given(text(alphabet=sorted(set(whitespace) - set(" ")), min_size=1, max_size=5)) + def test_non_printable(self, non_printable_text): + with assertRaisesRegex(self, DecodeError, "non-printable"): + self.base_klass(non_printable_text) + + @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) + + def test_allowable_invalid_chars(self): + for c, kwargs in ( + ("*", {"allow_asterisk": True}), + ("&", {"allow_ampersand": True}), + ("&*", {"allow_asterisk": True, "allow_ampersand": True}), + ): + s = "hello invalid " + c + with assertRaisesRegex(self, DecodeError, "non-printable"): + self.base_klass(s) + self.base_klass(s, **kwargs) + klass = self.base_klass(**kwargs) + obj = klass(s) + obj = obj.copy() + obj(s) + + +class TestTeletexString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): + base_klass = TeletexString -class TestIA5String(StringMixin, CommonMixin, TestCase): + +class TestVideotexString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): + base_klass = VideotexString + + +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 - -class TestGeneralString(StringMixin, CommonMixin, TestCase): + def test_x690_vector(self): + obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573")) + self.assertSequenceEqual(tail, b"") + self.assertEqual(str(obj), "Jones") + self.assertFalse(obj.ber_encoded) + self.assertFalse(obj.lenindef) + self.assertFalse(obj.bered) + + obj, tail = VisibleString().decode( + hexdec("3A0904034A6F6E04026573"), + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, b"") + self.assertEqual(str(obj), "Jones") + self.assertTrue(obj.ber_encoded) + self.assertFalse(obj.lenindef) + self.assertTrue(obj.bered) + obj = obj.copy() + self.assertTrue(obj.ber_encoded) + self.assertFalse(obj.lenindef) + self.assertTrue(obj.bered) + + obj, tail = VisibleString().decode( + hexdec("3A8004034A6F6E040265730000"), + ctx={"bered": True}, + ) + self.assertSequenceEqual(tail, b"") + self.assertEqual(str(obj), "Jones") + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) + obj = obj.copy() + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) + + +class TestGeneralString( + UnicodeDecodeErrorMixin, + StringMixin, + CommonMixin, + TestCase, +): base_klass = GeneralString @@ -2782,7 +3621,7 @@ class TestBMPString(StringMixin, CommonMixin, TestCase): @composite -def generalized_time_values_strat( +def generalized_time_values_strategy( draw, min_datetime, max_datetime, @@ -2835,7 +3674,8 @@ class TimeMixin(object): obj = self.base_klass() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) @@ -2843,7 +3683,8 @@ class TimeMixin(object): obj = self.base_klass(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given(data_strategy()) def test_comparison(self, d): @@ -2855,19 +3696,21 @@ 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) obj1 = self.base_klass(value1) obj2 = self.base_klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == obj2.todatetime(), value1 == value2) self.assertEqual(obj1 == bytes(obj2), value1 == value2) obj1 = self.base_klass(value1, impl=tag1) obj2 = self.base_klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(data_strategy()) def test_call(self, d): @@ -2878,7 +3721,7 @@ class TimeMixin(object): default_initial, optional_initial, _decoded_initial, - ) = d.draw(generalized_time_values_strat( + ) = d.draw(generalized_time_values_strategy( min_datetime=self.min_datetime, max_datetime=self.max_datetime, omit_ms=self.omit_ms, @@ -2898,7 +3741,7 @@ class TimeMixin(object): default, optional, _decoded, - ) = d.draw(generalized_time_values_strat( + ) = d.draw(generalized_time_values_strategy( min_datetime=self.min_datetime, max_datetime=self.max_datetime, omit_ms=self.omit_ms, @@ -2933,7 +3776,7 @@ class TimeMixin(object): @given(data_strategy()) def test_copy(self, d): - values = d.draw(generalized_time_values_strat( + values = d.draw(generalized_time_values_strategy( min_datetime=self.min_datetime, max_datetime=self.max_datetime, )) @@ -2967,7 +3810,7 @@ class TimeMixin(object): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given(data_strategy()) def test_symmetric(self, d): - values = d.draw(generalized_time_values_strat( + values = d.draw(generalized_time_values_strategy( min_datetime=self.min_datetime, max_datetime=self.max_datetime, )) @@ -2977,6 +3820,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, @@ -2985,18 +3829,28 @@ class TimeMixin(object): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.additional_symmetric_check(value, obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + 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()) @@ -3014,6 +3868,11 @@ class TimeMixin(object): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): @@ -3022,6 +3881,28 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): min_datetime = datetime(1900, 1, 1) max_datetime = datetime(9999, 12, 31) + def additional_symmetric_check(self, value, obj_encoded): + if value.microsecond > 0: + self.assertFalse(obj_encoded.endswith(b"0Z")) + + def test_x690_vector_valid(self): + for data in (( + b"19920521000000Z", + b"19920622123421Z", + b"19920722132100.3Z", + )): + GeneralizedTime(data) + + def test_x690_vector_invalid(self): + for data in (( + b"19920520240000Z", + b"19920622123421.0Z", + b"19920722132100.30Z", + )): + with self.assertRaises(DecodeError) as err: + GeneralizedTime(data) + repr(err.exception) + def test_go_vectors_invalid(self): for data in (( b"20100102030405", @@ -3056,6 +3937,53 @@ 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 + ) + + def test_ns_fractions(self): + GeneralizedTime(b"20010101000000.000001Z") + with assertRaisesRegex(self, DecodeError, "only microsecond fractions"): + GeneralizedTime(b"20010101000000.0000001Z") + class TestUTCTime(TimeMixin, CommonMixin, TestCase): base_klass = UTCTime @@ -3063,6 +3991,26 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): min_datetime = datetime(2000, 1, 1) max_datetime = datetime(2049, 12, 31) + def additional_symmetric_check(self, value, obj_encoded): + pass + + def test_x690_vector_valid(self): + for data in (( + b"920521000000Z", + b"920622123421Z", + b"920722132100Z", + )): + UTCTime(data) + + def test_x690_vector_invalid(self): + for data in (( + b"920520240000Z", + b"9207221321Z", + )): + with self.assertRaises(DecodeError) as err: + UTCTime(data) + repr(err.exception) + def test_go_vectors_invalid(self): for data in (( b"a10506234540Z", @@ -3120,9 +4068,30 @@ 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_strat(draw, do_expl=False): +def any_values_strategy(draw, do_expl=False): value = draw(one_of(none(), binary())) expl = None if do_expl: @@ -3137,7 +4106,7 @@ def any_values_strat(draw, do_expl=False): class AnyInherited(Any): - __slots__ = () + pass class TestAny(CommonMixin, TestCase): @@ -3158,14 +4127,16 @@ class TestAny(CommonMixin, TestCase): obj = Any() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) obj = Any(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given(integers()) def test_basic(self, value): @@ -3181,7 +4152,8 @@ class TestAny(CommonMixin, TestCase): len(integer_encoded), ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertSequenceEqual(obj.encode(), integer_encoded) @given(binary(), binary()) @@ -3190,6 +4162,7 @@ class TestAny(CommonMixin, TestCase): obj1 = klass(value1) obj2 = klass(value2) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == bytes(obj2), value1 == value2) @given(data_strategy()) @@ -3200,7 +4173,7 @@ class TestAny(CommonMixin, TestCase): expl_initial, optional_initial, _decoded_initial, - ) = d.draw(any_values_strat()) + ) = d.draw(any_values_strategy()) obj_initial = klass( value_initial, expl_initial, @@ -3212,7 +4185,7 @@ class TestAny(CommonMixin, TestCase): expl, optional, _decoded, - ) = d.draw(any_values_strat(do_expl=True)) + ) = d.draw(any_values_strategy(do_expl=True)) obj = obj_initial(value, expl, optional) if obj.ready: value_expected = None if value is None else value @@ -3231,7 +4204,7 @@ class TestAny(CommonMixin, TestCase): # override it, as Any does not have implicit tag pass - @given(any_values_strat()) + @given(any_values_strategy()) def test_copy(self, values): for klass in (Any, AnyInherited): obj = klass(*values) @@ -3257,10 +4230,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], @@ -3274,10 +4246,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], @@ -3290,28 +4261,38 @@ class TestAny(CommonMixin, TestCase): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( - any_values_strat(), + any_values_strategy(), 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) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + 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)) @@ -3332,18 +4313,120 @@ class TestAny(CommonMixin, TestCase): self.assertEqual(obj_decoded.tlen, 0) self.assertEqual(obj_decoded.llen, 0) self.assertEqual(obj_decoded.vlen, len(value)) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) + + @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 + ) + with self.assertRaises(LenIndefForm): + Any().decode( + encoded + junk, + offset=offset, + decode_path=decode_path, + ) + 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) + obj = obj.copy() + self.assertTrue(obj.lenindef) + self.assertFalse(obj.ber_encoded) + self.assertTrue(obj.bered) + repr(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) + 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),)) + + class SeqOf(SequenceOf): + schema = Boolean(expl=expl) + + class Seq(Sequence): + schema = ( + ("type", ObjectIdentifier(defines=((("value",), { + ObjectIdentifier("1.2.3"): SeqOf(impl=OctetString.tag_default), + }),))), + ("value", Any()), + ) + seq = Seq(( + ("type", ObjectIdentifier("1.2.3")), + ("value", Any(encoded)), + )) + seq_encoded = seq.encode() + seq_decoded, _ = Seq().decode(seq_encoded, ctx={"bered": True}) + self.assertIsNotNone(seq_decoded["value"].defined) + repr(seq_decoded) + list(seq_decoded.pps()) + pprint(seq_decoded, big_blobs=True, with_decode_path=True) + self.assertTrue(seq_decoded.bered) + self.assertFalse(seq_decoded["type"].bered) + self.assertTrue(seq_decoded["value"].bered) + + chunk = chunk[:-1] + b"\x01" + chunks = b"".join([chunk] * (chunks + 1)) + encoded = OctetString.tag_default + len_encode(len(chunks)) + chunks + seq = Seq(( + ("type", ObjectIdentifier("1.2.3")), + ("value", Any(encoded)), + )) + seq_encoded = seq.encode() + seq_decoded, _ = Seq().decode(seq_encoded, ctx={"bered": True}) + self.assertIsNotNone(seq_decoded["value"].defined) + repr(seq_decoded) + list(seq_decoded.pps()) + pprint(seq_decoded, big_blobs=True, with_decode_path=True) + self.assertTrue(seq_decoded.bered) + self.assertFalse(seq_decoded["type"].bered) + self.assertTrue(seq_decoded["value"].bered) @composite -def choice_values_strat(draw, value_required=False, schema=None, do_expl=False): +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( @@ -3367,7 +4450,7 @@ def choice_values_strat(draw, value_required=False, schema=None, do_expl=False): class ChoiceInherited(Choice): - __slots__ = () + pass class TestChoice(CommonMixin, TestCase): @@ -3407,7 +4490,8 @@ class TestChoice(CommonMixin, TestCase): obj = self.base_klass() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertIsNone(obj["whatever"]) with self.assertRaises(ObjNotReady) as err: obj.encode() @@ -3415,20 +4499,23 @@ class TestChoice(CommonMixin, TestCase): obj["whatever"] = Boolean() self.assertFalse(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) obj["whatever"] = Boolean(value) self.assertTrue(obj.ready) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given(booleans(), booleans()) def test_comparison(self, value1, value2): class WahlInherited(self.base_klass): - __slots__ = () + pass for klass in (self.base_klass, WahlInherited): obj1 = klass(("whatever", Boolean(value1))) obj2 = klass(("whatever", Boolean(value2))) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == obj2._value, value1 == value2) self.assertFalse(obj1 == obj2._value[1]) @@ -3442,10 +4529,9 @@ class TestChoice(CommonMixin, TestCase): default_initial, optional_initial, _decoded_initial, - ) = d.draw(choice_values_strat()) + ) = d.draw(choice_values_strategy()) class Wahl(klass): - __slots__ = () schema = schema_initial obj_initial = Wahl( value=value_initial, @@ -3461,7 +4547,7 @@ class TestChoice(CommonMixin, TestCase): default, optional, _decoded, - ) = d.draw(choice_values_strat(schema=schema_initial, do_expl=True)) + ) = d.draw(choice_values_strategy(schema=schema_initial, do_expl=True)) obj = obj_initial(value, expl, default, optional) if obj.ready: value_expected = default if value is None else value @@ -3492,12 +4578,11 @@ class TestChoice(CommonMixin, TestCase): # override it, as Any does not have implicit tag pass - @given(choice_values_strat()) + @given(choice_values_strategy()) def test_copy(self, values): _schema, value, expl, default, optional, _decoded = values class Wahl(self.base_klass): - __slots__ = () schema = _schema obj = Wahl( value=value, @@ -3535,13 +4620,13 @@ class TestChoice(CommonMixin, TestCase): @given(data_strategy()) def test_symmetric(self, d): _schema, value, _, default, optional, _decoded = d.draw( - choice_values_strat(value_required=True) + choice_values_strategy(value_required=True) ) 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): - __slots__ = () schema = _schema obj = Wahl( value=value, @@ -3550,18 +4635,27 @@ class TestChoice(CommonMixin, TestCase): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + 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) @@ -3583,11 +4677,16 @@ 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, ) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given(integers()) def test_set_get(self, value): @@ -3621,21 +4720,39 @@ 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_strat(draw, seq_klass, do_expl=False): +def seq_values_strategy(draw, seq_klass, do_expl=False): value = None if draw(booleans()): value = seq_klass() - value._value = { - k: v for k, v in draw(dictionaries( - integers(), - one_of( - booleans().map(Boolean), - integers().map(Integer), - ), - )).items() - } + value._value = draw(dictionaries( + integers(), + one_of( + booleans().map(Boolean), + integers().map(Integer), + ), + )) schema = None if draw(booleans()): schema = list(draw(dictionaries( @@ -3654,15 +4771,13 @@ def seq_values_strat(draw, seq_klass, do_expl=False): default = None if draw(booleans()): default = seq_klass() - default._value = { - k: v for k, v in draw(dictionaries( - integers(), - one_of( - booleans().map(Boolean), - integers().map(Integer), - ), - )).items() - } + default._value = draw(dictionaries( + integers(), + one_of( + booleans().map(Boolean), + integers().map(Integer), + ), + )) optional = draw(one_of(none(), booleans())) _decoded = ( draw(integers(min_value=0)), @@ -3673,7 +4788,7 @@ def seq_values_strat(draw, seq_klass, do_expl=False): @composite -def sequence_strat(draw, seq_klass): +def sequence_strategy(draw, seq_klass): inputs = draw(lists( one_of( tuples(just(Boolean), booleans(), one_of(none(), booleans())), @@ -3714,7 +4829,7 @@ def sequence_strat(draw, seq_klass): for i, (klass, value, default) in enumerate(inputs): schema.append((names[i], klass(default=default, **inits[i]))) seq_name = draw(text_letters()) - Seq = type(seq_name, (seq_klass,), {"__slots__": (), "schema": tuple(schema)}) + Seq = type(seq_name, (seq_klass,), {"schema": tuple(schema)}) seq = Seq() expects = [] for i, (klass, value, default) in enumerate(inputs): @@ -3742,7 +4857,7 @@ def sequence_strat(draw, seq_klass): @composite -def sequences_strat(draw, seq_klass): +def sequences_strategy(draw, seq_klass): tags = draw(sets(integers(min_value=1), min_size=0, max_size=5)) inits = [ ({"expl": tag_ctxc(tag)} if expled else {"impl": tag_encode(tag)}) @@ -3765,7 +4880,7 @@ def sequences_strat(draw, seq_klass): max_size=len(tags), ))) seq_expectses = draw(lists( - sequence_strat(seq_klass=seq_klass), + sequence_strategy(seq_klass=seq_klass), min_size=len(tags), max_size=len(tags), )) @@ -3777,7 +4892,7 @@ def sequences_strat(draw, seq_klass): seq(default=(seq if i in defaulted else None), **inits[i]), )) seq_name = draw(text_letters()) - Seq = type(seq_name, (seq_klass,), {"__slots__": (), "schema": tuple(schema)}) + Seq = type(seq_name, (seq_klass,), {"schema": tuple(schema)}) seq_outer = Seq() expect_outers = [] for name, (seq_inner, expects_inner) in zip(names, seq_expectses): @@ -3796,12 +4911,11 @@ def sequences_strat(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): class Seq(self.base_klass): - __slots__ = () schema = (("whatever", Boolean()),) seq = Seq() with self.assertRaises(InvalidValueType) as err: @@ -3836,7 +4950,6 @@ class SeqMixing(object): schema_input.append((name, Boolean())) class Seq(self.base_klass): - __slots__ = () schema = tuple(schema_input) seq = Seq() for name in ready.keys(): @@ -3844,12 +4957,14 @@ class SeqMixing(object): seq[name] = Boolean() self.assertFalse(seq.ready) repr(seq) - pprint(seq) + list(seq.pps()) + pprint(seq, big_blobs=True, with_decode_path=True) for name, value in ready.items(): seq[name] = Boolean(value) self.assertFalse(seq.ready) repr(seq) - pprint(seq) + list(seq.pps()) + pprint(seq, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: seq.encode() repr(err.exception) @@ -3857,12 +4972,13 @@ class SeqMixing(object): seq[name] = Boolean(value) self.assertTrue(seq.ready) repr(seq) - pprint(seq) + list(seq.pps()) + pprint(seq, big_blobs=True, with_decode_path=True) @given(data_strategy()) def test_call(self, d): class SeqInherited(self.base_klass): - __slots__ = () + pass for klass in (self.base_klass, SeqInherited): ( value_initial, @@ -3872,7 +4988,7 @@ class SeqMixing(object): default_initial, optional_initial, _decoded_initial, - ) = d.draw(seq_values_strat(seq_klass=klass)) + ) = d.draw(seq_values_strategy(seq_klass=klass)) obj_initial = klass( value_initial, schema_initial, @@ -3890,7 +5006,7 @@ class SeqMixing(object): default, optional, _decoded, - ) = d.draw(seq_values_strat( + ) = d.draw(seq_values_strategy( seq_klass=klass, do_expl=impl_initial is None, )) @@ -3918,9 +5034,9 @@ class SeqMixing(object): @given(data_strategy()) def test_copy(self, d): class SeqInherited(self.base_klass): - __slots__ = () + pass for klass in (self.base_klass, SeqInherited): - values = d.draw(seq_values_strat(seq_klass=klass)) + values = d.draw(seq_values_strategy(seq_klass=klass)) obj = klass(*values) obj_copied = obj.copy() self.assert_copied_basic_fields(obj, obj_copied) @@ -3933,7 +5049,6 @@ class SeqMixing(object): tag_impl = tag_encode(d.draw(integers(min_value=1))) class Seq(self.base_klass): - __slots__ = () impl = tag_impl schema = (("whatever", Integer()),) seq = Seq() @@ -3947,7 +5062,6 @@ class SeqMixing(object): tag_expl = tag_ctxc(d.draw(integers(min_value=1))) class Seq(self.base_klass): - __slots__ = () expl = tag_expl schema = (("whatever", Integer()),) seq = Seq() @@ -3966,7 +5080,6 @@ class SeqMixing(object): assume(False) class Seq(self.base_klass): - __slots__ = () schema = ( ("whatever", Integer()), ("junk", Any()), @@ -3982,10 +5095,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], @@ -3999,10 +5111,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], @@ -4031,37 +5142,79 @@ class SeqMixing(object): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given(data_strategy()) def test_symmetric(self, d): - seq, expects = d.draw(sequence_strat(seq_klass=self.base_klass)) + 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"") + list(seq.pps()) + pprint(seq, big_blobs=True, with_decode_path=True) 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) + 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 + ctx_copied = deepcopy(ctx_dummy) + ctx_copied["bered"] = True + seq_decoded_lenindef, tail_lenindef = seq.decode( + seq_encoded_lenindef + tail_junk, + ctx=ctx_copied, + ) + del ctx_copied["bered"] + self.assertDictEqual(ctx_copied, ctx_dummy) + self.assertTrue(seq_decoded_lenindef.lenindef) + self.assertTrue(seq_decoded_lenindef.bered) + seq_decoded_lenindef = seq_decoded_lenindef.copy() + 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) + list(seq_decoded_lenindef.pps()) + pprint(seq_decoded_lenindef, big_blobs=True, with_decode_path=True) + 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(), + ) + + assert_exceeding_data( + self, + lambda: seq.decod(seq_encoded_lenindef + tail_junk, ctx={"bered": True}), + tail_junk, + ) @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given(data_strategy()) def test_symmetric_with_seq(self, d): - seq, expect_outers = d.draw(sequences_strat(seq_klass=self.base_klass)) + seq, expect_outers = d.draw(sequences_strategy(seq_klass=self.base_klass)) self.assertTrue(seq.ready) seq_encoded = seq.encode() seq_decoded, tail = seq.decode(seq_encoded) @@ -4093,7 +5246,6 @@ class SeqMixing(object): )).items()) class Seq(self.base_klass): - __slots__ = () schema = [ (n, Integer(default=d)) for n, (_, d) in _schema @@ -4110,7 +5262,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(), @@ -4123,7 +5275,6 @@ class SeqMixing(object): ))] class SeqWithoutDefault(self.base_klass): - __slots__ = () schema = [ (n, Integer(impl=t)) for (n, _), t in zip(_schema, tags) @@ -4134,16 +5285,23 @@ class SeqMixing(object): seq_encoded = seq_without_default.encode() class SeqWithDefault(self.base_klass): - __slots__ = () schema = [ (n, Integer(default=v, impl=t)) 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) + seq_decoded = seq_decoded.copy() + 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): @@ -4156,7 +5314,6 @@ class SeqMixing(object): names_tags = [(name, tag) for tag, name in sorted(zip(tags, names))] class SeqFull(self.base_klass): - __slots__ = () schema = [(n, Integer(impl=t)) for n, t in names_tags] seq_full = SeqFull() for i, name in enumerate(names): @@ -4165,12 +5322,45 @@ class SeqMixing(object): altered = names_tags[:-2] + names_tags[-1:] class SeqMissing(self.base_klass): - __slots__ = () schema = [(n, Integer(impl=t)) for n, t in altered] seq_missing = SeqMissing() with self.assertRaises(TagMismatch): seq_missing.decode(seq_encoded) + def test_bered(self): + 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) + decoded = decoded.copy() + 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 + with self.assertRaises(DecodeError): + Seq().decode(encoded) + decoded, _ = Seq().decode(encoded, ctx={"bered": True}) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + decoded = decoded.copy() + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + class TestSequence(SeqMixing, CommonMixin, TestCase): base_klass = Sequence @@ -4181,7 +5371,6 @@ class TestSequence(SeqMixing, CommonMixin, TestCase): ) def test_remaining(self, value, junk): class Seq(Sequence): - __slots__ = () schema = ( ("whatever", Integer()), ) @@ -4199,7 +5388,6 @@ class TestSequence(SeqMixing, CommonMixin, TestCase): missing = names.pop() class Seq(Sequence): - __slots__ = () schema = [(n, Boolean()) for n in names] seq = Seq() with self.assertRaises(ObjUnknown) as err: @@ -4209,6 +5397,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 @@ -4222,7 +5420,6 @@ class TestSet(SeqMixing, CommonMixin, TestCase): ] class Seq(Set): - __slots__ = () schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)] seq = Seq() for name, _ in Seq.schema: @@ -4234,9 +5431,42 @@ 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) + seq_decoded = seq_decoded.copy() + self.assertTrue(seq_decoded.ber_encoded) + self.assertTrue(seq_decoded.bered) + self.assertSequenceEqual( + [bytes(seq_decoded[str(i)]) for i, t in enumerate(tags)], + tags, + ) + @composite -def seqof_values_strat(draw, schema=None, do_expl=False): +def seqof_values_strategy(draw, schema=None, do_expl=False): if schema is None: schema = draw(sampled_from((Boolean(), Integer()))) bound_min, bound_max = sorted(draw(sets( @@ -4287,7 +5517,6 @@ class SeqOfMixing(object): def test_invalid_values_type(self): class SeqOf(self.base_klass): - __slots__ = () schema = Integer() with self.assertRaises(InvalidValueType) as err: SeqOf([Integer(123), Boolean(False), Integer(234)]) @@ -4300,21 +5529,21 @@ class SeqOfMixing(object): @given(booleans(), booleans(), binary(), binary()) def test_comparison(self, value1, value2, tag1, tag2): class SeqOf(self.base_klass): - __slots__ = () schema = Boolean() obj1 = SeqOf([Boolean(value1)]) obj2 = SeqOf([Boolean(value2)]) self.assertEqual(obj1 == obj2, value1 == value2) + self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == list(obj2), value1 == value2) self.assertEqual(obj1 == tuple(obj2), value1 == value2) obj1 = SeqOf([Boolean(value1)], impl=tag1) obj2 = SeqOf([Boolean(value1)], impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) + self.assertEqual(obj1 != obj2, tag1 != tag2) @given(lists(booleans())) def test_iter(self, values): class SeqOf(self.base_klass): - __slots__ = () schema = Boolean() obj = SeqOf([Boolean(value) for value in values]) self.assertEqual(len(obj), len(values)) @@ -4334,7 +5563,6 @@ class SeqOfMixing(object): ] class SeqOf(self.base_klass): - __slots__ = () schema = Integer() values = d.draw(permutations(ready + non_ready)) seqof = SeqOf() @@ -4342,7 +5570,8 @@ class SeqOfMixing(object): seqof.append(value) self.assertFalse(seqof.ready) repr(seqof) - pprint(seqof) + list(seqof.pps()) + pprint(seqof, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: seqof.encode() repr(err.exception) @@ -4352,11 +5581,11 @@ class SeqOfMixing(object): seqof[i] = Integer(i) self.assertTrue(seqof.ready) repr(seqof) - pprint(seqof) + list(seqof.pps()) + pprint(seqof, big_blobs=True, with_decode_path=True) def test_spec_mismatch(self): class SeqOf(self.base_klass): - __slots__ = () schema = Integer() seqof = SeqOf() seqof.append(Integer(123)) @@ -4368,7 +5597,6 @@ class SeqOfMixing(object): @given(data_strategy()) def test_bounds_satisfied(self, d): class SeqOf(self.base_klass): - __slots__ = () schema = Boolean() bound_min = d.draw(integers(min_value=0, max_value=1 << 7)) bound_max = d.draw(integers(min_value=bound_min, max_value=1 << 7)) @@ -4378,26 +5606,34 @@ class SeqOfMixing(object): @given(data_strategy()) def test_bounds_unsatisfied(self, d): class SeqOf(self.base_klass): - __slots__ = () 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): class SeqOf(self.base_klass): - __slots__ = () schema = Integer() bounds = (0, bound_max) seqof = SeqOf() @@ -4417,10 +5653,9 @@ class SeqOfMixing(object): default_initial, optional_initial, _decoded_initial, - ) = d.draw(seqof_values_strat()) + ) = d.draw(seqof_values_strategy()) class SeqOf(self.base_klass): - __slots__ = () schema = schema_initial obj_initial = SeqOf( value=value_initial, @@ -4440,7 +5675,7 @@ class SeqOfMixing(object): default, optional, _decoded, - ) = d.draw(seqof_values_strat( + ) = d.draw(seqof_values_strategy( schema=schema_initial, do_expl=impl_initial is None, )) @@ -4493,12 +5728,11 @@ class SeqOfMixing(object): bounds or bounds_initial or (0, float("+inf")), ) - @given(seqof_values_strat()) + @given(seqof_values_strategy()) def test_copy(self, values): _schema, value, bounds, impl, expl, default, optional, _decoded = values class SeqOf(self.base_klass): - __slots__ = () schema = _schema obj = SeqOf( value=value, @@ -4521,7 +5755,6 @@ class SeqOfMixing(object): ) def test_stripped(self, values, tag_impl): class SeqOf(self.base_klass): - __slots__ = () schema = OctetString() obj = SeqOf([OctetString(v) for v in values], impl=tag_impl) with self.assertRaises(NotEnoughData): @@ -4533,7 +5766,6 @@ class SeqOfMixing(object): ) def test_stripped_expl(self, values, tag_expl): class SeqOf(self.base_klass): - __slots__ = () schema = OctetString() obj = SeqOf([OctetString(v) for v in values], expl=tag_expl) with self.assertRaises(NotEnoughData): @@ -4542,10 +5774,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], @@ -4559,10 +5790,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], @@ -4581,16 +5811,16 @@ class SeqOfMixing(object): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( - seqof_values_strat(schema=Integer()), + seqof_values_strategy(schema=Integer()), 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): - __slots__ = () schema = Integer() obj = SeqOf( value=value, @@ -4599,18 +5829,27 @@ class SeqOfMixing(object): _decoded=_decoded, ) repr(obj) - pprint(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) - pprint(obj_expled) + list(obj_expled.pps()) + pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() - obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset) + ctx_copied = deepcopy(ctx_dummy) + obj_decoded, tail = obj_expled.decode( + obj_expled_encoded + tail_junk, + offset=offset, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) repr(obj_decoded) - pprint(obj_decoded) - self.assertEqual(tail, b"") + list(obj_decoded.pps()) + pprint(obj_decoded, big_blobs=True, with_decode_path=True) + 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) @@ -4636,10 +5875,75 @@ 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) + obj_decoded_lenindef = obj_decoded_lenindef.copy() + self.assertTrue(obj_decoded_lenindef.lenindef) + self.assertTrue(obj_decoded_lenindef.bered) + repr(obj_decoded_lenindef) + list(obj_decoded_lenindef.pps()) + pprint(obj_decoded_lenindef, big_blobs=True, with_decode_path=True) + self.assertEqual(tail_lenindef, tail_junk) + 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}) + + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) + + def test_bered(self): + 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 + with self.assertRaises(DecodeError): + SeqOf().decode(encoded) + decoded, _ = SeqOf().decode(encoded, ctx={"bered": True}) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + decoded = decoded.copy() + 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 + with self.assertRaises(DecodeError): + SeqOf().decode(encoded) + decoded, _ = SeqOf().decode(encoded, ctx={"bered": True}) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + decoded = decoded.copy() + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) + class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase): class SeqOf(SequenceOf): - __slots__ = () schema = "whatever" base_klass = SeqOf @@ -4650,7 +5954,6 @@ class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase): class TestSetOf(SeqOfMixing, CommonMixin, TestCase): class SeqOf(SetOf): - __slots__ = () schema = "whatever" base_klass = SeqOf @@ -4666,7 +5969,6 @@ class TestSetOf(SeqOfMixing, CommonMixin, TestCase): values = [OctetString(v) for v in d.draw(lists(binary()))] class Seq(SetOf): - __slots__ = () schema = OctetString() seq = Seq(values) seq_encoded = seq.encode() @@ -4676,6 +5978,41 @@ 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) + seq_decoded = seq_decoded.copy() + 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): @@ -4686,7 +6023,6 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(Integer(-129).encode(), hexdec("0202ff7f")) class Seq(Sequence): - __slots__ = () schema = ( ("erste", Integer()), ("zweite", Integer(optional=True)) @@ -4701,7 +6037,6 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(seq.encode(), hexdec("3006020140020141")) class NestedSeq(Sequence): - __slots__ = () schema = ( ("nest", Seq()), ) @@ -4717,7 +6052,6 @@ class TestGoMarshalVectors(TestCase): ) class Seq(Sequence): - __slots__ = () schema = ( ("erste", Integer(impl=tag_encode(5, klass=TagClassContext))), ) @@ -4726,7 +6060,6 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(seq.encode(), hexdec("3003850140")) class Seq(Sequence): - __slots__ = () schema = ( ("erste", Integer(expl=tag_ctxc(5))), ) @@ -4735,7 +6068,6 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(seq.encode(), hexdec("3005a503020140")) class Seq(Sequence): - __slots__ = () schema = ( ("erste", Null( impl=tag_encode(0, klass=TagClassContext), @@ -4762,7 +6094,6 @@ class TestGoMarshalVectors(TestCase): ) class Seq(Sequence): - __slots__ = () schema = ( ("erste", GeneralizedTime()), ) @@ -4810,7 +6141,6 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(UTF8String("Σ").encode(), hexdec("0c02cea3")) class Seq(Sequence): - __slots__ = () schema = ( ("erste", IA5String()), ) @@ -4819,18 +6149,19 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(seq.encode(), hexdec("3006160474657374")) class Seq(Sequence): - __slots__ = () schema = ( ("erste", PrintableString()), ) seq = Seq() seq["erste"] = PrintableString("test") self.assertSequenceEqual(seq.encode(), hexdec("3006130474657374")) + # Asterisk is actually not allowable + PrintableString._allowable_chars |= set(b"*") seq["erste"] = PrintableString("test*") self.assertSequenceEqual(seq.encode(), hexdec("30071305746573742a")) + PrintableString._allowable_chars -= set(b"*") class Seq(Sequence): - __slots__ = () schema = ( ("erste", Any(optional=True)), ("zweite", Integer()), @@ -4840,18 +6171,15 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(seq.encode(), hexdec("3003020140")) class Seq(SetOf): - __slots__ = () schema = Integer() seq = Seq() seq.append(Integer(10)) self.assertSequenceEqual(seq.encode(), hexdec("310302010a")) class _SeqOf(SequenceOf): - __slots__ = () schema = PrintableString() class SeqOf(SequenceOf): - __slots__ = () schema = _SeqOf() _seqof = _SeqOf() _seqof.append(PrintableString("1")) @@ -4860,7 +6188,6 @@ class TestGoMarshalVectors(TestCase): self.assertSequenceEqual(seqof.encode(), hexdec("30053003130131")) class Seq(Sequence): - __slots__ = () schema = ( ("erste", Integer(default=1)), ) @@ -4884,4 +6211,366 @@ class TestPP(TestCase): chosen_id = oids[chosen] pp = _pp(asn1_type_name=ObjectIdentifier.asn1_type_name, value=chosen) self.assertNotIn(chosen_id, pp_console_row(pp)) - self.assertIn(chosen_id, pp_console_row(pp, oids=oids)) + self.assertIn( + chosen_id, + pp_console_row(pp, oid_maps=[{'whatever': 'whenever'}, oids]), + ) + + +class TestAutoAddSlots(TestCase): + def runTest(self): + class Inher(Integer): + pass + + with self.assertRaises(AttributeError): + inher = Inher() + inher.unexistent = "whatever" + + +class TestOIDDefines(TestCase): + @given(data_strategy()) + def runTest(self, d): + value_names = list(d.draw(sets(text_letters(), min_size=1, max_size=10))) + value_name_chosen = d.draw(sampled_from(value_names)) + oids = [ + ObjectIdentifier(oid) + for oid in d.draw(sets(oid_strategy(), min_size=2, max_size=10)) + ] + oid_chosen = d.draw(sampled_from(oids)) + values = d.draw(lists( + integers(), + min_size=len(value_names), + max_size=len(value_names), + )) + _schema = [ + ("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)))) + + class Seq(Sequence): + schema = _schema + seq = Seq() + for value_name, value in zip(value_names, values): + seq[value_name] = Any(Integer(value).encode()) + seq["type"] = oid_chosen + seq, _ = Seq().decode(seq.encode()) + for value_name in value_names: + if value_name == value_name_chosen: + continue + self.assertIsNone(seq[value_name].defined) + if value_name_chosen in oids[:-1]: + self.assertIsNotNone(seq[value_name_chosen].defined) + self.assertEqual(seq[value_name_chosen].defined[0], oid_chosen) + self.assertIsInstance(seq[value_name_chosen].defined[1], Integer) + repr(seq) + list(seq.pps()) + pprint(seq, big_blobs=True, with_decode_path=True) + + +class TestDefinesByPath(TestCase): + def test_generated(self): + class Seq(Sequence): + schema = ( + ("type", ObjectIdentifier()), + ("value", OctetString(expl=tag_ctxc(123))), + ) + + class SeqInner(Sequence): + schema = ( + ("typeInner", ObjectIdentifier()), + ("valueInner", Any()), + ) + + class PairValue(SetOf): + schema = Any() + + class Pair(Sequence): + schema = ( + ("type", ObjectIdentifier()), + ("value", PairValue()), + ) + + class Pairs(SequenceOf): + schema = Pair() + + ( + type_integered, + type_sequenced, + type_innered, + type_octet_stringed, + ) = [ + ObjectIdentifier(oid) + for oid in sets(oid_strategy(), min_size=4, max_size=4).example() + ] + seq_integered = Seq() + seq_integered["type"] = type_integered + seq_integered["value"] = OctetString(Integer(123).encode()) + seq_integered_raw = seq_integered.encode() + + pairs = Pairs() + pairs_input = ( + (type_octet_stringed, OctetString(b"whatever")), + (type_integered, Integer(123)), + (type_octet_stringed, OctetString(b"whenever")), + (type_integered, Integer(234)), + ) + for t, v in pairs_input: + pair = Pair() + pair["type"] = t + pair["value"] = PairValue((Any(v),)) + pairs.append(pair) + seq_inner = SeqInner() + seq_inner["typeInner"] = type_innered + seq_inner["valueInner"] = Any(pairs) + seq_sequenced = Seq() + seq_sequenced["type"] = type_sequenced + seq_sequenced["value"] = OctetString(seq_inner.encode()) + seq_sequenced_raw = seq_sequenced.encode() + repr(seq_sequenced) + list(seq_sequenced.pps()) + pprint(seq_sequenced, big_blobs=True, with_decode_path=True) + + defines_by_path = [] + ctx_copied = deepcopy(ctx_dummy) + seq_integered, _ = Seq().decode( + seq_integered_raw, + ctx=ctx_copied, + ) + self.assertDictEqual(ctx_copied, ctx_dummy) + self.assertIsNone(seq_integered["value"].defined) + defines_by_path.append( + (("type",), ((("value",), { + type_integered: Integer(), + type_sequenced: SeqInner(), + }),)) + ) + ctx_copied["defines_by_path"] = defines_by_path + seq_integered, _ = Seq().decode( + seq_integered_raw, + ctx=ctx_copied, + ) + del ctx_copied["defines_by_path"] + self.assertDictEqual(ctx_copied, ctx_dummy) + 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())) + repr(seq_integered) + list(seq_integered.pps()) + pprint(seq_integered, big_blobs=True, with_decode_path=True) + + ctx_copied["defines_by_path"] = defines_by_path + seq_sequenced, _ = Seq().decode( + seq_sequenced_raw, + ctx=ctx_copied, + ) + del ctx_copied["defines_by_path"] + self.assertDictEqual(ctx_copied, ctx_dummy) + 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) + repr(seq_sequenced) + list(seq_sequenced.pps()) + pprint(seq_sequenced, big_blobs=True, with_decode_path=True) + + defines_by_path.append(( + ("value", DecodePathDefBy(type_sequenced), "typeInner"), + ((("valueInner",), {type_innered: Pairs()}),), + )) + ctx_copied["defines_by_path"] = defines_by_path + seq_sequenced, _ = Seq().decode( + seq_sequenced_raw, + ctx=ctx_copied, + ) + del ctx_copied["defines_by_path"] + self.assertDictEqual(ctx_copied, ctx_dummy) + self.assertIsNotNone(seq_sequenced["value"].defined) + self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced) + seq_inner = seq_sequenced["value"].defined[1] + self.assertIsNotNone(seq_inner["valueInner"].defined) + self.assertEqual(seq_inner["valueInner"].defined[0], type_innered) + pairs = seq_inner["valueInner"].defined[1] + for pair in pairs: + self.assertIsNone(pair["value"][0].defined) + repr(seq_sequenced) + list(seq_sequenced.pps()) + pprint(seq_sequenced, big_blobs=True, with_decode_path=True) + + defines_by_path.append(( + ( + "value", + DecodePathDefBy(type_sequenced), + "valueInner", + DecodePathDefBy(type_innered), + any, + "type", + ), + ((("value",), { + type_integered: Integer(), + type_octet_stringed: OctetString(), + }),), + )) + ctx_copied["defines_by_path"] = defines_by_path + seq_sequenced, _ = Seq().decode( + seq_sequenced_raw, + ctx=ctx_copied, + ) + del ctx_copied["defines_by_path"] + self.assertDictEqual(ctx_copied, ctx_dummy) + self.assertIsNotNone(seq_sequenced["value"].defined) + self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced) + seq_inner = seq_sequenced["value"].defined[1] + self.assertIsNotNone(seq_inner["valueInner"].defined) + self.assertEqual(seq_inner["valueInner"].defined[0], type_innered) + pairs_got = seq_inner["valueInner"].defined[1] + 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]) + repr(seq_sequenced) + list(seq_sequenced.pps()) + pprint(seq_sequenced, big_blobs=True, with_decode_path=True) + + @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) + ] + 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 = decoded.copy() + 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) + decoded = decoded.copy() + self.assertTrue(decoded.ber_encoded) + self.assertTrue(decoded.bered) + + +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"), + ) + + +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})