]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Faster Set readiness determining during decode
[pyderasn.git] / pyderasn.py
index 717c06cce42d63a15ed38356f71eb92cce217076..ed5b763ccef4123c790d52569d9b376ef2acf168 100755 (executable)
@@ -1,12 +1,12 @@
 #!/usr/bin/env python
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
-# Copyright (C) 2017 Sergey Matveev <stargrave@stargrave.org>
+# cython: language_level=3
+# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
+# Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # 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/>.
-"""Python ASN.1 DER codec with abstract structures
+# 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 and unmarshal various structures in
-ASN.1 DER format, like this:
+This library allows you to marshal various structures in ASN.1 DER
+format, unmarshal them in BER/CER/DER ones.
 
     >>> i = Integer(123)
     >>> raw = i.encode()
-    >>> Integer().decode(raw) == i
+    >>> Integer().decod(raw) == i
     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
-either ``IMPLICIT`` (using ``impl`` keyword argument), or
-``EXPLICIT`` one (using ``expl`` keyword argument). Both arguments takes
-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``
@@ -88,10 +88,10 @@ number. Pay attention that explicit tags always have *constructed* tag
 
 Implicit tag is not explicitly shown.
 
-Two object of the same type, but with different implicit/explicit tags
+Two objects of the same type, but with different implicit/explicit tags
 are **not** equal.
 
-You can get objects effective tag (either default or implicited) through
+You can get object's effective tag (either default or implicited) through
 ``tag`` property. You can decode it using :py:func:`pyderasn.tag_decode`
 function::
 
@@ -159,25 +159,31 @@ raised.
 Common methods
 ______________
 
-All objects have ``ready`` boolean property, that tells if it is 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 ``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.
 
-All objects have ``copy()`` method, returning its copy, that can be safely
-mutated.
+All objects are friendly to ``copy.copy()`` and copied objects can be
+safely mutated.
+
+Also all objects can be safely ``pickle``-d, but pay attention that
+pickling among different PyDERASN versions is prohibited.
 
 .. _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.
 
+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:
 
@@ -189,11 +195,35 @@ use following properties:
 
 Pay attention that those values do **not** include anything related to
 explicit tag. If you want to know information about it, then use:
-``expled`` (to know if explicit tag is set), ``expl_offset`` (it is
-lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen``
-(that actually equals to ordinary ``tlvlen``).
 
-When error occurs, then :py:exc:`pyderasn.DecodeError` is raised.
+* ``expled`` -- to know if explicit tag is set
+* ``expl_offset`` (it is lesser than ``offset``)
+* ``expl_tlen``,
+* ``expl_llen``
+* ``expl_vlen`` (that actually equals to ordinary ``tlvlen``)
+* ``fulloffset`` -- it equals to ``expl_offset`` if explicit tag is set,
+  ``offset`` otherwise
+* ``fulllen`` -- it equals to ``expl_len`` if explicit tag is set,
+  ``tlvlen`` otherwise
+
+When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
+
+.. _ctx:
+
+Context
+_______
+
+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:
+
+* :ref:`allow_default_values <allow_default_values_ctx>`
+* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
+* :ref:`allow_unordered_set <allow_unordered_set_ctx>`
+* :ref:`bered <bered_ctx>`
+* :ref:`defines_by_path <defines_by_path_ctx>`
 
 .. _pprinting:
 
@@ -215,6 +245,106 @@ all object ``repr``. But it is easy to write custom formatters.
     >>> print(pprint(obj))
         0   [1,1,   2] INTEGER -12345
 
+.. _pprint_example:
+
+Example certificate::
+
+    >>> print(pprint(crt))
+        0   [1,3,1604] Certificate SEQUENCE
+        4   [1,3,1453]  . tbsCertificate: TBSCertificate SEQUENCE
+       10-2 [1,1,   1]  . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL
+       13   [1,1,   3]  . . serialNumber: CertificateSerialNumber INTEGER 61595
+       18   [1,1,  13]  . . signature: AlgorithmIdentifier SEQUENCE
+       20   [1,1,   9]  . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+       31   [0,0,   2]  . . . parameters: [UNIV 5] ANY OPTIONAL
+                        . . . . 05:00
+       33   [0,0, 278]  . . issuer: Name CHOICE rdnSequence
+       33   [1,3, 274]  . . . rdnSequence: RDNSequence SEQUENCE OF
+       37   [1,1,  11]  . . . . 0: RelativeDistinguishedName SET OF
+       39   [1,1,   9]  . . . . . 0: AttributeTypeAndValue SEQUENCE
+       41   [1,1,   3]  . . . . . . type: AttributeType OBJECT IDENTIFIER 2.5.4.6
+       46   [0,0,   4]  . . . . . . value: [UNIV 19] AttributeValue ANY
+                        . . . . . . . 13:02:45:53
+    [...]
+     1461   [1,1,  13]  . signatureAlgorithm: AlgorithmIdentifier SEQUENCE
+     1463   [1,1,   9]  . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+     1474   [0,0,   2]  . . parameters: [UNIV 5] ANY OPTIONAL
+                        . . . 05:00
+     1476   [1,2, 129]  . signatureValue: BIT STRING 1024 bits
+                        . . 68:EE:79:97:97:DD:3B:EF:16:6A:06:F2:14:9A:6E:CD
+                        . . 9E:12:F7:AA:83:10:BD:D1:7C:98:FA:C7:AE:D4:0E:2C
+     [...]
+
+    Trailing data: 0a
+
+Let's parse that output, human::
+
+       10-2 [1,1,   1]    . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL
+       ^  ^  ^ ^    ^     ^   ^        ^            ^       ^       ^  ^
+       0  1  2 3    4     5   6        7            8       9       10 11
+
+::
+
+       20   [1,1,   9]    . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+       ^     ^ ^    ^     ^     ^          ^                 ^
+       0     2 3    4     5     6          9                 10
+
+::
+
+       33   [0,0, 278]    . . issuer: Name CHOICE rdnSequence
+       ^     ^ ^    ^     ^   ^       ^    ^      ^
+       0     2 3    4     5   6       8    9      10
+
+::
+
+       52-2∞ B [1,1,1054]∞  . . . . eContent: [0] EXPLICIT BER OCTET STRING 1046 bytes
+             ^           ^                                 ^   ^            ^
+            12          13                                14   9            10
+
+:0:
+ Offset of the object, where its DER/BER encoding begins.
+ Pay attention that it does **not** include explicit tag.
+:1:
+ If explicit tag exists, then this is its length (tag + encoded length).
+:2:
+ Length of object's tag. For example CHOICE does not have its own tag,
+ so it is zero.
+:3:
+ Length of encoded length.
+:4:
+ Length of encoded value.
+:5:
+ Visual indentation to show the depth of object in the hierarchy.
+:6:
+ Object's name inside SEQUENCE/CHOICE.
+:7:
+ If either IMPLICIT or EXPLICIT tag is set, then it will be shown
+ here. "IMPLICIT" is omitted.
+:8:
+ Object's class name, if set. Omitted if it is just an ordinary simple
+ value (like with ``algorithm`` in example above).
+:9:
+ Object's ASN.1 type.
+:10:
+ Object's value, if set. Can consist of multiple words (like OCTET/BIT
+ STRINGs above). We see ``v3`` value in Version, because it is named.
+ ``rdnSequence`` is the choice of CHOICE type.
+:11:
+ Possible other flags like OPTIONAL and DEFAULT, if value equals to the
+ default one, specified in the schema.
+:12:
+ Shows does object contains any kind of BER encoded data (possibly
+ Sequence holding BER-encoded underlying value).
+:13:
+ Only applicable to BER encoded data. Indefinite length encoding mark.
+:14:
+ 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``, ``UTCTime``, ``GeneralizedTime``
+ could be BERed.
+
+
 .. _definedby:
 
 DEFINED BY
@@ -225,20 +355,22 @@ 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.
 
+.. _defines:
+
 defines kwarg
 _____________
 
 :py:class:`pyderasn.ObjectIdentifier` field inside
 :py:class:`pyderasn.Sequence` can hold mapping between OIDs and
-necessary for decoding structrures. For example, CMS (:rfc:`5652`)
+necessary for decoding structures. For example, CMS (:rfc:`5652`)
 container::
 
     class ContentInfo(Sequence):
         schema = (
-            ("contentType", ContentType(defines=("content", {
+            ("contentType", ContentType(defines=((("content",), {
                 id_digestedData: DigestedData(),
                 id_signedData: SignedData(),
-            }))),
+            }),))),
             ("content", Any(expl=tag_ctxc(0))),
         )
 
@@ -248,82 +380,156 @@ decoded with ``SignedData`` specification, if ``contentType`` equals to
 ``contentType`` contains unknown OID, then no automatic decoding is
 done.
 
+You can specify multiple fields, that will be autodecoded -- that is why
+``defines`` kwarg is a sequence. You can specify defined field
+relatively or absolutely to current decode path. For example ``defines``
+for AlgorithmIdentifier of X.509's
+``tbsCertificate:subjectPublicKeyInfo:algorithm:algorithm``::
+
+        (
+            (("parameters",), {
+                id_ecPublicKey: ECParameters(),
+                id_GostR3410_2001: GostR34102001PublicKeyParameters(),
+            }),
+            (("..", "subjectPublicKey"), {
+                id_rsaEncryption: RSAPublicKey(),
+                id_GostR3410_2001: OctetString(),
+            }),
+        ),
+
+tells that if certificate's SPKI algorithm is GOST R 34.10-2001, then
+autodecode its parameters inside SPKI's algorithm and its public key
+itself.
+
 Following types can be automatically decoded (DEFINED BY):
 
 * :py:class:`pyderasn.Any`
+* :py:class:`pyderasn.BitString` (that is multiple of 8 bits)
 * :py:class:`pyderasn.OctetString`
 * :py:class:`pyderasn.SequenceOf`/:py:class:`pyderasn.SetOf`
-  ``Any``/``OctetString``-s
+  ``Any``/``BitString``/``OctetString``-s
 
 When any of those fields is automatically decoded, then ``.defined``
-attribute contains ``(OID, value)`` tuple. OID tell by which OID it was
-defined, ``value`` contains corresponding decoded value. For example
-above, ``content_info["content"].defined == (id_signedData,
-signed_data)``.
+attribute contains ``(OID, value)`` tuple. ``OID`` tells by which OID it
+was defined, ``value`` contains corresponding decoded value. For example
+above, ``content_info["content"].defined == (id_signedData, signed_data)``.
 
-defines_by_path kwarg
-_____________________
+.. _defines_by_path_ctx:
+
+defines_by_path context option
+______________________________
 
 Sometimes you either can not or do not want to explicitly set *defines*
 in the scheme. You can dynamically apply those definitions when calling
 ``.decode()`` method.
 
-Decode method takes optional ``defines_by_path`` keyword argument that
-must be sequence of following tuples::
+Specify ``defines_by_path`` key in the :ref:`decode context <ctx>`. Its
+value must be sequence of following tuples::
 
     (decode_path, defines)
 
 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``::
 
-    content_info, tail = ContentInfo().decode(data, defines_by_path=(
+    content_info = ContentInfo().decod(data, ctx={"defines_by_path": (
         (
             ("contentType",),
-            ("content", {id_signedData: SignedData()}),
+            ((("content",), {id_signedData: SignedData()}),),
         ),
         (
             (
                 "content",
-                decode_path_defby(id_signedData),
+                DecodePathDefBy(id_signedData),
                 "encapContentInfo",
                 "eContentType",
             ),
-            ("eContent", {
+            ((("eContent",), {
                 id_cct_PKIData: PKIData(),
                 id_cct_PKIResponse: PKIResponse(),
-            }),
+            })),
         ),
         (
             (
                 "content",
-                decode_path_defby(id_signedData),
+                DecodePathDefBy(id_signedData),
                 "encapContentInfo",
                 "eContent",
-                decode_path_defby(id_cct_PKIResponse),
+                DecodePathDefBy(id_cct_PKIResponse),
                 "controlSequence",
                 any,
                 "attrType",
             ),
-            ("attrValues", {
+            ((("attrValues",), {
                 id_cmc_recipientNonce: RecipientNonce(),
                 id_cmc_senderNonce: SenderNonce(),
                 id_cmc_statusInfoV2: CMCStatusInfoV2(),
                 id_cmc_transactionId: TransactionId(),
-            }),
+            })),
         ),
-    ))
+    )})
 
-Pay attention for :py:func:`pyderasn.decode_path_defby` and ``any``.
+Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
 First function is useful for path construction when some automatic
-decoding is already done. ``any`` is used for human readability and
-means literally any value it meet -- useful for sequence and set of-s.
+decoding is already done. ``any`` means literally any value it meet --
+useful for SEQUENCE/SET OF-s.
+
+.. _bered_ctx:
+
+BER encoding
+------------
+
+By default PyDERASN accepts only DER encoded data. It always encodes to
+DER. But you can optionally enable BER decoding with setting ``bered``
+:ref:`context <ctx>` argument to True. Indefinite lengths and
+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
+  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
+  contain it.
+* If object has an indefinite length encoded explicit tag, then
+  ``expl_lenindef`` is set to True.
+* If object has either any of BER-related encoding (explicit tag
+  indefinite length, object's indefinite length, BER-encoding) or any
+  underlying component has that kind of encoding, then ``bered``
+  attribute is set to True. For example SignedData CMS can have
+  ``ContentInfo:content:signerInfos:*`` ``bered`` value set to True, but
+  ``ContentInfo:content:signerInfos:*:signedAttrs`` won't.
+
+EOC (end-of-contents) token's length is taken in advance in object's
+value length.
+
+.. _allow_expl_oob_ctx:
+
+Allow explicit tag out-of-bound
+-------------------------------
+
+Invalid BER encoding could contain ``EXPLICIT`` tag containing more than
+one value, more than one object. If you set ``allow_expl_oob`` context
+option to True, then no error will be raised and that invalid encoding
+will be silently further processed. But pay attention that offsets and
+lengths will be invalid in that case.
+
+.. warning::
+
+   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
 ---------------
@@ -366,6 +572,15 @@ CommonString
 ____________
 .. autoclass:: pyderasn.CommonString
 
+NumericString
+_____________
+.. autoclass:: pyderasn.NumericString
+
+PrintableString
+_______________
+.. autoclass:: pyderasn.PrintableString
+   :members: __init__
+
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
@@ -418,21 +633,40 @@ _____
 Various
 -------
 
+.. autofunction:: pyderasn.abs_decode_path
+.. autofunction:: pyderasn.colonize_hex
 .. autofunction:: pyderasn.hexenc
 .. autofunction:: pyderasn.hexdec
 .. autofunction:: pyderasn.tag_encode
 .. 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.ExceedingData
+.. autoclass:: pyderasn.LenIndefForm
+.. autoclass:: pyderasn.TagMismatch
+.. autoclass:: pyderasn.InvalidLength
+.. autoclass:: pyderasn.InvalidOID
+.. autoclass:: pyderasn.ObjUnknown
+.. autoclass:: pyderasn.ObjNotReady
+.. autoclass:: pyderasn.InvalidValueType
+.. autoclass:: pyderasn.BoundsError
 """
 
 from codecs import getdecoder
 from codecs import getencoder
 from collections import namedtuple
 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 unicodedata import category as unicat
 
 from six import add_metaclass
 from six import binary_type
