]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Faster Set readiness determining during decode
[pyderasn.git] / pyderasn.py
index 814d85fba9113ef595bdbd326a93c3c60382b5ca..ed5b763ccef4123c790d52569d9b376ef2acf168 100755 (executable)
@@ -1,12 +1,12 @@
 #!/usr/bin/env python
 # coding: utf-8
 #!/usr/bin/env python
 # coding: utf-8
+# cython: language_level=3
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2019 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -14,8 +14,7 @@
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
-# License along with this program.  If not, see
-# <http://www.gnu.org/licenses/>.
+# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """Python ASN.1 DER/BER codec with abstract structures
 
 This library allows you to marshal various structures in ASN.1 DER
 """Python ASN.1 DER/BER codec with abstract structures
 
 This library allows you to marshal various structures in ASN.1 DER
@@ -23,7 +22,7 @@ format, unmarshal them in BER/CER/DER ones.
 
     >>> i = Integer(123)
     >>> raw = i.encode()
 
     >>> i = Integer(123)
     >>> raw = i.encode()
-    >>> Integer().decode(raw) == i
+    >>> Integer().decod(raw) == i
     True
 
 There are primitive types, holding single values
     True
 
 There are primitive types, holding single values
@@ -67,10 +66,11 @@ ____
 
 Most types in ASN.1 has specific tag for them. ``Obj.tag_default`` is
 the default tag used during coding process. You can override it with
 
 Most types in ASN.1 has specific tag for them. ``Obj.tag_default`` is
 the default tag used during coding process. You can override it with
-either ``IMPLICIT`` (using ``impl`` keyword argument), or
-``EXPLICIT`` one (using ``expl`` keyword argument). Both arguments take
-raw binary string, containing that tag. You can **not** set implicit and
-explicit tags simultaneously.
+either ``IMPLICIT`` (using either ``impl`` keyword argument or ``impl``
+class attribute), or ``EXPLICIT`` one (using either ``expl`` keyword
+argument or ``expl`` class attribute). Both arguments take raw binary
+string, containing that tag. You can **not** set implicit and explicit
+tags simultaneously.
 
 There are :py:func:`pyderasn.tag_ctxp` and :py:func:`pyderasn.tag_ctxc`
 functions, allowing you to easily create ``CONTEXT``
 
 There are :py:func:`pyderasn.tag_ctxp` and :py:func:`pyderasn.tag_ctxc`
 functions, allowing you to easily create ``CONTEXT``
@@ -163,21 +163,27 @@ All objects have ``ready`` boolean property, that tells if object is
 ready to be encoded. If that kind of action is performed on unready
 object, then :py:exc:`pyderasn.ObjNotReady` exception will be raised.
 
 ready to be encoded. If that kind of action is performed on unready
 object, then :py:exc:`pyderasn.ObjNotReady` exception will be raised.
 
-All objects have ``copy()`` method, that returns their copy, that can be
+All objects are friendly to ``copy.copy()`` and copied objects can be
 safely mutated.
 
 safely mutated.
 
+Also all objects can be safely ``pickle``-d, but pay attention that
+pickling among different PyDERASN versions is prohibited.
+
 .. _decoding:
 
 Decoding
 --------
 
 .. _decoding:
 
 Decoding
 --------
 
-Decoding is performed using ``decode()`` method. ``offset`` optional
-argument could be used to set initial object's offset in the binary
-data, for convenience. It returns decoded object and remaining
-unmarshalled data (tail). Internally all work is done on
+Decoding is performed using :py:meth:`pyderasn.Obj.decode` method.
+``offset`` optional argument could be used to set initial object's
+offset in the binary data, for convenience. It returns decoded object
+and remaining unmarshalled data (tail). Internally all work is done on
 ``memoryview(data)``, and you can leave returning tail as a memoryview,
 by specifying ``leavemm=True`` argument.
 
 ``memoryview(data)``, and you can leave returning tail as a memoryview,
 by specifying ``leavemm=True`` argument.
 
+Also note convenient :py:meth:`pyderasn.Obj.decod` method, that
+immediately checks and raises if there is non-empty tail.
+
 When object is decoded, ``decoded`` property is true and you can safely
 use following properties:
 
 When object is decoded, ``decoded`` property is true and you can safely
 use following properties:
 
@@ -207,9 +213,9 @@ When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
 Context
 _______
 
 Context
 _______
 
-You can specify so called context keyword argument during ``decode()``
-invocation. It is dictionary containing various options governing
-decoding process.
+You can specify so called context keyword argument during
+:py:meth:`pyderasn.Obj.decode` invocation. It is dictionary containing
+various options governing decoding process.
 
 Currently available context options:
 
 
 Currently available context options:
 
@@ -335,7 +341,8 @@ Let's parse that output, human::
  Only applicable to BER encoded data. If object has BER-specific
  encoding, then ``BER`` will be shown. It does not depend on indefinite
  length encoding. ``EOC``, ``BOOLEAN``, ``BIT STRING``, ``OCTET STRING``
  Only applicable to BER encoded data. If object has BER-specific
  encoding, then ``BER`` will be shown. It does not depend on indefinite
  length encoding. ``EOC``, ``BOOLEAN``, ``BIT STRING``, ``OCTET STRING``
- (and its derivatives), ``SET``, ``SET OF`` could be BERed.
+ (and its derivatives), ``SET``, ``SET OF``, ``UTCTime``, ``GeneralizedTime``
+ could be BERed.
 
 
 .. _definedby:
 
 
 .. _definedby:
@@ -348,6 +355,8 @@ DEFINED BY some previously met ObjectIdentifier. This library provides
 ability to specify mapping between some OID and field that must be
 decoded with specific specification.
 
 ability to specify mapping between some OID and field that must be
 decoded with specific specification.
 
+.. _defines:
+
 defines kwarg
 _____________
 
 defines kwarg
 _____________
 
@@ -421,15 +430,15 @@ value must be sequence of following tuples::
 
 where ``decode_path`` is a tuple holding so-called decode path to the
 exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
 
 where ``decode_path`` is a tuple holding so-called decode path to the
 exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
-``defines``, holding exactly the same value as accepted in its keyword
-argument.
+``defines``, holding exactly the same value as accepted in its
+:ref:`keyword argument <defines>`.
 
 For example, again for CMS, you want to automatically decode
 ``SignedData`` and CMC's (:rfc:`5272`) ``PKIData`` and ``PKIResponse``
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
 
 For example, again for CMS, you want to automatically decode
 ``SignedData`` and CMC's (:rfc:`5272`) ``PKIData`` and ``PKIResponse``
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
-    content_info, tail = ContentInfo().decode(data, defines_by_path=(
+    content_info = ContentInfo().decod(data, ctx={"defines_by_path": (
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
@@ -464,7 +473,7 @@ of ``PKIResponse``::
                 id_cmc_transactionId: TransactionId(),
             })),
         ),
                 id_cmc_transactionId: TransactionId(),
             })),
         ),
-    ))
+    )})
 
 Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
 First function is useful for path construction when some automatic
 
 Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
 First function is useful for path construction when some automatic
@@ -483,8 +492,8 @@ constructed primitive types should be parsed successfully.
 
 * If object is encoded in BER form (not the DER one), then ``ber_encoded``
   attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET
 
 * If object is encoded in BER form (not the DER one), then ``ber_encoded``
   attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET
-  STRING``, ``OBJECT IDENTIFIER``, ``SEQUENCE``, ``SET``, ``SET OF``
-  can contain it.
+  STRING``, ``OBJECT IDENTIFIER``, ``SEQUENCE``, ``SET``, ``SET OF``,
+  ``UTCTime``, ``GeneralizedTime`` can contain it.
 * If object has an indefinite length encoding, then its ``lenindef``
   attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``,
   ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can
 * If object has an indefinite length encoding, then its ``lenindef``
   attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``,
   ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can
@@ -517,6 +526,11 @@ lengths will be invalid in that case.
    This option should be used only for skipping some decode errors, just
    to see the decoded structure somehow.
 
    This option should be used only for skipping some decode errors, just
    to see the decoded structure somehow.
 
+Base Obj
+--------
+.. autoclass:: pyderasn.Obj
+   :members:
+
 Primitive types
 ---------------
 
 Primitive types
 ---------------
 
@@ -565,6 +579,7 @@ _____________
 PrintableString
 _______________
 .. autoclass:: pyderasn.PrintableString
 PrintableString
 _______________
 .. autoclass:: pyderasn.PrintableString
+   :members: __init__
 
 UTCTime
 _______
 
 UTCTime
 _______
@@ -626,10 +641,10 @@ Various
 .. autofunction:: pyderasn.tag_decode
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
 .. autofunction:: pyderasn.tag_decode
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
-.. autoclass:: pyderasn.Obj
 .. autoclass:: pyderasn.DecodeError
    :members: __init__
 .. autoclass:: pyderasn.NotEnoughData
 .. autoclass:: pyderasn.DecodeError
    :members: __init__
 .. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.ExceedingData
 .. autoclass:: pyderasn.LenIndefForm
 .. autoclass:: pyderasn.TagMismatch
 .. autoclass:: pyderasn.InvalidLength
 .. autoclass:: pyderasn.LenIndefForm
 .. autoclass:: pyderasn.TagMismatch
 .. autoclass:: pyderasn.InvalidLength
@@ -646,10 +661,12 @@ from collections import namedtuple
 from collections import OrderedDict
 from copy import copy
 from datetime import datetime
 from collections import OrderedDict
 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 math import ceil
 from os import environ
 from string import ascii_letters
 from string import digits
+from unicodedata import category as unicat
 
 from six import add_metaclass
 from six import binary_type
 
 from six import add_metaclass
 from six import binary_type
@@ -670,9 +687,10 @@ from six.moves import xrange as six_xrange
 try:
     from termcolor import colored
 except ImportError:  # pragma: no cover
 try:
     from termcolor import colored
 except ImportError:  # pragma: no cover
-    def colored(what, *args):
+    def colored(what, *args, **kwargs):
         return what
 
         return what
 
+__version__ = "6.2"
 
 __all__ = (
     "Any",
 
 __all__ = (
     "Any",
@@ -684,6 +702,7 @@ __all__ = (
     "DecodeError",
     "DecodePathDefBy",
     "Enumerated",
     "DecodeError",
     "DecodePathDefBy",
     "Enumerated",
+    "ExceedingData",
     "GeneralizedTime",
     "GeneralString",
     "GraphicString",
     "GeneralizedTime",
     "GeneralString",
     "GraphicString",
@@ -745,6 +764,20 @@ EOC = b"\x00\x00"
 EOC_LEN = len(EOC)
 LENINDEF = b"\x80"  # length indefinite mark
 LENINDEF_PP_CHAR = "I" if PY2 else "∞"
 EOC_LEN = len(EOC)
 LENINDEF = b"\x80"  # length indefinite mark
 LENINDEF_PP_CHAR = "I" if PY2 else "∞"
+NAMEDTUPLE_KWARGS = {} if PY2 else {"module": __name__}
+SET01 = frozenset("01")
+DECIMALS = frozenset(digits)
+DECIMAL_SIGNS = ".,"
+
+
+def pureint(value):
+    if not set(value) <= DECIMALS:
+        raise ValueError("non-pure integer")
+    return int(value)
+
+def fractions2float(fractions_raw):
+    pureint(fractions_raw)
+    return float("0." + fractions_raw)
 
 
 ########################################################################
 
 
 ########################################################################
@@ -794,6 +827,18 @@ class NotEnoughData(DecodeError):
     pass
 
 
     pass
 
 
+class ExceedingData(ASN1Error):
+    def __init__(self, nbytes):
+        super(ExceedingData, self).__init__()
+        self.nbytes = nbytes
+
+    def __str__(self):
+        return "%d trailing bytes" % self.nbytes
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, self)
+
+
 class LenIndefForm(DecodeError):
     pass
 
 class LenIndefForm(DecodeError):
     pass
 
@@ -1019,9 +1064,9 @@ def len_decode(data):
 ########################################################################
 
 class AutoAddSlots(type):
 ########################################################################
 
 class AutoAddSlots(type):
-    def __new__(mcs, name, bases, _dict):
+    def __new__(cls, name, bases, _dict):
         _dict["__slots__"] = _dict.get("__slots__", ())
         _dict["__slots__"] = _dict.get("__slots__", ())
-        return type.__new__(mcs, name, bases, _dict)
+        return type.__new__(cls, name, bases, _dict)
 
 
 @add_metaclass(AutoAddSlots)
 
 
 @add_metaclass(AutoAddSlots)
@@ -1088,17 +1133,36 @@ class Obj(object):
         """
         return (self.llen + self.vlen) > 0
 
         """
         return (self.llen + self.vlen) > 0
 
-    def copy(self):  # pragma: no cover
-        """Make a copy of object, safe to be mutated
+    def __getstate__(self):  # pragma: no cover
+        """Used for making safe to be mutable pickleable copies
         """
         raise NotImplementedError()
 
         """
         raise NotImplementedError()
 
+    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
+
     @property
     def tlen(self):
     @property
     def tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self.tag)
 
     @property
     def tlvlen(self):
         return len(self.tag)
 
     @property
     def tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
@@ -1123,11 +1187,20 @@ class Obj(object):
         raise NotImplementedError()
 
     def encode(self):
         raise NotImplementedError()
 
     def encode(self):
+        """Encode the structure
+
+        :returns: DER representation
+        """
         raw = self._encode()
         if self._expl is None:
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
         raw = self._encode()
         if self._expl is None:
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
+    def hexencode(self):
+        """Do hexadecimal encoded :py:meth:`pyderasn.Obj.encode`
+        """
+        return hexenc(self.encode())
+
     def decode(
             self,
             data,
     def decode(
             self,
             data,
@@ -1148,8 +1221,11 @@ class Obj(object):
         :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)
         :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)
-        :param _ctx_immutable: do we need to copy ``ctx`` before using it
+        :param _ctx_immutable: do we need to ``copy.copy()`` ``ctx``
+                               before using it?
         :returns: (Obj, remaining data)
         :returns: (Obj, remaining data)
+
+        .. seealso:: :ref:`decoding`
         """
         if ctx is None:
             ctx = {}
         """
         if ctx is None:
             ctx = {}
@@ -1165,7 +1241,7 @@ class Obj(object):
                 tag_only=tag_only,
             )
             if tag_only:
                 tag_only=tag_only,
             )
             if tag_only:
-                return
+                return None
             obj, tail = result
         else:
             try:
             obj, tail = result
         else:
             try:
@@ -1203,7 +1279,7 @@ class Obj(object):
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
-                    return
+                    return None
                 obj, tail = result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
                 obj, tail = result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
@@ -1238,7 +1314,7 @@ class Obj(object):
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
-                    return
+                    return None
                 obj, tail = result
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
                     raise DecodeError(
                 obj, tail = result
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
                     raise DecodeError(
@@ -1249,48 +1325,96 @@ class Obj(object):
                     )
         return obj, (tail if leavemm else tail.tobytes())
 
                     )
         return obj, (tail if leavemm else tail.tobytes())
 
+    def decod(self, data, offset=0, decode_path=(), ctx=None):
+        """Decode the data, check that tail is empty
+
+        :raises ExceedingData: if tail is not empty
+
+        This is just a wrapper over :py:meth:`pyderasn.Obj.decode`
+        (decode without tail) that also checks that there is no
+        trailing data left.
+        """
+        obj, tail = self.decode(
+            data,
+            offset=offset,
+            decode_path=decode_path,
+            ctx=ctx,
+            leavemm=True,
+        )
+        if len(tail) > 0:
+            raise ExceedingData(len(tail))
+        return obj
+
+    def hexdecode(self, data, *args, **kwargs):
+        """Do :py:meth:`pyderasn.Obj.decode` with hexadecimal decoded data
+        """
+        return self.decode(hexdec(data), *args, **kwargs)
+
+    def hexdecod(self, data, *args, **kwargs):
+        """Do :py:meth:`pyderasn.Obj.decod` with hexadecimal decoded data
+        """
+        return self.decod(hexdec(data), *args, **kwargs)
+
     @property
     def expled(self):
     @property
     def expled(self):
+        """See :ref:`decoding`
+        """
         return self._expl is not None
 
     @property
     def expl_tag(self):
         return self._expl is not None
 
     @property
     def expl_tag(self):
+        """See :ref:`decoding`
+        """
         return self._expl
 
     @property
     def expl_tlen(self):
         return self._expl
 
     @property
     def expl_tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self._expl)
 
     @property
     def expl_llen(self):
         return len(self._expl)
 
     @property
     def expl_llen(self):
+        """See :ref:`decoding`
+        """
         if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
         if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
+        """See :ref:`decoding`
+        """
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     @property
     def fulloffset(self):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     @property
     def fulloffset(self):
+        """See :ref:`decoding`
+        """
         return self.expl_offset if self.expled else self.offset
 
     @property
     def fulllen(self):
         return self.expl_offset if self.expled else self.offset
 
     @property
     def fulllen(self):
+        """See :ref:`decoding`
+        """
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
         if self.lenindef and not (
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
         if self.lenindef and not (
-            getattr(self, "defined", None) is not None and
-            self.defined[1].lenindef
+                getattr(self, "defined", None) is not None and
+                self.defined[1].lenindef
         ):
             yield _pp(
                 asn1_type_name="EOC",
         ):
             yield _pp(
                 asn1_type_name="EOC",
@@ -1370,7 +1494,7 @@ PP = namedtuple("PP", (
     "lenindef",
     "ber_encoded",
     "bered",
     "lenindef",
     "ber_encoded",
     "bered",
-))
+), **NAMEDTUPLE_KWARGS)
 
 
 def _pp(
 
 
 def _pp(
@@ -1520,9 +1644,9 @@ def pp_console_row(
                 with_colours,
             ))
     if with_blob:
                 with_colours,
             ))
     if with_blob:
-        if isinstance(pp.blob, binary_type):
+        if pp.blob.__class__ == binary_type:
             cols.append(hexenc(pp.blob))
             cols.append(hexenc(pp.blob))
-        elif isinstance(pp.blob, tuple):
+        elif pp.blob.__class__ == tuple:
             cols.append(", ".join(pp.blob))
     if pp.optional:
         cols.append(_colourize("OPTIONAL", "red", with_colours))
             cols.append(", ".join(pp.blob))
     if pp.optional:
         cols.append(_colourize("OPTIONAL", "red", with_colours))
@@ -1542,12 +1666,12 @@ def pp_console_blob(pp, decode_path_len_decrease=0):
     decode_path_len = len(pp.decode_path) - decode_path_len_decrease
     if decode_path_len > 0:
         cols.append(" ." * (decode_path_len + 1))
     decode_path_len = len(pp.decode_path) - decode_path_len_decrease
     if decode_path_len > 0:
         cols.append(" ." * (decode_path_len + 1))
-    if isinstance(pp.blob, binary_type):
+    if pp.blob.__class__ == binary_type:
         blob = hexenc(pp.blob).upper()
         for i in six_xrange(0, len(blob), 32):
             chunk = blob[i:i + 32]
             yield " ".join(cols + [colonize_hex(chunk)])
         blob = hexenc(pp.blob).upper()
         for i in six_xrange(0, len(blob), 32):
             chunk = blob[i:i + 32]
             yield " ".join(cols + [colonize_hex(chunk)])
-    elif isinstance(pp.blob, tuple):
+    elif pp.blob.__class__ == tuple:
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
@@ -1562,9 +1686,8 @@ def pprint(
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
-    :param oid_maps: list of ``OID <-> humand readable string`` dictionary.
-                     When OID from it is met, then its humand readable form
-                     is printed
+    :param oid_maps: list of ``str(OID) <-> human readable string`` dictionary.
+                     Its human readable form is printed when OID is met
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
@@ -1577,10 +1700,10 @@ def pprint(
         for pp in pps:
             if hasattr(pp, "_fields"):
                 if (
         for pp in pps:
             if hasattr(pp, "_fields"):
                 if (
-                    decode_path_only != () and
-                    tuple(
-                        str(p) for p in pp.decode_path[:len(decode_path_only)]
-                    ) != decode_path_only
+                        decode_path_only != () and
+                        tuple(
+                            str(p) for p in pp.decode_path[:len(decode_path_only)]
+                        ) != decode_path_only
                 ):
                     continue
                 if big_blobs:
                 ):
                     continue
                 if big_blobs:
@@ -1594,8 +1717,8 @@ def pprint(
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
-                        pp,
-                        decode_path_len_decrease=len(decode_path_only),
+                            pp,
+                            decode_path_len_decrease=len(decode_path_only),
                     ):
                         yield row
                 else:
                     ):
                         yield row
                 else:
@@ -1618,6 +1741,22 @@ def pprint(
 # ASN.1 primitive types
 ########################################################################
 
 # ASN.1 primitive types
 ########################################################################
 
+BooleanState = namedtuple("BooleanState", (
+    "version",
+    "value",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
+
+
 class Boolean(Obj):
     """``BOOLEAN`` boolean type
 
 class Boolean(Obj):
     """``BOOLEAN`` boolean type
 
@@ -1662,7 +1801,7 @@ class Boolean(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if isinstance(value, bool):
+        if value.__class__ == bool:
             return value
         if issubclass(value.__class__, Boolean):
             return value._value
             return value
         if issubclass(value.__class__, Boolean):
             return value._value
@@ -1672,20 +1811,35 @@ class Boolean(Obj):
     def ready(self):
         return self._value is not None
 
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
+    def __getstate__(self):
+        return BooleanState(
+            __version__,
+            self._value,
+            self.tag,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+        )
+
+    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()
 
     def __nonzero__(self):
         self._assert_ready()
@@ -1696,7 +1850,7 @@ class Boolean(Obj):
         return self._value
 
     def __eq__(self, their):
         return self._value
 
     def __eq__(self, their):
-        if isinstance(their, bool):
+        if their.__class__ == bool:
             return self._value == their
         if not issubclass(their.__class__, Boolean):
             return False
             return self._value == their
         if not issubclass(their.__class__, Boolean):
             return False
@@ -1747,7 +1901,7 @@ class Boolean(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -1828,6 +1982,25 @@ class Boolean(Obj):
             yield pp
 
 
             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)
+
+
 class Integer(Obj):
     """``INTEGER`` integer type
 
 class Integer(Obj):
     """``INTEGER`` integer type
 
@@ -1891,7 +2064,7 @@ class Integer(Obj):
         super(Integer, self).__init__(impl, expl, default, optional, _decoded)
         self._value = value
         specs = getattr(self, "schema", {}) if _specs is None else _specs
         super(Integer, self).__init__(impl, expl, default, optional, _decoded)
         self._value = value
         specs = getattr(self, "schema", {}) if _specs is None else _specs
-        self.specs = specs if isinstance(specs, dict) else dict(specs)
+        self.specs = specs if specs.__class__ == dict else dict(specs)
         self._bound_min, self._bound_max = getattr(
             self,
             "bounds",
         self._bound_min, self._bound_max = getattr(
             self,
             "bounds",
@@ -1915,7 +2088,7 @@ class Integer(Obj):
             pass
         elif issubclass(value.__class__, Integer):
             value = value._value
             pass
         elif issubclass(value.__class__, Integer):
             value = value._value
-        elif isinstance(value, str):
+        elif value.__class__ == str:
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
@@ -1929,22 +2102,41 @@ class Integer(Obj):
     def ready(self):
         return self._value is not None
 
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__(_specs=self.specs)
-        obj._value = self._value
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
+    def __getstate__(self):
+        return IntegerState(
+            __version__,
+            self.specs,
+            self._value,
+            self._bound_min,
+            self._bound_max,
+            self.tag,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+        )
+
+    def __setstate__(self, state):
+        super(Integer, self).__setstate__(state)
+        self.specs = state.specs
+        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()
 
     def __int__(self):
         self._assert_ready()
@@ -1977,6 +2169,7 @@ class Integer(Obj):
         for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
         for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
+        return None
 
     def __call__(
             self,
 
     def __call__(
             self,
@@ -2056,7 +2249,7 @@ class Integer(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2158,7 +2351,23 @@ class Integer(Obj):
             yield pp
 
 
             yield pp
 
 
-SET01 = frozenset(("0", "1"))
+BitStringState = namedtuple("BitStringState", (
+    "version",
+    "specs",
+    "value",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+    "tag_constructed",
+    "defined",
+), **NAMEDTUPLE_KWARGS)
 
 
 class BitString(Obj):
 
 
 class BitString(Obj):
@@ -2236,7 +2445,7 @@ class BitString(Obj):
         """
         super(BitString, self).__init__(impl, expl, default, optional, _decoded)
         specs = getattr(self, "schema", {}) if _specs is None else _specs
         """
         super(BitString, self).__init__(impl, expl, default, optional, _decoded)
         specs = getattr(self, "schema", {}) if _specs is None else _specs
-        self.specs = specs if isinstance(specs, dict) else dict(specs)
+        self.specs = specs if specs.__class__ == dict else dict(specs)
         self._value = None if value is None else self._value_sanitize(value)
         if default is not None:
             default = self._value_sanitize(default)
         self._value = None if value is None else self._value_sanitize(value)
         if default is not None:
             default = self._value_sanitize(default)
@@ -2276,21 +2485,20 @@ class BitString(Obj):
                     if not frozenset(value) <= SET01:
                         raise ValueError("B's coding contains unacceptable chars")
                     return self._bits2octets(value)
                     if not frozenset(value) <= SET01:
                         raise ValueError("B's coding contains unacceptable chars")
                     return self._bits2octets(value)
-                elif value.endswith("'H"):
+                if value.endswith("'H"):
                     value = value[1:-2]
                     return (
                         len(value) * 4,
                         hexdec(value + ("" if len(value) % 2 == 0 else "0")),
                     )
                     value = value[1:-2]
                     return (
                         len(value) * 4,
                         hexdec(value + ("" if len(value) % 2 == 0 else "0")),
                     )
-            if isinstance(value, binary_type):
+            if value.__class__ == binary_type:
                 return (len(value) * 8, value)
                 return (len(value) * 8, value)
-            else:
-                raise InvalidValueType((self.__class__, string_types, binary_type))
-        if isinstance(value, tuple):
+            raise InvalidValueType((self.__class__, string_types, binary_type))
+        if value.__class__ == tuple:
             if (
                     len(value) == 2 and
                     isinstance(value[0], integer_types) and
             if (
                     len(value) == 2 and
                     isinstance(value[0], integer_types) and
-                    isinstance(value[1], binary_type)
+                    value[1].__class__ == binary_type
             ):
                 return value
             bits = []
             ):
                 return value
             bits = []
@@ -2314,23 +2522,41 @@ class BitString(Obj):
     def ready(self):
         return self._value is not None
 
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__(_specs=self.specs)
-        value = self._value
-        if value is not None:
-            value = (value[0], value[1])
-        obj._value = value
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
+    def __getstate__(self):
+        return BitStringState(
+            __version__,
+            self.specs,
+            self._value,
+            self.tag,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self.tag_constructed,
+            self.defined,
+        )
+
+    def __setstate__(self, state):
+        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
 
     def __iter__(self):
         self._assert_ready()
 
     def __iter__(self):
         self._assert_ready()
@@ -2347,7 +2573,7 @@ class BitString(Obj):
         return self._value[1]
 
     def __eq__(self, their):
         return self._value[1]
 
     def __eq__(self, their):
-        if isinstance(their, bytes):
+        if their.__class__ == bytes:
             return self._value[1] == their
         if not issubclass(their.__class__, BitString):
             return False
             return self._value[1] == their
         if not issubclass(their.__class__, BitString):
             return False
@@ -2379,7 +2605,7 @@ class BitString(Obj):
         )
 
     def __getitem__(self, key):
         )
 
     def __getitem__(self, key):
-        if isinstance(key, int):
+        if key.__class__ == int:
             bit_len, octets = self._value
             if key >= bit_len:
                 return False
             bit_len, octets = self._value
             if key >= bit_len:
                 return False
@@ -2404,64 +2630,6 @@ class BitString(Obj):
             octets,
         ))
 
             octets,
         ))
 
-    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,
-            )
-        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)
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
@@ -2474,24 +2642,9 @@ class BitString(Obj):
             )
         if t == self.tag:
             if tag_only:  # pragma: no cover
             )
         if t == self.tag:
             if tag_only:  # pragma: no cover
-                return
-            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:  # pragma: no cover
-                return
-            lenindef = False
+                return None
             try:
                 l, llen, v = len_decode(lv)
             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,
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -2506,90 +2659,160 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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,
                 )
                 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(
                 raise DecodeError(
-                    "no chunks",
+                    "invalid empty value",
                     klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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__(
             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,
                 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()))
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -2635,6 +2858,26 @@ class BitString(Obj):
             yield pp
 
 
             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)
+
+
 class OctetString(Obj):
     """``OCTET STRING`` binary string type
 
 class OctetString(Obj):
     """``OCTET STRING`` binary string type
 
@@ -2672,6 +2915,7 @@ class OctetString(Obj):
             default=None,
             optional=False,
             _decoded=(0, 0, 0),
             default=None,
             optional=False,
             _decoded=(0, 0, 0),
+            ctx=None,
     ):
         """
         :param value: set the value. Either binary type, or
     ):
         """
         :param value: set the value. Either binary type, or
@@ -2683,13 +2927,7 @@ class OctetString(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(OctetString, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super(OctetString, self).__init__(impl, expl, default, optional, _decoded)
         self._value = value
         self._bound_min, self._bound_max = getattr(
             self,
         self._value = value
         self._bound_min, self._bound_max = getattr(
             self,
@@ -2716,7 +2954,7 @@ class OctetString(Obj):
         )
 
     def _value_sanitize(self, value):
         )
 
     def _value_sanitize(self, value):
-        if isinstance(value, binary_type):
+        if value.__class__ == binary_type:
             pass
         elif issubclass(value.__class__, OctetString):
             value = value._value
             pass
         elif issubclass(value.__class__, OctetString):
             value = value._value
@@ -2730,29 +2968,50 @@ class OctetString(Obj):
     def ready(self):
         return self._value is not None
 
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
+    def __getstate__(self):
+        return OctetStringState(
+            __version__,
+            self._value,
+            self._bound_min,
+            self._bound_max,
+            self.tag,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self.tag_constructed,
+            self.defined,
+        )
+
+    def __setstate__(self, state):
+        super(OctetString, self).__setstate__(state)
+        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
 
     def __bytes__(self):
         self._assert_ready()
         return self._value
 
     def __eq__(self, their):
 
     def __bytes__(self):
         self._assert_ready()
         return self._value
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             return self._value == their
         if not issubclass(their.__class__, OctetString):
             return False
             return self._value == their
         if not issubclass(their.__class__, OctetString):
             return False
@@ -2794,50 +3053,6 @@ class OctetString(Obj):
             self._value,
         ))
 
             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),
-            )
-        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)
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
@@ -2850,24 +3065,9 @@ class OctetString(Obj):
             )
         if t == self.tag:
             if tag_only:
             )
         if t == self.tag:
             if tag_only:
-                return
-            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
-            lenindef = False
+                return None
             try:
                 l, llen, v = len_decode(lv)
             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,
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -2882,53 +3082,17 @@ class OctetString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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:
-                    raise DecodeError(
-                        "expected OctetString 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
+            v, tail = v[:l], v[l:]
             try:
                 obj = self.__class__(
             try:
                 obj = self.__class__(
-                    value=b"".join(bytes(chunk) for chunk in chunks),
+                    value=v.tobytes(),
                     bounds=(self._bound_min, self._bound_max),
                     impl=self.tag,
                     expl=self._expl,
                     default=self.default,
                     optional=self.optional,
                     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)),
+                    _decoded=(offset, llen, l),
+                    ctx=ctx,
                 )
             except DecodeError as err:
                 raise DecodeError(
                 )
             except DecodeError as err:
                 raise DecodeError(
@@ -2944,14 +3108,108 @@ class OctetString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     decode_path=decode_path,
                     offset=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,
-        )
+            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(
+                        "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:
+                raise DecodeError(
+                    "expected OctetString 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
+        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()))
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -2990,6 +3248,21 @@ class OctetString(Obj):
             yield pp
 
 
             yield pp
 
 
+NullState = namedtuple("NullState", (
+    "version",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
+
+
 class Null(Obj):
     """``NULL`` null object
 
 class Null(Obj):
     """``NULL`` null object
 
@@ -3022,19 +3295,33 @@ class Null(Obj):
     def ready(self):
         return True
 
     def ready(self):
         return True
 
-    def copy(self):
-        obj = self.__class__()
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
+    def __getstate__(self):
+        return NullState(
+            __version__,
+            self.tag,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            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):
 
     def __eq__(self, their):
         if not issubclass(their.__class__, Null):
@@ -3077,7 +3364,7 @@ class Null(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -3129,6 +3416,23 @@ class Null(Obj):
             yield pp
 
 
             yield pp
 
 
+ObjectIdentifierState = namedtuple("ObjectIdentifierState", (
+    "version",
+    "value",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+    "defines",
+), **NAMEDTUPLE_KWARGS)
+
+
 class ObjectIdentifier(Obj):
     """``OBJECT IDENTIFIER`` OID type
 
 class ObjectIdentifier(Obj):
     """``OBJECT IDENTIFIER`` OID type
 
@@ -3179,13 +3483,7 @@ class ObjectIdentifier(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(ObjectIdentifier, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super(ObjectIdentifier, self).__init__(impl, expl, default, optional, _decoded)
         self._value = value
         if value is not None:
             self._value = self._value_sanitize(value)
         self._value = value
         if value is not None:
             self._value = self._value_sanitize(value)
@@ -3201,10 +3499,10 @@ class ObjectIdentifier(Obj):
         self.defines = defines
 
     def __add__(self, their):
         self.defines = defines
 
     def __add__(self, their):
+        if their.__class__ == tuple:
+            return self.__class__(self._value + their)
         if isinstance(their, self.__class__):
             return self.__class__(self._value + their._value)
         if isinstance(their, self.__class__):
             return self.__class__(self._value + their._value)
-        if isinstance(their, tuple):
-            return self.__class__(self._value + their)
         raise InvalidValueType((self.__class__, tuple))
 
     def _value_sanitize(self, value):
         raise InvalidValueType((self.__class__, tuple))
 
     def _value_sanitize(self, value):
@@ -3212,10 +3510,10 @@ class ObjectIdentifier(Obj):
             return value._value
         if isinstance(value, string_types):
             try:
             return value._value
         if isinstance(value, string_types):
             try:
-                value = tuple(int(arc) for arc in value.split("."))
+                value = tuple(pureint(arc) for arc in value.split("."))
             except ValueError:
                 raise InvalidOID("unacceptable arcs values")
             except ValueError:
                 raise InvalidOID("unacceptable arcs values")
-        if isinstance(value, tuple):
+        if value.__class__ == tuple:
             if len(value) < 2:
                 raise InvalidOID("less than 2 arcs")
             first_arc = value[0]
             if len(value) < 2:
                 raise InvalidOID("less than 2 arcs")
             first_arc = value[0]
@@ -3226,6 +3524,8 @@ class ObjectIdentifier(Obj):
                 pass
             else:
                 raise InvalidOID("unacceptable first arc value")
                 pass
             else:
                 raise InvalidOID("unacceptable first arc value")
+            if not all(arc >= 0 for arc in value):
+                raise InvalidOID("negative arc value")
             return value
         raise InvalidValueType((self.__class__, str, tuple))
 
             return value
         raise InvalidValueType((self.__class__, str, tuple))
 
@@ -3233,21 +3533,37 @@ class ObjectIdentifier(Obj):
     def ready(self):
         return self._value is not None
 
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj.defines = self.defines
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
+    def __getstate__(self):
+        return ObjectIdentifierState(
+            __version__,
+            self._value,
+            self.tag,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            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):
         self._assert_ready()
 
     def __iter__(self):
         self._assert_ready()
@@ -3265,7 +3581,7 @@ class ObjectIdentifier(Obj):
         )
 
     def __eq__(self, their):
         )
 
     def __eq__(self, their):
-        if isinstance(their, tuple):
+        if their.__class__ == tuple:
             return self._value == their
         if not issubclass(their.__class__, ObjectIdentifier):
             return False
             return self._value == their
         if not issubclass(their.__class__, ObjectIdentifier):
             return False
@@ -3332,7 +3648,7 @@ class ObjectIdentifier(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -3456,13 +3772,7 @@ class Enumerated(Integer):
             bounds=None,  # dummy argument, workability for Integer.decode
     ):
         super(Enumerated, self).__init__(
             bounds=None,  # dummy argument, workability for Integer.decode
     ):
         super(Enumerated, self).__init__(
-            value=value,
-            impl=impl,
-            expl=expl,
-            default=default,
-            optional=optional,
-            _specs=_specs,
-            _decoded=_decoded,
+            value, bounds, impl, expl, default, optional, _specs, _decoded,
         )
         if len(self.specs) == 0:
             raise ValueError("schema must be specified")
         )
         if len(self.specs) == 0:
             raise ValueError("schema must be specified")
@@ -3487,23 +3797,6 @@ class Enumerated(Integer):
             raise InvalidValueType((self.__class__, int, str))
         return value
 
             raise InvalidValueType((self.__class__, int, str))
         return value
 
-    def copy(self):
-        obj = self.__class__(_specs=self.specs)
-        obj._value = self._value
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
-
     def __call__(
             self,
             value=None,
     def __call__(
             self,
             value=None,
@@ -3523,6 +3816,12 @@ class Enumerated(Integer):
         )
 
 
         )
 
 
+def escape_control_unicode(c):
+    if unicat(c)[0] == "C":
+        c = repr(c).lstrip("u").strip("'")
+    return c
+
+
 class CommonString(OctetString):
     """Common class for all strings
 
 class CommonString(OctetString):
     """Common class for all strings
 
@@ -3585,16 +3884,16 @@ class CommonString(OctetString):
        * - :py:class:`pyderasn.BMPString`
          - utf-16-be
     """
        * - :py:class:`pyderasn.BMPString`
          - utf-16-be
     """
-    __slots__ = ("encoding",)
+    __slots__ = ()
 
     def _value_sanitize(self, value):
         value_raw = None
         value_decoded = None
         if isinstance(value, self.__class__):
             value_raw = value._value
 
     def _value_sanitize(self, value):
         value_raw = None
         value_decoded = None
         if isinstance(value, self.__class__):
             value_raw = value._value
-        elif isinstance(value, text_type):
+        elif value.__class__ == text_type:
             value_decoded = value
             value_decoded = value
-        elif isinstance(value, binary_type):
+        elif value.__class__ == binary_type:
             value_raw = value
         else:
             raise InvalidValueType((self.__class__, text_type, binary_type))
             value_raw = value
         else:
             raise InvalidValueType((self.__class__, text_type, binary_type))
@@ -3618,9 +3917,9 @@ class CommonString(OctetString):
         return value_raw
 
     def __eq__(self, their):
         return value_raw
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             return self._value == their
             return self._value == their
-        if isinstance(their, text_type):
+        if their.__class__ == text_type:
             return self._value == their.encode(self.encoding)
         if not isinstance(their, self.__class__):
             return False
             return self._value == their.encode(self.encoding)
         if not isinstance(their, self.__class__):
             return False
@@ -3641,7 +3940,10 @@ class CommonString(OctetString):
     def pps(self, decode_path=(), no_unicode=False):
         value = None
         if self.ready:
     def pps(self, decode_path=(), no_unicode=False):
         value = None
         if self.ready:
-            value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
+            value = (
+                hexenc(bytes(self)) if no_unicode else
+                "".join(escape_control_unicode(c) for c in self.__unicode__())
+            )
         yield _pp(
             obj=self,
             asn1_type_name=self.asn1_type_name,
         yield _pp(
             obj=self,
             asn1_type_name=self.asn1_type_name,
@@ -3705,6 +4007,13 @@ class NumericString(AllowableCharsMixin, CommonString):
         return value
 
 
         return value
 
 
+PrintableStringState = namedtuple(
+    "PrintableStringState",
+    OctetStringState._fields + ("allowable_chars",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class PrintableString(AllowableCharsMixin, CommonString):
     """Printable string
 
 class PrintableString(AllowableCharsMixin, CommonString):
     """Printable string
 
@@ -3712,6 +4021,10 @@ class PrintableString(AllowableCharsMixin, CommonString):
 
     >>> PrintableString().allowable_chars
     frozenset([' ', "'", ..., 'z'])
 
     >>> PrintableString().allowable_chars
     frozenset([' ', "'", ..., 'z'])
+    >>> obj = PrintableString("foo*bar", allow_asterisk=True)
+    PrintableString PrintableString foo*bar
+    >>> obj.allow_asterisk, obj.allow_ampersand
+    (True, False)
     """
     __slots__ = ()
     tag_default = tag_encode(19)
     """
     __slots__ = ()
     tag_default = tag_encode(19)
@@ -3720,6 +4033,45 @@ class PrintableString(AllowableCharsMixin, CommonString):
     _allowable_chars = frozenset(
         (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
     )
     _allowable_chars = frozenset(
         (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
     )
+    _asterisk = frozenset("*".encode("ascii"))
+    _ampersand = frozenset("&".encode("ascii"))
+
+    def __init__(
+            self,
+            value=None,
+            bounds=None,
+            impl=None,
+            expl=None,
+            default=None,
+            optional=False,
+            _decoded=(0, 0, 0),
+            ctx=None,
+            allow_asterisk=False,
+            allow_ampersand=False,
+    ):
+        """
+        :param allow_asterisk: allow asterisk character
+        :param allow_ampersand: allow ampersand character
+        """
+        if allow_asterisk:
+            self._allowable_chars |= self._asterisk
+        if allow_ampersand:
+            self._allowable_chars |= self._ampersand
+        super(PrintableString, self).__init__(
+            value, bounds, impl, expl, default, optional, _decoded, ctx,
+        )
+
+    @property
+    def allow_asterisk(self):
+        """Is asterisk character allowed?
+        """
+        return self._asterisk <= self._allowable_chars
+
+    @property
+    def allow_ampersand(self):
+        """Is ampersand character allowed?
+        """
+        return self._ampersand <= self._allowable_chars
 
     def _value_sanitize(self, value):
         value = super(PrintableString, self)._value_sanitize(value)
 
     def _value_sanitize(self, value):
         value = super(PrintableString, self)._value_sanitize(value)
@@ -3727,6 +4079,39 @@ class PrintableString(AllowableCharsMixin, CommonString):
             raise DecodeError("non-printable value")
         return value
 
             raise DecodeError("non-printable value")
         return value
 
+    def __getstate__(self):
+        return PrintableStringState(
+            *super(PrintableString, self).__getstate__(),
+            **{"allowable_chars": self._allowable_chars}
+        )
+
+    def __setstate__(self, state):
+        super(PrintableString, self).__setstate__(state)
+        self._allowable_chars = state.allowable_chars
+
+    def __call__(
+            self,
+            value=None,
+            bounds=None,
+            impl=None,
+            expl=None,
+            default=None,
+            optional=None,
+    ):
+        return self.__class__(
+            value=value,
+            bounds=(
+                (self._bound_min, self._bound_max)
+                if bounds is None else bounds
+            ),
+            impl=self.tag if impl is None else impl,
+            expl=self._expl if expl is None else expl,
+            default=self.default if default is None else default,
+            optional=self.optional if optional is None else optional,
+            allow_asterisk=self.allow_asterisk,
+            allow_ampersand=self.allow_ampersand,
+        )
+
 
 class TeletexString(CommonString):
     __slots__ = ()
 
 class TeletexString(CommonString):
     __slots__ = ()
@@ -3759,7 +4144,31 @@ LEN_YYYYMMDDHHMMSSDMZ = len("YYYYMMDDHHMMSSDMZ")
 LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
 
 
 LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
 
 
-class UTCTime(CommonString):
+class VisibleString(CommonString):
+    __slots__ = ()
+    tag_default = tag_encode(26)
+    encoding = "ascii"
+    asn1_type_name = "VisibleString"
+
+
+UTCTimeState = namedtuple(
+    "UTCTimeState",
+    OctetStringState._fields + ("ber_raw",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
+def str_to_time_fractions(value):
+    v = pureint(value)
+    year, v = (v // 10**10), (v % 10**10)
+    month, v = (v // 10**8), (v % 10**8)
+    day, v = (v // 10**6), (v % 10**6)
+    hour, v = (v // 10**4), (v % 10**4)
+    minute, second = (v // 100), (v % 100)
+    return year, month, day, hour, minute, second
+
+
+class UTCTime(VisibleString):
     """``UTCTime`` datetime type
 
     >>> t = UTCTime(datetime(2017, 9, 30, 22, 7, 50, 123))
     """``UTCTime`` datetime type
 
     >>> t = UTCTime(datetime(2017, 9, 30, 22, 7, 50, 123))
@@ -3773,11 +4182,23 @@ class UTCTime(CommonString):
     >>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).todatetime()
     datetime.datetime(1957, 9, 30, 22, 7, 50)
 
     >>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).todatetime()
     datetime.datetime(1957, 9, 30, 22, 7, 50)
 
+    If BER encoded value was met, then ``ber_raw`` attribute will hold
+    its raw representation.
+
     .. warning::
 
     .. warning::
 
-       BER encoding is unsupported.
+       Pay attention that UTCTime can not hold full year, so all years
+       having < 50 years are treated as 20xx, 19xx otherwise, according
+       to X.509 recommendation.
+
+    .. warning::
+
+       No strict validation of UTC offsets are made, but very crude:
+
+       * minutes are not exceeding 60
+       * offset value is not exceeding 14 hours
     """
     """
-    __slots__ = ()
+    __slots__ = ("ber_raw",)
     tag_default = tag_encode(23)
     encoding = "ascii"
     asn1_type_name = "UTCTime"
     tag_default = tag_encode(23)
     encoding = "ascii"
     asn1_type_name = "UTCTime"
@@ -3791,6 +4212,7 @@ class UTCTime(CommonString):
             optional=False,
             _decoded=(0, 0, 0),
             bounds=None,  # dummy argument, workability for OctetString.decode
             optional=False,
             _decoded=(0, 0, 0),
             bounds=None,  # dummy argument, workability for OctetString.decode
+            ctx=None,
     ):
         """
         :param value: set the value. Either datetime type, or
     ):
         """
         :param value: set the value. Either datetime type, or
@@ -3801,17 +4223,15 @@ class UTCTime(CommonString):
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         super(UTCTime, self).__init__(
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         super(UTCTime, self).__init__(
-            impl=impl,
-            expl=expl,
-            default=default,
-            optional=optional,
-            _decoded=_decoded,
+            None, None, impl, expl, None, optional, _decoded, ctx,
         )
         self._value = value
         )
         self._value = value
+        self.ber_raw = None
         if value is not None:
         if value is not None:
-            self._value = self._value_sanitize(value)
+            self._value, self.ber_raw = self._value_sanitize(value, ctx)
+            self.ber_encoded = self.ber_raw is not None
         if default is not None:
         if default is not None:
-            default = self._value_sanitize(default)
+            default, _ = self._value_sanitize(default)
             self.default = self.__class__(
                 value=default,
                 impl=self.tag,
             self.default = self.__class__(
                 value=default,
                 impl=self.tag,
@@ -3819,6 +4239,45 @@ class UTCTime(CommonString):
             )
             if self._value is None:
                 self._value = default
             )
             if self._value is None:
                 self._value = default
+            optional = True
+        self.optional = optional
+
+    def _strptime_bered(self, value):
+        year, month, day, hour, minute, _ = str_to_time_fractions(value[:10] + "00")
+        value = value[10:]
+        if len(value) == 0:
+            raise ValueError("no timezone")
+        year += 2000 if year < 50 else 1900
+        decoded = datetime(year, month, day, hour, minute)
+        offset = 0
+        if value[-1] == "Z":
+            value = value[:-1]
+        else:
+            if len(value) < 5:
+                raise ValueError("invalid UTC offset")
+            if value[-5] == "-":
+                sign = -1
+            elif value[-5] == "+":
+                sign = 1
+            else:
+                raise ValueError("invalid UTC offset")
+            v = pureint(value[-4:])
+            offset, v = (60 * (v % 100)), v // 100
+            if offset >= 3600:
+                raise ValueError("invalid UTC offset minutes")
+            offset += 3600 * v
+            if offset > 14 * 3600:
+                raise ValueError("too big UTC offset")
+            offset *= sign
+            value = value[:-5]
+        if len(value) == 0:
+            return offset, decoded
+        if len(value) != 2:
+            raise ValueError("invalid UTC offset seconds")
+        seconds = pureint(value)
+        if seconds >= 60:
+            raise ValueError("invalid seconds value")
+        return offset, decoded + timedelta(seconds=seconds)
 
     def _strptime(self, value):
         # datetime.strptime's format: %y%m%d%H%M%SZ
 
     def _strptime(self, value):
         # datetime.strptime's format: %y%m%d%H%M%SZ
@@ -3826,36 +4285,76 @@ class UTCTime(CommonString):
             raise ValueError("invalid UTCTime length")
         if value[-1] != "Z":
             raise ValueError("non UTC timezone")
             raise ValueError("invalid UTCTime length")
         if value[-1] != "Z":
             raise ValueError("non UTC timezone")
-        return datetime(
-            2000 + int(value[:2]),  # %y
-            int(value[2:4]),  # %m
-            int(value[4:6]),  # %d
-            int(value[6:8]),  # %H
-            int(value[8:10]),  # %M
-            int(value[10:12]),  # %S
-        )
+        year, month, day, hour, minute, second = str_to_time_fractions(value[:-1])
+        year += 2000 if year < 50 else 1900
+        return datetime(year, month, day, hour, minute, second)
 
 
-    def _value_sanitize(self, value):
-        if isinstance(value, binary_type):
+    def _dt_sanitize(self, value):
+        if value.year < 1950 or value.year > 2049:
+            raise ValueError("UTCTime can hold only 1950-2049 years")
+        return value.replace(microsecond=0)
+
+    def _value_sanitize(self, value, ctx=None):
+        if value.__class__ == binary_type:
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
                 raise DecodeError("invalid UTCTime encoding: %r" % err)
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
                 raise DecodeError("invalid UTCTime encoding: %r" % err)
+            err = None
             try:
             try:
-                self._strptime(value_decoded)
-            except (TypeError, ValueError) as err:
-                raise DecodeError("invalid UTCTime format: %r" % err)
-            return value
+                return self._strptime(value_decoded), None
+            except (TypeError, ValueError) as _err:
+                err = _err
+                if (ctx is not None) and ctx.get("bered", False):
+                    try:
+                        offset, _value = self._strptime_bered(value_decoded)
+                        _value = _value - timedelta(seconds=offset)
+                        return self._dt_sanitize(_value), value
+                    except (TypeError, ValueError, OverflowError) as _err:
+                        err = _err
+            raise DecodeError(
+                "invalid %s format: %r" % (self.asn1_type_name, err),
+                klass=self.__class__,
+            )
         if isinstance(value, self.__class__):
         if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime("%y%m%d%H%M%SZ").encode("ascii")
+            return value._value, None
+        if value.__class__ == datetime:
+            return self._dt_sanitize(value), None
         raise InvalidValueType((self.__class__, datetime))
 
         raise InvalidValueType((self.__class__, datetime))
 
+    def _pp_value(self):
+        if self.ready:
+            value = self._value.isoformat()
+            if self.ber_encoded:
+                value += " (%s)" % self.ber_raw
+            return value
+
+    def __unicode__(self):
+        if self.ready:
+            value = self._value.isoformat()
+            if self.ber_encoded:
+                value += " (%s)" % self.ber_raw
+            return value
+        return text_type(self._pp_value())
+
+    def __getstate__(self):
+        return UTCTimeState(
+            *super(UTCTime, self).__getstate__(),
+            **{"ber_raw": self.ber_raw}
+        )
+
+    def __setstate__(self, state):
+        super(UTCTime, self).__setstate__(state)
+        self.ber_raw = state.ber_raw
+
+    def __bytes__(self):
+        self._assert_ready()
+        return self._encode_time()
+
     def __eq__(self, their):
     def __eq__(self, their):
-        if isinstance(their, binary_type):
-            return self._value == their
-        if isinstance(their, datetime):
+        if their.__class__ == binary_type:
+            return self._encode_time() == their
+        if their.__class__ == datetime:
             return self.todatetime() == their
         if not isinstance(their, self.__class__):
             return False
             return self.todatetime() == their
         if not isinstance(their, self.__class__):
             return False
@@ -3865,25 +4364,16 @@ class UTCTime(CommonString):
             self._expl == their._expl
         )
 
             self._expl == their._expl
         )
 
-    def todatetime(self):
-        """Convert to datetime
+    def _encode_time(self):
+        return self._value.strftime("%y%m%d%H%M%SZ").encode("ascii")
 
 
-        :returns: datetime
+    def _encode(self):
+        self._assert_ready()
+        value = self._encode_time()
+        return b"".join((self.tag, len_encode(len(value)), value))
 
 
-        Pay attention that UTCTime can not hold full year, so all years
-        having < 50 years are treated as 20xx, 19xx otherwise, according
-        to X.509 recomendation.
-        """
-        value = self._strptime(self._value.decode("ascii"))
-        year = value.year % 100
-        return datetime(
-            year=(2000 + year) if year < 50 else (1900 + year),
-            month=value.month,
-            day=value.day,
-            hour=value.hour,
-            minute=value.minute,
-            second=value.second,
-        )
+    def todatetime(self):
+        return self._value
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -3894,7 +4384,7 @@ class UTCTime(CommonString):
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
-            value=self.todatetime().isoformat() if self.ready else None,
+            value=self._pp_value(),
             optional=self.optional,
             default=self == self.default,
             impl=None if self.tag == self.tag_default else tag_decode(self.tag),
             optional=self.optional,
             default=self == self.default,
             impl=None if self.tag == self.tag_default else tag_decode(self.tag),
@@ -3929,32 +4419,96 @@ class GeneralizedTime(UTCTime):
 
     .. warning::
 
 
     .. warning::
 
-       BER encoding is unsupported.
+       Only microsecond fractions are supported in DER encoding.
+       :py:exc:`pyderasn.DecodeError` will be raised during decoding of
+       higher precision values.
+
+    .. warning::
+
+       BER encoded data can loss information (accuracy) during decoding
+       because of float transformations.
 
     .. warning::
 
 
     .. warning::
 
-       Only microsecond fractions are supported.
-       :py:exc:`pyderasn.DecodeError` will be raised during decoding of
-       higher precision values.
+       Local times (without explicit timezone specification) are treated
+       as UTC one, no transformations are made.
+
+    .. warning::
+
+       Zero year is unsupported.
     """
     __slots__ = ()
     tag_default = tag_encode(24)
     asn1_type_name = "GeneralizedTime"
 
     """
     __slots__ = ()
     tag_default = tag_encode(24)
     asn1_type_name = "GeneralizedTime"
 
+    def _dt_sanitize(self, value):
+        return value
+
+    def _strptime_bered(self, value):
+        if len(value) < 4 + 3 * 2:
+            raise ValueError("invalid GeneralizedTime")
+        year, month, day, hour, _, _ = str_to_time_fractions(value[:10] + "0000")
+        decoded = datetime(year, month, day, hour)
+        offset, value = 0, value[10:]
+        if len(value) == 0:
+            return offset, decoded
+        if value[-1] == "Z":
+            value = value[:-1]
+        else:
+            for char, sign in (("-", -1), ("+", 1)):
+                idx = value.rfind(char)
+                if idx == -1:
+                    continue
+                offset_raw, value = value[idx + 1:].replace(":", ""), value[:idx]
+                v = pureint(offset_raw)
+                if len(offset_raw) == 4:
+                    offset, v = (60 * (v % 100)), v // 100
+                    if offset >= 3600:
+                        raise ValueError("invalid UTC offset minutes")
+                elif len(offset_raw) == 2:
+                    pass
+                else:
+                    raise ValueError("invalid UTC offset")
+                offset += 3600 * v
+                if offset > 14 * 3600:
+                    raise ValueError("too big UTC offset")
+                offset *= sign
+                break
+        if len(value) == 0:
+            return offset, decoded
+        if value[0] in DECIMAL_SIGNS:
+            return offset, (
+                decoded + timedelta(seconds=3600 * fractions2float(value[1:]))
+            )
+        if len(value) < 2:
+            raise ValueError("stripped minutes")
+        decoded += timedelta(seconds=60 * pureint(value[:2]))
+        value = value[2:]
+        if len(value) == 0:
+            return offset, decoded
+        if value[0] in DECIMAL_SIGNS:
+            return offset, (
+                decoded + timedelta(seconds=60 * fractions2float(value[1:]))
+            )
+        if len(value) < 2:
+            raise ValueError("stripped seconds")
+        decoded += timedelta(seconds=pureint(value[:2]))
+        value = value[2:]
+        if len(value) == 0:
+            return offset, decoded
+        if value[0] not in DECIMAL_SIGNS:
+            raise ValueError("invalid format after seconds")
+        return offset, (
+            decoded + timedelta(microseconds=10**6 * fractions2float(value[1:]))
+        )
+
     def _strptime(self, value):
         l = len(value)
         if l == LEN_YYYYMMDDHHMMSSZ:
     def _strptime(self, value):
         l = len(value)
         if l == LEN_YYYYMMDDHHMMSSZ:
-            # datetime.strptime's format: %y%m%d%H%M%SZ
+            # datetime.strptime's format: %Y%m%d%H%M%SZ
             if value[-1] != "Z":
                 raise ValueError("non UTC timezone")
             if value[-1] != "Z":
                 raise ValueError("non UTC timezone")
-            return datetime(
-                int(value[:4]),  # %Y
-                int(value[4:6]),  # %m
-                int(value[6:8]),  # %d
-                int(value[8:10]),  # %H
-                int(value[10:12]),  # %M
-                int(value[12:14]),  # %S
-            )
+            return datetime(*str_to_time_fractions(value[:-1]))
         if l >= LEN_YYYYMMDDHHMMSSDMZ:
             # datetime.strptime's format: %Y%m%d%H%M%S.%fZ
             if value[-1] != "Z":
         if l >= LEN_YYYYMMDDHHMMSSDMZ:
             # datetime.strptime's format: %Y%m%d%H%M%S.%fZ
             if value[-1] != "Z":
@@ -3967,44 +4521,17 @@ class GeneralizedTime(UTCTime):
             us_len = len(us)
             if us_len > 6:
                 raise ValueError("only microsecond fractions are supported")
             us_len = len(us)
             if us_len > 6:
                 raise ValueError("only microsecond fractions are supported")
-            us = int(us + ("0" * (6 - us_len)))
-            decoded = datetime(
-                int(value[:4]),  # %Y
-                int(value[4:6]),  # %m
-                int(value[6:8]),  # %d
-                int(value[8:10]),  # %H
-                int(value[10:12]),  # %M
-                int(value[12:14]),  # %S
-                us,  # %f
-            )
-            return decoded
+            us = pureint(us + ("0" * (6 - us_len)))
+            year, month, day, hour, minute, second = str_to_time_fractions(value[:14])
+            return datetime(year, month, day, hour, minute, second, us)
         raise ValueError("invalid GeneralizedTime length")
 
         raise ValueError("invalid GeneralizedTime length")
 
-    def _value_sanitize(self, value):
-        if isinstance(value, binary_type):
-            try:
-                value_decoded = value.decode("ascii")
-            except (UnicodeEncodeError, UnicodeDecodeError) as err:
-                raise DecodeError("invalid GeneralizedTime encoding: %r" % err)
-            try:
-                self._strptime(value_decoded)
-            except (TypeError, ValueError) as err:
-                raise DecodeError(
-                    "invalid GeneralizedTime format: %r" % err,
-                    klass=self.__class__,
-                )
-            return value
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            encoded = value.strftime("%Y%m%d%H%M%S")
-            if value.microsecond > 0:
-                encoded = encoded + (".%06d" % value.microsecond).rstrip("0")
-            return (encoded + "Z").encode("ascii")
-        raise InvalidValueType((self.__class__, datetime))
-
-    def todatetime(self):
-        return self._strptime(self._value.decode("ascii"))
+    def _encode_time(self):
+        value = self._value
+        encoded = value.strftime("%Y%m%d%H%M%S")
+        if value.microsecond > 0:
+            encoded += (".%06d" % value.microsecond).rstrip("0")
+        return (encoded + "Z").encode("ascii")
 
 
 class GraphicString(CommonString):
 
 
 class GraphicString(CommonString):
@@ -4014,13 +4541,6 @@ class GraphicString(CommonString):
     asn1_type_name = "GraphicString"
 
 
     asn1_type_name = "GraphicString"
 
 
-class VisibleString(CommonString):
-    __slots__ = ()
-    tag_default = tag_encode(26)
-    encoding = "ascii"
-    asn1_type_name = "VisibleString"
-
-
 class ISO646String(VisibleString):
     __slots__ = ()
     asn1_type_name = "ISO646String"
 class ISO646String(VisibleString):
     __slots__ = ()
     asn1_type_name = "ISO646String"
@@ -4047,6 +4567,23 @@ class BMPString(CommonString):
     asn1_type_name = "BMPString"
 
 
     asn1_type_name = "BMPString"
 
 
+ChoiceState = namedtuple("ChoiceState", (
+    "version",
+    "specs",
+    "value",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
+
+
 class Choice(Obj):
     """``CHOICE`` special type
 
 class Choice(Obj):
     """``CHOICE`` special type
 
@@ -4108,7 +4645,7 @@ class Choice(Obj):
         if len(schema) == 0:
             raise ValueError("schema must be specified")
         self.specs = (
         if len(schema) == 0:
             raise ValueError("schema must be specified")
         self.specs = (
-            schema if isinstance(schema, OrderedDict) else OrderedDict(schema)
+            schema if schema.__class__ == OrderedDict else OrderedDict(schema)
         )
         self._value = None
         if value is not None:
         )
         self._value = None
         if value is not None:
@@ -4120,10 +4657,10 @@ class Choice(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
-                self._value = default_obj.copy()._value
+                self._value = copy(default_obj._value)
 
     def _value_sanitize(self, value):
 
     def _value_sanitize(self, value):
-        if isinstance(value, tuple) and len(value) == 2:
+        if (value.__class__ == tuple) and len(value) == 2:
             choice, obj = value
             spec = self.specs.get(choice)
             if spec is None:
             choice, obj = value
             spec = self.specs.get(choice)
             if spec is None:
@@ -4146,24 +4683,39 @@ class Choice(Obj):
             self._value[1].bered
         )
 
             self._value[1].bered
         )
 
-    def copy(self):
-        obj = self.__class__(schema=self.specs)
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        value = self._value
-        if value is not None:
-            obj._value = (value[0], value[1].copy())
-        return obj
+    def __getstate__(self):
+        return ChoiceState(
+            __version__,
+            self.specs,
+            copy(self._value),
+            self.tag,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+        )
+
+    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):
 
     def __eq__(self, their):
-        if isinstance(their, tuple) and len(their) == 2:
+        if (their.__class__ == tuple) and len(their) == 2:
             return self._value == their
         if not isinstance(their, self.__class__):
             return False
             return self._value == their
         if not isinstance(their, self.__class__):
             return False
@@ -4250,7 +4802,7 @@ class Choice(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         value, tail = spec.decode(
             tlv,
             offset=offset,
         value, tail = spec.decode(
             tlv,
             offset=offset,
@@ -4304,9 +4856,9 @@ class PrimitiveTypes(Choice):
 
     It could be useful for general decoding of some unspecified values:
 
 
     It could be useful for general decoding of some unspecified values:
 
-    >>> PrimitiveTypes().decode(hexdec("0403666f6f"))[0].value
+    >>> PrimitiveTypes().decod(hexdec("0403666f6f")).value
     OCTET STRING 3 bytes 666f6f
     OCTET STRING 3 bytes 666f6f
-    >>> PrimitiveTypes().decode(hexdec("0203123456"))[0].value
+    >>> PrimitiveTypes().decod(hexdec("0203123456")).value
     INTEGER 1193046
     """
     __slots__ = ()
     INTEGER 1193046
     """
     __slots__ = ()
@@ -4334,6 +4886,22 @@ class PrimitiveTypes(Choice):
     ))
 
 
     ))
 
 
+AnyState = namedtuple("AnyState", (
+    "version",
+    "value",
+    "tag",
+    "expl",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+    "defined",
+), **NAMEDTUPLE_KWARGS)
+
+
 class Any(Obj):
     """``ANY`` special type
 
 class Any(Obj):
     """``ANY`` special type
 
@@ -4368,7 +4936,7 @@ class Any(Obj):
         self.defined = None
 
     def _value_sanitize(self, value):
         self.defined = None
 
     def _value_sanitize(self, value):
-        if isinstance(value, binary_type):
+        if value.__class__ == binary_type:
             return value
         if isinstance(value, self.__class__):
             return value._value
             return value
         if isinstance(value, self.__class__):
             return value._value
@@ -4388,22 +4956,38 @@ class Any(Obj):
             return False
         return self.defined[1].bered
 
             return False
         return self.defined[1].bered
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
+    def __getstate__(self):
+        return AnyState(
+            __version__,
+            self._value,
+            self.tag,
+            self._expl,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            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):
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             return self._value == their
         if issubclass(their.__class__, Any):
             return self._value == their._value
             return self._value == their
         if issubclass(their.__class__, Any):
             return self._value == their._value
@@ -4476,7 +5060,7 @@ class Any(Obj):
                 _decoded=(offset, 0, tlvlen),
             )
             obj.lenindef = True
                 _decoded=(offset, 0, tlvlen),
             )
             obj.lenindef = True
-            obj.tag = t
+            obj.tag = t.tobytes()
             return obj, v[EOC_LEN:]
         except DecodeError as err:
             raise err.__class__(
             return obj, v[EOC_LEN:]
         except DecodeError as err:
             raise err.__class__(
@@ -4500,7 +5084,7 @@ class Any(Obj):
             optional=self.optional,
             _decoded=(offset, 0, tlvlen),
         )
             optional=self.optional,
             _decoded=(offset, 0, tlvlen),
         )
-        obj.tag = t
+        obj.tag = t.tobytes()
         return obj, tail
 
     def __repr__(self):
         return obj, tail
 
     def __repr__(self):
@@ -4580,6 +5164,23 @@ def abs_decode_path(decode_path, rel_path):
     return 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)
+
+
 class Sequence(Obj):
     """``SEQUENCE`` structure type
 
 class Sequence(Obj):
     """``SEQUENCE`` structure type
 
@@ -4689,7 +5290,7 @@ class Sequence(Obj):
         if schema is None:
             schema = getattr(self, "schema", ())
         self.specs = (
         if schema is None:
             schema = getattr(self, "schema", ())
         self.specs = (
-            schema if isinstance(schema, OrderedDict) else OrderedDict(schema)
+            schema if schema.__class__ == OrderedDict else OrderedDict(schema)
         )
         self._value = {}
         if value is not None:
         )
         self._value = {}
         if value is not None:
@@ -4709,7 +5310,7 @@ class Sequence(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
-                self._value = default_obj.copy()._value
+                self._value = copy(default_obj._value)
 
     @property
     def ready(self):
 
     @property
     def ready(self):
@@ -4719,9 +5320,8 @@ class Sequence(Obj):
                 if spec.optional:
                     continue
                 return False
                 if spec.optional:
                     continue
                 return False
-            else:
-                if not value.ready:
-                    return False
+            if not value.ready:
+                return False
         return True
 
     @property
         return True
 
     @property
@@ -4730,20 +5330,37 @@ class Sequence(Obj):
             return True
         return any(value.bered for value in itervalues(self._value))
 
             return True
         return any(value.bered for value in itervalues(self._value))
 
-    def copy(self):
-        obj = self.__class__(schema=self.specs)
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        obj._value = {k: v.copy() for k, v in iteritems(self._value)}
-        return 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,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+        )
+
+    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__):
 
     def __eq__(self, their):
         if not isinstance(their, self.__class__):
@@ -4833,7 +5450,7 @@ class Sequence(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
@@ -4885,8 +5502,8 @@ class Sequence(Obj):
                     ctx=ctx,
                     _ctx_immutable=False,
                 )
                     ctx=ctx,
                     _ctx_immutable=False,
                 )
-            except TagMismatch:
-                if spec.optional:
+            except TagMismatch as err:
+                if (len(err.decode_path) == len(decode_path) + 1) and spec.optional:
                     continue
                 raise
 
                     continue
                 raise
 
@@ -5083,7 +5700,7 @@ class Set(Sequence):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
@@ -5199,17 +5816,37 @@ class Set(Sequence):
             tail = v[EOC_LEN:]
             obj.lenindef = True
         obj._value = values
             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
 
 
         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)
+
+
 class SequenceOf(Obj):
     """``SEQUENCE OF`` sequence type
 
 class SequenceOf(Obj):
     """``SEQUENCE OF`` sequence type
 
@@ -5255,13 +5892,7 @@ class SequenceOf(Obj):
             optional=False,
             _decoded=(0, 0, 0),
     ):
             optional=False,
             _decoded=(0, 0, 0),
     ):
-        super(SequenceOf, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super(SequenceOf, self).__init__(impl, expl, default, optional, _decoded)
         if schema is None:
             schema = getattr(self, "schema", None)
         if schema is None:
         if schema is None:
             schema = getattr(self, "schema", None)
         if schema is None:
@@ -5285,7 +5916,7 @@ class SequenceOf(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
-                self._value = default_obj.copy()._value
+                self._value = copy(default_obj._value)
 
     def _value_sanitize(self, value):
         if issubclass(value.__class__, SequenceOf):
 
     def _value_sanitize(self, value):
         if issubclass(value.__class__, SequenceOf):
@@ -5311,22 +5942,41 @@ class SequenceOf(Obj):
             return True
         return any(v.bered for v in self._value)
 
             return True
         return any(v.bered for v in self._value)
 
-    def copy(self):
-        obj = self.__class__(schema=self.spec)
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        obj._value = [v.copy() for v in self._value]
-        return 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,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+        )
+
+    def __setstate__(self, state):
+        super(SequenceOf, self).__setstate__(state)
+        self.spec = state.spec
+        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__):
 
     def __eq__(self, their):
         if isinstance(their, self.__class__):
@@ -5416,7 +6066,7 @@ class SequenceOf(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
@@ -5621,8 +6271,8 @@ def generic_decoder():  # pragma: no cover
             for pp in pps:
                 if hasattr(pp, "_fields"):
                     if (
             for pp in pps:
                 if hasattr(pp, "_fields"):
                     if (
-                        decode_path_only != () and
-                        pp.decode_path[:len(decode_path_only)] != decode_path_only
+                            decode_path_only != () and
+                            pp.decode_path[:len(decode_path_only)] != decode_path_only
                     ):
                         continue
                     if pp.asn1_type_name == Choice.asn1_type_name:
                     ):
                         continue
                     if pp.asn1_type_name == Choice.asn1_type_name:
@@ -5640,8 +6290,8 @@ def generic_decoder():  # pragma: no cover
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
-                        pp,
-                        decode_path_len_decrease=len(decode_path_only),
+                            pp,
+                            decode_path_len_decrease=len(decode_path_only),
                     ):
                         yield row
                 else:
                     ):
                         yield row
                 else:
@@ -5700,7 +6350,10 @@ def main():  # pragma: no cover
     args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
     args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
-    oid_maps = [obj_by_path(_path) for _path in (args.oids or "").split(",")]
+    oid_maps = (
+        [obj_by_path(_path) for _path in (args.oids or "").split(",")]
+        if args.oids else ()
+    )
     if args.schema:
         schema = obj_by_path(args.schema)
         from functools import partial
     if args.schema:
         schema = obj_by_path(args.schema)
         from functools import partial
@@ -5717,7 +6370,7 @@ def main():  # pragma: no cover
     print(pprinter(
         obj,
         oid_maps=oid_maps,
     print(pprinter(
         obj,
         oid_maps=oid_maps,
-        with_colours=True if environ.get("NO_COLOR") is None else False,
+        with_colours=environ.get("NO_COLOR") is None,
         with_decode_path=args.print_decode_path,
         decode_path_only=(
             () if args.decode_path_only is None else
         with_decode_path=args.print_decode_path,
         decode_path_only=(
             () if args.decode_path_only is None else