]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
BER usage documentation
[pyderasn.git] / tests / test_pyderasn.py
index 7bce6b49bcfdb7d237e6353e0677d6da0efa3acb..8b93827d1e14a4d28155925dd5660015704aff0e 100644 (file)
@@ -1,6 +1,6 @@
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
-# Copyright (C) 2017 Sergey Matveev <stargrave@stargrave.org>
+# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
+# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
@@ -18,6 +18,7 @@
 
 from datetime import datetime
 from string import ascii_letters
+from string import digits
 from string import printable
 from string import whitespace
 from unittest import TestCase
@@ -42,14 +43,17 @@ from hypothesis.strategies import sets
 from hypothesis.strategies import text
 from hypothesis.strategies import tuples
 from six import assertRaisesRegex
+from six import binary_type
 from six import byte2int
 from six import indexbytes
 from six import int2byte
 from six import iterbytes
 from six import PY2
 from six import text_type
+from six import unichr as six_unichr
 
 from pyderasn import _pp
+from pyderasn import abs_decode_path
 from pyderasn import Any
 from pyderasn import BitString
 from pyderasn import BMPString
@@ -57,7 +61,10 @@ from pyderasn import Boolean
 from pyderasn import BoundsError
 from pyderasn import Choice
 from pyderasn import DecodeError
+from pyderasn import DecodePathDefBy
 from pyderasn import Enumerated
+from pyderasn import EOC
+from pyderasn import EOC_LEN
 from pyderasn import GeneralizedTime
 from pyderasn import GeneralString
 from pyderasn import GraphicString
@@ -85,6 +92,7 @@ from pyderasn import SequenceOf
 from pyderasn import Set
 from pyderasn import SetOf
 from pyderasn import tag_ctxc
+from pyderasn import tag_ctxp
 from pyderasn import tag_decode
 from pyderasn import tag_encode
 from pyderasn import tag_strip
@@ -103,11 +111,11 @@ from pyderasn import VideotexString
 from pyderasn import VisibleString
 
 
-settings.register_profile('local', settings(
+settings.register_profile("local", settings(
     deadline=5000,
     perform_health_check=False,
 ))
-settings.load_profile('local')
+settings.load_profile("local")
 LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4
 
 tag_classes = sampled_from((
@@ -280,7 +288,7 @@ class CommonMixin(object):
         with self.assertRaises(ValueError):
             self.base_klass(impl=b"whatever", expl=b"whenever")
 
-    @given(binary(), integers(), integers(), integers())
+    @given(binary(min_size=1), integers(), integers(), integers())
     def test_decoded(self, impl, offset, llen, vlen):
         obj = self.base_klass(impl=impl, _decoded=(offset, llen, vlen))
         self.assertEqual(obj.offset, offset)
@@ -289,7 +297,7 @@ class CommonMixin(object):
         self.assertEqual(obj.tlen, len(impl))
         self.assertEqual(obj.tlvlen, obj.tlen + obj.llen + obj.vlen)
 
-    @given(binary())
+    @given(binary(min_size=1))
     def test_impl_inherited(self, impl_tag):
         class Inherited(self.base_klass):
             impl = impl_tag
@@ -317,7 +325,7 @@ class CommonMixin(object):
 
 
 @composite
-def boolean_values_strat(draw, do_expl=False):
+def boolean_values_strategy(draw, do_expl=False):
     value = draw(one_of(none(), booleans()))
     impl = None
     expl = None
@@ -389,7 +397,7 @@ class TestBoolean(CommonMixin, TestCase):
                 default_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(boolean_values_strat())
+            ) = d.draw(boolean_values_strategy())
             obj_initial = klass(
                 value_initial,
                 impl_initial,
@@ -405,7 +413,7 @@ class TestBoolean(CommonMixin, TestCase):
                 default,
                 optional,
                 _decoded,
-            ) = d.draw(boolean_values_strat(do_expl=impl_initial is None))
+            ) = d.draw(boolean_values_strategy(do_expl=impl_initial is None))
             obj = obj_initial(value, impl, expl, default, optional)
             if obj.ready:
                 value_expected = default if value is None else value
@@ -427,7 +435,7 @@ class TestBoolean(CommonMixin, TestCase):
                 optional = True
             self.assertEqual(obj.optional, optional)
 
-    @given(boolean_values_strat())
+    @given(boolean_values_strategy())
     def test_copy(self, values):
         for klass in (Boolean, BooleanInherited):
             obj = klass(*values)
@@ -522,12 +530,13 @@ class TestBoolean(CommonMixin, TestCase):
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
-        boolean_values_strat(),
+        boolean_values_strategy(),
         booleans(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (Boolean, BooleanInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -545,10 +554,13 @@ class TestBoolean(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bool(obj_decoded), bool(obj_expled))
@@ -578,17 +590,75 @@ class TestBoolean(CommonMixin, TestCase):
             )))
 
     @given(integers(min_value=0 + 1, max_value=255 - 1))
-    def test_invalid_value(self, value):
+    def test_ber_value(self, value):
         with assertRaisesRegex(self, DecodeError, "unacceptable Boolean value"):
             Boolean().decode(b"".join((
                 Boolean.tag_default,
                 len_encode(1),
                 int2byte(value),
             )))
+        obj, _ = Boolean().decode(
+            b"".join((
+                Boolean.tag_default,
+                len_encode(1),
+                int2byte(value),
+            )),
+            ctx={"bered": True},
+        )
+        self.assertTrue(bool(obj))
+        self.assertTrue(obj.bered)
+        self.assertFalse(obj.lenindef)
+
+    @given(
+        integers(min_value=1).map(tag_ctxc),
+        lists(
+            booleans(),
+            min_size=1,
+            max_size=5,
+        ),
+    )
+    def test_ber_expl(self, expl, values):
+        encoded = b""
+        for value in values:
+            encoded += (
+                expl +
+                b"\x80" +
+                Boolean(value).encode() +
+                EOC
+            )
+        encoded = SequenceOf.tag_default + len_encode(len(encoded)) + encoded
+
+        class SeqOf(SequenceOf):
+            schema = Boolean(expl=expl)
+        seqof, tail = SeqOf().decode(encoded, ctx={"bered": True})
+        self.assertSequenceEqual(tail, b"")
+        self.assertSequenceEqual([bool(v) for v in seqof], values)
+        self.assertSetEqual(
+            set(
+                (
+                    v.tlvlen,
+                    v.expl_tlvlen,
+                    v.expl_tlen,
+                    v.expl_llen,
+                    v.bered,
+                    v.lenindef,
+                    v.expl_lenindef,
+                ) for v in seqof
+            ),
+            set(((
+                3 + EOC_LEN,
+                len(expl) + 1 + 3 + EOC_LEN,
+                len(expl),
+                1,
+                False,
+                False,
+                True,
+            ),)),
+        )
 
 
 @composite
-def integer_values_strat(draw, do_expl=False):
+def integer_values_strategy(draw, do_expl=False):
     bound_min, value, default, bound_max = sorted(draw(sets(
         integers(),
         min_size=4,
@@ -738,7 +808,7 @@ class TestInteger(CommonMixin, TestCase):
                 optional_initial,
                 _specs_initial,
                 _decoded_initial,
-            ) = d.draw(integer_values_strat())
+            ) = d.draw(integer_values_strategy())
             obj_initial = klass(
                 value_initial,
                 bounds_initial,
@@ -758,7 +828,7 @@ class TestInteger(CommonMixin, TestCase):
                 optional,
                 _,
                 _decoded,
-            ) = d.draw(integer_values_strat(do_expl=impl_initial is None))
+            ) = d.draw(integer_values_strategy(do_expl=impl_initial is None))
             if (default is None) and (obj_initial.default is not None):
                 bounds = None
             if (
@@ -804,7 +874,7 @@ class TestInteger(CommonMixin, TestCase):
                 {} if _specs_initial is None else dict(_specs_initial),
             )
 
-    @given(integer_values_strat())
+    @given(integer_values_strategy())
     def test_copy(self, values):
         for klass in (Integer, IntegerInherited):
             obj = klass(*values)
@@ -897,12 +967,13 @@ class TestInteger(CommonMixin, TestCase):
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
-        integer_values_strat(),
+        integer_values_strategy(),
         integers(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (Integer, IntegerInherited):
             _, _, _, _, default, optional, _, _decoded = values
             obj = klass(
@@ -920,10 +991,13 @@ class TestInteger(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(int(obj_decoded), int(obj_expled))
@@ -982,7 +1056,7 @@ class TestInteger(CommonMixin, TestCase):
 
 
 @composite
-def bit_string_values_strat(draw, schema=None, value_required=False, do_expl=False):
+def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl=False):
     if schema is None:
         schema = ()
         if draw(booleans()):
@@ -1125,8 +1199,8 @@ class TestBitString(CommonMixin, TestCase):
     @given(
         tuples(integers(min_value=0), binary()),
         tuples(integers(min_value=0), binary()),
-        binary(),
-        binary(),
+        binary(min_size=1),
+        binary(min_size=1),
     )
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (BitString, BitStringInherited):
@@ -1151,7 +1225,7 @@ class TestBitString(CommonMixin, TestCase):
                 default_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(bit_string_values_strat())
+            ) = d.draw(bit_string_values_strategy())
 
             class BS(klass):
                 schema = schema_initial
@@ -1171,7 +1245,7 @@ class TestBitString(CommonMixin, TestCase):
                 default,
                 optional,
                 _decoded,
-            ) = d.draw(bit_string_values_strat(
+            ) = d.draw(bit_string_values_strategy(
                 schema=schema_initial,
                 do_expl=impl_initial is None,
             ))
@@ -1192,7 +1266,7 @@ class TestBitString(CommonMixin, TestCase):
             self.assertEqual(obj.optional, optional)
             self.assertEqual(obj.specs, obj_initial.specs)
 
-    @given(bit_string_values_strat())
+    @given(bit_string_values_strategy())
     def test_copy(self, values):
         for klass in (BitString, BitStringInherited):
             _schema, value, impl, expl, default, optional, _decoded = values
@@ -1275,7 +1349,8 @@ class TestBitString(CommonMixin, TestCase):
             default,
             optional,
             _decoded,
-        ) = d.draw(bit_string_values_strat(value_required=True))
+        ) = d.draw(bit_string_values_strategy(value_required=True))
+        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))
         for klass in (BitString, BitStringInherited):
