]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
Raise copyright years
[pyderasn.git] / tests / test_pyderasn.py
index 23127261feb9175309eea0f8b6e41356f0e7cf56..e8f8bdf4570e1c52adbf2b7a7c5a827c47a2099c 100644 (file)
@@ -1,11 +1,10 @@
 # coding: utf-8
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
-# Copyright (C) 2017 Sergey Matveev <stargrave@stargrave.org>
+# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
+# Copyright (C) 2017-2024 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
 #
 # 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
 #
 # 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/>.
 
 # 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 datetime
+from datetime import timedelta
+from importlib import import_module
+from io import BytesIO
+from operator import attrgetter
+from os import environ
+from os import urandom
+from random import random
 from string import ascii_letters
 from string import ascii_letters
+from string import digits
 from string import printable
 from string import whitespace
 from string import printable
 from string import whitespace
+from time import mktime
+from time import time
 from unittest import TestCase
 from unittest import TestCase
+from unittest.mock import patch
 
 
+from dateutil.tz import UTC
 from hypothesis import assume
 from hypothesis import given
 from hypothesis import settings
 from hypothesis import assume
 from hypothesis import given
 from hypothesis import settings
@@ -41,24 +54,26 @@ from hypothesis.strategies import sampled_from
 from hypothesis.strategies import sets
 from hypothesis.strategies import text
 from hypothesis.strategies import tuples
 from hypothesis.strategies import sets
 from hypothesis.strategies import text
 from hypothesis.strategies import tuples
-from six import assertRaisesRegex
-from six import byte2int
-from six import indexbytes
-from six import int2byte
-from six import iterbytes
-from six import PY2
-from six import text_type
+from pickle import dumps as pickle_dumps
+from pickle import HIGHEST_PROTOCOL as pickle_proto
+from pickle import loads as pickle_loads
 
 from pyderasn import _pp
 
 from pyderasn import _pp
+from pyderasn import abs_decode_path
 from pyderasn import Any
 from pyderasn import BitString
 from pyderasn import BMPString
 from pyderasn import Boolean
 from pyderasn import BoundsError
 from pyderasn import Choice
 from pyderasn import Any
 from pyderasn import BitString
 from pyderasn import BMPString
 from pyderasn import Boolean
 from pyderasn import BoundsError
 from pyderasn import Choice
-from pyderasn import decode_path_defby
 from pyderasn import DecodeError
 from pyderasn import DecodeError
+from pyderasn import DecodePathDefBy
+from pyderasn import encode2pass
+from pyderasn import encode_cer
 from pyderasn import Enumerated
 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
 from pyderasn import GeneralizedTime
 from pyderasn import GeneralString
 from pyderasn import GraphicString
@@ -71,6 +86,11 @@ from pyderasn import InvalidOID
 from pyderasn import InvalidValueType
 from pyderasn import len_decode
 from pyderasn import len_encode
 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
 from pyderasn import NotEnoughData
 from pyderasn import Null
 from pyderasn import NumericString
@@ -103,13 +123,15 @@ from pyderasn import UTCTime
 from pyderasn import UTF8String
 from pyderasn import VideotexString
 from pyderasn import VisibleString
 from pyderasn import UTF8String
 from pyderasn import VideotexString
 from pyderasn import VisibleString
+import pyderasn
 
 
 
 
-settings.register_profile('local', settings(
+max_examples = environ.get("MAX_EXAMPLES")
+settings.register_profile("local", settings(
     deadline=5000,
     deadline=5000,
-    perform_health_check=False,
+    **({"max_examples": int(max_examples)} if max_examples else {})
 ))
 ))
