]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
OID test vector from Go
[pyderasn.git] / tests / test_pyderasn.py
index 3161102997daf57449b26c20194b1081613f8b65..73eea21c6151a8ac3979f103eb42c3b7632cf0b5 100644 (file)
@@ -1,5 +1,5 @@
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
+# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
 # Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
@@ -20,7 +20,10 @@ from copy import deepcopy
 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 digits
@@ -30,6 +33,7 @@ from time import mktime
 from time import time
 from unittest import TestCase
 
+from dateutil.tz import UTC
 from hypothesis import assume
 from hypothesis import given
 from hypothesis import settings
@@ -58,6 +62,7 @@ from six import iterbytes
 from six import PY2
 from six import text_type
 from six import unichr as six_unichr
+from six.moves import xrange as six_xrange
 from six.moves.cPickle import dumps as pickle_dumps
 from six.moves.cPickle import HIGHEST_PROTOCOL as pickle_proto
 from six.moves.cPickle import loads as pickle_loads
@@ -72,6 +77,8 @@ from pyderasn import BoundsError
 from pyderasn import Choice
 from pyderasn import DecodeError
 from pyderasn import DecodePathDefBy
+from pyderasn import encode2pass
+from pyderasn import encode_cer
 from pyderasn import Enumerated
 from pyderasn import EOC
 from pyderasn import EOC_LEN
@@ -270,6 +277,19 @@ class TestTagCoder(TestCase):
         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 assertRaisesRegex(self, 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 = int2byte(klass | form | 31) + int2byte(num)
+        with assertRaisesRegex(self, DecodeError, "unexpected long form"):
+            tag_strip(raw)
+
 
 class TestLenCoder(TestCase):
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
@@ -345,6 +365,9 @@ class CommonMixin(object):
         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(min_size=1))
     def test_expl_inherited(self, expl_tag):
@@ -353,6 +376,9 @@ class CommonMixin(object):
         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)
@@ -363,6 +389,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)
+        if obj.ready:
+            self.assertEqual(obj.tag_order, obj_copied.tag_order)
 
 
 @composite
@@ -410,6 +438,8 @@ class TestBoolean(CommonMixin, TestCase):
         pprint(obj, big_blobs=True, with_decode_path=True)
         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)
@@ -494,6 +524,8 @@ class TestBoolean(CommonMixin, TestCase):
         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(),
@@ -503,6 +535,8 @@ class TestBoolean(CommonMixin, TestCase):
         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),
@@ -575,8 +609,9 @@ class TestBoolean(CommonMixin, TestCase):
         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, tail_junk):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (Boolean, BooleanInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -590,11 +625,19 @@ class TestBoolean(CommonMixin, TestCase):
             pprint(obj, big_blobs=True, with_decode_path=True)
             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)
             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(
@@ -631,6 +674,21 @@ class TestBoolean(CommonMixin, TestCase):
                 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))
     def test_invalid_len(self, l):
         with self.assertRaises(InvalidLength):
@@ -648,14 +706,13 @@ class TestBoolean(CommonMixin, TestCase):
                 len_encode(1),
                 int2byte(value),
             )))
-        obj, _ = Boolean().decode(
-            b"".join((
-                Boolean.tag_default,
-                len_encode(1),
-                int2byte(value),
-            )),
-            ctx={"bered": True},
-        )
+        encoded = b"".join((
+            Boolean.tag_default,
+            len_encode(1),
+            int2byte(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)
@@ -717,6 +774,7 @@ class TestBoolean(CommonMixin, TestCase):
         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(
@@ -828,6 +886,8 @@ class TestInteger(CommonMixin, TestCase):
         pprint(obj, big_blobs=True, with_decode_path=True)
         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)
@@ -890,6 +950,10 @@ class TestInteger(CommonMixin, TestCase):
                 Integer(values[0]).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, 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)
@@ -898,6 +962,10 @@ class TestInteger(CommonMixin, TestCase):
                 Integer(values[2]).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            Integer(bounds=(values[0], values[1])).decode(
+                encode2pass(Integer(values[2]))
+            )
 
     @given(data_strategy())
     def test_call(self, d):
@@ -1073,8 +1141,9 @@ class TestInteger(CommonMixin, TestCase):
         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, tail_junk):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (Integer, IntegerInherited):
             _, _, _, _, default, optional, _, _decoded = values
             obj = klass(
@@ -1088,12 +1157,20 @@ class TestInteger(CommonMixin, TestCase):
             pprint(obj, big_blobs=True, with_decode_path=True)
             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)
             list(obj_expled.pps())
             pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            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,
