X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=tests%2Ftest_pyderasn.py;h=1cce9ed5a3988a92b92248e987d2e012366bf798;hb=593509516d4ba74685772f920b19001ac43d1219;hp=174abb6d7beaaca1871cc4fadb84c208d0e8cc37;hpb=40b9fcc9381c4456152deb9a448b4e5fd014b7e7;p=pyderasn.git diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 174abb6..1cce9ed 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -1,11 +1,10 @@ # coding: utf-8 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures -# Copyright (C) 2017-2019 Sergey Matveev +# Copyright (C) 2017-2020 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 -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# published by the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,12 +15,19 @@ # License along with this program. If not, see # . +from copy import copy from copy import deepcopy from datetime import datetime +from datetime import timedelta +from importlib import import_module +from os import environ +from random import random from string import ascii_letters from string import digits from string import printable from string import whitespace +from time import mktime +from time import time from unittest import TestCase from hypothesis import assume @@ -52,6 +58,9 @@ 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 pyderasn import _pp from pyderasn import abs_decode_path @@ -66,6 +75,7 @@ from pyderasn import DecodePathDefBy from pyderasn import Enumerated from pyderasn import EOC from pyderasn import EOC_LEN +from pyderasn import ExceedingData from pyderasn import GeneralizedTime from pyderasn import GeneralString from pyderasn import GraphicString @@ -117,8 +127,10 @@ from pyderasn import VideotexString from pyderasn import VisibleString +max_examples = environ.get("MAX_EXAMPLES") settings.register_profile("local", settings( deadline=5000, + **({"max_examples": int(max_examples)} if max_examples else {}) )) settings.load_profile("local") LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4 @@ -134,6 +146,26 @@ decode_path_strat = lists(integers(), max_size=3).map( lambda decode_path: tuple(str(dp) for dp in decode_path) ) ctx_dummy = dictionaries(integers(), integers(), min_size=2, max_size=4).example() +copy_funcs = ( + copy, + lambda obj: pickle_loads(pickle_dumps(obj, pickle_proto)), +) +self_module = import_module(__name__) + + +def register_class(klass): + klassname = klass.__name__ + str(time()).replace(".", "") + klass.__name__ = klassname + klass.__qualname__ = klassname + setattr(self_module, klassname, klass) + + +def assert_exceeding_data(self, call, junk): + if len(junk) <= 0: + return + with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)) as err: + call() + repr(err) class TestHex(TestCase): @@ -314,7 +346,7 @@ class CommonMixin(object): self.assertSequenceEqual(obj.impl, impl_tag) self.assertFalse(obj.expled) - @given(binary()) + @given(binary(min_size=1)) def test_expl_inherited(self, expl_tag): class Inherited(self.base_klass): expl = expl_tag @@ -385,7 +417,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) @@ -450,8 +482,9 @@ class TestBoolean(CommonMixin, TestCase): def test_copy(self, values): for klass in (Boolean, BooleanInherited): obj = klass(*values) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) @given( booleans(), @@ -562,10 +595,10 @@ class TestBoolean(CommonMixin, TestCase): 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_hex_encoded = obj_expled.hexencode() ctx_copied = deepcopy(ctx_dummy) - obj_decoded, tail = obj_expled.decode( - obj_expled_encoded + tail_junk, + obj_decoded, tail = obj_expled.hexdecode( + obj_expled_hex_encoded + hexenc(tail_junk), offset=offset, ctx=ctx_copied, ) @@ -578,7 +611,7 @@ class TestBoolean(CommonMixin, TestCase): self.assertNotEqual(obj_decoded, obj) self.assertEqual(bool(obj_decoded), bool(obj_expled)) self.assertEqual(bool(obj_decoded), bool(obj)) - self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded) + self.assertSequenceEqual(obj_decoded.hexencode(), obj_expled_hex_encoded) self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl) self.assertEqual(obj_decoded.expl_tlen, len(tag_expl)) self.assertEqual( @@ -592,6 +625,11 @@ class TestBoolean(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.hexdecod(obj_expled_hex_encoded + hexenc(tail_junk)), + tail_junk, + ) @given(integers(min_value=2)) def test_invalid_len(self, l): @@ -622,6 +660,10 @@ class TestBoolean(CommonMixin, TestCase): self.assertTrue(obj.ber_encoded) self.assertFalse(obj.lenindef) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertFalse(obj.lenindef) + self.assertTrue(obj.bered) @given( integers(min_value=1).map(tag_ctxc), @@ -641,6 +683,11 @@ class TestBoolean(CommonMixin, TestCase): self.assertFalse(obj.lenindef) self.assertFalse(obj.ber_encoded) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.expl_lenindef) + self.assertFalse(obj.lenindef) + self.assertFalse(obj.ber_encoded) + self.assertTrue(obj.bered) self.assertSequenceEqual(tail, junk) repr(obj) list(obj.pps()) @@ -789,7 +836,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) @@ -934,12 +981,13 @@ class TestInteger(CommonMixin, TestCase): def test_copy(self, values): for klass in (Integer, IntegerInherited): obj = klass(*values) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj.specs, obj_copied.specs) - self.assertEqual(obj._bound_min, obj_copied._bound_min) - self.assertEqual(obj._bound_max, obj_copied._bound_max) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj.specs, obj_copied.specs) + self.assertEqual(obj._bound_min, obj_copied._bound_min) + self.assertEqual(obj._bound_max, obj_copied._bound_max) + self.assertEqual(obj._value, obj_copied._value) @given( integers(), @@ -1075,6 +1123,11 @@ class TestInteger(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) def test_go_vectors_valid(self): for data, expect in (( @@ -1129,7 +1182,7 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl= def _value(value_required): if not value_required and draw(booleans()): - return + return None generation_choice = 0 if value_required: generation_choice = draw(sampled_from((1, 2, 3))) @@ -1138,9 +1191,9 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl= sampled_from(("0", "1")), max_size=len(schema), ))) - elif generation_choice == 2 or draw(booleans()): + if generation_choice == 2 or draw(booleans()): return draw(binary(max_size=len(schema) // 8)) - elif generation_choice == 3 or draw(booleans()): + if generation_choice == 3 or draw(booleans()): return tuple(draw(lists(sampled_from([name for name, _ in schema])))) return None value = _value(value_required) @@ -1334,6 +1387,7 @@ class TestBitString(CommonMixin, TestCase): class BS(klass): schema = _schema + register_class(BS) obj = BS( value=value, impl=impl, @@ -1342,10 +1396,11 @@ class TestBitString(CommonMixin, TestCase): optional=optional or False, _decoded=_decoded, ) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj.specs, obj_copied.specs) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj.specs, obj_copied.specs) + self.assertEqual(obj._value, obj_copied._value) @given( binary(), @@ -1465,6 +1520,11 @@ class TestBitString(CommonMixin, TestCase): self.assertSetEqual(set(value), set(obj_decoded.named)) for name in value: obj_decoded[name] + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given(integers(min_value=1, max_value=255)) def test_bad_zero_value(self, pad_size): @@ -1584,7 +1644,14 @@ class TestBitString(CommonMixin, TestCase): self.assertTrue(obj.ber_encoded) self.assertEqual(obj.lenindef, lenindef_expected) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertEqual(obj.lenindef, lenindef_expected) + self.assertTrue(obj.bered) self.assertEqual(len(encoded), obj.tlvlen) + repr(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given( integers(min_value=0), @@ -1716,6 +1783,10 @@ class TestBitString(CommonMixin, TestCase): self.assertTrue(obj.ber_encoded) self.assertTrue(obj.lenindef) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) @composite @@ -1909,11 +1980,12 @@ class TestOctetString(CommonMixin, TestCase): def test_copy(self, values): for klass in (OctetString, OctetStringInherited): obj = klass(*values) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj._bound_min, obj_copied._bound_min) - self.assertEqual(obj._bound_max, obj_copied._bound_max) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj._bound_min, obj_copied._bound_min) + self.assertEqual(obj._bound_max, obj_copied._bound_max) + self.assertEqual(obj._value, obj_copied._value) @given( binary(), @@ -2042,6 +2114,11 @@ class TestOctetString(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given( integers(min_value=1, max_value=30), @@ -2103,7 +2180,14 @@ class TestOctetString(CommonMixin, TestCase): self.assertTrue(obj.ber_encoded) self.assertEqual(obj.lenindef, lenindef_expected) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertEqual(obj.lenindef, lenindef_expected) + self.assertTrue(obj.bered) self.assertEqual(len(encoded), obj.tlvlen) + repr(obj) + list(obj.pps()) + pprint(obj, big_blobs=True, with_decode_path=True) @given( integers(min_value=0), @@ -2192,7 +2276,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) @@ -2239,8 +2323,9 @@ class TestNull(CommonMixin, TestCase): optional=optional or False, _decoded=_decoded, ) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) @given(integers(min_value=1).map(tag_encode)) def test_stripped(self, tag_impl): @@ -2340,6 +2425,11 @@ class TestNull(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given(integers(min_value=1)) def test_invalid_len(self, l): @@ -2410,12 +2500,13 @@ class TestObjectIdentifier(CommonMixin, TestCase): repr(err.exception) obj = ObjectIdentifier(value) self.assertTrue(obj.ready) + self.assertFalse(obj.ber_encoded) repr(obj) list(obj.pps()) 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) @@ -2509,9 +2600,10 @@ class TestObjectIdentifier(CommonMixin, TestCase): optional=optional, _decoded=_decoded, ) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj._value, obj_copied._value) @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given( @@ -2677,6 +2769,11 @@ class TestObjectIdentifier(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given( oid_strategy().map(ObjectIdentifier), @@ -2720,18 +2817,44 @@ class TestObjectIdentifier(CommonMixin, TestCase): ObjectIdentifier((2, 999, 3)), ) - @given(data_strategy()) - def test_nonnormalized_first_arc(self, d): + def test_nonnormalized_first_arc(self): tampered = ( ObjectIdentifier.tag_default + len_encode(2) + b'\x80' + ObjectIdentifier((1, 0)).encode()[-1:] ) - ObjectIdentifier().decode(tampered, ctx={"bered": True}) + obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True}) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"): ObjectIdentifier().decode(tampered) + @given(data_strategy()) + def test_negative_arcs(self, d): + oid = list(d.draw(oid_strategy())) + if len(oid) == 2: + return + idx = d.draw(integers(min_value=3, max_value=len(oid))) + oid[idx - 1] *= -1 + if oid[idx - 1] == 0: + oid[idx - 1] = -1 + with self.assertRaises(InvalidOID): + ObjectIdentifier(tuple(oid)) + with self.assertRaises(InvalidOID): + ObjectIdentifier(".".join(str(i) for i in oid)) + + @given(data_strategy()) + def test_plused_arcs(self, d): + oid = [str(arc) for arc in d.draw(oid_strategy())] + idx = d.draw(integers(min_value=0, max_value=len(oid))) + oid[idx - 1] = "+" + oid[idx - 1] + with self.assertRaises(InvalidOID): + ObjectIdentifier(".".join(str(i) for i in oid)) + @given(data_strategy()) def test_nonnormalized_arcs(self, d): arcs = d.draw(lists( @@ -2740,8 +2863,8 @@ class TestObjectIdentifier(CommonMixin, TestCase): max_size=5, )) dered = ObjectIdentifier((1, 0) + tuple(arcs)).encode() - _, tlen, lv = tag_strip(dered) - _, llen, v = len_decode(lv) + _, _, lv = tag_strip(dered) + _, _, v = len_decode(lv) v_no_first_arc = v[1:] idx_for_tamper = d.draw(integers( min_value=0, @@ -2756,7 +2879,12 @@ class TestObjectIdentifier(CommonMixin, TestCase): len_encode(len(tampered)) + tampered ) - ObjectIdentifier().decode(tampered, ctx={"bered": True}) + obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True}) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"): ObjectIdentifier().decode(tampered) @@ -2848,7 +2976,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 = ( @@ -2940,6 +3068,7 @@ class TestEnumerated(CommonMixin, TestCase): class E(Enumerated): schema = schema_input + register_class(E) obj = E( value=value, impl=impl, @@ -2948,9 +3077,10 @@ class TestEnumerated(CommonMixin, TestCase): optional=optional, _decoded=_decoded, ) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj.specs, obj_copied.specs) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj.specs, obj_copied.specs) @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given(data_strategy()) @@ -3011,6 +3141,11 @@ class TestEnumerated(CommonMixin, TestCase): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @composite @@ -3209,11 +3344,12 @@ class StringMixin(object): def test_copy(self, d): values = d.draw(string_values_strategy(self.text_alphabet())) obj = self.base_klass(*values) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj._bound_min, obj_copied._bound_min) - self.assertEqual(obj._bound_max, obj_copied._bound_max) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj._bound_min, obj_copied._bound_min) + self.assertEqual(obj._bound_max, obj_copied._bound_max) + self.assertEqual(obj._value, obj_copied._value) @given(data_strategy()) def test_stripped(self, d): @@ -3341,6 +3477,11 @@ class StringMixin(object): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) class TestUTF8String(StringMixin, CommonMixin, TestCase): @@ -3429,6 +3570,29 @@ class TestPrintableString( self.assertEqual(err.exception.offset, offset) self.assertEqual(err.exception.decode_path, decode_path) + def test_allowable_invalid_chars(self): + for c, kwargs in ( + ("*", {"allow_asterisk": True}), + ("&", {"allow_ampersand": True}), + ("&*", {"allow_asterisk": True, "allow_ampersand": True}), + ): + s = "hello invalid" + obj = self.base_klass(s) + for prop in kwargs.keys(): + self.assertFalse(getattr(obj, prop)) + s += c + with assertRaisesRegex(self, DecodeError, "non-printable"): + self.base_klass(s) + self.base_klass(s, **kwargs) + klass = self.base_klass(**kwargs) + obj = klass(s) + for prop in kwargs.keys(): + self.assertTrue(getattr(obj, prop)) + obj = copy(obj) + obj(s) + for prop in kwargs.keys(): + self.assertTrue(getattr(obj, prop)) + class TestTeletexString( UnicodeDecodeErrorMixin, @@ -3491,6 +3655,10 @@ class TestVisibleString( self.assertTrue(obj.ber_encoded) self.assertFalse(obj.lenindef) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertFalse(obj.lenindef) + self.assertTrue(obj.bered) obj, tail = VisibleString().decode( hexdec("3A8004034A6F6E040265730000"), @@ -3501,6 +3669,10 @@ class TestVisibleString( self.assertTrue(obj.ber_encoded) self.assertTrue(obj.lenindef) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.lenindef) + self.assertTrue(obj.bered) class TestGeneralString( @@ -3579,7 +3751,10 @@ class TimeMixin(object): with self.assertRaises(ObjNotReady) as err: obj.encode() repr(err.exception) - value = d.draw(datetimes(min_value=self.min_datetime)) + value = d.draw(datetimes( + min_value=self.min_datetime, + max_value=self.max_datetime, + )) obj = self.base_klass(value) self.assertTrue(obj.ready) repr(obj) @@ -3681,9 +3856,10 @@ class TimeMixin(object): max_datetime=self.max_datetime, )) obj = self.base_klass(*values) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj._value, obj_copied._value) @given(data_strategy()) def test_stripped(self, d): @@ -3733,6 +3909,7 @@ class TimeMixin(object): pprint(obj, big_blobs=True, with_decode_path=True) self.assertFalse(obj.expled) obj_encoded = obj.encode() + self.additional_symmetric_check(value, obj_encoded) obj_expled = obj(value, expl=tag_expl) self.assertTrue(obj_expled.expled) repr(obj_expled) @@ -3767,6 +3944,11 @@ class TimeMixin(object): offset + obj_decoded.expl_tlen + obj_decoded.expl_llen, ) self.assertEqual(obj_decoded.expl_offset, offset) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): @@ -3775,6 +3957,32 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): min_datetime = datetime(1900, 1, 1) max_datetime = datetime(9999, 12, 31) + def additional_symmetric_check(self, value, obj_encoded): + if value.microsecond > 0: + self.assertFalse(obj_encoded.endswith(b"0Z")) + + def test_repr_not_ready(self): + unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime()) + repr(GeneralizedTime()) + + def test_x690_vector_valid(self): + for data in (( + b"19920521000000Z", + b"19920622123421Z", + b"19920722132100.3Z", + )): + GeneralizedTime(data) + + def test_x690_vector_invalid(self): + for data in (( + b"19920520240000Z", + b"19920622123421.0Z", + b"19920722132100.30Z", + )): + with self.assertRaises(DecodeError) as err: + GeneralizedTime(data) + repr(err.exception) + def test_go_vectors_invalid(self): for data in (( b"20100102030405", @@ -3809,6 +4017,239 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): datetime(2010, 1, 2, 3, 4, 5, 0), ) + def test_go_vectors_valid_ber(self): + for data in (( + b"20100102030405+0607", + b"20100102030405-0607", + )): + GeneralizedTime(data, ctx={"bered": True}) + + def test_utc_offsets(self): + """Some know equal UTC offsets + """ + dts = [ + GeneralizedTime(data.encode("ascii"), ctx={"bered": True}) + for data in ( + "200101011830Z", + "200101012230+04", + "200101011130-0700", + "200101011500-03:30", + ) + ] + self.assertEqual(dts[0], dts[1]) + self.assertEqual(dts[0], dts[2]) + self.assertEqual(dts[0], dts[3]) + + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(data_strategy()) + def test_valid_ber(self, d): + min_year = 1901 if PY2 else 2 + year = d.draw(integers(min_value=min_year, max_value=9999)) + month = d.draw(integers(min_value=1, max_value=12)) + day = d.draw(integers(min_value=1, max_value=28)) + hours = d.draw(integers(min_value=0, max_value=23)) + data = "%04d%02d%02d%02d" % (year, month, day, hours) + dt = datetime(year, month, day, hours) + fractions_sign = d.draw(sampled_from(" ,.")) + fractions = None + if fractions_sign != " ": + fractions = random() + if d.draw(booleans()): + minutes = d.draw(integers(min_value=0, max_value=59)) + data += "%02d" % minutes + dt += timedelta(seconds=60 * minutes) + if d.draw(booleans()): + seconds = d.draw(integers(min_value=0, max_value=59)) + data += "%02d" % seconds + dt += timedelta(seconds=seconds) + if fractions is not None: + dt += timedelta(microseconds=10**6 * fractions) + elif fractions is not None: + dt += timedelta(seconds=60 * fractions) + elif fractions is not None: + dt += timedelta(seconds=3600 * fractions) + if fractions is not None: + data += fractions_sign + str(fractions)[2:] + if d.draw(booleans()): + data += "Z" + elif d.draw(booleans()): + offset_hour = d.draw(integers(min_value=0, max_value=13)) + sign = 1 + if d.draw(booleans()): + data += "-" + sign = -1 + else: + data += "+" + dt -= timedelta(seconds=sign * 3600 * offset_hour) + data += "%02d" % offset_hour + minutes_separator = d.draw(sampled_from((None, "", ":"))) + if minutes_separator is not None: + offset_minute = d.draw(integers(min_value=0, max_value=59)) + dt -= timedelta(seconds=sign * 60 * offset_minute) + data += "%s%02d" % (minutes_separator, offset_minute) + data = data.encode("ascii") + data_der = GeneralizedTime.tag_default + len_encode(len(data)) + data + try: + GeneralizedTime().decod(data_der) + except DecodeError: + dered = False + else: + dered = True + obj = GeneralizedTime().decod(data_der, ctx={"bered": True}) + if dt.year > 1970: + self.assertEqual( + mktime(obj.todatetime().timetuple()), + mktime(dt.timetuple()), + ) + elif not PY2: + self.assertEqual(obj.todatetime().timestamp(), dt.timestamp()) + self.assertEqual(obj.ber_encoded, not dered) + self.assertEqual(obj.bered, not dered) + self.assertEqual(obj.ber_raw, None if dered else data) + self.assertEqual(obj.encode() == data_der, dered) + repr(obj) + bytes(obj) + str(obj) + + def test_invalid_ber(self): + for data in (( + # "00010203040506.07", + "-0010203040506.07", + "0001-203040506.07", + "000102-3040506.07", + "00010203-40506.07", + "0001020304-506.07", + "000102030405-6.07", + "00010203040506.-7", + "+0010203040506.07", + "0001+203040506.07", + "000102+3040506.07", + "00010203+40506.07", + "0001020304+506.07", + "000102030405+6.07", + "00010203040506.+7", + " 0010203040506.07", + "0001 203040506.07", + "000102 3040506.07", + "00010203 40506.07", + "0001020304 506.07", + "000102030405 6.07", + "00010203040506. 7", + "001 0203040506.07", + "00012 03040506.07", + "0001023 040506.07", + "000102034 0506.07", + "00010203045 06.07", + "0001020304056 .07", + "00010203040506.7 ", + "00010203040506.", + "0001020304050607", + + "-0010203040506", + "0001-203040506", + "000102-3040506", + "00010203-40506", + "0001020304-506", + "000102030405-6", + "0001+203040506", + "000102+3040506", + "00010203+40506", + "0001020304+506", + "000102030405+6", + " 0010203040506", + "0001 203040506", + "000102 3040506", + "00010203 40506", + "0001020304 506", + "000102030405 6", + "001 0203040506", + "00012 03040506", + "0001023 040506", + "000102034 0506", + "00010203045 06", + "0001020304056 ", + + "-00102030405.07", + "0001-2030405.07", + "000102-30405.07", + "00010203-405.07", + "0001020304-5.07", + "000102030405.-7", + "+00102030405.07", + "0001+2030405.07", + "00010203+405.07", + "0001020304+5.07", + "000102030405.+7", + " 00102030405.07", + "0001 2030405.07", + "000102 30405.07", + "00010203 405.07", + "0001020304 5.07", + "000102030405. 7", + "001 02030405.07", + "00012 030405.07", + "0001023 0405.07", + "000102034 05.07", + "00010203045 .07", + "000102030405.7 ", + "000102030405.", + + "-001020304.07", + "0001-20304.07", + "000102-304.07", + "00010203-4.07", + "0001020304.-7", + "+001020304.07", + "0001+20304.07", + "00010203+4.07", + "0001020304.+7", + " 001020304.07", + "0001 20304.07", + "000102 304.07", + "00010203 4.07", + "0001020304. 7", + "001 020304.07", + "00012 0304.07", + "0001023 04.07", + "000102034 .07", + "0001020304.7 ", + "0001020304.", + + "00010203", + "00010203040506Y", + "0001010100+0001", + "0001010100+00:01", + "0001010100+01", + + "00010203040506.07+15", + "00010203040506.07-15", + "00010203040506.07+14:60", + "00010203040506.07+1460", + "00010203040506.07-1460", + "00010203040506.07+00:60", + "00010203040506.07-00:60", + + "00010203040506+15", + "00010203040506-15", + "00010203040506+14:60", + "00010203040506+1460", + "00010203040506-1460", + "00010203040506+00:60", + "00010203040506-00:60", + + "0001020304050.07", + "00010203040.07", + "000102030.07", + "0001020304050", + "00010203040", + "000102030", + )): + with self.assertRaises(DecodeError): + GeneralizedTime(data.encode("ascii"), ctx={"bered": True}) + data = data.replace(".", ",") + with self.assertRaises(DecodeError): + GeneralizedTime(data.encode("ascii"), ctx={"bered": True}) + @given( binary( min_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2, @@ -3851,6 +4292,48 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase): junk ) + def test_ns_fractions(self): + GeneralizedTime(b"20010101000000.000001Z") + with assertRaisesRegex(self, DecodeError, "only microsecond fractions"): + GeneralizedTime(b"20010101000000.0000001Z") + + def test_non_pure_integers(self): + for data in (( + # b"20000102030405Z, + b"+2000102030405Z", + b"2000+102030405Z", + b"200001+2030405Z", + b"20000102+30405Z", + b"2000010203+405Z", + b"200001020304+5Z", + b"20000102030405.+6Z", + b"20000102030405.-6Z", + b"_2000102030405Z", + b"2000_102030405Z", + b"200001_2030405Z", + b"20000102_30405Z", + b"2000010203_405Z", + b"200001020304_5Z", + b"20000102030405._6Z", + b"20000102030405.6_Z", + b" 2000102030405Z", + b"2000 102030405Z", + b"200001 2030405Z", + b"20000102 30405Z", + b"2000010203 405Z", + b"200001020304 5Z", + b"20000102030405. 6Z", + b"200 0102030405Z", + b"20001 02030405Z", + b"2000012 030405Z", + b"200001023 0405Z", + b"20000102034 05Z", + b"2000010203045 Z", + b"20000102030405.6 Z", + )): + with self.assertRaises(DecodeError): + GeneralizedTime(data) + class TestUTCTime(TimeMixin, CommonMixin, TestCase): base_klass = UTCTime @@ -3858,6 +4341,30 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): min_datetime = datetime(2000, 1, 1) max_datetime = datetime(2049, 12, 31) + def additional_symmetric_check(self, value, obj_encoded): + pass + + def test_repr_not_ready(self): + unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime()) + repr(UTCTime()) + + def test_x690_vector_valid(self): + for data in (( + b"920521000000Z", + b"920622123421Z", + b"920722132100Z", + )): + UTCTime(data) + + def test_x690_vector_invalid(self): + for data in (( + b"920520240000Z", + b"9207221321Z", + )): + with self.assertRaises(DecodeError) as err: + UTCTime(data) + repr(err.exception) + def test_go_vectors_invalid(self): for data in (( b"a10506234540Z", @@ -3901,6 +4408,229 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase): datetime(1991, 5, 6, 23, 45, 40, 0), ) + def test_non_pure_integers(self): + for data in (( + # b"000102030405Z", + b"+10102030405Z", + b"00+102030405Z", + b"0001+2030405Z", + b"000102+30405Z", + b"00010203+405Z", + b"0001020304+5Z", + b"_10102030405Z", + b"00_102030405Z", + b"0001_2030405Z", + b"000102_30405Z", + b"00010203_405Z", + b"0001020304_5Z", + b"00010203045_Z", + b" 10102030405Z", + b"00 102030405Z", + b"0001 2030405Z", + b"000102 30405Z", + b"00010203 405Z", + b"0001020304 5Z", + b"1 0102030405Z", + b"001 02030405Z", + b"00012 030405Z", + b"0001023 0405Z", + b"000102034 05Z", + b"00010203045 Z", + )): + with self.assertRaises(DecodeError): + UTCTime(data) + + def test_x680_vector_valid_ber(self): + for data, dt in (( + (b"8201021200Z", datetime(1982, 1, 2, 12)), + (b"8201020700-0500", datetime(1982, 1, 2, 12)), + (b"0101021200Z", datetime(2001, 1, 2, 12)), + (b"0101020700-0500", datetime(2001, 1, 2, 12)), + )): + data_der = UTCTime.tag_default + len_encode(len(data)) + data + obj = UTCTime().decod(data_der, ctx={"bered": True}) + self.assertEqual(obj, dt) + self.assertEqual(obj.todatetime(), dt) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + self.assertEqual(obj.ber_raw, data) + self.assertNotEqual(obj.encode(), data_der) + repr(obj) + + def test_go_vectors_valid_ber(self): + for data in (( + b"910506164540-0700", + b"910506164540+0730", + b"9105062345Z", + b"5105062345Z", + )): + data = UTCTime.tag_default + len_encode(len(data)) + data + obj = UTCTime().decod(data, ctx={"bered": True}) + self.assertTrue(obj.ber_encoded) + self.assertTrue(obj.bered) + self.assertNotEqual(obj.encode(), data) + repr(obj) + + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(data_strategy()) + def test_valid_ber(self, d): + year = d.draw(integers(min_value=0, max_value=99)) + month = d.draw(integers(min_value=1, max_value=12)) + day = d.draw(integers(min_value=1, max_value=28)) + hours = d.draw(integers(min_value=0, max_value=23)) + minute = d.draw(integers(min_value=0, max_value=59)) + data = "%02d%02d%02d%02d%02d" % (year, month, day, hours, minute) + dt = datetime( + year + (2000 if year < 50 else 1900), + month, + day, + hours, + minute, + ) + dered = False + if d.draw(booleans()): + dered = True + seconds = d.draw(integers(min_value=0, max_value=59)) + data += "%02d" % seconds + dt += timedelta(seconds=seconds) + if d.draw(booleans()): + data += "Z" + else: + dered = False + offset_hour = d.draw(integers(min_value=0, max_value=13)) + offset_minute = d.draw(integers(min_value=0, max_value=59)) + offset = timedelta(seconds=offset_hour * 3600 + offset_minute * 60) + if d.draw(booleans()): + dt += offset + data += "-" + else: + dt -= offset + data += "+" + data += "%02d%02d" % (offset_hour, offset_minute) + data = data.encode("ascii") + data_der = UTCTime.tag_default + len_encode(len(data)) + data + obj = UTCTime().decod(data_der, ctx={"bered": True}) + self.assertEqual(obj, dt) + self.assertEqual(obj.todatetime(), dt) + self.assertEqual(obj.ber_encoded, not dered) + self.assertEqual(obj.bered, not dered) + self.assertEqual(obj.ber_raw, None if dered else data) + self.assertEqual(obj.encode() == data_der, dered) + repr(obj) + bytes(obj) + str(obj) + + def test_invalid_ber(self): + for data in (( + # b"0001020304Z", + b"-101020304Z", + b"00-1020304Z", + b"0001-20304Z", + b"000102-304Z", + b"000102-104Z", + b"00000203-4Z", + b"+101020304Z", + b"00+1020304Z", + b"0001+20304Z", + b"000102+304Z", + b"000102+104Z", + b"00000203+4Z", + b" 101020304Z", + b"00 1020304Z", + b"0001 20304Z", + b"000102 304Z", + b"000102 104Z", + b"00000203 4Z", + b"1 01020304Z", + b"001 020304Z", + b"00012 0304Z", + b"0001023 04Z", + b"0001021 04Z", + b"000002034 Z", + b"0013020304Z", + b"0001000304Z", + b"0001320304Z", + b"0001022404Z", + b"0001020360Z", + b"0002300304Z", + b"0001020304", + b"0001020304T", + b"0001020304+", + b"0001020304-", + b"0001020304+0", + b"0001020304+00", + b"0001020304+000", + b"0001020304+000Z", + b"0001020304+0000Z", + b"0001020304+-101", + b"0001020304+01-1", + b"0001020304+0060", + b"0001020304+1401", + b"5001010000+0001", + b"000102030Z", + b"0001020Z", + )): + with self.assertRaises(DecodeError): + UTCTime(data, ctx={"bered": True}) + data = data[:8] + data[8+2:] + with self.assertRaises(DecodeError): + UTCTime(data, ctx={"bered": True}) + + for data in (( + # b"000102030405Z", + b"-10102030405Z", + b"00-102030405Z", + b"0001-2030405Z", + b"000102-30405Z", + b"000102-10405Z", + b"00000203-405Z", + b"0000020304-5Z", + b"+10102030405Z", + b"00+102030405Z", + b"0001+2030405Z", + b"000102+30405Z", + b"000102+10405Z", + b"00000203+405Z", + b"0000020304+5Z", + b" 10102030405Z", + b"00 102030405Z", + b"0001 2030405Z", + b"000102 30405Z", + b"000102 10405Z", + b"00000203 405Z", + b"0000020304 5Z", + b"1 0102030405Z", + b"001 02030405Z", + b"00012 030405Z", + b"0001023 0405Z", + b"0001021 0405Z", + b"000002034 05Z", + b"00000203045 Z", + b"001302030405Z", + b"000100030405Z", + b"000132030405Z", + b"000102240405Z", + b"000102036005Z", + b"000230030405Z", + b"000102030460Z", + b"000102030405", + b"000102030405T", + b"000102030405+", + b"000102030405-", + b"000102030405+0", + b"000102030405+00", + b"000102030405+000", + b"000102030405+000Z", + b"000102030405+0000Z", + b"000102030405+-101", + b"000102030405+01-1", + b"000102030405+0060", + b"000102030405+1401", + b"500101000002+0003", + )): + with self.assertRaises(DecodeError): + UTCTime(data, ctx={"bered": True}) + @given(integers(min_value=0, max_value=49)) def test_pre50(self, year): self.assertEqual( @@ -4003,7 +4733,7 @@ class TestAny(CommonMixin, TestCase): pprint(obj, big_blobs=True, with_decode_path=True) self.assertSequenceEqual(obj.encode(), integer_encoded) - @given(binary(), binary()) + @given(binary(min_size=1), binary(min_size=1)) def test_comparison(self, value1, value2): for klass in (Any, AnyInherited): obj1 = klass(value1) @@ -4055,9 +4785,10 @@ class TestAny(CommonMixin, TestCase): def test_copy(self, values): for klass in (Any, AnyInherited): obj = klass(*values) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj._value, obj_copied._value) @given(binary().map(OctetString)) def test_stripped(self, value): @@ -4160,6 +4891,11 @@ class TestAny(CommonMixin, TestCase): self.assertEqual(obj_decoded.tlen, 0) self.assertEqual(obj_decoded.llen, 0) self.assertEqual(obj_decoded.vlen, len(value)) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given( integers(min_value=1).map(tag_ctxc), @@ -4194,6 +4930,10 @@ class TestAny(CommonMixin, TestCase): self.assertTrue(obj.lenindef) self.assertFalse(obj.ber_encoded) self.assertTrue(obj.bered) + obj = copy(obj) + self.assertTrue(obj.lenindef) + self.assertFalse(obj.ber_encoded) + self.assertTrue(obj.bered) repr(obj) list(obj.pps()) pprint(obj, big_blobs=True, with_decode_path=True) @@ -4422,6 +5162,7 @@ class TestChoice(CommonMixin, TestCase): class Wahl(self.base_klass): schema = _schema + register_class(Wahl) obj = Wahl( value=value, expl=expl, @@ -4429,15 +5170,17 @@ class TestChoice(CommonMixin, TestCase): optional=optional or False, _decoded=_decoded, ) - obj_copied = obj.copy() - self.assertIsNone(obj.tag) - self.assertIsNone(obj_copied.tag) - # hack for assert_copied_basic_fields - obj.tag = "whatever" - obj_copied.tag = "whatever" - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj._value, obj_copied._value) - self.assertEqual(obj.specs, obj_copied.specs) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assertIsNone(obj.tag) + self.assertIsNone(obj_copied.tag) + # hack for assert_copied_basic_fields + obj.tag = "whatever" + obj_copied.tag = "whatever" + self.assert_copied_basic_fields(obj, obj_copied) + obj.tag = None + self.assertEqual(obj._value, obj_copied._value) + self.assertEqual(obj.specs, obj_copied.specs) @given(booleans()) def test_stripped(self, value): @@ -4520,6 +5263,11 @@ class TestChoice(CommonMixin, TestCase): ], obj_encoded, ) + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) @given(integers()) def test_set_get(self, value): @@ -4579,15 +5327,13 @@ def seq_values_strategy(draw, seq_klass, do_expl=False): value = None if draw(booleans()): value = seq_klass() - value._value = { - k: v for k, v in draw(dictionaries( - integers(), - one_of( - booleans().map(Boolean), - integers().map(Integer), - ), - )).items() - } + value._value = draw(dictionaries( + integers(), + one_of( + booleans().map(Boolean), + integers().map(Integer), + ), + )) schema = None if draw(booleans()): schema = list(draw(dictionaries( @@ -4606,15 +5352,13 @@ def seq_values_strategy(draw, seq_klass, do_expl=False): default = None if draw(booleans()): default = seq_klass() - default._value = { - k: v for k, v in draw(dictionaries( - integers(), - one_of( - booleans().map(Boolean), - integers().map(Integer), - ), - )).items() - } + default._value = draw(dictionaries( + integers(), + one_of( + booleans().map(Boolean), + integers().map(Integer), + ), + )) optional = draw(one_of(none(), booleans())) _decoded = ( draw(integers(min_value=0)), @@ -4872,13 +5616,15 @@ class SeqMixing(object): def test_copy(self, d): class SeqInherited(self.base_klass): pass + register_class(SeqInherited) for klass in (self.base_klass, SeqInherited): values = d.draw(seq_values_strategy(seq_klass=klass)) obj = klass(*values) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj.specs, obj_copied.specs) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj.specs, obj_copied.specs) + self.assertEqual(obj._value, obj_copied._value) @given(data_strategy()) def test_stripped(self, d): @@ -4997,6 +5743,8 @@ class SeqMixing(object): t, _, lv = tag_strip(seq_encoded) _, _, v = len_decode(lv) seq_encoded_lenindef = t + LENINDEF + v + EOC + with self.assertRaises(DecodeError): + seq.decode(seq_encoded_lenindef) ctx_copied = deepcopy(ctx_dummy) ctx_copied["bered"] = True seq_decoded_lenindef, tail_lenindef = seq.decode( @@ -5007,6 +5755,9 @@ class SeqMixing(object): self.assertDictEqual(ctx_copied, ctx_dummy) self.assertTrue(seq_decoded_lenindef.lenindef) self.assertTrue(seq_decoded_lenindef.bered) + seq_decoded_lenindef = copy(seq_decoded_lenindef) + self.assertTrue(seq_decoded_lenindef.lenindef) + self.assertTrue(seq_decoded_lenindef.bered) with self.assertRaises(DecodeError): seq.decode(seq_encoded_lenindef[:-1], ctx={"bered": True}) with self.assertRaises(DecodeError): @@ -5039,6 +5790,12 @@ class SeqMixing(object): obj.encode(), ) + assert_exceeding_data( + self, + lambda: seq.decod(seq_encoded_lenindef + tail_junk, ctx={"bered": True}), + tail_junk, + ) + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) @given(data_strategy()) def test_symmetric_with_seq(self, d): @@ -5097,7 +5854,7 @@ class SeqMixing(object): min_size=1, )).items()) tags = [tag_encode(tag) for tag in d.draw(sets( - integers(min_value=0), + integers(min_value=1), min_size=len(_schema), max_size=len(_schema), ))] @@ -5124,6 +5881,9 @@ class SeqMixing(object): seq_decoded, _ = seq_with_default.decode(seq_encoded, ctx=ctx) self.assertTrue(seq_decoded.ber_encoded) self.assertTrue(seq_decoded.bered) + seq_decoded = copy(seq_decoded) + self.assertTrue(seq_decoded.ber_encoded) + 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) @@ -5132,7 +5892,7 @@ class SeqMixing(object): def test_missing_from_spec(self, d): names = list(d.draw(sets(text_letters(), min_size=2))) tags = [tag_encode(tag) for tag in d.draw(sets( - integers(min_value=0), + integers(min_value=1), min_size=len(names), max_size=len(names), ))] @@ -5152,8 +5912,7 @@ class SeqMixing(object): with self.assertRaises(TagMismatch): seq_missing.decode(seq_encoded) - @given(data_strategy()) - def test_bered(self, d): + def test_bered(self): class Seq(self.base_klass): schema = (("underlying", Boolean()),) encoded = Boolean.tag_default + len_encode(1) + b"\x01" @@ -5162,6 +5921,10 @@ class SeqMixing(object): self.assertFalse(decoded.ber_encoded) self.assertFalse(decoded.lenindef) self.assertTrue(decoded.bered) + decoded = copy(decoded) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) class Seq(self.base_klass): schema = (("underlying", OctetString()),) @@ -5178,6 +5941,10 @@ class SeqMixing(object): self.assertFalse(decoded.ber_encoded) self.assertFalse(decoded.lenindef) self.assertTrue(decoded.bered) + decoded = copy(decoded) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) class TestSequence(SeqMixing, CommonMixin, TestCase): @@ -5274,9 +6041,12 @@ class TestSet(SeqMixing, CommonMixin, TestCase): seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx) self.assertTrue(seq_decoded.ber_encoded) self.assertTrue(seq_decoded.bered) + 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)], - [t for t in tags], + tags, ) @@ -5341,7 +6111,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() @@ -5549,6 +6319,7 @@ class SeqOfMixing(object): class SeqOf(self.base_klass): schema = _schema + register_class(SeqOf) obj = SeqOf( value=value, bounds=bounds, @@ -5558,11 +6329,12 @@ class SeqOfMixing(object): optional=optional or False, _decoded=_decoded, ) - obj_copied = obj.copy() - self.assert_copied_basic_fields(obj, obj_copied) - self.assertEqual(obj._bound_min, obj_copied._bound_min) - self.assertEqual(obj._bound_max, obj_copied._bound_max) - self.assertEqual(obj._value, obj_copied._value) + for copy_func in copy_funcs: + obj_copied = copy_func(obj) + self.assert_copied_basic_fields(obj, obj_copied) + self.assertEqual(obj._bound_min, obj_copied._bound_min) + self.assertEqual(obj._bound_max, obj_copied._bound_max) + self.assertEqual(obj._value, obj_copied._value) @given( lists(binary()), @@ -5693,23 +6465,34 @@ class SeqOfMixing(object): t, _, lv = tag_strip(obj_encoded) _, _, v = len_decode(lv) obj_encoded_lenindef = t + LENINDEF + v + EOC + with self.assertRaises(DecodeError): + obj.decode(obj_encoded_lenindef) obj_decoded_lenindef, tail_lenindef = obj.decode( obj_encoded_lenindef + tail_junk, ctx={"bered": True}, ) self.assertTrue(obj_decoded_lenindef.lenindef) self.assertTrue(obj_decoded_lenindef.bered) + obj_decoded_lenindef = copy(obj_decoded_lenindef) + self.assertTrue(obj_decoded_lenindef.lenindef) + self.assertTrue(obj_decoded_lenindef.bered) repr(obj_decoded_lenindef) list(obj_decoded_lenindef.pps()) pprint(obj_decoded_lenindef, big_blobs=True, with_decode_path=True) + self.assertEqual(tail_lenindef, tail_junk) self.assertEqual(obj_decoded_lenindef.tlvlen, len(obj_encoded_lenindef)) with self.assertRaises(DecodeError): obj.decode(obj_encoded_lenindef[:-1], ctx={"bered": True}) with self.assertRaises(DecodeError): obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True}) - @given(data_strategy()) - def test_bered(self, d): + assert_exceeding_data( + self, + lambda: obj_expled.decod(obj_expled_encoded + tail_junk), + tail_junk, + ) + + def test_bered(self): class SeqOf(self.base_klass): schema = Boolean() encoded = Boolean(False).encode() @@ -5721,6 +6504,10 @@ class SeqOfMixing(object): self.assertFalse(decoded.ber_encoded) self.assertFalse(decoded.lenindef) self.assertTrue(decoded.bered) + decoded = copy(decoded) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) class SeqOf(self.base_klass): schema = OctetString() @@ -5738,6 +6525,10 @@ class SeqOfMixing(object): self.assertFalse(decoded.ber_encoded) self.assertFalse(decoded.lenindef) self.assertTrue(decoded.bered) + decoded = copy(decoded) + self.assertFalse(decoded.ber_encoded) + self.assertFalse(decoded.lenindef) + self.assertTrue(decoded.bered) class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase): @@ -5803,6 +6594,9 @@ class TestSetOf(SeqOfMixing, CommonMixin, TestCase): seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx) self.assertTrue(seq_decoded.ber_encoded) self.assertTrue(seq_decoded.bered) + seq_decoded = copy(seq_decoded) + self.assertTrue(seq_decoded.ber_encoded) + self.assertTrue(seq_decoded.bered) self.assertSequenceEqual( [obj.encode() for obj in seq_decoded], values, @@ -6006,7 +6800,10 @@ class TestPP(TestCase): chosen_id = oids[chosen] pp = _pp(asn1_type_name=ObjectIdentifier.asn1_type_name, value=chosen) self.assertNotIn(chosen_id, pp_console_row(pp)) - self.assertIn(chosen_id, pp_console_row(pp, oids=oids)) + self.assertIn( + chosen_id, + pp_console_row(pp, oid_maps=[{'whatever': 'whenever'}, oids]), + ) class TestAutoAddSlots(TestCase): @@ -6034,32 +6831,33 @@ class TestOIDDefines(TestCase): min_size=len(value_names), max_size=len(value_names), )) - _schema = [ - ("type", ObjectIdentifier(defines=(((value_name_chosen,), { - oid: Integer() for oid in oids[:-1] - }),))), - ] - for i, value_name in enumerate(value_names): - _schema.append((value_name, Any(expl=tag_ctxp(i)))) + for definable_class in (Any, OctetString, BitString): + _schema = [ + ("type", ObjectIdentifier(defines=(((value_name_chosen,), { + oid: Integer() for oid in oids[:-1] + }),))), + ] + for i, value_name in enumerate(value_names): + _schema.append((value_name, definable_class(expl=tag_ctxp(i)))) - class Seq(Sequence): - schema = _schema - seq = Seq() - for value_name, value in zip(value_names, values): - seq[value_name] = Any(Integer(value).encode()) - seq["type"] = oid_chosen - seq, _ = Seq().decode(seq.encode()) - for value_name in value_names: - if value_name == value_name_chosen: - continue - self.assertIsNone(seq[value_name].defined) - if value_name_chosen in oids[:-1]: - self.assertIsNotNone(seq[value_name_chosen].defined) - self.assertEqual(seq[value_name_chosen].defined[0], oid_chosen) - self.assertIsInstance(seq[value_name_chosen].defined[1], Integer) - repr(seq) - list(seq.pps()) - pprint(seq, big_blobs=True, with_decode_path=True) + class Seq(Sequence): + schema = _schema + seq = Seq() + for value_name, value in zip(value_names, values): + seq[value_name] = definable_class(Integer(value).encode()) + seq["type"] = oid_chosen + seq, _ = Seq().decode(seq.encode()) + for value_name in value_names: + if value_name == value_name_chosen: + continue + self.assertIsNone(seq[value_name].defined) + if value_name_chosen in oids[:-1]: + self.assertIsNotNone(seq[value_name_chosen].defined) + self.assertEqual(seq[value_name_chosen].defined[0], oid_chosen) + self.assertIsInstance(seq[value_name_chosen].defined[1], Integer) + repr(seq) + list(seq.pps()) + pprint(seq, big_blobs=True, with_decode_path=True) class TestDefinesByPath(TestCase): @@ -6110,10 +6908,10 @@ class TestDefinesByPath(TestCase): (type_integered, Integer(234)), ) for t, v in pairs_input: - pair = Pair() - pair["type"] = t - pair["value"] = PairValue((Any(v),)) - pairs.append(pair) + pairs.append(Pair(( + ("type", t), + ("value", PairValue((Any(v),))), + ))) seq_inner = SeqInner() seq_inner["typeInner"] = type_innered seq_inner["valueInner"] = Any(pairs) @@ -6251,6 +7049,43 @@ class TestDefinesByPath(TestCase): decoded, _ = Outer().decode(outer.encode()) self.assertEqual(decoded["tgt"].defined[1], Integer(tgt)) + def test_remaining_data(self): + oid = ObjectIdentifier("1.2.3") + class Seq(Sequence): + schema = ( + ("oid", ObjectIdentifier(defines=((("tgt",), { + oid: Integer(), + }),))), + ("tgt", OctetString()), + ) + + seq = Seq(( + ("oid", oid), + ("tgt", OctetString(Integer(123).encode() + b"junk")), + )) + with assertRaisesRegex(self, DecodeError, "remaining data"): + Seq().decode(seq.encode()) + + def test_remaining_data_seqof(self): + oid = ObjectIdentifier("1.2.3") + class SeqOf(SetOf): + schema = OctetString() + + class Seq(Sequence): + schema = ( + ("oid", ObjectIdentifier(defines=((("tgt",), { + oid: Integer(), + }),))), + ("tgt", SeqOf()), + ) + + seq = Seq(( + ("oid", oid), + ("tgt", SeqOf([OctetString(Integer(123).encode() + b"junk")])), + )) + with assertRaisesRegex(self, DecodeError, "remaining data"): + Seq().decode(seq.encode()) + class TestAbsDecodePath(TestCase): @given( @@ -6258,10 +7093,9 @@ class TestAbsDecodePath(TestCase): lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple), ) def test_concat(self, decode_path, rel_path): - self.assertSequenceEqual( - abs_decode_path(decode_path, rel_path), - decode_path + rel_path, - ) + dp = abs_decode_path(decode_path, rel_path) + self.assertSequenceEqual(dp, decode_path + rel_path) + repr(dp) @given( lists(text(alphabet=ascii_letters, min_size=1)).map(tuple), @@ -6308,9 +7142,15 @@ class TestStrictDefaultExistence(TestCase): decoded, _ = seq.decode(raw, ctx={"allow_default_values": True}) self.assertTrue(decoded.ber_encoded) self.assertTrue(decoded.bered) + decoded = copy(decoded) + self.assertTrue(decoded.ber_encoded) + self.assertTrue(decoded.bered) decoded, _ = seq.decode(raw, ctx={"bered": True}) self.assertTrue(decoded.ber_encoded) self.assertTrue(decoded.bered) + decoded = copy(decoded) + self.assertTrue(decoded.ber_encoded) + self.assertTrue(decoded.bered) class TestX690PrefixedType(TestCase): @@ -6360,3 +7200,15 @@ class TestExplOOB(TestCase): with assertRaisesRegex(self, DecodeError, "explicit tag out-of-bound"): Integer(expl=expl).decode(raw) Integer(expl=expl).decode(raw, ctx={"allow_expl_oob": True}) + + +class TestPickleDifferentVersion(TestCase): + def runTest(self): + pickled = pickle_dumps(Integer(123), pickle_proto) + import pyderasn + version_orig = pyderasn.__version__ + pyderasn.__version__ += "different" + with assertRaisesRegex(self, ValueError, "different PyDERASN version"): + pickle_loads(pickled) + pyderasn.__version__ = version_orig + pickle_loads(pickled)