@@ -1296,10 +1371,13 @@ class TestBitString(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -1369,9 +1447,94 @@ class TestBitString(CommonMixin, TestCase):
         self.assertTrue(obj[9])
         self.assertFalse(obj[17])
 
+    @given(
+        integers(min_value=1, max_value=30),
+        lists(
+            one_of(
+                binary(min_size=1, max_size=5),
+                lists(
+                    binary(min_size=1, max_size=5),
+                    min_size=1,
+                    max_size=3,
+                ),
+            ),
+            min_size=0,
+            max_size=3,
+        ),
+        lists(booleans(), min_size=1),
+    )
+    def test_constructed(self, impl, chunk_inputs, chunk_last_bits):
+        def chunk_constructed(contents):
+            return (
+                tag_encode(form=TagFormConstructed, num=3) +
+                b"\x80" +
+                b"".join(BitString(content).encode() for content in contents) +
+                EOC
+            )
+        chunks = []
+        payload_expected = b""
+        bit_len_expected = 0
+        for chunk_input in chunk_inputs:
+            if isinstance(chunk_input, binary_type):
+                chunks.append(BitString(chunk_input).encode())
+                payload_expected += chunk_input
+                bit_len_expected += len(chunk_input) * 8
+            else:
+                chunks.append(chunk_constructed(chunk_input))
+                payload = b"".join(chunk_input)
+                payload_expected += payload
+                bit_len_expected += len(payload) * 8
+        chunk_last = BitString("'%s'B" % "".join(
+            "1" if bit else "0" for bit in chunk_last_bits
+        ))
+        payload_expected += bytes(chunk_last)
+        bit_len_expected += chunk_last.bit_len
+        encoded_indefinite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            b"\x80" +
+            b"".join(chunks) +
+            chunk_last.encode() +
+            EOC
+        )
+        encoded_definite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            len_encode(len(b"".join(chunks) + chunk_last.encode())) +
+            b"".join(chunks) +
+            chunk_last.encode()
+        )
+        with assertRaisesRegex(self, DecodeError, "unallowed BER"):
+            BitString(impl=tag_encode(impl)).decode(encoded_indefinite)
+        for lenindef_expected, encoded in (
+                (True, encoded_indefinite),
+                (False, encoded_definite),
+        ):
+            obj, tail = BitString(impl=tag_encode(impl)).decode(
+                encoded, ctx={"bered": True}
+            )
+            self.assertSequenceEqual(tail, b"")
+            self.assertEqual(obj.bit_len, bit_len_expected)
+            self.assertSequenceEqual(bytes(obj), payload_expected)
+            self.assertTrue(obj.bered)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertEqual(len(encoded), obj.tlvlen)
+
+    def test_x690_vector(self):
+        vector = BitString("'0A3B5F291CD'H")
+        obj, tail = BitString().decode(hexdec("0307040A3B5F291CD0"))
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(obj, vector)
+        obj, tail = BitString().decode(
+            hexdec("23800303000A3B0305045F291CD00000"),
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(obj, vector)
+        self.assertTrue(obj.bered)
+        self.assertTrue(obj.lenindef)
+
 
 @composite
-def octet_string_values_strat(draw, do_expl=False):
+def octet_string_values_strategy(draw, do_expl=False):
     bound_min, bound_max = sorted(draw(sets(
         integers(min_value=0, max_value=1 << 7),
         min_size=2,
@@ -1434,7 +1597,7 @@ class TestOctetString(CommonMixin, TestCase):
         repr(obj)
         pprint(obj)
 
-    @given(binary(), binary(), binary(), binary())
+    @given(binary(), binary(), binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (OctetString, OctetStringInherited):
             obj1 = klass(value1)
@@ -1485,7 +1648,7 @@ class TestOctetString(CommonMixin, TestCase):
                 default_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(octet_string_values_strat())
+            ) = d.draw(octet_string_values_strategy())
             obj_initial = klass(
                 value_initial,
                 bounds_initial,
@@ -1503,7 +1666,7 @@ class TestOctetString(CommonMixin, TestCase):
                 default,
                 optional,
                 _decoded,
-            ) = d.draw(octet_string_values_strat(do_expl=impl_initial is None))
+            ) = d.draw(octet_string_values_strategy(do_expl=impl_initial is None))
             if (default is None) and (obj_initial.default is not None):
                 bounds = None
             if (
@@ -1545,7 +1708,7 @@ class TestOctetString(CommonMixin, TestCase):
                 bounds or bounds_initial or (0, float("+inf")),
             )
 
-    @given(octet_string_values_strat())
+    @given(octet_string_values_strategy())
     def test_copy(self, values):
         for klass in (OctetString, OctetStringInherited):
             obj = klass(*values)
@@ -1630,12 +1793,13 @@ class TestOctetString(CommonMixin, TestCase):
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
-        octet_string_values_strat(),
+        octet_string_values_strategy(),
         binary(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (OctetString, OctetStringInherited):
             _, _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -1653,10 +1817,13 @@ class TestOctetString(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -1676,9 +1843,68 @@ class TestOctetString(CommonMixin, TestCase):
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
 
+    @given(
+        integers(min_value=1, max_value=30),
+        lists(
+            one_of(
+                binary(min_size=1, max_size=5),
+                lists(
+                    binary(min_size=1, max_size=5),
+                    min_size=1,
+                    max_size=3,
+                ),
+            ),
+            min_size=1,
+            max_size=3,
+        ),
+    )
+    def test_constructed(self, impl, chunk_inputs):
+        def chunk_constructed(contents):
+            return (
+                tag_encode(form=TagFormConstructed, num=4) +
+                b"\x80" +
+                b"".join(OctetString(content).encode() for content in contents) +
+                EOC
+            )
+        chunks = []
+        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
+            else:
+                chunks.append(chunk_constructed(chunk_input))
+                payload = b"".join(chunk_input)
+                payload_expected += payload
+        encoded_indefinite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            b"\x80" +
+            b"".join(chunks) +
+            EOC
+        )
+        encoded_definite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            len_encode(len(b"".join(chunks))) +
+            b"".join(chunks)
+        )
+        with assertRaisesRegex(self, DecodeError, "unallowed BER"):
+            OctetString(impl=tag_encode(impl)).decode(encoded_indefinite)
+        for lenindef_expected, encoded in (
+                (True, encoded_indefinite),
+                (False, encoded_definite),
+        ):
+            obj, tail = OctetString(impl=tag_encode(impl)).decode(
+                encoded, ctx={"bered": True}
+            )
+            self.assertSequenceEqual(tail, b"")
+            self.assertSequenceEqual(bytes(obj), payload_expected)
+            self.assertTrue(obj.bered)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertEqual(len(encoded), obj.tlvlen)
+
 
 @composite
-def null_values_strat(draw, do_expl=False):
+def null_values_strategy(draw, do_expl=False):
     impl = None
     expl = None
     if do_expl:
@@ -1724,7 +1950,7 @@ class TestNull(CommonMixin, TestCase):
                 expl_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(null_values_strat())
+            ) = d.draw(null_values_strategy())
             obj_initial = klass(
                 impl=impl_initial,
                 expl=expl_initial,
@@ -1736,7 +1962,7 @@ class TestNull(CommonMixin, TestCase):
                 expl,
                 optional,
                 _decoded,
-            ) = d.draw(null_values_strat(do_expl=impl_initial is None))
+            ) = d.draw(null_values_strategy(do_expl=impl_initial is None))
             obj = obj_initial(impl=impl, expl=expl, optional=optional)
             self.assertEqual(obj.tag, impl or impl_initial or obj.tag_default)
             self.assertEqual(obj.expl_tag, expl or expl_initial)
@@ -1744,7 +1970,7 @@ class TestNull(CommonMixin, TestCase):
             optional = False if optional is None else optional
             self.assertEqual(obj.optional, optional)
 
-    @given(null_values_strat())
+    @given(null_values_strategy())
     def test_copy(self, values):
         for klass in (Null, NullInherited):
             impl, expl, optional, _decoded = values
@@ -1810,11 +2036,12 @@ class TestNull(CommonMixin, TestCase):
             Null(impl=impl).decode(Null().encode())
 
     @given(
-        null_values_strat(),
+        null_values_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, tag_expl, offset):
+    def test_symmetric(self, values, tag_expl, offset, tail_junk):
         for klass in (Null, NullInherited):
             _, _, optional, _decoded = values
             obj = klass(optional=optional, _decoded=_decoded)
@@ -1827,10 +2054,13 @@ class TestNull(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
@@ -1870,7 +2100,7 @@ def oid_strategy(draw):
 
 
 @composite
-def oid_values_strat(draw, do_expl=False):
+def oid_values_strategy(draw, do_expl=False):
     value = draw(one_of(none(), oid_strategy()))
     impl = None
     expl = None
@@ -1951,14 +2181,14 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 default_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(oid_values_strat())
+            ) = d.draw(oid_values_strategy())
             obj_initial = klass(
-                value_initial,
-                impl_initial,
-                expl_initial,
-                default_initial,
-                optional_initial or False,
-                _decoded_initial,
+                value=value_initial,
+                impl=impl_initial,
+                expl=expl_initial,
+                default=default_initial,
+                optional=optional_initial or False,
+                _decoded=_decoded_initial,
             )
             (
                 value,
@@ -1967,8 +2197,14 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 default,
                 optional,
                 _decoded,
-            ) = d.draw(oid_values_strat(do_expl=impl_initial is None))
-            obj = obj_initial(value, impl, expl, default, optional)
+            ) = d.draw(oid_values_strategy(do_expl=impl_initial is None))
+            obj = obj_initial(
+                value=value,
+                impl=impl,
+                expl=expl,
+                default=default,
+                optional=optional,
+            )
             if obj.ready:
                 value_expected = default if value is None else value
                 value_expected = (
@@ -1989,10 +2225,25 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 optional = True
             self.assertEqual(obj.optional, optional)
 
-    @given(oid_values_strat())
+    @given(oid_values_strategy())
     def test_copy(self, values):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
-            obj = klass(*values)
+            (
+                value,
+                impl,
+                expl,
+                default,
+                optional,
+                _decoded,
+            ) = values
+            obj = klass(
+                value=value,
+                impl=impl,
+                expl=expl,
+                default=default,
+                optional=optional,
+                _decoded=_decoded,
+            )
             obj_copied = obj.copy()
             self.assert_copied_basic_fields(obj, obj_copied)
             self.assertEqual(obj._value, obj_copied._value)
@@ -2108,12 +2359,13 @@ class TestObjectIdentifier(CommonMixin, TestCase):
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
-        oid_values_strat(),
+        oid_values_strategy(),
         oid_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -2131,10 +2383,13 @@ class TestObjectIdentifier(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(tuple(obj_decoded), tuple(obj_expled))
@@ -2190,9 +2445,15 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 data,
             )))
 