@@ -441,12 +675,23 @@ from six import indexbytes
 from six import int2byte
 from six import integer_types
 from six import iterbytes
+from six import iteritems
+from six import itervalues
 from six import PY2
 from six import string_types
 from six import text_type
+from six import unichr as six_unichr
 from six.moves import xrange as six_xrange
 
 
+try:
+    from termcolor import colored
+except ImportError:  # pragma: no cover
+    def colored(what, *args, **kwargs):
+        return what
+
+__version__ = "6.2"
+
 __all__ = (
     "Any",
     "BitString",
@@ -454,9 +699,10 @@ __all__ = (
     "Boolean",
     "BoundsError",
     "Choice",
-    "decode_path_defby",
     "DecodeError",
+    "DecodePathDefBy",
     "Enumerated",
+    "ExceedingData",
     "GeneralizedTime",
     "GeneralString",
     "GraphicString",
@@ -468,6 +714,7 @@ __all__ = (
     "InvalidOID",
     "InvalidValueType",
     "ISO646String",
+    "LenIndefForm",
     "NotEnoughData",
     "Null",
     "NumericString",
@@ -513,13 +760,35 @@ TagClassReprs = {
     TagClassPrivate: "PRIVATE ",
     TagClassUniversal: "UNIV ",
 }
+EOC = b"\x00\x00"
+EOC_LEN = len(EOC)
+LENINDEF = b"\x80"  # length indefinite mark
+LENINDEF_PP_CHAR = "I" if PY2 else "∞"
+NAMEDTUPLE_KWARGS = {} if PY2 else {"module": __name__}
+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)
 
 
 ########################################################################
 # Errors
 ########################################################################
 
-class DecodeError(Exception):
+class ASN1Error(ValueError):
+    pass
+
+
+class DecodeError(ASN1Error):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
         """
         :param str msg: reason of decode failing
@@ -542,7 +811,7 @@ class DecodeError(Exception):
             c for c in (
                 "" if self.klass is None else self.klass.__name__,
                 (
-                    ("(%s)" % ".".join(self.decode_path))
+                    ("(%s)" % ":".join(str(dp) for dp in self.decode_path))
                     if len(self.decode_path) > 0 else ""
                 ),
                 ("(at %d)" % self.offset) if self.offset > 0 else "",
@@ -558,6 +827,22 @@ class NotEnoughData(DecodeError):
     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 TagMismatch(DecodeError):
     pass
 
@@ -570,7 +855,7 @@ class InvalidOID(DecodeError):
     pass
 
 
-class ObjUnknown(ValueError):
+class ObjUnknown(ASN1Error):
     def __init__(self, name):
         super(ObjUnknown, self).__init__()
         self.name = name
@@ -582,7 +867,7 @@ class ObjUnknown(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class ObjNotReady(ValueError):
+class ObjNotReady(ASN1Error):
     def __init__(self, name):
         super(ObjNotReady, self).__init__()
         self.name = name
@@ -594,7 +879,7 @@ class ObjNotReady(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class InvalidValueType(ValueError):
+class InvalidValueType(ASN1Error):
     def __init__(self, expected_types):
         super(InvalidValueType, self).__init__()
         self.expected_types = expected_types
@@ -608,7 +893,7 @@ class InvalidValueType(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class BoundsError(ValueError):
+class BoundsError(ASN1Error):
     def __init__(self, bound_min, value, bound_max):
         super(BoundsError, self).__init__()
         self.bound_min = bound_min
@@ -749,6 +1034,11 @@ def len_encode(l):
 
 
 def len_decode(data):
+    """Decode length
+
+    :returns: (decoded length, length's length, remaining data)
+    :raises LenIndefForm: if indefinite form encoding is met
+    """
     if len(data) == 0:
         raise NotEnoughData("no data at all")
     first_octet = byte2int(data)
@@ -758,7 +1048,7 @@ def len_decode(data):
     if octets_num + 1 > len(data):
         raise NotEnoughData("encoded length is longer than data")
     if octets_num == 0:
-        raise DecodeError("long form instead of short one")
+        raise LenIndefForm()
     if byte2int(data[1:]) == 0:
         raise DecodeError("leading zeros")
     l = 0
@@ -795,6 +1085,9 @@ class Obj(object):
         "offset",
         "llen",
         "vlen",
+        "expl_lenindef",
+        "lenindef",
+        "ber_encoded",
     )
 
     def __init__(
@@ -808,14 +1101,15 @@ class Obj(object):
         self.tag = getattr(self, "impl", self.tag_default) if impl is None else impl
         self._expl = getattr(self, "expl", None) if expl is None else expl
         if self.tag != self.tag_default and self._expl is not None:
-            raise ValueError(
-                "implicit and explicit tags can not be set simultaneously"
-            )
+            raise ValueError("implicit and explicit tags can not be set simultaneously")
         if default is not None:
             optional = True
         self.optional = optional
         self.offset, self.llen, self.vlen = _decoded
         self.default = None
+        self.expl_lenindef = False
+        self.lenindef = False
+        self.ber_encoded = False
 
     @property
     def ready(self):  # pragma: no cover
@@ -827,23 +1121,48 @@ class Obj(object):
         if not self.ready:
             raise ObjNotReady(self.__class__.__name__)
 
+    @property
+    def bered(self):
+        """Is either object or any elements inside is BER encoded?
+        """
+        return self.expl_lenindef or self.lenindef or self.ber_encoded
+
     @property
     def decoded(self):
         """Is object decoded?
         """
         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()
 
+    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):
+        """See :ref:`decoding`
+        """
         return len(self.tag)
 
     @property
     def tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
@@ -864,33 +1183,66 @@ class Obj(object):
     def _encode(self):  # pragma: no cover
         raise NotImplementedError()
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):  # pragma: no cover
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):  # pragma: no cover
         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))
 
-    def decode(self, data, offset=0, leavemm=False, decode_path=(), defines_by_path=None):
+    def hexencode(self):
+        """Do hexadecimal encoded :py:meth:`pyderasn.Obj.encode`
+        """
+        return hexenc(self.encode())
+
+    def decode(
+            self,
+            data,
+            offset=0,
+            leavemm=False,
+            decode_path=(),
+            ctx=None,
+            tag_only=False,
+            _ctx_immutable=True,
+    ):
         """Decode the data
 
         :param data: either binary or memoryview
         :param int offset: initial data's offset
         :param bool leavemm: do we need to leave memoryview of remaining
                     data as is, or convert it to bytes otherwise
-        :param defines_by_path: :ref:`Read about DEFINED BY <definedby>`
+        :param ctx: optional :ref:`context <ctx>` governing decoding process
+        :param tag_only: decode only the tag, without length and contents
+                         (used only in Choice and Set structures, trying to
+                         determine if tag satisfies the scheme)
+        :param _ctx_immutable: do we need to ``copy.copy()`` ``ctx``
+                               before using it?
         :returns: (Obj, remaining data)
+
+        .. seealso:: :ref:`decoding`
         """
+        if ctx is None:
+            ctx = {}
+        elif _ctx_immutable:
+            ctx = copy(ctx)
         tlv = memoryview(data)
         if self._expl is None:
-            obj, tail = self._decode(
+            result = self._decode(
                 tlv,
                 offset,
                 decode_path=decode_path,
-                defines_by_path=defines_by_path,
+                ctx=ctx,
+                tag_only=tag_only,
             )
+            if tag_only:
+                return None
+            obj, tail = result
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
@@ -909,6 +1261,36 @@ class Obj(object):
                 )
             try:
                 l, llen, v = len_decode(lv)
+            except LenIndefForm as err:
+                if not ctx.get("bered", False):
+                    raise err.__class__(
+                        msg=err.msg,
+                        klass=self.__class__,
+                        decode_path=decode_path,
+                        offset=offset,
+                    )
+                llen, v = 1, lv[1:]
+                offset += tlen + llen
+                result = self._decode(
+                    v,
+                    offset=offset,
+                    decode_path=decode_path,
+                    ctx=ctx,
+                    tag_only=tag_only,
+                )
+                if tag_only:  # pragma: no cover
+                    return None
+                obj, tail = result
+                eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
+                if eoc_expected.tobytes() != EOC:
+                    raise DecodeError(
+                        "no EOC",
+                        klass=self.__class__,
+                        decode_path=decode_path,
+                        offset=offset,
+                    )
+                obj.vlen += EOC_LEN
+                obj.expl_lenindef = True
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -916,54 +1298,173 @@ class Obj(object):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if l > len(v):
-                raise NotEnoughData(
-                    "encoded length is longer than data",
-                    klass=self.__class__,
+            else:
+                if l > len(v):
+                    raise NotEnoughData(
+                        "encoded length is longer than data",
+                        klass=self.__class__,
+                        decode_path=decode_path,
+                        offset=offset,
+                    )
+                result = self._decode(
+                    v,
+                    offset=offset + tlen + llen,
                     decode_path=decode_path,
-                    offset=offset,
+                    ctx=ctx,
+                    tag_only=tag_only,
                 )
-            obj, tail = self._decode(
-                v,
-                offset=offset + tlen + llen,
-                decode_path=decode_path,
-                defines_by_path=defines_by_path,
-            )
+                if tag_only:  # pragma: no cover
+                    return None
+                obj, tail = result
+                if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
+                    raise DecodeError(
+                        "explicit tag out-of-bound, longer than data",
+                        klass=self.__class__,
+                        decode_path=decode_path,
+                        offset=offset,
+                    )
         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):
+        """See :ref:`decoding`
+        """
         return self._expl is not None
 
     @property
     def expl_tag(self):
+        """See :ref:`decoding`
+        """
         return self._expl
 
     @property
     def expl_tlen(self):
+        """See :ref:`decoding`
+        """
         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):
+        """See :ref:`decoding`
+        """
         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):
+        """See :ref:`decoding`
+        """
         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):
+        """See :ref:`decoding`
+        """
+        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
+        ):
+            yield _pp(
+                asn1_type_name="EOC",
+                obj_name="",
+                decode_path=decode_path,
+                offset=(
+                    self.offset + self.tlvlen -
+                    (EOC_LEN * 2 if self.expl_lenindef else EOC_LEN)
+                ),
+                tlen=1,
+                llen=1,
+                vlen=0,
+                ber_encoded=True,
+                bered=True,
+            )
+        if self.expl_lenindef:
+            yield _pp(
+                asn1_type_name="EOC",
+                obj_name="EXPLICIT",
+                decode_path=decode_path,
+                offset=self.expl_offset + self.expl_tlvlen - EOC_LEN,
+                tlen=1,
+                llen=1,
+                vlen=0,
+                ber_encoded=True,
+                bered=True,
+            )
+
 
-def decode_path_defby(defined_by):
+class DecodePathDefBy(object):
     """DEFINED BY representation inside decode path
     """
-    return "DEFINED BY (%s)" % defined_by
+    __slots__ = ("defined_by",)
+
+    def __init__(self, defined_by):
+        self.defined_by = defined_by
+
+    def __ne__(self, their):
+        return not(self == their)
+
+    def __eq__(self, their):
+        if not isinstance(their, self.__class__):
+            return False
+        return self.defined_by == their.defined_by
+
+    def __str__(self):
+        return "DEFINED BY " + str(self.defined_by)
+
+    def __repr__(self):
+        return "<%s: %s>" % (self.__class__.__name__, self.defined_by)
 
 
 ########################################################################
@@ -971,6 +1472,7 @@ def decode_path_defby(defined_by):
 ########################################################################
 
 PP = namedtuple("PP", (
+    "obj",
     "asn1_type_name",
     "obj_name",
     "decode_path",
@@ -988,10 +1490,15 @@ PP = namedtuple("PP", (
     "expl_tlen",
     "expl_llen",
     "expl_vlen",
-))
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+    "bered",
+), **NAMEDTUPLE_KWARGS)
 
 
 def _pp(
+        obj=None,
         asn1_type_name="unknown",
         obj_name="unknown",
         decode_path=(),
@@ -1009,8 +1516,13 @@ def _pp(
         expl_tlen=None,
         expl_llen=None,
         expl_vlen=None,
+        expl_lenindef=False,
+        lenindef=False,
+        ber_encoded=False,
+        bered=False,
 ):
     return PP(
+        obj,
         asn1_type_name,
         obj_name,
         decode_path,
@@ -1028,94 +1540,197 @@ def _pp(
         expl_tlen,
         expl_llen,
         expl_vlen,
+        expl_lenindef,
+        lenindef,
+        ber_encoded,
+        bered,
     )
 
 
-def pp_console_row(pp, oids=None, with_offsets=False, with_blob=True):
+def _colourize(what, colour, with_colours, attrs=("bold",)):
+    return colored(what, colour, attrs=attrs) if with_colours else what
+
+
+def colonize_hex(hexed):
+    """Separate hexadecimal string with colons
+    """
+    return ":".join(hexed[i:i + 2] for i in six_xrange(0, len(hexed), 2))
+
+
+def pp_console_row(
+        pp,
+        oid_maps=(),
+        with_offsets=False,
+        with_blob=True,
+        with_colours=False,
+        with_decode_path=False,
+        decode_path_len_decrease=0,
+):
     cols = []
     if with_offsets:
-        cols.append("%5d%s [%d,%d,%4d]" % (
+        col = "%5d%s%s" % (
             pp.offset,
             (
                 "  " if pp.expl_offset is None else
                 ("-%d" % (pp.offset - pp.expl_offset))
             ),
+            LENINDEF_PP_CHAR if pp.expl_lenindef else " ",
+        )
+        col = _colourize(col, "red", with_colours, ())
+        col += _colourize("B", "red", with_colours) if pp.bered else " "
+        cols.append(col)
+        col = "[%d,%d,%4d]%s" % (
             pp.tlen,
             pp.llen,
             pp.vlen,
-        ))
-    if len(pp.decode_path) > 0:
-        cols.append(" ." * (len(pp.decode_path)))
-        cols.append("%s:" % pp.decode_path[-1])
+            LENINDEF_PP_CHAR if pp.lenindef else " "
+        )
+        col = _colourize(col, "green", with_colours, ())
+        cols.append(col)
+    decode_path_len = len(pp.decode_path) - decode_path_len_decrease
+    if decode_path_len > 0:
+        cols.append(" ." * decode_path_len)
+        ent = pp.decode_path[-1]
+        if isinstance(ent, DecodePathDefBy):
+            cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
+            value = str(ent.defined_by)
+            oid_name = None
+            if (
+                    len(oid_maps) > 0 and
+                    ent.defined_by.asn1_type_name ==
+                    ObjectIdentifier.asn1_type_name
+            ):
+                for oid_map in oid_maps:
+                    oid_name = oid_map.get(value)
+                    if oid_name is not None:
+                        cols.append(_colourize("%s:" % oid_name, "green", with_colours))
+                        break
+            if oid_name is None:
+                cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
+        else:
+            cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
     if pp.expl is not None:
         klass, _, num = pp.expl
-        cols.append("[%s%d] EXPLICIT" % (TagClassReprs[klass], num))
+        col = "[%s%d] EXPLICIT" % (TagClassReprs[klass], num)
+        cols.append(_colourize(col, "blue", with_colours))
     if pp.impl is not None:
         klass, _, num = pp.impl
-        cols.append("[%s%d]" % (TagClassReprs[klass], num))
+        col = "[%s%d]" % (TagClassReprs[klass], num)
+        cols.append(_colourize(col, "blue", with_colours))
     if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
-        cols.append(pp.obj_name)
-    cols.append(pp.asn1_type_name)
+        cols.append(_colourize(pp.obj_name, "magenta", with_colours))
+    if pp.ber_encoded:
+        cols.append(_colourize("BER", "red", with_colours))
+    cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours))
     if pp.value is not None:
         value = pp.value
+        cols.append(_colourize(value, "white", with_colours, ("reverse",)))
         if (
-                oids is not None and
-                pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
-                value in oids
+                len(oid_maps) > 0 and
+                pp.asn1_type_name == ObjectIdentifier.asn1_type_name
         ):
-            value = "%s (%s)" % (oids[value], pp.value)
-        cols.append(value)
+            for oid_map in oid_maps:
+                oid_name = oid_map.get(value)
+                if oid_name is not None:
+                    cols.append(_colourize("(%s)" % oid_name, "green", with_colours))
+                    break
+        if pp.asn1_type_name == Integer.asn1_type_name:
+            hex_repr = hex(int(pp.obj._value))[2:].upper()
+            if len(hex_repr) % 2 != 0:
+                hex_repr = "0" + hex_repr
+            cols.append(_colourize(
+                "(%s)" % colonize_hex(hex_repr),
+                "green",
+                with_colours,
+            ))
     if with_blob:
-        if isinstance(pp.blob, binary_type):
+        if pp.blob.__class__ == binary_type:
             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("OPTIONAL")
+        cols.append(_colourize("OPTIONAL", "red", with_colours))
     if pp.default:
-        cols.append("DEFAULT")
+        cols.append(_colourize("DEFAULT", "red", with_colours))
+    if with_decode_path:
+        cols.append(_colourize(
+            "[%s]" % ":".join(str(p) for p in pp.decode_path),
+            "grey",
+            with_colours,
+        ))
     return " ".join(cols)
 
 
-def pp_console_blob(pp):
-    cols = [" " * len("XXXXXYY [X,X,XXXX]")]
-    if len(pp.decode_path) > 0:
-        cols.append(" ." * (len(pp.decode_path) + 1))
-    if isinstance(pp.blob, binary_type):
+def pp_console_blob(pp, decode_path_len_decrease=0):
+    cols = [" " * len("XXXXXYYZZ [X,X,XXXX]Z")]
+    decode_path_len = len(pp.decode_path) - decode_path_len_decrease
+    if decode_path_len > 0:
+        cols.append(" ." * (decode_path_len + 1))
+    if pp.blob.__class__ == binary_type:
         blob = hexenc(pp.blob).upper()
-        for i in range(0, len(blob), 32):
+        for i in six_xrange(0, len(blob), 32):
             chunk = blob[i:i + 32]
-            yield " ".join(cols + [":".join(
-                chunk[j:j + 2] for j in range(0, len(chunk), 2)
-            )])
-    elif isinstance(pp.blob, tuple):
+            yield " ".join(cols + [colonize_hex(chunk)])
+    elif pp.blob.__class__ == tuple:
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
-def pprint(obj, oids=None, big_blobs=False):
+def pprint(
+        obj,
+        oid_maps=(),
+        big_blobs=False,
+        with_colours=False,
+        with_decode_path=False,
+        decode_path_only=(),
+):
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
-    :param oids: ``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 with_colours: colourize output, if ``termcolor`` library
+                         is available
+    :param with_decode_path: print decode path
+    :param decode_path_only: print only that specified decode path
     """
     def _pprint_pps(pps):
         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
+                ):
+                    continue
                 if big_blobs:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
+                        with_colours=with_colours,
+                        with_decode_path=with_decode_path,
+                        decode_path_len_decrease=len(decode_path_only),
                     )
-                    for row in pp_console_blob(pp):
+                    for row in pp_console_blob(
+                            pp,
+                            decode_path_len_decrease=len(decode_path_only),
+                    ):
                         yield row
                 else:
-                    yield pp_console_row(pp, oids=oids, with_offsets=True)
+                    yield pp_console_row(
+                        pp,
+                        oid_maps=oid_maps,
+                        with_offsets=True,
+                        with_blob=True,
+                        with_colours=with_colours,
+                        with_decode_path=with_decode_path,
+                        decode_path_len_decrease=len(decode_path_only),
+                    )
             else:
                 for row in _pprint_pps(pp):
                     yield row
@@ -1126,6 +1741,22 @@ def pprint(obj, oids=None, big_blobs=False):
 # 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
 
@@ -1170,27 +1801,45 @@ class Boolean(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
+        if value.__class__ == bool:
+            return value
         if issubclass(value.__class__, Boolean):
             return value._value
-        if isinstance(value, bool):
-            return value
         raise InvalidValueType((self.__class__, bool))
 
     @property
     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
-        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()
@@ -1201,7 +1850,7 @@ class Boolean(Obj):
         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
@@ -1235,7 +1884,7 @@ class Boolean(Obj):
             (b"\xFF" if self._value else b"\x00"),
         ))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1251,6 +1900,8 @@ class Boolean(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -1275,10 +1926,14 @@ class Boolean(Obj):
                 offset=offset,
             )
         first_octet = byte2int(v)
