]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
Fix minimal tag value in tests: zero tag has only EOC
[pyderasn.git] / tests / test_pyderasn.py
index faa85699fd2d628058d3c9cf5d2ce83d8fbcc218..1cce9ed5a3988a92b92248e987d2e012366bf798 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
 # License along with this program.  If not, see
 # <http://www.gnu.org/licenses/>.
 
+from copy import copy
+from copy import deepcopy
 from datetime import datetime
+from datetime import timedelta
+from importlib import import_module
+from os import environ
+from random import random
 from string import ascii_letters
 from string import digits
 from string import printable
 from string import whitespace
+from time import mktime
+from time import time
 from unittest import TestCase
 
 from hypothesis import assume
@@ -51,6 +58,9 @@ from six import iterbytes
 from six import PY2
 from six import text_type
 from six import unichr as six_unichr
+from six.moves.cPickle import dumps as pickle_dumps
+from six.moves.cPickle import HIGHEST_PROTOCOL as pickle_proto
+from six.moves.cPickle import loads as pickle_loads
 
 from pyderasn import _pp
 from pyderasn import abs_decode_path
@@ -65,6 +75,7 @@ from pyderasn import DecodePathDefBy
 from pyderasn import Enumerated
 from pyderasn import EOC
 from pyderasn import EOC_LEN
+from pyderasn import ExceedingData
 from pyderasn import GeneralizedTime
 from pyderasn import GeneralString
 from pyderasn import GraphicString
@@ -77,6 +88,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
@@ -111,9 +127,10 @@ from pyderasn import VideotexString
 from pyderasn import VisibleString
 
 
+max_examples = environ.get("MAX_EXAMPLES")
 settings.register_profile("local", settings(
     deadline=5000,
-    perform_health_check=False,
+    **({"max_examples": int(max_examples)} if max_examples else {})
 ))
 settings.load_profile("local")
 LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4
@@ -125,6 +142,30 @@ 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()
+copy_funcs = (
+    copy,
+    lambda obj: pickle_loads(pickle_dumps(obj, pickle_proto)),
+)
+self_module = import_module(__name__)
+
+
+def register_class(klass):
+    klassname = klass.__name__ + str(time()).replace(".", "")
+    klass.__name__ = klassname
+    klass.__qualname__ = klassname
+    setattr(self_module, klassname, klass)
+
+
+def assert_exceeding_data(self, call, junk):
+    if len(junk) <= 0:
+        return
+    with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)) as err:
+        call()
+    repr(err)
 
 
 class TestHex(TestCase):
@@ -305,7 +346,7 @@ class CommonMixin(object):
         self.assertSequenceEqual(obj.impl, impl_tag)
         self.assertFalse(obj.expled)
 
-    @given(binary())
+    @given(binary(min_size=1))
     def test_expl_inherited(self, expl_tag):
         class Inherited(self.base_klass):
             expl = expl_tag
@@ -365,16 +406,18 @@ 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())
+    @given(booleans(), booleans(), binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (Boolean, BooleanInherited):
             obj1 = klass(value1)
@@ -439,8 +482,9 @@ class TestBoolean(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Boolean, BooleanInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
 
     @given(
         booleans(),
@@ -463,10 +507,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],
@@ -480,10 +523,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],
@@ -497,10 +539,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],
@@ -514,10 +555,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],
@@ -546,26 +586,32 @@ 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)
-            obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(
-                obj_expled_encoded + tail_junk,
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
+            obj_expled_hex_encoded = obj_expled.hexencode()
+            ctx_copied = deepcopy(ctx_dummy)
+            obj_decoded, tail = obj_expled.hexdecode(
+                obj_expled_hex_encoded + hexenc(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)
             self.assertEqual(bool(obj_decoded), bool(obj_expled))
             self.assertEqual(bool(obj_decoded), bool(obj))
-            self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
+            self.assertSequenceEqual(obj_decoded.hexencode(), obj_expled_hex_encoded)
             self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
             self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
             self.assertEqual(
@@ -579,6 +625,11 @@ class TestBoolean(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.hexdecod(obj_expled_hex_encoded + hexenc(tail_junk)),
+                tail_junk,
+            )
 
     @given(integers(min_value=2))
     def test_invalid_len(self, l):
@@ -606,7 +657,41 @@ 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 = copy(obj)
+        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 = copy(obj)
+        self.assertTrue(obj.expl_lenindef)
+        self.assertFalse(obj.lenindef)
+        self.assertFalse(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        self.assertSequenceEqual(tail, junk)
+        repr(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(
         integers(min_value=1).map(tag_ctxc),
@@ -621,7 +706,7 @@ class TestBoolean(CommonMixin, TestCase):
         for value in values:
             encoded += (
                 expl +
-                b"\x80" +
+                LENINDEF +
                 Boolean(value).encode() +
                 EOC
             )
@@ -629,13 +714,38 @@ 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)
         self.assertSetEqual(
-            set((v.tlvlen, v.expl_tlvlen, v.expl_tlen, v.expl_llen) for v in seqof),
-            set(((3 + EOC_LEN, len(expl) + 1 + 3 + EOC_LEN, len(expl), 1),)),
+            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
@@ -714,17 +824,19 @@ 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())
+    @given(integers(), integers(), binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (Integer, IntegerInherited):
             obj1 = klass(value1)
@@ -773,9 +885,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):
@@ -859,12 +981,13 @@ class TestInteger(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Integer, IntegerInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj.specs, obj_copied.specs)
-            self.assertEqual(obj._bound_min, obj_copied._bound_min)
-            self.assertEqual(obj._bound_max, obj_copied._bound_max)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj.specs, obj_copied.specs)
+                self.assertEqual(obj._bound_min, obj_copied._bound_min)
+                self.assertEqual(obj._bound_max, obj_copied._bound_max)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         integers(),
@@ -894,10 +1017,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],
@@ -911,10 +1033,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],
@@ -928,10 +1049,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):
@@ -964,20 +1084,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)
@@ -997,6 +1123,11 @@ class TestInteger(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     def test_go_vectors_valid(self):
         for data, expect in ((
@@ -1051,7 +1182,7 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl=
 
     def _value(value_required):
         if not value_required and draw(booleans()):
-            return
+            return None
         generation_choice = 0
         if value_required:
             generation_choice = draw(sampled_from((1, 2, 3)))
@@ -1060,9 +1191,9 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl=
                 sampled_from(("0", "1")),
                 max_size=len(schema),
             )))