-settings.load_profile('local')
+settings.load_profile("local")
 LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4
 
 tag_classes = sampled_from((
 LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4
 
 tag_classes = sampled_from((
@@ -119,6 +141,30 @@ tag_classes = sampled_from((
     TagClassUniversal,
 ))
 tag_forms = sampled_from((TagFormConstructed, TagFormPrimitive))
     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 self.assertRaisesRegex(ExceedingData, "%d trailing bytes" % len(junk)) as err:
+        call()
+    repr(err)
 
 
 class TestHex(TestCase):
 
 
 class TestHex(TestCase):
@@ -140,8 +186,8 @@ class TestTagCoder(TestCase):
         self.assertEqual(tag_decode(raw), (klass, form, num))
         self.assertEqual(len(raw), 1)
         self.assertEqual(
         self.assertEqual(tag_decode(raw), (klass, form, num))
         self.assertEqual(len(raw), 1)
         self.assertEqual(
-            byte2int(tag_encode(klass=klass, form=form, num=0)),
-            byte2int(raw) & (1 << 7 | 1 << 6 | 1 << 5),
+            tag_encode(klass=klass, form=form, num=0)[0],
+            raw[0] & (1 << 7 | 1 << 6 | 1 << 5),
         )
         stripped, tlen, tail = tag_strip(memoryview(raw + junk))
         self.assertSequenceEqual(stripped.tobytes(), raw)
         )
         stripped, tlen, tail = tag_strip(memoryview(raw + junk))
         self.assertSequenceEqual(stripped.tobytes(), raw)
@@ -160,11 +206,11 @@ class TestTagCoder(TestCase):
         self.assertEqual(tag_decode(raw), (klass, form, num))
         self.assertGreater(len(raw), 1)
         self.assertEqual(
         self.assertEqual(tag_decode(raw), (klass, form, num))
         self.assertGreater(len(raw), 1)
         self.assertEqual(
-            byte2int(tag_encode(klass=klass, form=form, num=0)) | 31,
-            byte2int(raw[:1]),
+            tag_encode(klass=klass, form=form, num=0)[0] | 31,
+            raw[0],
         )
         )
-        self.assertEqual(byte2int(raw[-1:]) & 0x80, 0)
-        self.assertTrue(all(b & 0x80 > 0 for b in iterbytes(raw[1:-1])))
+        self.assertEqual(raw[-1] & 0x80, 0)
+        self.assertTrue(all(b & 0x80 > 0 for b in raw[1:-1]))
         stripped, tlen, tail = tag_strip(memoryview(raw + junk))
         self.assertSequenceEqual(stripped.tobytes(), raw)
         self.assertEqual(tlen, len(raw))
         stripped, tlen, tail = tag_strip(memoryview(raw + junk))
         self.assertSequenceEqual(stripped.tobytes(), raw)
         self.assertEqual(tlen, len(raw))
@@ -176,7 +222,7 @@ class TestTagCoder(TestCase):
         raw = bytearray(tag_encode(num=num))
         for i in range(1, len(raw)):
             raw[i] |= 0x80
         raw = bytearray(tag_encode(num=num))
         for i in range(1, len(raw)):
             raw[i] |= 0x80
-        with assertRaisesRegex(self, DecodeError, "unfinished tag"):
+        with self.assertRaisesRegex(DecodeError, "unfinished tag"):
             tag_strip(bytes(raw))
 
     def test_go_vectors_valid(self):
             tag_strip(bytes(raw))
 
     def test_go_vectors_valid(self):
@@ -218,11 +264,24 @@ class TestTagCoder(TestCase):
         integers(min_value=0, max_value=2),
     )
     def test_long_instead_of_short(self, l, dummy_num):
         integers(min_value=0, max_value=2),
     )
     def test_long_instead_of_short(self, l, dummy_num):
-        octets = (b"\x00" * dummy_num) + int2byte(l)
-        octets = int2byte((dummy_num + 1) | 0x80) + octets
+        octets = (b"\x00" * dummy_num) + bytes([l])
+        octets = bytes([(dummy_num + 1) | 0x80]) + octets
         with self.assertRaises(DecodeError):
             len_decode(octets)
 
         with self.assertRaises(DecodeError):
             len_decode(octets)
 
+    @given(tag_classes, tag_forms, integers(min_value=31))
+    def test_leading_zero_byte(self, klass, form, num):
+        raw = tag_encode(klass=klass, form=form, num=num)
+        raw = b"".join((raw[:1], b"\x80", raw[1:]))
+        with self.assertRaisesRegex(DecodeError, "leading zero byte"):
+            tag_strip(raw)
+
+    @given(tag_classes, tag_forms, integers(max_value=30, min_value=0))
+    def test_unexpected_long_form(self, klass, form, num):
+        raw = bytes([klass | form | 31, num])
+        with self.assertRaisesRegex(DecodeError, "unexpected long form"):
+            tag_strip(raw)
+
 
 class TestLenCoder(TestCase):
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
 
 class TestLenCoder(TestCase):
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
@@ -247,9 +306,9 @@ class TestLenCoder(TestCase):
         raw = len_encode(l) + junk
         decoded, llen, tail = len_decode(memoryview(raw))
         self.assertEqual(decoded, l)
         raw = len_encode(l) + junk
         decoded, llen, tail = len_decode(memoryview(raw))
         self.assertEqual(decoded, l)
-        self.assertEqual((llen - 1) | 0x80, byte2int(raw))
+        self.assertEqual((llen - 1) | 0x80, raw[0])
         self.assertEqual(llen, len(raw) - len(junk))
         self.assertEqual(llen, len(raw) - len(junk))
-        self.assertNotEqual(indexbytes(raw, 1), 0)
+        self.assertNotEqual(raw[1], 0)
         self.assertSequenceEqual(tail.tobytes(), junk)
 
     def test_empty(self):
         self.assertSequenceEqual(tail.tobytes(), junk)
 
     def test_empty(self):
@@ -268,8 +327,6 @@ text_printable = text(alphabet=printable, min_size=1)
 @composite
 def text_letters(draw):
     result = draw(text(alphabet=ascii_letters, min_size=1))
 @composite
 def text_letters(draw):
     result = draw(text(alphabet=ascii_letters, min_size=1))
-    if PY2:
-        result = result.encode("ascii")
     return result
 
 
     return result
 
 
@@ -282,7 +339,7 @@ class CommonMixin(object):
         with self.assertRaises(ValueError):
             self.base_klass(impl=b"whatever", expl=b"whenever")
 
         with self.assertRaises(ValueError):
             self.base_klass(impl=b"whatever", expl=b"whenever")
 
-    @given(binary(), integers(), integers(), integers())
+    @given(binary(min_size=1), integers(), integers(), integers())
     def test_decoded(self, impl, offset, llen, vlen):
         obj = self.base_klass(impl=impl, _decoded=(offset, llen, vlen))
         self.assertEqual(obj.offset, offset)
     def test_decoded(self, impl, offset, llen, vlen):
         obj = self.base_klass(impl=impl, _decoded=(offset, llen, vlen))
         self.assertEqual(obj.offset, offset)
@@ -291,21 +348,27 @@ class CommonMixin(object):
         self.assertEqual(obj.tlen, len(impl))
         self.assertEqual(obj.tlvlen, obj.tlen + obj.llen + obj.vlen)
 
         self.assertEqual(obj.tlen, len(impl))
         self.assertEqual(obj.tlvlen, obj.tlen + obj.llen + obj.vlen)
 
-    @given(binary())
+    @given(binary(min_size=1))
     def test_impl_inherited(self, impl_tag):
         class Inherited(self.base_klass):
             impl = impl_tag
         obj = Inherited()
         self.assertSequenceEqual(obj.impl, impl_tag)
         self.assertFalse(obj.expled)
     def test_impl_inherited(self, impl_tag):
         class Inherited(self.base_klass):
             impl = impl_tag
         obj = Inherited()
         self.assertSequenceEqual(obj.impl, impl_tag)
         self.assertFalse(obj.expled)
+        if obj.ready:
+            tag_class, _, tag_num = tag_decode(impl_tag)
+            self.assertEqual(obj.tag_order, (tag_class, tag_num))
 
 
-    @given(binary())
+    @given(binary(min_size=1))
     def test_expl_inherited(self, expl_tag):
         class Inherited(self.base_klass):
             expl = expl_tag
         obj = Inherited()
         self.assertSequenceEqual(obj.expl, expl_tag)
         self.assertTrue(obj.expled)
     def test_expl_inherited(self, expl_tag):
         class Inherited(self.base_klass):
             expl = expl_tag
         obj = Inherited()
         self.assertSequenceEqual(obj.expl, expl_tag)
         self.assertTrue(obj.expled)
+        if obj.ready:
+            tag_class, _, tag_num = tag_decode(expl_tag)
+            self.assertEqual(obj.tag_order, (tag_class, tag_num))
 
     def assert_copied_basic_fields(self, obj, obj_copied):
         self.assertEqual(obj, obj_copied)
 
     def assert_copied_basic_fields(self, obj, obj_copied):
         self.assertEqual(obj, obj_copied)
@@ -316,6 +379,8 @@ class CommonMixin(object):
         self.assertEqual(obj.offset, obj_copied.offset)
         self.assertEqual(obj.llen, obj_copied.llen)
         self.assertEqual(obj.vlen, obj_copied.vlen)
         self.assertEqual(obj.offset, obj_copied.offset)
         self.assertEqual(obj.llen, obj_copied.llen)
         self.assertEqual(obj.vlen, obj_copied.vlen)
+        if obj.ready:
+            self.assertEqual(obj.tag_order, obj_copied.tag_order)
 
 
 @composite
 
 
 @composite
@@ -359,16 +424,20 @@ class TestBoolean(CommonMixin, TestCase):
         obj = Boolean()
         self.assertFalse(obj.ready)
         repr(obj)
         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()
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         repr(err.exception)
         obj = Boolean(value)
         self.assertTrue(obj.ready)
         repr(obj)
         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)
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (Boolean, BooleanInherited):
             obj1 = klass(value1)
@@ -433,8 +502,9 @@ class TestBoolean(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Boolean, BooleanInherited):
             obj = klass(*values)
     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(),
 
     @given(
         booleans(),
@@ -444,6 +514,8 @@ class TestBoolean(CommonMixin, TestCase):
         obj = Boolean(value, impl=tag_impl)
         with self.assertRaises(NotEnoughData):
             obj.decode(obj.encode()[:-1])
         obj = Boolean(value, impl=tag_impl)
         with self.assertRaises(NotEnoughData):
             obj.decode(obj.encode()[:-1])
+        with self.assertRaises(NotEnoughData):
+            obj.decode(encode2pass(obj)[:-1])
 
     @given(
         booleans(),
 
     @given(
         booleans(),
@@ -453,14 +525,15 @@ class TestBoolean(CommonMixin, TestCase):
         obj = Boolean(value, expl=tag_expl)
         with self.assertRaises(NotEnoughData):
             obj.decode(obj.encode()[:-1])
         obj = Boolean(value, expl=tag_expl)
         with self.assertRaises(NotEnoughData):
             obj.decode(obj.encode()[:-1])
+        with self.assertRaises(NotEnoughData):
+            obj.decode(encode2pass(obj)[:-1])
 
     @given(
         integers(min_value=31),
         integers(min_value=0),
 
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Boolean().decode(
                 tag_encode(tag)[:-1],
@@ -474,10 +547,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_expl_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Boolean(expl=Boolean.tag_default).decode(
                 tag_encode(tag)[:-1],
@@ -491,10 +563,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Boolean().decode(
                 Boolean.tag_default + len_encode(l)[:-1],
@@ -508,10 +579,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_expl_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Boolean(expl=Boolean.tag_default).decode(
                 Boolean.tag_default + len_encode(l)[:-1],
@@ -528,8 +598,10 @@ class TestBoolean(CommonMixin, TestCase):
         booleans(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
         booleans(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
+        decode_path_strat,
     )
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (Boolean, BooleanInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
         for klass in (Boolean, BooleanInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -539,23 +611,40 @@ class TestBoolean(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
                 _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.assertFalse(obj.expled)
             obj_encoded = obj.encode()
+            self.assertEqual(encode2pass(obj), obj_encoded)
+            self.assertSequenceEqual(encode_cer(obj), obj_encoded)
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
             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, offset=offset)
+            list(obj_expled.pps())
+            pprint(obj_expled, big_blobs=True, with_decode_path=True)
+            obj_expled_cer = encode_cer(obj_expled)
+            self.assertNotEqual(obj_expled_cer, obj_encoded)
+            self.assertSequenceEqual(
+                obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(),
+                obj_expled.encode(),
+            )
+            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)
             repr(obj_decoded)
-            pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            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.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(
             self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
             self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
             self.assertEqual(
@@ -569,8 +658,28 @@ class TestBoolean(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
                 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,
+            )
+
+            evgens = list(obj_expled.decode_evgen(
+                hexdec(obj_expled_hex_encoded) + tail_junk,
+                offset=offset,
+                decode_path=decode_path,
+                ctx=ctx_copied,
+            ))
+            self.assertEqual(len(evgens), 1)
+            _decode_path, obj, tail = evgens[0]
+            self.assertSequenceEqual(tail, tail_junk)
+            self.assertEqual(_decode_path, decode_path)
+            self.assertEqual(obj, obj_decoded)
+            self.assertEqual(obj.expl_offset, offset)
+            repr(obj)
+            list(obj.pps())
 
 
-    @given(integers(min_value=2))
+    @given(integers(min_value=2, max_value=10))
     def test_invalid_len(self, l):
         with self.assertRaises(InvalidLength):
             Boolean().decode(b"".join((
     def test_invalid_len(self, l):
         with self.assertRaises(InvalidLength):
             Boolean().decode(b"".join((
@@ -580,13 +689,107 @@ class TestBoolean(CommonMixin, TestCase):
             )))
 
     @given(integers(min_value=0 + 1, max_value=255 - 1))
             )))
 
     @given(integers(min_value=0 + 1, max_value=255 - 1))
-    def test_invalid_value(self, value):
-        with assertRaisesRegex(self, DecodeError, "unacceptable Boolean value"):
+    def test_ber_value(self, value):
+        with self.assertRaisesRegex(DecodeError, "unacceptable Boolean value"):
             Boolean().decode(b"".join((
                 Boolean.tag_default,
                 len_encode(1),
             Boolean().decode(b"".join((
                 Boolean.tag_default,
                 len_encode(1),
-                int2byte(value),
+                bytes([value]),
             )))
             )))
+        encoded = b"".join((Boolean.tag_default, len_encode(1), bytes([value])))
+        obj, _ = Boolean().decode(encoded, ctx={"bered": True})
+        list(Boolean().decode_evgen(encoded, 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 self.assertRaisesRegex(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),
+        lists(
+            booleans(),
+            min_size=1,
+            max_size=5,
+        ),
+    )
+    def test_ber_expl(self, expl, values):
+        encoded = b""
+        for value in values:
+            encoded += (
+                expl +
+                LENINDEF +
+                Boolean(value).encode() +
+                EOC
+            )
+        encoded = SequenceOf.tag_default + len_encode(len(encoded)) + encoded
+
+        class SeqOf(SequenceOf):
+            schema = Boolean(expl=expl)
+        with self.assertRaises(LenIndefForm):
+            SeqOf().decode(encoded)
+        seqof, tail = SeqOf().decode(encoded, ctx={"bered": True})
+        list(SeqOf().decode_evgen(encoded, ctx={"bered": True}))
+        self.assertSequenceEqual(tail, b"")
+        self.assertSequenceEqual([bool(v) for v in seqof], values)
+        self.assertSetEqual(
+            set(
+                (
+                    v.tlvlen,
+                    v.expl_tlvlen,
+                    v.expl_tlen,
+                    v.expl_llen,
+                    v.ber_encoded,
+                    v.lenindef,
+                    v.expl_lenindef,
+                    v.bered,
+                ) for v in seqof
+            ),
+            set(((
+                3 + EOC_LEN,
+                len(expl) + 1 + 3 + EOC_LEN,
+                len(expl),
+                1,
+                False,
+                False,
+                True,
+                True,
+            ),)),
+        )
+        repr(seqof)
+        list(seqof.pps())
+        pprint(seqof, big_blobs=True, with_decode_path=True)
 
 
 @composite
 
 
 @composite
@@ -665,17 +868,21 @@ class TestInteger(CommonMixin, TestCase):
         obj = Integer()
         self.assertFalse(obj.ready)
         repr(obj)
         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()
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         repr(err.exception)
         obj = Integer(value)
         self.assertTrue(obj.ready)
         repr(obj)
         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)
 
         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)
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (Integer, IntegerInherited):
             obj1 = klass(value1)
@@ -724,9 +931,27 @@ class TestInteger(CommonMixin, TestCase):
         with self.assertRaises(BoundsError) as err:
             Integer(value=values[0], bounds=(values[1], values[2]))
         repr(err.exception)
         with self.assertRaises(BoundsError) as err:
             Integer(value=values[0], bounds=(values[1], values[2]))
         repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            Integer(bounds=(values[1], values[2])).decode(
+                Integer(values[0]).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            Integer(bounds=(values[1], values[2])).decode(
+                encode2pass(Integer(values[0]))
+            )
         with self.assertRaises(BoundsError) as err:
             Integer(value=values[2], bounds=(values[0], values[1]))
         repr(err.exception)
         with self.assertRaises(BoundsError) as err:
             Integer(value=values[2], bounds=(values[0], values[1]))
         repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            Integer(bounds=(values[0], values[1])).decode(
+                Integer(values[2]).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            Integer(bounds=(values[0], values[1])).decode(
+                encode2pass(Integer(values[2]))
+            )
 
     @given(data_strategy())
     def test_call(self, d):
 
     @given(data_strategy())
     def test_call(self, d):
@@ -810,12 +1035,13 @@ class TestInteger(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Integer, IntegerInherited):
             obj = klass(*values)
     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(),
 
     @given(
         integers(),
@@ -845,10 +1071,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Integer().decode(
                 tag_encode(tag)[:-1],
@@ -862,10 +1087,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Integer().decode(
                 Integer.tag_default + len_encode(l)[:-1],
@@ -879,10 +1103,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         sets(integers(), min_size=2, max_size=2),
         integers(min_value=0),
     @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):
     )
     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):
         value, bound_min = list(sorted(ints))
 
         class Int(Integer):
@@ -903,8 +1126,10 @@ class TestInteger(CommonMixin, TestCase):
         integers(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
         integers(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
+        decode_path_strat,
     )
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (Integer, IntegerInherited):
             _, _, _, _, default, optional, _, _decoded = values
             obj = klass(
         for klass in (Integer, IntegerInherited):
             _, _, _, _, default, optional, _, _decoded = values
             obj = klass(
@@ -914,18 +1139,35 @@ class TestInteger(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
                 _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.assertFalse(obj.expled)
             obj_encoded = obj.encode()
+            self.assertEqual(encode2pass(obj), obj_encoded)
+            self.assertSequenceEqual(encode_cer(obj), obj_encoded)
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
             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()
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_expled_cer = encode_cer(obj_expled)
+            self.assertNotEqual(obj_expled_cer, obj_encoded)
+            self.assertSequenceEqual(
+                obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(),
+                obj_expled_encoded,
+            )
+            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)
             repr(obj_decoded)
-            pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            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(int(obj_decoded), int(obj_expled))
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(int(obj_decoded), int(obj_expled))
@@ -944,6 +1186,26 @@ class TestInteger(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
                 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,
+            )
+
+            evgens = list(obj_expled.decode_evgen(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+                decode_path=decode_path,
+                ctx=ctx_copied,
+            ))
+            self.assertEqual(len(evgens), 1)
+            _decode_path, obj, tail = evgens[0]
+            self.assertSequenceEqual(tail, tail_junk)
+            self.assertEqual(_decode_path, decode_path)
+            self.assertEqual(obj, obj_decoded)
+            self.assertEqual(obj.expl_offset, offset)
+            repr(obj)
+            list(obj.pps())
 
     def test_go_vectors_valid(self):
         for data, expect in ((
 
     def test_go_vectors_valid(self):
         for data, expect in ((
@@ -998,7 +1260,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()):
 
     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)))
         generation_choice = 0
         if value_required:
             generation_choice = draw(sampled_from((1, 2, 3)))
@@ -1007,9 +1269,11 @@ def bit_string_values_strategy(draw, schema=None, value_required=False, do_expl=
                 sampled_from(("0", "1")),
                 max_size=len(schema),
             )))
                 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))
             return draw(binary(max_size=len(schema) // 8))
-        elif generation_choice == 3 or draw(booleans()):
+        if generation_choice == 3 or draw(booleans()):
+            if len(schema) == 0:
+                return ()
             return tuple(draw(lists(sampled_from([name for name, _ in schema]))))
         return None
     value = _value(value_required)
             return tuple(draw(lists(sampled_from([name for name, _ in schema]))))
         return None
     value = _value(value_required)
@@ -1115,20 +1379,24 @@ class TestBitString(CommonMixin, TestCase):
         obj = BitString()
         self.assertFalse(obj.ready)
         repr(obj)
         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)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         obj = BitString(value)
         self.assertTrue(obj.ready)
         repr(obj)
         obj = BitString(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
     @given(
         tuples(integers(min_value=0), binary()),
         tuples(integers(min_value=0), binary()),
 
     @given(
         tuples(integers(min_value=0), binary()),
         tuples(integers(min_value=0), binary()),
-        binary(),
-        binary(),
+        binary(min_size=1),
+        binary(min_size=1),
     )
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (BitString, BitStringInherited):
     )
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (BitString, BitStringInherited):
@@ -1201,6 +1469,7 @@ class TestBitString(CommonMixin, TestCase):
 
             class BS(klass):
                 schema = _schema
 
             class BS(klass):
                 schema = _schema
+            register_class(BS)
             obj = BS(
                 value=value,
                 impl=impl,
             obj = BS(
                 value=value,
                 impl=impl,
@@ -1209,10 +1478,11 @@ class TestBitString(CommonMixin, TestCase):
                 optional=optional or False,
                 _decoded=_decoded,
             )
                 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(),
 
     @given(
         binary(),
@@ -1235,10 +1505,9 @@ class TestBitString(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             BitString().decode(
                 tag_encode(tag)[:-1],
@@ -1252,10 +1521,9 @@ class TestBitString(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             BitString().decode(
                 BitString.tag_default + len_encode(l)[:-1],
@@ -1278,8 +1546,10 @@ class TestBitString(CommonMixin, TestCase):
             optional,
             _decoded,
         ) = d.draw(bit_string_values_strategy(value_required=True))
             optional,
             _decoded,
         ) = d.draw(bit_string_values_strategy(value_required=True))
+        tail_junk = d.draw(binary(max_size=5))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
+        decode_path = d.draw(decode_path_strat)
         for klass in (BitString, BitStringInherited):
             class BS(klass):
                 schema = _schema
         for klass in (BitString, BitStringInherited):
             class BS(klass):
                 schema = _schema
@@ -1290,18 +1560,35 @@ class TestBitString(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
                 _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.assertFalse(obj.expled)
             obj_encoded = obj.encode()
+            self.assertEqual(encode2pass(obj), obj_encoded)
+            self.assertSequenceEqual(encode_cer(obj), obj_encoded)
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
             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()
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_expled_cer = encode_cer(obj_expled)
+            self.assertNotEqual(obj_expled_cer, obj_encoded)
+            self.assertSequenceEqual(
+                obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(),
+                obj_expled_encoded,
+            )
+            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)
             repr(obj_decoded)
-            pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            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(bytes(obj_decoded), bytes(obj_expled))
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -1324,6 +1611,25 @@ class TestBitString(CommonMixin, TestCase):
                 self.assertSetEqual(set(value), set(obj_decoded.named))
                 for name in value:
                     obj_decoded[name]
                 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,
+            )
+
+            evgens = list(obj_expled.decode_evgen(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+                decode_path=decode_path,
+                ctx=ctx_copied,
+            ))
+            self.assertEqual(len(evgens), 1)
+            _decode_path, obj, tail = evgens[0]
+            self.assertSequenceEqual(tail, tail_junk)
+            self.assertEqual(_decode_path, decode_path)
+            self.assertEqual(obj.expl_offset, offset)
+            repr(obj)
+            list(obj.pps())
 
     @given(integers(min_value=1, max_value=255))
     def test_bad_zero_value(self, pad_size):
 
     @given(integers(min_value=1, max_value=255))
     def test_bad_zero_value(self, pad_size):
@@ -1331,7 +1637,7 @@ class TestBitString(CommonMixin, TestCase):
             BitString().decode(b"".join((
                 BitString.tag_default,
                 len_encode(1),
             BitString().decode(b"".join((
                 BitString.tag_default,
                 len_encode(1),
-                int2byte(pad_size),
+                bytes([pad_size]),
             )))
 
     def test_go_vectors_invalid(self):
             )))
 
     def test_go_vectors_invalid(self):
@@ -1371,6 +1677,258 @@ class TestBitString(CommonMixin, TestCase):
         self.assertTrue(obj[9])
         self.assertFalse(obj[17])
 
         self.assertTrue(obj[9])
         self.assertFalse(obj[17])
 
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(
+        integers(min_value=1, max_value=30),
+        lists(
+            one_of(
+                binary(min_size=1, max_size=5),
+                lists(
+                    binary(min_size=1, max_size=5),
+                    min_size=1,
+                    max_size=3,
+                ),
+            ),
+            min_size=0,
+            max_size=3,
+        ),
+        lists(booleans(), min_size=1),
+        binary(),
+        decode_path_strat,
+    )
+    def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk, decode_path):
+        def chunk_constructed(contents):
+            return (
+                tag_encode(form=TagFormConstructed, num=3) +
+                LENINDEF +
+                b"".join(BitString(content).encode() for content in contents) +
+                EOC
+            )
+        chunks = []
+        chunks_len_expected = []
+        payload_expected = b""
+        bit_len_expected = 0
+        for chunk_input in chunk_inputs:
+            if isinstance(chunk_input, bytes):
+                chunks.append(BitString(chunk_input).encode())
+                payload_expected += chunk_input
+                bit_len_expected += len(chunk_input) * 8
+                chunks_len_expected.append(len(chunk_input) + 1)
+            else:
+                chunks.append(chunk_constructed(chunk_input))
+                payload = b"".join(chunk_input)
+                payload_expected += payload
+                bit_len_expected += len(payload) * 8
+                for c in chunk_input:
+                    chunks_len_expected.append(len(c) + 1)
+                chunks_len_expected.append(len(chunks[-1]) - 1 - 1)
+        chunk_last = BitString("'%s'B" % "".join(
+            "1" if bit else "0" for bit in chunk_last_bits
+        ))
+        chunks_len_expected.append(BitString().decod(chunk_last.encode()).vlen)
+        payload_expected += bytes(chunk_last)
+        bit_len_expected += chunk_last.bit_len
+        encoded_indefinite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            LENINDEF +
+            b"".join(chunks) +
+            chunk_last.encode() +
+            EOC
+        )
+        encoded_definite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            len_encode(len(b"".join(chunks) + chunk_last.encode())) +
+            b"".join(chunks) +
+            chunk_last.encode()
+        )
+        with self.assertRaisesRegex(DecodeError, "unallowed BER"):
+            BitString(impl=tag_encode(impl)).decode(encoded_indefinite)
+        for lenindef_expected, encoded in (
+                (True, encoded_indefinite),
+                (False, encoded_definite),
+        ):
+            obj, tail = BitString(impl=tag_encode(impl)).decode(
+                encoded + junk,
+                ctx={"bered": True},
+            )
+            self.assertSequenceEqual(tail, junk)
+            self.assertEqual(obj.bit_len, bit_len_expected)
+            self.assertSequenceEqual(bytes(obj), payload_expected)
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertTrue(obj.bered)
+            obj = 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)
+
+            evgens = list(BitString(impl=tag_encode(impl)).decode_evgen(
+                encoded,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            ))
+            self.assertEqual(len(evgens), len(chunks_len_expected) + 1)
+            for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens):
+                self.assertGreater(len(dp), len(decode_path))
+                self.assertEqual(obj.vlen, chunk_len_expected)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_too_short(self, offset, decode_path):
+        with self.assertRaisesRegex(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 self.assertRaisesRegex(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 self.assertRaisesRegex(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 self.assertRaisesRegex(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 self.assertRaisesRegex(DecodeError, "multiple of 8 bits") as err:
+            BitString().decode(
+                (
+                    tag_encode(3, form=TagFormConstructed) +
+                    LENINDEF +
+                    b"".join(chunks) +
+                    EOC
+                ),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(
+            err.exception.decode_path,
+            decode_path + (str(chunks.index(bs_short)),),
+        )
+        self.assertEqual(
+            err.exception.offset,
+            offset + 1 + 1 + chunks.index(bs_short) * len(bs_full),
+        )
+
+    def test_x690_vector(self):
+        vector = BitString("'0A3B5F291CD'H")
+        obj, tail = BitString().decode(hexdec("0307040A3B5F291CD0"))
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(obj, vector)
+        obj, tail = BitString().decode(
+            hexdec("23800303000A3B0305045F291CD00000"),
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(obj, vector)
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
+        obj = copy(obj)
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.lenindef)
+        self.assertTrue(obj.bered)
+
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(integers(min_value=1000, max_value=3000))
+    def test_cer(self, data_len):
+        data = urandom(data_len)
+        encoded = encode_cer(BitString(data))
+        ctx = {"bered": True}
+        self.assertSequenceEqual(bytes(BitString().decod(encoded, ctx=ctx)), data)
+        evgens = list(BitString().decode_evgen(encoded, ctx=ctx))
+        evgens_expected = data_len // 999
+        if evgens_expected * 999 != data_len:
+            evgens_expected += 1
+        evgens_expected += 1
+        self.assertEqual(len(evgens), evgens_expected)
+        for (_, obj, _) in evgens[:-2]:
+            self.assertEqual(obj.vlen, 1000)
+        _, obj, _ = evgens[-2]
+        self.assertEqual(obj.vlen, 1 + data_len - len(evgens[:-2]) * 999)
+
 
 @composite
 def octet_string_values_strategy(draw, do_expl=False):
 
 @composite
 def octet_string_values_strategy(draw, do_expl=False):
@@ -1414,7 +1972,7 @@ class TestOctetString(CommonMixin, TestCase):
 
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
 
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
-            OctetString(text_type(123))
+            OctetString(str(123))
         repr(err.exception)
 
     @given(booleans())
         repr(err.exception)
 
     @given(booleans())
@@ -1427,16 +1985,20 @@ class TestOctetString(CommonMixin, TestCase):
         obj = OctetString()
         self.assertFalse(obj.ready)
         repr(obj)
         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)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         obj = OctetString(value)
         self.assertTrue(obj.ready)
         repr(obj)
         obj = OctetString(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
 
 
-    @given(binary(), binary(), binary(), binary())
+    @given(binary(), binary(), binary(min_size=1), binary(min_size=1))
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (OctetString, OctetStringInherited):
             obj1 = klass(value1)
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (OctetString, OctetStringInherited):
             obj1 = klass(value1)
@@ -1471,10 +2033,28 @@ class TestOctetString(CommonMixin, TestCase):
         with self.assertRaises(BoundsError) as err:
             OctetString(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
         with self.assertRaises(BoundsError) as err:
             OctetString(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            OctetString(bounds=(bound_min, bound_max)).decode(
+                OctetString(value).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            OctetString(bounds=(bound_min, bound_max)).decode(
+                encode2pass(OctetString(value))
+            )
         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)
         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 self.assertRaisesRegex(DecodeError, "bounds") as err:
+            OctetString(bounds=(bound_min, bound_max)).decode(
+                OctetString(value).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            OctetString(bounds=(bound_min, bound_max)).decode(
+                encode2pass(OctetString(value))
+            )
 
     @given(data_strategy())
     def test_call(self, d):
 
     @given(data_strategy())
     def test_call(self, d):
@@ -1551,11 +2131,12 @@ class TestOctetString(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (OctetString, OctetStringInherited):
             obj = klass(*values)
     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(),
 
     @given(
         binary(),
@@ -1578,10 +2159,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             OctetString().decode(
                 tag_encode(tag)[:-1],
@@ -1595,10 +2175,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             OctetString().decode(
                 OctetString.tag_default + len_encode(l)[:-1],
@@ -1612,10 +2191,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
     @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):
     )
     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):
         value, bound_min = list(sorted(ints))
 
         class String(OctetString):
@@ -1636,8 +2214,20 @@ class TestOctetString(CommonMixin, TestCase):
         binary(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
         binary(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
+        decode_path_strat,
+        booleans(),
     )
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(
+            self,
+            values,
+            value,
+            tag_expl,
+            offset,
+            tail_junk,
+            decode_path,
+            keep_memoryview,
+    ):
         for klass in (OctetString, OctetStringInherited):
             _, _, _, _, default, optional, _decoded = values
             obj = klass(
         for klass in (OctetString, OctetStringInherited):
             _, _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -1647,22 +2237,44 @@ class TestOctetString(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
                 _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.assertFalse(obj.expled)
             obj_encoded = obj.encode()
+            self.assertEqual(encode2pass(obj), obj_encoded)
+            self.assertSequenceEqual(encode_cer(obj), obj_encoded)
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
             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()
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_expled_cer = encode_cer(obj_expled)
+            self.assertNotEqual(obj_expled_cer, obj_encoded)
+            self.assertSequenceEqual(
+                obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(),
+                obj_expled_encoded,
+            )
+            ctx_dummy["keep_memoryview"] = keep_memoryview
+            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)
             repr(obj_decoded)
-            pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            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(bytes(obj_decoded), bytes(obj_expled))
             self.assertEqual(bytes(obj_decoded), bytes(obj))
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
             self.assertEqual(bytes(obj_decoded), bytes(obj))
+            self.assertIsInstance(
+                obj_decoded._value,
+                memoryview if keep_memoryview else bytes,
+            )
             self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
             self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
             self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
             self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
             self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
             self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
@@ -1677,6 +2289,184 @@ class TestOctetString(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
                 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,
+            )
+
+            evgens = list(obj_expled.decode_evgen(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+                decode_path=decode_path,
+                ctx=ctx_copied,
+            ))
+            self.assertEqual(len(evgens), 1)
+            _decode_path, obj, tail = evgens[0]
+            self.assertSequenceEqual(tail, tail_junk)
+            self.assertEqual(_decode_path, decode_path)
+            self.assertEqual(obj.expl_offset, offset)
+            repr(obj)
+            list(obj.pps())
+
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(
+        integers(min_value=1, max_value=30),
+        lists(
+            one_of(
+                binary(min_size=1, max_size=5),
+                lists(
+                    binary(min_size=1, max_size=5),
+                    min_size=1,
+                    max_size=3,
+                ),
+            ),
+            min_size=1,
+            max_size=3,
+        ),
+        binary(),
+        decode_path_strat,
+    )
+    def test_constructed(self, impl, chunk_inputs, junk, decode_path):
+        def chunk_constructed(contents):
+            return (
+                tag_encode(form=TagFormConstructed, num=4) +
+                LENINDEF +
+                b"".join(OctetString(content).encode() for content in contents) +
+                EOC
+            )
+        chunks = []
+        chunks_len_expected = []
+        payload_expected = b""
+        for chunk_input in chunk_inputs:
+            if isinstance(chunk_input, bytes):
+                chunks.append(OctetString(chunk_input).encode())
+                payload_expected += chunk_input
+                chunks_len_expected.append(len(chunk_input))
+            else:
+                chunks.append(chunk_constructed(chunk_input))
+                payload = b"".join(chunk_input)
+                payload_expected += payload
+                for c in chunk_input:
+                    chunks_len_expected.append(len(c))
+                chunks_len_expected.append(len(chunks[-1]) - 1 - 1)
+        encoded_indefinite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            LENINDEF +
+            b"".join(chunks) +
+            EOC
+        )
+        encoded_definite = (
+            tag_encode(form=TagFormConstructed, num=impl) +
+            len_encode(len(b"".join(chunks))) +
+            b"".join(chunks)
+        )
+        with self.assertRaisesRegex(DecodeError, "unallowed BER"):
+            OctetString(impl=tag_encode(impl)).decode(encoded_indefinite)
+        for lenindef_expected, encoded in (
+                (True, encoded_indefinite),
+                (False, encoded_definite),
+        ):
+            obj, tail = OctetString(impl=tag_encode(impl)).decode(
+                encoded + junk,
+                ctx={"bered": True},
+            )
+            self.assertSequenceEqual(tail, junk)
+            self.assertSequenceEqual(bytes(obj), payload_expected)
+            self.assertTrue(obj.ber_encoded)
+            self.assertEqual(obj.lenindef, lenindef_expected)
+            self.assertTrue(obj.bered)
+            obj = 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)
+
+            evgens = list(OctetString(impl=tag_encode(impl)).decode_evgen(
+                encoded,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            ))
+            self.assertEqual(len(evgens), len(chunks_len_expected) + 1)
+            for chunk_len_expected, (dp, obj, _) in zip(chunks_len_expected, evgens):
+                self.assertGreater(len(dp), len(decode_path))
+                self.assertEqual(obj.vlen, chunk_len_expected)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_too_short(self, offset, decode_path):
+        with self.assertRaisesRegex(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 self.assertRaisesRegex(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))
+
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(integers(min_value=1001, max_value=3000))
+    def test_cer(self, data_len):
+        data = urandom(data_len)
+        encoded = encode_cer(OctetString(data))
+        ctx = {"bered": True}
+        self.assertSequenceEqual(bytes(OctetString().decod(encoded, ctx=ctx)), data)
+        evgens = list(OctetString().decode_evgen(encoded, ctx=ctx))
+        evgens_expected = data_len // 1000
+        if evgens_expected * 1000 != data_len:
+            evgens_expected += 1
+        evgens_expected += 1
+        self.assertEqual(len(evgens), evgens_expected)
+        for (_, obj, _) in evgens[:-2]:
+            self.assertEqual(obj.vlen, 1000)
+        _, obj, _ = evgens[-2]
+        self.assertEqual(obj.vlen, data_len - len(evgens[:-2]) * 1000)
 
 
 @composite
 
 
 @composite
@@ -1707,9 +2497,10 @@ class TestNull(CommonMixin, TestCase):
         obj = Null()
         self.assertTrue(obj.ready)
         repr(obj)
         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)
     def test_comparison(self, tag1, tag2):
         for klass in (Null, NullInherited):
             obj1 = klass(impl=tag1)
@@ -1756,8 +2547,9 @@ class TestNull(CommonMixin, TestCase):
                 optional=optional or False,
                 _decoded=_decoded,
             )
                 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):
 
     @given(integers(min_value=1).map(tag_encode))
     def test_stripped(self, tag_impl):
@@ -1774,10 +2566,9 @@ class TestNull(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Null().decode(
                 tag_encode(tag)[:-1],
@@ -1791,10 +2582,9 @@ class TestNull(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Null().decode(
                 Null.tag_default + len_encode(l)[:-1],
@@ -1815,24 +2605,43 @@ class TestNull(CommonMixin, TestCase):
         null_values_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
         null_values_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
+        decode_path_strat,
     )
     )
-    def test_symmetric(self, values, tag_expl, offset):
+    def test_symmetric(self, values, tag_expl, offset, tail_junk, decode_path):
         for klass in (Null, NullInherited):
             _, _, optional, _decoded = values
             obj = klass(optional=optional, _decoded=_decoded)
             repr(obj)
         for klass in (Null, NullInherited):
             _, _, 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()
             self.assertFalse(obj.expled)
             obj_encoded = obj.encode()
+            self.assertEqual(encode2pass(obj), obj_encoded)
+            self.assertSequenceEqual(encode_cer(obj), obj_encoded)
             obj_expled = obj(expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
             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()
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_expled_cer = encode_cer(obj_expled)
+            self.assertNotEqual(obj_expled_cer, obj_encoded)
+            self.assertSequenceEqual(
+                obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(),
+                obj_expled_encoded,
+            )
+            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)
             repr(obj_decoded)
-            pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            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.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
@@ -1849,6 +2658,26 @@ class TestNull(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
                 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,
+            )
+
+            evgens = list(obj_expled.decode_evgen(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+                decode_path=decode_path,
+                ctx=ctx_copied,
+            ))
+            self.assertEqual(len(evgens), 1)
+            _decode_path, obj, tail = evgens[0]
+            self.assertSequenceEqual(tail, tail_junk)
+            self.assertEqual(_decode_path, decode_path)
+            self.assertEqual(obj, obj_decoded)
+            self.assertEqual(obj.expl_offset, offset)
+            repr(obj)
+            list(obj.pps())
 
     @given(integers(min_value=1))
     def test_invalid_len(self, l):
 
     @given(integers(min_value=1))
     def test_invalid_len(self, l):
@@ -1866,8 +2695,8 @@ def oid_strategy(draw):
     if first_arc in (0, 1):
         second_arc = draw(integers(min_value=0, max_value=39))
     else:
     if first_arc in (0, 1):
         second_arc = draw(integers(min_value=0, max_value=39))
     else:
-        second_arc = draw(integers(min_value=0))
-    other_arcs = draw(lists(integers(min_value=0)))
+        second_arc = draw(integers(min_value=0, max_value=1 << 63))
+    other_arcs = draw(lists(integers(min_value=0, max_value=1 << 63)))
     return tuple([first_arc, second_arc] + other_arcs)
 
 
     return tuple([first_arc, second_arc] + other_arcs)
 
 
@@ -1912,17 +2741,22 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         obj = ObjectIdentifier()
         self.assertFalse(obj.ready)
         repr(obj)
         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)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         obj = ObjectIdentifier(value)
         self.assertTrue(obj.ready)
         obj = ObjectIdentifier(value)
         self.assertTrue(obj.ready)
+        self.assertFalse(obj.ber_encoded)
         repr(obj)
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         hash(obj)
 
         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)
     def test_comparison(self, value1, value2, tag1, tag2):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
             obj1 = klass(value1)
@@ -2016,9 +2850,10 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 optional=optional,
                 _decoded=_decoded,
             )
                 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(
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
@@ -2042,10 +2877,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             ObjectIdentifier().decode(
                 tag_encode(tag)[:-1],
@@ -2059,10 +2893,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             ObjectIdentifier().decode(
                 ObjectIdentifier.tag_default + len_encode(l)[:-1],
@@ -2091,24 +2924,30 @@ class TestObjectIdentifier(CommonMixin, TestCase):
             len_encode(len(data)),
             data,
         ))
             len_encode(len(data)),
             data,
         ))
-        with assertRaisesRegex(self, DecodeError, "unfinished OID"):
+        with self.assertRaisesRegex(DecodeError, "unfinished OID"):
             obj.decode(data)
 
             obj.decode(data)
 
-    @given(integers(min_value=0))
+    @given(integers(min_value=0, max_value=1 << 63))
     def test_invalid_short(self, value):
         with self.assertRaises(InvalidOID):
             ObjectIdentifier((value,))
         with self.assertRaises(InvalidOID):
             ObjectIdentifier("%d" % value)
 
     def test_invalid_short(self, value):
         with self.assertRaises(InvalidOID):
             ObjectIdentifier((value,))
         with self.assertRaises(InvalidOID):
             ObjectIdentifier("%d" % value)
 
-    @given(integers(min_value=3), integers(min_value=0))
+    @given(
+        integers(min_value=3, max_value=1 << 63),
+        integers(min_value=0, max_value=1 << 63),
+    )
     def test_invalid_first_arc(self, first_arc, second_arc):
         with self.assertRaises(InvalidOID):
             ObjectIdentifier((first_arc, second_arc))
         with self.assertRaises(InvalidOID):
             ObjectIdentifier("%d.%d" % (first_arc, second_arc))
 
     def test_invalid_first_arc(self, first_arc, second_arc):
         with self.assertRaises(InvalidOID):
             ObjectIdentifier((first_arc, second_arc))
         with self.assertRaises(InvalidOID):
             ObjectIdentifier("%d.%d" % (first_arc, second_arc))
 
-    @given(integers(min_value=0, max_value=1), integers(min_value=40))
+    @given(
+        integers(min_value=0, max_value=1),
+        integers(min_value=40, max_value=1 << 63),
+    )
     def test_invalid_second_arc(self, first_arc, second_arc):
         with self.assertRaises(InvalidOID):
             ObjectIdentifier((first_arc, second_arc))
     def test_invalid_second_arc(self, first_arc, second_arc):
         with self.assertRaises(InvalidOID):
             ObjectIdentifier((first_arc, second_arc))
@@ -2127,7 +2966,7 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         self.assertEqual(obj, ObjectIdentifier(".".join(str(arc) for arc in oid)))
         str(obj)
         repr(obj)
         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(
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
@@ -2135,8 +2974,10 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         oid_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
         oid_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
+        decode_path_strat,
     )
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -2146,18 +2987,35 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 _decoded=_decoded,
             )
             repr(obj)
                 _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.assertFalse(obj.expled)
             obj_encoded = obj.encode()
+            self.assertEqual(encode2pass(obj), obj_encoded)
+            self.assertSequenceEqual(encode_cer(obj), obj_encoded)
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             repr(obj_expled)
             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()
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_expled_cer = encode_cer(obj_expled)
+            self.assertNotEqual(obj_expled_cer, obj_encoded)
+            self.assertSequenceEqual(
+                obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(),
+                obj_expled_encoded,
+            )
+            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)
             repr(obj_decoded)
-            pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            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(tuple(obj_decoded), tuple(obj_expled))
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(tuple(obj_decoded), tuple(obj_expled))
@@ -2176,6 +3034,26 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
                 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,
+            )
+
+            evgens = list(obj_expled.decode_evgen(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+                decode_path=decode_path,
+                ctx=ctx_copied,
+            ))
+            self.assertEqual(len(evgens), 1)
+            _decode_path, obj, tail = evgens[0]
+            self.assertSequenceEqual(tail, tail_junk)
+            self.assertEqual(_decode_path, decode_path)
+            self.assertEqual(obj, obj_decoded)
+            self.assertEqual(obj.expl_offset, offset)
+            repr(obj)
+            list(obj.pps())
 
     @given(
         oid_strategy().map(ObjectIdentifier),
 
     @given(
         oid_strategy().map(ObjectIdentifier),
@@ -2213,6 +3091,87 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 data,
             )))
 
                 data,
             )))
 
+    def test_go_non_minimal_encoding(self):
+        with self.assertRaises(DecodeError):
+            ObjectIdentifier().decode(hexdec("060a2a80864886f70d01010b"))
+
+    def test_x690_vector(self):
+        self.assertEqual(
+            ObjectIdentifier().decode(hexdec("0603883703"))[0],
+            ObjectIdentifier((2, 999, 3)),
+        )
+
+    def test_nonnormalized_first_arc(self):
+        tampered = (
+            ObjectIdentifier.tag_default +
+            len_encode(2) +
+            b'\x80' +
+            ObjectIdentifier((1, 0)).encode()[-1:]
+        )
+        obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True})
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        obj = copy(obj)
+        self.assertTrue(obj.ber_encoded)
+        self.assertTrue(obj.bered)
+        with self.assertRaisesRegex(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 self.assertRaisesRegex(DecodeError, "non normalized arc encoding"):
+            ObjectIdentifier().decode(tampered)
+
 
 @composite
 def enumerated_values_strategy(draw, schema=None, do_expl=False):
 
 @composite
 def enumerated_values_strategy(draw, schema=None, do_expl=False):
@@ -2248,7 +3207,7 @@ class TestEnumerated(CommonMixin, TestCase):
     base_klass = EWhatever
 
     def test_schema_required(self):
     base_klass = EWhatever
 
     def test_schema_required(self):
-        with assertRaisesRegex(self, ValueError, "schema must be specified"):
+        with self.assertRaisesRegex(ValueError, "schema must be specified"):
             Enumerated()
 
     def test_invalid_value_type(self):
             Enumerated()
 
     def test_invalid_value_type(self):
@@ -2290,16 +3249,18 @@ class TestEnumerated(CommonMixin, TestCase):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
         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)
         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 = (
     def test_comparison(self, value1, value2, tag1, tag2):
         class E(Enumerated):
             schema = (
@@ -2391,6 +3352,7 @@ class TestEnumerated(CommonMixin, TestCase):
 
         class E(Enumerated):
             schema = schema_input
 
         class E(Enumerated):
             schema = schema_input
+        register_class(E)
         obj = E(
             value=value,
             impl=impl,
         obj = E(
             value=value,
             impl=impl,
@@ -2399,9 +3361,10 @@ class TestEnumerated(CommonMixin, TestCase):
             optional=optional,
             _decoded=_decoded,
         )
             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())
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
@@ -2412,6 +3375,8 @@ class TestEnumerated(CommonMixin, TestCase):
         tag_expl = d.draw(integers(min_value=1).map(tag_ctxc))
         offset = d.draw(integers(min_value=0))
         value = d.draw(sampled_from(sorted([v for _, v in schema_input])))
         tag_expl = d.draw(integers(min_value=1).map(tag_ctxc))
         offset = d.draw(integers(min_value=0))
         value = d.draw(sampled_from(sorted([v for _, v in schema_input])))
+        tail_junk = d.draw(binary(max_size=5))
+        decode_path = d.draw(decode_path_strat)
 
         class E(Enumerated):
             schema = schema_input
 
         class E(Enumerated):
             schema = schema_input
@@ -2422,18 +3387,28 @@ class TestEnumerated(CommonMixin, TestCase):
             _decoded=_decoded,
         )
         repr(obj)
             _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.assertFalse(obj.expled)
         obj_encoded = obj.encode()
+        self.assertEqual(encode2pass(obj), obj_encoded)
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
         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()
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        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)
         repr(obj_decoded)
-        pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        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(int(obj_decoded), int(obj_expled))
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
         self.assertEqual(int(obj_decoded), int(obj_expled))
@@ -2452,6 +3427,26 @@ class TestEnumerated(CommonMixin, TestCase):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
             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,
+        )
+
+        evgens = list(obj_expled.decode_evgen(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+            decode_path=decode_path,
+            ctx=ctx_copied,
+        ))
+        self.assertEqual(len(evgens), 1)
+        _decode_path, obj, tail = evgens[0]
+        self.assertSequenceEqual(tail, tail_junk)
+        self.assertEqual(_decode_path, decode_path)
+        self.assertEqual(obj, obj_decoded)
+        self.assertEqual(obj.expl_offset, offset)
+        repr(obj)
+        list(obj.pps())
 
 
 @composite
 
 
 @composite
@@ -2494,9 +3489,7 @@ class StringMixin(object):
         repr(err.exception)
 
     def text_alphabet(self):
         repr(err.exception)
 
     def text_alphabet(self):
-        if self.base_klass.encoding in ("ascii", "iso-8859-1"):
-            return printable + whitespace
-        return None
+        return "".join(chr(c) for c in range(256))
 
     @given(booleans())
     def test_optional(self, optional):
 
     @given(booleans())
     def test_optional(self, optional):
@@ -2508,30 +3501,34 @@ class StringMixin(object):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
-        pprint(obj)
-        text_type(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
+        str(obj)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         value = d.draw(text(alphabet=self.text_alphabet()))
         obj = self.base_klass(value)
         self.assertTrue(obj.ready)
         repr(obj)
         value = d.draw(text(alphabet=self.text_alphabet()))
         obj = self.base_klass(value)
         self.assertTrue(obj.ready)
         repr(obj)
-        pprint(obj)
-        text_type(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
+        str(obj)
 
     @given(data_strategy())
     def test_comparison(self, d):
         value1 = d.draw(text(alphabet=self.text_alphabet()))
         value2 = d.draw(text(alphabet=self.text_alphabet()))
 
     @given(data_strategy())
     def test_comparison(self, d):
         value1 = d.draw(text(alphabet=self.text_alphabet()))
         value2 = d.draw(text(alphabet=self.text_alphabet()))
-        tag1 = d.draw(binary())
-        tag2 = d.draw(binary())
+        tag1 = d.draw(binary(min_size=1))
+        tag2 = d.draw(binary(min_size=1))
         obj1 = self.base_klass(value1)
         obj2 = self.base_klass(value2)
         self.assertEqual(obj1 == obj2, value1 == value2)
         self.assertEqual(obj1 != obj2, value1 != value2)
         self.assertEqual(obj1 == bytes(obj2), value1 == value2)
         obj1 = self.base_klass(value1)
         obj2 = self.base_klass(value2)
         self.assertEqual(obj1 == obj2, value1 == value2)
         self.assertEqual(obj1 != obj2, value1 != value2)
         self.assertEqual(obj1 == bytes(obj2), value1 == value2)
-        self.assertEqual(obj1 == text_type(obj2), value1 == value2)
+        self.assertEqual(obj1 == str(obj2), value1 == value2)
         obj1 = self.base_klass(value1, impl=tag1)
         obj2 = self.base_klass(value1, impl=tag2)
         self.assertEqual(obj1 == obj2, tag1 == tag2)
         obj1 = self.base_klass(value1, impl=tag1)
         obj2 = self.base_klass(value1, impl=tag2)
         self.assertEqual(obj1 == obj2, tag1 == tag2)
@@ -2556,10 +3553,28 @@ class StringMixin(object):
         with self.assertRaises(BoundsError) as err:
             self.base_klass(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
         with self.assertRaises(BoundsError) as err:
             self.base_klass(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            self.base_klass(bounds=(bound_min, bound_max)).decode(
+                self.base_klass(value).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            self.base_klass(bounds=(bound_min, bound_max)).decode(
+                encode2pass(self.base_klass(value))
+            )
         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)
         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 self.assertRaisesRegex(DecodeError, "bounds") as err:
+            self.base_klass(bounds=(bound_min, bound_max)).decode(
+                self.base_klass(value).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            self.base_klass(bounds=(bound_min, bound_max)).decode(
+                encode2pass(self.base_klass(value))
+            )
 
     @given(data_strategy())
     def test_call(self, d):
 
     @given(data_strategy())
     def test_call(self, d):
@@ -2638,11 +3653,12 @@ class StringMixin(object):
     def test_copy(self, d):
         values = d.draw(string_values_strategy(self.text_alphabet()))
         obj = self.base_klass(*values)
     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):
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -2663,10 +3679,9 @@ class StringMixin(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -2680,10 +3695,9 @@ class StringMixin(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -2697,10 +3711,9 @@ class StringMixin(object):
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
     @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):
     )
     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):
         value, bound_min = list(sorted(ints))
 
         class String(self.base_klass):
@@ -2723,6 +3736,8 @@ class StringMixin(object):
         value = d.draw(text(alphabet=self.text_alphabet()))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
         value = d.draw(text(alphabet=self.text_alphabet()))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
+        tail_junk = d.draw(binary(max_size=5))
+        decode_path = d.draw(decode_path_strat)
         _, _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
         _, _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
@@ -2731,24 +3746,34 @@ class StringMixin(object):
             _decoded=_decoded,
         )
         repr(obj)
             _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.assertFalse(obj.expled)
         obj_encoded = obj.encode()
+        self.assertEqual(encode2pass(obj), obj_encoded)
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
         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()
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        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)
         repr(obj_decoded)
-        pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        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(bytes(obj_decoded), bytes(obj_expled))
         self.assertEqual(bytes(obj_decoded), bytes(obj))
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
         self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
         self.assertEqual(bytes(obj_decoded), bytes(obj))
-        self.assertEqual(text_type(obj_decoded), text_type(obj_expled))
-        self.assertEqual(text_type(obj_decoded), text_type(obj))
+        self.assertEqual(str(obj_decoded), str(obj_expled))
+        self.assertEqual(str(obj_decoded), str(obj))
         self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
         self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
         self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
         self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
         self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
         self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
@@ -2763,41 +3788,302 @@ class StringMixin(object):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
             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,
+        )
+
+        evgens = list(obj_expled.decode_evgen(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+            decode_path=decode_path,
+            ctx=ctx_copied,
+        ))
+        self.assertEqual(len(evgens), 1)
+        _decode_path, obj, tail = evgens[0]
+        self.assertSequenceEqual(tail, tail_junk)
+        self.assertEqual(_decode_path, decode_path)
+        if not getattr(self, "evgen_mode_skip_value", True):
+            self.assertEqual(obj, obj_decoded)
+        self.assertEqual(obj.expl_offset, offset)
+        repr(obj)
+        list(obj.pps())
+
+
+cyrillic_letters = text(
+    alphabet="".join(chr(i) for i in list(range(0x0410, 0x044f + 1))),
+    min_size=1,
+    max_size=5,
+)
 
 
 class TestUTF8String(StringMixin, CommonMixin, TestCase):
     base_klass = UTF8String
 
 
 
 class TestUTF8String(StringMixin, CommonMixin, TestCase):
     base_klass = UTF8String
 
+    @given(cyrillic_letters)
+    def test_byte_per_primitive(self, chars):
+        char = chars[0]
+        char_raw = char.encode("utf-8")
+        encoded = b"".join((
+            self.base_klass().tag_constructed,
+            LENINDEF,
+            OctetString(char_raw[:1]).encode(),
+            OctetString(char_raw[1:2]).encode(),
+            EOC,
+        ))
+        self.assertEqual(
+            self.base_klass().decod(encoded, ctx={"bered": True}),
+            char,
+        )
+
+
+class UnicodeDecodeErrorMixin(object):
+    @given(cyrillic_letters)
+    def test_unicode_decode_error(self, cyrillic_text):
+        with self.assertRaises(DecodeError):
+            self.base_klass(cyrillic_text)
+
 
 class TestNumericString(StringMixin, CommonMixin, TestCase):
     base_klass = NumericString
 
 
 class TestNumericString(StringMixin, CommonMixin, TestCase):
     base_klass = NumericString
 
+    def text_alphabet(self):
+        return digits + " "
+
+    @given(text(alphabet=ascii_letters, min_size=1, max_size=5))
+    def test_non_numeric(self, non_numeric_text):
+        with self.assertRaisesRegex(DecodeError, "alphabet value"):
+            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),
+        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_byte_per_primitive(self):
+        encoded = b"".join((
+            self.base_klass().tag_constructed,
+            LENINDEF,
+            OctetString(b"1").encode(),
+            OctetString(b"2").encode(),
+            EOC,
+        ))
+        self.assertEqual(
+            self.base_klass().decod(encoded, ctx={"bered": True}),
+            "12",
+        )
 
 
-class TestPrintableString(StringMixin, CommonMixin, TestCase):
+
+class TestPrintableString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = PrintableString
 
     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 self.assertRaisesRegex(DecodeError, "alphabet value"):
+            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 TestTeletexString(StringMixin, CommonMixin, TestCase):
+        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 self.assertRaisesRegex(DecodeError, "alphabet value"):
+                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,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = TeletexString
 
 
     base_klass = TeletexString
 
 
-class TestVideotexString(StringMixin, CommonMixin, TestCase):
+class TestVideotexString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = VideotexString
 
 
     base_klass = VideotexString
 
 
-class TestIA5String(StringMixin, CommonMixin, TestCase):
+class TestIA5String(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = IA5String
 
     base_klass = IA5String
 
+    def text_alphabet(self):
+        return "".join(chr(c) for c in range(128))
+
+    @given(integers(min_value=128, max_value=255))
+    def test_alphabet_bad(self, code):
+        with self.assertRaises(DecodeError):
+            self.base_klass().decod(
+                self.base_klass.tag_default +
+                len_encode(1) +
+                bytes(bytearray([code])),
+            )
+
 
 
-class TestGraphicString(StringMixin, CommonMixin, TestCase):
+class TestGraphicString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = GraphicString
 
 
     base_klass = GraphicString
 
 
-class TestVisibleString(StringMixin, CommonMixin, TestCase):
+class TestVisibleString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = VisibleString
 
     base_klass = VisibleString
 
+    def text_alphabet(self):
+        return " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+
+    def test_x690_vector(self):
+        obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573"))
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertFalse(obj.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertFalse(obj.bered)
+
+        obj, tail = VisibleString().decode(
+            hexdec("3A0904034A6F6E04026573"),
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(str(obj), "Jones")
+        self.assertTrue(obj.ber_encoded)
+        self.assertFalse(obj.lenindef)
+        self.assertTrue(obj.bered)
+        obj = 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)
+
+    @given(one_of((
+        integers(min_value=0, max_value=ord(" ") - 1),
+        integers(min_value=ord("~") + 1, max_value=255),
+    )))
+    def test_alphabet_bad(self, code):
+        with self.assertRaises(DecodeError):
+            self.base_klass().decod(
+                self.base_klass.tag_default +
+                len_encode(1) +
+                bytes(bytearray([code])),
+            )
+
+    @given(
+        sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
+        value, bound_min = list(sorted(ints))
+
+        class String(self.base_klass):
+            bounds = (bound_min, bound_min)
+        with self.assertRaises(DecodeError) as err:
+            String().decode(
+                self.base_klass(b"1" * value).encode(),
+                offset=offset,
+                decode_path=decode_path,
+            )
+        repr(err.exception)
+        self.assertEqual(err.exception.offset, offset)
+        self.assertEqual(err.exception.decode_path, decode_path)
+
 
 
-class TestGeneralString(StringMixin, CommonMixin, TestCase):
+class TestGeneralString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = GeneralString
 
 
     base_klass = GeneralString
 
 
@@ -2863,15 +4149,22 @@ class TimeMixin(object):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
         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)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
-        value = d.draw(datetimes(min_value=self.min_datetime))
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
+        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)
         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):
 
     @given(data_strategy())
     def test_comparison(self, d):
@@ -2883,8 +4176,8 @@ class TimeMixin(object):
             min_value=self.min_datetime,
             max_value=self.max_datetime,
         ))
             min_value=self.min_datetime,
             max_value=self.max_datetime,
         ))
-        tag1 = d.draw(binary())
-        tag2 = d.draw(binary())
+        tag1 = d.draw(binary(min_size=1))
+        tag2 = d.draw(binary(min_size=1))
         if self.omit_ms:
             value1 = value1.replace(microsecond=0)
             value2 = value2.replace(microsecond=0)
         if self.omit_ms:
             value1 = value1.replace(microsecond=0)
             value2 = value2.replace(microsecond=0)
@@ -2968,9 +4261,10 @@ class TimeMixin(object):
             max_datetime=self.max_datetime,
         ))
         obj = self.base_klass(*values)
             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):
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -3007,6 +4301,7 @@ class TimeMixin(object):
         ))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
         ))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