+    def test_x690_vector(self):
+        self.assertEqual(
+            ObjectIdentifier().decode(hexdec("0603883703"))[0],
+            ObjectIdentifier((2, 999, 3)),
+        )
+
 
 @composite
-def enumerated_values_strat(draw, schema=None, do_expl=False):
+def enumerated_values_strategy(draw, schema=None, do_expl=False):
     if schema is None:
         schema = list(draw(sets(text_printable, min_size=1, max_size=3)))
         values = list(draw(sets(
@@ -2307,7 +2568,7 @@ class TestEnumerated(CommonMixin, TestCase):
             default_initial,
             optional_initial,
             _decoded_initial,
-        ) = d.draw(enumerated_values_strat())
+        ) = d.draw(enumerated_values_strategy())
 
         class E(Enumerated):
             schema = schema_initial
@@ -2327,7 +2588,7 @@ class TestEnumerated(CommonMixin, TestCase):
             default,
             optional,
             _decoded,
-        ) = d.draw(enumerated_values_strat(
+        ) = d.draw(enumerated_values_strategy(
             schema=schema_initial,
             do_expl=impl_initial is None,
         ))
@@ -2362,7 +2623,7 @@ class TestEnumerated(CommonMixin, TestCase):
         self.assertEqual(obj.optional, optional)
         self.assertEqual(obj.specs, dict(schema_initial))
 
-    @given(enumerated_values_strat())
+    @given(enumerated_values_strategy())
     def test_copy(self, values):
         schema_input, value, impl, expl, default, optional, _decoded = values
 
@@ -2384,11 +2645,12 @@ class TestEnumerated(CommonMixin, TestCase):
     @given(data_strategy())
     def test_symmetric(self, d):
         schema_input, _, _, _, default, optional, _decoded = d.draw(
-            enumerated_values_strat(),
+            enumerated_values_strategy(),
         )
         tag_expl = d.draw(integers(min_value=1).map(tag_ctxc))
         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))
 
         class E(Enumerated):
             schema = schema_input