-        elif generation_choice == 2 or draw(booleans()):
+        if generation_choice == 2 or draw(booleans()):
             return draw(binary(max_size=len(schema) // 8))
-        elif generation_choice == 3 or draw(booleans()):
+        if generation_choice == 3 or draw(booleans()):
             return tuple(draw(lists(sampled_from([name for name, _ in schema]))))
         return None
     value = _value(value_required)
@@ -1168,14 +1299,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()),
@@ -1254,6 +1387,7 @@ class TestBitString(CommonMixin, TestCase):
 
             class BS(klass):
                 schema = _schema
+            register_class(BS)
             obj = BS(
                 value=value,
                 impl=impl,
@@ -1262,10 +1396,11 @@ class TestBitString(CommonMixin, TestCase):
                 optional=optional or False,
                 _decoded=_decoded,
             )
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj.specs, obj_copied.specs)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj.specs, obj_copied.specs)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         binary(),
@@ -1288,10 +1423,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],
@@ -1305,10 +1439,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],
@@ -1344,20 +1477,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)
@@ -1381,6 +1520,11 @@ class TestBitString(CommonMixin, TestCase):
                 self.assertSetEqual(set(value), set(obj_decoded.named))
                 for name in value:
                     obj_decoded[name]
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=1, max_value=255))
     def test_bad_zero_value(self, pad_size):
@@ -1443,12 +1587,13 @@ class TestBitString(CommonMixin, TestCase):
             max_size=3,
         ),
         lists(booleans(), min_size=1),
+        binary(),
     )
-    def test_constructed(self, impl, chunk_inputs, chunk_last_bits):
+    def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk):
         def chunk_constructed(contents):
             return (
                 tag_encode(form=TagFormConstructed, num=3) +
-                b"\x80" +
+                LENINDEF +
                 b"".join(BitString(content).encode() for content in contents) +
                 EOC
             )
@@ -1472,7 +1617,7 @@ class TestBitString(CommonMixin, TestCase):
         bit_len_expected += chunk_last.bit_len
         encoded_indefinite = (
             tag_encode(form=TagFormConstructed, num=impl) +
-            b"\x80" +
+            LENINDEF +
             b"".join(chunks) +
             chunk_last.encode() +
             EOC
@@ -1485,19 +1630,147 @@ class TestBitString(CommonMixin, TestCase):
         )
         with assertRaisesRegex(self, DecodeError, "unallowed BER"):
             BitString(impl=tag_encode(impl)).decode(encoded_indefinite)
-        for encoded in (encoded_indefinite, encoded_definite):
+        for lenindef_expected, encoded in (
+                (True, encoded_indefinite),
+                (False, encoded_definite),
+        ):
             obj, tail = BitString(impl=tag_encode(impl)).decode(
-                encoded, ctx={"bered": True}
+                encoded + junk,
+                ctx={"bered": True},
             )
-            self.assertSequenceEqual(tail, b"")
+            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 = copy(obj)
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
             self.assertEqual(len(encoded), obj.tlvlen)
+            repr(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
+
+    @given(
+        integers(min_value=0),
+        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_payload = hexdec("0A3B5F291CD0")
-        vector = BitString((len(vector_payload) * 8 - 4, vector_payload))
+        vector = BitString("'0A3B5F291CD'H")
         obj, tail = BitString().decode(hexdec("0307040A3B5F291CD0"))
         self.assertSequenceEqual(tail, b"")
         self.assertEqual(obj, vector)
@@ -1507,6 +1780,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 = copy(obj)
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
 
 
 @composite
@@ -1564,14 +1844,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):
@@ -1608,10 +1890,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):
@@ -1688,11 +1980,12 @@ class TestOctetString(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (OctetString, OctetStringInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj._bound_min, obj_copied._bound_min)
-            self.assertEqual(obj._bound_max, obj_copied._bound_max)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj._bound_min, obj_copied._bound_min)
+                self.assertEqual(obj._bound_max, obj_copied._bound_max)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         binary(),
@@ -1715,10 +2008,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],
@@ -1732,10 +2024,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],
@@ -1749,10 +2040,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):
@@ -1785,20 +2075,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)
@@ -1818,6 +2114,11 @@ class TestOctetString(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         integers(min_value=1, max_value=30),
@@ -1833,12 +2134,13 @@ class TestOctetString(CommonMixin, TestCase):
             min_size=1,
             max_size=3,
         ),
+        binary(),
     )
-    def test_constructed(self, impl, chunk_inputs):
+    def test_constructed(self, impl, chunk_inputs, junk):
         def chunk_constructed(contents):
             return (
                 tag_encode(form=TagFormConstructed, num=4) +
-                b"\x80" +
+                LENINDEF +
                 b"".join(OctetString(content).encode() for content in contents) +
                 EOC
             )
@@ -1854,7 +2156,7 @@ class TestOctetString(CommonMixin, TestCase):
                 payload_expected += payload
         encoded_indefinite = (
             tag_encode(form=TagFormConstructed, num=impl) +
-            b"\x80" +
+            LENINDEF +
             b"".join(chunks) +
             EOC
         )
@@ -1865,14 +2167,82 @@ class TestOctetString(CommonMixin, TestCase):
         )
         with assertRaisesRegex(self, DecodeError, "unallowed BER"):
             OctetString(impl=tag_encode(impl)).decode(encoded_indefinite)
-        for encoded in (encoded_indefinite, encoded_definite):
+        for lenindef_expected, encoded in (
+                (True, encoded_indefinite),
+                (False, encoded_definite),
+        ):
             obj, tail = OctetString(impl=tag_encode(impl)).decode(
-                encoded, ctx={"bered": True}
+                encoded + junk,
+                ctx={"bered": True},
             )
-            self.assertSequenceEqual(tail, b"")
+            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 = copy(obj)
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
             self.assertEqual(len(encoded), obj.tlvlen)
+            repr(obj)
+            list(obj.pps())
+            pprint(obj, big_blobs=True, with_decode_path=True)
+
+    @given(
+        integers(min_value=0),
+        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
@@ -1903,9 +2273,10 @@ 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())
+    @given(binary(min_size=1), binary(min_size=1))
     def test_comparison(self, tag1, tag2):
         for klass in (Null, NullInherited):
             obj1 = klass(impl=tag1)
@@ -1952,8 +2323,9 @@ class TestNull(CommonMixin, TestCase):
                 optional=optional or False,
                 _decoded=_decoded,
             )
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
 
     @given(integers(min_value=1).map(tag_encode))
     def test_stripped(self, tag_impl):
@@ -1970,10 +2342,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],
@@ -1987,10 +2358,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],
@@ -2018,20 +2388,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)
@@ -2049,6 +2425,11 @@ class TestNull(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=1))
     def test_invalid_len(self, l):