+        tail_junk = d.draw(binary(max_size=5))
         _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
         _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
@@ -3015,18 +4310,29 @@ class TimeMixin(object):
             _decoded=_decoded,
         )
         repr(obj)
             _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.assertFalse(obj.expled)
         obj_encoded = obj.encode()
+        self.assertEqual(encode2pass(obj), obj_encoded)
+        self.additional_symmetric_check(value, obj_encoded)
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
         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()
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        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)
         repr(obj_decoded)
-        pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        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())
         self.assertEqual(obj_decoded.todatetime(), obj.todatetime())
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.todatetime(), obj_expled.todatetime())
         self.assertEqual(obj_decoded.todatetime(), obj.todatetime())
@@ -3044,6 +4350,11 @@ class TimeMixin(object):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
             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):
 
 
 class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
@@ -3051,6 +4362,33 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
     omit_ms = False
     min_datetime = datetime(1900, 1, 1)
     max_datetime = datetime(9999, 12, 31)
     omit_ms = False
     min_datetime = datetime(1900, 1, 1)
     max_datetime = datetime(9999, 12, 31)
+    evgen_mode_skip_value = False
+
+    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):
+        str(GeneralizedTime())
+        repr(GeneralizedTime())
+
+    def test_x690_vector_valid(self):
+        for data in ((
+                b"19920521000000Z",
+                b"19920622123421Z",
+                b"19920722132100.3Z",
+        )):
+            GeneralizedTime(data)
+
+    def test_x690_vector_invalid(self):
+        for data in ((
+                b"19920520240000Z",
+                b"19920622123421.0Z",
+                b"19920722132100.30Z",
+        )):
+            with self.assertRaises(DecodeError) as err:
+                GeneralizedTime(data)
+            repr(err.exception)
 
     def test_go_vectors_invalid(self):
         for data in ((
 
     def test_go_vectors_invalid(self):
         for data in ((
@@ -3086,12 +4424,362 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
             datetime(2010, 1, 2, 3, 4, 5, 0),
         )
 
             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):
+        year = d.draw(integers(min_value=2, 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()),
+            )
+        else:
+            try:
+                obj.todatetime().timestamp()
+            except:
+                pass
+            else:
+                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 self.assertRaisesRegex(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)
+
+    def test_aware(self):
+        with self.assertRaisesRegex(ValueError, "only naive"):
+            GeneralizedTime(datetime(2000, 1, 1, 1, tzinfo=UTC))
+
 
 class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     base_klass = UTCTime
     omit_ms = True
     min_datetime = datetime(2000, 1, 1)
     max_datetime = datetime(2049, 12, 31)
 
 class TestUTCTime(TimeMixin, CommonMixin, TestCase):
     base_klass = UTCTime
     omit_ms = True
     min_datetime = datetime(2000, 1, 1)
     max_datetime = datetime(2049, 12, 31)
+    evgen_mode_skip_value = False
+
+    def additional_symmetric_check(self, value, obj_encoded):
+        pass
+
+    def test_repr_not_ready(self):
+        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 ((
 
     def test_go_vectors_invalid(self):
         for data in ((
@@ -3136,6 +4824,229 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
             datetime(1991, 5, 6, 23, 45, 40, 0),
         )
 
             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(
     @given(integers(min_value=0, max_value=49))
     def test_pre50(self, year):
         self.assertEqual(
@@ -3150,10 +5061,50 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
             1900 + year,
         )
 
             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
+            )
+
+    def test_aware(self):
+        with self.assertRaisesRegex(ValueError, "only naive"):
+            UTCTime(datetime(2000, 1, 1, 1, tzinfo=UTC))
+
+    def test_raises_if_no_dateutil(self):
+        with patch("pyderasn.tzUTC", new="missing"):
+            with self.assertRaisesRegex(NotImplementedError, "dateutil"):
+                UTCTime(datetime.now()).totzdatetime()
+
+    def test_tzinfo_gives_datetime_with_tzutc_tzinfo(self):
+        self.assertEqual(UTCTime(datetime.now()).totzdatetime().tzinfo, UTC)
+
+
+@composite
+def tlv_value_strategy(draw):
+    tag_num = draw(integers(min_value=1))
+    data = draw(binary())
+    return b"".join((tag_encode(tag_num), len_encode(len(data)), data))
+
 
 @composite
 def any_values_strategy(draw, do_expl=False):
 
 @composite
 def any_values_strategy(draw, do_expl=False):
-    value = draw(one_of(none(), binary()))
+    value = draw(one_of(none(), tlv_value_strategy()))
     expl = None
     if do_expl:
         expl = draw(one_of(none(), integers(min_value=1).map(tag_encode)))
     expl = None
     if do_expl:
         expl = draw(one_of(none(), integers(min_value=1).map(tag_encode)))
@@ -3183,19 +5134,23 @@ class TestAny(CommonMixin, TestCase):
         obj = Any(optional=optional)
         self.assertEqual(obj.optional, optional)
 
         obj = Any(optional=optional)
         self.assertEqual(obj.optional, optional)
 
-    @given(binary())
+    @given(tlv_value_strategy())
     def test_ready(self, value):
         obj = Any()
         self.assertFalse(obj.ready)
         repr(obj)
     def test_ready(self, value):
         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)
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         obj = Any(value)
         self.assertTrue(obj.ready)
         repr(obj)
         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):
 
     @given(integers())
     def test_basic(self, value):
@@ -3211,10 +5166,11 @@ class TestAny(CommonMixin, TestCase):
                 len(integer_encoded),
             )
             repr(obj)
                 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)
 
             self.assertSequenceEqual(obj.encode(), integer_encoded)
 
-    @given(binary(), binary())
+    @given(tlv_value_strategy(), tlv_value_strategy())
     def test_comparison(self, value1, value2):
         for klass in (Any, AnyInherited):
             obj1 = klass(value1)
     def test_comparison(self, value1, value2):
         for klass in (Any, AnyInherited):
             obj1 = klass(value1)
@@ -3266,9 +5222,10 @@ class TestAny(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Any, AnyInherited):
             obj = klass(*values)
     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):
 
     @given(binary().map(OctetString))
     def test_stripped(self, value):
