X-Git-Url: http://www.git.cypherpunks.ru/?p=pyderasn.git;a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=e8f8bdf4570e1c52adbf2b7a7c5a827c47a2099c;hp=1c2f9b037bf47c1279056344b9fb07b91f784505;hb=HEAD;hpb=5e98515aa98abf6ad1f650078551e6543d431b99 diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 1c2f9b0..e8f8bdf 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -1,6 +1,6 @@ # coding: utf-8 -# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures -# Copyright (C) 2017-2020 Sergey Matveev +# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures +# Copyright (C) 2017-2024 Sergey Matveev # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as @@ -20,6 +20,10 @@ from copy import deepcopy from datetime import datetime from datetime import timedelta from importlib import import_module +from io import BytesIO +from operator import attrgetter +from os import environ +from os import urandom from random import random from string import ascii_letters from string import digits @@ -28,7 +32,9 @@ from string import whitespace from time import mktime from time import time from unittest import TestCase +from unittest.mock import patch +from dateutil.tz import UTC from hypothesis import assume from hypothesis import given from hypothesis import settings @@ -48,18 +54,9 @@ from hypothesis.strategies import sampled_from from hypothesis.strategies import sets from hypothesis.strategies import text from hypothesis.strategies import tuples -from six import assertRaisesRegex -from six import binary_type -from six import byte2int -from six import indexbytes -from six import int2byte -from six import iterbytes -from six import PY2 -from six import text_type -from six import unichr as six_unichr -from 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 +from pickle import dumps as pickle_dumps +from pickle import HIGHEST_PROTOCOL as pickle_proto +from pickle import loads as pickle_loads from pyderasn import _pp from pyderasn import abs_decode_path @@ -71,6 +68,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 @@ -124,10 +123,13 @@ from pyderasn import UTCTime from pyderasn import UTF8String from pyderasn import VideotexString from pyderasn import VisibleString +import pyderasn +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 @@ -160,7 +162,7 @@ def register_class(klass): def assert_exceeding_data(self, call, junk): if len(junk) <= 0: return - with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)) as err: + with self.assertRaisesRegex(ExceedingData, "%d trailing bytes" % len(junk)) as err: call() repr(err) @@ -184,8 +186,8 @@ class TestTagCoder(TestCase): self.assertEqual(tag_decode(raw), (klass, form, num)) self.assertEqual(len(raw), 1) self.assertEqual( - byte2int(tag_encode(klass=klass, form=form, num=0)), - byte2int(raw) & (1 << 7 | 1 << 6 | 1 << 5), + tag_encode(klass=klass, form=form, num=0)[0], + raw[0] & (1 << 7 | 1 << 6 | 1 << 5), ) stripped, tlen, tail = tag_strip(memoryview(raw + junk)) self.assertSequenceEqual(stripped.tobytes(), raw) @@ -204,11 +206,11 @@ class TestTagCoder(TestCase): self.assertEqual(tag_decode(raw), (klass, form, num)) self.assertGreater(len(raw), 1) self.assertEqual( - byte2int(tag_encode(klass=klass, form=form, num=0)) | 31, - byte2int(raw[:1]), + tag_encode(klass=klass, form=form, num=0)[0] | 31, + raw[0], ) - self.assertEqual(byte2int(raw[-1:]) & 0x80, 0) - self.assertTrue(all(b & 0x80 > 0 for b in iterbytes(raw[1:-1]))) + self.assertEqual(raw[-1] & 0x80, 0) + self.assertTrue(all(b & 0x80 > 0 for b in raw[1:-1])) stripped, tlen, tail = tag_strip(memoryview(raw + junk)) self.assertSequenceEqual(stripped.tobytes(), raw) self.assertEqual(tlen, len(raw)) @@ -220,7 +222,7 @@ class TestTagCoder(TestCase): raw = bytearray(tag_encode(num=num)) for i in range(1, len(raw)): raw[i] |= 0x80 - with assertRaisesRegex(self, DecodeError, "unfinished tag"): + with self.assertRaisesRegex(DecodeError, "unfinished tag"): tag_strip(bytes(raw)) def test_go_vectors_valid(self): @@ -262,11 +264,24 @@ class TestTagCoder(TestCase): integers(min_value=0, max_value=2), ) def test_long_instead_of_short(self, l, dummy_num): - octets = (b"\x00" * dummy_num) + int2byte(l) - octets = int2byte((dummy_num + 1) | 0x80) + octets + octets = (b"\x00" * dummy_num) + bytes([l]) + octets = bytes([(dummy_num + 1) | 0x80]) + octets 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 self.assertRaisesRegex(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 = bytes([klass | form | 31, num]) + with self.assertRaisesRegex(DecodeError, "unexpected long form"): + tag_strip(raw) + class TestLenCoder(TestCase): @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @@ -291,9 +306,9 @@ class TestLenCoder(TestCase): raw = len_encode(l) + junk decoded, llen, tail = len_decode(memoryview(raw)) self.assertEqual(decoded, l) - self.assertEqual((llen - 1) | 0x80, byte2int(raw)) + self.assertEqual((llen - 1) | 0x80, raw[0]) self.assertEqual(llen, len(raw) - len(junk)) - self.assertNotEqual(indexbytes(raw, 1), 0) + self.assertNotEqual(raw[1], 0) self.assertSequenceEqual(tail.tobytes(), junk) def test_empty(self): @@ -312,8 +327,6 @@ text_printable = text(alphabet=printable, min_size=1) @composite def text_letters(draw): result = draw(text(alphabet=ascii_letters, min_size=1)) - if PY2: - result = result.encode("ascii") return result @@ -342,14 +355,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) @@ -360,6 +379,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 @@ -407,6 +428,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) @@ -414,7 +437,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) @@ -491,6 +514,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(), @@ -500,6 +525,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), @@ -572,8 +599,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( @@ -587,11 +615,19 @@ class TestBoolean(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled.encode(), + ) obj_expled_hex_encoded = obj_expled.hexencode() ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.hexdecode( @@ -628,7 +664,22 @@ class TestBoolean(CommonMixin, TestCase): tail_junk, ) - @given(integers(min_value=2)) + 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, max_value=10)) def test_invalid_len(self, l): with self.assertRaises(InvalidLength): Boolean().decode(b"".join(( @@ -639,20 +690,15 @@ class TestBoolean(CommonMixin, TestCase): @given(integers(min_value=0 + 1, max_value=255 - 1)) def test_ber_value(self, value): - with assertRaisesRegex(self, DecodeError, "unacceptable Boolean value"): + with self.assertRaisesRegex(DecodeError, "unacceptable Boolean value"): Boolean().decode(b"".join(( Boolean.tag_default, len_encode(1), - int2byte(value), + bytes([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), bytes([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) @@ -670,7 +716,7 @@ class TestBoolean(CommonMixin, TestCase): encoded = expl + LENINDEF + Boolean(False).encode() with self.assertRaises(LenIndefForm): Boolean(expl=expl).decode(encoded + junk) - with assertRaisesRegex(self, DecodeError, "no EOC"): + with self.assertRaisesRegex(DecodeError, "no EOC"): Boolean(expl=expl).decode(encoded + junk, ctx={"bered": True}) obj, tail = Boolean(expl=expl).decode( encoded + EOC + junk, @@ -714,6 +760,7 @@ class TestBoolean(CommonMixin, TestCase): with self.assertRaises(LenIndefForm): SeqOf().decode(encoded) seqof, tail = SeqOf().decode(encoded, ctx={"bered": True}) + list(SeqOf().decode_evgen(encoded, ctx={"bered": True})) self.assertSequenceEqual(tail, b"") self.assertSequenceEqual([bool(v) for v in seqof], values) self.assertSetEqual( @@ -825,6 +872,8 @@ class TestInteger(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) with self.assertRaises(ObjNotReady) as err: obj.encode() + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) repr(err.exception) obj = Integer(value) self.assertTrue(obj.ready) @@ -833,7 +882,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,19 +931,27 @@ class TestInteger(CommonMixin, TestCase): with self.assertRaises(BoundsError) as err: Integer(value=values[0], bounds=(values[1], values[2])) repr(err.exception) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: Integer(bounds=(values[1], values[2])).decode( Integer(values[0]).encode() ) repr(err.exception) + with self.assertRaisesRegex(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) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: Integer(bounds=(values[0], values[1])).decode( Integer(values[2]).encode() ) repr(err.exception) + with self.assertRaisesRegex(DecodeError, "bounds") as err: + Integer(bounds=(values[0], values[1])).decode( + encode2pass(Integer(values[2])) + ) @given(data_strategy()) def test_call(self, d): @@ -1070,8 +1127,9 @@ class TestInteger(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path): for klass in (Integer, IntegerInherited): _, _, _, _, default, optional, _, _decoded = values obj = klass( @@ -1085,12 +1143,20 @@ class TestInteger(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.assertEqual(encode2pass(obj), obj_encoded) + self.assertSequenceEqual(encode_cer(obj), obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) list(obj_expled.pps()) pprint(obj_expled, big_blobs=True, with_decode_path=True) obj_expled_encoded = obj_expled.encode() + obj_expled_cer = encode_cer(obj_expled) + self.assertNotEqual(obj_expled_cer, obj_encoded) + self.assertSequenceEqual( + obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(), + obj_expled_encoded, + ) ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -1126,6 +1192,21 @@ class TestInteger(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + obj_expled_encoded + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + def test_go_vectors_valid(self): for data, expect in (( (b"\x00", 0), @@ -1191,6 +1272,8 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl= if generation_choice == 2 or draw(booleans()): return draw(binary(max_size=len(schema) // 8)) if generation_choice == 3 or draw(booleans()): + if len(schema) == 0: + return () return tuple(draw(lists(sampled_from([name for name, _ in schema])))) return None value = _value(value_required) @@ -1301,6 +1384,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) @@ -1464,6 +1549,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 @@ -1478,12 +1564,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, @@ -1523,13 +1617,27 @@ 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): BitString().decode(b"".join(( BitString.tag_default, len_encode(1), - int2byte(pad_size), + bytes([pad_size]), ))) def test_go_vectors_invalid(self): @@ -1569,6 +1677,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( @@ -1585,8 +1694,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) + @@ -1595,21 +1705,27 @@ class TestBitString(CommonMixin, TestCase): EOC ) chunks = [] + chunks_len_expected = [] payload_expected = b"" bit_len_expected = 0 for chunk_input in chunk_inputs: - if isinstance(chunk_input, binary_type): + if isinstance(chunk_input, bytes): 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 = ( @@ -1625,7 +1741,7 @@ class TestBitString(CommonMixin, TestCase): b"".join(chunks) + chunk_last.encode() ) - with assertRaisesRegex(self, DecodeError, "unallowed BER"): + with self.assertRaisesRegex(DecodeError, "unallowed BER"): BitString(impl=tag_encode(impl)).decode(encoded_indefinite) for lenindef_expected, encoded in ( (True, encoded_indefinite), @@ -1650,12 +1766,22 @@ class TestBitString(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) + evgens = list(BitString(impl=tag_encode(impl)).decode_evgen( + encoded, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(chunks_len_expected) + 1) + for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens): + self.assertGreater(len(dp), len(decode_path)) + self.assertEqual(obj.vlen, chunk_len_expected) + @given( integers(min_value=0), decode_path_strat, ) def test_ber_definite_too_short(self, offset, decode_path): - with assertRaisesRegex(self, DecodeError, "longer than data") as err: + with self.assertRaisesRegex(DecodeError, "longer than data") as err: BitString().decode( tag_encode(3, form=TagFormConstructed) + len_encode(1), offset=offset, @@ -1670,7 +1796,7 @@ class TestBitString(CommonMixin, TestCase): decode_path_strat, ) def test_ber_definite_no_data(self, offset, decode_path): - with assertRaisesRegex(self, DecodeError, "zero length") as err: + with self.assertRaisesRegex(DecodeError, "zero length") as err: BitString().decode( tag_encode(3, form=TagFormConstructed) + len_encode(0), offset=offset, @@ -1705,7 +1831,7 @@ class TestBitString(CommonMixin, TestCase): def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks): bs = BitString(b"data").encode() bs_longer = BitString(b"data-longer").encode() - with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err: + with self.assertRaisesRegex(DecodeError, "chunk out of bounds") as err: BitString().decode( ( tag_encode(3, form=TagFormConstructed) + @@ -1725,7 +1851,7 @@ class TestBitString(CommonMixin, TestCase): decode_path_strat, ) def test_ber_indefinite_no_chunks(self, offset, decode_path): - with assertRaisesRegex(self, DecodeError, "no chunks") as err: + with self.assertRaisesRegex(DecodeError, "no chunks") as err: BitString().decode( tag_encode(3, form=TagFormConstructed) + LENINDEF + EOC, offset=offset, @@ -1745,7 +1871,7 @@ class TestBitString(CommonMixin, TestCase): chunks.append(bs_short) offset = d.draw(integers(min_value=0)) decode_path = d.draw(decode_path_strat) - with assertRaisesRegex(self, DecodeError, "multiple of 8 bits") as err: + with self.assertRaisesRegex(DecodeError, "multiple of 8 bits") as err: BitString().decode( ( tag_encode(3, form=TagFormConstructed) + @@ -1785,6 +1911,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): @@ -1828,7 +1972,7 @@ class TestOctetString(CommonMixin, TestCase): def test_invalid_value_type(self): with self.assertRaises(InvalidValueType) as err: - OctetString(text_type(123)) + OctetString(str(123)) repr(err.exception) @given(booleans()) @@ -1846,6 +1990,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) @@ -1887,20 +2033,28 @@ class TestOctetString(CommonMixin, TestCase): with self.assertRaises(BoundsError) as err: OctetString(value=value, bounds=(bound_min, bound_max)) repr(err.exception) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: OctetString(bounds=(bound_min, bound_max)).decode( OctetString(value).encode() ) repr(err.exception) + with self.assertRaisesRegex(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)) repr(err.exception) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: OctetString(bounds=(bound_min, bound_max)).decode( OctetString(value).encode() ) repr(err.exception) + with self.assertRaisesRegex(DecodeError, "bounds") as err: + OctetString(bounds=(bound_min, bound_max)).decode( + encode2pass(OctetString(value)) + ) @given(data_strategy()) def test_call(self, d): @@ -2061,8 +2215,19 @@ class TestOctetString(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, + booleans(), ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric( + self, + values, + value, + tag_expl, + offset, + tail_junk, + decode_path, + keep_memoryview, + ): for klass in (OctetString, OctetStringInherited): _, _, _, _, default, optional, _decoded = values obj = klass( @@ -2076,12 +2241,21 @@ 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_dummy["keep_memoryview"] = keep_memoryview ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -2097,6 +2271,10 @@ class TestOctetString(CommonMixin, TestCase): self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) self.assertEqual(bytes(obj_decoded), bytes(obj)) + self.assertIsInstance( + obj_decoded._value, + memoryview if keep_memoryview else bytes, + ) self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl) self.assertEqual(obj_decoded.expl_tlen, len(tag_expl)) @@ -2117,6 +2295,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( @@ -2132,8 +2325,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) + @@ -2142,15 +2336,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): + if isinstance(chunk_input, bytes): 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 + @@ -2162,7 +2361,7 @@ class TestOctetString(CommonMixin, TestCase): len_encode(len(b"".join(chunks))) + b"".join(chunks) ) - with assertRaisesRegex(self, DecodeError, "unallowed BER"): + with self.assertRaisesRegex(DecodeError, "unallowed BER"): OctetString(impl=tag_encode(impl)).decode(encoded_indefinite) for lenindef_expected, encoded in ( (True, encoded_indefinite), @@ -2186,12 +2385,22 @@ class TestOctetString(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) + evgens = list(OctetString(impl=tag_encode(impl)).decode_evgen( + encoded, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(chunks_len_expected) + 1) + for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens): + self.assertGreater(len(dp), len(decode_path)) + self.assertEqual(obj.vlen, chunk_len_expected) + @given( integers(min_value=0), decode_path_strat, ) def test_ber_definite_too_short(self, offset, decode_path): - with assertRaisesRegex(self, DecodeError, "longer than data") as err: + with self.assertRaisesRegex(DecodeError, "longer than data") as err: OctetString().decode( tag_encode(4, form=TagFormConstructed) + len_encode(1), offset=offset, @@ -2226,7 +2435,7 @@ class TestOctetString(CommonMixin, TestCase): def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks): bs = OctetString(b"data").encode() bs_longer = OctetString(b"data-longer").encode() - with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err: + with self.assertRaisesRegex(DecodeError, "chunk out of bounds") as err: OctetString().decode( ( tag_encode(4, form=TagFormConstructed) + @@ -2241,6 +2450,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): @@ -2273,7 +2500,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) @@ -2379,8 +2606,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) @@ -2389,12 +2617,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, @@ -2428,6 +2664,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): @@ -2444,8 +2695,8 @@ def oid_strategy(draw): if first_arc in (0, 1): second_arc = draw(integers(min_value=0, max_value=39)) else: - second_arc = draw(integers(min_value=0)) - other_arcs = draw(lists(integers(min_value=0))) + second_arc = draw(integers(min_value=0, max_value=1 << 63)) + other_arcs = draw(lists(integers(min_value=0, max_value=1 << 63))) return tuple([first_arc, second_arc] + other_arcs) @@ -2495,6 +2746,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) @@ -2503,7 +2756,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) @@ -2671,24 +2924,30 @@ class TestObjectIdentifier(CommonMixin, TestCase): len_encode(len(data)), data, )) - with assertRaisesRegex(self, DecodeError, "unfinished OID"): + with self.assertRaisesRegex(DecodeError, "unfinished OID"): obj.decode(data) - @given(integers(min_value=0)) + @given(integers(min_value=0, max_value=1 << 63)) def test_invalid_short(self, value): with self.assertRaises(InvalidOID): ObjectIdentifier((value,)) with self.assertRaises(InvalidOID): ObjectIdentifier("%d" % value) - @given(integers(min_value=3), integers(min_value=0)) + @given( + integers(min_value=3, max_value=1 << 63), + integers(min_value=0, max_value=1 << 63), + ) def test_invalid_first_arc(self, first_arc, second_arc): with self.assertRaises(InvalidOID): ObjectIdentifier((first_arc, second_arc)) with self.assertRaises(InvalidOID): ObjectIdentifier("%d.%d" % (first_arc, second_arc)) - @given(integers(min_value=0, max_value=1), integers(min_value=40)) + @given( + integers(min_value=0, max_value=1), + integers(min_value=40, max_value=1 << 63), + ) def test_invalid_second_arc(self, first_arc, second_arc): with self.assertRaises(InvalidOID): ObjectIdentifier((first_arc, second_arc)) @@ -2716,8 +2975,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( @@ -2731,12 +2991,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, @@ -2772,6 +3040,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), @@ -2808,6 +3091,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], @@ -2827,7 +3114,7 @@ class TestObjectIdentifier(CommonMixin, TestCase): obj = copy(obj) self.assertTrue(obj.ber_encoded) self.assertTrue(obj.bered) - with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"): + with self.assertRaisesRegex(DecodeError, "non normalized arc encoding"): ObjectIdentifier().decode(tampered) @given(data_strategy()) @@ -2882,7 +3169,7 @@ class TestObjectIdentifier(CommonMixin, TestCase): obj = copy(obj) self.assertTrue(obj.ber_encoded) self.assertTrue(obj.bered) - with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"): + with self.assertRaisesRegex(DecodeError, "non normalized arc encoding"): ObjectIdentifier().decode(tampered) @@ -2920,7 +3207,7 @@ class TestEnumerated(CommonMixin, TestCase): base_klass = EWhatever def test_schema_required(self): - with assertRaisesRegex(self, ValueError, "schema must be specified"): + with self.assertRaisesRegex(ValueError, "schema must be specified"): Enumerated() def test_invalid_value_type(self): @@ -2973,7 +3260,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 = ( @@ -3089,6 +3376,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 @@ -3103,6 +3391,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) @@ -3144,6 +3433,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): @@ -3185,9 +3489,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(chr(c) for c in range(256)) @given(booleans()) def test_optional(self, optional): @@ -3201,17 +3503,19 @@ class StringMixin(object): repr(obj) list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) - text_type(obj) + str(obj) 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) repr(obj) list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) - text_type(obj) + str(obj) @given(data_strategy()) def test_comparison(self, d): @@ -3224,7 +3528,7 @@ class StringMixin(object): self.assertEqual(obj1 == obj2, value1 == value2) self.assertEqual(obj1 != obj2, value1 != value2) self.assertEqual(obj1 == bytes(obj2), value1 == value2) - self.assertEqual(obj1 == text_type(obj2), value1 == value2) + self.assertEqual(obj1 == str(obj2), value1 == value2) obj1 = self.base_klass(value1, impl=tag1) obj2 = self.base_klass(value1, impl=tag2) self.assertEqual(obj1 == obj2, tag1 == tag2) @@ -3249,20 +3553,28 @@ class StringMixin(object): with self.assertRaises(BoundsError) as err: self.base_klass(value=value, bounds=(bound_min, bound_max)) repr(err.exception) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: self.base_klass(bounds=(bound_min, bound_max)).decode( self.base_klass(value).encode() ) repr(err.exception) + with self.assertRaisesRegex(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)) repr(err.exception) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: self.base_klass(bounds=(bound_min, bound_max)).decode( self.base_klass(value).encode() ) repr(err.exception) + with self.assertRaisesRegex(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): @@ -3425,6 +3737,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, @@ -3437,6 +3750,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) @@ -3458,8 +3772,8 @@ class StringMixin(object): self.assertNotEqual(obj_decoded, obj) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) self.assertEqual(bytes(obj_decoded), bytes(obj)) - self.assertEqual(text_type(obj_decoded), text_type(obj_expled)) - self.assertEqual(text_type(obj_decoded), text_type(obj)) + self.assertEqual(str(obj_decoded), str(obj_expled)) + self.assertEqual(str(obj_decoded), str(obj)) self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl) self.assertEqual(obj_decoded.expl_tlen, len(tag_expl)) @@ -3480,18 +3794,50 @@ 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( - alphabet="".join(six_unichr(i) for i in list(range(0x0410, 0x044f + 1))), + alphabet="".join(chr(i) for i in list(range(0x0410, 0x044f + 1))), min_size=1, max_size=5, ) +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): @@ -3507,7 +3853,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 self.assertRaisesRegex(DecodeError, "alphabet value"): self.base_klass(non_numeric_text) @given( @@ -3530,6 +3876,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, @@ -3544,7 +3903,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 self.assertRaisesRegex(DecodeError, "alphabet value"): self.base_klass(non_printable_text) @given( @@ -3578,7 +3937,7 @@ class TestPrintableString( for prop in kwargs.keys(): self.assertFalse(getattr(obj, prop)) s += c - with assertRaisesRegex(self, DecodeError, "non-printable"): + with self.assertRaisesRegex(DecodeError, "alphabet value"): self.base_klass(s) self.base_klass(s, **kwargs) klass = self.base_klass(**kwargs) @@ -3617,6 +3976,18 @@ class TestIA5String( ): base_klass = IA5String + def text_alphabet(self): + return "".join(chr(c) for c in range(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, @@ -3635,6 +4006,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"") @@ -3671,6 +4045,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, @@ -3748,6 +4154,8 @@ class TimeMixin(object): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) + with self.assertRaises(ObjNotReady) as err: + encode2pass(obj) value = d.draw(datetimes( min_value=self.min_datetime, max_value=self.max_datetime, @@ -3906,6 +4314,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) @@ -3953,13 +4362,14 @@ 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()) + str(GeneralizedTime()) repr(GeneralizedTime()) def test_x690_vector_valid(self): @@ -4040,8 +4450,7 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): @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)) + year = d.draw(integers(min_value=2, 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)) @@ -4098,8 +4507,13 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): mktime(obj.todatetime().timetuple()), mktime(dt.timetuple()), ) - elif not PY2: - self.assertEqual(obj.todatetime().timestamp(), dt.timestamp()) + 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) @@ -4291,7 +4705,7 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): def test_ns_fractions(self): GeneralizedTime(b"20010101000000.000001Z") - with assertRaisesRegex(self, DecodeError, "only microsecond fractions"): + with self.assertRaisesRegex(DecodeError, "only microsecond fractions"): GeneralizedTime(b"20010101000000.0000001Z") def test_non_pure_integers(self): @@ -4331,18 +4745,23 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): with self.assertRaises(DecodeError): GeneralizedTime(data) + def test_aware(self): + with self.assertRaisesRegex(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()) + str(GeneralizedTime()) repr(UTCTime()) def test_x690_vector_valid(self): @@ -4663,10 +5082,29 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): junk ) + def test_aware(self): + with self.assertRaisesRegex(ValueError, "only naive"): + UTCTime(datetime(2000, 1, 1, 1, tzinfo=UTC)) + + def test_raises_if_no_dateutil(self): + with patch("pyderasn.tzUTC", new="missing"): + with self.assertRaisesRegex(NotImplementedError, "dateutil"): + UTCTime(datetime.now()).totzdatetime() + + def test_tzinfo_gives_datetime_with_tzutc_tzinfo(self): + self.assertEqual(UTCTime(datetime.now()).totzdatetime().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))) @@ -4696,7 +5134,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) @@ -4706,6 +5144,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) @@ -4730,7 +5170,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) @@ -4794,7 +5234,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): @@ -4841,8 +5281,19 @@ class TestAny(CommonMixin, TestCase): integers(min_value=1).map(tag_ctxc), integers(min_value=0), binary(max_size=5), + decode_path_strat, + booleans(), ) - def test_symmetric(self, values, value, tag_expl, offset, tail_junk): + def test_symmetric( + self, + values, + value, + tag_expl, + offset, + tail_junk, + decode_path, + keep_memoryview, + ): for klass in (Any, AnyInherited): _, _, optional, _decoded = values obj = klass(value=value, optional=optional, _decoded=_decoded) @@ -4850,13 +5301,19 @@ 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) obj_expled_encoded = obj_expled.encode() + ctx_dummy["keep_memoryview"] = keep_memoryview ctx_copied = deepcopy(ctx_dummy) obj_decoded, tail = obj_expled.decode( obj_expled_encoded + tail_junk, @@ -4871,6 +5328,10 @@ class TestAny(CommonMixin, TestCase): self.assertEqual(obj_decoded, obj_expled) self.assertEqual(bytes(obj_decoded), bytes(obj_expled)) self.assertEqual(bytes(obj_decoded), bytes(obj)) + self.assertIsInstance( + obj_decoded._value, + memoryview if keep_memoryview else bytes, + ) self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl) self.assertEqual(obj_decoded.expl_tlen, len(tag_expl)) @@ -4894,6 +5355,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), @@ -5034,11 +5509,11 @@ class TestChoice(CommonMixin, TestCase): base_klass = Wahl def test_schema_required(self): - with assertRaisesRegex(self, ValueError, "schema must be specified"): + with self.assertRaisesRegex(ValueError, "schema must be specified"): Choice() def test_impl_forbidden(self): - with assertRaisesRegex(self, ValueError, "no implicit tag allowed"): + with self.assertRaisesRegex(ValueError, "no implicit tag allowed"): Choice(impl=b"whatever") def test_invalid_value_type(self): @@ -5071,6 +5546,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) @@ -5203,6 +5680,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 @@ -5216,9 +5694,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) @@ -5266,6 +5748,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): @@ -5486,7 +5984,7 @@ def sequences_strategy(draw, seq_klass): return seq_outer, expect_outers -class SeqMixing(object): +class SeqMixin(object): def test_invalid_value_type(self): with self.assertRaises(InvalidValueType) as err: self.base_klass(123) @@ -5546,6 +6044,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) @@ -5649,8 +6149,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) @@ -5724,6 +6225,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) @@ -5732,6 +6234,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) @@ -5787,6 +6296,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}), @@ -5799,6 +6323,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) @@ -5851,29 +6376,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"): + with self.assertRaisesRegex(DecodeError, "DEFAULT value met"): seq_with_default.decode(seq_encoded) + with self.assertRaisesRegex(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) @@ -5883,13 +6418,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), ))] @@ -5908,6 +6457,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): @@ -5934,6 +6485,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) @@ -5944,7 +6498,7 @@ class SeqMixing(object): self.assertTrue(decoded.bered) -class TestSequence(SeqMixing, CommonMixin, TestCase): +class TestSequence(SeqMixin, CommonMixin, TestCase): base_klass = Sequence @given( @@ -5962,7 +6516,7 @@ class TestSequence(SeqMixing, CommonMixin, TestCase): len_encode(len(int_encoded + junk)), int_encoded + junk, )) - with assertRaisesRegex(self, DecodeError, "remaining"): + with self.assertRaisesRegex(DecodeError, "remaining"): Seq().decode(junked) @given(sets(text_letters(), min_size=2)) @@ -5990,49 +6544,51 @@ class TestSequence(SeqMixing, CommonMixin, TestCase): self.assertEqual(seq["ok"], True) -class TestSet(SeqMixing, CommonMixin, TestCase): +class TestSet(SeqMixin, CommonMixin, TestCase): base_klass = Set @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"): + with self.assertRaisesRegex(DecodeError, "unordered SET"): seq.decode(seq_encoded) for ctx in ({"bered": True}, {"allow_unordered_set": True}): seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx) @@ -6041,11 +6597,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): @@ -6091,7 +6659,7 @@ def seqof_values_strategy(draw, schema=None, do_expl=False): ) -class SeqOfMixing(object): +class SeqOfMixin(object): def test_invalid_value_type(self): with self.assertRaises(InvalidValueType) as err: self.base_klass(123) @@ -6105,10 +6673,10 @@ class SeqOfMixing(object): repr(err.exception) def test_schema_required(self): - with assertRaisesRegex(self, ValueError, "schema must be specified"): + with self.assertRaisesRegex(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() @@ -6157,6 +6725,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: @@ -6191,15 +6761,19 @@ class SeqOfMixing(object): schema = Boolean() bound_min = d.draw(integers(min_value=1, max_value=1 << 7)) bound_max = d.draw(integers(min_value=bound_min, max_value=1 << 7)) - value = [Boolean(False)] * d.draw(integers(max_value=bound_min - 1)) + value = [Boolean(False)] * d.draw(integers(min_value=0, max_value=bound_min - 1)) with self.assertRaises(BoundsError) as err: SeqOf(value=value, bounds=(bound_min, bound_max)) repr(err.exception) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: SeqOf(bounds=(bound_min, bound_max)).decode( SeqOf(value).encode() ) repr(err.exception) + with self.assertRaisesRegex(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, @@ -6207,11 +6781,15 @@ class SeqOfMixing(object): with self.assertRaises(BoundsError) as err: SeqOf(value=value, bounds=(bound_min, bound_max)) repr(err.exception) - with assertRaisesRegex(self, DecodeError, "bounds") as err: + with self.assertRaisesRegex(DecodeError, "bounds") as err: SeqOf(bounds=(bound_min, bound_max)).decode( SeqOf(value).encode() ) repr(err.exception) + with self.assertRaisesRegex(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): @@ -6400,8 +6978,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): @@ -6417,6 +6996,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) @@ -6483,6 +7069,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), @@ -6528,7 +7129,7 @@ class SeqOfMixing(object): self.assertTrue(decoded.bered) -class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase): +class TestSequenceOf(SeqOfMixin, CommonMixin, TestCase): class SeqOf(SequenceOf): schema = "whatever" base_klass = SeqOf @@ -6537,8 +7138,82 @@ 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(range(10))) + with self.assertRaisesRegex(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 range(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 range(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 range(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 TestSetOf(SeqOfMixin, CommonMixin, TestCase): class SeqOf(SetOf): schema = "whatever" base_klass = SeqOf @@ -6584,7 +7259,7 @@ class TestSetOf(SeqOfMixing, CommonMixin, TestCase): class Seq(SetOf): schema = OctetString() seq = Seq() - with assertRaisesRegex(self, DecodeError, "unordered SET OF"): + with self.assertRaisesRegex(DecodeError, "unordered SET OF"): seq.decode(seq_encoded) for ctx in ({"bered": True}, {"allow_unordered_set": True}): @@ -6742,10 +7417,10 @@ class TestGoMarshalVectors(TestCase): seq["erste"] = PrintableString("test") self.assertSequenceEqual(seq.encode(), hexdec("3006130474657374")) # Asterisk is actually not allowable - PrintableString._allowable_chars |= set(b"*") + pyderasn.PRINTABLE_ALLOWABLE_CHARS |= set(b"*") seq["erste"] = PrintableString("test*") self.assertSequenceEqual(seq.encode(), hexdec("30071305746573742a")) - PrintableString._allowable_chars -= set(b"*") + pyderasn.PRINTABLE_ALLOWABLE_CHARS -= set(b"*") class Seq(Sequence): schema = ( @@ -6791,7 +7466,11 @@ class TestPP(TestCase): def test_oid_printing(self, d): oids = { str(ObjectIdentifier(k)): v * 2 - for k, v in d.draw(dictionaries(oid_strategy(), text_letters())).items() + for k, v in d.draw(dictionaries( + oid_strategy(), + text_letters(), + min_size=1, + )).items() } chosen = d.draw(sampled_from(sorted(oids))) chosen_id = oids[chosen] @@ -7048,6 +7727,7 @@ class TestDefinesByPath(TestCase): def test_remaining_data(self): oid = ObjectIdentifier("1.2.3") + class Seq(Sequence): schema = ( ("oid", ObjectIdentifier(defines=((("tgt",), { @@ -7060,11 +7740,12 @@ class TestDefinesByPath(TestCase): ("oid", oid), ("tgt", OctetString(Integer(123).encode() + b"junk")), )) - with assertRaisesRegex(self, DecodeError, "remaining data"): + with self.assertRaisesRegex(DecodeError, "remaining data"): Seq().decode(seq.encode()) def test_remaining_data_seqof(self): oid = ObjectIdentifier("1.2.3") + class SeqOf(SetOf): schema = OctetString() @@ -7080,7 +7761,7 @@ class TestDefinesByPath(TestCase): ("oid", oid), ("tgt", SeqOf([OctetString(Integer(123).encode() + b"junk")])), )) - with assertRaisesRegex(self, DecodeError, "remaining data"): + with self.assertRaisesRegex(DecodeError, "remaining data"): Seq().decode(seq.encode()) @@ -7134,7 +7815,7 @@ class TestStrictDefaultExistence(TestCase): raw = seq.encode() chosen_choice = "int%d" % chosen seq.specs[chosen_choice] = seq.specs[chosen_choice](default=123) - with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): + with self.assertRaisesRegex(DecodeError, "DEFAULT value met"): seq.decode(raw) decoded, _ = seq.decode(raw, ctx={"allow_default_values": True}) self.assertTrue(decoded.ber_encoded) @@ -7151,11 +7832,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", @@ -7163,6 +7846,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("43054A6F6E6573"), ) + + def test_3(self): self.assertSequenceEqual( Any( VisibleString( @@ -7173,6 +7858,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("A20743054A6F6E6573"), ) + + def test_4(self): self.assertSequenceEqual( OctetString( VisibleString( @@ -7183,6 +7870,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("670743054A6F6E6573"), ) + + def test_5(self): self.assertSequenceEqual( VisibleString("Jones", impl=tag_ctxp(2)).encode(), hexdec("82054A6F6E6573"), @@ -7194,7 +7883,7 @@ class TestExplOOB(TestCase): expl = tag_ctxc(123) raw = Integer(123).encode() + Integer(234).encode() raw = b"".join((expl, len_encode(len(raw)), raw)) - with assertRaisesRegex(self, DecodeError, "explicit tag out-of-bound"): + with self.assertRaisesRegex(DecodeError, "explicit tag out-of-bound"): Integer(expl=expl).decode(raw) Integer(expl=expl).decode(raw, ctx={"allow_expl_oob": True}) @@ -7205,7 +7894,53 @@ class TestPickleDifferentVersion(TestCase): import pyderasn version_orig = pyderasn.__version__ pyderasn.__version__ += "different" - with assertRaisesRegex(self, ValueError, "different PyDERASN version"): + with self.assertRaisesRegex(ValueError, "different PyDERASN version"): 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"), + )