@@ -2112,17 +2493,20 @@ 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())
+    @given(oid_strategy(), oid_strategy(), binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
             obj1 = klass(value1)
@@ -2216,9 +2600,10 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 optional=optional,
                 _decoded=_decoded,
             )
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
@@ -2242,10 +2627,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],
@@ -2259,10 +2643,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],
@@ -2327,7 +2710,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(
@@ -2347,20 +2730,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)
@@ -2380,6 +2769,11 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         oid_strategy().map(ObjectIdentifier),
@@ -2423,6 +2817,77 @@ 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 = copy(obj)
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"):
+            ObjectIdentifier().decode(tampered)
+
+    @given(data_strategy())
+    def test_negative_arcs(self, d):
+        oid = list(d.draw(oid_strategy()))
+        if len(oid) == 2:
+            return
+        idx = d.draw(integers(min_value=3, max_value=len(oid)))
+        oid[idx - 1] *= -1
+        if oid[idx - 1] == 0:
+            oid[idx - 1] = -1
+        with self.assertRaises(InvalidOID):
+            ObjectIdentifier(tuple(oid))
+        with self.assertRaises(InvalidOID):
+            ObjectIdentifier(".".join(str(i) for i in oid))
+
+    @given(data_strategy())
+    def test_plused_arcs(self, d):
+        oid = [str(arc) for arc in d.draw(oid_strategy())]
+        idx = d.draw(integers(min_value=0, max_value=len(oid)))
+        oid[idx - 1] = "+" + oid[idx - 1]
+        with self.assertRaises(InvalidOID):
+            ObjectIdentifier(".".join(str(i) for i in oid))
+
+    @given(data_strategy())
+    def test_nonnormalized_arcs(self, d):
+        arcs = d.draw(lists(
+            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 = copy(obj)
+        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):
@@ -2500,16 +2965,18 @@ 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())
+    @given(integers(), integers(), binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
         class E(Enumerated):
             schema = (
@@ -2601,6 +3068,7 @@ class TestEnumerated(CommonMixin, TestCase):
 
         class E(Enumerated):
             schema = schema_input
+        register_class(E)
         obj = E(
             value=value,
             impl=impl,
@@ -2609,9 +3077,10 @@ class TestEnumerated(CommonMixin, TestCase):
             optional=optional,
             _decoded=_decoded,
         )
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj.specs, obj_copied.specs)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj.specs, obj_copied.specs)
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
@@ -2633,20 +3102,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)
@@ -2666,6 +3141,11 @@ class TestEnumerated(CommonMixin, TestCase):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 @composite
@@ -2722,7 +3202,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()
@@ -2731,7 +3212,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())
@@ -2770,10 +3252,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):
@@ -2852,11 +3344,12 @@ class StringMixin(object):
     def test_copy(self, d):
         values = d.draw(string_values_strategy(self.text_alphabet()))
         obj = self.base_klass(*values)
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._bound_min, obj_copied._bound_min)
-        self.assertEqual(obj._bound_max, obj_copied._bound_max)
-        self.assertEqual(obj._value, obj_copied._value)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj._bound_min, obj_copied._bound_min)
+            self.assertEqual(obj._bound_max, obj_copied._bound_max)
+            self.assertEqual(obj._value, obj_copied._value)
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -2877,10 +3370,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],
@@ -2894,10 +3386,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],
@@ -2911,10 +3402,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):
@@ -2946,20 +3436,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)
@@ -2981,18 +3477,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)
@@ -3002,20 +3506,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):
@@ -3039,6 +3542,57 @@ 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)
+
+    def test_allowable_invalid_chars(self):
+        for c, kwargs in (
+                ("*", {"allow_asterisk": True}),
+                ("&", {"allow_ampersand": True}),
+                ("&*", {"allow_asterisk": True, "allow_ampersand": True}),
+        ):
+            s = "hello invalid"
+            obj = self.base_klass(s)
+            for prop in kwargs.keys():
+                self.assertFalse(getattr(obj, prop))
+            s += c
+            with assertRaisesRegex(self, DecodeError, "non-printable"):
+                self.base_klass(s)
+            self.base_klass(s, **kwargs)
+            klass = self.base_klass(**kwargs)
+            obj = klass(s)
+            for prop in kwargs.keys():
+                self.assertTrue(getattr(obj, prop))
+            obj = copy(obj)
+            obj(s)
+            for prop in kwargs.keys():
+                self.assertTrue(getattr(obj, prop))
+
 
 class TestTeletexString(
         UnicodeDecodeErrorMixin,
@@ -3085,24 +3639,40 @@ class TestVisibleString(
     base_klass = VisibleString
 
     def test_x690_vector(self):
-        self.assertEqual(
-            str(VisibleString().decode(hexdec("1A054A6F6E6573"))[0]),
-            "Jones",
-        )
-        self.assertEqual(
-            str(VisibleString().decode(
-                hexdec("3A0904034A6F6E04026573"),
-                ctx={"bered": True},
-            )[0]),
-            "Jones",
+        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.assertEqual(
-            str(VisibleString().decode(
-                hexdec("3A8004034A6F6E040265730000"),
-                ctx={"bered": True},
-            )[0]),
-            "Jones",
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertTrue(obj.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertTrue(obj.bered)
+        obj = copy(obj)
+        self.assertTrue(obj.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertTrue(obj.bered)
+
+        obj, tail = VisibleString().decode(
+            hexdec("3A8004034A6F6E040265730000"),
+            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 = copy(obj)
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
 
 
 class TestGeneralString(
@@ -3176,15 +3746,20 @@ 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)
-        value = d.draw(datetimes(min_value=self.min_datetime))
+        value = d.draw(datetimes(
+            min_value=self.min_datetime,
+            max_value=self.max_datetime,
+        ))
         obj = self.base_klass(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(data_strategy())
     def test_comparison(self, d):
@@ -3281,9 +3856,10 @@ class TimeMixin(object):
             max_datetime=self.max_datetime,
         ))
         obj = self.base_klass(*values)
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._value, obj_copied._value)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj._value, obj_copied._value)
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -3329,20 +3905,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())
@@ -3361,6 +3944,11 @@ class TimeMixin(object):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
@@ -3369,9 +3957,35 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
     min_datetime = datetime(1900, 1, 1)
     max_datetime = datetime(9999, 12, 31)
 
-    def test_go_vectors_invalid(self):
+    def additional_symmetric_check(self, value, obj_encoded):
+        if value.microsecond > 0:
+            self.assertFalse(obj_encoded.endswith(b"0Z"))
+
+    def test_repr_not_ready(self):
+        unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime())
+        repr(GeneralizedTime())
+
+    def test_x690_vector_valid(self):
         for data in ((
-                b"20100102030405",
+                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",
                 b"00000100000000Z",
                 b"20101302030405Z",
                 b"20100002030405Z",
@@ -3403,6 +4017,323 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
             datetime(2010, 1, 2, 3, 4, 5, 0),
         )
 
+    def test_go_vectors_valid_ber(self):
+        for data in ((
+                b"20100102030405+0607",
+                b"20100102030405-0607",
+        )):
+            GeneralizedTime(data, ctx={"bered": True})
+
+    def test_utc_offsets(self):
+        """Some know equal UTC offsets
+        """
+        dts = [
+            GeneralizedTime(data.encode("ascii"), ctx={"bered": True})
+            for data in (
+                "200101011830Z",
+                "200101012230+04",
+                "200101011130-0700",
+                "200101011500-03:30",
+            )
+        ]
+        self.assertEqual(dts[0], dts[1])
+        self.assertEqual(dts[0], dts[2])
+        self.assertEqual(dts[0], dts[3])
+
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(data_strategy())
+    def test_valid_ber(self, d):
+        min_year = 1901 if PY2 else 2
+        year = d.draw(integers(min_value=min_year, max_value=9999))
+        month = d.draw(integers(min_value=1, max_value=12))
+        day = d.draw(integers(min_value=1, max_value=28))
+        hours = d.draw(integers(min_value=0, max_value=23))
+        data = "%04d%02d%02d%02d" % (year, month, day, hours)
+        dt = datetime(year, month, day, hours)
+        fractions_sign = d.draw(sampled_from("  ,."))
+        fractions = None
+        if fractions_sign != " ":
+            fractions = random()
+        if d.draw(booleans()):
+            minutes = d.draw(integers(min_value=0, max_value=59))
+            data += "%02d" % minutes
+            dt += timedelta(seconds=60 * minutes)
+            if d.draw(booleans()):
+                seconds = d.draw(integers(min_value=0, max_value=59))
+                data += "%02d" % seconds
+                dt += timedelta(seconds=seconds)
+                if fractions is not None:
+                    dt += timedelta(microseconds=10**6 * fractions)
+            elif fractions is not None:
+                dt += timedelta(seconds=60 * fractions)
+        elif fractions is not None:
+            dt += timedelta(seconds=3600 * fractions)
+        if fractions is not None:
+            data += fractions_sign + str(fractions)[2:]
+        if d.draw(booleans()):
+            data += "Z"
+        elif d.draw(booleans()):
+            offset_hour = d.draw(integers(min_value=0, max_value=13))
+            sign = 1
+            if d.draw(booleans()):
+                data += "-"
+                sign = -1
+            else:
+                data += "+"
+            dt -= timedelta(seconds=sign * 3600 * offset_hour)
+            data += "%02d" % offset_hour
+            minutes_separator = d.draw(sampled_from((None, "", ":")))
+            if minutes_separator is not None:
+                offset_minute = d.draw(integers(min_value=0, max_value=59))
+                dt -= timedelta(seconds=sign * 60 * offset_minute)
+                data += "%s%02d" % (minutes_separator, offset_minute)
+        data = data.encode("ascii")
+        data_der = GeneralizedTime.tag_default + len_encode(len(data)) + data
+        try:
+            GeneralizedTime().decod(data_der)
+        except DecodeError:
+            dered = False
+        else:
+            dered = True
+        obj = GeneralizedTime().decod(data_der, ctx={"bered": True})
+        if dt.year > 1970:
+            self.assertEqual(
+                mktime(obj.todatetime().timetuple()),
+                mktime(dt.timetuple()),
+            )
+        elif not PY2:
+            self.assertEqual(obj.todatetime().timestamp(), dt.timestamp())
+        self.assertEqual(obj.ber_encoded, not dered)
+        self.assertEqual(obj.bered, not dered)
+        self.assertEqual(obj.ber_raw, None if dered else data)
+        self.assertEqual(obj.encode() == data_der, dered)
+        repr(obj)
+        bytes(obj)
+        str(obj)
+
+    def test_invalid_ber(self):
+        for data in ((
+                # "00010203040506.07",
+                "-0010203040506.07",
+                "0001-203040506.07",
+                "000102-3040506.07",
+                "00010203-40506.07",
+                "0001020304-506.07",
+                "000102030405-6.07",
+                "00010203040506.-7",
+                "+0010203040506.07",
+                "0001+203040506.07",
+                "000102+3040506.07",
+                "00010203+40506.07",
+                "0001020304+506.07",
+                "000102030405+6.07",
+                "00010203040506.+7",
+                " 0010203040506.07",
+                "0001 203040506.07",
+                "000102 3040506.07",
+                "00010203 40506.07",
+                "0001020304 506.07",
+                "000102030405 6.07",
+                "00010203040506. 7",
+                "001 0203040506.07",
+                "00012 03040506.07",
+                "0001023 040506.07",
+                "000102034 0506.07",
+                "00010203045 06.07",
+                "0001020304056 .07",
+                "00010203040506.7 ",
+                "00010203040506.",
+                "0001020304050607",
+
+                "-0010203040506",
+                "0001-203040506",
+                "000102-3040506",
+                "00010203-40506",
+                "0001020304-506",
+                "000102030405-6",
+                "0001+203040506",
+                "000102+3040506",
+                "00010203+40506",
+                "0001020304+506",
+                "000102030405+6",
+                " 0010203040506",
+                "0001 203040506",
+                "000102 3040506",
+                "00010203 40506",
+                "0001020304 506",
+                "000102030405 6",
+                "001 0203040506",
+                "00012 03040506",
+                "0001023 040506",
+                "000102034 0506",
+                "00010203045 06",
+                "0001020304056 ",
+
+                "-00102030405.07",
+                "0001-2030405.07",
+                "000102-30405.07",
+                "00010203-405.07",
+                "0001020304-5.07",
+                "000102030405.-7",
+                "+00102030405.07",
+                "0001+2030405.07",
+                "00010203+405.07",
+                "0001020304+5.07",
+                "000102030405.+7",
+                " 00102030405.07",
+                "0001 2030405.07",
+                "000102 30405.07",
+                "00010203 405.07",
+                "0001020304 5.07",
+                "000102030405. 7",
+                "001 02030405.07",
+                "00012 030405.07",
+                "0001023 0405.07",
+                "000102034 05.07",
+                "00010203045 .07",
+                "000102030405.7 ",
+                "000102030405.",
+
+                "-001020304.07",
+                "0001-20304.07",
+                "000102-304.07",
+                "00010203-4.07",
+                "0001020304.-7",
+                "+001020304.07",
+                "0001+20304.07",
+                "00010203+4.07",
+                "0001020304.+7",
+                " 001020304.07",
+                "0001 20304.07",
+                "000102 304.07",
+                "00010203 4.07",
+                "0001020304. 7",
+                "001 020304.07",
+                "00012 0304.07",
+                "0001023 04.07",
+                "000102034 .07",
+                "0001020304.7 ",
+                "0001020304.",
+
+                "00010203",
+                "00010203040506Y",
+                "0001010100+0001",
+                "0001010100+00:01",
+                "0001010100+01",
+
+                "00010203040506.07+15",
+                "00010203040506.07-15",
+                "00010203040506.07+14:60",
+                "00010203040506.07+1460",
+                "00010203040506.07-1460",
+                "00010203040506.07+00:60",
+                "00010203040506.07-00:60",
+
+                "00010203040506+15",
+                "00010203040506-15",
+                "00010203040506+14:60",
+                "00010203040506+1460",
+                "00010203040506-1460",
+                "00010203040506+00:60",
+                "00010203040506-00:60",
+
+                "0001020304050.07",
+                "00010203040.07",
+                "000102030.07",
+                "0001020304050",
+                "00010203040",
+                "000102030",
+        )):
+            with self.assertRaises(DecodeError):
+                GeneralizedTime(data.encode("ascii"), ctx={"bered": True})
+            data = data.replace(".", ",")
+            with self.assertRaises(DecodeError):
+                GeneralizedTime(data.encode("ascii"), ctx={"bered": True})
+
+    @given(
+        binary(
+            min_size=(LEN_YYYYMMDDHHMMSSZ - 1) // 2,
+            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")
+
+    def test_non_pure_integers(self):
+        for data in ((
+                # b"20000102030405Z,
+                b"+2000102030405Z",
+                b"2000+102030405Z",
+                b"200001+2030405Z",
+                b"20000102+30405Z",
+                b"2000010203+405Z",
+                b"200001020304+5Z",
+                b"20000102030405.+6Z",
+                b"20000102030405.-6Z",
+                b"_2000102030405Z",
+                b"2000_102030405Z",
+                b"200001_2030405Z",
+                b"20000102_30405Z",
+                b"2000010203_405Z",
+                b"200001020304_5Z",
+                b"20000102030405._6Z",
+                b"20000102030405.6_Z",
+                b" 2000102030405Z",
+                b"2000 102030405Z",
+                b"200001 2030405Z",
+                b"20000102 30405Z",
+                b"2000010203 405Z",
+                b"200001020304 5Z",
+                b"20000102030405. 6Z",
+                b"200 0102030405Z",
+                b"20001 02030405Z",
+                b"2000012 030405Z",
+                b"200001023 0405Z",
+                b"20000102034 05Z",
+                b"2000010203045 Z",
+                b"20000102030405.6 Z",
+        )):
+            with self.assertRaises(DecodeError):
+                GeneralizedTime(data)
+
 
 class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     base_klass = UTCTime
@@ -3410,6 +4341,30 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     min_datetime = datetime(2000, 1, 1)
     max_datetime = datetime(2049, 12, 31)
 
+    def additional_symmetric_check(self, value, obj_encoded):
+        pass
+
+    def test_repr_not_ready(self):
+        unicode(GeneralizedTime()) if PY2 else str(GeneralizedTime())
+        repr(UTCTime())
+
+    def test_x690_vector_valid(self):
+        for data in ((
+                b"920521000000Z",
+                b"920622123421Z",
+                b"920722132100Z",
+        )):
+            UTCTime(data)
+
+    def test_x690_vector_invalid(self):
+        for data in ((
+                b"920520240000Z",
+                b"9207221321Z",
+        )):
+            with self.assertRaises(DecodeError) as err:
+                UTCTime(data)
+            repr(err.exception)
+
     def test_go_vectors_invalid(self):
         for data in ((
                 b"a10506234540Z",
@@ -3453,6 +4408,229 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
             datetime(1991, 5, 6, 23, 45, 40, 0),
         )
 
+    def test_non_pure_integers(self):
+        for data in ((
+                # b"000102030405Z",
+                b"+10102030405Z",
+                b"00+102030405Z",
+                b"0001+2030405Z",
+                b"000102+30405Z",
+                b"00010203+405Z",
+                b"0001020304+5Z",
+                b"_10102030405Z",
+                b"00_102030405Z",
+                b"0001_2030405Z",
+                b"000102_30405Z",
+                b"00010203_405Z",
+                b"0001020304_5Z",
+                b"00010203045_Z",
+                b" 10102030405Z",
+                b"00 102030405Z",
+                b"0001 2030405Z",
+                b"000102 30405Z",
+                b"00010203 405Z",
+                b"0001020304 5Z",
+                b"1 0102030405Z",
+                b"001 02030405Z",
+                b"00012 030405Z",
+                b"0001023 0405Z",
+                b"000102034 05Z",
+                b"00010203045 Z",
+        )):
+            with self.assertRaises(DecodeError):
+                UTCTime(data)
+
+    def test_x680_vector_valid_ber(self):
+        for data, dt in ((
+                (b"8201021200Z", datetime(1982, 1, 2, 12)),
+                (b"8201020700-0500", datetime(1982, 1, 2, 12)),
+                (b"0101021200Z", datetime(2001, 1, 2, 12)),
+                (b"0101020700-0500", datetime(2001, 1, 2, 12)),
+        )):
+            data_der = UTCTime.tag_default + len_encode(len(data)) + data
+            obj = UTCTime().decod(data_der, ctx={"bered": True})
+            self.assertEqual(obj, dt)
+            self.assertEqual(obj.todatetime(), dt)
+            self.assertTrue(obj.ber_encoded)
+            self.assertTrue(obj.bered)
+            self.assertEqual(obj.ber_raw, data)
+            self.assertNotEqual(obj.encode(), data_der)
+            repr(obj)
+
+    def test_go_vectors_valid_ber(self):
+        for data in ((
+                b"910506164540-0700",
+                b"910506164540+0730",
+                b"9105062345Z",
+                b"5105062345Z",
+        )):
+            data = UTCTime.tag_default + len_encode(len(data)) + data
+            obj = UTCTime().decod(data, ctx={"bered": True})
+            self.assertTrue(obj.ber_encoded)
+            self.assertTrue(obj.bered)
+            self.assertNotEqual(obj.encode(), data)
+            repr(obj)
+
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(data_strategy())
+    def test_valid_ber(self, d):
+        year = d.draw(integers(min_value=0, max_value=99))
+        month = d.draw(integers(min_value=1, max_value=12))
+        day = d.draw(integers(min_value=1, max_value=28))
+        hours = d.draw(integers(min_value=0, max_value=23))
+        minute = d.draw(integers(min_value=0, max_value=59))
+        data = "%02d%02d%02d%02d%02d" % (year, month, day, hours, minute)
+        dt = datetime(
+            year + (2000 if year < 50 else 1900),
+            month,
+            day,
+            hours,
+            minute,
+        )
+        dered = False
+        if d.draw(booleans()):
+            dered = True
+            seconds = d.draw(integers(min_value=0, max_value=59))
+            data += "%02d" % seconds
+            dt += timedelta(seconds=seconds)
+        if d.draw(booleans()):
+            data += "Z"
+        else:
+            dered = False
+            offset_hour = d.draw(integers(min_value=0, max_value=13))
+            offset_minute = d.draw(integers(min_value=0, max_value=59))
+            offset = timedelta(seconds=offset_hour * 3600 + offset_minute * 60)
+            if d.draw(booleans()):
+                dt += offset
+                data += "-"
+            else:
+                dt -= offset
+                data += "+"
+            data += "%02d%02d" % (offset_hour, offset_minute)
+        data = data.encode("ascii")
+        data_der = UTCTime.tag_default + len_encode(len(data)) + data
+        obj = UTCTime().decod(data_der, ctx={"bered": True})
+        self.assertEqual(obj, dt)
+        self.assertEqual(obj.todatetime(), dt)
+        self.assertEqual(obj.ber_encoded, not dered)
+        self.assertEqual(obj.bered, not dered)
+        self.assertEqual(obj.ber_raw, None if dered else data)
+        self.assertEqual(obj.encode() == data_der, dered)
+        repr(obj)
+        bytes(obj)
+        str(obj)
+
+    def test_invalid_ber(self):
+        for data in ((
+                # b"0001020304Z",
+                b"-101020304Z",
+                b"00-1020304Z",
+                b"0001-20304Z",
+                b"000102-304Z",
+                b"000102-104Z",
+                b"00000203-4Z",
+                b"+101020304Z",
+                b"00+1020304Z",
+                b"0001+20304Z",
+                b"000102+304Z",
+                b"000102+104Z",
+                b"00000203+4Z",
+                b" 101020304Z",
+                b"00 1020304Z",
+                b"0001 20304Z",
+                b"000102 304Z",
+                b"000102 104Z",
+                b"00000203 4Z",
+                b"1 01020304Z",
+                b"001 020304Z",
+                b"00012 0304Z",
+                b"0001023 04Z",
+                b"0001021 04Z",
+                b"000002034 Z",
+                b"0013020304Z",
+                b"0001000304Z",
+                b"0001320304Z",
+                b"0001022404Z",
+                b"0001020360Z",
+                b"0002300304Z",
+                b"0001020304",
+                b"0001020304T",
+                b"0001020304+",
+                b"0001020304-",
+                b"0001020304+0",
+                b"0001020304+00",
+                b"0001020304+000",
+                b"0001020304+000Z",
+                b"0001020304+0000Z",
+                b"0001020304+-101",
+                b"0001020304+01-1",
+                b"0001020304+0060",
+                b"0001020304+1401",
+                b"5001010000+0001",
+                b"000102030Z",
+                b"0001020Z",
+        )):
+            with self.assertRaises(DecodeError):
+                UTCTime(data, ctx={"bered": True})
+            data = data[:8] + data[8+2:]
+            with self.assertRaises(DecodeError):
+                UTCTime(data, ctx={"bered": True})
+
+        for data in ((
+                # b"000102030405Z",
+                b"-10102030405Z",
+                b"00-102030405Z",
+                b"0001-2030405Z",
+                b"000102-30405Z",
+                b"000102-10405Z",
+                b"00000203-405Z",
+                b"0000020304-5Z",
+                b"+10102030405Z",
+                b"00+102030405Z",
+                b"0001+2030405Z",
+                b"000102+30405Z",
+                b"000102+10405Z",
+                b"00000203+405Z",
+                b"0000020304+5Z",
+                b" 10102030405Z",
+                b"00 102030405Z",
+                b"0001 2030405Z",
+                b"000102 30405Z",
+                b"000102 10405Z",
+                b"00000203 405Z",
+                b"0000020304 5Z",
+                b"1 0102030405Z",
+                b"001 02030405Z",
+                b"00012 030405Z",
+                b"0001023 0405Z",
+                b"0001021 0405Z",
+                b"000002034 05Z",
+                b"00000203045 Z",
+                b"001302030405Z",
+                b"000100030405Z",
+                b"000132030405Z",
+                b"000102240405Z",
+                b"000102036005Z",
+                b"000230030405Z",
+                b"000102030460Z",
+                b"000102030405",
+                b"000102030405T",
+                b"000102030405+",
+                b"000102030405-",
+                b"000102030405+0",
+                b"000102030405+00",
+                b"000102030405+000",
+                b"000102030405+000Z",
+                b"000102030405+0000Z",
+                b"000102030405+-101",
+                b"000102030405+01-1",
+                b"000102030405+0060",
+                b"000102030405+1401",
+                b"500101000002+0003",
+        )):
+            with self.assertRaises(DecodeError):
+                UTCTime(data, ctx={"bered": True})
+
     @given(integers(min_value=0, max_value=49))
     def test_pre50(self, year):
         self.assertEqual(
@@ -3467,6 +4645,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):
@@ -3505,14 +4704,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):
@@ -3528,10 +4729,11 @@ 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())
+    @given(binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2):
         for klass in (Any, AnyInherited):
             obj1 = klass(value1)
@@ -3583,9 +4785,10 @@ class TestAny(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Any, AnyInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(binary().map(OctetString))
     def test_stripped(self, value):
@@ -3605,10 +4808,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],
@@ -3622,10 +4824,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],
@@ -3649,20 +4850,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))
@@ -3684,18 +4891,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 = copy(obj)
+        self.assertTrue(obj.lenindef)
+        self.assertFalse(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        repr(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
+        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(
@@ -3759,7 +5068,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()
@@ -3767,11 +5077,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):
@@ -3850,6 +5162,7 @@ class TestChoice(CommonMixin, TestCase):
 
         class Wahl(self.base_klass):
             schema = _schema
+        register_class(Wahl)
         obj = Wahl(
             value=value,
             expl=expl,
@@ -3857,15 +5170,17 @@ class TestChoice(CommonMixin, TestCase):
             optional=optional or False,
             _decoded=_decoded,
         )
-        obj_copied = obj.copy()
-        self.assertIsNone(obj.tag)
-        self.assertIsNone(obj_copied.tag)
-        # hack for assert_copied_basic_fields
-        obj.tag = "whatever"
-        obj_copied.tag = "whatever"
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._value, obj_copied._value)
-        self.assertEqual(obj.specs, obj_copied.specs)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assertIsNone(obj.tag)
+            self.assertIsNone(obj_copied.tag)
+            # hack for assert_copied_basic_fields
+            obj.tag = "whatever"
+            obj_copied.tag = "whatever"
+            self.assert_copied_basic_fields(obj, obj_copied)
+            obj.tag = None
+            self.assertEqual(obj._value, obj_copied._value)
+            self.assertEqual(obj.specs, obj_copied.specs)
 
     @given(booleans())
     def test_stripped(self, value):
@@ -3901,20 +5216,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)
@@ -3937,11 +5258,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):
@@ -4001,15 +5327,13 @@ def seq_values_strategy(draw, seq_klass, do_expl=False):
     value = None
     if draw(booleans()):
         value = seq_klass()