@@ -3277,7 +5234,7 @@ class TestAny(CommonMixin, TestCase):
             obj.decode(obj.encode()[:-1])
 
     @given(
             obj.decode(obj.encode()[:-1])
 
     @given(
-        binary(),
+        tlv_value_strategy(),
         integers(min_value=1).map(tag_ctxc),
     )
     def test_stripped_expl(self, value, tag_expl):
         integers(min_value=1).map(tag_ctxc),
     )
     def test_stripped_expl(self, value, tag_expl):
@@ -3288,10 +5245,9 @@ class TestAny(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Any().decode(
                 tag_encode(tag)[:-1],
@@ -3305,10 +5261,9 @@ class TestAny(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             Any().decode(
                 Any.tag_default + len_encode(l)[:-1],
@@ -3325,27 +5280,58 @@ class TestAny(CommonMixin, TestCase):
         integers().map(lambda x: Integer(x).encode()),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
         integers().map(lambda x: Integer(x).encode()),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
+        decode_path_strat,
+        booleans(),
     )
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(
+            self,
+            values,
+            value,
+            tag_expl,
+            offset,
+            tail_junk,
+            decode_path,
+            keep_memoryview,
+    ):
         for klass in (Any, AnyInherited):
             _, _, optional, _decoded = values
             obj = klass(value=value, optional=optional, _decoded=_decoded)
             repr(obj)
         for klass in (Any, AnyInherited):
             _, _, 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)
             self.assertFalse(obj.expled)
+            tag_class, _, tag_num = tag_decode(tag_strip(value)[0])
+            self.assertEqual(obj.tag_order, (tag_class, tag_num))
             obj_encoded = obj.encode()
             obj_encoded = obj.encode()
+            self.assertEqual(encode2pass(obj), obj_encoded)
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
             obj_expled = obj(value, expl=tag_expl)
             self.assertTrue(obj_expled.expled)
+            tag_class, _, tag_num = tag_decode(tag_expl)
+            self.assertEqual(obj_expled.tag_order, (tag_class, tag_num))
             repr(obj_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()
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            ctx_dummy["keep_memoryview"] = keep_memoryview
+            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)
             repr(obj_decoded)
-            pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            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))
             self.assertEqual(bytes(obj_decoded), bytes(obj))
             self.assertEqual(obj_decoded, obj_expled)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
             self.assertEqual(bytes(obj_decoded), bytes(obj))
