]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
Convenient decod() helper method
[pyderasn.git] / tests / test_pyderasn.py
index 723020c4820802e2970d87712bbb879a36146ece..9fbb33eb39f2d75231721cee73de95a32d66d717 100644 (file)
@@ -1,11 +1,10 @@
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
-# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
+# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
+# Copyright (C) 2017-2020 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
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -16,6 +15,7 @@
 # License along with this program.  If not, see
 # <http://www.gnu.org/licenses/>.
 
+from copy import deepcopy
 from datetime import datetime
 from string import ascii_letters
 from string import digits
@@ -43,6 +43,7 @@ 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
@@ -62,6 +63,9 @@ 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 ExceedingData
 from pyderasn import GeneralizedTime
 from pyderasn import GeneralString
 from pyderasn import GraphicString
@@ -74,6 +78,11 @@ from pyderasn import InvalidOID
 from pyderasn import InvalidValueType
 from pyderasn import len_decode
 from pyderasn import len_encode
+from pyderasn import LEN_YYMMDDHHMMSSZ
+from pyderasn import LEN_YYYYMMDDHHMMSSDMZ
+from pyderasn import LEN_YYYYMMDDHHMMSSZ
+from pyderasn import LENINDEF
+from pyderasn import LenIndefForm
 from pyderasn import NotEnoughData
 from pyderasn import Null
 from pyderasn import NumericString
@@ -110,7 +119,6 @@ from pyderasn import VisibleString
 
 settings.register_profile("local", settings(
     deadline=5000,
-    perform_health_check=False,
 ))
 settings.load_profile("local")
 LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4
@@ -122,6 +130,16 @@ tag_classes = sampled_from((
     TagClassUniversal,
 ))
 tag_forms = sampled_from((TagFormConstructed, TagFormPrimitive))
+decode_path_strat = lists(integers(), max_size=3).map(
+    lambda decode_path: tuple(str(dp) for dp in decode_path)
+)
+ctx_dummy = dictionaries(integers(), integers(), min_size=2, max_size=4).example()
+
+
+def assert_exceeding_data(self, call, junk):
+    if len(junk) > 0:
+        with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)):
+            call()
 
 
 class TestHex(TestCase):