+        ber_encoded = False
         if first_octet == 0:
             value = False
         elif first_octet == 0xFF:
             value = True
+        elif ctx.get("bered", False):
+            value = True
+            ber_encoded = True
         else:
             raise DecodeError(
                 "unacceptable Boolean value",
@@ -1294,6 +1949,7 @@ class Boolean(Obj):
             optional=self.optional,
             _decoded=(offset, 1, 1),
         )
+        obj.ber_encoded = ber_encoded
         return obj, v[1:]
 
     def __repr__(self):
@@ -1301,6 +1957,7 @@ class Boolean(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1317,7 +1974,31 @@ class Boolean(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
+        for pp in self.pps_lenindef(decode_path):
+            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):
@@ -1383,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
-        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",
@@ -1403,11 +2084,11 @@ class Integer(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, Integer):
-            value = value._value
-        elif isinstance(value, integer_types):
+        if isinstance(value, integer_types):
             pass
-        elif isinstance(value, str):
+        elif issubclass(value.__class__, Integer):
+            value = value._value
+        elif value.__class__ == str:
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
@@ -1421,19 +2102,41 @@ class Integer(Obj):
     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
-        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()
@@ -1463,9 +2166,10 @@ class Integer(Obj):
 
     @property
     def named(self):
-        for name, value in self.specs.items():
+        for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
+        return None
 
     def __call__(
             self,
@@ -1528,7 +2232,7 @@ class Integer(Obj):
                     break
         return b"".join((self.tag, len_encode(len(octets)), octets))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1544,6 +2248,8 @@ class Integer(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return None
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -1621,6 +2327,7 @@ class Integer(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1637,7 +2344,30 @@ class Integer(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
+
+
+BitStringState = namedtuple("BitStringState", (
+    "version",
+    "specs",
+    "value",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+    "tag_constructed",
+    "defined",
+), **NAMEDTUPLE_KWARGS)
 
 
 class BitString(Obj):
@@ -1652,6 +2382,8 @@ class BitString(Obj):
     >>> b.bit_len
     88
 
+    >>> BitString("'0A3B5F291CD'H")
+    BIT STRING 44 bits 0a3b5f291cd0
     >>> b = BitString("'010110000000'B")
     BIT STRING 12 bits 5800
     >>> b.bit_len
@@ -1667,19 +2399,27 @@ class BitString(Obj):
 
         class KeyUsage(BitString):
             schema = (
-                ('digitalSignature', 0),
-                ('nonRepudiation', 1),
-                ('keyEncipherment', 2),
+                ("digitalSignature", 0),
+                ("nonRepudiation", 1),
+                ("keyEncipherment", 2),
             )
 
-    >>> b = KeyUsage(('keyEncipherment', 'nonRepudiation'))
+    >>> b = KeyUsage(("keyEncipherment", "nonRepudiation"))
     KeyUsage BIT STRING 3 bits nonRepudiation, keyEncipherment
     >>> b.named
     ['nonRepudiation', 'keyEncipherment']
     >>> b.specs
     {'nonRepudiation': 1, 'digitalSignature': 0, 'keyEncipherment': 2}
+
+    .. note::
+
+       Pay attention that BIT STRING can be encoded both in primitive
+       and constructed forms. Decoder always checks constructed form tag
+       additionally to specified primitive one. If BER decoding is
+       :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+       of DER restrictions.
     """
-    __slots__ = ("specs",)
+    __slots__ = ("tag_constructed", "specs", "defined")
     tag_default = tag_encode(3)
     asn1_type_name = "BIT STRING"
 
@@ -1705,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
-        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)
@@ -1716,6 +2456,13 @@ class BitString(Obj):
             )
             if value is None:
                 self._value = default
+        self.defined = None
+        tag_klass, _, tag_num = tag_decode(self.tag)
+        self.tag_constructed = tag_encode(
+            klass=tag_klass,
+            form=TagFormConstructed,
+            num=tag_num,
+        )
 
     def _bits2octets(self, bits):
         if len(self.specs) > 0:
@@ -1728,31 +2475,30 @@ class BitString(Obj):
         return bit_len, bytes(octets)
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, BitString):
-            return value._value
         if isinstance(value, (string_types, binary_type)):
             if (
                     isinstance(value, string_types) and
-                    value.startswith("'") and
-                    value.endswith("'B")
+                    value.startswith("'")
             ):
-                value = value[1:-2]
-                if not set(value) <= set(("0", "1")):
-                    raise ValueError("B's coding contains unacceptable chars")
-                return self._bits2octets(value)
-            elif isinstance(value, binary_type):
+                if value.endswith("'B"):
+                    value = value[1:-2]
+                    if not frozenset(value) <= SET01:
+                        raise ValueError("B's coding contains unacceptable chars")
+                    return self._bits2octets(value)
+                if value.endswith("'H"):
+                    value = value[1:-2]
+                    return (
+                        len(value) * 4,
+                        hexdec(value + ("" if len(value) % 2 == 0 else "0")),
+                    )
+            if value.__class__ == binary_type:
                 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
-                    isinstance(value[1], binary_type)
+                    value[1].__class__ == binary_type
             ):
                 return value
             bits = []
@@ -1763,31 +2509,54 @@ class BitString(Obj):
                 bits.append(bit)
             if len(bits) == 0:
                 return self._bits2octets("")
-            bits = set(bits)
+            bits = frozenset(bits)
             return self._bits2octets("".join(
                 ("1" if bit in bits else "0")
                 for bit in six_xrange(max(bits) + 1)
             ))
+        if issubclass(value.__class__, BitString):
+            return value._value
         raise InvalidValueType((self.__class__, binary_type, string_types))
 
     @property
     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
-        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()
@@ -1804,7 +2573,7 @@ class BitString(Obj):
         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
@@ -1816,7 +2585,7 @@ class BitString(Obj):
 
     @property
     def named(self):
-        return [name for name, bit in self.specs.items() if self[bit]]
+        return [name for name, bit in iteritems(self.specs) if self[bit]]
 
     def __call__(
             self,
@@ -1836,7 +2605,7 @@ class BitString(Obj):
         )
 
     def __getitem__(self, key):
-        if isinstance(key, int):
+        if key.__class__ == int:
             bit_len, octets = self._value
             if key >= bit_len:
                 return False
@@ -1861,24 +2630,9 @@ class BitString(Obj):
             octets,
         ))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
-        try:
-            t, _, lv = tag_strip(tlv)
-        except DecodeError as err:
-            raise err.__class__(
-                msg=err.msg,
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if t != self.tag:
-            raise TagMismatch(
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
-            l, llen, v = len_decode(lv)
+            t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -1886,53 +2640,179 @@ class BitString(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if l > len(v):
-            raise NotEnoughData(
-                "encoded length is longer than data",
+        if t == self.tag:
+            if tag_only:  # pragma: no cover
+                return None
+            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
+        if t != self.tag_constructed:
+            raise TagMismatch(
                 klass=self.__class__,
                 decode_path=decode_path,
                 offset=offset,
             )
-        if l == 0:
-            raise NotEnoughData(
-                "zero length",
+        if not ctx.get("bered", False):
+            raise DecodeError(
+                "unallowed BER constructed encoding",
                 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",
+        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 pad_size > 7:
-            raise DecodeError(
-                "too big pad",
+        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,
             )
-        if byte2int(v[-1:]) & ((1 << pad_size) - 1) != 0:
+        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(
-                "invalid pad",
+                "no chunks",
                 klass=self.__class__,
                 decode_path=decode_path,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        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=((len(v) - 1) * 8 - pad_size, v[1:].tobytes()),
+            value=(bit_len, b"".join(values)),
             impl=self.tag,
             expl=self._expl,
             default=self.default,
             optional=self.optional,
             _specs=self.specs,
-            _decoded=(offset, llen, l),
+            _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
-        return obj, tail
+        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()))
@@ -1946,6 +2826,7 @@ class BitString(Obj):
             if len(self.specs) > 0:
                 blob = tuple(self.named)
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1963,7 +2844,38 @@ class BitString(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
+        defined_by, defined = self.defined or (None, None)
+        if defined_by is not None:
+            yield defined.pps(
+                decode_path=decode_path + (DecodePathDefBy(defined_by),)
+            )
+        for pp in self.pps_lenindef(decode_path):
+            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):
@@ -1981,8 +2893,16 @@ class OctetString(Obj):
     pyderasn.BoundsError: unsatisfied bounds: 4 <= 5 <= 4
     >>> OctetString(b"hell", bounds=(4, 4))
     OCTET STRING 4 bytes 68656c6c
+
+    .. note::
+
+       Pay attention that OCTET STRING can be encoded both in primitive
+       and constructed forms. Decoder always checks constructed form tag
+       additionally to specified primitive one. If BER decoding is
+       :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+       of DER restrictions.
     """
-    __slots__ = ("_bound_min", "_bound_max", "defined")
+    __slots__ = ("tag_constructed", "_bound_min", "_bound_max", "defined")
     tag_default = tag_encode(4)
     asn1_type_name = "OCTET STRING"
 
@@ -1995,6 +2915,7 @@ class OctetString(Obj):
             default=None,
             optional=False,
             _decoded=(0, 0, 0),
+            ctx=None,
     ):
         """
         :param value: set the value. Either binary type, or
@@ -2006,13 +2927,7 @@ class OctetString(Obj):
         :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,
@@ -2031,12 +2946,18 @@ class OctetString(Obj):
             if self._value is None:
                 self._value = default
         self.defined = None
+        tag_klass, _, tag_num = tag_decode(self.tag)
+        self.tag_constructed = tag_encode(
+            klass=tag_klass,
+            form=TagFormConstructed,
+            num=tag_num,
+        )
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, OctetString):
-            value = value._value
-        elif isinstance(value, binary_type):
+        if value.__class__ == binary_type:
             pass
+        elif issubclass(value.__class__, OctetString):
+            value = value._value
         else:
             raise InvalidValueType((self.__class__, bytes))
         if not self._bound_min <= len(value) <= self._bound_max:
@@ -2047,26 +2968,50 @@ class OctetString(Obj):
     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
-        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):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             return self._value == their
         if not issubclass(their.__class__, OctetString):
             return False
@@ -2108,9 +3053,9 @@ class OctetString(Obj):
             self._value,
         ))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
-            t, _, lv = tag_strip(tlv)
+            t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -2118,14 +3063,73 @@ class OctetString(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if t != self.tag:
+        if t == self.tag:
+            if tag_only:
+                return None
+            try:
+                l, llen, v = len_decode(lv)
+            except DecodeError as err:
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if l > len(v):
+                raise NotEnoughData(
+                    "encoded length is longer than data",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            v, tail = v[:l], v[l:]
+            try:
+                obj = self.__class__(
+                    value=v.tobytes(),
+                    bounds=(self._bound_min, self._bound_max),
+                    impl=self.tag,
+                    expl=self._expl,
+                    default=self.default,
+                    optional=self.optional,
+                    _decoded=(offset, llen, l),
+                    ctx=ctx,
+                )
+            except DecodeError as err:
+                raise DecodeError(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            except BoundsError as err:
+                raise DecodeError(
+                    msg=str(err),
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            return obj, tail
+        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,
@@ -2140,16 +3144,61 @@ class OctetString(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        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=v.tobytes(),
+                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, l),
+                _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(
@@ -2158,13 +3207,16 @@ class OctetString(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        return obj, tail
+        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 pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2182,12 +3234,33 @@ class OctetString(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
             yield defined.pps(
-                decode_path=decode_path + (decode_path_defby(defined_by),)
+                decode_path=decode_path + (DecodePathDefBy(defined_by),)
             )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
+
+
+NullState = namedtuple("NullState", (
+    "version",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
 
 
 class Null(Obj):
@@ -2222,16 +3295,33 @@ class Null(Obj):
     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
-        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):
@@ -2257,7 +3347,7 @@ class Null(Obj):
     def _encode(self):
         return self.tag + len_encode(0)
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2273,6 +3363,8 @@ class Null(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:  # pragma: no cover
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -2302,6 +3394,7 @@ class Null(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2316,7 +3409,28 @@ class Null(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
+        for pp in self.pps_lenindef(decode_path):
+            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):
@@ -2344,7 +3458,7 @@ class ObjectIdentifier(Obj):
     def __init__(
             self,
             value=None,
-            defines=None,
+            defines=(),
             impl=None,
             expl=None,
             default=None,
@@ -2355,25 +3469,21 @@ class ObjectIdentifier(Obj):
         :param value: set the value. Either tuples of integers,
                       string of "."-concatenated integers, or
                       :py:class:`pyderasn.ObjectIdentifier` object
-        :param defines: tuple of two elements. First one is a name of
-                        field inside :py:class:`pyderasn.Sequence`,
-                        defining with that OID. Second element is a
-                        ``{OID: pyderasn.Obj()}`` dictionary, mapping
-                        between current OID value and structure applied
-                        to defined field.
+        :param defines: sequence of tuples. Each tuple has two elements.
+                        First one is relative to current one decode
+                        path, aiming to the field defined by that OID.
+                        Read about relative path in
+                        :py:func:`pyderasn.abs_decode_path`. Second
+                        tuple element is ``{OID: pyderasn.Obj()}``
+                        dictionary, mapping between current OID value
+                        and structure applied to defined field.
                         :ref:`Read about DEFINED BY <definedby>`
         :param bytes impl: override default tag with ``IMPLICIT`` one
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :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)
@@ -2389,10 +3499,10 @@ class ObjectIdentifier(Obj):
         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, tuple):
-            return self.__class__(self._value + their)
         raise InvalidValueType((self.__class__, tuple))
 
     def _value_sanitize(self, value):
@@ -2400,10 +3510,10 @@ class ObjectIdentifier(Obj):
             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")
-        if isinstance(value, tuple):
+        if value.__class__ == tuple:
             if len(value) < 2:
                 raise InvalidOID("less than 2 arcs")
             first_arc = value[0]
@@ -2414,6 +3524,8 @@ class ObjectIdentifier(Obj):
                 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))
 
@@ -2421,18 +3533,37 @@ class ObjectIdentifier(Obj):
     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
-        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()
@@ -2450,7 +3581,7 @@ class ObjectIdentifier(Obj):
         )
 
     def __eq__(self, their):
-        if isinstance(their, tuple):
+        if their.__class__ == tuple:
             return self._value == their
         if not issubclass(their.__class__, ObjectIdentifier):
             return False
@@ -2500,7 +3631,7 @@ class ObjectIdentifier(Obj):
         v = b"".join(octets)
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2516,6 +3647,8 @@ class ObjectIdentifier(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:  # pragma: no cover
+            return None
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2541,11 +3674,17 @@ class ObjectIdentifier(Obj):
             )
         v, tail = v[:l], v[l:]
         arcs = []
+        ber_encoded = False
         while len(v) > 0:
             i = 0
             arc = 0
             while True:
                 octet = indexbytes(v, i)
+                if i == 0 and octet == 0x80:
+                    if ctx.get("bered", False):
+                        ber_encoded = True
+                    else:
+                        raise DecodeError("non normalized arc encoding")
                 arc = (arc << 7) | (octet & 0x7F)
                 if octet & 0x80 == 0:
                     arcs.append(arc)
@@ -2577,6 +3716,8 @@ class ObjectIdentifier(Obj):
             optional=self.optional,
             _decoded=(offset, llen, l),
         )
+        if ber_encoded:
+            obj.ber_encoded = True
         return obj, tail
 
     def __repr__(self):
@@ -2584,6 +3725,7 @@ class ObjectIdentifier(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2600,7 +3742,12 @@ class ObjectIdentifier(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class Enumerated(Integer):
@@ -2625,13 +3772,7 @@ class Enumerated(Integer):
             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")
@@ -2640,7 +3781,10 @@ class Enumerated(Integer):
         if isinstance(value, self.__class__):
             value = value._value
         elif isinstance(value, integer_types):
-            if value not in list(self.specs.values()):
+            for _value in itervalues(self.specs):
+                if _value == value:
+                    break
+            else:
                 raise DecodeError(
                     "unknown integer value: %s" % value,
                     klass=self.__class__,
@@ -2653,20 +3797,6 @@ class Enumerated(Integer):
             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
-        return obj
-
     def __call__(
             self,
             value=None,
@@ -2686,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
 
@@ -2705,7 +3841,7 @@ class CommonString(OctetString):
 
     >>> PrintableString("привет мир")
     Traceback (most recent call last):
-    UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
+    pyderasn.DecodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
 
     >>> BMPString("ада", bounds=(2, 2))
     Traceback (most recent call last):
@@ -2748,27 +3884,30 @@ class CommonString(OctetString):
        * - :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
-        elif isinstance(value, text_type):
+        elif value.__class__ == text_type:
             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_decoded.encode(self.encoding)
-            if value_raw is None else value_raw
-        )
-        value_decoded = (
-            value_raw.decode(self.encoding)
-            if value_decoded is None else value_decoded
-        )
+        try:
+            value_raw = (
+                value_decoded.encode(self.encoding)
+                if value_raw is None else value_raw
+            )
+            value_decoded = (
+                value_raw.decode(self.encoding)
+                if value_decoded is None else value_decoded
+            )
+        except (UnicodeEncodeError, UnicodeDecodeError) as err:
+            raise DecodeError(str(err))
         if not self._bound_min <= len(value_decoded) <= self._bound_max:
             raise BoundsError(
                 self._bound_min,
@@ -2778,9 +3917,9 @@ class CommonString(OctetString):
         return value_raw
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             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
@@ -2801,8 +3940,12 @@ class CommonString(OctetString):
     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,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2815,7 +3958,16 @@ class CommonString(OctetString):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
+            expl_offset=self.expl_offset if self.expled else None,
+            expl_tlen=self.expl_tlen if self.expled else None,
+            expl_llen=self.expl_llen if self.expled else None,
+            expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class UTF8String(CommonString):
@@ -2825,18 +3977,140 @@ class UTF8String(CommonString):
     asn1_type_name = "UTF8String"
 
 
-class NumericString(CommonString):
+class AllowableCharsMixin(object):
+    @property
+    def allowable_chars(self):
+        if PY2:
+            return self._allowable_chars
+        return frozenset(six_unichr(c) for c in self._allowable_chars)
+
+
+class NumericString(AllowableCharsMixin, CommonString):
+    """Numeric string
+
+    Its value is properly sanitized: only ASCII digits with spaces can
+    be stored.
+
+    >>> NumericString().allowable_chars
+    frozenset(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' '])
+    """
     __slots__ = ()
     tag_default = tag_encode(18)
     encoding = "ascii"
     asn1_type_name = "NumericString"
+    _allowable_chars = frozenset(digits.encode("ascii") + b" ")
+
+    def _value_sanitize(self, value):
+        value = super(NumericString, self)._value_sanitize(value)
+        if not frozenset(value) <= self._allowable_chars:
+            raise DecodeError("non-numeric value")
+        return value
+
+
+PrintableStringState = namedtuple(
+    "PrintableStringState",
+    OctetStringState._fields + ("allowable_chars",),
+    **NAMEDTUPLE_KWARGS
+)
 
 
-class PrintableString(CommonString):
+class PrintableString(AllowableCharsMixin, CommonString):
+    """Printable string
+
+    Its value is properly sanitized: see X.680 41.4 table 10.
+
+    >>> 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)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
+    _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)
+        if not frozenset(value) <= self._allowable_chars:
+            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):
@@ -2870,7 +4144,31 @@ LEN_YYYYMMDDHHMMSSDMZ = len("YYYYMMDDHHMMSSDMZ")
 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))
