X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=b16c6f6cc7fd318c6534599d3887ef1415688dbc;hb=079b95a6c3933a37516187a17a6ef971fcdca17b;hp=309bc2290df05440bccc2b63d65a53547100e852;hpb=2e6117387cfb10eca87e9846498a9a045f05dba3;p=pyderasn.git diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 309bc22..b16c6f6 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -1,5 +1,5 @@ # coding: utf-8 -# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures +# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures # Copyright (C) 2017-2020 Sergey Matveev # # This program is free software: you can redistribute it and/or modify @@ -20,7 +20,10 @@ from copy import deepcopy from datetime import datetime from datetime import timedelta from importlib import import_module +from io import BytesIO +from operator import attrgetter from os import environ +from os import urandom from random import random from string import ascii_letters from string import digits @@ -58,6 +61,7 @@ from six import iterbytes from six import PY2 from six import text_type from six import unichr as six_unichr +from six.moves import xrange as six_xrange from six.moves.cPickle import dumps as pickle_dumps from six.moves.cPickle import HIGHEST_PROTOCOL as pickle_proto from six.moves.cPickle import loads as pickle_loads @@ -72,6 +76,8 @@ from pyderasn import BoundsError from pyderasn import Choice from pyderasn import DecodeError from pyderasn import DecodePathDefBy +from pyderasn import encode2pass +from pyderasn import encode_cer from pyderasn import Enumerated from pyderasn import EOC from pyderasn import EOC_LEN @@ -418,6 +424,8 @@ class TestBoolean(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) repr(err.exception) obj = Boolean(value) self.assertTrue(obj.ready) @@ -502,6 +510,8 @@ class TestBoolean(CommonMixin, TestCase): obj = Boolean(value, impl=tag_impl) with self.assertRaises(NotEnoughData): obj.decode(obj.encode()[:-1]) + with self.assertRaises(NotEnoughData): + obj.decode(encode2pass(obj)[:-1]) @given( booleans(), @@ -511,6 +521,8 @@ class TestBoolean(CommonMixin, TestCase): obj = Boolean(value, expl=tag_expl) with self.assertRaises(NotEnoughData): obj.decode(obj.encode()[:-1]) + with self.assertRaises(NotEnoughData): + obj.decode(encode2pass(obj)[:-1]) @given( integers(min_value=31), @@ -583,8 +595,9 @@ class TestBoolean(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path): for klass in (Boolean, BooleanInherited): _, _, _, default, optional, _decoded = values obj = klass( @@ -598,11 +611,19 @@ class TestBoolean(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled.encode(), + ) obj_expled_hex_encoded = obj_expled.hexencode() ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.hexdecode( @@ -639,6 +660,21 @@ class TestBoolean(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + hexdec(obj_expled_hex_encoded) + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + @given(integers(min_value=2)) def test_invalid_len(self, l): with self.assertRaises(InvalidLength): @@ -656,14 +692,13 @@ class TestBoolean(CommonMixin, TestCase): len_encode(1), int2byte(value), ))) - obj, _ = Boolean().decode( - b"".join(( - Boolean.tag_default, - len_encode(1), - int2byte(value), - )), - ctx={"bered": True}, - ) + encoded = b"".join(( + Boolean.tag_default, + len_encode(1), + int2byte(value), + )) + obj, _ = Boolean().decode(encoded, ctx={"bered": True}) + list(Boolean().decode_evgen(encoded, ctx={"bered": True})) self.assertTrue(bool(obj)) self.assertTrue(obj.ber_encoded) self.assertFalse(obj.lenindef) @@ -725,6 +760,7 @@ class TestBoolean(CommonMixin, TestCase): with self.assertRaises(LenIndefForm): SeqOf().decode(encoded) seqof, tail = SeqOf().decode(encoded, ctx={"bered": True}) + list(SeqOf().decode_evgen(encoded, ctx={"bered": True})) self.assertSequenceEqual(tail, b"") self.assertSequenceEqual([bool(v) for v in seqof], values) self.assertSetEqual( @@ -836,6 +872,8 @@ class TestInteger(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) repr(err.exception) obj = Integer(value) self.assertTrue(obj.ready) @@ -898,6 +936,10 @@ class TestInteger(CommonMixin, TestCase): Integer(values[0]).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + Integer(bounds=(values[1], values[2])).decode( + encode2pass(Integer(values[0])) + ) with self.assertRaises(BoundsError) as err: Integer(value=values[2], bounds=(values[0], values[1])) repr(err.exception) @@ -906,6 +948,10 @@ class TestInteger(CommonMixin, TestCase): Integer(values[2]).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + Integer(bounds=(values[0], values[1])).decode( + encode2pass(Integer(values[2])) + ) @given(data_strategy()) def test_call(self, d): @@ -1081,8 +1127,9 @@ class TestInteger(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path): for klass in (Integer, IntegerInherited): _, _, _, _, default, optional, _, _decoded = values obj = klass( @@ -1096,12 +1143,20 @@ class TestInteger(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled_encoded, + ) ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -1137,6 +1192,21 @@ class TestInteger(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + def test_go_vectors_valid(self): for data, expect in (( (b"\x00", 0), @@ -1312,6 +1382,8 @@ class TestBitString(CommonMixin, TestCase): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) obj = BitString(value) self.assertTrue(obj.ready) repr(obj) @@ -1475,6 +1547,7 @@ class TestBitString(CommonMixin, TestCase): 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)) + decode_path = d.draw(decode_path_strat) for klass in (BitString, BitStringInherited): class BS(klass): schema = _schema @@ -1489,12 +1562,20 @@ class TestBitString(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled_encoded, + ) ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -1534,6 +1615,20 @@ class TestBitString(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + @given(integers(min_value=1, max_value=255)) def test_bad_zero_value(self, pad_size): with self.assertRaises(DecodeError): @@ -1580,6 +1675,7 @@ class TestBitString(CommonMixin, TestCase): self.assertTrue(obj[9]) self.assertFalse(obj[17]) + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( integers(min_value=1, max_value=30), lists( @@ -1596,8 +1692,9 @@ class TestBitString(CommonMixin, TestCase): ), lists(booleans(), min_size=1), binary(), + decode_path_strat, ) - def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk): + def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk, decode_path): def chunk_constructed(contents): return ( tag_encode(form=TagFormConstructed, num=3) + @@ -1606,6 +1703,7 @@ class TestBitString(CommonMixin, TestCase): EOC ) chunks = [] + chunks_len_expected = [] payload_expected = b"" bit_len_expected = 0 for chunk_input in chunk_inputs: @@ -1613,14 +1711,19 @@ class TestBitString(CommonMixin, TestCase): chunks.append(BitString(chunk_input).encode()) payload_expected += chunk_input bit_len_expected += len(chunk_input) * 8 + chunks_len_expected.append(len(chunk_input) + 1) else: chunks.append(chunk_constructed(chunk_input)) payload = b"".join(chunk_input) payload_expected += payload bit_len_expected += len(payload) * 8 + for c in chunk_input: + chunks_len_expected.append(len(c) + 1) + chunks_len_expected.append(len(chunks[-1]) - 1 - 1) chunk_last = BitString("'%s'B" % "".join( "1" if bit else "0" for bit in chunk_last_bits )) + chunks_len_expected.append(BitString().decod(chunk_last.encode()).vlen) payload_expected += bytes(chunk_last) bit_len_expected += chunk_last.bit_len encoded_indefinite = ( @@ -1661,6 +1764,16 @@ class TestBitString(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) + evgens = list(BitString(impl=tag_encode(impl)).decode_evgen( + encoded, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(chunks_len_expected) + 1) + for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens): + self.assertGreater(len(dp), len(decode_path)) + self.assertEqual(obj.vlen, chunk_len_expected) + @given( integers(min_value=0), decode_path_strat, @@ -1796,6 +1909,24 @@ class TestBitString(CommonMixin, TestCase): self.assertTrue(obj.lenindef) self.assertTrue(obj.bered) + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(integers(min_value=1000, max_value=3000)) + def test_cer(self, data_len): + data = urandom(data_len) + encoded = encode_cer(BitString(data)) + ctx = {"bered": True} + self.assertSequenceEqual(bytes(BitString().decod(encoded, ctx=ctx)), data) + evgens = list(BitString().decode_evgen(encoded, ctx=ctx)) + evgens_expected = data_len // 999 + if evgens_expected * 999 != data_len: + evgens_expected += 1 + evgens_expected += 1 + self.assertEqual(len(evgens), evgens_expected) + for (_, obj, _) in evgens[:-2]: + self.assertEqual(obj.vlen, 1000) + _, obj, _ = evgens[-2] + self.assertEqual(obj.vlen, 1 + data_len - len(evgens[:-2]) * 999) + @composite def octet_string_values_strategy(draw, do_expl=False): @@ -1857,6 +1988,8 @@ class TestOctetString(CommonMixin, TestCase): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) obj = OctetString(value) self.assertTrue(obj.ready) repr(obj) @@ -1903,6 +2036,10 @@ class TestOctetString(CommonMixin, TestCase): OctetString(value).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + OctetString(bounds=(bound_min, bound_max)).decode( + encode2pass(OctetString(value)) + ) value = d.draw(binary(min_size=bound_max + 1)) with self.assertRaises(BoundsError) as err: OctetString(value=value, bounds=(bound_min, bound_max)) @@ -1912,6 +2049,10 @@ class TestOctetString(CommonMixin, TestCase): OctetString(value).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + OctetString(bounds=(bound_min, bound_max)).decode( + encode2pass(OctetString(value)) + ) @given(data_strategy()) def test_call(self, d): @@ -2072,8 +2213,9 @@ class TestOctetString(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path): for klass in (OctetString, OctetStringInherited): _, _, _, _, default, optional, _decoded = values obj = klass( @@ -2087,12 +2229,20 @@ class TestOctetString(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled_encoded, + ) ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -2128,6 +2278,21 @@ class TestOctetString(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( integers(min_value=1, max_value=30), lists( @@ -2143,8 +2308,9 @@ class TestOctetString(CommonMixin, TestCase): max_size=3, ), binary(), + decode_path_strat, ) - def test_constructed(self, impl, chunk_inputs, junk): + def test_constructed(self, impl, chunk_inputs, junk, decode_path): def chunk_constructed(contents): return ( tag_encode(form=TagFormConstructed, num=4) + @@ -2153,15 +2319,20 @@ class TestOctetString(CommonMixin, TestCase): EOC ) chunks = [] + chunks_len_expected = [] 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 + chunks_len_expected.append(len(chunk_input)) else: chunks.append(chunk_constructed(chunk_input)) payload = b"".join(chunk_input) payload_expected += payload + for c in chunk_input: + chunks_len_expected.append(len(c)) + chunks_len_expected.append(len(chunks[-1]) - 1 - 1) encoded_indefinite = ( tag_encode(form=TagFormConstructed, num=impl) + LENINDEF + @@ -2197,6 +2368,16 @@ class TestOctetString(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) + evgens = list(OctetString(impl=tag_encode(impl)).decode_evgen( + encoded, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(chunks_len_expected) + 1) + for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens): + self.assertGreater(len(dp), len(decode_path)) + self.assertEqual(obj.vlen, chunk_len_expected) + @given( integers(min_value=0), decode_path_strat, @@ -2252,6 +2433,24 @@ class TestOctetString(CommonMixin, TestCase): self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),)) self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs)) + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(integers(min_value=1001, max_value=3000)) + def test_cer(self, data_len): + data = urandom(data_len) + encoded = encode_cer(OctetString(data)) + ctx = {"bered": True} + self.assertSequenceEqual(bytes(OctetString().decod(encoded, ctx=ctx)), data) + evgens = list(OctetString().decode_evgen(encoded, ctx=ctx)) + evgens_expected = data_len // 1000 + if evgens_expected * 1000 != data_len: + evgens_expected += 1 + evgens_expected += 1 + self.assertEqual(len(evgens), evgens_expected) + for (_, obj, _) in evgens[:-2]: + self.assertEqual(obj.vlen, 1000) + _, obj, _ = evgens[-2] + self.assertEqual(obj.vlen, data_len - len(evgens[:-2]) * 1000) + @composite def null_values_strategy(draw, do_expl=False): @@ -2390,8 +2589,9 @@ class TestNull(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, tag_expl, offset, tail_junk): + def test_symmetric(self, values, tag_expl, offset, tail_junk, decode_path): for klass in (Null, NullInherited): _, _, optional, _decoded = values obj = klass(optional=optional, _decoded=_decoded) @@ -2400,12 +2600,20 @@ class TestNull(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled_encoded, + ) ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -2439,6 +2647,22 @@ class TestNull(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + + @given(integers(min_value=1)) def test_invalid_len(self, l): with self.assertRaises(InvalidLength): @@ -2506,6 +2730,8 @@ class TestObjectIdentifier(CommonMixin, TestCase): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) obj = ObjectIdentifier(value) self.assertTrue(obj.ready) self.assertFalse(obj.ber_encoded) @@ -2727,8 +2953,9 @@ class TestObjectIdentifier(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path): for klass in (ObjectIdentifier, ObjectIdentifierInherited): _, _, _, default, optional, _decoded = values obj = klass( @@ -2742,12 +2969,20 @@ class TestObjectIdentifier(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled_encoded, + ) ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -2783,6 +3018,21 @@ class TestObjectIdentifier(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + @given( oid_strategy().map(ObjectIdentifier), oid_strategy().map(ObjectIdentifier), @@ -3100,6 +3350,7 @@ class TestEnumerated(CommonMixin, TestCase): 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)) + decode_path = d.draw(decode_path_strat) class E(Enumerated): schema = schema_input @@ -3114,6 +3365,7 @@ class TestEnumerated(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) @@ -3155,6 +3407,21 @@ class TestEnumerated(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + @composite def string_values_strategy(draw, alphabet, do_expl=False): @@ -3216,6 +3483,8 @@ class StringMixin(object): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) value = d.draw(text(alphabet=self.text_alphabet())) obj = self.base_klass(value) self.assertTrue(obj.ready) @@ -3265,6 +3534,10 @@ class StringMixin(object): self.base_klass(value).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + self.base_klass(bounds=(bound_min, bound_max)).decode( + encode2pass(self.base_klass(value)) + ) 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)) @@ -3274,6 +3547,10 @@ class StringMixin(object): self.base_klass(value).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + self.base_klass(bounds=(bound_min, bound_max)).decode( + encode2pass(self.base_klass(value)) + ) @given(data_strategy()) def test_call(self, d): @@ -3436,6 +3713,7 @@ class StringMixin(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)) + decode_path = d.draw(decode_path_strat) _, _, _, _, default, optional, _decoded = values obj = self.base_klass( value=value, @@ -3448,6 +3726,7 @@ class StringMixin(object): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) @@ -3491,9 +3770,21 @@ class StringMixin(object): tail_junk, ) - -class TestUTF8String(StringMixin, CommonMixin, TestCase): - base_klass = UTF8String + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + if not getattr(self, "evgen_mode_skip_value", True): + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) cyrillic_letters = text( @@ -3503,6 +3794,26 @@ cyrillic_letters = text( ) +class TestUTF8String(StringMixin, CommonMixin, TestCase): + base_klass = UTF8String + + @given(cyrillic_letters) + def test_byte_per_primitive(self, chars): + char = chars[0] + char_raw = char.encode("utf-8") + encoded = b"".join(( + self.base_klass().tag_constructed, + LENINDEF, + OctetString(char_raw[:1]).encode(), + OctetString(char_raw[1:2]).encode(), + EOC, + )) + self.assertEqual( + self.base_klass().decod(encoded, ctx={"bered": True}), + char, + ) + + class UnicodeDecodeErrorMixin(object): @given(cyrillic_letters) def test_unicode_decode_error(self, cyrillic_text): @@ -3541,6 +3852,19 @@ class TestNumericString(StringMixin, CommonMixin, TestCase): self.assertEqual(err.exception.offset, offset) self.assertEqual(err.exception.decode_path, decode_path) + def test_byte_per_primitive(self): + encoded = b"".join(( + self.base_klass().tag_constructed, + LENINDEF, + OctetString(b"1").encode(), + OctetString(b"2").encode(), + EOC, + )) + self.assertEqual( + self.base_klass().decod(encoded, ctx={"bered": True}), + "12", + ) + class TestPrintableString( UnicodeDecodeErrorMixin, @@ -3759,6 +4083,8 @@ class TimeMixin(object): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) value = d.draw(datetimes( min_value=self.min_datetime, max_value=self.max_datetime, @@ -3917,6 +4243,7 @@ class TimeMixin(object): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) self.additional_symmetric_check(value, obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) @@ -3964,6 +4291,7 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): omit_ms = False min_datetime = datetime(1900, 1, 1) max_datetime = datetime(9999, 12, 31) + evgen_mode_skip_value = False def additional_symmetric_check(self, value, obj_encoded): if value.microsecond > 0: @@ -4348,6 +4676,7 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): omit_ms = True min_datetime = datetime(2000, 1, 1) max_datetime = datetime(2049, 12, 31) + evgen_mode_skip_value = False def additional_symmetric_check(self, value, obj_encoded): pass @@ -4724,6 +5053,8 @@ class TestAny(CommonMixin, TestCase): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) obj = Any(value) self.assertTrue(obj.ready) repr(obj) @@ -4859,8 +5190,9 @@ class TestAny(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path): for klass in (Any, AnyInherited): _, _, optional, _decoded = values obj = klass(value=value, optional=optional, _decoded=_decoded) @@ -4871,6 +5203,7 @@ class TestAny(CommonMixin, TestCase): tag_class, _, tag_num = tag_decode(tag_strip(value)[0]) self.assertEqual(obj.tag_order, (tag_class, tag_num)) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) tag_class, _, tag_num = tag_decode(tag_expl) @@ -4916,6 +5249,20 @@ class TestAny(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + @given( integers(min_value=1).map(tag_ctxc), integers(min_value=0, max_value=3), @@ -5093,6 +5440,8 @@ class TestChoice(CommonMixin, TestCase): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) obj["whatever"] = Boolean() self.assertFalse(obj.ready) repr(obj) @@ -5225,6 +5574,7 @@ class TestChoice(CommonMixin, TestCase): tag_expl = tag_ctxc(d.draw(integers(min_value=1))) offset = d.draw(integers(min_value=0)) tail_junk = d.draw(binary(max_size=5)) + decode_path = d.draw(decode_path_strat) class Wahl(self.base_klass): schema = _schema @@ -5240,6 +5590,7 @@ class TestChoice(CommonMixin, TestCase): self.assertFalse(obj.expled) self.assertEqual(obj.tag_order, obj.value.tag_order) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) tag_class, _, tag_num = tag_decode(tag_expl) @@ -5291,6 +5642,22 @@ class TestChoice(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 2) + _decode_path, obj, tail = evgens[0] + self.assertEqual(_decode_path, decode_path + (obj_decoded.choice,)) + _decode_path, obj, tail = evgens[1] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + @given(integers()) def test_set_get(self, value): class Wahl(Choice): @@ -5571,6 +5938,8 @@ class SeqMixing(object): with self.assertRaises(ObjNotReady) as err: seq.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(seq) for name, value in non_ready.items(): seq[name] = Boolean(value) self.assertTrue(seq.ready) @@ -5750,6 +6119,7 @@ class SeqMixing(object): def test_symmetric(self, d): seq, expects = d.draw(sequence_strategy(seq_klass=self.base_klass)) tail_junk = d.draw(binary(max_size=5)) + decode_path = d.draw(decode_path_strat) self.assertTrue(seq.ready) self.assertFalse(seq.decoded) self._assert_expects(seq, expects) @@ -5758,6 +6128,13 @@ class SeqMixing(object): pprint(seq, big_blobs=True, with_decode_path=True) self.assertTrue(seq.ready) seq_encoded = seq.encode() + self.assertEqual(encode2pass(seq), seq_encoded) + seq_encoded_cer = encode_cer(seq) + self.assertNotEqual(seq_encoded_cer, seq_encoded) + self.assertSequenceEqual( + seq.decod(seq_encoded_cer, ctx={"bered": True}).encode(), + seq_encoded, + ) seq_decoded, tail = seq.decode(seq_encoded + tail_junk) self.assertFalse(seq_decoded.lenindef) self.assertFalse(seq_decoded.ber_encoded) @@ -5813,6 +6190,21 @@ class SeqMixing(object): obj.encode(), ) + evgens = list(seq.decode_evgen( + encoded + decoded_tail, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(list(decoded._values_for_encoding())) + 1) + for _decode_path, obj, _ in evgens[:-1]: + self.assertEqual(_decode_path[:-1], decode_path) + repr(obj) + list(obj.pps()) + _decode_path, obj, tail = evgens[-1] + self.assertEqual(_decode_path, decode_path) + repr(obj) + list(obj.pps()) + assert_exceeding_data( self, lambda: seq.decod(seq_encoded_lenindef + tail_junk, ctx={"bered": True}), @@ -5825,6 +6217,7 @@ class SeqMixing(object): seq, expect_outers = d.draw(sequences_strategy(seq_klass=self.base_klass)) self.assertTrue(seq.ready) seq_encoded = seq.encode() + self.assertEqual(encode2pass(seq), seq_encoded) seq_decoded, tail = seq.decode(seq_encoded) self.assertEqual(tail, b"") self.assertTrue(seq.ready) @@ -5881,25 +6274,34 @@ class SeqMixing(object): min_size=len(_schema), max_size=len(_schema), ))] + class Wahl(Choice): + schema = (("int", Integer()),) class SeqWithoutDefault(self.base_klass): schema = [ - (n, Integer(impl=t)) + (n, Wahl(expl=t)) for (n, _), t in zip(_schema, tags) ] seq_without_default = SeqWithoutDefault() for name, value in _schema: - seq_without_default[name] = Integer(value) + seq_without_default[name] = Wahl(("int", Integer(value))) seq_encoded = seq_without_default.encode() + seq_without_default.decode(seq_encoded) + self.assertEqual( + len(list(seq_without_default.decode_evgen(seq_encoded))), + len(_schema) * 2 + 1, + ) class SeqWithDefault(self.base_klass): schema = [ - (n, Integer(default=v, impl=t)) + (n, Wahl(default=Wahl(("int", Integer(v))), expl=t)) for (n, v), t in zip(_schema, tags) ] seq_with_default = SeqWithDefault() with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): seq_with_default.decode(seq_encoded) + with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): + list(seq_with_default.decode_evgen(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) @@ -5909,7 +6311,21 @@ class SeqMixing(object): 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) + self.assertEqual(seq_decoded[name].value, value) + self.assertEqual( + len(list(seq_with_default.decode_evgen(seq_encoded, ctx=ctx))), + len(_schema) + 1, + ) + + seq_without_default = SeqWithoutDefault() + for name, value in _schema: + seq_without_default[name] = Wahl(("int", Integer(value + 1))) + seq_encoded = seq_without_default.encode() + seq_with_default.decode(seq_encoded) + self.assertEqual( + len(list(seq_with_default.decode_evgen(seq_encoded))), + len(_schema) + 1, + ) @given(data_strategy()) def test_missing_from_spec(self, d): @@ -5934,6 +6350,8 @@ class SeqMixing(object): seq_missing = SeqMissing() with self.assertRaises(TagMismatch): seq_missing.decode(seq_encoded) + with self.assertRaises(TagMismatch): + list(seq_missing.decode_evgen(seq_encoded)) def test_bered(self): class Seq(self.base_klass): @@ -5960,6 +6378,9 @@ class SeqMixing(object): encoded = Seq.tag_default + len_encode(len(encoded)) + encoded with self.assertRaises(DecodeError): Seq().decode(encoded) + with self.assertRaises(DecodeError): + list(Seq().decode_evgen(encoded)) + list(Seq().decode_evgen(encoded, ctx={"bered": True})) decoded, _ = Seq().decode(encoded, ctx={"bered": True}) self.assertFalse(decoded.ber_encoded) self.assertFalse(decoded.lenindef) @@ -6197,6 +6618,8 @@ class SeqOfMixing(object): with self.assertRaises(ObjNotReady) as err: seqof.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(seqof) for i, value in enumerate(values): self.assertEqual(seqof[i], value) if not seqof[i].ready: @@ -6240,6 +6663,10 @@ class SeqOfMixing(object): SeqOf(value).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + SeqOf(bounds=(bound_min, bound_max)).decode( + encode2pass(SeqOf(value)) + ) value = [Boolean(True)] * d.draw(integers( min_value=bound_max + 1, max_value=bound_max + 10, @@ -6252,6 +6679,10 @@ class SeqOfMixing(object): SeqOf(value).encode() ) repr(err.exception) + with assertRaisesRegex(self, DecodeError, "bounds") as err: + SeqOf(bounds=(bound_min, bound_max)).decode( + encode2pass(SeqOf(value)) + ) @given(integers(min_value=1, max_value=10)) def test_out_of_bounds(self, bound_max): @@ -6440,8 +6871,9 @@ class SeqOfMixing(object): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path): _, _, _, _, _, default, optional, _decoded = values class SeqOf(self.base_klass): @@ -6457,6 +6889,13 @@ class SeqOfMixing(object): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + obj_encoded_cer = encode_cer(obj) + self.assertNotEqual(obj_encoded_cer, obj_encoded) + self.assertSequenceEqual( + obj.decod(obj_encoded_cer, ctx={"bered": True}).encode(), + obj_encoded, + ) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) @@ -6523,6 +6962,21 @@ class SeqOfMixing(object): with self.assertRaises(DecodeError): obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True}) + evgens = list(obj.decode_evgen( + obj_encoded_lenindef + tail_junk, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(obj_decoded_lenindef) + 1) + for i, (_decode_path, obj, _) in enumerate(evgens[:-1]): + self.assertEqual(_decode_path, decode_path + (str(i),)) + repr(obj) + list(obj.pps()) + _decode_path, obj, tail = evgens[-1] + self.assertEqual(_decode_path, decode_path) + repr(obj) + list(obj.pps()) + assert_exceeding_data( self, lambda: obj_expled.decod(obj_expled_encoded + tail_junk), @@ -6577,6 +7031,77 @@ class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase): self.assertEqual(obj1, obj2) self.assertSequenceEqual(list(obj1), list(obj2)) + def test_iterator_pickling(self): + class SeqOf(SequenceOf): + schema = Integer() + register_class(SeqOf) + seqof = SeqOf() + pickle_dumps(seqof) + seqof = seqof(iter(six_xrange(10))) + with assertRaisesRegex(self, ValueError, "iterator"): + pickle_dumps(seqof) + + def test_iterator_bounds(self): + class SeqOf(SequenceOf): + schema = Integer() + bounds = (10, 20) + seqof = None + def gen(n): + for i in six_xrange(n): + yield Integer(i) + for n in (9, 21): + seqof = SeqOf(gen(n)) + self.assertTrue(seqof.ready) + with self.assertRaises(BoundsError): + seqof.encode() + self.assertFalse(seqof.ready) + seqof = seqof(gen(n)) + self.assertTrue(seqof.ready) + with self.assertRaises(BoundsError): + encode_cer(seqof) + self.assertFalse(seqof.ready) + + def test_iterator_twice(self): + class SeqOf(SequenceOf): + schema = Integer() + bounds = (1, float("+inf")) + def gen(): + for i in six_xrange(10): + yield Integer(i) + seqof = SeqOf(gen()) + self.assertTrue(seqof.ready) + seqof.encode() + self.assertFalse(seqof.ready) + register_class(SeqOf) + pickle_dumps(seqof) + + def test_iterator_2pass(self): + class SeqOf(SequenceOf): + schema = Integer() + bounds = (1, float("+inf")) + def gen(): + for i in six_xrange(10): + yield Integer(i) + seqof = SeqOf(gen()) + self.assertTrue(seqof.ready) + _, state = seqof.encode1st() + self.assertFalse(seqof.ready) + seqof = seqof(gen()) + self.assertTrue(seqof.ready) + buf = BytesIO() + seqof.encode2nd(buf.write, iter(state)) + self.assertSequenceEqual( + [int(i) for i in seqof.decod(buf.getvalue())], + list(gen()), + ) + + def test_non_ready_bound_min(self): + class SeqOf(SequenceOf): + schema = Integer() + bounds = (1, float("+inf")) + seqof = SeqOf() + self.assertFalse(seqof.ready) + class TestSetOf(SeqOfMixing, CommonMixin, TestCase): class SeqOf(SetOf): @@ -7191,11 +7716,13 @@ class TestStrictDefaultExistence(TestCase): class TestX690PrefixedType(TestCase): - def runTest(self): + def test_1(self): self.assertSequenceEqual( VisibleString("Jones").encode(), hexdec("1A054A6F6E6573"), ) + + def test_2(self): self.assertSequenceEqual( VisibleString( "Jones", @@ -7203,6 +7730,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("43054A6F6E6573"), ) + + def test_3(self): self.assertSequenceEqual( Any( VisibleString( @@ -7213,6 +7742,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("A20743054A6F6E6573"), ) + + def test_4(self): self.assertSequenceEqual( OctetString( VisibleString( @@ -7223,6 +7754,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("670743054A6F6E6573"), ) + + def test_5(self): self.assertSequenceEqual( VisibleString("Jones", impl=tag_ctxp(2)).encode(), hexdec("82054A6F6E6573"), @@ -7249,3 +7782,49 @@ class TestPickleDifferentVersion(TestCase): pickle_loads(pickled) pyderasn.__version__ = version_orig pickle_loads(pickled) + + +class TestCERSetOrdering(TestCase): + def test_vectors(self): + """Taken from X.690-201508 + """ + class B(Choice): + schema = ( + ("c", Integer(impl=tag_ctxp(2))), + ("d", Integer(impl=tag_ctxp(4))), + ) + + class F(Choice): + schema = ( + ("g", Integer(impl=tag_ctxp(5))), + ("h", Integer(impl=tag_ctxp(6))), + ) + + class I(Choice): + schema = ( + ("j", Integer(impl=tag_ctxp(0))), + ) + + class E(Choice): + schema = ( + ("f", F()), + ("i", I()), + ) + + class A(Set): + schema = ( + ("a", Integer(impl=tag_ctxp(3))), + ("b", B(expl=tag_ctxc(1))), + ("e", E()), + ) + + a = A(( + ("a", Integer(123)), + ("b", B(("d", Integer(234)))), + ("e", E(("f", F(("g", Integer(345)))))), + )) + order = sorted(a._values_for_encoding(), key=attrgetter("tag_order_cer")) + self.assertSequenceEqual( + [i.__class__.__name__ for i in order], + ("E", "B", "Integer"), + )