@@ -285,7 +303,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)
@@ -294,7 +312,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
@@ -362,14 +380,16 @@ class TestBoolean(CommonMixin, TestCase):
         obj = Boolean()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         obj = Boolean(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(booleans(), booleans(), binary(), binary())
     def test_comparison(self, value1, value2, tag1, tag2):
@@ -460,10 +480,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean().decode(
                 tag_encode(tag)[:-1],
@@ -477,10 +496,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_expl_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean(expl=Boolean.tag_default).decode(
                 tag_encode(tag)[:-1],
@@ -494,10 +512,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean().decode(
                 Boolean.tag_default + len_encode(l)[:-1],
@@ -511,10 +528,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_expl_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean(expl=Boolean.tag_default).decode(
                 Boolean.tag_default + len_encode(l)[:-1],
@@ -543,20 +559,26 @@ class TestBoolean(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
-            pprint(obj_expled)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            ctx_copied = deepcopy(ctx_dummy)
             obj_decoded, tail = obj_expled.decode(
                 obj_expled_encoded + tail_junk,
                 offset=offset,
+                ctx=ctx_copied,
             )
+            self.assertDictEqual(ctx_copied, ctx_dummy)
             repr(obj_decoded)
-            pprint(obj_decoded)
+            list(obj_decoded.pps())
+            pprint(obj_decoded, big_blobs=True, with_decode_path=True)
             self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
@@ -576,6 +598,11 @@ class TestBoolean(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=2))
     def test_invalid_len(self, l):
@@ -587,13 +614,111 @@ 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.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertTrue(obj.bered)
+
+    @given(
+        integers(min_value=1).map(tag_ctxc),
+        binary().filter(lambda x: not x.startswith(EOC)),
+    )
+    def test_ber_expl_no_eoc(self, expl, junk):
+        encoded = expl + LENINDEF + Boolean(False).encode()
+        with self.assertRaises(LenIndefForm):
+            Boolean(expl=expl).decode(encoded + junk)
+        with assertRaisesRegex(self, DecodeError, "no EOC"):
+            Boolean(expl=expl).decode(encoded + junk, ctx={"bered": True})
+        obj, tail = Boolean(expl=expl).decode(
+            encoded + EOC + junk,
+            ctx={"bered": True},
+        )
+        self.assertTrue(obj.expl_lenindef)
+        self.assertFalse(obj.lenindef)
+        self.assertFalse(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.expl_lenindef)
+        self.assertFalse(obj.lenindef)
+        self.assertFalse(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        self.assertSequenceEqual(tail, junk)
+        repr(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
+
+    @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 +
+                LENINDEF +
+                Boolean(value).encode() +
+                EOC
+            )
+        encoded = SequenceOf.tag_default + len_encode(len(encoded)) + encoded
+
+        class SeqOf(SequenceOf):
+            schema = Boolean(expl=expl)
+        with self.assertRaises(LenIndefForm):
+            SeqOf().decode(encoded)
+        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.ber_encoded,
+                    v.lenindef,
+                    v.expl_lenindef,
+                    v.bered,
+                ) for v in seqof
+            ),
+            set(((
+                3 + EOC_LEN,
+                len(expl) + 1 + 3 + EOC_LEN,
+                len(expl),
+                1,
+                False,
+                False,
+                True,
+                True,
+            ),)),
+        )
+        repr(seqof)
+        list(seqof.pps())
+        pprint(seqof, big_blobs=True, with_decode_path=True)
 
 
 @composite
@@ -672,14 +797,16 @@ class TestInteger(CommonMixin, TestCase):
         obj = Integer()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         obj = Integer(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         hash(obj)
 
     @given(integers(), integers(), binary(), binary())
@@ -731,9 +858,19 @@ class TestInteger(CommonMixin, TestCase):
         with self.assertRaises(BoundsError) as err:
             Integer(value=values[0], bounds=(values[1], values[2]))
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            Integer(bounds=(values[1], values[2])).decode(
+                Integer(values[0]).encode()
+            )
+        repr(err.exception)
         with self.assertRaises(BoundsError) as err:
             Integer(value=values[2], bounds=(values[0], values[1]))
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            Integer(bounds=(values[0], values[1])).decode(
+                Integer(values[2]).encode()
+            )
+        repr(err.exception)
 
     @given(data_strategy())
     def test_call(self, d):
@@ -852,10 +989,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Integer().decode(
                 tag_encode(tag)[:-1],
@@ -869,10 +1005,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Integer().decode(
                 Integer.tag_default + len_encode(l)[:-1],
@@ -886,10 +1021,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         sets(integers(), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     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 Int(Integer):
@@ -922,20 +1056,26 @@ class TestInteger(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
-            pprint(obj_expled)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            ctx_copied = deepcopy(ctx_dummy)
             obj_decoded, tail = obj_expled.decode(
                 obj_expled_encoded + tail_junk,
                 offset=offset,
+                ctx=ctx_copied,
             )
+            self.assertDictEqual(ctx_copied, ctx_dummy)
             repr(obj_decoded)
-            pprint(obj_decoded)
+            list(obj_decoded.pps())
+            pprint(obj_decoded, big_blobs=True, with_decode_path=True)
             self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
@@ -955,6 +1095,11 @@ class TestInteger(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     def test_go_vectors_valid(self):
         for data, expect in ((
@@ -1009,7 +1154,7 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl=
 
     def _value(value_required):
         if not value_required and draw(booleans()):
-            return
+            return None
         generation_choice = 0
         if value_required:
             generation_choice = draw(sampled_from((1, 2, 3)))
@@ -1018,9 +1163,9 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl=
                 sampled_from(("0", "1")),
                 max_size=len(schema),
             )))
-        elif generation_choice == 2 or draw(booleans()):
+        if generation_choice == 2 or draw(booleans()):
             return draw(binary(max_size=len(schema) // 8))
-        elif generation_choice == 3 or draw(booleans()):
+        if generation_choice == 3 or draw(booleans()):
             return tuple(draw(lists(sampled_from([name for name, _ in schema]))))
         return None
     value = _value(value_required)
@@ -1126,20 +1271,22 @@ class TestBitString(CommonMixin, TestCase):
         obj = BitString()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         obj = BitString(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @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):
@@ -1246,10 +1393,9 @@ class TestBitString(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             BitString().decode(
                 tag_encode(tag)[:-1],
@@ -1263,10 +1409,9 @@ class TestBitString(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             BitString().decode(
                 BitString.tag_default + len_encode(l)[:-1],
@@ -1302,20 +1447,26 @@ class TestBitString(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
-            pprint(obj_expled)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            ctx_copied = deepcopy(ctx_dummy)
             obj_decoded, tail = obj_expled.decode(
                 obj_expled_encoded + tail_junk,
                 offset=offset,
+                ctx=ctx_copied,
             )
+            self.assertDictEqual(ctx_copied, ctx_dummy)
             repr(obj_decoded)
-            pprint(obj_decoded)
+            list(obj_decoded.pps())
+            pprint(obj_decoded, big_blobs=True, with_decode_path=True)
             self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
@@ -1339,6 +1490,11 @@ class TestBitString(CommonMixin, TestCase):
                 self.assertSetEqual(set(value), set(obj_decoded.named))
                 for name in value:
                     obj_decoded[name]
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=1, max_value=255))
     def test_bad_zero_value(self, pad_size):
@@ -1386,6 +1542,219 @@ 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),
+        binary(),
+    )
+    def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk):
+        def chunk_constructed(contents):
+            return (
+                tag_encode(form=TagFormConstructed, num=3) +
+                LENINDEF +
+                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) +
+            LENINDEF +
+            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 + junk,
+                ctx={"bered": True},
+            )
+            self.assertSequenceEqual(tail, junk)
+            self.assertEqual(obj.bit_len, bit_len_expected)
+            self.assertSequenceEqual(bytes(obj), payload_expected)
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertTrue(obj.bered)
+            obj = obj.copy()
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertTrue(obj.bered)
+            self.assertEqual(len(encoded), obj.tlvlen)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_too_short(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "longer than data") as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + len_encode(1),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_no_data(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "zero length") as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + len_encode(0),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks):
+        bs = BitString(b"data").encode()
+        with self.assertRaises(NotEnoughData) as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + LENINDEF + chunks * bs,
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks):
+        bs = BitString(b"data").encode()
+        bs_longer = BitString(b"data-longer").encode()
+        with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err:
+            BitString().decode(
+                (
+                    tag_encode(3, form=TagFormConstructed) +
+                    len_encode((chunks + 1) * len(bs)) +
+                    chunks * bs +
+                    bs_longer
+                ),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_indefinite_no_chunks(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "no chunks") as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + LENINDEF + EOC,
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(data_strategy())
+    def test_ber_indefinite_not_multiple(self, d):
+        bs_short = BitString("'A'H").encode()
+        bs_full = BitString("'AA'H").encode()
+        chunks = [bs_full for _ in range(d.draw(integers(min_value=0, max_value=3)))]
+        chunks.append(bs_short)
+        d.draw(permutations(chunks))
+        chunks.append(bs_short)
+        offset = d.draw(integers(min_value=0))
+        decode_path = d.draw(decode_path_strat)
+        with assertRaisesRegex(self, DecodeError, "multiple of 8 bits") as err:
+            BitString().decode(
+                (
+                    tag_encode(3, form=TagFormConstructed) +
+                    LENINDEF +
+                    b"".join(chunks) +
+                    EOC
+                ),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(
+            err.exception.decode_path,
+            decode_path + (str(chunks.index(bs_short)),),
+        )
+        self.assertEqual(
+            err.exception.offset,
+            offset + 1 + 1 + chunks.index(bs_short) * len(bs_full),
+        )
+
+    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.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
+
 
 @composite
 def octet_string_values_strategy(draw, do_expl=False):
@@ -1442,16 +1811,18 @@ class TestOctetString(CommonMixin, TestCase):
         obj = OctetString()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         obj = OctetString(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
-    @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)
@@ -1486,10 +1857,20 @@ class TestOctetString(CommonMixin, TestCase):
         with self.assertRaises(BoundsError) as err:
             OctetString(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            OctetString(bounds=(bound_min, bound_max)).decode(
+                OctetString(value).encode()
+            )
+        repr(err.exception)
         value = d.draw(binary(min_size=bound_max + 1))
         with self.assertRaises(BoundsError) as err:
             OctetString(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            OctetString(bounds=(bound_min, bound_max)).decode(
+                OctetString(value).encode()
+            )
+        repr(err.exception)
 
     @given(data_strategy())
     def test_call(self, d):
@@ -1593,10 +1974,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             OctetString().decode(
                 tag_encode(tag)[:-1],
@@ -1610,10 +1990,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             OctetString().decode(
                 OctetString.tag_default + len_encode(l)[:-1],
@@ -1627,10 +2006,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     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(OctetString):
@@ -1663,20 +2041,26 @@ class TestOctetString(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
-            pprint(obj_expled)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            ctx_copied = deepcopy(ctx_dummy)
             obj_decoded, tail = obj_expled.decode(
                 obj_expled_encoded + tail_junk,
                 offset=offset,
+                ctx=ctx_copied,
             )
+            self.assertDictEqual(ctx_copied, ctx_dummy)
             repr(obj_decoded)
-            pprint(obj_decoded)
+            list(obj_decoded.pps())
+            pprint(obj_decoded, big_blobs=True, with_decode_path=True)
             self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
@@ -1696,6 +2080,132 @@ class TestOctetString(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
+
+    @given(
+        integers(min_value=1, max_value=30),
+        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,
+        ),
+        binary(),
+    )
+    def test_constructed(self, impl, chunk_inputs, junk):
+        def chunk_constructed(contents):
+            return (
+                tag_encode(form=TagFormConstructed, num=4) +
+                LENINDEF +
+                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) +
+            LENINDEF +
+            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 + junk,
+                ctx={"bered": True},
+            )
+            self.assertSequenceEqual(tail, junk)
+            self.assertSequenceEqual(bytes(obj), payload_expected)
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertTrue(obj.bered)
+            obj = obj.copy()
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertTrue(obj.bered)
+            self.assertEqual(len(encoded), obj.tlvlen)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_too_short(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "longer than data") as err:
+            OctetString().decode(
+                tag_encode(4, form=TagFormConstructed) + len_encode(1),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks):
+        bs = OctetString(b"data").encode()
+        with self.assertRaises(NotEnoughData) as err:
+            OctetString().decode(
+                tag_encode(4, form=TagFormConstructed) + LENINDEF + chunks * bs,
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks):
+        bs = OctetString(b"data").encode()
+        bs_longer = OctetString(b"data-longer").encode()
+        with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err:
+            OctetString().decode(
+                (
+                    tag_encode(4, form=TagFormConstructed) +
+                    len_encode((chunks + 1) * len(bs)) +
+                    chunks * bs +
+                    bs_longer
+                ),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
 
 
 @composite
@@ -1726,7 +2236,8 @@ class TestNull(CommonMixin, TestCase):
         obj = Null()
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(binary(), binary())
     def test_comparison(self, tag1, tag2):
@@ -1793,10 +2304,9 @@ class TestNull(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Null().decode(
                 tag_encode(tag)[:-1],
@@ -1810,10 +2320,9 @@ class TestNull(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Null().decode(
                 Null.tag_default + len_encode(l)[:-1],
@@ -1841,20 +2350,26 @@ class TestNull(CommonMixin, TestCase):
             _, _, optional, _decoded = values
             obj = klass(optional=optional, _decoded=_decoded)
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
             obj_expled = obj(expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
-            pprint(obj_expled)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            ctx_copied = deepcopy(ctx_dummy)
             obj_decoded, tail = obj_expled.decode(
                 obj_expled_encoded + tail_junk,
                 offset=offset,
+                ctx=ctx_copied,
             )
+            self.assertDictEqual(ctx_copied, ctx_dummy)
             repr(obj_decoded)
-            pprint(obj_decoded)
+            list(obj_decoded.pps())
+            pprint(obj_decoded, big_blobs=True, with_decode_path=True)
             self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
@@ -1872,6 +2387,11 @@ class TestNull(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=1))
     def test_invalid_len(self, l):
@@ -1935,14 +2455,17 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         obj = ObjectIdentifier()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         obj = ObjectIdentifier(value)
         self.assertTrue(obj.ready)
+        self.assertFalse(obj.ber_encoded)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         hash(obj)
 
     @given(oid_strategy(), oid_strategy(), binary(), binary())
@@ -2065,10 +2588,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             ObjectIdentifier().decode(
                 tag_encode(tag)[:-1],
@@ -2082,10 +2604,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             ObjectIdentifier().decode(
                 ObjectIdentifier.tag_default + len_encode(l)[:-1],
@@ -2150,7 +2671,7 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         self.assertEqual(obj, ObjectIdentifier(".".join(str(arc) for arc in oid)))
         str(obj)
         repr(obj)
-        pprint(obj)
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
@@ -2170,20 +2691,26 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
-            pprint(obj_expled)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            ctx_copied = deepcopy(ctx_dummy)
             obj_decoded, tail = obj_expled.decode(
                 obj_expled_encoded + tail_junk,
                 offset=offset,
+                ctx=ctx_copied,
             )
+            self.assertDictEqual(ctx_copied, ctx_dummy)
             repr(obj_decoded)
-            pprint(obj_decoded)
+            list(obj_decoded.pps())
+            pprint(obj_decoded, big_blobs=True, with_decode_path=True)
             self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
@@ -2203,6 +2730,11 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         oid_strategy().map(ObjectIdentifier),
@@ -2240,6 +2772,61 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 data,
             )))
 
+    def test_x690_vector(self):
+        self.assertEqual(
+            ObjectIdentifier().decode(hexdec("0603883703"))[0],
+            ObjectIdentifier((2, 999, 3)),
+        )
+
+    def test_nonnormalized_first_arc(self):
+        tampered = (
+            ObjectIdentifier.tag_default +
+            len_encode(2) +
+            b'\x80' +
+            ObjectIdentifier((1, 0)).encode()[-1:]
+        )
+        obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True})
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"):
+            ObjectIdentifier().decode(tampered)
+
+    @given(data_strategy())
+    def test_nonnormalized_arcs(self, d):
+        arcs = d.draw(lists(
+            integers(min_value=0, max_value=100),
+            min_size=1,
+            max_size=5,
+        ))
+        dered = ObjectIdentifier((1, 0) + tuple(arcs)).encode()
+        _, _, lv = tag_strip(dered)
+        _, _, v = len_decode(lv)
+        v_no_first_arc = v[1:]
+        idx_for_tamper = d.draw(integers(
+            min_value=0,
+            max_value=len(v_no_first_arc) - 1,
+        ))
+        tampered = list(bytearray(v_no_first_arc))
+        for _ in range(d.draw(integers(min_value=1, max_value=3))):
+            tampered.insert(idx_for_tamper, 0x80)
+        tampered = bytes(bytearray(tampered))
+        tampered = (
+            ObjectIdentifier.tag_default +
+            len_encode(len(tampered)) +
+            tampered
+        )
+        obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True})
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"):
+            ObjectIdentifier().decode(tampered)
+
 
 @composite
 def enumerated_values_strategy(draw, schema=None, do_expl=False):
@@ -2317,14 +2904,16 @@ class TestEnumerated(CommonMixin, TestCase):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         obj = self.base_klass("whatever")
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(integers(), integers(), binary(), binary())
     def test_comparison(self, value1, value2, tag1, tag2):
@@ -2450,20 +3039,26 @@ class TestEnumerated(CommonMixin, TestCase):
             _decoded=_decoded,
         )
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertFalse(obj.expled)
         obj_encoded = obj.encode()
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
-        pprint(obj_expled)
+        list(obj_expled.pps())
+        pprint(obj_expled, big_blobs=True, with_decode_path=True)
         obj_expled_encoded = obj_expled.encode()
+        ctx_copied = deepcopy(ctx_dummy)
         obj_decoded, tail = obj_expled.decode(
             obj_expled_encoded + tail_junk,
             offset=offset,
+            ctx=ctx_copied,
         )
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         repr(obj_decoded)
-        pprint(obj_decoded)
+        list(obj_decoded.pps())
+        pprint(obj_decoded, big_blobs=True, with_decode_path=True)
         self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
@@ -2483,6 +3078,11 @@ class TestEnumerated(CommonMixin, TestCase):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 @composite
@@ -2539,7 +3139,8 @@ class StringMixin(object):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         text_type(obj)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
@@ -2548,15 +3149,16 @@ class StringMixin(object):
         obj = self.base_klass(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         text_type(obj)
 
     @given(data_strategy())
     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)
@@ -2587,10 +3189,20 @@ class StringMixin(object):
         with self.assertRaises(BoundsError) as err:
             self.base_klass(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            self.base_klass(bounds=(bound_min, bound_max)).decode(
+                self.base_klass(value).encode()
+            )
+        repr(err.exception)
         value = d.draw(text(alphabet=self.text_alphabet(), min_size=bound_max + 1))
         with self.assertRaises(BoundsError) as err:
             self.base_klass(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            self.base_klass(bounds=(bound_min, bound_max)).decode(
+                self.base_klass(value).encode()
+            )
+        repr(err.exception)
 
     @given(data_strategy())
     def test_call(self, d):
@@ -2694,10 +3306,9 @@ class StringMixin(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -2711,10 +3322,9 @@ class StringMixin(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -2728,10 +3338,9 @@ class StringMixin(object):
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     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):
@@ -2763,20 +3372,26 @@ class StringMixin(object):
             _decoded=_decoded,
         )
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertFalse(obj.expled)
         obj_encoded = obj.encode()
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
-        pprint(obj_expled)
+        list(obj_expled.pps())
+        pprint(obj_expled, big_blobs=True, with_decode_path=True)
         obj_expled_encoded = obj_expled.encode()
+        ctx_copied = deepcopy(ctx_dummy)
         obj_decoded, tail = obj_expled.decode(
             obj_expled_encoded + tail_junk,
             offset=offset,
+            ctx=ctx_copied,
         )
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         repr(obj_decoded)
-        pprint(obj_decoded)
+        list(obj_decoded.pps())
+        pprint(obj_decoded, big_blobs=True, with_decode_path=True)
         self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
@@ -2798,18 +3413,26 @@ class StringMixin(object):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 class TestUTF8String(StringMixin, CommonMixin, TestCase):
     base_klass = UTF8String
 
 
+cyrillic_letters = text(
+    alphabet="".join(six_unichr(i) for i in list(range(0x0410, 0x044f + 1))),
+    min_size=1,
+    max_size=5,
+)
+
+
 class UnicodeDecodeErrorMixin(object):
-    @given(text(
-        alphabet="".join(six_unichr(i) for i in list(range(0x0410, 0x044f + 1))),
-        min_size=1,
-        max_size=5,
-    ))
+    @given(cyrillic_letters)
     def test_unicode_decode_error(self, cyrillic_text):
         with self.assertRaises(DecodeError):
             self.base_klass(cyrillic_text)
@@ -2819,20 +3442,19 @@ class TestNumericString(StringMixin, CommonMixin, TestCase):
     base_klass = NumericString
 
     def text_alphabet(self):
-        return digits
+        return digits + " "
 
     @given(text(alphabet=ascii_letters, min_size=1, max_size=5))
-    def test_non_numeric(self, cyrillic_text):
+    def test_non_numeric(self, non_numeric_text):
         with assertRaisesRegex(self, DecodeError, "non-numeric"):
-            self.base_klass(cyrillic_text)
+            self.base_klass(non_numeric_text)
 
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     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):
@@ -2856,6 +3478,34 @@ class TestPrintableString(
 ):
     base_klass = PrintableString
 
+    def text_alphabet(self):
+        return ascii_letters + digits + " '()+,-./:=?"
+
+    @given(text(alphabet=sorted(set(whitespace) - set(" ")), min_size=1, max_size=5))
+    def test_non_printable(self, non_printable_text):
+        with assertRaisesRegex(self, DecodeError, "non-printable"):
+            self.base_klass(non_printable_text)
+
+    @given(
+        sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
+        value, bound_min = list(sorted(ints))
+
+        class String(self.base_klass):
+            bounds = (bound_min, bound_min)
+        with self.assertRaises(DecodeError) as err:
+            String().decode(
+                self.base_klass(b"1" * value).encode(),
+                offset=offset,
+                decode_path=decode_path,
+            )
+        repr(err.exception)
+        self.assertEqual(err.exception.offset, offset)
+        self.assertEqual(err.exception.decode_path, decode_path)
+
 
 class TestTeletexString(
         UnicodeDecodeErrorMixin,
@@ -2901,6 +3551,42 @@ class TestVisibleString(
 ):
     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.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertFalse(obj.bered)
+
+        obj, tail = VisibleString().decode(
+            hexdec("3A0904034A6F6E04026573"),
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertTrue(obj.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertTrue(obj.bered)
+
+        obj, tail = VisibleString().decode(
+            hexdec("3A8004034A6F6E040265730000"),
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
+
 
 class TestGeneralString(
         UnicodeDecodeErrorMixin,
@@ -2973,7 +3659,8 @@ class TimeMixin(object):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
@@ -2981,7 +3668,8 @@ class TimeMixin(object):
         obj = self.base_klass(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(data_strategy())
     def test_comparison(self, d):
@@ -2993,8 +3681,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)
@@ -3126,20 +3814,27 @@ class TimeMixin(object):
             _decoded=_decoded,
         )
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertFalse(obj.expled)
         obj_encoded = obj.encode()
+        self.additional_symmetric_check(value, obj_encoded)
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
-        pprint(obj_expled)
+        list(obj_expled.pps())
+        pprint(obj_expled, big_blobs=True, with_decode_path=True)
         obj_expled_encoded = obj_expled.encode()
+        ctx_copied = deepcopy(ctx_dummy)
         obj_decoded, tail = obj_expled.decode(
             obj_expled_encoded + tail_junk,
             offset=offset,
+            ctx=ctx_copied,
         )
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         repr(obj_decoded)
-        pprint(obj_decoded)
+        list(obj_decoded.pps())
+        pprint(obj_decoded, big_blobs=True, with_decode_path=True)
         self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.todatetime(), obj_expled.todatetime())
@@ -3158,6 +3853,11 @@ class TimeMixin(object):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
@@ -3166,6 +3866,28 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
     min_datetime = datetime(1900, 1, 1)
     max_datetime = datetime(9999, 12, 31)
 
+    def additional_symmetric_check(self, value, obj_encoded):
+        if value.microsecond > 0:
+            self.assertFalse(obj_encoded.endswith(b"0Z"))
+
+    def test_x690_vector_valid(self):
+        for data in ((
+                b"19920521000000Z",
+                b"19920622123421Z",
+                b"19920722132100.3Z",
+        )):
+            GeneralizedTime(data)
+
+    def test_x690_vector_invalid(self):
+        for data in ((
+                b"19920520240000Z",
+                b"19920622123421.0Z",
+                b"19920722132100.30Z",
+        )):
+            with self.assertRaises(DecodeError) as err:
+                GeneralizedTime(data)
+            repr(err.exception)
+
     def test_go_vectors_invalid(self):
         for data in ((
                 b"20100102030405",
@@ -3200,6 +3922,53 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
             datetime(2010, 1, 2, 3, 4, 5, 0),
         )
 
+    @given(
+        binary(
+            min_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2,
+            max_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2,
+        ),
+        binary(min_size=1, max_size=1),
+        binary(
+            min_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2,
+            max_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2,
+        ),
+    )
+    def test_junk(self, part0, part1, part2):
+        junk = part0 + part1 + part2
+        assume(not (set(junk) <= set(digits.encode("ascii"))))
+        with self.assertRaises(DecodeError):
+            GeneralizedTime().decode(
+                GeneralizedTime.tag_default +
+                len_encode(len(junk)) +
+                junk
+            )
+
+    @given(
+        binary(
+            min_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2,
+            max_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2,
+        ),
+        binary(min_size=1, max_size=1),
+        binary(
+            min_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2,
+            max_size=(LEN_YYYYMMDDHHMMSSDMZ - 1) // 2,
+        ),
+    )
+    def test_junk_dm(self, part0, part1, part2):
+        junk = part0 + part1 + part2
+        assume(not (set(junk) <= set(digits.encode("ascii"))))
+        with self.assertRaises(DecodeError):
+            GeneralizedTime().decode(
+                GeneralizedTime.tag_default +
+                len_encode(len(junk)) +
+                junk
+            )
+
+    def test_ns_fractions(self):
+        GeneralizedTime(b"20010101000000.000001Z")
+        with assertRaisesRegex(self, DecodeError, "only microsecond fractions"):
+            GeneralizedTime(b"20010101000000.0000001Z")
+
 
 class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     base_klass = UTCTime
@@ -3207,6 +3976,26 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     min_datetime = datetime(2000, 1, 1)
     max_datetime = datetime(2049, 12, 31)
 
+    def additional_symmetric_check(self, value, obj_encoded):
+        pass
+
+    def test_x690_vector_valid(self):
+        for data in ((
+                b"920521000000Z",
+                b"920622123421Z",
+                b"920722132100Z",
+        )):
+            UTCTime(data)
+
+    def test_x690_vector_invalid(self):
+        for data in ((
+                b"920520240000Z",
+                b"9207221321Z",
+        )):
+            with self.assertRaises(DecodeError) as err:
+                UTCTime(data)
+            repr(err.exception)
+
     def test_go_vectors_invalid(self):
         for data in ((
                 b"a10506234540Z",
@@ -3264,6 +4053,27 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
             1900 + year,
         )
 
+    @given(
+        binary(
+            min_size=(LEN_YYMMDDHHMMSSZ - 1) // 2,
+            max_size=(LEN_YYMMDDHHMMSSZ - 1) // 2,
+        ),
+        binary(min_size=1, max_size=1),
+        binary(
+            min_size=(LEN_YYMMDDHHMMSSZ - 1) // 2,
+            max_size=(LEN_YYMMDDHHMMSSZ - 1) // 2,
+        ),
+    )
+    def test_junk(self, part0, part1, part2):
+        junk = part0 + part1 + part2
+        assume(not (set(junk) <= set(digits.encode("ascii"))))
+        with self.assertRaises(DecodeError):
+            UTCTime().decode(
+                UTCTime.tag_default +
+                len_encode(len(junk)) +
+                junk
+            )
+
 
 @composite
 def any_values_strategy(draw, do_expl=False):
@@ -3302,14 +4112,16 @@ class TestAny(CommonMixin, TestCase):
         obj = Any()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         obj = Any(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(integers())
     def test_basic(self, value):
@@ -3325,7 +4137,8 @@ class TestAny(CommonMixin, TestCase):
                 len(integer_encoded),
             )
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertSequenceEqual(obj.encode(), integer_encoded)
 
     @given(binary(), binary())
@@ -3402,10 +4215,9 @@ class TestAny(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Any().decode(
                 tag_encode(tag)[:-1],
@@ -3419,10 +4231,9 @@ class TestAny(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Any().decode(
                 Any.tag_default + len_encode(l)[:-1],
@@ -3446,20 +4257,26 @@ class TestAny(CommonMixin, TestCase):
             _, _, optional, _decoded = values
             obj = klass(value=value, optional=optional, _decoded=_decoded)
             repr(obj)
-            pprint(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
-            pprint(obj_expled)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            ctx_copied = deepcopy(ctx_dummy)
             obj_decoded, tail = obj_expled.decode(
                 obj_expled_encoded + tail_junk,
                 offset=offset,
+                ctx=ctx_copied,
             )
+            self.assertDictEqual(ctx_copied, ctx_dummy)
             repr(obj_decoded)
-            pprint(obj_decoded)
+            list(obj_decoded.pps())
+            pprint(obj_decoded, big_blobs=True, with_decode_path=True)
             self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -3481,18 +4298,120 @@ class TestAny(CommonMixin, TestCase):
             self.assertEqual(obj_decoded.tlen, 0)
             self.assertEqual(obj_decoded.llen, 0)
             self.assertEqual(obj_decoded.vlen, len(value))
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
+
+    @given(
+        integers(min_value=1).map(tag_ctxc),
+        integers(min_value=0, max_value=3),
+        integers(min_value=0),
+        decode_path_strat,
+        binary(),
+    )
+    def test_indefinite(self, expl, chunks, offset, decode_path, junk):
+        chunk = Boolean(False, expl=expl).encode()
+        encoded = (
+            OctetString.tag_default +
+            LENINDEF +
+            b"".join([chunk] * chunks) +
+            EOC
+        )
+        with self.assertRaises(LenIndefForm):
+            Any().decode(
+                encoded + junk,
+                offset=offset,
+                decode_path=decode_path,
+            )
+        obj, tail = Any().decode(
+            encoded + junk,
+            offset=offset,
+            decode_path=decode_path,
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, junk)
+        self.assertEqual(obj.offset, offset)
+        self.assertEqual(obj.tlvlen, len(encoded))
+        self.assertTrue(obj.lenindef)
+        self.assertFalse(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        obj = obj.copy()
+        self.assertTrue(obj.lenindef)
+        self.assertFalse(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        repr(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
+        with self.assertRaises(NotEnoughData) as err:
+            Any().decode(
+                encoded[:-1],
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + len(chunk) * chunks)
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+
+        class SeqOf(SequenceOf):
+            schema = Boolean(expl=expl)
+
+        class Seq(Sequence):
+            schema = (
+                ("type", ObjectIdentifier(defines=((("value",), {
+                    ObjectIdentifier("1.2.3"): SeqOf(impl=OctetString.tag_default),
+                }),))),
+                ("value", Any()),
+            )
+        seq = Seq((
+            ("type", ObjectIdentifier("1.2.3")),
+            ("value", Any(encoded)),
+        ))
+        seq_encoded = seq.encode()
+        seq_decoded, _ = Seq().decode(seq_encoded, ctx={"bered": True})
+        self.assertIsNotNone(seq_decoded["value"].defined)
+        repr(seq_decoded)
+        list(seq_decoded.pps())
+        pprint(seq_decoded, big_blobs=True, with_decode_path=True)
+        self.assertTrue(seq_decoded.bered)
+        self.assertFalse(seq_decoded["type"].bered)
+        self.assertTrue(seq_decoded["value"].bered)
+
+        chunk = chunk[:-1] + b"\x01"
+        chunks = b"".join([chunk] * (chunks + 1))
+        encoded = OctetString.tag_default + len_encode(len(chunks)) + chunks
+        seq = Seq((
+            ("type", ObjectIdentifier("1.2.3")),
+            ("value", Any(encoded)),
+        ))
+        seq_encoded = seq.encode()
+        seq_decoded, _ = Seq().decode(seq_encoded, ctx={"bered": True})
+        self.assertIsNotNone(seq_decoded["value"].defined)
+        repr(seq_decoded)
+        list(seq_decoded.pps())
+        pprint(seq_decoded, big_blobs=True, with_decode_path=True)
+        self.assertTrue(seq_decoded.bered)
+        self.assertFalse(seq_decoded["type"].bered)
+        self.assertTrue(seq_decoded["value"].bered)
 
 
 @composite
 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(
-            integers(min_value=0),
+        tags = [{tag_type: tag_value} for tag_type, tag_value in draw(sets(
+            one_of(
+                tuples(just("impl"), integers(min_value=0).map(tag_encode)),
+                tuples(just("expl"), integers(min_value=0).map(tag_ctxp)),
+            ),
             min_size=len(names),
             max_size=len(names),
         ))]
-        schema = [(name, Integer(impl=tag)) for name, tag in zip(names, tags)]
+        schema = [
+            (name, Integer(**tag_kwargs))
+            for name, tag_kwargs in zip(names, tags)
+        ]
     value = None
     if value_required or draw(booleans()):
         value = draw(tuples(
@@ -3556,7 +4475,8 @@ class TestChoice(CommonMixin, TestCase):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertIsNone(obj["whatever"])
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
@@ -3564,11 +4484,13 @@ class TestChoice(CommonMixin, TestCase):
         obj["whatever"] = Boolean()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         obj["whatever"] = Boolean(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(booleans(), booleans())
     def test_comparison(self, value1, value2):
@@ -3698,20 +4620,26 @@ class TestChoice(CommonMixin, TestCase):
             _decoded=_decoded,
         )
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertFalse(obj.expled)
         obj_encoded = obj.encode()
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
-        pprint(obj_expled)
+        list(obj_expled.pps())
+        pprint(obj_expled, big_blobs=True, with_decode_path=True)
         obj_expled_encoded = obj_expled.encode()
+        ctx_copied = deepcopy(ctx_dummy)
         obj_decoded, tail = obj_expled.decode(
             obj_expled_encoded + tail_junk,
             offset=offset,
+            ctx=ctx_copied,
         )
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         repr(obj_decoded)
-        pprint(obj_decoded)
+        list(obj_decoded.pps())
+        pprint(obj_decoded, big_blobs=True, with_decode_path=True)
         self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.choice, obj_expled.choice)
@@ -3734,11 +4662,16 @@ class TestChoice(CommonMixin, TestCase):
         self.assertEqual(obj_decoded.expl_offset, offset)
         self.assertSequenceEqual(
             obj_expled_encoded[
-                obj_decoded.value.offset - offset:
-                obj_decoded.value.offset + obj_decoded.value.tlvlen - offset
+                obj_decoded.value.fulloffset - offset:
+                obj_decoded.value.fulloffset + obj_decoded.value.fulllen - offset
             ],
             obj_encoded,
         )
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
     @given(integers())
     def test_set_get(self, value):
@@ -3798,15 +4731,13 @@ def seq_values_strategy(draw, seq_klass, do_expl=False):
     value = None
     if draw(booleans()):
         value = seq_klass()
-        value._value = {
-            k: v for k, v in draw(dictionaries(
-                integers(),
-                one_of(
-                    booleans().map(Boolean),
-                    integers().map(Integer),
-                ),
-            )).items()
-        }
+        value._value = draw(dictionaries(
+            integers(),
+            one_of(
+                booleans().map(Boolean),
+                integers().map(Integer),
+            ),
+        ))
     schema = None
     if draw(booleans()):
         schema = list(draw(dictionaries(
@@ -3825,15 +4756,13 @@ def seq_values_strategy(draw, seq_klass, do_expl=False):
     default = None
     if draw(booleans()):
         default = seq_klass()
-        default._value = {
-            k: v for k, v in draw(dictionaries(
-                integers(),
-                one_of(
-                    booleans().map(Boolean),
-                    integers().map(Integer),
-                ),
-            )).items()
-        }
+        default._value = draw(dictionaries(
+            integers(),
+            one_of(
+                booleans().map(Boolean),
+                integers().map(Integer),
+            ),
+        ))
     optional = draw(one_of(none(), booleans()))
     _decoded = (
         draw(integers(min_value=0)),
@@ -3967,7 +4896,7 @@ def sequences_strategy(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):
@@ -4013,12 +4942,14 @@ class SeqMixing(object):
             seq[name] = Boolean()
         self.assertFalse(seq.ready)
         repr(seq)
-        pprint(seq)
+        list(seq.pps())
+        pprint(seq, big_blobs=True, with_decode_path=True)
         for name, value in ready.items():
             seq[name] = Boolean(value)
         self.assertFalse(seq.ready)
         repr(seq)
-        pprint(seq)
+        list(seq.pps())
+        pprint(seq, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             seq.encode()
         repr(err.exception)
@@ -4026,7 +4957,8 @@ class SeqMixing(object):
             seq[name] = Boolean(value)
         self.assertTrue(seq.ready)
         repr(seq)
-        pprint(seq)
+        list(seq.pps())
+        pprint(seq, big_blobs=True, with_decode_path=True)
 
     @given(data_strategy())
     def test_call(self, d):
@@ -4148,10 +5080,9 @@ class SeqMixing(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -4165,10 +5096,9 @@ class SeqMixing(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -4203,27 +5133,68 @@ class SeqMixing(object):
         self.assertFalse(seq.decoded)
         self._assert_expects(seq, expects)
         repr(seq)
-        pprint(seq)
+        list(seq.pps())
+        pprint(seq, big_blobs=True, with_decode_path=True)
+        self.assertTrue(seq.ready)
         seq_encoded = seq.encode()
         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)
-        self.assertEqual(seq_decoded.encode(), seq_encoded)
-        for expect in expects:
-            if not expect["presented"]:
-                self.assertNotIn(expect["name"], seq_decoded)
-                continue
-            self.assertIn(expect["name"], seq_decoded)
-            obj = seq_decoded[expect["name"]]
-            self.assertTrue(obj.decoded)
-            offset = obj.expl_offset if obj.expled else obj.offset
-            tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen
-            self.assertSequenceEqual(
-                seq_encoded[offset:offset + tlvlen],
-                obj.encode(),
-            )
+        self.assertFalse(seq_decoded.lenindef)
+        self.assertFalse(seq_decoded.ber_encoded)
+        self.assertFalse(seq_decoded.bered)
+
+        t, _, lv = tag_strip(seq_encoded)
+        _, _, v = len_decode(lv)
+        seq_encoded_lenindef = t + LENINDEF + v + EOC
+        ctx_copied = deepcopy(ctx_dummy)
+        ctx_copied["bered"] = True
+        seq_decoded_lenindef, tail_lenindef = seq.decode(
+            seq_encoded_lenindef + tail_junk,
+            ctx=ctx_copied,
+        )
+        del ctx_copied["bered"]
+        self.assertDictEqual(ctx_copied, ctx_dummy)
+        self.assertTrue(seq_decoded_lenindef.lenindef)
+        self.assertTrue(seq_decoded_lenindef.bered)
+        seq_decoded_lenindef = seq_decoded_lenindef.copy()
+        self.assertTrue(seq_decoded_lenindef.lenindef)
+        self.assertTrue(seq_decoded_lenindef.bered)
+        with self.assertRaises(DecodeError):
+            seq.decode(seq_encoded_lenindef[:-1], ctx={"bered": True})
+        with self.assertRaises(DecodeError):
+            seq.decode(seq_encoded_lenindef[:-2], ctx={"bered": True})
+        repr(seq_decoded_lenindef)
+        list(seq_decoded_lenindef.pps())
+        pprint(seq_decoded_lenindef, big_blobs=True, with_decode_path=True)
+        self.assertTrue(seq_decoded_lenindef.ready)
+
+        for decoded, decoded_tail, encoded in (
+                (seq_decoded, tail, seq_encoded),
+                (seq_decoded_lenindef, tail_lenindef, seq_encoded_lenindef),
+        ):
+            self.assertEqual(decoded_tail, tail_junk)
+            self._assert_expects(decoded, expects)
+            self.assertEqual(seq, decoded)
+            self.assertEqual(decoded.encode(), seq_encoded)
+            self.assertEqual(decoded.tlvlen, len(encoded))
+            for expect in expects:
+                if not expect["presented"]:
+                    self.assertNotIn(expect["name"], decoded)
+                    continue
+                self.assertIn(expect["name"], decoded)
+                obj = decoded[expect["name"]]
+                self.assertTrue(obj.decoded)
+                offset = obj.expl_offset if obj.expled else obj.offset
+                tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen
+                self.assertSequenceEqual(
+                    seq_encoded[offset:offset + tlvlen],
+                    obj.encode(),
+                )
+
+        assert_exceeding_data(
+            self,
+            lambda: seq.decod(seq_encoded_lenindef + tail_junk, ctx={"bered": True}),
+            tail_junk,
+        )
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
@@ -4276,7 +5247,7 @@ class SeqMixing(object):
         self.assertSequenceEqual(seq.encode(), empty_seq)
 
     @given(data_strategy())
-    def test_encoded_default_accepted(self, d):
+    def test_encoded_default_not_accepted(self, d):
         _schema = list(d.draw(dictionaries(
             text_letters(),
             integers(),
@@ -4304,10 +5275,18 @@ class SeqMixing(object):
                 for (n, v), t in zip(_schema, tags)
             ]
         seq_with_default = SeqWithDefault()
-        seq_decoded, _ = seq_with_default.decode(seq_encoded)
-        for name, value in _schema:
-            self.assertEqual(seq_decoded[name], seq_with_default[name])
-            self.assertEqual(seq_decoded[name], value)
+        with assertRaisesRegex(self, DecodeError, "DEFAULT value met"):
+            seq_with_default.decode(seq_encoded)
+        for ctx in ({"bered": True}, {"allow_default_values": True}):
+            seq_decoded, _ = seq_with_default.decode(seq_encoded, ctx=ctx)
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            seq_decoded = seq_decoded.copy()
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            for name, value in _schema:
+                self.assertEqual(seq_decoded[name], seq_with_default[name])
+                self.assertEqual(seq_decoded[name], value)
 
     @given(data_strategy())
     def test_missing_from_spec(self, d):
@@ -4333,6 +5312,40 @@ class SeqMixing(object):
         with self.assertRaises(TagMismatch):
             seq_missing.decode(seq_encoded)
 
+    def test_bered(self):
+        class Seq(self.base_klass):
+            schema = (("underlying", Boolean()),)
+        encoded = Boolean.tag_default + len_encode(1) + b"\x01"
+        encoded = Seq.tag_default + len_encode(len(encoded)) + encoded
+        decoded, _ = Seq().decode(encoded, ctx={"bered": True})
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+        decoded = decoded.copy()
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+
+        class Seq(self.base_klass):
+            schema = (("underlying", OctetString()),)
+        encoded = (
+            tag_encode(form=TagFormConstructed, num=4) +
+            LENINDEF +
+            OctetString(b"whatever").encode() +
+            EOC
+        )
+        encoded = Seq.tag_default + len_encode(len(encoded)) + encoded
+        with self.assertRaises(DecodeError):
+            Seq().decode(encoded)
+        decoded, _ = Seq().decode(encoded, ctx={"bered": True})
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+        decoded = decoded.copy()
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+
 
 class TestSequence(SeqMixing, CommonMixin, TestCase):
     base_klass = Sequence
@@ -4369,6 +5382,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
@@ -4393,6 +5416,39 @@ class TestSet(SeqMixing, CommonMixin, TestCase):
             b"".join(sorted([seq[name].encode() for name, _ in Seq.schema])),
         )
 
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(data_strategy())
+    def test_unsorted(self, d):
+        tags = [
+            tag_encode(tag) for tag in
+            d.draw(sets(integers(min_value=1), min_size=2, max_size=5))
+        ]
+        tags = d.draw(permutations(tags))
+        assume(tags != sorted(tags))
+        encoded = b"".join(OctetString(t, impl=t).encode() for t in tags)
+        seq_encoded = b"".join((
+            Set.tag_default,
+            len_encode(len(encoded)),
+            encoded,
+        ))
+
+        class Seq(Set):
+            schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)]
+        seq = Seq()
+        with assertRaisesRegex(self, DecodeError, "unordered SET"):
+            seq.decode(seq_encoded)
+        for ctx in ({"bered": True}, {"allow_unordered_set": True}):
+            seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx)
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            seq_decoded = seq_decoded.copy()
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            self.assertSequenceEqual(
+                [bytes(seq_decoded[str(i)]) for i, t in enumerate(tags)],
+                tags,
+            )
+
 
 @composite
 def seqof_values_strategy(draw, schema=None, do_expl=False):
@@ -4499,7 +5555,8 @@ class SeqOfMixing(object):
             seqof.append(value)
         self.assertFalse(seqof.ready)
         repr(seqof)
-        pprint(seqof)
+        list(seqof.pps())
+        pprint(seqof, big_blobs=True, with_decode_path=True)
         with self.assertRaises(ObjNotReady) as err:
             seqof.encode()
         repr(err.exception)
@@ -4509,7 +5566,8 @@ class SeqOfMixing(object):
                 seqof[i] = Integer(i)
         self.assertTrue(seqof.ready)
         repr(seqof)
-        pprint(seqof)
+        list(seqof.pps())
+        pprint(seqof, big_blobs=True, with_decode_path=True)
 
     def test_spec_mismatch(self):
         class SeqOf(self.base_klass):
@@ -4536,17 +5594,27 @@ class SeqOfMixing(object):
             schema = Boolean()
         bound_min = d.draw(integers(min_value=1, max_value=1 << 7))
         bound_max = d.draw(integers(min_value=bound_min, max_value=1 << 7))
-        value = [Boolean()] * d.draw(integers(max_value=bound_min - 1))
+        value = [Boolean(False)] * d.draw(integers(max_value=bound_min - 1))
         with self.assertRaises(BoundsError) as err:
             SeqOf(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
-        value = [Boolean()] * d.draw(integers(
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            SeqOf(bounds=(bound_min, bound_max)).decode(
+                SeqOf(value).encode()
+            )
+        repr(err.exception)
+        value = [Boolean(True)] * d.draw(integers(
             min_value=bound_max + 1,
             max_value=bound_max + 10,
         ))
         with self.assertRaises(BoundsError) as err:
             SeqOf(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            SeqOf(bounds=(bound_min, bound_max)).decode(
+                SeqOf(value).encode()
+            )
+        repr(err.exception)
 
     @given(integers(min_value=1, max_value=10))
     def test_out_of_bounds(self, bound_max):
@@ -4691,10 +5759,9 @@ class SeqOfMixing(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -4708,10 +5775,9 @@ class SeqOfMixing(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -4748,20 +5814,26 @@ class SeqOfMixing(object):
             _decoded=_decoded,
         )
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertFalse(obj.expled)
         obj_encoded = obj.encode()
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
-        pprint(obj_expled)
+        list(obj_expled.pps())
+        pprint(obj_expled, big_blobs=True, with_decode_path=True)
         obj_expled_encoded = obj_expled.encode()
+        ctx_copied = deepcopy(ctx_dummy)
         obj_decoded, tail = obj_expled.decode(
             obj_expled_encoded + tail_junk,
             offset=offset,
+            ctx=ctx_copied,
         )
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         repr(obj_decoded)
-        pprint(obj_decoded)
+        list(obj_decoded.pps())
+        pprint(obj_decoded, big_blobs=True, with_decode_path=True)
         self.assertEqual(tail, tail_junk)
         self._test_symmetric_compare_objs(obj_decoded, obj_expled)
         self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
@@ -4788,6 +5860,72 @@ class SeqOfMixing(object):
                 ],
             )
 
+        t, _, lv = tag_strip(obj_encoded)
+        _, _, v = len_decode(lv)
+        obj_encoded_lenindef = t + LENINDEF + v + EOC
+        obj_decoded_lenindef, tail_lenindef = obj.decode(
+            obj_encoded_lenindef + tail_junk,
+            ctx={"bered": True},
+        )
+        self.assertTrue(obj_decoded_lenindef.lenindef)
+        self.assertTrue(obj_decoded_lenindef.bered)
+        obj_decoded_lenindef = obj_decoded_lenindef.copy()
+        self.assertTrue(obj_decoded_lenindef.lenindef)
+        self.assertTrue(obj_decoded_lenindef.bered)
+        repr(obj_decoded_lenindef)
+        list(obj_decoded_lenindef.pps())
+        pprint(obj_decoded_lenindef, big_blobs=True, with_decode_path=True)
+        self.assertEqual(tail_lenindef, tail_junk)
+        self.assertEqual(obj_decoded_lenindef.tlvlen, len(obj_encoded_lenindef))
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef[:-1], ctx={"bered": True})
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True})
+
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
+
+    def test_bered(self):
+        class SeqOf(self.base_klass):
+            schema = Boolean()
+        encoded = Boolean(False).encode()
+        encoded += Boolean.tag_default + len_encode(1) + b"\x01"
+        encoded = SeqOf.tag_default + len_encode(len(encoded)) + encoded
+        with self.assertRaises(DecodeError):
+            SeqOf().decode(encoded)
+        decoded, _ = SeqOf().decode(encoded, ctx={"bered": True})
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+        decoded = decoded.copy()
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+
+        class SeqOf(self.base_klass):
+            schema = OctetString()
+        encoded = OctetString(b"whatever").encode()
+        encoded += (
+            tag_encode(form=TagFormConstructed, num=4) +
+            LENINDEF +
+            OctetString(b"whatever").encode() +
+            EOC
+        )
+        encoded = SeqOf.tag_default + len_encode(len(encoded)) + encoded
+        with self.assertRaises(DecodeError):
+            SeqOf().decode(encoded)
+        decoded, _ = SeqOf().decode(encoded, ctx={"bered": True})
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+        decoded = decoded.copy()
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+
 
 class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase):
     class SeqOf(SequenceOf):
@@ -4825,6 +5963,41 @@ class TestSetOf(SeqOfMixing, CommonMixin, TestCase):
             b"".join(sorted([v.encode() for v in values])),
         )
 
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(data_strategy())
+    def test_unsorted(self, d):
+        values = [OctetString(v).encode() for v in d.draw(sets(
+            binary(min_size=1, max_size=5),
+            min_size=2,
+            max_size=5,
+        ))]
+        values = d.draw(permutations(values))
+        assume(values != sorted(values))
+        encoded = b"".join(values)
+        seq_encoded = b"".join((
+            SetOf.tag_default,
+            len_encode(len(encoded)),
+            encoded,
+        ))
+
+        class Seq(SetOf):
+            schema = OctetString()
+        seq = Seq()
+        with assertRaisesRegex(self, DecodeError, "unordered SET OF"):
+            seq.decode(seq_encoded)
+
+        for ctx in ({"bered": True}, {"allow_unordered_set": True}):
+            seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx)
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            seq_decoded = seq_decoded.copy()
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            self.assertSequenceEqual(
+                [obj.encode() for obj in seq_decoded],
+                values,
+            )
+
 
 class TestGoMarshalVectors(TestCase):
     def runTest(self):
@@ -4967,8 +6140,11 @@ class TestGoMarshalVectors(TestCase):
         seq = Seq()
         seq["erste"] = PrintableString("test")
         self.assertSequenceEqual(seq.encode(), hexdec("3006130474657374"))
+        # Asterisk is actually not allowable
+        PrintableString._allowable_chars |= set(b"*")
         seq["erste"] = PrintableString("test*")
         self.assertSequenceEqual(seq.encode(), hexdec("30071305746573742a"))
+        PrintableString._allowable_chars -= set(b"*")
 
         class Seq(Sequence):
             schema = (
@@ -5020,7 +6196,10 @@ class TestPP(TestCase):
         chosen_id = oids[chosen]
         pp = _pp(asn1_type_name=ObjectIdentifier.asn1_type_name, value=chosen)
         self.assertNotIn(chosen_id, pp_console_row(pp))
-        self.assertIn(chosen_id, pp_console_row(pp, oids=oids))
+        self.assertIn(
+            chosen_id,
+            pp_console_row(pp, oid_maps=[{'whatever': 'whenever'}, oids]),
+        )
 
 
 class TestAutoAddSlots(TestCase):
@@ -5071,6 +6250,9 @@ class TestOIDDefines(TestCase):
             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):
@@ -5132,9 +6314,17 @@ class TestDefinesByPath(TestCase):
         seq_sequenced["type"] = type_sequenced
         seq_sequenced["value"] = OctetString(seq_inner.encode())
         seq_sequenced_raw = seq_sequenced.encode()
+        repr(seq_sequenced)
+        list(seq_sequenced.pps())
+        pprint(seq_sequenced, big_blobs=True, with_decode_path=True)
 
         defines_by_path = []
-        seq_integered, _ = Seq().decode(seq_integered_raw)
+        ctx_copied = deepcopy(ctx_dummy)
+        seq_integered, _ = Seq().decode(
+            seq_integered_raw,
+            ctx=ctx_copied,
+        )
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         self.assertIsNone(seq_integered["value"].defined)
         defines_by_path.append(
             (("type",), ((("value",), {
@@ -5142,34 +6332,49 @@ class TestDefinesByPath(TestCase):
                 type_sequenced: SeqInner(),
             }),))
         )
+        ctx_copied["defines_by_path"] = defines_by_path
         seq_integered, _ = Seq().decode(
             seq_integered_raw,
-            ctx={"defines_by_path": defines_by_path},
+            ctx=ctx_copied,
         )
+        del ctx_copied["defines_by_path"]
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         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()))
+        repr(seq_integered)
+        list(seq_integered.pps())
+        pprint(seq_integered, big_blobs=True, with_decode_path=True)
 
+        ctx_copied["defines_by_path"] = defines_by_path
         seq_sequenced, _ = Seq().decode(
             seq_sequenced_raw,
-            ctx={"defines_by_path": defines_by_path},
+            ctx=ctx_copied,
         )
+        del ctx_copied["defines_by_path"]
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         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)
+        repr(seq_sequenced)
+        list(seq_sequenced.pps())
+        pprint(seq_sequenced, big_blobs=True, with_decode_path=True)
 
         defines_by_path.append((
             ("value", DecodePathDefBy(type_sequenced), "typeInner"),
             ((("valueInner",), {type_innered: Pairs()}),),
         ))
+        ctx_copied["defines_by_path"] = defines_by_path
         seq_sequenced, _ = Seq().decode(
             seq_sequenced_raw,
-            ctx={"defines_by_path": defines_by_path},
+            ctx=ctx_copied,
         )
+        del ctx_copied["defines_by_path"]
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         self.assertIsNotNone(seq_sequenced["value"].defined)
         self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
         seq_inner = seq_sequenced["value"].defined[1]
@@ -5178,6 +6383,9 @@ class TestDefinesByPath(TestCase):
         pairs = seq_inner["valueInner"].defined[1]
         for pair in pairs:
             self.assertIsNone(pair["value"][0].defined)
+        repr(seq_sequenced)
+        list(seq_sequenced.pps())
+        pprint(seq_sequenced, big_blobs=True, with_decode_path=True)
 
         defines_by_path.append((
             (
@@ -5193,10 +6401,13 @@ class TestDefinesByPath(TestCase):
                 type_octet_stringed: OctetString(),
             }),),
         ))
+        ctx_copied["defines_by_path"] = defines_by_path
         seq_sequenced, _ = Seq().decode(
             seq_sequenced_raw,
-            ctx={"defines_by_path": defines_by_path},
+            ctx=ctx_copied,
         )
+        del ctx_copied["defines_by_path"]
+        self.assertDictEqual(ctx_copied, ctx_dummy)
         self.assertIsNotNone(seq_sequenced["value"].defined)
         self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
         seq_inner = seq_sequenced["value"].defined[1]
@@ -5206,6 +6417,9 @@ class TestDefinesByPath(TestCase):
         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])
+        repr(seq_sequenced)
+        list(seq_sequenced.pps())
+        pprint(seq_sequenced, big_blobs=True, with_decode_path=True)
 
     @given(oid_strategy(), integers())
     def test_simple(self, oid, tgt):
@@ -5273,15 +6487,75 @@ class TestStrictDefaultExistence(TestCase):
             ("int%d" % i, Integer(expl=tag_ctxc(i + 1)))
             for i in range(count)
         ]
+        for klass in (Sequence, Set):
+            class Seq(klass):
+                schema = _schema
+            seq = Seq()
+            for i in range(count):
+                seq["int%d" % i] = Integer(123)
+            raw = seq.encode()
+            chosen_choice = "int%d" % chosen
+            seq.specs[chosen_choice] = seq.specs[chosen_choice](default=123)
+            with assertRaisesRegex(self, DecodeError, "DEFAULT value met"):
+                seq.decode(raw)
+            decoded, _ = seq.decode(raw, ctx={"allow_default_values": True})
+            self.assertTrue(decoded.ber_encoded)
+            self.assertTrue(decoded.bered)
+            decoded = decoded.copy()
+            self.assertTrue(decoded.ber_encoded)
+            self.assertTrue(decoded.bered)
+            decoded, _ = seq.decode(raw, ctx={"bered": True})
+            self.assertTrue(decoded.ber_encoded)
+            self.assertTrue(decoded.bered)
+            decoded = decoded.copy()
+            self.assertTrue(decoded.ber_encoded)
+            self.assertTrue(decoded.bered)
+
+
+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"),
+        )
 
-        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 TestExplOOB(TestCase):
+    def runTest(self):
+        expl = tag_ctxc(123)
+        raw = Integer(123).encode() + Integer(234).encode()
+        raw = b"".join((expl, len_encode(len(raw)), raw))
+        with assertRaisesRegex(self, DecodeError, "explicit tag out-of-bound"):
+            Integer(expl=expl).decode(raw)
+        Integer(expl=expl).decode(raw, ctx={"allow_expl_oob": True})