X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=0fa0485ec9680cfe1261bb24dcafe6375d7bd3af;hb=e1676457cb8e988795e8347fc27b08a60f80f469;hp=1a7ed2c488f713e9af4b79a174816546547fc957;hpb=b3160726b5f1aaf721569018291e9bc6b25a9ab7;p=pyderasn.git diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 1a7ed2c..0fa0485 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -18,11 +18,17 @@ from copy import copy from copy import deepcopy from datetime import datetime +from datetime import timedelta from importlib import import_module +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 from string import printable from string import whitespace +from time import mktime from time import time from unittest import TestCase @@ -54,6 +60,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 @@ -68,6 +75,7 @@ from pyderasn import BoundsError from pyderasn import Choice from pyderasn import DecodeError from pyderasn import DecodePathDefBy +from pyderasn import encode_cer from pyderasn import Enumerated from pyderasn import EOC from pyderasn import EOC_LEN @@ -123,8 +131,10 @@ from pyderasn import VideotexString from pyderasn import VisibleString +max_examples = environ.get("MAX_EXAMPLES") settings.register_profile("local", settings( deadline=5000, + **({"max_examples": int(max_examples)} if max_examples else {}) )) settings.load_profile("local") LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4 @@ -155,9 +165,11 @@ def register_class(klass): def assert_exceeding_data(self, call, junk): - if len(junk) > 0: - with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)): - call() + if len(junk) <= 0: + return + with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)) as err: + call() + repr(err) class TestHex(TestCase): @@ -337,14 +349,20 @@ class CommonMixin(object): obj = Inherited() self.assertSequenceEqual(obj.impl, impl_tag) self.assertFalse(obj.expled) + if obj.ready: + tag_class, _, tag_num = tag_decode(impl_tag) + self.assertEqual(obj.tag_order, (tag_class, tag_num)) - @given(binary()) + @given(binary(min_size=1)) def test_expl_inherited(self, expl_tag): class Inherited(self.base_klass): expl = expl_tag obj = Inherited() self.assertSequenceEqual(obj.expl, expl_tag) self.assertTrue(obj.expled) + if obj.ready: + tag_class, _, tag_num = tag_decode(expl_tag) + self.assertEqual(obj.tag_order, (tag_class, tag_num)) def assert_copied_basic_fields(self, obj, obj_copied): self.assertEqual(obj, obj_copied) @@ -355,6 +373,8 @@ class CommonMixin(object): self.assertEqual(obj.offset, obj_copied.offset) self.assertEqual(obj.llen, obj_copied.llen) self.assertEqual(obj.vlen, obj_copied.vlen) + if obj.ready: + self.assertEqual(obj.tag_order, obj_copied.tag_order) @composite @@ -409,7 +429,7 @@ class TestBoolean(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) - @given(booleans(), booleans(), binary(), binary()) + @given(booleans(), booleans(), binary(min_size=1), binary(min_size=1)) def test_comparison(self, value1, value2, tag1, tag2): for klass in (Boolean, BooleanInherited): obj1 = klass(value1) @@ -567,8 +587,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( @@ -582,11 +603,18 @@ class TestBoolean(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + 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( @@ -623,6 +651,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): @@ -640,14 +683,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) @@ -709,6 +751,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( @@ -828,7 +871,7 @@ class TestInteger(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) hash(obj) - @given(integers(), integers(), binary(), binary()) + @given(integers(), integers(), binary(min_size=1), binary(min_size=1)) def test_comparison(self, value1, value2, tag1, tag2): for klass in (Integer, IntegerInherited): obj1 = klass(value1) @@ -1065,8 +1108,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( @@ -1080,12 +1124,19 @@ class TestInteger(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + 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, @@ -1121,6 +1172,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), @@ -1459,6 +1525,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 @@ -1473,12 +1540,19 @@ class TestBitString(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + 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, @@ -1518,6 +1592,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): @@ -1564,6 +1652,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( @@ -1580,8 +1669,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) + @@ -1590,6 +1680,7 @@ class TestBitString(CommonMixin, TestCase): EOC ) chunks = [] + chunks_len_expected = [] payload_expected = b"" bit_len_expected = 0 for chunk_input in chunk_inputs: @@ -1597,14 +1688,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 = ( @@ -1641,6 +1737,19 @@ class TestBitString(CommonMixin, TestCase): self.assertEqual(obj.lenindef, lenindef_expected) self.assertTrue(obj.bered) self.assertEqual(len(encoded), obj.tlvlen) + repr(obj) + 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), @@ -1777,6 +1886,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): @@ -2053,8 +2180,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( @@ -2068,12 +2196,19 @@ class TestOctetString(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + 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, @@ -2109,6 +2244,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( @@ -2124,8 +2274,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) + @@ -2134,15 +2285,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 + @@ -2174,6 +2330,19 @@ class TestOctetString(CommonMixin, TestCase): self.assertEqual(obj.lenindef, lenindef_expected) self.assertTrue(obj.bered) self.assertEqual(len(encoded), obj.tlvlen) + repr(obj) + 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), @@ -2230,6 +2399,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): @@ -2262,7 +2449,7 @@ class TestNull(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) - @given(binary(), binary()) + @given(binary(min_size=1), binary(min_size=1)) def test_comparison(self, tag1, tag2): for klass in (Null, NullInherited): obj1 = klass(impl=tag1) @@ -2368,8 +2555,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) @@ -2378,12 +2566,19 @@ class TestNull(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + 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, @@ -2417,6 +2612,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): @@ -2492,7 +2703,7 @@ class TestObjectIdentifier(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) hash(obj) - @given(oid_strategy(), oid_strategy(), binary(), binary()) + @given(oid_strategy(), oid_strategy(), binary(min_size=1), binary(min_size=1)) def test_comparison(self, value1, value2, tag1, tag2): for klass in (ObjectIdentifier, ObjectIdentifierInherited): obj1 = klass(value1) @@ -2705,8 +2916,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( @@ -2720,12 +2932,19 @@ class TestObjectIdentifier(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + 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, @@ -2761,6 +2980,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), @@ -2819,6 +3053,28 @@ class TestObjectIdentifier(CommonMixin, TestCase): with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"): ObjectIdentifier().decode(tampered) + @given(data_strategy()) + def test_negative_arcs(self, d): + oid = list(d.draw(oid_strategy())) + if len(oid) == 2: + return + idx = d.draw(integers(min_value=3, max_value=len(oid))) + oid[idx - 1] *= -1 + if oid[idx - 1] == 0: + oid[idx - 1] = -1 + with self.assertRaises(InvalidOID): + ObjectIdentifier(tuple(oid)) + with self.assertRaises(InvalidOID): + ObjectIdentifier(".".join(str(i) for i in oid)) + + @given(data_strategy()) + def test_plused_arcs(self, d): + oid = [str(arc) for arc in d.draw(oid_strategy())] + idx = d.draw(integers(min_value=0, max_value=len(oid))) + oid[idx - 1] = "+" + oid[idx - 1] + with self.assertRaises(InvalidOID): + ObjectIdentifier(".".join(str(i) for i in oid)) + @given(data_strategy()) def test_nonnormalized_arcs(self, d): arcs = d.draw(lists( @@ -2940,7 +3196,7 @@ class TestEnumerated(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) - @given(integers(), integers(), binary(), binary()) + @given(integers(), integers(), binary(min_size=1), binary(min_size=1)) def test_comparison(self, value1, value2, tag1, tag2): class E(Enumerated): schema = ( @@ -3056,6 +3312,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 @@ -3111,6 +3368,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): @@ -3392,6 +3664,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, @@ -3447,6 +3720,22 @@ class StringMixin(object): 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) + 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()) + class TestUTF8String(StringMixin, CommonMixin, TestCase): base_klass = UTF8String @@ -3715,7 +4004,10 @@ class TimeMixin(object): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) - value = d.draw(datetimes(min_value=self.min_datetime)) + value = d.draw(datetimes( + min_value=self.min_datetime, + max_value=self.max_datetime, + )) obj = self.base_klass(value) self.assertTrue(obj.ready) repr(obj) @@ -3917,11 +4209,16 @@ 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: self.assertFalse(obj_encoded.endswith(b"0Z")) + def test_repr_not_ready(self): + unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime()) + repr(GeneralizedTime()) + def test_x690_vector_valid(self): for data in (( b"19920521000000Z", @@ -3974,6 +4271,239 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): datetime(2010, 1, 2, 3, 4, 5, 0), ) + def test_go_vectors_valid_ber(self): + for data in (( + b"20100102030405+0607", + b"20100102030405-0607", + )): + GeneralizedTime(data, ctx={"bered": True}) + + def test_utc_offsets(self): + """Some know equal UTC offsets + """ + dts = [ + GeneralizedTime(data.encode("ascii"), ctx={"bered": True}) + for data in ( + "200101011830Z", + "200101012230+04", + "200101011130-0700", + "200101011500-03:30", + ) + ] + self.assertEqual(dts[0], dts[1]) + self.assertEqual(dts[0], dts[2]) + self.assertEqual(dts[0], dts[3]) + + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(data_strategy()) + def test_valid_ber(self, d): + min_year = 1901 if PY2 else 2 + year = d.draw(integers(min_value=min_year, max_value=9999)) + month = d.draw(integers(min_value=1, max_value=12)) + day = d.draw(integers(min_value=1, max_value=28)) + hours = d.draw(integers(min_value=0, max_value=23)) + data = "%04d%02d%02d%02d" % (year, month, day, hours) + dt = datetime(year, month, day, hours) + fractions_sign = d.draw(sampled_from(" ,.")) + fractions = None + if fractions_sign != " ": + fractions = random() + if d.draw(booleans()): + minutes = d.draw(integers(min_value=0, max_value=59)) + data += "%02d" % minutes + dt += timedelta(seconds=60 * minutes) + if d.draw(booleans()): + seconds = d.draw(integers(min_value=0, max_value=59)) + data += "%02d" % seconds + dt += timedelta(seconds=seconds) + if fractions is not None: + dt += timedelta(microseconds=10**6 * fractions) + elif fractions is not None: + dt += timedelta(seconds=60 * fractions) + elif fractions is not None: + dt += timedelta(seconds=3600 * fractions) + if fractions is not None: + data += fractions_sign + str(fractions)[2:] + if d.draw(booleans()): + data += "Z" + elif d.draw(booleans()): + offset_hour = d.draw(integers(min_value=0, max_value=13)) + sign = 1 + if d.draw(booleans()): + data += "-" + sign = -1 + else: + data += "+" + dt -= timedelta(seconds=sign * 3600 * offset_hour) + data += "%02d" % offset_hour + minutes_separator = d.draw(sampled_from((None, "", ":"))) + if minutes_separator is not None: + offset_minute = d.draw(integers(min_value=0, max_value=59)) + dt -= timedelta(seconds=sign * 60 * offset_minute) + data += "%s%02d" % (minutes_separator, offset_minute) + data = data.encode("ascii") + data_der = GeneralizedTime.tag_default + len_encode(len(data)) + data + try: + GeneralizedTime().decod(data_der) + except DecodeError: + dered = False + else: + dered = True + obj = GeneralizedTime().decod(data_der, ctx={"bered": True}) + if dt.year > 1970: + self.assertEqual( + mktime(obj.todatetime().timetuple()), + mktime(dt.timetuple()), + ) + elif not PY2: + self.assertEqual(obj.todatetime().timestamp(), dt.timestamp()) + self.assertEqual(obj.ber_encoded, not dered) + self.assertEqual(obj.bered, not dered) + self.assertEqual(obj.ber_raw, None if dered else data) + self.assertEqual(obj.encode() == data_der, dered) + repr(obj) + bytes(obj) + str(obj) + + def test_invalid_ber(self): + for data in (( + # "00010203040506.07", + "-0010203040506.07", + "0001-203040506.07", + "000102-3040506.07", + "00010203-40506.07", + "0001020304-506.07", + "000102030405-6.07", + "00010203040506.-7", + "+0010203040506.07", + "0001+203040506.07", + "000102+3040506.07", + "00010203+40506.07", + "0001020304+506.07", + "000102030405+6.07", + "00010203040506.+7", + " 0010203040506.07", + "0001 203040506.07", + "000102 3040506.07", + "00010203 40506.07", + "0001020304 506.07", + "000102030405 6.07", + "00010203040506. 7", + "001 0203040506.07", + "00012 03040506.07", + "0001023 040506.07", + "000102034 0506.07", + "00010203045 06.07", + "0001020304056 .07", + "00010203040506.7 ", + "00010203040506.", + "0001020304050607", + + "-0010203040506", + "0001-203040506", + "000102-3040506", + "00010203-40506", + "0001020304-506", + "000102030405-6", + "0001+203040506", + "000102+3040506", + "00010203+40506", + "0001020304+506", + "000102030405+6", + " 0010203040506", + "0001 203040506", + "000102 3040506", + "00010203 40506", + "0001020304 506", + "000102030405 6", + "001 0203040506", + "00012 03040506", + "0001023 040506", + "000102034 0506", + "00010203045 06", + "0001020304056 ", + + "-00102030405.07", + "0001-2030405.07", + "000102-30405.07", + "00010203-405.07", + "0001020304-5.07", + "000102030405.-7", + "+00102030405.07", + "0001+2030405.07", + "00010203+405.07", + "0001020304+5.07", + "000102030405.+7", + " 00102030405.07", + "0001 2030405.07", + "000102 30405.07", + "00010203 405.07", + "0001020304 5.07", + "000102030405. 7", + "001 02030405.07", + "00012 030405.07", + "0001023 0405.07", + "000102034 05.07", + "00010203045 .07", + "000102030405.7 ", + "000102030405.", + + "-001020304.07", + "0001-20304.07", + "000102-304.07", + "00010203-4.07", + "0001020304.-7", + "+001020304.07", + "0001+20304.07", + "00010203+4.07", + "0001020304.+7", + " 001020304.07", + "0001 20304.07", + "000102 304.07", + "00010203 4.07", + "0001020304. 7", + "001 020304.07", + "00012 0304.07", + "0001023 04.07", + "000102034 .07", + "0001020304.7 ", + "0001020304.", + + "00010203", + "00010203040506Y", + "0001010100+0001", + "0001010100+00:01", + "0001010100+01", + + "00010203040506.07+15", + "00010203040506.07-15", + "00010203040506.07+14:60", + "00010203040506.07+1460", + "00010203040506.07-1460", + "00010203040506.07+00:60", + "00010203040506.07-00:60", + + "00010203040506+15", + "00010203040506-15", + "00010203040506+14:60", + "00010203040506+1460", + "00010203040506-1460", + "00010203040506+00:60", + "00010203040506-00:60", + + "0001020304050.07", + "00010203040.07", + "000102030.07", + "0001020304050", + "00010203040", + "000102030", + )): + with self.assertRaises(DecodeError): + GeneralizedTime(data.encode("ascii"), ctx={"bered": True}) + data = data.replace(".", ",") + with self.assertRaises(DecodeError): + GeneralizedTime(data.encode("ascii"), ctx={"bered": True}) + @given( binary( min_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2, @@ -4021,16 +4551,58 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): with assertRaisesRegex(self, DecodeError, "only microsecond fractions"): GeneralizedTime(b"20010101000000.0000001Z") + def test_non_pure_integers(self): + for data in (( + # b"20000102030405Z, + b"+2000102030405Z", + b"2000+102030405Z", + b"200001+2030405Z", + b"20000102+30405Z", + b"2000010203+405Z", + b"200001020304+5Z", + b"20000102030405.+6Z", + b"20000102030405.-6Z", + b"_2000102030405Z", + b"2000_102030405Z", + b"200001_2030405Z", + b"20000102_30405Z", + b"2000010203_405Z", + b"200001020304_5Z", + b"20000102030405._6Z", + b"20000102030405.6_Z", + b" 2000102030405Z", + b"2000 102030405Z", + b"200001 2030405Z", + b"20000102 30405Z", + b"2000010203 405Z", + b"200001020304 5Z", + b"20000102030405. 6Z", + b"200 0102030405Z", + b"20001 02030405Z", + b"2000012 030405Z", + b"200001023 0405Z", + b"20000102034 05Z", + b"2000010203045 Z", + b"20000102030405.6 Z", + )): + with self.assertRaises(DecodeError): + GeneralizedTime(data) + class TestUTCTime(TimeMixin, CommonMixin, TestCase): base_klass = UTCTime 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 + def test_repr_not_ready(self): + unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime()) + repr(UTCTime()) + def test_x690_vector_valid(self): for data in (( b"920521000000Z", @@ -4091,6 +4663,229 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): datetime(1991, 5, 6, 23, 45, 40, 0), ) + def test_non_pure_integers(self): + for data in (( + # b"000102030405Z", + b"+10102030405Z", + b"00+102030405Z", + b"0001+2030405Z", + b"000102+30405Z", + b"00010203+405Z", + b"0001020304+5Z", + b"_10102030405Z", + b"00_102030405Z", + b"0001_2030405Z", + b"000102_30405Z", + b"00010203_405Z", + b"0001020304_5Z", + b"00010203045_Z", + b" 10102030405Z", + b"00 102030405Z", + b"0001 2030405Z", + b"000102 30405Z", + b"00010203 405Z", + b"0001020304 5Z", + b"1 0102030405Z", + b"001 02030405Z", + b"00012 030405Z", + b"0001023 0405Z", + b"000102034 05Z", + b"00010203045 Z", + )): + with self.assertRaises(DecodeError): + UTCTime(data) + + def test_x680_vector_valid_ber(self): + for data, dt in (( + (b"8201021200Z", datetime(1982, 1, 2, 12)), + (b"8201020700-0500", datetime(1982, 1, 2, 12)), + (b"0101021200Z", datetime(2001, 1, 2, 12)), + (b"0101020700-0500", datetime(2001, 1, 2, 12)), + )): + data_der = UTCTime.tag_default + len_encode(len(data)) + data + obj = UTCTime().decod(data_der, ctx={"bered": True}) + self.assertEqual(obj, dt) + self.assertEqual(obj.todatetime(), dt) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + self.assertEqual(obj.ber_raw, data) + self.assertNotEqual(obj.encode(), data_der) + repr(obj) + + def test_go_vectors_valid_ber(self): + for data in (( + b"910506164540-0700", + b"910506164540+0730", + b"9105062345Z", + b"5105062345Z", + )): + data = UTCTime.tag_default + len_encode(len(data)) + data + obj = UTCTime().decod(data, ctx={"bered": True}) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + self.assertNotEqual(obj.encode(), data) + repr(obj) + + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(data_strategy()) + def test_valid_ber(self, d): + year = d.draw(integers(min_value=0, max_value=99)) + month = d.draw(integers(min_value=1, max_value=12)) + day = d.draw(integers(min_value=1, max_value=28)) + hours = d.draw(integers(min_value=0, max_value=23)) + minute = d.draw(integers(min_value=0, max_value=59)) + data = "%02d%02d%02d%02d%02d" % (year, month, day, hours, minute) + dt = datetime( + year + (2000 if year < 50 else 1900), + month, + day, + hours, + minute, + ) + dered = False + if d.draw(booleans()): + dered = True + seconds = d.draw(integers(min_value=0, max_value=59)) + data += "%02d" % seconds + dt += timedelta(seconds=seconds) + if d.draw(booleans()): + data += "Z" + else: + dered = False + offset_hour = d.draw(integers(min_value=0, max_value=13)) + offset_minute = d.draw(integers(min_value=0, max_value=59)) + offset = timedelta(seconds=offset_hour * 3600 + offset_minute * 60) + if d.draw(booleans()): + dt += offset + data += "-" + else: + dt -= offset + data += "+" + data += "%02d%02d" % (offset_hour, offset_minute) + data = data.encode("ascii") + data_der = UTCTime.tag_default + len_encode(len(data)) + data + obj = UTCTime().decod(data_der, ctx={"bered": True}) + self.assertEqual(obj, dt) + self.assertEqual(obj.todatetime(), dt) + self.assertEqual(obj.ber_encoded, not dered) + self.assertEqual(obj.bered, not dered) + self.assertEqual(obj.ber_raw, None if dered else data) + self.assertEqual(obj.encode() == data_der, dered) + repr(obj) + bytes(obj) + str(obj) + + def test_invalid_ber(self): + for data in (( + # b"0001020304Z", + b"-101020304Z", + b"00-1020304Z", + b"0001-20304Z", + b"000102-304Z", + b"000102-104Z", + b"00000203-4Z", + b"+101020304Z", + b"00+1020304Z", + b"0001+20304Z", + b"000102+304Z", + b"000102+104Z", + b"00000203+4Z", + b" 101020304Z", + b"00 1020304Z", + b"0001 20304Z", + b"000102 304Z", + b"000102 104Z", + b"00000203 4Z", + b"1 01020304Z", + b"001 020304Z", + b"00012 0304Z", + b"0001023 04Z", + b"0001021 04Z", + b"000002034 Z", + b"0013020304Z", + b"0001000304Z", + b"0001320304Z", + b"0001022404Z", + b"0001020360Z", + b"0002300304Z", + b"0001020304", + b"0001020304T", + b"0001020304+", + b"0001020304-", + b"0001020304+0", + b"0001020304+00", + b"0001020304+000", + b"0001020304+000Z", + b"0001020304+0000Z", + b"0001020304+-101", + b"0001020304+01-1", + b"0001020304+0060", + b"0001020304+1401", + b"5001010000+0001", + b"000102030Z", + b"0001020Z", + )): + with self.assertRaises(DecodeError): + UTCTime(data, ctx={"bered": True}) + data = data[:8] + data[8+2:] + with self.assertRaises(DecodeError): + UTCTime(data, ctx={"bered": True}) + + for data in (( + # b"000102030405Z", + b"-10102030405Z", + b"00-102030405Z", + b"0001-2030405Z", + b"000102-30405Z", + b"000102-10405Z", + b"00000203-405Z", + b"0000020304-5Z", + b"+10102030405Z", + b"00+102030405Z", + b"0001+2030405Z", + b"000102+30405Z", + b"000102+10405Z", + b"00000203+405Z", + b"0000020304+5Z", + b" 10102030405Z", + b"00 102030405Z", + b"0001 2030405Z", + b"000102 30405Z", + b"000102 10405Z", + b"00000203 405Z", + b"0000020304 5Z", + b"1 0102030405Z", + b"001 02030405Z", + b"00012 030405Z", + b"0001023 0405Z", + b"0001021 0405Z", + b"000002034 05Z", + b"00000203045 Z", + b"001302030405Z", + b"000100030405Z", + b"000132030405Z", + b"000102240405Z", + b"000102036005Z", + b"000230030405Z", + b"000102030460Z", + b"000102030405", + b"000102030405T", + b"000102030405+", + b"000102030405-", + b"000102030405+0", + b"000102030405+00", + b"000102030405+000", + b"000102030405+000Z", + b"000102030405+0000Z", + b"000102030405+-101", + b"000102030405+01-1", + b"000102030405+0060", + b"000102030405+1401", + b"500101000002+0003", + )): + with self.assertRaises(DecodeError): + UTCTime(data, ctx={"bered": True}) + @given(integers(min_value=0, max_value=49)) def test_pre50(self, year): self.assertEqual( @@ -4127,9 +4922,16 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): ) +@composite +def tlv_value_strategy(draw): + tag_num = draw(integers(min_value=1)) + data = draw(binary()) + return b"".join((tag_encode(tag_num), len_encode(len(data)), data)) + + @composite def any_values_strategy(draw, do_expl=False): - value = draw(one_of(none(), binary())) + value = draw(one_of(none(), tlv_value_strategy())) expl = None if do_expl: expl = draw(one_of(none(), integers(min_value=1).map(tag_encode))) @@ -4159,7 +4961,7 @@ class TestAny(CommonMixin, TestCase): obj = Any(optional=optional) self.assertEqual(obj.optional, optional) - @given(binary()) + @given(tlv_value_strategy()) def test_ready(self, value): obj = Any() self.assertFalse(obj.ready) @@ -4193,7 +4995,7 @@ class TestAny(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertSequenceEqual(obj.encode(), integer_encoded) - @given(binary(), binary()) + @given(tlv_value_strategy(), tlv_value_strategy()) def test_comparison(self, value1, value2): for klass in (Any, AnyInherited): obj1 = klass(value1) @@ -4257,7 +5059,7 @@ class TestAny(CommonMixin, TestCase): obj.decode(obj.encode()[:-1]) @given( - binary(), + tlv_value_strategy(), integers(min_value=1).map(tag_ctxc), ) def test_stripped_expl(self, value, tag_expl): @@ -4304,8 +5106,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) @@ -4313,9 +5116,13 @@ class TestAny(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) + tag_class, _, tag_num = tag_decode(tag_strip(value)[0]) + self.assertEqual(obj.tag_order, (tag_class, tag_num)) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) + tag_class, _, tag_num = tag_decode(tag_expl) + self.assertEqual(obj_expled.tag_order, (tag_class, tag_num)) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) @@ -4357,6 +5164,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), @@ -4666,6 +5487,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 @@ -4679,9 +5501,12 @@ class TestChoice(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) + self.assertEqual(obj.tag_order, obj.value.tag_order) obj_encoded = obj.encode() obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) + tag_class, _, tag_num = tag_decode(tag_expl) + self.assertEqual(obj_expled.tag_order, (tag_class, tag_num)) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) @@ -4729,6 +5554,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): @@ -5112,8 +5953,9 @@ class SeqMixing(object): with self.assertRaises(NotEnoughData): seq.decode(seq.encode()[:-1]) - @given(binary(min_size=2)) - def test_non_tag_mismatch_raised(self, junk): + @given(integers(min_value=3), binary(min_size=2)) + def test_non_tag_mismatch_raised(self, junk_tag_num, junk): + junk = tag_encode(junk_tag_num) + junk try: _, _, len_encoded = tag_strip(memoryview(junk)) len_decode(len_encoded) @@ -5187,6 +6029,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) @@ -5195,6 +6038,12 @@ class SeqMixing(object): pprint(seq, big_blobs=True, with_decode_path=True) self.assertTrue(seq.ready) seq_encoded = seq.encode() + 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) @@ -5203,6 +6052,8 @@ class SeqMixing(object): t, _, lv = tag_strip(seq_encoded) _, _, v = len_decode(lv) seq_encoded_lenindef = t + LENINDEF + v + EOC + with self.assertRaises(DecodeError): + seq.decode(seq_encoded_lenindef) ctx_copied = deepcopy(ctx_dummy) ctx_copied["bered"] = True seq_decoded_lenindef, tail_lenindef = seq.decode( @@ -5248,6 +6099,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}), @@ -5312,7 +6178,7 @@ class SeqMixing(object): min_size=1, )).items()) tags = [tag_encode(tag) for tag in d.draw(sets( - integers(min_value=0), + integers(min_value=1), min_size=len(_schema), max_size=len(_schema), ))] @@ -5350,7 +6216,7 @@ class SeqMixing(object): def test_missing_from_spec(self, d): names = list(d.draw(sets(text_letters(), min_size=2))) tags = [tag_encode(tag) for tag in d.draw(sets( - integers(min_value=0), + integers(min_value=1), min_size=len(names), max_size=len(names), ))] @@ -5457,42 +6323,44 @@ class TestSet(SeqMixing, CommonMixin, TestCase): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given(data_strategy()) def test_sorted(self, d): - tags = [ - tag_encode(tag) for tag in - d.draw(sets(integers(min_value=1), min_size=1, max_size=10)) - ] + class DummySeq(Sequence): + schema = (("null", Null()),) + + tag_nums = d.draw(sets(integers(min_value=1), min_size=1, max_size=50)) + _, _, dummy_seq_tag_num = tag_decode(DummySeq.tag_default) + assume(any(i > dummy_seq_tag_num for i in tag_nums)) + tag_nums -= set([dummy_seq_tag_num]) + _schema = [(str(i), OctetString(impl=tag_encode(i))) for i in tag_nums] + _schema.append(("seq", DummySeq())) class Seq(Set): - schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)] + schema = d.draw(permutations(_schema)) seq = Seq() - for name, _ in Seq.schema: - seq[name] = OctetString(b"") + for name, _ in _schema: + if name != "seq": + seq[name] = OctetString(name.encode("ascii")) + seq["seq"] = DummySeq((("null", Null()),)) + seq_encoded = seq.encode() seq_decoded, _ = seq.decode(seq_encoded) + seq_encoded_expected = [] + for tag_num in sorted(tag_nums | set([dummy_seq_tag_num])): + if tag_num == dummy_seq_tag_num: + seq_encoded_expected.append(seq["seq"].encode()) + else: + seq_encoded_expected.append(seq[str(tag_num)].encode()) self.assertSequenceEqual( seq_encoded[seq_decoded.tlen + seq_decoded.llen:], - b"".join(sorted([seq[name].encode() for name, _ in Seq.schema])), + b"".join(seq_encoded_expected), ) - @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) + encoded = b"".join(seq[str(i)].encode() for i in tag_nums) + encoded += seq["seq"].encode() 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}): @@ -5502,11 +6370,23 @@ class TestSet(SeqMixing, CommonMixin, TestCase): seq_decoded = copy(seq_decoded) 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, + + def test_same_value_twice(self): + class Seq(Set): + schema = ( + ("bool", Boolean()), + ("int", Integer()), ) + encoded = b"".join(( + Integer(123).encode(), + Integer(234).encode(), + Boolean(True).encode(), + )) + encoded = Seq.tag_default + len_encode(len(encoded)) + encoded + with self.assertRaises(TagMismatch): + Seq().decod(encoded, ctx={"allow_unordered_set": True}) + @composite def seqof_values_strategy(draw, schema=None, do_expl=False): @@ -5569,7 +6449,7 @@ class SeqOfMixing(object): with assertRaisesRegex(self, ValueError, "schema must be specified"): self.base_klass.__mro__[1]() - @given(booleans(), booleans(), binary(), binary()) + @given(booleans(), booleans(), binary(min_size=1), binary(min_size=1)) def test_comparison(self, value1, value2, tag1, tag2): class SeqOf(self.base_klass): schema = Boolean() @@ -5861,8 +6741,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): @@ -5878,6 +6759,12 @@ class SeqOfMixing(object): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + 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) @@ -5923,6 +6810,8 @@ class SeqOfMixing(object): t, _, lv = tag_strip(obj_encoded) _, _, v = len_decode(lv) obj_encoded_lenindef = t + LENINDEF + v + EOC + with self.assertRaises(DecodeError): + obj.decode(obj_encoded_lenindef) obj_decoded_lenindef, tail_lenindef = obj.decode( obj_encoded_lenindef + tail_junk, ctx={"bered": True}, @@ -5942,6 +6831,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), @@ -5996,6 +6900,57 @@ 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_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): @@ -6287,32 +7242,33 @@ class TestOIDDefines(TestCase): 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)))) + for definable_class in (Any, OctetString, BitString): + _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, definable_class(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 Seq(Sequence): + schema = _schema + seq = Seq() + for value_name, value in zip(value_names, values): + seq[value_name] = definable_class(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): @@ -6363,10 +7319,10 @@ class TestDefinesByPath(TestCase): (type_integered, Integer(234)), ) for t, v in pairs_input: - pair = Pair() - pair["type"] = t - pair["value"] = PairValue((Any(v),)) - pairs.append(pair) + pairs.append(Pair(( + ("type", t), + ("value", PairValue((Any(v),))), + ))) seq_inner = SeqInner() seq_inner["typeInner"] = type_innered seq_inner["valueInner"] = Any(pairs) @@ -6504,6 +7460,43 @@ class TestDefinesByPath(TestCase): decoded, _ = Outer().decode(outer.encode()) self.assertEqual(decoded["tgt"].defined[1], Integer(tgt)) + def test_remaining_data(self): + oid = ObjectIdentifier("1.2.3") + class Seq(Sequence): + schema = ( + ("oid", ObjectIdentifier(defines=((("tgt",), { + oid: Integer(), + }),))), + ("tgt", OctetString()), + ) + + seq = Seq(( + ("oid", oid), + ("tgt", OctetString(Integer(123).encode() + b"junk")), + )) + with assertRaisesRegex(self, DecodeError, "remaining data"): + Seq().decode(seq.encode()) + + def test_remaining_data_seqof(self): + oid = ObjectIdentifier("1.2.3") + class SeqOf(SetOf): + schema = OctetString() + + class Seq(Sequence): + schema = ( + ("oid", ObjectIdentifier(defines=((("tgt",), { + oid: Integer(), + }),))), + ("tgt", SeqOf()), + ) + + seq = Seq(( + ("oid", oid), + ("tgt", SeqOf([OctetString(Integer(123).encode() + b"junk")])), + )) + with assertRaisesRegex(self, DecodeError, "remaining data"): + Seq().decode(seq.encode()) + class TestAbsDecodePath(TestCase): @given( @@ -6511,10 +7504,9 @@ class TestAbsDecodePath(TestCase): 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, - ) + dp = abs_decode_path(decode_path, rel_path) + self.assertSequenceEqual(dp, decode_path + rel_path) + repr(dp) @given( lists(text(alphabet=ascii_letters, min_size=1)).map(tuple), @@ -6573,11 +7565,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", @@ -6585,6 +7579,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("43054A6F6E6573"), ) + + def test_3(self): self.assertSequenceEqual( Any( VisibleString( @@ -6595,6 +7591,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("A20743054A6F6E6573"), ) + + def test_4(self): self.assertSequenceEqual( OctetString( VisibleString( @@ -6605,6 +7603,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("670743054A6F6E6573"), ) + + def test_5(self): self.assertSequenceEqual( VisibleString("Jones", impl=tag_ctxp(2)).encode(), hexdec("82054A6F6E6573"), @@ -6631,3 +7631,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"), + )