@@ -2883,14 +4181,28 @@ class UTCTime(CommonString):
     datetime.datetime(2017, 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::
+
+       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"
 
-    fmt = "%y%m%d%H%M%SZ"
-
     def __init__(
             self,
             value=None,
@@ -2900,6 +4212,7 @@ class UTCTime(CommonString):
             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
@@ -2910,17 +4223,15 @@ class UTCTime(CommonString):
         :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.ber_raw = 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:
-            default = self._value_sanitize(default)
+            default, _ = self._value_sanitize(default)
             self.default = self.__class__(
                 value=default,
                 impl=self.tag,
@@ -2928,28 +4239,122 @@ class UTCTime(CommonString):
             )
             if self._value is None:
                 self._value = default
+            optional = True
+        self.optional = optional
 
-    def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime(self.fmt).encode("ascii")
-        if isinstance(value, binary_type):
-            value_decoded = value.decode("ascii")
-            if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
-                try:
-                    datetime.strptime(value_decoded, self.fmt)
-                except ValueError:
-                    raise DecodeError("invalid UTCTime format")
-                return value
+    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 DecodeError("invalid UTCTime length")
+                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
+        if len(value) != LEN_YYMMDDHHMMSSZ:
+            raise ValueError("invalid UTCTime length")
+        if value[-1] != "Z":
+            raise ValueError("non UTC timezone")
+        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 _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)
+            err = None
+            try:
+                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__):
+            return value._value, None
+        if value.__class__ == datetime:
+            return self._dt_sanitize(value), None
         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):