@@ -1129,6 +1206,21 @@ class TestInteger(CommonMixin, TestCase):
                 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 ((
                 (b"\x00", 0),
@@ -1304,6 +1396,8 @@ class TestBitString(CommonMixin, TestCase):
         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)
@@ -1467,6 +1561,7 @@ class TestBitString(CommonMixin, TestCase):
         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))
+        decode_path = d.draw(decode_path_strat)
         for klass in (BitString, BitStringInherited):
             class BS(klass):
                 schema = _schema
@@ -1481,12 +1576,20 @@ class TestBitString(CommonMixin, TestCase):
             pprint(obj, big_blobs=True, with_decode_path=True)
             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)
             list(obj_expled.pps())
             pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            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,
@@ -1526,6 +1629,20 @@ class TestBitString(CommonMixin, TestCase):
                 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):
         with self.assertRaises(DecodeError):
@@ -1572,6 +1689,7 @@ class TestBitString(CommonMixin, TestCase):
         self.assertTrue(obj[9])
         self.assertFalse(obj[17])
 
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
         integers(min_value=1, max_value=30),
         lists(
@@ -1588,8 +1706,9 @@ class TestBitString(CommonMixin, TestCase):
         ),
         lists(booleans(), min_size=1),
         binary(),
+        decode_path_strat,
     )
-    def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk):
+    def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk, decode_path):
         def chunk_constructed(contents):
             return (
                 tag_encode(form=TagFormConstructed, num=3) +
@@ -1598,6 +1717,7 @@ class TestBitString(CommonMixin, TestCase):
                 EOC
             )
         chunks = []
+        chunks_len_expected = []
         payload_expected = b""
         bit_len_expected = 0
         for chunk_input in chunk_inputs:
@@ -1605,14 +1725,19 @@ class TestBitString(CommonMixin, TestCase):
                 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 = (
@@ -1653,6 +1778,16 @@ class TestBitString(CommonMixin, TestCase):
             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,
@@ -1788,6 +1923,24 @@ class TestBitString(CommonMixin, TestCase):
         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):
@@ -1849,6 +2002,8 @@ class TestOctetString(CommonMixin, TestCase):
         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)
@@ -1895,6 +2050,10 @@ class TestOctetString(CommonMixin, TestCase):
                 OctetString(value).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, 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))
@@ -1904,6 +2063,10 @@ class TestOctetString(CommonMixin, TestCase):
                 OctetString(value).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, DecodeError, "bounds") as err:
+            OctetString(bounds=(bound_min, bound_max)).decode(
+                encode2pass(OctetString(value))
+            )
 
     @given(data_strategy())
     def test_call(self, d):
@@ -2064,8 +2227,9 @@ class TestOctetString(CommonMixin, TestCase):
         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, tail_junk):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (OctetString, OctetStringInherited):
             _, _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -2079,12 +2243,20 @@ class TestOctetString(CommonMixin, TestCase):
             pprint(obj, big_blobs=True, with_decode_path=True)
             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)
             list(obj_expled.pps())
             pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            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,
@@ -2120,6 +2292,21 @@ class TestOctetString(CommonMixin, TestCase):
                 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(
@@ -2135,8 +2322,9 @@ class TestOctetString(CommonMixin, TestCase):
             max_size=3,
         ),
         binary(),
+        decode_path_strat,
     )
