]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
Fix minimal tag value in tests: zero tag has only EOC
[pyderasn.git] / tests / test_pyderasn.py
index a39d125ee036d8c12259732254a11543aec88c15..1cce9ed5a3988a92b92248e987d2e012366bf798 100644 (file)
 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
 
@@ -123,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
@@ -155,9 +161,11 @@ def register_class(klass):
 
 
 def assert_exceeding_data(self, call, junk):
-    if len(junk) > 0:
-        with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)):
-            call()
+    if len(junk) <= 0:
+        return
+    with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)) as err:
+        call()
+    repr(err)
 
 
 class TestHex(TestCase):
@@ -338,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
@@ -409,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)
@@ -587,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,
             )
@@ -603,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(
@@ -619,7 +627,7 @@ class TestBoolean(CommonMixin, TestCase):
             self.assertEqual(obj_decoded.expl_offset, offset)
             assert_exceeding_data(
                 self,
-                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                lambda: obj_expled.hexdecod(obj_expled_hex_encoded + hexenc(tail_junk)),
                 tail_junk,
             )
 
@@ -828,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)
@@ -1641,6 +1649,9 @@ class TestBitString(CommonMixin, TestCase):
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
             self.assertEqual(len(encoded), obj.tlvlen)
+            repr(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(
         integers(min_value=0),
@@ -2174,6 +2185,9 @@ class TestOctetString(CommonMixin, TestCase):
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
             self.assertEqual(len(encoded), obj.tlvlen)
+            repr(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(
         integers(min_value=0),
@@ -2262,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)
@@ -2492,7 +2506,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)
@@ -2819,6 +2833,28 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"):
             ObjectIdentifier().decode(tampered)
 
+    @given(data_strategy())
+    def test_negative_arcs(self, d):
+        oid = list(d.draw(oid_strategy()))
+        if len(oid) == 2:
+            return
+        idx = d.draw(integers(min_value=3, max_value=len(oid)))
+        oid[idx - 1] *= -1
+        if oid[idx - 1] == 0:
+            oid[idx - 1] = -1
+        with self.assertRaises(InvalidOID):
+            ObjectIdentifier(tuple(oid))
+        with self.assertRaises(InvalidOID):
+            ObjectIdentifier(".".join(str(i) for i in oid))
+
+    @given(data_strategy())
+    def test_plused_arcs(self, d):
+        oid = [str(arc) for arc in d.draw(oid_strategy())]
+        idx = d.draw(integers(min_value=0, max_value=len(oid)))
+        oid[idx - 1] = "+" + oid[idx - 1]
+        with self.assertRaises(InvalidOID):
+            ObjectIdentifier(".".join(str(i) for i in oid))
+
     @given(data_strategy())
     def test_nonnormalized_arcs(self, d):
         arcs = d.draw(lists(
@@ -2940,7 +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 = (
@@ -3540,14 +3576,22 @@ class TestPrintableString(
                 ("&", {"allow_ampersand": True}),
                 ("&*", {"allow_asterisk": True, "allow_ampersand": True}),
         ):
-            s = "hello invalid " + c
+            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(
@@ -3707,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)
@@ -3914,6 +3961,10 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
         if value.microsecond > 0:
             self.assertFalse(obj_encoded.endswith(b"0Z"))
 
+    def test_repr_not_ready(self):
+        unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime())
+        repr(GeneralizedTime())
+
     def test_x690_vector_valid(self):
         for data in ((
                 b"19920521000000Z",
@@ -3966,6 +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,
@@ -4013,6 +4297,43 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
         with assertRaisesRegex(self, DecodeError, "only microsecond fractions"):
             GeneralizedTime(b"20010101000000.0000001Z")
 
+    def test_non_pure_integers(self):
+        for data in ((
+                # b"20000102030405Z,
+                b"+2000102030405Z",
+                b"2000+102030405Z",
+                b"200001+2030405Z",
+                b"20000102+30405Z",
+                b"2000010203+405Z",
+                b"200001020304+5Z",
+                b"20000102030405.+6Z",
+                b"20000102030405.-6Z",
+                b"_2000102030405Z",
+                b"2000_102030405Z",
+                b"200001_2030405Z",
+                b"20000102_30405Z",
+                b"2000010203_405Z",
+                b"200001020304_5Z",
+                b"20000102030405._6Z",
+                b"20000102030405.6_Z",
+                b" 2000102030405Z",
+                b"2000 102030405Z",
+                b"200001 2030405Z",
+                b"20000102 30405Z",
+                b"2000010203 405Z",
+                b"200001020304 5Z",
+                b"20000102030405. 6Z",
+                b"200 0102030405Z",
+                b"20001 02030405Z",
+                b"2000012 030405Z",
+                b"200001023 0405Z",
+                b"20000102034 05Z",
+                b"2000010203045 Z",
+                b"20000102030405.6 Z",
+        )):
+            with self.assertRaises(DecodeError):
+                GeneralizedTime(data)
+
 
 class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     base_klass = UTCTime
@@ -4023,6 +4344,10 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     def additional_symmetric_check(self, value, obj_encoded):
         pass
 
+    def test_repr_not_ready(self):
+        unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime())
+        repr(UTCTime())
+
     def test_x690_vector_valid(self):
         for data in ((
                 b"920521000000Z",
@@ -4083,6 +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(
@@ -4185,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)
@@ -5195,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(
@@ -5304,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),
         ))]
@@ -5342,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),
         ))]
@@ -5561,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()
@@ -5915,6 +6465,8 @@ class SeqOfMixing(object):
         t, _, lv = tag_strip(obj_encoded)
         _, _, v = len_decode(lv)
         obj_encoded_lenindef = t + LENINDEF + v + EOC
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef)
         obj_decoded_lenindef, tail_lenindef = obj.decode(
             obj_encoded_lenindef + tail_junk,
             ctx={"bered": True},
@@ -6279,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):
@@ -6355,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)
@@ -6496,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(
@@ -6503,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),