-        value._value = {
-            k: v for k, v in draw(dictionaries(
-                integers(),
-                one_of(
-                    booleans().map(Boolean),
-                    integers().map(Integer),
-                ),
-            )).items()
-        }
+        value._value = draw(dictionaries(
+            integers(),
+            one_of(
+                booleans().map(Boolean),
+                integers().map(Integer),
+            ),
+        ))
     schema = None
     if draw(booleans()):
         schema = list(draw(dictionaries(
@@ -4028,15 +5352,13 @@ def seq_values_strategy(draw, seq_klass, do_expl=False):
     default = None
     if draw(booleans()):
         default = seq_klass()
-        default._value = {
-            k: v for k, v in draw(dictionaries(
-                integers(),
-                one_of(
-                    booleans().map(Boolean),
-                    integers().map(Integer),
-                ),
-            )).items()
-        }
+        default._value = draw(dictionaries(
+            integers(),
+            one_of(
+                booleans().map(Boolean),
+                integers().map(Integer),
+            ),
+        ))
     optional = draw(one_of(none(), booleans()))
     _decoded = (
         draw(integers(min_value=0)),
@@ -4216,12 +5538,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)
@@ -4229,7 +5553,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):
@@ -4291,13 +5616,15 @@ class SeqMixing(object):
     def test_copy(self, d):
         class SeqInherited(self.base_klass):
             pass
+        register_class(SeqInherited)
         for klass in (self.base_klass, SeqInherited):
             values = d.draw(seq_values_strategy(seq_klass=klass))
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj.specs, obj_copied.specs)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj.specs, obj_copied.specs)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -4351,10 +5678,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],
@@ -4368,10 +5694,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],
@@ -4406,27 +5731,70 @@ 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
+        with self.assertRaises(DecodeError):
+            seq.decode(seq_encoded_lenindef)
+        ctx_copied = deepcopy(ctx_dummy)
+        ctx_copied["bered"] = True
+        seq_decoded_lenindef, tail_lenindef = seq.decode(
+            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 = copy(seq_decoded_lenindef)
+        self.assertTrue(seq_decoded_lenindef.lenindef)
+        self.assertTrue(seq_decoded_lenindef.bered)
+        with self.assertRaises(DecodeError):
+            seq.decode(seq_encoded_lenindef[:-1], ctx={"bered": True})
+        with self.assertRaises(DecodeError):
+            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())
@@ -4479,14 +5847,14 @@ 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(),
             min_size=1,
         )).items())
         tags = [tag_encode(tag) for tag in d.draw(sets(
-            integers(min_value=0),
+            integers(min_value=1),
             min_size=len(_schema),
             max_size=len(_schema),
         ))]
