]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
Convenient decod() helper method
[pyderasn.git] / tests / test_pyderasn.py
index c8321bb0d25116a2ea4320c2ff187bed8df54499..9fbb33eb39f2d75231721cee73de95a32d66d717 100644 (file)
@@ -1,11 +1,10 @@
 # coding: utf-8
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
+# 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
@@ -65,6 +65,7 @@ from pyderasn import DecodePathDefBy
 from pyderasn import Enumerated
 from pyderasn import EOC
 from pyderasn import EOC_LEN
+from pyderasn import ExceedingData
 from pyderasn import GeneralizedTime
 from pyderasn import GeneralString
 from pyderasn import GraphicString
@@ -81,6 +82,7 @@ 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
@@ -131,6 +133,13 @@ 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):
@@ -371,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):
@@ -548,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)
@@ -581,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):
@@ -608,8 +630,13 @@ class TestBoolean(CommonMixin, TestCase):
             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),
@@ -617,6 +644,8 @@ class TestBoolean(CommonMixin, TestCase):
     )
     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(
@@ -624,7 +653,18 @@ class TestBoolean(CommonMixin, TestCase):
             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),
@@ -647,6 +687,8 @@ class TestBoolean(CommonMixin, TestCase):
 
         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)
@@ -657,9 +699,10 @@ class TestBoolean(CommonMixin, TestCase):
                     v.expl_tlvlen,
                     v.expl_tlen,
                     v.expl_llen,
-                    v.bered,
+                    v.ber_encoded,
                     v.lenindef,
                     v.expl_lenindef,
+                    v.bered,
                 ) for v in seqof
             ),
             set(((
@@ -670,8 +713,12 @@ class TestBoolean(CommonMixin, TestCase):
                 False,
                 False,
                 True,
+                True,
             ),)),
         )
+        repr(seqof)
+        list(seqof.pps())
+        pprint(seqof, big_blobs=True, with_decode_path=True)
 
 
 @composite
@@ -750,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())
@@ -1007,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)
@@ -1040,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 ((
@@ -1094,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)))
@@ -1103,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)
@@ -1211,14 +1271,16 @@ 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()),
@@ -1385,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)
@@ -1422,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):
@@ -1538,8 +1611,13 @@ class TestBitString(CommonMixin, TestCase):
             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(
@@ -1669,8 +1747,13 @@ class TestBitString(CommonMixin, TestCase):
         )
         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
@@ -1728,14 +1811,16 @@ 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(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
@@ -1956,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)
@@ -1989,6 +2080,11 @@ class TestOctetString(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         integers(min_value=1, max_value=30),
@@ -2047,8 +2143,13 @@ class TestOctetString(CommonMixin, TestCase):
             )
             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(
@@ -2135,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):
@@ -2248,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)
@@ -2279,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):
@@ -2342,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())
@@ -2555,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(
@@ -2575,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)
@@ -2608,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),
@@ -2651,6 +2778,55 @@ class TestObjectIdentifier(CommonMixin, TestCase):
             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):
@@ -2728,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):
@@ -2861,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)
@@ -2894,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
@@ -2950,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()
@@ -2959,7 +3149,8 @@ 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())
@@ -3181,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)
@@ -3216,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)
@@ -3237,12 +3442,12 @@ 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),
@@ -3273,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,
@@ -3322,8 +3555,9 @@ class TestVisibleString(
         obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573"))
         self.assertSequenceEqual(tail, b"")
         self.assertEqual(str(obj), "Jones")
-        self.assertFalse(obj.bered)
+        self.assertFalse(obj.ber_encoded)
         self.assertFalse(obj.lenindef)
+        self.assertFalse(obj.bered)
 
         obj, tail = VisibleString().decode(
             hexdec("3A0904034A6F6E04026573"),
@@ -3331,8 +3565,13 @@ class TestVisibleString(
         )
         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"),
@@ -3340,8 +3579,13 @@ class TestVisibleString(
         )
         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(
@@ -3415,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)
@@ -3423,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):
@@ -3568,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())
@@ -3600,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):
@@ -3608,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",
@@ -3684,6 +3964,11 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
                 junk
             )
 
+    def test_ns_fractions(self):
+        GeneralizedTime(b"20010101000000.000001Z")
+        with assertRaisesRegex(self, DecodeError, "only microsecond fractions"):
+            GeneralizedTime(b"20010101000000.0000001Z")
+
 
 class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     base_klass = UTCTime
@@ -3691,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",
@@ -3807,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):
@@ -3830,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())
@@ -3949,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))
@@ -3984,6 +4298,11 @@ class TestAny(CommonMixin, TestCase):
             self.assertEqual(obj_decoded.tlen, 0)
             self.assertEqual(obj_decoded.llen, 0)
             self.assertEqual(obj_decoded.vlen, len(value))
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         integers(min_value=1).map(tag_ctxc),
@@ -4000,6 +4319,12 @@ class TestAny(CommonMixin, TestCase):
             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,
@@ -4009,6 +4334,16 @@ class TestAny(CommonMixin, TestCase):
         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],
@@ -4019,6 +4354,47 @@ class TestAny(CommonMixin, TestCase):
         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):
@@ -4099,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()
@@ -4107,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):
@@ -4241,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)
@@ -4282,6 +4667,11 @@ class TestChoice(CommonMixin, TestCase):
             ],
             obj_encoded,
         )
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
     @given(integers())
     def test_set_get(self, value):
@@ -4341,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(
@@ -4368,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)),
@@ -4556,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)
@@ -4569,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):
@@ -4744,27 +5133,38 @@ 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.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={"bered": True},
+            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)
-        pprint(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 (
@@ -4790,6 +5190,12 @@ class SeqMixing(object):
                     obj.encode(),
                 )
 
+        assert_exceeding_data(
+            self,
+            lambda: seq.decod(seq_encoded_lenindef + tail_junk, ctx={"bered": True}),
+            tail_junk,
+        )
+
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
     def test_symmetric_with_seq(self, d):
@@ -4873,6 +5279,10 @@ class SeqMixing(object):
             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])
@@ -4902,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
@@ -4972,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):
@@ -5078,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)
@@ -5088,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):
@@ -5335,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)
@@ -5383,14 +5868,64 @@ class SeqOfMixing(object):
             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)
-        pprint(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):
@@ -5428,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):
@@ -5570,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 = (
@@ -5623,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):
@@ -5674,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):
@@ -5735,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",), {
@@ -5745,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]
@@ -5781,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((
             (
@@ -5796,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]
@@ -5809,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):
@@ -5888,8 +6499,16 @@ class TestStrictDefaultExistence(TestCase):
             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)