@@ -2407,10 +2669,13 @@ class TestEnumerated(CommonMixin, TestCase):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
         self.assertEqual(int(obj_decoded), int(obj_expled))
@@ -2432,7 +2697,7 @@ class TestEnumerated(CommonMixin, TestCase):
 
 
 @composite
-def string_values_strat(draw, alphabet, do_expl=False):
+def string_values_strategy(draw, alphabet, do_expl=False):
     bound_min, bound_max = sorted(draw(sets(
         integers(min_value=0, max_value=1 << 7),
         min_size=2,
@@ -2501,8 +2766,8 @@ class StringMixin(object):
     def test_comparison(self, d):
         value1 = d.draw(text(alphabet=self.text_alphabet()))
         value2 = d.draw(text(alphabet=self.text_alphabet()))
-        tag1 = d.draw(binary())
-        tag2 = d.draw(binary())
+        tag1 = d.draw(binary(min_size=1))
+        tag2 = d.draw(binary(min_size=1))
         obj1 = self.base_klass(value1)
         obj2 = self.base_klass(value2)
         self.assertEqual(obj1 == obj2, value1 == value2)
@@ -2548,7 +2813,7 @@ class StringMixin(object):
             default_initial,
             optional_initial,
             _decoded_initial,
-        ) = d.draw(string_values_strat(self.text_alphabet()))
+        ) = d.draw(string_values_strategy(self.text_alphabet()))
         obj_initial = self.base_klass(
             value_initial,
             bounds_initial,
@@ -2566,7 +2831,7 @@ class StringMixin(object):
             default,
             optional,
             _decoded,
-        ) = d.draw(string_values_strat(
+        ) = d.draw(string_values_strategy(
             self.text_alphabet(),
             do_expl=impl_initial is None,
         ))
@@ -2613,7 +2878,7 @@ class StringMixin(object):
 
     @given(data_strategy())
     def test_copy(self, d):
-        values = d.draw(string_values_strat(self.text_alphabet()))
+        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)
@@ -2696,10 +2961,11 @@ class StringMixin(object):
 
     @given(data_strategy())
     def test_symmetric(self, d):
-        values = d.draw(string_values_strat(self.text_alphabet()))
+        values = d.draw(string_values_strategy(self.text_alphabet()))
         value = d.draw(text(alphabet=self.text_alphabet()))
         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))
         _, _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