@@ -4507,16 +5875,24 @@ 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 = copy(seq_decoded)
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            for name, value in _schema:
+                self.assertEqual(seq_decoded[name], seq_with_default[name])
+                self.assertEqual(seq_decoded[name], value)
 
     @given(data_strategy())
     def test_missing_from_spec(self, d):
         names = list(d.draw(sets(text_letters(), min_size=2)))
         tags = [tag_encode(tag) for tag in d.draw(sets(
-            integers(min_value=0),
+            integers(min_value=1),
             min_size=len(names),
             max_size=len(names),
         ))]
@@ -4536,6 +5912,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 = copy(decoded)
+        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 = copy(decoded)
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+
 
 class TestSequence(SeqMixing, CommonMixin, TestCase):
     base_klass = Sequence
@@ -4606,6 +6016,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 = copy(seq_decoded)
+            self.assertTrue(seq_decoded.ber_encoded)
+            self.assertTrue(seq_decoded.bered)
+            self.assertSequenceEqual(
+                [bytes(seq_decoded[str(i)]) for i, t in enumerate(tags)],
+                tags,
+            )
+
 
 @composite
 def seqof_values_strategy(draw, schema=None, do_expl=False):
@@ -4668,7 +6111,7 @@ class SeqOfMixing(object):
         with assertRaisesRegex(self, ValueError, "schema must be specified"):
             self.base_klass.__mro__[1]()
 
