X-Git-Url: http://www.git.cypherpunks.ru/?p=pyderasn.git;a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=73eea21c6151a8ac3979f103eb42c3b7632cf0b5;hp=a39d125ee036d8c12259732254a11543aec88c15;hb=3eb38566e734d6f5b274f4bb99fadb55f99ff2c7;hpb=521a4868199657f49e0b20973dab53730b93fd54 diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index a39d125..73eea21 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 @@ -18,14 +18,22 @@ from copy import copy 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 from string import printable from string import whitespace +from time import mktime from time import time from unittest import TestCase +from dateutil.tz import UTC from hypothesis import assume from hypothesis import given from hypothesis import settings @@ -54,6 +62,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 +77,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 @@ -123,8 +134,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 +168,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): @@ -262,6 +277,19 @@ class TestTagCoder(TestCase): with self.assertRaises(DecodeError): len_decode(octets) + @given(tag_classes, tag_forms, integers(min_value=31)) + def test_leading_zero_byte(self, klass, form, num): + raw = tag_encode(klass=klass, form=form, num=num) + raw = b"".join((raw[:1], b"\x80", raw[1:])) + with assertRaisesRegex(self, DecodeError, "leading zero byte"): + tag_strip(raw) + + @given(tag_classes, tag_forms, integers(max_value=30, min_value=0)) + def test_unexpected_long_form(self, klass, form, num): + raw = int2byte(klass | form | 31) + int2byte(num) + with assertRaisesRegex(self, DecodeError, "unexpected long form"): + tag_strip(raw) + class TestLenCoder(TestCase): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @@ -337,14 +365,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 +389,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 @@ -402,6 +438,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) @@ -409,7 +447,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) @@ -486,6 +524,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(), @@ -495,6 +535,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), @@ -567,8 +609,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,15 +625,23 @@ 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_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.encode(), + ) + obj_expled_hex_encoded = obj_expled.hexencode() ctx_copied = deepcopy(ctx_dummy) - obj_decoded, tail = obj_expled.decode( - obj_expled_encoded + tail_junk, + obj_decoded, tail = obj_expled.hexdecode( + obj_expled_hex_encoded + hexenc(tail_junk), offset=offset, ctx=ctx_copied, ) @@ -603,7 +654,7 @@ class TestBoolean(CommonMixin, TestCase): self.assertNotEqual(obj_decoded, obj) self.assertEqual(bool(obj_decoded), bool(obj_expled)) self.assertEqual(bool(obj_decoded), bool(obj)) - self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) + self.assertSequenceEqual(obj_decoded.hexencode(), obj_expled_hex_encoded) self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl) self.assertEqual(obj_decoded.expl_tlen, len(tag_expl)) self.assertEqual( @@ -619,10 +670,25 @@ class TestBoolean(CommonMixin, TestCase): self.assertEqual(obj_decoded.expl_offset, offset) assert_exceeding_data( self, - lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + lambda: obj_expled.hexdecod(obj_expled_hex_encoded + hexenc(tail_junk)), 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 +706,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 +774,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( @@ -820,6 +886,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) @@ -828,7 +896,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) @@ -882,6 +950,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) @@ -890,6 +962,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): @@ -1065,8 +1141,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 +1157,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, @@ -1121,6 +1206,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), @@ -1296,6 +1396,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) @@ -1459,6 +1561,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 +1576,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, @@ -1518,6 +1629,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 +1689,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 +1706,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 +1717,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 +1725,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 +1774,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 +1923,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): @@ -1838,6 +2002,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) @@ -1884,6 +2050,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)) @@ -1893,6 +2063,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): @@ -2053,8 +2227,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 +2243,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, @@ -2109,6 +2292,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 +2322,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 +2333,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 +2378,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 +2447,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 +2497,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 +2603,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 +2614,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, @@ -2417,6 +2661,21 @@ 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): @@ -2484,6 +2743,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) @@ -2492,7 +2753,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 +2966,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 +2982,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, @@ -2761,6 +3031,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), @@ -2797,6 +3082,10 @@ class TestObjectIdentifier(CommonMixin, TestCase): data, ))) + def test_go_non_minimal_encoding(self): + with self.assertRaises(DecodeError): + ObjectIdentifier().decode(hexdec("060a2a80864886f70d01010b")) + def test_x690_vector(self): self.assertEqual( ObjectIdentifier().decode(hexdec("0603883703"))[0], @@ -2819,6 +3108,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 +3251,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 +3367,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 @@ -3070,6 +3382,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) @@ -3111,6 +3424,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): @@ -3152,9 +3480,7 @@ class StringMixin(object): repr(err.exception) def text_alphabet(self): - if self.base_klass.encoding in ("ascii", "iso-8859-1"): - return printable + whitespace - return None + return "".join(six_unichr(c) for c in six_xrange(256)) @given(booleans()) def test_optional(self, optional): @@ -3172,6 +3498,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) @@ -3221,6 +3549,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)) @@ -3230,6 +3562,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): @@ -3392,6 +3728,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, @@ -3404,6 +3741,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) @@ -3447,9 +3785,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( @@ -3459,6 +3809,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): @@ -3474,7 +3844,7 @@ class TestNumericString(StringMixin, CommonMixin, TestCase): @given(text(alphabet=ascii_letters, min_size=1, max_size=5)) def test_non_numeric(self, non_numeric_text): - with assertRaisesRegex(self, DecodeError, "non-numeric"): + with assertRaisesRegex(self, DecodeError, "alphabet value"): self.base_klass(non_numeric_text) @given( @@ -3497,6 +3867,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, @@ -3511,7 +3894,7 @@ class TestPrintableString( @given(text(alphabet=sorted(set(whitespace) - set(" ")), min_size=1, max_size=5)) def test_non_printable(self, non_printable_text): - with assertRaisesRegex(self, DecodeError, "non-printable"): + with assertRaisesRegex(self, DecodeError, "alphabet value"): self.base_klass(non_printable_text) @given( @@ -3540,14 +3923,22 @@ class TestPrintableString( ("&", {"allow_ampersand": True}), ("&*", {"allow_asterisk": True, "allow_ampersand": True}), ): - s = "hello invalid " + c - with assertRaisesRegex(self, DecodeError, "non-printable"): + s = "hello invalid" + obj = self.base_klass(s) + for prop in kwargs.keys(): + self.assertFalse(getattr(obj, prop)) + s += c + with assertRaisesRegex(self, DecodeError, "alphabet value"): self.base_klass(s) self.base_klass(s, **kwargs) klass = self.base_klass(**kwargs) obj = klass(s) + for prop in kwargs.keys(): + self.assertTrue(getattr(obj, prop)) obj = copy(obj) obj(s) + for prop in kwargs.keys(): + self.assertTrue(getattr(obj, prop)) class TestTeletexString( @@ -3576,6 +3967,18 @@ class TestIA5String( ): base_klass = IA5String + def text_alphabet(self): + return "".join(six_unichr(c) for c in six_xrange(128)) + + @given(integers(min_value=128, max_value=255)) + def test_alphabet_bad(self, code): + with self.assertRaises(DecodeError): + self.base_klass().decod( + self.base_klass.tag_default + + len_encode(1) + + bytes(bytearray([code])), + ) + class TestGraphicString( UnicodeDecodeErrorMixin, @@ -3594,6 +3997,9 @@ class TestVisibleString( ): base_klass = VisibleString + def text_alphabet(self): + return " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + def test_x690_vector(self): obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573")) self.assertSequenceEqual(tail, b"") @@ -3630,6 +4036,38 @@ class TestVisibleString( self.assertTrue(obj.lenindef) self.assertTrue(obj.bered) + @given(one_of(( + integers(min_value=0, max_value=ord(" ") - 1), + integers(min_value=ord("~") + 1, max_value=255), + ))) + def test_alphabet_bad(self, code): + with self.assertRaises(DecodeError): + self.base_klass().decod( + self.base_klass.tag_default + + len_encode(1) + + bytes(bytearray([code])), + ) + + @given( + sets(integers(min_value=0, max_value=10), min_size=2, max_size=2), + integers(min_value=0), + decode_path_strat, + ) + def test_invalid_bounds_while_decoding(self, ints, offset, decode_path): + value, bound_min = list(sorted(ints)) + + class String(self.base_klass): + bounds = (bound_min, bound_min) + with self.assertRaises(DecodeError) as err: + String().decode( + self.base_klass(b"1" * value).encode(), + offset=offset, + decode_path=decode_path, + ) + repr(err.exception) + self.assertEqual(err.exception.offset, offset) + self.assertEqual(err.exception.decode_path, decode_path) + class TestGeneralString( UnicodeDecodeErrorMixin, @@ -3707,7 +4145,12 @@ class TimeMixin(object): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) - value = d.draw(datetimes(min_value=self.min_datetime)) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) + 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) @@ -3862,6 +4305,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) @@ -3909,11 +4353,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", @@ -3966,6 +4415,244 @@ 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()), + ) + else: + try: + obj.todatetime().timestamp() + except: + pass + else: + 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, @@ -4013,16 +4700,62 @@ 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) + + def test_aware(self): + with assertRaisesRegex(self, ValueError, "only naive"): + GeneralizedTime(datetime(2000, 1, 1, 1, tzinfo=UTC)) + 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", @@ -4083,6 +4816,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( @@ -4118,10 +5074,21 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): junk ) + def test_aware(self): + with assertRaisesRegex(self, ValueError, "only naive"): + UTCTime(datetime(2000, 1, 1, 1, tzinfo=UTC)) + + +@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))) @@ -4151,7 +5118,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) @@ -4161,6 +5128,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) @@ -4185,7 +5154,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) @@ -4249,7 +5218,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): @@ -4296,8 +5265,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) @@ -4305,9 +5275,14 @@ 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() + 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) + 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) @@ -4349,6 +5324,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), @@ -4526,6 +5515,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) @@ -4658,6 +5649,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 @@ -4671,9 +5663,13 @@ 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() + 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) + 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) @@ -4721,6 +5717,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): @@ -5001,6 +6013,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) @@ -5104,8 +6118,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) @@ -5179,6 +6194,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) @@ -5187,6 +6203,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) @@ -5195,6 +6218,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( @@ -5240,6 +6265,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}), @@ -5252,6 +6292,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) @@ -5304,29 +6345,39 @@ 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), ))] + 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) @@ -5336,13 +6387,27 @@ 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): 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), ))] @@ -5361,6 +6426,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): @@ -5387,6 +6454,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) @@ -5449,42 +6519,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}): @@ -5494,11 +6566,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): @@ -5561,7 +6645,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() @@ -5610,6 +6694,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: @@ -5653,6 +6739,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, @@ -5665,6 +6755,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): @@ -5853,8 +6947,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): @@ -5870,6 +6965,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) @@ -5915,6 +7017,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}, @@ -5934,6 +7038,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), @@ -5988,6 +7107,80 @@ 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): @@ -6279,32 +7472,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): @@ -6355,10 +7549,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) @@ -6496,6 +7690,45 @@ 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( @@ -6503,10 +7736,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), @@ -6565,11 +7797,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", @@ -6577,6 +7811,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("43054A6F6E6573"), ) + + def test_3(self): self.assertSequenceEqual( Any( VisibleString( @@ -6587,6 +7823,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("A20743054A6F6E6573"), ) + + def test_4(self): self.assertSequenceEqual( OctetString( VisibleString( @@ -6597,6 +7835,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("670743054A6F6E6573"), ) + + def test_5(self): self.assertSequenceEqual( VisibleString("Jones", impl=tag_ctxp(2)).encode(), hexdec("82054A6F6E6573"), @@ -6623,3 +7863,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"), + )