+            self.assertIsInstance(
+                obj_decoded._value,
+                memoryview if keep_memoryview else bytes,
+            )
             self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
             self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
             self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
             self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
             self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
             self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
@@ -3363,18 +5349,134 @@ class TestAny(CommonMixin, TestCase):
             self.assertEqual(obj_decoded.tlen, 0)
             self.assertEqual(obj_decoded.llen, 0)
             self.assertEqual(obj_decoded.vlen, len(value))
             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,
+            )
+
+            evgens = list(obj_expled.decode_evgen(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+                decode_path=decode_path,
+                ctx=ctx_copied,
+            ))
+            self.assertEqual(len(evgens), 1)
+            _decode_path, obj, tail = evgens[0]
+            self.assertSequenceEqual(tail, tail_junk)
+            self.assertEqual(_decode_path, decode_path)
+            self.assertEqual(obj.expl_offset, offset)
+            repr(obj)
+            list(obj.pps())
+
+    @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)))
 
 
 @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),
         ))]
             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(
     value = None
     if value_required or draw(booleans()):
         value = draw(tuples(
@@ -3407,11 +5509,11 @@ class TestChoice(CommonMixin, TestCase):
     base_klass = Wahl
 
     def test_schema_required(self):
     base_klass = Wahl
 
     def test_schema_required(self):
-        with assertRaisesRegex(self, ValueError, "schema must be specified"):
+        with self.assertRaisesRegex(ValueError, "schema must be specified"):
             Choice()
 
     def test_impl_forbidden(self):
             Choice()
 
     def test_impl_forbidden(self):
-        with assertRaisesRegex(self, ValueError, "no implicit tag allowed"):
+        with self.assertRaisesRegex(ValueError, "no implicit tag allowed"):
             Choice(impl=b"whatever")
 
     def test_invalid_value_type(self):
             Choice(impl=b"whatever")
 
     def test_invalid_value_type(self):
@@ -3438,19 +5540,24 @@ class TestChoice(CommonMixin, TestCase):
         obj = self.base_klass()
         self.assertFalse(obj.ready)
         repr(obj)
         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()
         repr(err.exception)
         self.assertIsNone(obj["whatever"])
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         obj["whatever"] = Boolean()
         self.assertFalse(obj.ready)
         repr(obj)
         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)
         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):
 
     @given(booleans(), booleans())
     def test_comparison(self, value1, value2):
@@ -3529,6 +5636,7 @@ class TestChoice(CommonMixin, TestCase):
 
         class Wahl(self.base_klass):
             schema = _schema
 
         class Wahl(self.base_klass):
             schema = _schema
+        register_class(Wahl)
         obj = Wahl(
             value=value,
             expl=expl,
         obj = Wahl(
             value=value,
             expl=expl,
@@ -3536,15 +5644,17 @@ class TestChoice(CommonMixin, TestCase):
             optional=optional or False,
             _decoded=_decoded,
         )
             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):
 
     @given(booleans())
     def test_stripped(self, value):
@@ -3569,6 +5679,8 @@ class TestChoice(CommonMixin, TestCase):
         )
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
         )
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
+        tail_junk = d.draw(binary(max_size=5))
+        decode_path = d.draw(decode_path_strat)
 
         class Wahl(self.base_klass):
             schema = _schema
 
         class Wahl(self.base_klass):
             schema = _schema