-    @given(booleans(), booleans(), binary(), binary())
+    @given(booleans(), booleans(), binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
         class SeqOf(self.base_klass):
             schema = Boolean()
@@ -4712,7 +6155,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)
@@ -4722,7 +6166,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):
@@ -4749,17 +6194,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):
@@ -4864,6 +6319,7 @@ class SeqOfMixing(object):
 
         class SeqOf(self.base_klass):
             schema = _schema
+        register_class(SeqOf)
         obj = SeqOf(
             value=value,
             bounds=bounds,
@@ -4873,11 +6329,12 @@ class SeqOfMixing(object):
             optional=optional or False,
             _decoded=_decoded,
         )
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._bound_min, obj_copied._bound_min)
-        self.assertEqual(obj._bound_max, obj_copied._bound_max)
-        self.assertEqual(obj._value, obj_copied._value)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj._bound_min, obj_copied._bound_min)
+            self.assertEqual(obj._bound_max, obj_copied._bound_max)
+            self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         lists(binary()),
@@ -4904,10 +6361,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],
@@ -4921,10 +6377,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],
@@ -4961,20 +6416,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)
@@ -5001,6 +6462,74 @@ class SeqOfMixing(object):
                 ],
             )
 
+        t, _, lv = tag_strip(obj_encoded)
+        _, _, v = len_decode(lv)
+        obj_encoded_lenindef = t + LENINDEF + v + EOC
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef)
+        obj_decoded_lenindef, tail_lenindef = obj.decode(
+            obj_encoded_lenindef + tail_junk,
+            ctx={"bered": True},
+        )
+        self.assertTrue(obj_decoded_lenindef.lenindef)
+        self.assertTrue(obj_decoded_lenindef.bered)
+        obj_decoded_lenindef = copy(obj_decoded_lenindef)
+        self.assertTrue(obj_decoded_lenindef.lenindef)
+        self.assertTrue(obj_decoded_lenindef.bered)
+        repr(obj_decoded_lenindef)
+        list(obj_decoded_lenindef.pps())
+        pprint(obj_decoded_lenindef, big_blobs=True, with_decode_path=True)
+        self.assertEqual(tail_lenindef, tail_junk)
+        self.assertEqual(obj_decoded_lenindef.tlvlen, len(obj_encoded_lenindef))
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef[:-1], ctx={"bered": True})
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True})
+
+        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 = copy(decoded)
+        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 = copy(decoded)
+        self.assertFalse(decoded.ber_encoded)
+        self.assertFalse(decoded.lenindef)
+        self.assertTrue(decoded.bered)
+
 
 class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase):
     class SeqOf(SequenceOf):
