]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Use BasicState for code reducing
[pyderasn.git] / pyderasn.py
index 0d2d3f5a2982d27fbe49239139c7a01919469104..39d2f2f3fe4260193b5f96959b9c47325b828318 100755 (executable)
@@ -75,9 +75,17 @@ tags simultaneously.
 There are :py:func:`pyderasn.tag_ctxp` and :py:func:`pyderasn.tag_ctxc`
 functions, allowing you to easily create ``CONTEXT``
 ``PRIMITIVE``/``CONSTRUCTED`` tags, by specifying only the required tag
-number. Pay attention that explicit tags always have *constructed* tag
-(``tag_ctxc``), but implicit tags for primitive types are primitive
-(``tag_ctxp``).
+number.
+
+.. note::
+
+   EXPLICIT tags always have **constructed** tag. PyDERASN does not
+   explicitly check correctness of schema input here.
+
+.. note::
+
+   Implicit tags have **primitive** (``tag_ctxp``) encoding for
+   primitive values.
 
 ::
 
@@ -420,7 +428,7 @@ defines_by_path context option
 ______________________________
 
 Sometimes you either can not or do not want to explicitly set *defines*
-in the scheme. You can dynamically apply those definitions when calling
+in the schema. You can dynamically apply those definitions when calling
 ``.decode()`` method.
 
 Specify ``defines_by_path`` key in the :ref:`decode context <ctx>`. Its
@@ -663,9 +671,9 @@ from copy import copy
 from datetime import datetime
 from datetime import timedelta
 from math import ceil
-from os import environ
 from string import ascii_letters
 from string import digits
+from sys import version_info
 from unicodedata import category as unicat
 
 from six import add_metaclass
@@ -690,7 +698,7 @@ except ImportError:  # pragma: no cover
     def colored(what, *args, **kwargs):
         return what
 