@@ -3579,18 +5691,31 @@ class TestChoice(CommonMixin, TestCase):
             _decoded=_decoded,
         )
         repr(obj)
             _decoded=_decoded,
         )
         repr(obj)
-        pprint(obj)
+        list(obj.pps())
+        pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertFalse(obj.expled)
         self.assertFalse(obj.expled)
+        self.assertEqual(obj.tag_order, obj.value.tag_order)
         obj_encoded = obj.encode()
         obj_encoded = obj.encode()
+        self.assertEqual(encode2pass(obj), obj_encoded)
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
+        tag_class, _, tag_num = tag_decode(tag_expl)
+        self.assertEqual(obj_expled.tag_order, (tag_class, tag_num))
         repr(obj_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()
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        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)
         repr(obj_decoded)
-        pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        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)
         self.assertEqual(obj_decoded.value, obj_expled.value)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.choice, obj_expled.choice)
         self.assertEqual(obj_decoded.value, obj_expled.value)
@@ -3612,11 +5737,32 @@ class TestChoice(CommonMixin, TestCase):
         self.assertEqual(obj_decoded.expl_offset, offset)
         self.assertSequenceEqual(
             obj_expled_encoded[
         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,
         )
             ],
             obj_encoded,
         )
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
+
+        evgens = list(obj_expled.decode_evgen(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+            decode_path=decode_path,
+            ctx=ctx_copied,
+        ))
+        self.assertEqual(len(evgens), 2)
+        _decode_path, obj, tail = evgens[0]
+        self.assertEqual(_decode_path, decode_path + (obj_decoded.choice,))
+        _decode_path, obj, tail = evgens[1]
+        self.assertSequenceEqual(tail, tail_junk)
+        self.assertEqual(_decode_path, decode_path)
+        self.assertEqual(obj.expl_offset, offset)
+        repr(obj)
+        list(obj.pps())
 
     @given(integers())
     def test_set_get(self, value):
 
     @given(integers())
     def test_set_get(self, value):
@@ -3650,21 +5796,39 @@ class TestChoice(CommonMixin, TestCase):
         with self.assertRaises(TagMismatch):
             obj.decode(int_encoded)
 
         with self.assertRaises(TagMismatch):
             obj.decode(int_encoded)
 
+    def test_tag_mismatch_underlying(self):
+        class SeqOfBoolean(SequenceOf):
+            schema = Boolean()
+
+        class SeqOfInteger(SequenceOf):
+            schema = Integer()
+
+        class Wahl(Choice):
+            schema = (
+                ("erste", SeqOfBoolean()),
+            )
+
+        int_encoded = SeqOfInteger((Integer(123),)).encode()
+        bool_encoded = SeqOfBoolean((Boolean(False),)).encode()
+        obj = Wahl()
+        obj.decode(bool_encoded)
+        with self.assertRaises(TagMismatch) as err:
+            obj.decode(int_encoded)
+        self.assertEqual(err.exception.decode_path, ("erste", "0"))
+
 
 @composite
 def seq_values_strategy(draw, seq_klass, do_expl=False):
     value = None
     if draw(booleans()):
         value = seq_klass()
 
 @composite
 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(
     schema = None
     if draw(booleans()):
         schema = list(draw(dictionaries(
@@ -3683,15 +5847,13 @@ def seq_values_strategy(draw, seq_klass, do_expl=False):
     default = None
     if draw(booleans()):
         default = seq_klass()
     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)),
     optional = draw(one_of(none(), booleans()))
     _decoded = (
         draw(integers(min_value=0)),
@@ -3822,10 +5984,10 @@ def sequences_strategy(draw, seq_klass):
     return seq_outer, expect_outers
 
 
     return seq_outer, expect_outers
 
 
-class SeqMixing(object):
+class SeqMixin(object):
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
-            self.base_klass((1, 2, 3))
+            self.base_klass(123)
         repr(err.exception)
 
     def test_invalid_value_type_set(self):
         repr(err.exception)
 
     def test_invalid_value_type_set(self):
@@ -3871,20 +6033,25 @@ class SeqMixing(object):
             seq[name] = Boolean()
         self.assertFalse(seq.ready)
         repr(seq)
             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)
         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)
         with self.assertRaises(ObjNotReady) as err:
             seq.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(seq)
         for name, value in non_ready.items():
             seq[name] = Boolean(value)
         self.assertTrue(seq.ready)
         repr(seq)
         for name, value in non_ready.items():
             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):
 
     @given(data_strategy())
     def test_call(self, d):
@@ -3946,13 +6113,15 @@ class SeqMixing(object):
     def test_copy(self, d):
         class SeqInherited(self.base_klass):
             pass
     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)
         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):
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -3980,8 +6149,9 @@ class SeqMixing(object):
         with self.assertRaises(NotEnoughData):
             seq.decode(seq.encode()[:-1])
 
         with self.assertRaises(NotEnoughData):
             seq.decode(seq.encode()[:-1])
 
-    @given(binary(min_size=2))
-    def test_non_tag_mismatch_raised(self, junk):
+    @given(integers(min_value=3), binary(min_size=2))
+    def test_non_tag_mismatch_raised(self, junk_tag_num, junk):
+        junk = tag_encode(junk_tag_num) + junk
         try:
             _, _, len_encoded = tag_strip(memoryview(junk))
             len_decode(len_encoded)
         try:
             _, _, len_encoded = tag_strip(memoryview(junk))
             len_decode(len_encoded)
@@ -4006,10 +6176,9 @@ class SeqMixing(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -4023,10 +6192,9 @@ class SeqMixing(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -4056,31 +6224,98 @@ class SeqMixing(object):
     @given(data_strategy())
     def test_symmetric(self, d):
         seq, expects = d.draw(sequence_strategy(seq_klass=self.base_klass))
     @given(data_strategy())
     def test_symmetric(self, d):
         seq, expects = d.draw(sequence_strategy(seq_klass=self.base_klass))
+        tail_junk = d.draw(binary(max_size=5))
+        decode_path = d.draw(decode_path_strat)
         self.assertTrue(seq.ready)
         self.assertFalse(seq.decoded)
         self._assert_expects(seq, expects)
         repr(seq)
         self.assertTrue(seq.ready)
         self.assertFalse(seq.decoded)
         self._assert_expects(seq, expects)
         repr(seq)
-        pprint(seq)
-        seq_encoded = seq.encode()
-        seq_decoded, tail = seq.decode(seq_encoded)
-        self.assertEqual(tail, b"")
+        list(seq.pps())
+        pprint(seq, big_blobs=True, with_decode_path=True)
         self.assertTrue(seq.ready)
         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(),
-            )
+        seq_encoded = seq.encode()
+        self.assertEqual(encode2pass(seq), seq_encoded)
+        seq_encoded_cer = encode_cer(seq)
+        self.assertNotEqual(seq_encoded_cer, seq_encoded)
+        self.assertSequenceEqual(
+            seq.decod(seq_encoded_cer, ctx={"bered": True}).encode(),
+            seq_encoded,
+        )
+        seq_decoded, tail = seq.decode(seq_encoded + tail_junk)
+        self.assertFalse(seq_decoded.lenindef)
+        self.assertFalse(seq_decoded.ber_encoded)
+        self.assertFalse(seq_decoded.bered)
+
+        t, _, lv = tag_strip(seq_encoded)
+        _, _, v = len_decode(lv)
+        seq_encoded_lenindef = t + LENINDEF + v + EOC
+        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(),
+                )
+
+            evgens = list(seq.decode_evgen(
+                encoded + decoded_tail,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            ))
+            self.assertEqual(len(evgens), len(list(decoded._values_for_encoding())) + 1)
+            for _decode_path, obj, _ in evgens[:-1]:
+                self.assertEqual(_decode_path[:-1], decode_path)
+                repr(obj)
+                list(obj.pps())
+            _decode_path, obj, tail = evgens[-1]
+            self.assertEqual(_decode_path, decode_path)
+            repr(obj)
+            list(obj.pps())
+
+        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())
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
@@ -4088,6 +6323,7 @@ class SeqMixing(object):
         seq, expect_outers = d.draw(sequences_strategy(seq_klass=self.base_klass))
         self.assertTrue(seq.ready)
         seq_encoded = seq.encode()
         seq, expect_outers = d.draw(sequences_strategy(seq_klass=self.base_klass))
         self.assertTrue(seq.ready)
         seq_encoded = seq.encode()