@@ -5038,6 +6567,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 = copy(seq_decoded)
+            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):
@@ -5180,8 +6744,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 = (
@@ -5233,7 +6800,10 @@ class TestPP(TestCase):
         chosen_id = oids[chosen]
         pp = _pp(asn1_type_name=ObjectIdentifier.asn1_type_name, value=chosen)
         self.assertNotIn(chosen_id, pp_console_row(pp))
-        self.assertIn(chosen_id, pp_console_row(pp, oids=oids))
+        self.assertIn(
+            chosen_id,
+            pp_console_row(pp, oid_maps=[{'whatever': 'whenever'}, oids]),
+        )
 
 
 class TestAutoAddSlots(TestCase):
@@ -5261,29 +6831,33 @@ class TestOIDDefines(TestCase):
             min_size=len(value_names),
             max_size=len(value_names),
         ))
-        _schema = [
-            ("type", ObjectIdentifier(defines=(((value_name_chosen,), {
-                oid: Integer() for oid in oids[:-1]
-            }),))),
-        ]
-        for i, value_name in enumerate(value_names):
-            _schema.append((value_name, Any(expl=tag_ctxp(i))))
+        for definable_class in (Any, OctetString, BitString):
+            _schema = [
+                ("type", ObjectIdentifier(defines=(((value_name_chosen,), {
+                    oid: Integer() for oid in oids[:-1]
+                }),))),
+            ]
+            for i, value_name in enumerate(value_names):
+                _schema.append((value_name, definable_class(expl=tag_ctxp(i))))
 