-    def test_constructed(self, impl, chunk_inputs, junk):
+    def test_constructed(self, impl, chunk_inputs, junk, decode_path):
         def chunk_constructed(contents):
             return (
                 tag_encode(form=TagFormConstructed, num=4) +
@@ -2145,15 +2333,20 @@ class TestOctetString(CommonMixin, TestCase):
                 EOC
             )
         chunks = []
+        chunks_len_expected = []
         payload_expected = b""
         for chunk_input in chunk_inputs:
             if isinstance(chunk_input, binary_type):
                 chunks.append(OctetString(chunk_input).encode())
                 payload_expected += chunk_input
+                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 +
@@ -2189,6 +2382,16 @@ class TestOctetString(CommonMixin, TestCase):
             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,
@@ -2244,6 +2447,24 @@ class TestOctetString(CommonMixin, TestCase):
         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
 def null_values_strategy(draw, do_expl=False):
@@ -2382,8 +2603,9 @@ class TestNull(CommonMixin, TestCase):
         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, tail_junk):
+    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)
@@ -2392,12 +2614,20 @@ class TestNull(CommonMixin, TestCase):
             pprint(obj, big_blobs=True, with_decode_path=True)
             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)
             list(obj_expled.pps())
             pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            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,
@@ -2431,6 +2661,21 @@ class TestNull(CommonMixin, TestCase):
                 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):
         with self.assertRaises(InvalidLength):
@@ -2498,6 +2743,8 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         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)
         self.assertFalse(obj.ber_encoded)
@@ -2719,8 +2966,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         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, tail_junk):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -2734,12 +2982,20 @@ class TestObjectIdentifier(CommonMixin, TestCase):
             pprint(obj, big_blobs=True, with_decode_path=True)
             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)
             list(obj_expled.pps())
             pprint(obj_expled, big_blobs=True, with_decode_path=True)
             obj_expled_encoded = obj_expled.encode()
+            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,
@@ -2775,6 +3031,21 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 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),
         oid_strategy().map(ObjectIdentifier),
@@ -2811,6 +3082,10 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 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],
@@ -3092,6 +3367,7 @@ class TestEnumerated(CommonMixin, TestCase):
         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
@@ -3106,6 +3382,7 @@ class TestEnumerated(CommonMixin, TestCase):
         pprint(obj, big_blobs=True, with_decode_path=True)
         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)
@@ -3147,6 +3424,21 @@ class TestEnumerated(CommonMixin, TestCase):
             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
 def string_values_strategy(draw, alphabet, do_expl=False):
@@ -3188,9 +3480,7 @@ class StringMixin(object):
         repr(err.exception)
 
     def text_alphabet(self):
-        if self.base_klass.encoding in ("ascii", "iso-8859-1"):
-            return printable + whitespace
-        return None
+        return "".join(six_unichr(c) for c in six_xrange(256))
 
     @given(booleans())
     def test_optional(self, optional):
@@ -3208,6 +3498,8 @@ class StringMixin(object):
         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)
@@ -3257,6 +3549,10 @@ class StringMixin(object):
                 self.base_klass(value).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, 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))
@@ -3266,6 +3562,10 @@ class StringMixin(object):
                 self.base_klass(value).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, 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):
@@ -3428,6 +3728,7 @@ class StringMixin(object):
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
         tail_junk = d.draw(binary(max_size=5))
+        decode_path = d.draw(decode_path_strat)
         _, _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
@@ -3440,6 +3741,7 @@ class StringMixin(object):
         pprint(obj, big_blobs=True, with_decode_path=True)
         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)
@@ -3483,9 +3785,21 @@ class StringMixin(object):
             tail_junk,
         )
 
-
-class TestUTF8String(StringMixin, CommonMixin, TestCase):
-    base_klass = UTF8String
+        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(
@@ -3495,6 +3809,26 @@ cyrillic_letters = text(
 )
 
 
+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):
@@ -3510,7 +3844,7 @@ class TestNumericString(StringMixin, CommonMixin, TestCase):
 
     @given(text(alphabet=ascii_letters, min_size=1, max_size=5))
     def test_non_numeric(self, non_numeric_text):