+        self.assertEqual(encode2pass(seq), seq_encoded)
         seq_decoded, tail = seq.decode(seq_encoded)
         self.assertEqual(tail, b"")
         self.assertTrue(seq.ready)
         seq_decoded, tail = seq.decode(seq_encoded)
         self.assertEqual(tail, b"")
         self.assertTrue(seq.ready)
@@ -4133,44 +6369,76 @@ class SeqMixing(object):
         self.assertSequenceEqual(seq.encode(), empty_seq)
 
     @given(data_strategy())
         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(
         _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),
         ))]
 
             min_size=len(_schema),
             max_size=len(_schema),
         ))]
 
+        class Wahl(Choice):
+            schema = (("int", Integer()),)
+
         class SeqWithoutDefault(self.base_klass):
             schema = [
         class SeqWithoutDefault(self.base_klass):
             schema = [
-                (n, Integer(impl=t))
+                (n, Wahl(expl=t))
                 for (n, _), t in zip(_schema, tags)
             ]
         seq_without_default = SeqWithoutDefault()
         for name, value in _schema:
                 for (n, _), t in zip(_schema, tags)
             ]
         seq_without_default = SeqWithoutDefault()
         for name, value in _schema:
-            seq_without_default[name] = Integer(value)
+            seq_without_default[name] = Wahl(("int", Integer(value)))
         seq_encoded = seq_without_default.encode()
         seq_encoded = seq_without_default.encode()
+        seq_without_default.decode(seq_encoded)
+        self.assertEqual(
+            len(list(seq_without_default.decode_evgen(seq_encoded))),
+            len(_schema) * 2 + 1,
+        )
 
         class SeqWithDefault(self.base_klass):
             schema = [
 
         class SeqWithDefault(self.base_klass):
             schema = [
-                (n, Integer(default=v, impl=t))
+                (n, Wahl(default=Wahl(("int", Integer(v))), expl=t))
                 for (n, v), t in zip(_schema, tags)
             ]
         seq_with_default = SeqWithDefault()
                 for (n, v), t in zip(_schema, tags)
             ]
         seq_with_default = SeqWithDefault()
-        seq_decoded, _ = seq_with_default.decode(seq_encoded)
+        with self.assertRaisesRegex(DecodeError, "DEFAULT value met"):
+            seq_with_default.decode(seq_encoded)
+        with self.assertRaisesRegex(DecodeError, "DEFAULT value met"):
+            list(seq_with_default.decode_evgen(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, value)
+            self.assertEqual(
+                len(list(seq_with_default.decode_evgen(seq_encoded, ctx=ctx))),
+                len(_schema) + 1,
+            )
+
+        seq_without_default = SeqWithoutDefault()
         for name, value in _schema:
         for name, value in _schema:
-            self.assertEqual(seq_decoded[name], seq_with_default[name])
-            self.assertEqual(seq_decoded[name], value)
+            seq_without_default[name] = Wahl(("int", Integer(value + 1)))
+        seq_encoded = seq_without_default.encode()
+        seq_with_default.decode(seq_encoded)
+        self.assertEqual(
+            len(list(seq_with_default.decode_evgen(seq_encoded))),
+            len(_schema) + 1,
+        )
 
     @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(
 
     @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),
         ))]
             min_size=len(names),
             max_size=len(names),
         ))]
@@ -4189,9 +6457,48 @@ class SeqMixing(object):
         seq_missing = SeqMissing()
         with self.assertRaises(TagMismatch):
             seq_missing.decode(seq_encoded)
         seq_missing = SeqMissing()
         with self.assertRaises(TagMismatch):
             seq_missing.decode(seq_encoded)
+        with self.assertRaises(TagMismatch):
+            list(seq_missing.decode_evgen(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 TestSequence(SeqMixing, CommonMixin, TestCase):
+        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)
+        with self.assertRaises(DecodeError):
+            list(Seq().decode_evgen(encoded))
+        list(Seq().decode_evgen(encoded, ctx={"bered": True}))
+        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(SeqMixin, CommonMixin, TestCase):
     base_klass = Sequence
 
     @given(
     base_klass = Sequence
 
     @given(
@@ -4209,7 +6516,7 @@ class TestSequence(SeqMixing, CommonMixin, TestCase):
             len_encode(len(int_encoded + junk)),
             int_encoded + junk,
         ))
             len_encode(len(int_encoded + junk)),
             int_encoded + junk,
         ))
-        with assertRaisesRegex(self, DecodeError, "remaining"):
+        with self.assertRaisesRegex(DecodeError, "remaining"):
             Seq().decode(junked)
 
     @given(sets(text_letters(), min_size=2))
             Seq().decode(junked)
 
     @given(sets(text_letters(), min_size=2))
@@ -4226,30 +6533,87 @@ class TestSequence(SeqMixing, CommonMixin, TestCase):
             seq[missing] = Boolean()
         repr(err.exception)
 
             seq[missing] = Boolean()
         repr(err.exception)
 
+    def test_x690_vector(self):
+        class Seq(Sequence):
+            schema = (
+                ("name", IA5String()),
+                ("ok", Boolean()),
+            )
+        seq = Seq().decode(hexdec("300A1605536d6974680101FF"))[0]
+        self.assertEqual(seq["name"], "Smith")
+        self.assertEqual(seq["ok"], True)
+
 
 
-class TestSet(SeqMixing, CommonMixin, TestCase):
+class TestSet(SeqMixin, CommonMixin, TestCase):
     base_klass = Set
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
     def test_sorted(self, d):
     base_klass = Set
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
     def test_sorted(self, d):
-        tags = [
-            tag_encode(tag) for tag in
-            d.draw(sets(integers(min_value=1), min_size=1, max_size=10))
-        ]
+        class DummySeq(Sequence):
+            schema = (("null", Null()),)
+
+        tag_nums = d.draw(sets(integers(min_value=1), min_size=1, max_size=50))
+        _, _, dummy_seq_tag_num = tag_decode(DummySeq.tag_default)
+        assume(any(i > dummy_seq_tag_num for i in tag_nums))
+        tag_nums -= set([dummy_seq_tag_num])
+        _schema = [(str(i), OctetString(impl=tag_encode(i))) for i in tag_nums]
+        _schema.append(("seq", DummySeq()))
 
         class Seq(Set):
 
         class Seq(Set):
-            schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)]
+            schema = d.draw(permutations(_schema))
         seq = Seq()
         seq = Seq()
-        for name, _ in Seq.schema:
-            seq[name] = OctetString(b"")
+        for name, _ in _schema:
+            if name != "seq":
+                seq[name] = OctetString(name.encode("ascii"))
+        seq["seq"] = DummySeq((("null", Null()),))
+
         seq_encoded = seq.encode()
         seq_decoded, _ = seq.decode(seq_encoded)
         seq_encoded = seq.encode()
         seq_decoded, _ = seq.decode(seq_encoded)
+        seq_encoded_expected = []
+        for tag_num in sorted(tag_nums | set([dummy_seq_tag_num])):
+            if tag_num == dummy_seq_tag_num:
+                seq_encoded_expected.append(seq["seq"].encode())
+            else:
+                seq_encoded_expected.append(seq[str(tag_num)].encode())
         self.assertSequenceEqual(
             seq_encoded[seq_decoded.tlen + seq_decoded.llen:],
         self.assertSequenceEqual(
             seq_encoded[seq_decoded.tlen + seq_decoded.llen:],
-            b"".join(sorted([seq[name].encode() for name, _ in Seq.schema])),
+            b"".join(seq_encoded_expected),
         )
 
         )
 
+        encoded = b"".join(seq[str(i)].encode() for i in tag_nums)
+        encoded += seq["seq"].encode()
+        seq_encoded = b"".join((
+            Set.tag_default,
+            len_encode(len(encoded)),
+            encoded,
+        ))
+        with self.assertRaisesRegex(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)
+
+    def test_same_value_twice(self):
+        class Seq(Set):
+            schema = (
+                ("bool", Boolean()),
+                ("int", Integer()),
+            )
+
+        encoded = b"".join((
+            Integer(123).encode(),
+            Integer(234).encode(),
+            Boolean(True).encode(),
+        ))
+        encoded = Seq.tag_default + len_encode(len(encoded)) + encoded
+        with self.assertRaises(TagMismatch):
+            Seq().decod(encoded, ctx={"allow_unordered_set": True})
+
 
 @composite
 def seqof_values_strategy(draw, schema=None, do_expl=False):
 
 @composite
 def seqof_values_strategy(draw, schema=None, do_expl=False):
@@ -4295,7 +6659,7 @@ def seqof_values_strategy(draw, schema=None, do_expl=False):
     )
 
 
     )
 
 
-class SeqOfMixing(object):
+class SeqOfMixin(object):
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
             self.base_klass(123)
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
             self.base_klass(123)
@@ -4309,10 +6673,10 @@ class SeqOfMixing(object):
         repr(err.exception)
 
     def test_schema_required(self):
         repr(err.exception)
 
     def test_schema_required(self):
-        with assertRaisesRegex(self, ValueError, "schema must be specified"):
+        with self.assertRaisesRegex(ValueError, "schema must be specified"):
             self.base_klass.__mro__[1]()
 
             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()
     def test_comparison(self, value1, value2, tag1, tag2):
         class SeqOf(self.base_klass):
             schema = Boolean()
@@ -4356,17 +6720,21 @@ class SeqOfMixing(object):
             seqof.append(value)
         self.assertFalse(seqof.ready)
         repr(seqof)
             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)
         with self.assertRaises(ObjNotReady) as err:
             seqof.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(seqof)
         for i, value in enumerate(values):
             self.assertEqual(seqof[i], value)
             if not seqof[i].ready:
                 seqof[i] = Integer(i)
         self.assertTrue(seqof.ready)
         repr(seqof)
         for i, value in enumerate(values):
             self.assertEqual(seqof[i], value)
             if not seqof[i].ready:
                 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):
 
     def test_spec_mismatch(self):
         class SeqOf(self.base_klass):
@@ -4393,17 +6761,35 @@ 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))
             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(min_value=0, max_value=bound_min - 1))
         with self.assertRaises(BoundsError) as err:
             SeqOf(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
         with self.assertRaises(BoundsError) as err:
             SeqOf(value=value, bounds=(bound_min, bound_max))
         repr(err.exception)
-        value = [Boolean()] * d.draw(integers(
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            SeqOf(bounds=(bound_min, bound_max)).decode(
+                SeqOf(value).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            SeqOf(bounds=(bound_min, bound_max)).decode(
+                encode2pass(SeqOf(value))
+            )
+        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)
             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 self.assertRaisesRegex(DecodeError, "bounds") as err:
+            SeqOf(bounds=(bound_min, bound_max)).decode(
+                SeqOf(value).encode()
+            )
+        repr(err.exception)
+        with self.assertRaisesRegex(DecodeError, "bounds") as err:
+            SeqOf(bounds=(bound_min, bound_max)).decode(
+                encode2pass(SeqOf(value))
+            )
 
     @given(integers(min_value=1, max_value=10))
     def test_out_of_bounds(self, bound_max):
 
     @given(integers(min_value=1, max_value=10))
     def test_out_of_bounds(self, bound_max):
@@ -4508,6 +6894,7 @@ class SeqOfMixing(object):
 
         class SeqOf(self.base_klass):
             schema = _schema
 
         class SeqOf(self.base_klass):
             schema = _schema
+        register_class(SeqOf)
         obj = SeqOf(
             value=value,
             bounds=bounds,
         obj = SeqOf(
             value=value,
             bounds=bounds,
@@ -4517,11 +6904,12 @@ class SeqOfMixing(object):
             optional=optional or False,
             _decoded=_decoded,
         )
             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()),
 
     @given(
         lists(binary()),
@@ -4548,10 +6936,9 @@ class SeqOfMixing(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -4565,10 +6952,9 @@ class SeqOfMixing(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
     )
     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],
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -4591,8 +6977,10 @@ class SeqOfMixing(object):
         lists(integers().map(Integer)),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
         lists(integers().map(Integer)),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
+        decode_path_strat,
     )
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         _, _, _, _, _, default, optional, _decoded = values
 
         class SeqOf(self.base_klass):
         _, _, _, _, _, default, optional, _decoded = values
 
         class SeqOf(self.base_klass):
@@ -4604,18 +6992,34 @@ class SeqOfMixing(object):
             _decoded=_decoded,
         )
         repr(obj)
             _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.assertFalse(obj.expled)
         obj_encoded = obj.encode()
+        self.assertEqual(encode2pass(obj), obj_encoded)
+        obj_encoded_cer = encode_cer(obj)
+        self.assertNotEqual(obj_encoded_cer, obj_encoded)
+        self.assertSequenceEqual(
+            obj.decod(obj_encoded_cer, ctx={"bered": True}).encode(),
+            obj_encoded,
+        )
         obj_expled = obj(value, expl=tag_expl)
         self.assertTrue(obj_expled.expled)
         repr(obj_expled)
         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()
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        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)
         repr(obj_decoded)
-        pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        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)
         self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
         self._test_symmetric_compare_objs(obj_decoded, obj_expled)
         self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
         self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
