X-Git-Url: http://www.git.cypherpunks.ru/?p=pyderasn.git;a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=73eea21c6151a8ac3979f103eb42c3b7632cf0b5;hp=5ba9adf75a91928a109d3bf33b01b1a3f21b94a0;hb=3eb38566e734d6f5b274f4bb99fadb55f99ff2c7;hpb=90725be7bf59abd3ad67d62d28e9717826e63958 diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 5ba9adf..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 @@ -20,7 +20,10 @@ from copy import deepcopy from datetime import datetime from datetime import timedelta from importlib import import_module +from io import BytesIO +from operator import attrgetter from os import environ +from os import urandom from random import random from string import ascii_letters from string import digits @@ -30,6 +33,7 @@ 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 @@ -58,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 @@ -72,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 @@ -270,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) @@ -345,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) @@ -363,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 @@ -410,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) @@ -417,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) @@ -494,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(), @@ -503,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), @@ -575,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( @@ -590,11 +625,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( @@ -631,6 +674,21 @@ class TestBoolean(CommonMixin, TestCase): tail_junk, ) + evgens = list(obj_expled.decode_evgen( + hexdec(obj_expled_hex_encoded) + tail_junk, + offset=offset, + decode_path=decode_path, + ctx=ctx_copied, + )) + self.assertEqual(len(evgens), 1) + _decode_path, obj, tail = evgens[0] + self.assertSequenceEqual(tail, tail_junk) + self.assertEqual(_decode_path, decode_path) + self.assertEqual(obj, obj_decoded) + self.assertEqual(obj.expl_offset, offset) + repr(obj) + list(obj.pps()) + @given(integers(min_value=2)) def test_invalid_len(self, l): with self.assertRaises(InvalidLength): @@ -648,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) @@ -717,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( @@ -828,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) @@ -836,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) @@ -890,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) @@ -898,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): @@ -1073,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( @@ -1088,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, @@ -1129,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), @@ -1304,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) @@ -1467,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 @@ -1481,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, @@ -1526,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): @@ -1572,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( @@ -1588,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) + @@ -1598,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: @@ -1605,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 = ( @@ -1653,6 +1778,16 @@ class TestBitString(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) + evgens = list(BitString(impl=tag_encode(impl)).decode_evgen( + encoded, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(chunks_len_expected) + 1) + for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens): + self.assertGreater(len(dp), len(decode_path)) + self.assertEqual(obj.vlen, chunk_len_expected) + @given( integers(min_value=0), decode_path_strat, @@ -1788,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): @@ -1849,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) @@ -1895,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)) @@ -1904,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): @@ -2064,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( @@ -2079,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, @@ -2120,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( @@ -2135,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) + @@ -2145,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 + @@ -2189,6 +2382,16 @@ class TestOctetString(CommonMixin, TestCase): list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) + evgens = list(OctetString(impl=tag_encode(impl)).decode_evgen( + encoded, + decode_path=decode_path, + ctx={"bered": True}, + )) + self.assertEqual(len(evgens), len(chunks_len_expected) + 1) + for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens): + self.assertGreater(len(dp), len(decode_path)) + self.assertEqual(obj.vlen, chunk_len_expected) + @given( integers(min_value=0), decode_path_strat, @@ -2244,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): @@ -2276,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) @@ -2382,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) @@ -2392,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, @@ -2431,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): @@ -2498,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) @@ -2506,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) @@ -2719,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( @@ -2734,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, @@ -2775,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), @@ -2811,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], @@ -2976,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 = ( @@ -3092,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 @@ -3106,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) @@ -3147,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): @@ -3188,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): @@ -3208,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) @@ -3257,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)) @@ -3266,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): @@ -3428,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, @@ -3440,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) @@ -3483,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( @@ -3495,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): @@ -3510,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( @@ -3533,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, @@ -3547,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( @@ -3581,7 +3928,7 @@ class TestPrintableString( for prop in kwargs.keys(): self.assertFalse(getattr(obj, prop)) s += c - with assertRaisesRegex(self, DecodeError, "non-printable"): + with assertRaisesRegex(self, DecodeError, "alphabet value"): self.base_klass(s) self.base_klass(s, **kwargs) klass = self.base_klass(**kwargs) @@ -3620,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, @@ -3638,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"") @@ -3674,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, @@ -3751,6 +4145,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, @@ -3909,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) @@ -3956,6 +4353,7 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): omit_ms = False min_datetime = datetime(1900, 1, 1) max_datetime = datetime(9999, 12, 31) + evgen_mode_skip_value = False def additional_symmetric_check(self, value, obj_encoded): if value.microsecond > 0: @@ -4101,8 +4499,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) @@ -4334,12 +4737,17 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): 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 @@ -4666,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))) @@ -4699,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) @@ -4709,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) @@ -4733,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) @@ -4797,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): @@ -4844,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) @@ -4853,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) @@ -4897,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), @@ -5074,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) @@ -5206,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 @@ -5219,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) @@ -5269,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): @@ -5549,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) @@ -5652,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) @@ -5727,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) @@ -5735,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) @@ -5790,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}), @@ -5802,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) @@ -5854,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) @@ -5886,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), ))] @@ -5911,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): @@ -5937,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) @@ -5999,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}): @@ -6044,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): @@ -6111,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() @@ -6160,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: @@ -6203,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, @@ -6215,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): @@ -6403,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): @@ -6420,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) @@ -6486,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), @@ -6540,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): @@ -7051,6 +7692,7 @@ class TestDefinesByPath(TestCase): def test_remaining_data(self): oid = ObjectIdentifier("1.2.3") + class Seq(Sequence): schema = ( ("oid", ObjectIdentifier(defines=((("tgt",), { @@ -7068,6 +7710,7 @@ class TestDefinesByPath(TestCase): def test_remaining_data_seqof(self): oid = ObjectIdentifier("1.2.3") + class SeqOf(SetOf): schema = OctetString() @@ -7154,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", @@ -7166,6 +7811,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("43054A6F6E6573"), ) + + def test_3(self): self.assertSequenceEqual( Any( VisibleString( @@ -7176,6 +7823,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("A20743054A6F6E6573"), ) + + def test_4(self): self.assertSequenceEqual( OctetString( VisibleString( @@ -7186,6 +7835,8 @@ class TestX690PrefixedType(TestCase): ).encode(), hexdec("670743054A6F6E6573"), ) + + def test_5(self): self.assertSequenceEqual( VisibleString("Jones", impl=tag_ctxp(2)).encode(), hexdec("82054A6F6E6573"), @@ -7212,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"), + )