-        with assertRaisesRegex(self, DecodeError, "non-numeric"):
+        with assertRaisesRegex(self, DecodeError, "alphabet value"):
             self.base_klass(non_numeric_text)
 
     @given(
@@ -3533,6 +3867,19 @@ class TestNumericString(StringMixin, CommonMixin, TestCase):
         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(
         UnicodeDecodeErrorMixin,
@@ -3547,7 +3894,7 @@ class TestPrintableString(
 
     @given(text(alphabet=sorted(set(whitespace) - set(" ")), min_size=1, max_size=5))
     def test_non_printable(self, non_printable_text):
-        with assertRaisesRegex(self, DecodeError, "non-printable"):
+        with assertRaisesRegex(self, DecodeError, "alphabet value"):
             self.base_klass(non_printable_text)
 
     @given(
@@ -3581,7 +3928,7 @@ class TestPrintableString(
             for prop in kwargs.keys():
                 self.assertFalse(getattr(obj, prop))
             s += c
-            with assertRaisesRegex(self, DecodeError, "non-printable"):
+            with assertRaisesRegex(self, DecodeError, "alphabet value"):
                 self.base_klass(s)
             self.base_klass(s, **kwargs)
             klass = self.base_klass(**kwargs)
@@ -3620,6 +3967,18 @@ class TestIA5String(
 ):
     base_klass = IA5String
 
+    def text_alphabet(self):
+        return "".join(six_unichr(c) for c in six_xrange(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(
         UnicodeDecodeErrorMixin,
@@ -3638,6 +3997,9 @@ class TestVisibleString(
 ):
     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"")
@@ -3674,6 +4036,38 @@ class TestVisibleString(
         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(
         UnicodeDecodeErrorMixin,
@@ -3751,6 +4145,8 @@ class TimeMixin(object):
         with self.assertRaises(ObjNotReady) as err:
             obj.encode()
         repr(err.exception)
+        with self.assertRaises(ObjNotReady) as err:
+            encode2pass(obj)
         value = d.draw(datetimes(
             min_value=self.min_datetime,
             max_value=self.max_datetime,
@@ -3909,6 +4305,7 @@ class TimeMixin(object):
         pprint(obj, big_blobs=True, with_decode_path=True)
         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)
@@ -3956,6 +4353,7 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
     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:
@@ -4101,8 +4499,13 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
                 mktime(obj.todatetime().timetuple()),
                 mktime(dt.timetuple()),
             )
-        elif not PY2:
-            self.assertEqual(obj.todatetime().timestamp(), dt.timestamp())
+        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)
@@ -4334,12 +4737,17 @@ class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
             with self.assertRaises(DecodeError):
                 GeneralizedTime(data)
 
+    def test_aware(self):
+        with assertRaisesRegex(self, 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)
+    evgen_mode_skip_value = False
 
     def additional_symmetric_check(self, value, obj_encoded):
         pass
@@ -4666,10 +5074,21 @@ class TestUTCTime(TimeMixin, CommonMixin, TestCase):
                 junk
             )
 
+    def test_aware(self):
+        with assertRaisesRegex(self, ValueError, "only naive"):
+            UTCTime(datetime(2000, 1, 1, 1, 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):
-    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)))
@@ -4699,7 +5118,7 @@ class TestAny(CommonMixin, TestCase):
         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)
@@ -4709,6 +5128,8 @@ class TestAny(CommonMixin, TestCase):
         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)
@@ -4733,7 +5154,7 @@ class TestAny(CommonMixin, TestCase):
             pprint(obj, big_blobs=True, with_decode_path=True)
             self.assertSequenceEqual(obj.encode(), integer_encoded)
 
-    @given(binary(min_size=1), binary(min_size=1))
+    @given(tlv_value_strategy(), tlv_value_strategy())
     def test_comparison(self, value1, value2):
         for klass in (Any, AnyInherited):
             obj1 = klass(value1)
@@ -4797,7 +5218,7 @@ class TestAny(CommonMixin, TestCase):
             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):
@@ -4844,8 +5265,9 @@ class TestAny(CommonMixin, TestCase):
         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, tail_junk):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         for klass in (Any, AnyInherited):
             _, _, optional, _decoded = values
             obj = klass(value=value, optional=optional, _decoded=_decoded)
@@ -4853,9 +5275,14 @@ class TestAny(CommonMixin, TestCase):
             list(obj.pps())
             pprint(obj, big_blobs=True, with_decode_path=True)
             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()
+            self.assertEqual(encode2pass(obj), obj_encoded)
             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)
             list(obj_expled.pps())
             pprint(obj_expled, big_blobs=True, with_decode_path=True)