@@ -4641,8 +7045,91 @@ 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})
+
+        evgens = list(obj.decode_evgen(
+            obj_encoded_lenindef + tail_junk,
+            decode_path=decode_path,
+            ctx={"bered": True},
+        ))
+        self.assertEqual(len(evgens), len(obj_decoded_lenindef) + 1)
+        for i, (_decode_path, obj, _) in enumerate(evgens[:-1]):
+            self.assertEqual(_decode_path, decode_path + (str(i),))
+            repr(obj)
+            list(obj.pps())
+        _decode_path, obj, tail = evgens[-1]
+        self.assertEqual(_decode_path, decode_path)
+        repr(obj)
+        list(obj.pps())
+
+        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 TestSequenceOf(SeqOfMixing, CommonMixin, TestCase):
+        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(SeqOfMixin, CommonMixin, TestCase):
     class SeqOf(SequenceOf):
         schema = "whatever"
     base_klass = SeqOf
     class SeqOf(SequenceOf):
         schema = "whatever"
     base_klass = SeqOf
@@ -4651,8 +7138,82 @@ class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase):
         self.assertEqual(obj1, obj2)
         self.assertSequenceEqual(list(obj1), list(obj2))
 
         self.assertEqual(obj1, obj2)
         self.assertSequenceEqual(list(obj1), list(obj2))
 
+    def test_iterator_pickling(self):
+        class SeqOf(SequenceOf):
+            schema = Integer()
+        register_class(SeqOf)
+        seqof = SeqOf()
+        pickle_dumps(seqof)
+        seqof = seqof(iter(range(10)))
+        with self.assertRaisesRegex(ValueError, "iterator"):
+            pickle_dumps(seqof)
+
+    def test_iterator_bounds(self):
+        class SeqOf(SequenceOf):
+            schema = Integer()
+            bounds = (10, 20)
+        seqof = None
+
+        def gen(n):
+            for i in range(n):
+                yield Integer(i)
+        for n in (9, 21):
+            seqof = SeqOf(gen(n))
+            self.assertTrue(seqof.ready)
+            with self.assertRaises(BoundsError):
+                seqof.encode()
+            self.assertFalse(seqof.ready)
+            seqof = seqof(gen(n))
+            self.assertTrue(seqof.ready)
+            with self.assertRaises(BoundsError):
+                encode_cer(seqof)
+            self.assertFalse(seqof.ready)
+
+    def test_iterator_twice(self):
+        class SeqOf(SequenceOf):
+            schema = Integer()
+            bounds = (1, float("+inf"))
+
+        def gen():
+            for i in range(10):
+                yield Integer(i)
+        seqof = SeqOf(gen())
+        self.assertTrue(seqof.ready)
+        seqof.encode()
+        self.assertFalse(seqof.ready)
+        register_class(SeqOf)
+        pickle_dumps(seqof)
+
+    def test_iterator_2pass(self):
+        class SeqOf(SequenceOf):
+            schema = Integer()
+            bounds = (1, float("+inf"))
+
+        def gen():
+            for i in range(10):
+                yield Integer(i)
+        seqof = SeqOf(gen())
+        self.assertTrue(seqof.ready)
+        _, state = seqof.encode1st()
+        self.assertFalse(seqof.ready)
+        seqof = seqof(gen())
+        self.assertTrue(seqof.ready)
+        buf = BytesIO()
+        seqof.encode2nd(buf.write, iter(state))
+        self.assertSequenceEqual(
+            [int(i) for i in seqof.decod(buf.getvalue())],
+            list(gen()),
+        )
+
+    def test_non_ready_bound_min(self):
+        class SeqOf(SequenceOf):
+            schema = Integer()
+            bounds = (1, float("+inf"))
+        seqof = SeqOf()
+        self.assertFalse(seqof.ready)
+
 
 
-class TestSetOf(SeqOfMixing, CommonMixin, TestCase):
+class TestSetOf(SeqOfMixin, CommonMixin, TestCase):
     class SeqOf(SetOf):
         schema = "whatever"
     base_klass = SeqOf
     class SeqOf(SetOf):
         schema = "whatever"
     base_klass = SeqOf
@@ -4678,6 +7239,41 @@ class TestSetOf(SeqOfMixing, CommonMixin, TestCase):
             b"".join(sorted([v.encode() for v in values])),
         )
 
             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 self.assertRaisesRegex(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):
 
 class TestGoMarshalVectors(TestCase):
     def runTest(self):
@@ -4820,8 +7416,11 @@ class TestGoMarshalVectors(TestCase):
         seq = Seq()
         seq["erste"] = PrintableString("test")
         self.assertSequenceEqual(seq.encode(), hexdec("3006130474657374"))
         seq = Seq()
         seq["erste"] = PrintableString("test")
         self.assertSequenceEqual(seq.encode(), hexdec("3006130474657374"))
+        # Asterisk is actually not allowable
+        pyderasn.PRINTABLE_ALLOWABLE_CHARS |= set(b"*")
         seq["erste"] = PrintableString("test*")
         self.assertSequenceEqual(seq.encode(), hexdec("30071305746573742a"))
         seq["erste"] = PrintableString("test*")
         self.assertSequenceEqual(seq.encode(), hexdec("30071305746573742a"))
+        pyderasn.PRINTABLE_ALLOWABLE_CHARS -= set(b"*")
 
         class Seq(Sequence):
             schema = (
 
         class Seq(Sequence):
             schema = (
@@ -4867,13 +7466,20 @@ class TestPP(TestCase):
     def test_oid_printing(self, d):
         oids = {
             str(ObjectIdentifier(k)): v * 2
     def test_oid_printing(self, d):
         oids = {
             str(ObjectIdentifier(k)): v * 2
-            for k, v in d.draw(dictionaries(oid_strategy(), text_letters())).items()
+            for k, v in d.draw(dictionaries(
+                oid_strategy(),
+                text_letters(),
+                min_size=1,
+            )).items()
         }
         chosen = d.draw(sampled_from(sorted(oids)))
         chosen_id = oids[chosen]
         pp = _pp(asn1_type_name=ObjectIdentifier.asn1_type_name, value=chosen)
         self.assertNotIn(chosen_id, pp_console_row(pp))
         }
         chosen = d.draw(sampled_from(sorted(oids)))
         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):
 
 
 class TestAutoAddSlots(TestCase):
@@ -4901,33 +7507,37 @@ class TestOIDDefines(TestCase):
             min_size=len(value_names),
             max_size=len(value_names),
         ))
             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):
 
 
 class TestDefinesByPath(TestCase):
-    def runTest(self):
+    def test_generated(self):
         class Seq(Sequence):
             schema = (
                 ("type", ObjectIdentifier()),
         class Seq(Sequence):
             schema = (
                 ("type", ObjectIdentifier()),
@@ -4974,10 +7584,10 @@ class TestDefinesByPath(TestCase):
             (type_integered, Integer(234)),
         )
         for t, v in pairs_input:
             (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)
         seq_inner = SeqInner()
         seq_inner["typeInner"] = type_innered
         seq_inner["valueInner"] = Any(pairs)
@@ -4985,32 +7595,67 @@ class TestDefinesByPath(TestCase):
         seq_sequenced["type"] = type_sequenced
         seq_sequenced["value"] = OctetString(seq_inner.encode())
         seq_sequenced_raw = seq_sequenced.encode()
         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 = []
 
         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(
         self.assertIsNone(seq_integered["value"].defined)
         defines_by_path.append(
-            (("type",), ("value", {
+            (("type",), ((("value",), {
                 type_integered: Integer(),
                 type_sequenced: SeqInner(),
                 type_integered: Integer(),
                 type_sequenced: SeqInner(),
-            }))
+            }),))
         )
         )
-        seq_integered, _ = Seq().decode(seq_integered_raw, defines_by_path=defines_by_path)
+        ctx_copied["defines_by_path"] = defines_by_path
+        seq_integered, _ = Seq().decode(
+            seq_integered_raw,
+            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.assertIsNotNone(seq_integered["value"].defined)
         self.assertEqual(seq_integered["value"].defined[0], type_integered)
         self.assertEqual(seq_integered["value"].defined[1], Integer(123))
-
-        seq_sequenced, _ = Seq().decode(seq_sequenced_raw, defines_by_path=defines_by_path)
+        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=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)
         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((
 
         defines_by_path.append((
-            ("value", decode_path_defby(type_sequenced), "typeInner"),
-            ("valueInner", {type_innered: Pairs()}),
+            ("value", DecodePathDefBy(type_sequenced), "typeInner"),
+            ((("valueInner",), {type_innered: Pairs()}),),
         ))
         ))
-        seq_sequenced, _ = Seq().decode(seq_sequenced_raw, defines_by_path=defines_by_path)
+        ctx_copied["defines_by_path"] = defines_by_path
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            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.assertIsNotNone(seq_sequenced["value"].defined)
         self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
         seq_inner = seq_sequenced["value"].defined[1]
@@ -5019,22 +7664,31 @@ class TestDefinesByPath(TestCase):
         pairs = seq_inner["valueInner"].defined[1]
         for pair in pairs:
             self.assertIsNone(pair["value"][0].defined)
         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((
             (
                 "value",
 
         defines_by_path.append((
             (
                 "value",
-                decode_path_defby(type_sequenced),
+                DecodePathDefBy(type_sequenced),
                 "valueInner",
                 "valueInner",
-                decode_path_defby(type_innered),
+                DecodePathDefBy(type_innered),
                 any,
                 "type",
             ),
                 any,
                 "type",
             ),
-            ("value", {
+            ((("value",), {
                 type_integered: Integer(),
                 type_octet_stringed: OctetString(),
                 type_integered: Integer(),
                 type_octet_stringed: OctetString(),
-            }),
+            }),),
         ))
         ))
-        seq_sequenced, _ = Seq().decode(seq_sequenced_raw, defines_by_path=defines_by_path)
+        ctx_copied["defines_by_path"] = defines_by_path
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            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.assertIsNotNone(seq_sequenced["value"].defined)
         self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
         seq_inner = seq_sequenced["value"].defined[1]
@@ -5044,3 +7698,249 @@ 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])
         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):
+        class Inner(Sequence):
+            schema = (
+                ("oid", ObjectIdentifier(defines=((("..", "tgt"), {
+                    ObjectIdentifier(oid): Integer(),
+                }),))),
+            )
+
+        class Outer(Sequence):
+            schema = (
+                ("inner", Inner()),
+                ("tgt", OctetString()),
+            )
+
+        inner = Inner()
+        inner["oid"] = ObjectIdentifier(oid)
+        outer = Outer()
+        outer["inner"] = inner
+        outer["tgt"] = OctetString(Integer(tgt).encode())
+        decoded, _ = Outer().decode(outer.encode())
+        self.assertEqual(decoded["tgt"].defined[1], Integer(tgt))
+
+    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 self.assertRaisesRegex(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 self.assertRaisesRegex(DecodeError, "remaining data"):
+            Seq().decode(seq.encode())
+
+
+class TestAbsDecodePath(TestCase):
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1)).map(tuple),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_concat(self, decode_path, rel_path):
+        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),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_abs(self, decode_path, rel_path):
+        self.assertSequenceEqual(
+            abs_decode_path(decode_path, ("/",) + rel_path),
+            rel_path,
+        )
+
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=5).map(tuple),
+        integers(min_value=1, max_value=3),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_dots(self, decode_path, number_of_dots, rel_path):
+        self.assertSequenceEqual(
+            abs_decode_path(decode_path, tuple([".."] * number_of_dots) + rel_path),
+            decode_path[:-number_of_dots] + rel_path,
+        )
+
+
+class TestStrictDefaultExistence(TestCase):
+    @given(data_strategy())
+    def runTest(self, d):
+        count = d.draw(integers(min_value=1, max_value=10))
+        chosen = d.draw(integers(min_value=0, max_value=count - 1))
+        _schema = [
+            ("int%d" % i, Integer(expl=tag_ctxc(i + 1)))
+            for i in range(count)
+        ]
+        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 self.assertRaisesRegex(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):
+    def test_1(self):
+        self.assertSequenceEqual(
+            VisibleString("Jones").encode(),
+            hexdec("1A054A6F6E6573"),
+        )
+
+    def test_2(self):
+        self.assertSequenceEqual(
+            VisibleString(
+                "Jones",
+                impl=tag_encode(3, klass=TagClassApplication),
+            ).encode(),
+            hexdec("43054A6F6E6573"),
+        )
+
+    def test_3(self):
+        self.assertSequenceEqual(
+            Any(
+                VisibleString(
+                    "Jones",
+                    impl=tag_encode(3, klass=TagClassApplication),
+                ),
+                expl=tag_ctxc(2),
+            ).encode(),
+            hexdec("A20743054A6F6E6573"),
+        )
+
+    def test_4(self):
+        self.assertSequenceEqual(
+            OctetString(
+                VisibleString(
+                    "Jones",
+                    impl=tag_encode(3, klass=TagClassApplication),
+                ).encode(),
+                impl=tag_encode(7, form=TagFormConstructed, klass=TagClassApplication),
+            ).encode(),
+            hexdec("670743054A6F6E6573"),
+        )
+
+    def test_5(self):
+        self.assertSequenceEqual(
+            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 self.assertRaisesRegex(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 self.assertRaisesRegex(ValueError, "different PyDERASN version"):
+            pickle_loads(pickled)
+        pyderasn.__version__ = version_orig
+        pickle_loads(pickled)
+
+
+class TestCERSetOrdering(TestCase):
+    def test_vectors(self):
+        """Taken from X.690-201508
+        """
+        class B(Choice):
+            schema = (
+                ("c", Integer(impl=tag_ctxp(2))),
+                ("d", Integer(impl=tag_ctxp(4))),
+            )
+
+        class F(Choice):
+            schema = (
+                ("g", Integer(impl=tag_ctxp(5))),
+                ("h", Integer(impl=tag_ctxp(6))),
+            )
+
+        class I(Choice):
+            schema = (
+                ("j", Integer(impl=tag_ctxp(0))),
+            )
+
+        class E(Choice):
+            schema = (
+                ("f", F()),
+                ("i", I()),
+            )
+
+        class A(Set):
+            schema = (
+                ("a", Integer(impl=tag_ctxp(3))),
+                ("b", B(expl=tag_ctxc(1))),
+                ("e", E()),
+            )
+
+        a = A((
+            ("a", Integer(123)),
+            ("b", B(("d", Integer(234)))),
+            ("e", E(("f", F(("g", Integer(345)))))),
+        ))
+        order = sorted(a._values_for_encoding(), key=attrgetter("tag_order_cer"))
+        self.assertSequenceEqual(
+            [i.__class__.__name__ for i in order],
+            ("E", "B", "Integer"),
+        )