@@ -2716,10 +2982,13 @@ class StringMixin(object):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
         self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -2746,35 +3015,135 @@ class TestUTF8String(StringMixin, CommonMixin, TestCase):
     base_klass = UTF8String
 
 
+class UnicodeDecodeErrorMixin(object):
+    @given(text(
+        alphabet="".join(six_unichr(i) for i in list(range(0x0410, 0x044f + 1))),
+        min_size=1,
+        max_size=5,
+    ))
+    def test_unicode_decode_error(self, cyrillic_text):
+        with self.assertRaises(DecodeError):
+            self.base_klass(cyrillic_text)
+
+
 class TestNumericString(StringMixin, CommonMixin, TestCase):
     base_klass = NumericString
 
+    def text_alphabet(self):
+        return digits
+
+    @given(text(alphabet=ascii_letters, min_size=1, max_size=5))
+    def test_non_numeric(self, cyrillic_text):
+        with assertRaisesRegex(self, DecodeError, "non-numeric"):
+            self.base_klass(cyrillic_text)
+
+    @given(
+        sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
+        integers(min_value=0),
+        lists(integers()),
+    )
+    def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
+        decode_path = tuple(str(i) for i in 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 TestPrintableString(StringMixin, CommonMixin, TestCase):
+class TestPrintableString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = PrintableString
 
 
-class TestTeletexString(StringMixin, CommonMixin, TestCase):
+class TestTeletexString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = TeletexString
 
 
-class TestVideotexString(StringMixin, CommonMixin, TestCase):
+class TestVideotexString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = VideotexString
 
 
-class TestIA5String(StringMixin, CommonMixin, TestCase):
+class TestIA5String(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = IA5String
 
 
-class TestGraphicString(StringMixin, CommonMixin, TestCase):
+class TestGraphicString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = GraphicString
 
 
-class TestVisibleString(StringMixin, CommonMixin, TestCase):
+class TestVisibleString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = VisibleString
 
+    def test_x690_vector(self):
+        obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573"))
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertFalse(obj.bered)
+        self.assertFalse(obj.lenindef)
+
+        obj, tail = VisibleString().decode(
+            hexdec("3A0904034A6F6E04026573"),
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertTrue(obj.bered)
+        self.assertFalse(obj.lenindef)
+
+        obj, tail = VisibleString().decode(
+            hexdec("3A8004034A6F6E040265730000"),
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertTrue(obj.bered)
+        self.assertTrue(obj.lenindef)
+
 
-class TestGeneralString(StringMixin, CommonMixin, TestCase):
+class TestGeneralString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = GeneralString
 
 
@@ -2787,7 +3156,7 @@ class TestBMPString(StringMixin, CommonMixin, TestCase):
 
 
 @composite
-def generalized_time_values_strat(
+def generalized_time_values_strategy(
         draw,
         min_datetime,
         max_datetime,
@@ -2860,8 +3229,8 @@ class TimeMixin(object):
             min_value=self.min_datetime,
             max_value=self.max_datetime,
         ))
-        tag1 = d.draw(binary())
-        tag2 = d.draw(binary())
+        tag1 = d.draw(binary(min_size=1))
+        tag2 = d.draw(binary(min_size=1))
         if self.omit_ms:
             value1 = value1.replace(microsecond=0)
             value2 = value2.replace(microsecond=0)
@@ -2885,7 +3254,7 @@ class TimeMixin(object):
             default_initial,
             optional_initial,
             _decoded_initial,
-        ) = d.draw(generalized_time_values_strat(
+        ) = d.draw(generalized_time_values_strategy(
             min_datetime=self.min_datetime,
             max_datetime=self.max_datetime,
             omit_ms=self.omit_ms,
@@ -2905,7 +3274,7 @@ class TimeMixin(object):
             default,
             optional,
             _decoded,
-        ) = d.draw(generalized_time_values_strat(
+        ) = d.draw(generalized_time_values_strategy(
             min_datetime=self.min_datetime,
             max_datetime=self.max_datetime,
             omit_ms=self.omit_ms,
@@ -2940,7 +3309,7 @@ class TimeMixin(object):
 
     @given(data_strategy())
     def test_copy(self, d):
-        values = d.draw(generalized_time_values_strat(
+        values = d.draw(generalized_time_values_strategy(
             min_datetime=self.min_datetime,
             max_datetime=self.max_datetime,
         ))
@@ -2974,7 +3343,7 @@ class TimeMixin(object):
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
     def test_symmetric(self, d):
-        values = d.draw(generalized_time_values_strat(
+        values = d.draw(generalized_time_values_strategy(
             min_datetime=self.min_datetime,
             max_datetime=self.max_datetime,
         ))
@@ -2984,6 +3353,7 @@ class TimeMixin(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))
         _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
@@ -3000,10 +3370,13 @@ class TimeMixin(object):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.todatetime(), obj_expled.todatetime())
         self.assertEqual(obj_decoded.todatetime(), obj.todatetime())
@@ -3129,7 +3502,7 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
 
 
 @composite
-def any_values_strat(draw, do_expl=False):
+def any_values_strategy(draw, do_expl=False):
     value = draw(one_of(none(), binary()))
     expl = None
     if do_expl:
@@ -3208,7 +3581,7 @@ class TestAny(CommonMixin, TestCase):
                 expl_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(any_values_strat())
+            ) = d.draw(any_values_strategy())
             obj_initial = klass(
                 value_initial,
                 expl_initial,
@@ -3220,7 +3593,7 @@ class TestAny(CommonMixin, TestCase):
                 expl,
                 optional,
                 _decoded,
-            ) = d.draw(any_values_strat(do_expl=True))
+            ) = d.draw(any_values_strategy(do_expl=True))
             obj = obj_initial(value, expl, optional)
             if obj.ready:
                 value_expected = None if value is None else value
@@ -3239,7 +3612,7 @@ class TestAny(CommonMixin, TestCase):
         # override it, as Any does not have implicit tag
         pass
 
-    @given(any_values_strat())
+    @given(any_values_strategy())
     def test_copy(self, values):
         for klass in (Any, AnyInherited):
             obj = klass(*values)
@@ -3298,12 +3671,13 @@ class TestAny(CommonMixin, TestCase):
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
-        any_values_strat(),
+        any_values_strategy(),
         integers().map(lambda x: Integer(x).encode()),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (Any, AnyInherited):
             _, _, optional, _decoded = values
             obj = klass(value=value, optional=optional, _decoded=_decoded)
@@ -3316,10 +3690,13 @@ class TestAny(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
             self.assertEqual(bytes(obj_decoded), bytes(obj))
@@ -3343,7 +3720,7 @@ class TestAny(CommonMixin, TestCase):
 
 
 @composite
-def choice_values_strat(draw, value_required=False, schema=None, do_expl=False):
+def choice_values_strategy(draw, value_required=False, schema=None, do_expl=False):
     if schema is None:
         names = list(draw(sets(text_letters(), min_size=1, max_size=5)))
         tags = [tag_encode(tag) for tag in draw(sets(
@@ -3451,7 +3828,7 @@ class TestChoice(CommonMixin, TestCase):
                 default_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(choice_values_strat())
+            ) = d.draw(choice_values_strategy())
 
             class Wahl(klass):
                 schema = schema_initial
@@ -3469,7 +3846,7 @@ class TestChoice(CommonMixin, TestCase):
                 default,
                 optional,
                 _decoded,
-            ) = d.draw(choice_values_strat(schema=schema_initial, do_expl=True))
+            ) = d.draw(choice_values_strategy(schema=schema_initial, do_expl=True))
             obj = obj_initial(value, expl, default, optional)
             if obj.ready:
                 value_expected = default if value is None else value
@@ -3500,7 +3877,7 @@ class TestChoice(CommonMixin, TestCase):
         # override it, as Any does not have implicit tag
         pass
 
-    @given(choice_values_strat())
+    @given(choice_values_strategy())
     def test_copy(self, values):
         _schema, value, expl, default, optional, _decoded = values
 
@@ -3542,10 +3919,11 @@ class TestChoice(CommonMixin, TestCase):
     @given(data_strategy())
     def test_symmetric(self, d):
         _schema, value, _, default, optional, _decoded = d.draw(
-            choice_values_strat(value_required=True)
+            choice_values_strategy(value_required=True)
         )
         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))
 
         class Wahl(self.base_klass):
             schema = _schema
@@ -3564,10 +3942,13 @@ class TestChoice(CommonMixin, TestCase):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.choice, obj_expled.choice)
         self.assertEqual(obj_decoded.value, obj_expled.value)
@@ -3627,9 +4008,29 @@ class TestChoice(CommonMixin, TestCase):
         with self.assertRaises(TagMismatch):
             obj.decode(int_encoded)
 
+    def test_tag_mismatch_underlying(self):
+        class SeqOfBoolean(SequenceOf):
+            schema = Boolean()
+
+        class SeqOfInteger(SequenceOf):
+            schema = Integer()
+
+        class Wahl(Choice):
+            schema = (
+                ("erste", SeqOfBoolean()),
+            )
+
+        int_encoded = SeqOfInteger((Integer(123),)).encode()
+        bool_encoded = SeqOfBoolean((Boolean(False),)).encode()
+        obj = Wahl()
+        obj.decode(bool_encoded)
+        with self.assertRaises(TagMismatch) as err:
+            obj.decode(int_encoded)
+        self.assertEqual(err.exception.decode_path, ("erste", "0"))
+
 
 @composite
-def seq_values_strat(draw, seq_klass, do_expl=False):
+def seq_values_strategy(draw, seq_klass, do_expl=False):
     value = None
     if draw(booleans()):
         value = seq_klass()
@@ -3679,7 +4080,7 @@ def seq_values_strat(draw, seq_klass, do_expl=False):
 
 
 @composite
-def sequence_strat(draw, seq_klass):
+def sequence_strategy(draw, seq_klass):
     inputs = draw(lists(
         one_of(
             tuples(just(Boolean), booleans(), one_of(none(), booleans())),
@@ -3748,7 +4149,7 @@ def sequence_strat(draw, seq_klass):
 
 
 @composite
-def sequences_strat(draw, seq_klass):
+def sequences_strategy(draw, seq_klass):
     tags = draw(sets(integers(min_value=1), min_size=0, max_size=5))
     inits = [
         ({"expl": tag_ctxc(tag)} if expled else {"impl": tag_encode(tag)})
@@ -3771,7 +4172,7 @@ def sequences_strat(draw, seq_klass):
         max_size=len(tags),
     )))
     seq_expectses = draw(lists(
-        sequence_strat(seq_klass=seq_klass),
+        sequence_strategy(seq_klass=seq_klass),
         min_size=len(tags),
         max_size=len(tags),
     ))
@@ -3802,7 +4203,7 @@ def sequences_strat(draw, seq_klass):
 class SeqMixing(object):
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
-            self.base_klass((1, 2, 3))
+            self.base_klass(123)
         repr(err.exception)
 
     def test_invalid_value_type_set(self):
@@ -3876,7 +4277,7 @@ class SeqMixing(object):
                 default_initial,
                 optional_initial,
                 _decoded_initial,
-            ) = d.draw(seq_values_strat(seq_klass=klass))
+            ) = d.draw(seq_values_strategy(seq_klass=klass))
             obj_initial = klass(
                 value_initial,
                 schema_initial,
@@ -3894,7 +4295,7 @@ class SeqMixing(object):
                 default,
                 optional,
                 _decoded,
-            ) = d.draw(seq_values_strat(
+            ) = d.draw(seq_values_strategy(
                 seq_klass=klass,
                 do_expl=impl_initial is None,
             ))
@@ -3924,7 +4325,7 @@ class SeqMixing(object):
         class SeqInherited(self.base_klass):
             pass
         for klass in (self.base_klass, SeqInherited):
-            values = d.draw(seq_values_strat(seq_klass=klass))
+            values = d.draw(seq_values_strategy(seq_klass=klass))
             obj = klass(*values)
             obj_copied = obj.copy()
             self.assert_copied_basic_fields(obj, obj_copied)
@@ -4032,15 +4433,16 @@ class SeqMixing(object):
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
     def test_symmetric(self, d):
-        seq, expects = d.draw(sequence_strat(seq_klass=self.base_klass))
+        seq, expects = d.draw(sequence_strategy(seq_klass=self.base_klass))
+        tail_junk = d.draw(binary(max_size=5))
         self.assertTrue(seq.ready)
         self.assertFalse(seq.decoded)
         self._assert_expects(seq, expects)
         repr(seq)
         pprint(seq)
         seq_encoded = seq.encode()
-        seq_decoded, tail = seq.decode(seq_encoded)
-        self.assertEqual(tail, b"")
+        seq_decoded, tail = seq.decode(seq_encoded + tail_junk)
+        self.assertEqual(tail, tail_junk)
         self.assertTrue(seq.ready)
         self._assert_expects(seq_decoded, expects)
         self.assertEqual(seq, seq_decoded)
@@ -4062,7 +4464,7 @@ class SeqMixing(object):
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
     def test_symmetric_with_seq(self, d):
-        seq, expect_outers = d.draw(sequences_strat(seq_klass=self.base_klass))
+        seq, expect_outers = d.draw(sequences_strategy(seq_klass=self.base_klass))
         self.assertTrue(seq.ready)
         seq_encoded = seq.encode()
         seq_decoded, tail = seq.decode(seq_encoded)
@@ -4203,6 +4605,16 @@ class TestSequence(SeqMixing, CommonMixin, TestCase):
             seq[missing] = Boolean()
         repr(err.exception)
 
+    def test_x690_vector(self):
+        class Seq(Sequence):
+            schema = (
+                ("name", IA5String()),
+                ("ok", Boolean()),
+            )
+        seq = Seq().decode(hexdec("300A1605536d6974680101FF"))[0]
+        self.assertEqual(seq["name"], "Smith")
+        self.assertEqual(seq["ok"], True)
+
 
 class TestSet(SeqMixing, CommonMixin, TestCase):
     base_klass = Set
@@ -4229,7 +4641,7 @@ class TestSet(SeqMixing, CommonMixin, TestCase):
 
 
 @composite
-def seqof_values_strat(draw, schema=None, do_expl=False):
+def seqof_values_strategy(draw, schema=None, do_expl=False):
     if schema is None:
         schema = draw(sampled_from((Boolean(), Integer())))
     bound_min, bound_max = sorted(draw(sets(
@@ -4404,7 +4816,7 @@ class SeqOfMixing(object):
             default_initial,
             optional_initial,
             _decoded_initial,
-        ) = d.draw(seqof_values_strat())
+        ) = d.draw(seqof_values_strategy())
 
         class SeqOf(self.base_klass):
             schema = schema_initial
@@ -4426,7 +4838,7 @@ class SeqOfMixing(object):
             default,
             optional,
             _decoded,
-        ) = d.draw(seqof_values_strat(
+        ) = d.draw(seqof_values_strategy(
             schema=schema_initial,
             do_expl=impl_initial is None,
         ))
@@ -4479,7 +4891,7 @@ class SeqOfMixing(object):
             bounds or bounds_initial or (0, float("+inf")),
         )
 
-    @given(seqof_values_strat())
+    @given(seqof_values_strategy())
     def test_copy(self, values):
         _schema, value, bounds, impl, expl, default, optional, _decoded = values
 
@@ -4564,12 +4976,13 @@ class SeqOfMixing(object):
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
-        seqof_values_strat(schema=Integer()),
+        seqof_values_strategy(schema=Integer()),
         lists(integers().map(Integer)),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         _, _, _, _, _, default, optional, _decoded = values
 
         class SeqOf(self.base_klass):
@@ -4589,10 +5002,13 @@ class SeqOfMixing(object):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self._test_symmetric_compare_objs(obj_decoded, obj_expled)
         self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
         self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
@@ -4861,3 +5277,296 @@ class TestAutoAddSlots(TestCase):
         with self.assertRaises(AttributeError):
             inher = Inher()
             inher.unexistent = "whatever"
+
+
+class TestOIDDefines(TestCase):
+    @given(data_strategy())
+    def runTest(self, d):
+        value_names = list(d.draw(sets(text_letters(), min_size=1, max_size=10)))
+        value_name_chosen = d.draw(sampled_from(value_names))
+        oids = [
+            ObjectIdentifier(oid)
+            for oid in d.draw(sets(oid_strategy(), min_size=2, max_size=10))
+        ]
+        oid_chosen = d.draw(sampled_from(oids))
+        values = d.draw(lists(
+            integers(),
+            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))))
+
+        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)
+
+
+class TestDefinesByPath(TestCase):
+    def test_generated(self):
+        class Seq(Sequence):
+            schema = (
+                ("type", ObjectIdentifier()),
+                ("value", OctetString(expl=tag_ctxc(123))),
+            )
+
+        class SeqInner(Sequence):
+            schema = (
+                ("typeInner", ObjectIdentifier()),
+                ("valueInner", Any()),
+            )
+
+        class PairValue(SetOf):
+            schema = Any()
+
+        class Pair(Sequence):
+            schema = (
+                ("type", ObjectIdentifier()),
+                ("value", PairValue()),
+            )
+
+        class Pairs(SequenceOf):
+            schema = Pair()
+
+        (
+            type_integered,
+            type_sequenced,
+            type_innered,
+            type_octet_stringed,
+        ) = [
+            ObjectIdentifier(oid)
+            for oid in sets(oid_strategy(), min_size=4, max_size=4).example()
+        ]
+        seq_integered = Seq()
+        seq_integered["type"] = type_integered
+        seq_integered["value"] = OctetString(Integer(123).encode())
+        seq_integered_raw = seq_integered.encode()
+
+        pairs = Pairs()
+        pairs_input = (
+            (type_octet_stringed, OctetString(b"whatever")),
+            (type_integered, Integer(123)),
+            (type_octet_stringed, OctetString(b"whenever")),
+            (type_integered, Integer(234)),
+        )
+        for t, v in pairs_input:
+            pair = Pair()
+            pair["type"] = t
+            pair["value"] = PairValue((Any(v),))
+            pairs.append(pair)
+        seq_inner = SeqInner()
+        seq_inner["typeInner"] = type_innered
+        seq_inner["valueInner"] = Any(pairs)
+        seq_sequenced = Seq()
+        seq_sequenced["type"] = type_sequenced
+        seq_sequenced["value"] = OctetString(seq_inner.encode())
+        seq_sequenced_raw = seq_sequenced.encode()
+
+        defines_by_path = []
+        seq_integered, _ = Seq().decode(seq_integered_raw)
+        self.assertIsNone(seq_integered["value"].defined)
+        defines_by_path.append(
+            (("type",), ((("value",), {
+                type_integered: Integer(),
+                type_sequenced: SeqInner(),
+            }),))
+        )
+        seq_integered, _ = Seq().decode(
+            seq_integered_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_integered["value"].defined)
+        self.assertEqual(seq_integered["value"].defined[0], type_integered)
+        self.assertEqual(seq_integered["value"].defined[1], Integer(123))
+        self.assertTrue(seq_integered_raw[
+            seq_integered["value"].defined[1].offset:
+        ].startswith(Integer(123).encode()))
+
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_sequenced["value"].defined)
+        self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
+        seq_inner = seq_sequenced["value"].defined[1]
+        self.assertIsNone(seq_inner["valueInner"].defined)
+
+        defines_by_path.append((
+            ("value", DecodePathDefBy(type_sequenced), "typeInner"),
+            ((("valueInner",), {type_innered: Pairs()}),),
+        ))
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_sequenced["value"].defined)
+        self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
+        seq_inner = seq_sequenced["value"].defined[1]
+        self.assertIsNotNone(seq_inner["valueInner"].defined)
+        self.assertEqual(seq_inner["valueInner"].defined[0], type_innered)
+        pairs = seq_inner["valueInner"].defined[1]
+        for pair in pairs:
+            self.assertIsNone(pair["value"][0].defined)
+
+        defines_by_path.append((
+            (
+                "value",
+                DecodePathDefBy(type_sequenced),
+                "valueInner",
+                DecodePathDefBy(type_innered),
+                any,
+                "type",
+            ),
+            ((("value",), {
+                type_integered: Integer(),
+                type_octet_stringed: OctetString(),
+            }),),
+        ))
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_sequenced["value"].defined)
+        self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
+        seq_inner = seq_sequenced["value"].defined[1]
+        self.assertIsNotNone(seq_inner["valueInner"].defined)
+        self.assertEqual(seq_inner["valueInner"].defined[0], type_innered)
+        pairs_got = seq_inner["valueInner"].defined[1]
+        for pair_input, pair_got in zip(pairs_input, pairs_got):
+            self.assertEqual(pair_got["value"][0].defined[0], pair_input[0])
+            self.assertEqual(pair_got["value"][0].defined[1], pair_input[1])
+
+    @given(oid_strategy(), integers())
+    def test_simple(self, oid, tgt):
+        class Inner(Sequence):
+            schema = (
+                ("oid", ObjectIdentifier(defines=((("..", "tgt"), {
+                    ObjectIdentifier(oid): Integer(),
+                }),))),
+            )
+
+        class Outer(Sequence):
+            schema = (
+                ("inner", Inner()),
+                ("tgt", OctetString()),
+            )
+
+        inner = Inner()
+        inner["oid"] = ObjectIdentifier(oid)
+        outer = Outer()
+        outer["inner"] = inner
+        outer["tgt"] = OctetString(Integer(tgt).encode())
+        decoded, _ = Outer().decode(outer.encode())
+        self.assertEqual(decoded["tgt"].defined[1], Integer(tgt))
+
+
+class TestAbsDecodePath(TestCase):
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1)).map(tuple),
+        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,
+        )
+
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1)).map(tuple),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_abs(self, decode_path, rel_path):
+        self.assertSequenceEqual(
+            abs_decode_path(decode_path, ("/",) + rel_path),
+            rel_path,
+        )
+
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=5).map(tuple),
+        integers(min_value=1, max_value=3),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_dots(self, decode_path, number_of_dots, rel_path):
+        self.assertSequenceEqual(
+            abs_decode_path(decode_path, tuple([".."] * number_of_dots) + rel_path),
+            decode_path[:-number_of_dots] + rel_path,
+        )
+
+
+class TestStrictDefaultExistence(TestCase):
+    @given(data_strategy())
+    def runTest(self, d):
+        count = d.draw(integers(min_value=1, max_value=10))
+        chosen = d.draw(integers(min_value=0, max_value=count - 1))
+        _schema = [
+            ("int%d" % i, Integer(expl=tag_ctxc(i + 1)))
+            for i in range(count)
+        ]
+
+        class Seq(Sequence):
+            schema = _schema
+        seq = Seq()
+        for i in range(count):
+            seq["int%d" % i] = Integer(123)
+        raw = seq.encode()
+        chosen = "int%d" % chosen
+        seq.specs[chosen] = seq.specs[chosen](default=123)
+        seq.decode(raw)
+        with assertRaisesRegex(self, DecodeError, "DEFAULT value met"):
+            seq.decode(raw, ctx={"strict_default_existence": True})
+
+
+class TestX690PrefixedType(TestCase):
+    def runTest(self):
+        self.assertSequenceEqual(
+            VisibleString("Jones").encode(),
+            hexdec("1A054A6F6E6573"),
+        )
+        self.assertSequenceEqual(
+            VisibleString(
+                "Jones",
+                impl=tag_encode(3, klass=TagClassApplication),
+            ).encode(),
+            hexdec("43054A6F6E6573"),
+        )
+        self.assertSequenceEqual(
+            Any(
+                VisibleString(
+                    "Jones",
+                    impl=tag_encode(3, klass=TagClassApplication),
+                ),
+                expl=tag_ctxc(2),
+            ).encode(),
+            hexdec("A20743054A6F6E6573"),
+        )
+        self.assertSequenceEqual(
+            OctetString(
+                VisibleString(
+                    "Jones",
+                    impl=tag_encode(3, klass=TagClassApplication),
+                ).encode(),
+                impl=tag_encode(7, form=TagFormConstructed, klass=TagClassApplication),
+            ).encode(),
+            hexdec("670743054A6F6E6573"),
+        )
+        self.assertSequenceEqual(
+            VisibleString("Jones", impl=tag_ctxp(2)).encode(),
+            hexdec("82054A6F6E6573"),
+        )