-        class Seq(Sequence):
-            schema = _schema
-        seq = Seq()
-        for value_name, value in zip(value_names, values):
-            seq[value_name] = Any(Integer(value).encode())
-        seq["type"] = oid_chosen
-        seq, _ = Seq().decode(seq.encode())
-        for value_name in value_names:
-            if value_name == value_name_chosen:
-                continue
-            self.assertIsNone(seq[value_name].defined)
-        if value_name_chosen in oids[:-1]:
-            self.assertIsNotNone(seq[value_name_chosen].defined)
-            self.assertEqual(seq[value_name_chosen].defined[0], oid_chosen)
-            self.assertIsInstance(seq[value_name_chosen].defined[1], Integer)
+            class Seq(Sequence):
+                schema = _schema
+            seq = Seq()
+            for value_name, value in zip(value_names, values):
+                seq[value_name] = definable_class(Integer(value).encode())
+            seq["type"] = oid_chosen
+            seq, _ = Seq().decode(seq.encode())
+            for value_name in value_names:
+                if value_name == value_name_chosen:
+                    continue
+                self.assertIsNone(seq[value_name].defined)
+            if value_name_chosen in oids[:-1]:
+                self.assertIsNotNone(seq[value_name_chosen].defined)
+                self.assertEqual(seq[value_name_chosen].defined[0], oid_chosen)
+                self.assertIsInstance(seq[value_name_chosen].defined[1], Integer)
+            repr(seq)
+            list(seq.pps())
+            pprint(seq, big_blobs=True, with_decode_path=True)
 
 
 class TestDefinesByPath(TestCase):
@@ -5334,10 +6908,10 @@ class TestDefinesByPath(TestCase):
             (type_integered, Integer(234)),
         )
         for t, v in pairs_input:
-            pair = Pair()
-            pair["type"] = t
-            pair["value"] = PairValue((Any(v),))
-            pairs.append(pair)
+            pairs.append(Pair((
+                ("type", t),
+                ("value", PairValue((Any(v),))),
+            )))
         seq_inner = SeqInner()
         seq_inner["typeInner"] = type_innered
         seq_inner["valueInner"] = Any(pairs)
@@ -5345,9 +6919,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",), {
@@ -5355,34 +6937,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]
@@ -5391,6 +6988,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((
             (
@@ -5406,10 +7006,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]
@@ -5419,6 +7022,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):
@@ -5443,6 +7049,43 @@ class TestDefinesByPath(TestCase):
         decoded, _ = Outer().decode(outer.encode())
         self.assertEqual(decoded["tgt"].defined[1], Integer(tgt))
 
+    def test_remaining_data(self):
+        oid = ObjectIdentifier("1.2.3")
+        class Seq(Sequence):
+            schema = (
+                ("oid", ObjectIdentifier(defines=((("tgt",), {
+                    oid: Integer(),
+                }),))),
+                ("tgt", OctetString()),
+            )
+
+        seq = Seq((
+            ("oid", oid),
+            ("tgt", OctetString(Integer(123).encode() + b"junk")),
+        ))
+        with assertRaisesRegex(self, DecodeError, "remaining data"):
+            Seq().decode(seq.encode())
+
+    def test_remaining_data_seqof(self):
+        oid = ObjectIdentifier("1.2.3")
+        class SeqOf(SetOf):
+            schema = OctetString()
+
+        class Seq(Sequence):
+            schema = (
+                ("oid", ObjectIdentifier(defines=((("tgt",), {
+                    oid: Integer(),
+                }),))),
+                ("tgt", SeqOf()),
+            )
+
+        seq = Seq((
+            ("oid", oid),
+            ("tgt", SeqOf([OctetString(Integer(123).encode() + b"junk")])),
+        ))
+        with assertRaisesRegex(self, DecodeError, "remaining data"):
+            Seq().decode(seq.encode())
+
 
 class TestAbsDecodePath(TestCase):
     @given(
@@ -5450,10 +7093,9 @@ class TestAbsDecodePath(TestCase):
         lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
     )
     def test_concat(self, decode_path, rel_path):
-        self.assertSequenceEqual(
-            abs_decode_path(decode_path, rel_path),
-            decode_path + rel_path,
-        )
+        dp = abs_decode_path(decode_path, rel_path)
+        self.assertSequenceEqual(dp, decode_path + rel_path)
+        repr(dp)
 
     @given(
         lists(text(alphabet=ascii_letters, min_size=1)).map(tuple),
@@ -5486,18 +7128,29 @@ class TestStrictDefaultExistence(TestCase):
             ("int%d" % i, Integer(expl=tag_ctxc(i + 1)))
             for i in range(count)
         ]
-
-        class Seq(Sequence):
-            schema = _schema
-        seq = Seq()
-        for i in range(count):
-            seq["int%d" % i] = Integer(123)
-        raw = seq.encode()
-        chosen = "int%d" % chosen
-        seq.specs[chosen] = seq.specs[chosen](default=123)
-        seq.decode(raw)
-        with assertRaisesRegex(self, DecodeError, "DEFAULT value met"):
-            seq.decode(raw, ctx={"strict_default_existence": True})
+        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 = copy(decoded)
+            self.assertTrue(decoded.ber_encoded)
+            self.assertTrue(decoded.bered)
+            decoded, _ = seq.decode(raw, ctx={"bered": True})
+            self.assertTrue(decoded.ber_encoded)
+            self.assertTrue(decoded.bered)
+            decoded = copy(decoded)
+            self.assertTrue(decoded.ber_encoded)
+            self.assertTrue(decoded.bered)
 
 
 class TestX690PrefixedType(TestCase):
@@ -5537,3 +7190,25 @@ class TestX690PrefixedType(TestCase):
             VisibleString("Jones", impl=tag_ctxp(2)).encode(),
             hexdec("82054A6F6E6573"),
         )
+
+
+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})
+
+
+class TestPickleDifferentVersion(TestCase):
+    def runTest(self):
+        pickled = pickle_dumps(Integer(123), pickle_proto)
+        import pyderasn
+        version_orig = pyderasn.__version__
+        pyderasn.__version__ += "different"
+        with assertRaisesRegex(self, ValueError, "different PyDERASN version"):
+            pickle_loads(pickled)
+        pyderasn.__version__ = version_orig
+        pickle_loads(pickled)