-        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
@@ -2959,35 +4364,27 @@ class UTCTime(CommonString):
             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 = datetime.strptime(self._value.decode("ascii"), self.fmt)
-        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 pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             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),
@@ -2996,7 +4393,16 @@ class UTCTime(CommonString):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
+            expl_offset=self.expl_offset if self.expled else None,
+            expl_tlen=self.expl_tlen if self.expled else None,
+            expl_llen=self.expl_llen if self.expled else None,
+            expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class GeneralizedTime(UTCTime):
@@ -3010,65 +4416,129 @@ class GeneralizedTime(UTCTime):
     '20170930220750.000123Z'
     >>> t = GeneralizedTime(datetime(2057, 9, 30, 22, 7, 50))
     GeneralizedTime GeneralizedTime 2057-09-30T22:07:50
+
+    .. warning::
+
+       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::
+
+       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"
 
-    fmt = "%Y%m%d%H%M%SZ"
-    fmt_ms = "%Y%m%d%H%M%S.%fZ"
+    def _dt_sanitize(self, value):
+        return value
 
-    def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime(
-                self.fmt_ms if value.microsecond > 0 else self.fmt
-            ).encode("ascii")
-        if isinstance(value, binary_type):
-            value_decoded = value.decode("ascii")
-            if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ:
-                try:
-                    datetime.strptime(value_decoded, self.fmt)
-                except ValueError:
-                    raise DecodeError(
-                        "invalid GeneralizedTime (without ms) format",
-                    )
-                return value
-            elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
-                try:
-                    datetime.strptime(value_decoded, self.fmt_ms)
-                except ValueError:
-                    raise DecodeError(
-                        "invalid GeneralizedTime (with ms) format",
-                    )
-                return value
-            else:
-                raise DecodeError(
-                    "invalid GeneralizedTime length",
-                    klass=self.__class__,
-                )
-        raise InvalidValueType((self.__class__, datetime))
+    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 todatetime(self):
-        value = self._value.decode("ascii")
-        if len(value) == LEN_YYYYMMDDHHMMSSZ:
-            return datetime.strptime(value, self.fmt)
-        return datetime.strptime(value, self.fmt_ms)
+    def _strptime(self, value):
+        l = len(value)
+        if l == LEN_YYYYMMDDHHMMSSZ:
+            # datetime.strptime's format: %Y%m%d%H%M%SZ
+            if value[-1] != "Z":
+                raise ValueError("non UTC timezone")
+            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":
+                raise ValueError("non UTC timezone")
+            if value[14] != ".":
+                raise ValueError("no fractions separator")
+            us = value[15:-1]
+            if us[-1] == "0":
+                raise ValueError("trailing zero")
+            us_len = len(us)
+            if us_len > 6:
+                raise ValueError("only microsecond fractions are supported")
+            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")
+
+    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):
-    __slots__ = ()
-    tag_default = tag_encode(25)
-    encoding = "iso-8859-1"
-    asn1_type_name = "GraphicString"
-
-
-class VisibleString(CommonString):
-    __slots__ = ()
-    tag_default = tag_encode(26)
-    encoding = "ascii"
-    asn1_type_name = "VisibleString"
+    __slots__ = ()
+    tag_default = tag_encode(25)
+    encoding = "iso-8859-1"
+    asn1_type_name = "GraphicString"
 
 
 class ISO646String(VisibleString):