@@ -4897,6 +5324,20 @@ class TestAny(CommonMixin, TestCase):
                 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),
@@ -5074,6 +5515,8 @@ class TestChoice(CommonMixin, TestCase):
         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)
@@ -5206,6 +5649,7 @@ class TestChoice(CommonMixin, TestCase):
         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
@@ -5219,9 +5663,13 @@ class TestChoice(CommonMixin, TestCase):
         list(obj.pps())
         pprint(obj, big_blobs=True, with_decode_path=True)
         self.assertFalse(obj.expled)
+        self.assertEqual(obj.tag_order, obj.value.tag_order)
         obj_encoded = obj.encode()
+        self.assertEqual(encode2pass(obj), obj_encoded)
         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)
         list(obj_expled.pps())
         pprint(obj_expled, big_blobs=True, with_decode_path=True)
@@ -5269,6 +5717,22 @@ class TestChoice(CommonMixin, TestCase):
             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):
         class Wahl(Choice):
@@ -5549,6 +6013,8 @@ class SeqMixing(object):
         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)
@@ -5652,8 +6118,9 @@ class SeqMixing(object):
         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)
@@ -5727,6 +6194,7 @@ class SeqMixing(object):
     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)
@@ -5735,6 +6203,13 @@ class SeqMixing(object):
         pprint(seq, big_blobs=True, with_decode_path=True)
         self.assertTrue(seq.ready)
         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)
@@ -5790,6 +6265,21 @@ class SeqMixing(object):
                     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}),
@@ -5802,6 +6292,7 @@ class SeqMixing(object):
         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)
@@ -5859,24 +6350,34 @@ class SeqMixing(object):
             max_size=len(_schema),
         ))]
 
+        class Wahl(Choice):
+            schema = (("int", Integer()),)
+
         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:
-            seq_without_default[name] = Integer(value)
+            seq_without_default[name] = Wahl(("int", Integer(value)))
         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 = [
-                (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()
         with assertRaisesRegex(self, DecodeError, "DEFAULT value met"):
             seq_with_default.decode(seq_encoded)
+        with assertRaisesRegex(self, 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)
@@ -5886,7 +6387,21 @@ class SeqMixing(object):
             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)
+                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:
+            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):
@@ -5911,6 +6426,8 @@ class SeqMixing(object):
         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):
@@ -5937,6 +6454,9 @@ class SeqMixing(object):
         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)
@@ -5999,42 +6519,44 @@ class TestSet(SeqMixing, CommonMixin, TestCase):
     @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):
-            schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)]
+            schema = d.draw(permutations(_schema))
         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_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:],
-            b"".join(sorted([seq[name].encode() for name, _ in Seq.schema])),
+            b"".join(seq_encoded_expected),
         )
 
-    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
-    @given(data_strategy())
-    def test_unsorted(self, d):
-        tags = [
-            tag_encode(tag) for tag in
-            d.draw(sets(integers(min_value=1), min_size=2, max_size=5))
-        ]
-        tags = d.draw(permutations(tags))
-        assume(tags != sorted(tags))
-        encoded = b"".join(OctetString(t, impl=t).encode() for t in tags)
+        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,
         ))