-__version__ = "6.2"
+__version__ = "6.3"
 
 __all__ = (
     "Any",
@@ -764,7 +772,7 @@ EOC = b"\x00\x00"
 EOC_LEN = len(EOC)
 LENINDEF = b"\x80"  # length indefinite mark
 LENINDEF_PP_CHAR = "I" if PY2 else "∞"
-NAMEDTUPLE_KWARGS = {} if PY2 else {"module": __name__}
+NAMEDTUPLE_KWARGS = {} if version_info < (3, 6) else {"module": __name__}
 SET01 = frozenset("01")
 DECIMALS = frozenset(digits)
 DECIMAL_SIGNS = ".,"
@@ -1069,6 +1077,21 @@ class AutoAddSlots(type):
         return type.__new__(cls, name, bases, _dict)
 
 
+BasicState = namedtuple("BasicState", (
+    "version",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
+
+
 @add_metaclass(AutoAddSlots)
 class Obj(object):
     """Common ASN.1 object class
@@ -1141,17 +1164,16 @@ class Obj(object):
     def __setstate__(self, state):
         if state.version != __version__:
             raise ValueError("data is pickled by different PyDERASN version")
-        self.tag = self.tag_default
-        self._value = None
-        self._expl = None
-        self.default = None
-        self.optional = False
-        self.offset = 0
-        self.llen = 0
-        self.vlen = 0
-        self.expl_lenindef = False
-        self.lenindef = False
-        self.ber_encoded = False
+        self.tag = state.tag
+        self._expl = state.expl
+        self.default = state.default
+        self.optional = state.optional
+        self.offset = state.offset
+        self.llen = state.llen
+        self.vlen = state.vlen
+        self.expl_lenindef = state.expl_lenindef
+        self.lenindef = state.lenindef
+        self.ber_encoded = state.ber_encoded
 
     @property
     def tlen(self):
@@ -1220,7 +1242,7 @@ class Obj(object):
         :param ctx: optional :ref:`context <ctx>` governing decoding process
         :param tag_only: decode only the tag, without length and contents
                          (used only in Choice and Set structures, trying to
-                         determine if tag satisfies the scheme)
+                         determine if tag satisfies the schema)
         :param _ctx_immutable: do we need to ``copy.copy()`` ``ctx``
                                before using it?
         :returns: (Obj, remaining data)
@@ -1741,20 +1763,11 @@ def pprint(
 # ASN.1 primitive types
 ########################################################################
 
-BooleanState = namedtuple("BooleanState", (
-    "version",
-    "value",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-), **NAMEDTUPLE_KWARGS)
+BooleanState = namedtuple(
+    "BooleanState",
+    BasicState._fields + ("value",),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class Boolean(Obj):
@@ -1814,7 +1827,6 @@ class Boolean(Obj):
     def __getstate__(self):
         return BooleanState(
             __version__,
-            self._value,
             self.tag,
             self._expl,
             self.default,
@@ -1825,21 +1837,12 @@ class Boolean(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self._value,
         )
 
     def __setstate__(self, state):
         super(Boolean, self).__setstate__(state)
         self._value = state.value
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
 
     def __nonzero__(self):
         self._assert_ready()
@@ -1982,23 +1985,11 @@ class Boolean(Obj):
             yield pp
 
 
-IntegerState = namedtuple("IntegerState", (
-    "version",
-    "specs",
-    "value",
-    "bound_min",
-    "bound_max",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-), **NAMEDTUPLE_KWARGS)
+IntegerState = namedtuple(
+    "IntegerState",
+    BasicState._fields + ("specs", "value", "bound_min", "bound_max"),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class Integer(Obj):
@@ -2105,10 +2096,6 @@ class Integer(Obj):
     def __getstate__(self):
         return IntegerState(
             __version__,
-            self.specs,
-            self._value,
-            self._bound_min,
-            self._bound_max,
             self.tag,
             self._expl,
             self.default,
@@ -2119,6 +2106,10 @@ class Integer(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self.specs,
+            self._value,
+            self._bound_min,
+            self._bound_max,
         )
 
     def __setstate__(self, state):
@@ -2127,16 +2118,6 @@ class Integer(Obj):
         self._value = state.value
         self._bound_min = state.bound_min
         self._bound_max = state.bound_max
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
 
     def __int__(self):
         self._assert_ready()
@@ -2351,23 +2332,11 @@ class Integer(Obj):
             yield pp
 
 
-BitStringState = namedtuple("BitStringState", (
-    "version",
-    "specs",
-    "value",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-    "tag_constructed",
-    "defined",
-), **NAMEDTUPLE_KWARGS)
+BitStringState = namedtuple(
+    "BitStringState",
+    BasicState._fields + ("specs", "value", "tag_constructed", "defined"),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class BitString(Obj):
@@ -2525,8 +2494,6 @@ class BitString(Obj):
     def __getstate__(self):
         return BitStringState(
             __version__,
-            self.specs,
-            self._value,
             self.tag,
             self._expl,
             self.default,
@@ -2537,6 +2504,8 @@ class BitString(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self.specs,
+            self._value,
             self.tag_constructed,
             self.defined,
         )
@@ -2545,16 +2514,6 @@ class BitString(Obj):
         super(BitString, self).__setstate__(state)
         self.specs = state.specs
         self._value = state.value
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
         self.tag_constructed = state.tag_constructed
         self.defined = state.defined
 
@@ -2630,64 +2589,6 @@ class BitString(Obj):
             octets,
         ))
 
-    def _decode_chunk(self, lv, offset, decode_path):
-        try:
-            l, llen, v = len_decode(lv)
-        except DecodeError as err:
-            raise err.__class__(
-                msg=err.msg,
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if l > len(v):
-            raise NotEnoughData(
-                "encoded length is longer than data",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if l == 0:
-            raise NotEnoughData(
-                "zero length",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        pad_size = byte2int(v)
-        if l == 1 and pad_size != 0:
-            raise DecodeError(
-                "invalid empty value",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if pad_size > 7:
-            raise DecodeError(
-                "too big pad",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if byte2int(v[l - 1:l]) & ((1 << pad_size) - 1) != 0:
-            raise DecodeError(
-                "invalid pad",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        v, tail = v[:l], v[l:]
-        obj = self.__class__(
-            value=((len(v) - 1) * 8 - pad_size, v[1:].tobytes()),
-            impl=self.tag,
-            expl=self._expl,
-            default=self.default,
-            optional=self.optional,
-            _specs=self.specs,
-            _decoded=(offset, llen, l),
-        )
-        return obj, tail
-
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
@@ -2701,23 +2602,8 @@ class BitString(Obj):
         if t == self.tag:
             if tag_only:  # pragma: no cover
                 return None
-            return self._decode_chunk(lv, offset, decode_path)
-        if t == self.tag_constructed:
-            if not ctx.get("bered", False):
-                raise DecodeError(
-                    "unallowed BER constructed encoding",
-                    klass=self.__class__,
-                    decode_path=decode_path,
-                    offset=offset,
-                )
-            if tag_only:  # pragma: no cover
-                return None
-            lenindef = False
             try:
                 l, llen, v = len_decode(lv)
-            except LenIndefForm:
-                llen, l, v = 1, 0, lv[1:]
-                lenindef = True
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -2732,90 +2618,160 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if not lenindef and l == 0:
+            if l == 0:
                 raise NotEnoughData(
                     "zero length",
                     klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
-            chunks = []
-            sub_offset = offset + tlen + llen
-            vlen = 0
-            while True:
-                if lenindef:
-                    if v[:EOC_LEN].tobytes() == EOC:
-                        break
-                else:
-                    if vlen == l:
-                        break
-                    if vlen > l:
-                        raise DecodeError(
-                            "chunk out of bounds",
-                            klass=self.__class__,
-                            decode_path=decode_path + (str(len(chunks) - 1),),
-                            offset=chunks[-1].offset,
-                        )
-                sub_decode_path = decode_path + (str(len(chunks)),)
-                try:
-                    chunk, v_tail = BitString().decode(
-                        v,
-                        offset=sub_offset,
-                        decode_path=sub_decode_path,
-                        leavemm=True,
-                        ctx=ctx,
-                        _ctx_immutable=False,
-                    )
-                except TagMismatch:
-                    raise DecodeError(
-                        "expected BitString encoded chunk",
-                        klass=self.__class__,
-                        decode_path=sub_decode_path,
-                        offset=sub_offset,
-                    )
-                chunks.append(chunk)
-                sub_offset += chunk.tlvlen
-                vlen += chunk.tlvlen
-                v = v_tail
-            if len(chunks) == 0:
+            pad_size = byte2int(v)
+            if l == 1 and pad_size != 0:
                 raise DecodeError(
-                    "no chunks",
+                    "invalid empty value",
                     klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
-            values = []
-            bit_len = 0
-            for chunk_i, chunk in enumerate(chunks[:-1]):
-                if chunk.bit_len % 8 != 0:
-                    raise DecodeError(
-                        "BitString chunk is not multiple of 8 bits",
-                        klass=self.__class__,
-                        decode_path=decode_path + (str(chunk_i),),
-                        offset=chunk.offset,
-                    )
-                values.append(bytes(chunk))
-                bit_len += chunk.bit_len
-            chunk_last = chunks[-1]
-            values.append(bytes(chunk_last))
-            bit_len += chunk_last.bit_len
+            if pad_size > 7:
+                raise DecodeError(
+                    "too big pad",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if byte2int(v[l - 1:l]) & ((1 << pad_size) - 1) != 0:
+                raise DecodeError(
+                    "invalid pad",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            v, tail = v[:l], v[l:]
             obj = self.__class__(
-                value=(bit_len, b"".join(values)),
+                value=((len(v) - 1) * 8 - pad_size, v[1:].tobytes()),
                 impl=self.tag,
                 expl=self._expl,
                 default=self.default,
                 optional=self.optional,
                 _specs=self.specs,
-                _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+                _decoded=(offset, llen, l),
             )
-            obj.lenindef = lenindef
-            obj.ber_encoded = True
-            return obj, (v[EOC_LEN:] if lenindef else v)
-        raise TagMismatch(
-            klass=self.__class__,
-            decode_path=decode_path,
-            offset=offset,
+            return obj, tail
+        if t != self.tag_constructed:
+            raise TagMismatch(
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if not ctx.get("bered", False):
+            raise DecodeError(
+                "unallowed BER constructed encoding",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if tag_only:  # pragma: no cover
+            return None
+        lenindef = False
+        try:
+            l, llen, v = len_decode(lv)
+        except LenIndefForm:
+            llen, l, v = 1, 0, lv[1:]
+            lenindef = True
+        except DecodeError as err:
+            raise err.__class__(
+                msg=err.msg,
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if l > len(v):
+            raise NotEnoughData(
+                "encoded length is longer than data",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if not lenindef and l == 0:
+            raise NotEnoughData(
+                "zero length",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        chunks = []
+        sub_offset = offset + tlen + llen
+        vlen = 0
+        while True:
+            if lenindef:
+                if v[:EOC_LEN].tobytes() == EOC:
+                    break
+            else:
+                if vlen == l:
+                    break
+                if vlen > l:
+                    raise DecodeError(
+                        "chunk out of bounds",
+                        klass=self.__class__,
+                        decode_path=decode_path + (str(len(chunks) - 1),),
+                        offset=chunks[-1].offset,
+                    )
+            sub_decode_path = decode_path + (str(len(chunks)),)
+            try:
+                chunk, v_tail = BitString().decode(
+                    v,
+                    offset=sub_offset,
+                    decode_path=sub_decode_path,
+                    leavemm=True,
+                    ctx=ctx,
+                    _ctx_immutable=False,
+                )
+            except TagMismatch:
+                raise DecodeError(
+                    "expected BitString encoded chunk",
+                    klass=self.__class__,
+                    decode_path=sub_decode_path,
+                    offset=sub_offset,
+                )
+            chunks.append(chunk)
+            sub_offset += chunk.tlvlen
+            vlen += chunk.tlvlen
+            v = v_tail
+        if len(chunks) == 0:
+            raise DecodeError(
+                "no chunks",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        values = []
+        bit_len = 0
+        for chunk_i, chunk in enumerate(chunks[:-1]):
+            if chunk.bit_len % 8 != 0:
+                raise DecodeError(
+                    "BitString chunk is not multiple of 8 bits",
+                    klass=self.__class__,
+                    decode_path=decode_path + (str(chunk_i),),
+                    offset=chunk.offset,
+                )
+            values.append(bytes(chunk))
+            bit_len += chunk.bit_len
+        chunk_last = chunks[-1]
+        values.append(bytes(chunk_last))
+        bit_len += chunk_last.bit_len
+        obj = self.__class__(
+            value=(bit_len, b"".join(values)),
+            impl=self.tag,
+            expl=self._expl,
+            default=self.default,
+            optional=self.optional,
+            _specs=self.specs,
+            _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
+        obj.lenindef = lenindef
+        obj.ber_encoded = True
+        return obj, (v[EOC_LEN:] if lenindef else v)
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -2861,24 +2817,17 @@ class BitString(Obj):
             yield pp
 
 
-OctetStringState = namedtuple("OctetStringState", (
-    "version",
-    "value",
-    "bound_min",
-    "bound_max",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-    "tag_constructed",
-    "defined",
-), **NAMEDTUPLE_KWARGS)
+OctetStringState = namedtuple(
+    "OctetStringState",
+    BasicState._fields + (
+        "value",
+        "bound_min",
+        "bound_max",
+        "tag_constructed",
+        "defined",
+    ),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class OctetString(Obj):
@@ -2974,9 +2923,6 @@ class OctetString(Obj):
     def __getstate__(self):
         return OctetStringState(
             __version__,
-            self._value,
-            self._bound_min,
-            self._bound_max,
             self.tag,
             self._expl,
             self.default,
@@ -2987,6 +2933,9 @@ class OctetString(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self._value,
+            self._bound_min,
+            self._bound_max,
             self.tag_constructed,
             self.defined,
         )
@@ -2996,16 +2945,6 @@ class OctetString(Obj):
         self._value = state.value
         self._bound_min = state.bound_min
         self._bound_max = state.bound_max
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
         self.tag_constructed = state.tag_constructed
         self.defined = state.defined
 
@@ -3056,51 +2995,6 @@ class OctetString(Obj):
             self._value,
         ))
 
-    def _decode_chunk(self, lv, offset, decode_path, ctx):
-        try:
-            l, llen, v = len_decode(lv)
-        except DecodeError as err:
-            raise err.__class__(
-                msg=err.msg,
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if l > len(v):
-            raise NotEnoughData(
-                "encoded length is longer than data",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        v, tail = v[:l], v[l:]
-        try:
-            obj = self.__class__(
-                value=v.tobytes(),
-                bounds=(self._bound_min, self._bound_max),
-                impl=self.tag,
-                expl=self._expl,
-                default=self.default,
-                optional=self.optional,
-                _decoded=(offset, llen, l),
-                ctx=ctx,
-            )
-        except DecodeError as err:
-            raise DecodeError(
-                msg=err.msg,
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        except BoundsError as err:
-            raise DecodeError(
-                msg=str(err),
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        return obj, tail
-
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
@@ -3114,23 +3008,8 @@ class OctetString(Obj):
         if t == self.tag:
             if tag_only:
                 return None
-            return self._decode_chunk(lv, offset, decode_path, ctx)
-        if t == self.tag_constructed:
-            if not ctx.get("bered", False):
-                raise DecodeError(
-                    "unallowed BER constructed encoding",
-                    klass=self.__class__,
-                    decode_path=decode_path,
-                    offset=offset,
-                )
-            if tag_only:
-                return None
-            lenindef = False
             try:
                 l, llen, v = len_decode(lv)
-            except LenIndefForm:
-                llen, l, v = 1, 0, lv[1:]
-                lenindef = True
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -3145,77 +3024,134 @@ class OctetString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            chunks = []
-            sub_offset = offset + tlen + llen
-            vlen = 0
-            while True:
-                if lenindef:
-                    if v[:EOC_LEN].tobytes() == EOC:
-                        break
-                else:
-                    if vlen == l:
-                        break
-                    if vlen > l:
-                        raise DecodeError(
-                            "chunk out of bounds",
-                            klass=self.__class__,
-                            decode_path=decode_path + (str(len(chunks) - 1),),
-                            offset=chunks[-1].offset,
-                        )
-                sub_decode_path = decode_path + (str(len(chunks)),)
-                try:
-                    chunk, v_tail = OctetString().decode(
-                        v,
-                        offset=sub_offset,
-                        decode_path=sub_decode_path,
-                        leavemm=True,
-                        ctx=ctx,
-                        _ctx_immutable=False,
-                    )
-                except TagMismatch:
+            v, tail = v[:l], v[l:]
+            try:
+                obj = self.__class__(
+                    value=v.tobytes(),
+                    bounds=(self._bound_min, self._bound_max),
+                    impl=self.tag,
+                    expl=self._expl,
+                    default=self.default,
+                    optional=self.optional,
+                    _decoded=(offset, llen, l),
+                    ctx=ctx,
+                )
+            except DecodeError as err:
+                raise DecodeError(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            except BoundsError as err:
+                raise DecodeError(
+                    msg=str(err),
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            return obj, tail
+        if t != self.tag_constructed:
+            raise TagMismatch(
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if not ctx.get("bered", False):
+            raise DecodeError(
+                "unallowed BER constructed encoding",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if tag_only:
+            return None
+        lenindef = False
+        try:
+            l, llen, v = len_decode(lv)
+        except LenIndefForm:
+            llen, l, v = 1, 0, lv[1:]
+            lenindef = True
+        except DecodeError as err:
+            raise err.__class__(
+                msg=err.msg,
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if l > len(v):
+            raise NotEnoughData(
+                "encoded length is longer than data",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        chunks = []
+        sub_offset = offset + tlen + llen
+        vlen = 0
+        while True:
+            if lenindef:
+                if v[:EOC_LEN].tobytes() == EOC:
+                    break
+            else:
+                if vlen == l:
+                    break
+                if vlen > l:
                     raise DecodeError(
-                        "expected OctetString encoded chunk",
+                        "chunk out of bounds",
                         klass=self.__class__,
-                        decode_path=sub_decode_path,
-                        offset=sub_offset,
+                        decode_path=decode_path + (str(len(chunks) - 1),),
+                        offset=chunks[-1].offset,
                     )
-                chunks.append(chunk)
-                sub_offset += chunk.tlvlen
-                vlen += chunk.tlvlen
-                v = v_tail
+            sub_decode_path = decode_path + (str(len(chunks)),)
             try:
-                obj = self.__class__(
-                    value=b"".join(bytes(chunk) for chunk in chunks),
-                    bounds=(self._bound_min, self._bound_max),
-                    impl=self.tag,
-                    expl=self._expl,
-                    default=self.default,
-                    optional=self.optional,
-                    _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+                chunk, v_tail = OctetString().decode(
+                    v,
+                    offset=sub_offset,
+                    decode_path=sub_decode_path,
+                    leavemm=True,
                     ctx=ctx,
+                    _ctx_immutable=False,
                 )
-            except DecodeError as err:
-                raise DecodeError(
-                    msg=err.msg,
-                    klass=self.__class__,
-                    decode_path=decode_path,
-                    offset=offset,
-                )
-            except BoundsError as err:
+            except TagMismatch:
                 raise DecodeError(
-                    msg=str(err),
+                    "expected OctetString encoded chunk",
                     klass=self.__class__,
-                    decode_path=decode_path,
-                    offset=offset,
+                    decode_path=sub_decode_path,
+                    offset=sub_offset,
                 )
-            obj.lenindef = lenindef
-            obj.ber_encoded = True
-            return obj, (v[EOC_LEN:] if lenindef else v)
-        raise TagMismatch(
-            klass=self.__class__,
-            decode_path=decode_path,
-            offset=offset,
-        )
+            chunks.append(chunk)
+            sub_offset += chunk.tlvlen
+            vlen += chunk.tlvlen
+            v = v_tail
+        try:
+            obj = self.__class__(
+                value=b"".join(bytes(chunk) for chunk in chunks),
+                bounds=(self._bound_min, self._bound_max),
+                impl=self.tag,
+                expl=self._expl,
+                default=self.default,
+                optional=self.optional,
+                _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+                ctx=ctx,
+            )
+        except DecodeError as err:
+            raise DecodeError(
+                msg=err.msg,
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        except BoundsError as err:
+            raise DecodeError(
+                msg=str(err),
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        obj.lenindef = lenindef
+        obj.ber_encoded = True
+        return obj, (v[EOC_LEN:] if lenindef else v)
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -3254,19 +3190,7 @@ class OctetString(Obj):
             yield pp
 
 
-NullState = namedtuple("NullState", (
-    "version",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-), **NAMEDTUPLE_KWARGS)
+NullState = namedtuple("NullState", BasicState._fields, **NAMEDTUPLE_KWARGS)
 
 
 class Null(Obj):
@@ -3316,19 +3240,6 @@ class Null(Obj):
             self.ber_encoded,
         )
 
-    def __setstate__(self, state):
-        super(Null, self).__setstate__(state)
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
-
     def __eq__(self, their):
         if not issubclass(their.__class__, Null):
             return False
@@ -3422,21 +3333,11 @@ class Null(Obj):
             yield pp
 
 
-ObjectIdentifierState = namedtuple("ObjectIdentifierState", (
-    "version",
-    "value",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-    "defines",
-), **NAMEDTUPLE_KWARGS)
+ObjectIdentifierState = namedtuple(
+    "ObjectIdentifierState",
+    BasicState._fields + ("value", "defines"),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class ObjectIdentifier(Obj):
@@ -3542,7 +3443,6 @@ class ObjectIdentifier(Obj):
     def __getstate__(self):
         return ObjectIdentifierState(
             __version__,
-            self._value,
             self.tag,
             self._expl,
             self.default,
@@ -3553,22 +3453,13 @@ class ObjectIdentifier(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self._value,
             self.defines,
         )
 
     def __setstate__(self, state):
         super(ObjectIdentifier, self).__setstate__(state)
         self._value = state.value
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
         self.defines = state.defines
 
     def __iter__(self):
@@ -4573,21 +4464,11 @@ class BMPString(CommonString):
     asn1_type_name = "BMPString"
 
 
-ChoiceState = namedtuple("ChoiceState", (
-    "version",
-    "specs",
-    "value",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-), **NAMEDTUPLE_KWARGS)
+ChoiceState = namedtuple(
+    "ChoiceState",
+    BasicState._fields + ("specs", "value",),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class Choice(Obj):
@@ -4692,8 +4573,6 @@ class Choice(Obj):
     def __getstate__(self):
         return ChoiceState(
             __version__,
-            self.specs,
-            copy(self._value),
             self.tag,
             self._expl,
             self.default,
@@ -4704,21 +4583,14 @@ class Choice(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self.specs,
+            copy(self._value),
         )
 
     def __setstate__(self, state):
         super(Choice, self).__setstate__(state)
         self.specs = state.specs
         self._value = state.value
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
 
     def __eq__(self, their):
         if (their.__class__ == tuple) and len(their) == 2:
@@ -4892,20 +4764,11 @@ class PrimitiveTypes(Choice):
     ))
 
 
-AnyState = namedtuple("AnyState", (
-    "version",
-    "value",
-    "tag",
-    "expl",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-    "defined",
-), **NAMEDTUPLE_KWARGS)
+AnyState = namedtuple(
+    "AnyState",
+    BasicState._fields + ("value", "defined"),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class Any(Obj):
@@ -4965,9 +4828,9 @@ class Any(Obj):
     def __getstate__(self):
         return AnyState(
             __version__,
-            self._value,
             self.tag,
             self._expl,
+            None,
             self.optional,
             self.offset,
             self.llen,
@@ -4975,21 +4838,13 @@ class Any(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self._value,
             self.defined,
         )
 
     def __setstate__(self, state):
         super(Any, self).__setstate__(state)
         self._value = state.value
-        self.tag = state.tag
-        self._expl = state.expl
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
         self.defined = state.defined
 
     def __eq__(self, their):
@@ -5139,7 +4994,7 @@ def get_def_by_path(defines_by_path, sub_decode_path):
         if len(path) != len(sub_decode_path):
             continue
         for p1, p2 in zip(path, sub_decode_path):
-            if (p1 != any) and (p1 != p2):
+            if (not p1 is any) and (p1 != p2):
                 break
         else:
             return define
@@ -5170,21 +5025,11 @@ def abs_decode_path(decode_path, rel_path):
     return decode_path + rel_path
 
 
-SequenceState = namedtuple("SequenceState", (
-    "version",
-    "specs",
-    "value",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-), **NAMEDTUPLE_KWARGS)
+SequenceState = namedtuple(
+    "SequenceState",
+    BasicState._fields + ("specs", "value",),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class Sequence(Obj):
@@ -5339,8 +5184,6 @@ class Sequence(Obj):
     def __getstate__(self):
         return SequenceState(
             __version__,
-            self.specs,
-            {k: copy(v) for k, v in iteritems(self._value)},
             self.tag,
             self._expl,
             self.default,
@@ -5351,22 +5194,14 @@ class Sequence(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self.specs,
+            {k: copy(v) for k, v in iteritems(self._value)},
         )
 
     def __setstate__(self, state):
         super(Sequence, self).__setstate__(state)
         self.specs = state.specs
         self._value = state.value
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
 
     def __eq__(self, their):
         if not isinstance(their, self.__class__):
@@ -5424,19 +5259,17 @@ class Sequence(Obj):
             return spec.default
         return None
 
-    def _encoded_values(self):
-        raws = []
+    def _values_for_encoding(self):
         for name, spec in iteritems(self.specs):
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
                     continue
                 raise ObjNotReady(name)
-            raws.append(value.encode())
-        return raws
+            yield value
 
     def _encode(self):
-        v = b"".join(self._encoded_values())
+        v = b"".join(v.encode() for v in self._values_for_encoding())
         return b"".join((self.tag, len_encode(len(v)), v))
 
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
@@ -5671,8 +5504,8 @@ class Set(Sequence):
     .. _allow_unordered_set_ctx:
 
     DER prohibits unordered values encoding and will raise an error
-    during decode. If If :ref:`bered <bered_ctx>` context option is set,
-    then no error will occure. Also you can disable strict values
+    during decode. If :ref:`bered <bered_ctx>` context option is set,
+    then no error will occur. Also you can disable strict values
     ordering check by setting ``"allow_unordered_set": True``
     :ref:`context <ctx>` option.
     """
@@ -5681,7 +5514,7 @@ class Set(Sequence):
     asn1_type_name = "SET"
 
     def _encode(self):
-        raws = self._encoded_values()
+        raws = [v.encode() for v in self._values_for_encoding()]
         raws.sort()
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
@@ -5822,34 +5655,23 @@ class Set(Sequence):
             tail = v[EOC_LEN:]
             obj.lenindef = True
         obj._value = values
-        if not obj.ready:
-            raise DecodeError(
-                "not all values are ready",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
+        for name, spec in iteritems(self.specs):
+            if name not in values and not spec.optional:
+                raise DecodeError(
+                    "%s value is not ready" % name,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
         obj.ber_encoded = ber_encoded
         return obj, tail
 
 
-SequenceOfState = namedtuple("SequenceOfState", (
-    "version",
-    "spec",
-    "value",
-    "bound_min",
-    "bound_max",
-    "tag",
-    "expl",
-    "default",
-    "optional",
-    "offset",
-    "llen",
-    "vlen",
-    "expl_lenindef",
-    "lenindef",
-    "ber_encoded",
-), **NAMEDTUPLE_KWARGS)
+SequenceOfState = namedtuple(
+    "SequenceOfState",
+    BasicState._fields + ("spec", "value", "bound_min", "bound_max"),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class SequenceOf(Obj):
@@ -5950,10 +5772,6 @@ class SequenceOf(Obj):
     def __getstate__(self):
         return SequenceOfState(
             __version__,
-            self.spec,
-            [copy(v) for v in self._value],
-            self._bound_min,
-            self._bound_max,
             self.tag,
             self._expl,
             self.default,
@@ -5964,6 +5782,10 @@ class SequenceOf(Obj):
             self.expl_lenindef,
             self.lenindef,
             self.ber_encoded,
+            self.spec,
+            [copy(v) for v in self._value],
+            self._bound_min,
+            self._bound_max,
         )
 
     def __setstate__(self, state):
@@ -5972,16 +5794,6 @@ class SequenceOf(Obj):
         self._value = state.value
         self._bound_min = state.bound_min
         self._bound_max = state.bound_max
-        self.tag = state.tag
-        self._expl = state.expl
-        self.default = state.default
-        self.optional = state.optional
-        self.offset = state.offset
-        self.llen = state.llen
-        self.vlen = state.vlen
-        self.expl_lenindef = state.expl_lenindef
-        self.lenindef = state.lenindef
-        self.ber_encoded = state.ber_encoded
 
     def __eq__(self, their):
         if isinstance(their, self.__class__):
@@ -6047,11 +5859,11 @@ class SequenceOf(Obj):
     def __getitem__(self, key):
         return self._value[key]
 
-    def _encoded_values(self):
-        return [v.encode() for v in self._value]
+    def _values_for_encoding(self):
+        return iter(self._value)
 
     def _encode(self):
-        v = b"".join(self._encoded_values())
+        v = b"".join(v.encode() for v in self._values_for_encoding())
         return b"".join((self.tag, len_encode(len(v)), v))
 
     def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
@@ -6214,7 +6026,7 @@ class SetOf(SequenceOf):
     asn1_type_name = "SET OF"
 
     def _encode(self):
-        raws = self._encoded_values()
+        raws = [v.encode() for v in self._values_for_encoding()]
         raws.sort()
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
@@ -6372,6 +6184,7 @@ def main():  # pragma: no cover
     if args.defines_by_path is not None:
         ctx["defines_by_path"] = obj_by_path(args.defines_by_path)
     obj, tail = schema().decode(der, ctx=ctx)
+    from os import environ
     print(pprinter(
         obj,
         oid_maps=oid_maps,