@@ -3097,6 +4567,23 @@ class BMPString(CommonString):
     asn1_type_name = "BMPString"
 
 
+ChoiceState = namedtuple("ChoiceState", (
+    "version",
+    "specs",
+    "value",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
+
+
 class Choice(Obj):
     """``CHOICE`` special type
 
@@ -3104,8 +4591,8 @@ class Choice(Obj):
 
         class GeneralName(Choice):
             schema = (
-                ('rfc822Name', IA5String(impl=tag_ctxp(1))),
-                ('dNSName', IA5String(impl=tag_ctxp(2))),
+                ("rfc822Name", IA5String(impl=tag_ctxp(1))),
+                ("dNSName", IA5String(impl=tag_ctxp(2))),
             )
 
     >>> gn = GeneralName()
@@ -3158,7 +4645,7 @@ class Choice(Obj):
         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:
@@ -3170,12 +4657,10 @@ class Choice(Obj):
             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 isinstance(value, self.__class__):
-            return value._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:
@@ -3183,27 +4668,54 @@ class Choice(Obj):
             if not isinstance(obj, spec.__class__):
                 raise InvalidValueType((spec,))
             return (choice, spec(obj))
+        if isinstance(value, self.__class__):
+            return value._value
         raise InvalidValueType((self.__class__, tuple))
 
     @property
     def ready(self):
         return self._value is not None and self._value[1].ready
 
-    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
-        value = self._value
-        if value is not None:
-            obj._value = (value[0], value[1].copy())
-        return obj
+    @property
+    def bered(self):
+        return self.expl_lenindef or (
+            (self._value is not None) and
+            self._value[1].bered
+        )
+
+    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):
-        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
@@ -3267,32 +4779,47 @@ class Choice(Obj):
         self._assert_ready()
         return self._value[1].encode()
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
-        for choice, spec in self.specs.items():
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+        for choice, spec in iteritems(self.specs):
+            sub_decode_path = decode_path + (choice,)
             try:
-                value, tail = spec.decode(
+                spec.decode(
                     tlv,
                     offset=offset,
                     leavemm=True,
-                    decode_path=decode_path + (choice,),
-                    defines_by_path=defines_by_path,
+                    decode_path=sub_decode_path,
+                    ctx=ctx,
+                    tag_only=True,
+                    _ctx_immutable=False,
                 )
             except TagMismatch:
                 continue
-            obj = self.__class__(
-                schema=self.specs,
-                expl=self._expl,
-                default=self.default,
-                optional=self.optional,
-                _decoded=(offset, 0, value.tlvlen),
+            break
+        else:
+            raise TagMismatch(
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
             )
-            obj._value = (choice, value)
-            return obj, tail
-        raise TagMismatch(
-            klass=self.__class__,
-            decode_path=decode_path,
+        if tag_only:  # pragma: no cover
+            return None
+        value, tail = spec.decode(
+            tlv,
             offset=offset,
+            leavemm=True,
+            decode_path=sub_decode_path,
+            ctx=ctx,
+            _ctx_immutable=False,
         )
+        obj = self.__class__(
+            schema=self.specs,
+            expl=self._expl,
+            default=self.default,
+            optional=self.optional,
+            _decoded=(offset, 0, value.fulllen),
+        )
+        obj._value = (choice, value)
+        return obj, tail
 
     def __repr__(self):
         value = pp_console_row(next(self.pps()))
@@ -3302,6 +4829,7 @@ class Choice(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3314,9 +4842,13 @@ class Choice(Obj):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
+            expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
         if self.ready:
             yield self.value.pps(decode_path=decode_path + (self.choice,))
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class PrimitiveTypes(Choice):
@@ -3324,9 +4856,9 @@ class PrimitiveTypes(Choice):
 
     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
-    >>> PrimitiveTypes().decode(hexdec("0203123456"))[0].value
+    >>> PrimitiveTypes().decod(hexdec("0203123456")).value
     INTEGER 1193046
     """
     __slots__ = ()
@@ -3354,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
 
@@ -3388,31 +4936,58 @@ class Any(Obj):
         self.defined = None
 
     def _value_sanitize(self, value):
+        if value.__class__ == binary_type:
+            return value
         if isinstance(value, self.__class__):
             return value._value
         if isinstance(value, Obj):
             return value.encode()
-        if isinstance(value, binary_type):
-            return value
         raise InvalidValueType((self.__class__, Obj, binary_type))
 
     @property
     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.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    @property
+    def bered(self):
+        if self.expl_lenindef or self.lenindef:
+            return True
+        if self.defined is None:
+            return False
+        return self.defined[1].bered
+
+    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):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             return self._value == their
         if issubclass(their.__class__, Any):
             return self._value == their._value
@@ -3442,10 +5017,51 @@ class Any(Obj):
         self._assert_ready()
         return self._value
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
+        except DecodeError as err:
+            raise err.__class__(
+                msg=err.msg,
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx.get("bered", False):
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            llen, vlen, v = 1, 0, lv[1:]
+            sub_offset = offset + tlen + llen
+            chunk_i = 0
+            while v[:EOC_LEN].tobytes() != EOC:
+                chunk, v = Any().decode(
+                    v,
+                    offset=sub_offset,
+                    decode_path=decode_path + (str(chunk_i),),
+                    leavemm=True,
+                    ctx=ctx,
+                    _ctx_immutable=False,
+                )
+                vlen += chunk.tlvlen
+                sub_offset += chunk.tlvlen
+                chunk_i += 1
+            tlvlen = tlen + llen + vlen + EOC_LEN
+            obj = self.__class__(
+                value=tlv[:tlvlen].tobytes(),
+                expl=self._expl,
+                optional=self.optional,
+                _decoded=(offset, 0, tlvlen),
+            )
+            obj.lenindef = True
+            obj.tag = t.tobytes()
+            return obj, v[EOC_LEN:]
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -3468,7 +5084,7 @@ class Any(Obj):
             optional=self.optional,
             _decoded=(offset, 0, tlvlen),
         )
-        obj.tag = t
+        obj.tag = t.tobytes()
         return obj, tail
 
     def __repr__(self):
@@ -3476,6 +5092,7 @@ class Any(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3492,12 +5109,17 @@ class Any(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            bered=self.bered,
         )
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
             yield defined.pps(
-                decode_path=decode_path + (decode_path_defby(defined_by),)
+                decode_path=decode_path + (DecodePathDefBy(defined_by),)
             )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 ########################################################################
@@ -3517,6 +5139,48 @@ def get_def_by_path(defines_by_path, sub_decode_path):
             return define
 
 
+def abs_decode_path(decode_path, rel_path):
+    """Create an absolute decode path from current and relative ones
+
+    :param decode_path: current decode path, starting point. Tuple of strings
+    :param rel_path: relative path to ``decode_path``. Tuple of strings.
+                     If first tuple's element is "/", then treat it as
+                     an absolute path, ignoring ``decode_path`` as
+                     starting point. Also this tuple can contain ".."
+                     elements, stripping the leading element from
+                     ``decode_path``
+
+    >>> abs_decode_path(("foo", "bar"), ("baz", "whatever"))
+    ("foo", "bar", "baz", "whatever")
+    >>> abs_decode_path(("foo", "bar", "baz"), ("..", "..", "whatever"))
+    ("foo", "whatever")
+    >>> abs_decode_path(("foo", "bar"), ("/", "baz", "whatever"))
+    ("baz", "whatever")
+    """
+    if rel_path[0] == "/":
+        return rel_path[1:]
+    if rel_path[0] == "..":
+        return abs_decode_path(decode_path[:-1], rel_path[1:])
+    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
 
@@ -3543,7 +5207,7 @@ class Sequence(Obj):
     pyderasn.InvalidValueType: invalid value type, expected: <class 'pyderasn.ObjectIdentifier'>
     >>> ext["extnID"] = ObjectIdentifier("1.2.3")
 
-    You can know if sequence is ready to be encoded:
+    You can determine if sequence is ready to be encoded:
 
     >>> ext.ready
     False
@@ -3567,7 +5231,17 @@ class Sequence(Obj):
     >>> tbs = TBSCertificate()
     >>> tbs["version"] = Version("v2") # no need to explicitly add ``expl``
 
-    You can know if value exists/set in the sequence and take its value:
+    Assign ``None`` to remove value from sequence.
+
+    You can set values in Sequence during its initialization:
+
+    >>> AlgorithmIdentifier((
+        ("algorithm", ObjectIdentifier("1.2.3")),
+        ("parameters", Any(Null()))
+    ))
+    AlgorithmIdentifier SEQUENCE[algorithm: OBJECT IDENTIFIER 1.2.3; parameters: ANY 0500 OPTIONAL]
+
+    You can determine if value exists/set in the sequence and take its value:
 
     >>> "extnID" in ext, "extnValue" in ext, "critical" in ext
     (True, True, False)
@@ -3586,13 +5260,14 @@ class Sequence(Obj):
 
     All defaulted values are always optional.
 
-    .. warning::
+    .. _allow_default_values_ctx:
 
-       When decoded DER contains defaulted value inside, then
-       technically this is not valid DER encoding. But we allow
-       and pass it. Of course reencoding of that kind of DER will
-       result in different binary representation (validly without
-       defaulted value inside).
+    DER prohibits default value encoding and will raise an error if
+    default value is unexpectedly met during decode.
+    If :ref:`bered <bered_ctx>` context option is set, then no error
+    will be raised, but ``bered`` attribute set. You can disable strict
+    defaulted values existence validation by setting
+    ``"allow_default_values": True`` :ref:`context <ctx>` option.
 
     Two sequences are equal if they have equal specification (schema),
     implicit/explicit tagging and the same values.
@@ -3615,49 +5290,77 @@ class Sequence(Obj):
         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 = self._value_sanitize(value)
+            if issubclass(value.__class__, Sequence):
+                self._value = value._value
+            elif hasattr(value, "__iter__"):
+                for seq_key, seq_value in value:
+                    self[seq_key] = seq_value
+            else:
+                raise InvalidValueType((Sequence,))
         if default is not None:
-            default_value = self._value_sanitize(default)
+            if not issubclass(default.__class__, Sequence):
+                raise InvalidValueType((Sequence,))
+            default_value = default._value
             default_obj = self.__class__(impl=self.tag, expl=self._expl)
             default_obj.specs = self.specs
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
-                self._value = default_obj.copy()._value
-
-    def _value_sanitize(self, value):
-        if not issubclass(value.__class__, Sequence):
-            raise InvalidValueType((Sequence,))
-        return value._value
+                self._value = copy(default_obj._value)
 
     @property
     def ready(self):
-        for name, spec in self.specs.items():
+        for name, spec in iteritems(self.specs):
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
                     continue
                 return False
-            else:
-                if not value.ready:
-                    return False
+            if not value.ready:
+                return False
         return True
 
-    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._value = {k: v.copy() for k, v in self._value.items()}
-        return obj
+    @property
+    def bered(self):
+        if self.expl_lenindef or self.lenindef or self.ber_encoded:
+            return True
+        return any(value.bered for value in itervalues(self._value))
+
+    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__):
@@ -3717,7 +5420,7 @@ class Sequence(Obj):
 
     def _encoded_values(self):
         raws = []
-        for name, spec in self.specs.items():
+        for name, spec in iteritems(self.specs):
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
@@ -3730,7 +5433,7 @@ class Sequence(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3746,8 +5449,22 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:  # pragma: no cover
+            return None
+        lenindef = False
+        ctx_bered = ctx.get("bered", False)
         try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx_bered:
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            l, llen, v = 0, 1, lv[1:]
+            lenindef = True
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -3762,12 +5479,18 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        if not lenindef:
+            v, tail = v[:l], v[l:]
+        vlen = 0
         sub_offset = offset + tlen + llen
         values = {}
-        defines = {}
-        for name, spec in self.specs.items():
-            if len(v) == 0 and spec.optional:
+        ber_encoded = False
+        ctx_allow_default_values = ctx.get("allow_default_values", False)
+        for name, spec in iteritems(self.specs):
+            if spec.optional and (
+                    (lenindef and v[:EOC_LEN].tobytes() == EOC) or
+                    len(v) == 0
+            ):
                 continue
             sub_decode_path = decode_path + (name,)
             try:
@@ -3776,28 +5499,33 @@ class Sequence(Obj):
                     sub_offset,
                     leavemm=True,
                     decode_path=sub_decode_path,
-                    defines_by_path=defines_by_path,
+                    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
 
-            defined = defines.pop(name, None)
+            defined = get_def_by_path(ctx.get("_defines", ()), sub_decode_path)
             if defined is not None:
                 defined_by, defined_spec = defined
                 if issubclass(value.__class__, SequenceOf):
                     for i, _value in enumerate(value):
                         sub_sub_decode_path = sub_decode_path + (
                             str(i),
-                            decode_path_defby(defined_by),
+                            DecodePathDefBy(defined_by),
                         )
                         defined_value, defined_tail = defined_spec.decode(
                             memoryview(bytes(_value)),
-                            sub_offset + value.tlen + value.llen,
+                            sub_offset + (
+                                (value.tlen + value.llen + value.expl_tlen + value.expl_llen)
+                                if value.expled else (value.tlen + value.llen)
+                            ),
                             leavemm=True,
                             decode_path=sub_sub_decode_path,
-                            defines_by_path=defines_by_path,
+                            ctx=ctx,
+                            _ctx_immutable=False,
                         )
                         if len(defined_tail) > 0:
                             raise DecodeError(
@@ -3810,37 +5538,64 @@ class Sequence(Obj):
                 else:
                     defined_value, defined_tail = defined_spec.decode(
                         memoryview(bytes(value)),
-                        sub_offset + value.tlen + value.llen,
+                        sub_offset + (
+                            (value.tlen + value.llen + value.expl_tlen + value.expl_llen)
+                            if value.expled else (value.tlen + value.llen)
+                        ),
                         leavemm=True,
-                        decode_path=sub_decode_path + (decode_path_defby(defined_by),),
-                        defines_by_path=defines_by_path,
+                        decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
+                        ctx=ctx,
+                        _ctx_immutable=False,
                     )
                     if len(defined_tail) > 0:
                         raise DecodeError(
                             "remaining data",
                             klass=self.__class__,
-                            decode_path=sub_decode_path + (decode_path_defby(defined_by),),
+                            decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
                             offset=offset,
                         )
                     value.defined = (defined_by, defined_value)
 
-            sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
+            value_len = value.fulllen
+            vlen += value_len
+            sub_offset += value_len
             v = v_tail
             if spec.default is not None and value == spec.default:
-                # Encoded default values are not valid in DER,
-                # but we allow that anyway
-                continue
+                if ctx_bered or ctx_allow_default_values:
+                    ber_encoded = True
+                else:
+                    raise DecodeError(
+                        "DEFAULT value met",
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
             values[name] = value
 
-            spec_defines = getattr(spec, "defines", None)
-            if defines_by_path is not None and spec_defines is None:
-                spec_defines = get_def_by_path(defines_by_path, sub_decode_path)
-            if spec_defines is not None:
-                what, schema = spec_defines
-                defined = schema.get(value, None)
-                if defined is not None:
-                    defines[what] = (value, defined)
-        if len(v) > 0:
+            spec_defines = getattr(spec, "defines", ())
+            if len(spec_defines) == 0:
+                defines_by_path = ctx.get("defines_by_path", ())
+                if len(defines_by_path) > 0:
+                    spec_defines = get_def_by_path(defines_by_path, sub_decode_path)
+            if spec_defines is not None and len(spec_defines) > 0:
+                for rel_path, schema in spec_defines:
+                    defined = schema.get(value, None)
+                    if defined is not None:
+                        ctx.setdefault("_defines", []).append((
+                            abs_decode_path(sub_decode_path[:-1], rel_path),
+                            (value, defined),
+                        ))
+        if lenindef:
+            if v[:EOC_LEN].tobytes() != EOC:
+                raise DecodeError(
+                    "no EOC",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            tail = v[EOC_LEN:]
+            vlen += EOC_LEN
+        elif len(v) > 0:
             raise DecodeError(
                 "remaining data",
                 klass=self.__class__,
@@ -3853,9 +5608,11 @@ class Sequence(Obj):
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, llen, l),
+            _decoded=(offset, llen, vlen),
         )
         obj._value = values
+        obj.lenindef = lenindef
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
     def __repr__(self):
@@ -3865,11 +5622,12 @@ class Sequence(Obj):
             _value = self._value.get(name)
             if _value is None:
                 continue
-            cols.append(repr(_value))
-        return "%s[%s]" % (value, ", ".join(cols))
+            cols.append("%s: %s" % (name, repr(_value)))
+        return "%s[%s]" % (value, "; ".join(cols))
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3885,18 +5643,32 @@ class Sequence(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
         for name in self.specs:
             value = self._value.get(name)
             if value is None:
                 continue
             yield value.pps(decode_path=decode_path + (name,))
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class Set(Sequence):
     """``SET`` structure type
 
     Its usage is identical to :py:class:`pyderasn.Sequence`.
+
+    .. _allow_unordered_set_ctx:
+
+    DER prohibits unordered values encoding and will raise an error
+    during decode. If If :ref:`bered <bered_ctx>` context option is set,
+    then no error will occure. Also you can disable strict values
+    ordering check by setting ``"allow_unordered_set": True``
+    :ref:`context <ctx>` option.
     """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
@@ -3908,7 +5680,10 @@ class Set(Sequence):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _specs_items(self):
+        return iteritems(self.specs)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3924,8 +5699,22 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return None
+        lenindef = False
+        ctx_bered = ctx.get("bered", False)
         try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx_bered:
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            l, llen, v = 0, 1, lv[1:]
+            lenindef = True
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -3939,29 +5728,33 @@ class Set(Sequence):
                 klass=self.__class__,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        if not lenindef:
+            v, tail = v[:l], v[l:]
+        vlen = 0
         sub_offset = offset + tlen + llen
         values = {}
-        specs_items = self.specs.items
+        ber_encoded = False
+        ctx_allow_default_values = ctx.get("allow_default_values", False)
+        ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+        value_prev = memoryview(v[:0])
+
         while len(v) > 0:
-            for name, spec in specs_items():
+            if lenindef and v[:EOC_LEN].tobytes() == EOC:
+                break
+            for name, spec in self._specs_items():
+                sub_decode_path = decode_path + (name,)
                 try:
-                    value, v_tail = spec.decode(
+                    spec.decode(
                         v,
                         sub_offset,
                         leavemm=True,
-                        decode_path=decode_path + (name,),
-                        defines_by_path=defines_by_path,
+                        decode_path=sub_decode_path,
+                        ctx=ctx,
+                        tag_only=True,
+                        _ctx_immutable=False,
                     )
                 except TagMismatch:
                     continue
-                sub_offset += (
-                    value.expl_tlvlen if value.expled else value.tlvlen
-                )
-                v = v_tail
-                if spec.default is None or value != spec.default:  # pragma: no cover
-                    # SeqMixing.test_encoded_default_accepted covers that place
-                    values[name] = value
                 break
             else:
                 raise TagMismatch(
@@ -3969,18 +5762,91 @@ class Set(Sequence):
                     decode_path=decode_path,
                     offset=offset,
                 )
+            value, v_tail = spec.decode(
+                v,
+                sub_offset,
+                leavemm=True,
+                decode_path=sub_decode_path,
+                ctx=ctx,
+                _ctx_immutable=False,
+            )
+            value_len = value.fulllen
+            if value_prev.tobytes() > v[:value_len].tobytes():
+                if ctx_bered or ctx_allow_unordered_set:
+                    ber_encoded = True
+                else:
+                    raise DecodeError(
+                        "unordered " + self.asn1_type_name,
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
+            if spec.default is None or value != spec.default:
+                pass
+            elif ctx_bered or ctx_allow_default_values:
+                ber_encoded = True
+            else:
+                raise DecodeError(
+                    "DEFAULT value met",
+                    klass=self.__class__,
+                    decode_path=sub_decode_path,
+                    offset=sub_offset,
+                )
+            values[name] = value
+            value_prev = v[:value_len]
+            sub_offset += value_len
+            vlen += value_len
+            v = v_tail
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, llen, l),
+            _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
+        if lenindef:
+            if v[:EOC_LEN].tobytes() != EOC:
+                raise DecodeError(
+                    "no EOC",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            tail = v[EOC_LEN:]
+            obj.lenindef = True
         obj._value = values
+        for name, spec in iteritems(self.specs):
+            if name not in values and not spec.optional:
+                raise DecodeError(
+                    "%s value is not ready" % name,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
 
+SequenceOfState = namedtuple("SequenceOfState", (
+    "version",
+    "spec",
+    "value",
+    "bound_min",
+    "bound_max",
+    "tag",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
+
+
 class SequenceOf(Obj):
     """``SEQUENCE OF`` sequence type
 
@@ -4026,13 +5892,7 @@ class SequenceOf(Obj):
             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:
@@ -4056,7 +5916,7 @@ class SequenceOf(Obj):
             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):
@@ -4076,19 +5936,47 @@ class SequenceOf(Obj):
     def ready(self):
         return all(v.ready 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._value = [v.copy() for v in self._value]
-        return obj
+    @property
+    def bered(self):
+        if self.expl_lenindef or self.lenindef or self.ber_encoded:
+            return True
+        return any(v.bered for v in self._value)
+
+    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__):
@@ -4161,7 +6049,7 @@ class SequenceOf(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4177,8 +6065,22 @@ class SequenceOf(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return None
+        lenindef = False
+        ctx_bered = ctx.get("bered", False)
         try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx_bered:
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            l, llen, v = 0, 1, lv[1:]
+            lenindef = True
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -4193,31 +6095,73 @@ class SequenceOf(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        if not lenindef:
+            v, tail = v[:l], v[l:]
+        vlen = 0
         sub_offset = offset + tlen + llen
         _value = []
+        ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+        value_prev = memoryview(v[:0])
+        ber_encoded = False
         spec = self.spec
         while len(v) > 0:
+            if lenindef and v[:EOC_LEN].tobytes() == EOC:
+                break
+            sub_decode_path = decode_path + (str(len(_value)),)
             value, v_tail = spec.decode(
                 v,
                 sub_offset,
                 leavemm=True,
-                decode_path=decode_path + (str(len(_value)),),
-                defines_by_path=defines_by_path,
+                decode_path=sub_decode_path,
+                ctx=ctx,
+                _ctx_immutable=False,
             )
-            sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
-            v = v_tail
+            value_len = value.fulllen
+            if ordering_check:
+                if value_prev.tobytes() > v[:value_len].tobytes():
+                    if ctx_bered or ctx_allow_unordered_set:
+                        ber_encoded = True
+                    else:
+                        raise DecodeError(
+                            "unordered " + self.asn1_type_name,
+                            klass=self.__class__,
+                            decode_path=sub_decode_path,
+                            offset=sub_offset,
+                        )
+                value_prev = v[:value_len]
             _value.append(value)
-        obj = self.__class__(
-            value=_value,
-            schema=spec,
-            bounds=(self._bound_min, self._bound_max),
-            impl=self.tag,
-            expl=self._expl,
-            default=self.default,
-            optional=self.optional,
-            _decoded=(offset, llen, l),
-        )
+            sub_offset += value_len
+            vlen += value_len
+            v = v_tail
+        try:
+            obj = self.__class__(
+                value=_value,
+                schema=spec,
+                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)),
+            )
+        except BoundsError as err:
+            raise DecodeError(
+                msg=str(err),
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if lenindef:
+            if v[:EOC_LEN].tobytes() != EOC:
+                raise DecodeError(
+                    "no EOC",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            obj.lenindef = True
+            tail = v[EOC_LEN:]
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
     def __repr__(self):
@@ -4228,6 +6172,7 @@ class SequenceOf(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -4243,9 +6188,15 @@ class SequenceOf(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
         for i, value in enumerate(self._value):
             yield value.pps(decode_path=decode_path + (str(i),))
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class SetOf(SequenceOf):
@@ -4263,6 +6214,16 @@ class SetOf(SequenceOf):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+        return super(SetOf, self)._decode(
+            tlv,
+            offset,
+            decode_path,
+            ctx,
+            tag_only,
+            ordering_check=True,
+        )
+
 
 def obj_by_path(pypath):  # pragma: no cover
     """Import object specified as string Python path
@@ -4287,7 +6248,7 @@ def generic_decoder():  # pragma: no cover
     choice = PrimitiveTypes()
     choice.specs["SequenceOf"] = SequenceOf(schema=choice)
     choice.specs["SetOf"] = SetOf(schema=choice)
-    for i in range(31):
+    for i in six_xrange(31):
         choice.specs["SequenceOf%d" % i] = SequenceOf(
             schema=choice,
             expl=tag_ctxc(i),
@@ -4299,10 +6260,21 @@ def generic_decoder():  # pragma: no cover
         __slots__ = ()
         schema = choice
 
-    def pprint_any(obj, oids=None):
+    def pprint_any(
+            obj,
+            oid_maps=(),
+            with_colours=False,
+            with_decode_path=False,
+            decode_path_only=(),
+    ):
         def _pprint_pps(pps):
             for pp in pps:
                 if hasattr(pp, "_fields"):
+                    if (
+                            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
                     pp_kwargs = pp._asdict()
@@ -4310,11 +6282,17 @@ def generic_decoder():  # pragma: no cover
                     pp = _pp(**pp_kwargs)
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
+                        with_colours=with_colours,
+                        with_decode_path=with_decode_path,
+                        decode_path_len_decrease=len(decode_path_only),
                     )
-                    for row in pp_console_blob(pp):
+                    for row in pp_console_blob(
+                            pp,
+                            decode_path_len_decrease=len(decode_path_only),
+                    ):
                         yield row
                 else:
                     for row in _pprint_pps(pp):
@@ -4325,32 +6303,80 @@ def generic_decoder():  # pragma: no cover
 
 def main():  # pragma: no cover
     import argparse
-    parser = argparse.ArgumentParser(description="PyDERASN ASN.1 DER decoder")
+    parser = argparse.ArgumentParser(description="PyDERASN ASN.1 BER/DER decoder")
+    parser.add_argument(
+        "--skip",
+        type=int,
+        default=0,
+        help="Skip that number of bytes from the beginning",
+    )
     parser.add_argument(
         "--oids",
-        help="Python path to dictionary with OIDs",
+        help="Python paths to dictionary with OIDs, comma separated",
     )
     parser.add_argument(
         "--schema",
         help="Python path to schema definition to use",
     )
+    parser.add_argument(
+        "--defines-by-path",
+        help="Python path to decoder's defines_by_path",
+    )
+    parser.add_argument(
+        "--nobered",
+        action="store_true",
+        help="Disallow BER encoding",
+    )
+    parser.add_argument(
+        "--print-decode-path",
+        action="store_true",
+        help="Print decode paths",
+    )
+    parser.add_argument(
+        "--decode-path-only",
+        help="Print only specified decode path",
+    )
+    parser.add_argument(
+        "--allow-expl-oob",
+        action="store_true",
+        help="Allow explicit tag out-of-bound",
+    )
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
         help="Path to DER file you want to decode",
     )
     args = parser.parse_args()
+    args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
-    oids = obj_by_path(args.oids) if args.oids else {}
+    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
         pprinter = partial(pprint, big_blobs=True)
     else:
         schema, pprinter = generic_decoder()
-    obj, tail = schema().decode(der)
-    print(pprinter(obj, oids=oids))
+    ctx = {
+        "bered": not args.nobered,
+        "allow_expl_oob": args.allow_expl_oob,
+    }
+    if args.defines_by_path is not None:
+        ctx["defines_by_path"] = obj_by_path(args.defines_by_path)
+    obj, tail = schema().decode(der, ctx=ctx)
+    print(pprinter(
+        obj,
+        oid_maps=oid_maps,
+        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
+            tuple(args.decode_path_only.split(":"))
+        ),
+    ))
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))