-
-        class Seq(Set):
-            schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)]
-        seq = Seq()
         with assertRaisesRegex(self, DecodeError, "unordered SET"):
             seq.decode(seq_encoded)
         for ctx in ({"bered": True}, {"allow_unordered_set": True}):
@@ -6044,10 +6566,6 @@ class TestSet(SeqMixing, CommonMixin, TestCase):
             seq_decoded = copy(seq_decoded)
             self.assertTrue(seq_decoded.ber_encoded)
             self.assertTrue(seq_decoded.bered)
-            self.assertSequenceEqual(
-                [bytes(seq_decoded[str(i)]) for i, t in enumerate(tags)],
-                tags,
-            )
 
     def test_same_value_twice(self):
         class Seq(Set):
@@ -6176,6 +6694,8 @@ class SeqOfMixing(object):
         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:
@@ -6219,6 +6739,10 @@ class SeqOfMixing(object):
                 SeqOf(value).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, 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,
@@ -6231,6 +6755,10 @@ class SeqOfMixing(object):
                 SeqOf(value).encode()
             )
         repr(err.exception)
+        with assertRaisesRegex(self, 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):
@@ -6419,8 +6947,9 @@ class SeqOfMixing(object):
         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, tail_junk):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
         _, _, _, _, _, default, optional, _decoded = values
 
         class SeqOf(self.base_klass):
@@ -6436,6 +6965,13 @@ class SeqOfMixing(object):
         pprint(obj, big_blobs=True, with_decode_path=True)
         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)
@@ -6502,6 +7038,21 @@ class SeqOfMixing(object):
         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),
@@ -6556,6 +7107,80 @@ class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase):
         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(six_xrange(10)))
+        with assertRaisesRegex(self, 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 six_xrange(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 six_xrange(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 six_xrange(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 SeqOf(SetOf):
@@ -7067,6 +7692,7 @@ class TestDefinesByPath(TestCase):
 
     def test_remaining_data(self):
         oid = ObjectIdentifier("1.2.3")
+
         class Seq(Sequence):
             schema = (
                 ("oid", ObjectIdentifier(defines=((("tgt",), {
@@ -7084,6 +7710,7 @@ class TestDefinesByPath(TestCase):
 
     def test_remaining_data_seqof(self):
         oid = ObjectIdentifier("1.2.3")
+
         class SeqOf(SetOf):
             schema = OctetString()
 
@@ -7170,11 +7797,13 @@ class TestStrictDefaultExistence(TestCase):
 
 
 class TestX690PrefixedType(TestCase):
-    def runTest(self):
+    def test_1(self):
         self.assertSequenceEqual(
             VisibleString("Jones").encode(),
             hexdec("1A054A6F6E6573"),
         )
+
+    def test_2(self):
         self.assertSequenceEqual(
             VisibleString(
                 "Jones",
@@ -7182,6 +7811,8 @@ class TestX690PrefixedType(TestCase):
             ).encode(),
             hexdec("43054A6F6E6573"),
         )
+
+    def test_3(self):
         self.assertSequenceEqual(
             Any(
                 VisibleString(
@@ -7192,6 +7823,8 @@ class TestX690PrefixedType(TestCase):
             ).encode(),
             hexdec("A20743054A6F6E6573"),
         )
+
+    def test_4(self):
         self.assertSequenceEqual(
             OctetString(
                 VisibleString(
@@ -7202,6 +7835,8 @@ class TestX690PrefixedType(TestCase):
             ).encode(),
             hexdec("670743054A6F6E6573"),
         )
+
+    def test_5(self):
         self.assertSequenceEqual(
             VisibleString("Jones", impl=tag_ctxp(2)).encode(),
             hexdec("82054A6F6E6573"),
@@ -7228,3 +7863,49 @@ class TestPickleDifferentVersion(TestCase):
             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"),
+        )