]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Unnecessary elif/else
[pyderasn.git] / pyderasn.py
index 41ed042a8424574a6fc4c1da0556fb197c8d773b..f563afef86964240e57971d85c91fa35d40de0c8 100755 (executable)
@@ -1,12 +1,11 @@
 #!/usr/bin/env python
 # coding: utf-8
 #!/usr/bin/env python
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
-# Copyright (C) 2017 Sergey Matveev <stargrave@stargrave.org>
+# PyDERASN -- Python ASN.1 DER/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
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
-# License along with this program.  If not, see
-# <http://www.gnu.org/licenses/>.
-"""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()
 
     >>> i = Integer(123)
     >>> raw = i.encode()
@@ -189,11 +187,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:
 
 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 ``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:
 
 
 .. _pprinting:
 
@@ -215,6 +237,105 @@ all object ``repr``. But it is easy to write custom formatters.
     >>> print(pprint(obj))
         0   [1,1,   2] INTEGER -12345
 
     >>> 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`` could be BERed.
+
+
 .. _definedby:
 
 DEFINED BY
 .. _definedby:
 
 DEFINED BY
@@ -225,20 +346,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.
 
 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
 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 = (
 container::
 
     class ContentInfo(Sequence):
         schema = (
-            ("contentType", ContentType(defines=("content", {
+            ("contentType", ContentType(defines=((("content",), {
                 id_digestedData: DigestedData(),
                 id_signedData: SignedData(),
                 id_digestedData: DigestedData(),
                 id_signedData: SignedData(),
-            }))),
+            }),))),
             ("content", Any(expl=tag_ctxc(0))),
         )
 
             ("content", Any(expl=tag_ctxc(0))),
         )
 
@@ -248,86 +371,157 @@ decoded with ``SignedData`` specification, if ``contentType`` equals to
 ``contentType`` contains unknown OID, then no automatic decoding is
 done.
 
 ``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`
 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`` tells by which OID it
 was defined, ``value`` contains corresponding decoded value. For example
 
 When any of those fields is automatically decoded, then ``.defined``
 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)``.
+above, ``content_info["content"].defined == (id_signedData, signed_data)``.
 
 
-.. _defines_by_path_kwarg:
+.. _defines_by_path_ctx:
 
 
-defines_by_path kwarg
-_____________________
+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.
 
 
 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
 
     (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``::
 
 
 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, tail = ContentInfo().decode(data, ctx={"defines_by_path": (
         (
             ("contentType",),
         (
             ("contentType",),
-            ("content", {id_signedData: SignedData()}),
+            ((("content",), {id_signedData: SignedData()}),),
         ),
         (
             (
                 "content",
         ),
         (
             (
                 "content",
-                decode_path_defby(id_signedData),
+                DecodePathDefBy(id_signedData),
                 "encapContentInfo",
                 "eContentType",
             ),
                 "encapContentInfo",
                 "eContentType",
             ),
-            ("eContent", {
+            ((("eContent",), {
                 id_cct_PKIData: PKIData(),
                 id_cct_PKIResponse: PKIResponse(),
                 id_cct_PKIData: PKIData(),
                 id_cct_PKIResponse: PKIResponse(),
-            }),
+            })),
         ),
         (
             (
                 "content",
         ),
         (
             (
                 "content",
-                decode_path_defby(id_signedData),
+                DecodePathDefBy(id_signedData),
                 "encapContentInfo",
                 "eContent",
                 "encapContentInfo",
                 "eContent",
-                decode_path_defby(id_cct_PKIResponse),
+                DecodePathDefBy(id_cct_PKIResponse),
                 "controlSequence",
                 any,
                 "attrType",
             ),
                 "controlSequence",
                 any,
                 "attrType",
             ),
-            ("attrValues", {
+            ((("attrValues",), {
                 id_cmc_recipientNonce: RecipientNonce(),
                 id_cmc_senderNonce: SenderNonce(),
                 id_cmc_statusInfoV2: CMCStatusInfoV2(),
                 id_cmc_transactionId: TransactionId(),
                 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`` means literally any value it meet --
 useful for SEQUENCE/SET OF-s.
 
 First function is useful for path construction when some automatic
 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``
+  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
 ---------------
 
 Primitive types
 ---------------
 
@@ -369,6 +563,14 @@ CommonString
 ____________
 .. autoclass:: pyderasn.CommonString
 
 ____________
 .. autoclass:: pyderasn.CommonString
 
+NumericString
+_____________
+.. autoclass:: pyderasn.NumericString
+
+PrintableString
+_______________
+.. autoclass:: pyderasn.PrintableString
+
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
@@ -421,21 +623,37 @@ _____
 Various
 -------
 
 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
 .. 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.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 codecs import getdecoder
 from codecs import getencoder
 from collections import namedtuple
 from collections import OrderedDict
+from copy import copy
 from datetime import datetime
 from math import ceil
 from datetime import datetime
 from math import ceil
+from os import environ
+from string import ascii_letters
+from string import digits
 
 from six import add_metaclass
 from six import binary_type
 
 from six import add_metaclass
 from six import binary_type
@@ -444,12 +662,23 @@ from six import indexbytes
 from six import int2byte
 from six import integer_types
 from six import iterbytes
 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 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
 
 
 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__ = "5.5"
+
 __all__ = (
     "Any",
     "BitString",
 __all__ = (
     "Any",
     "BitString",
@@ -457,8 +686,8 @@ __all__ = (
     "Boolean",
     "BoundsError",
     "Choice",
     "Boolean",
     "BoundsError",
     "Choice",
-    "decode_path_defby",
     "DecodeError",
     "DecodeError",
+    "DecodePathDefBy",
     "Enumerated",
     "GeneralizedTime",
     "GeneralString",
     "Enumerated",
     "GeneralizedTime",
     "GeneralString",
@@ -471,6 +700,7 @@ __all__ = (
     "InvalidOID",
     "InvalidValueType",
     "ISO646String",
     "InvalidOID",
     "InvalidValueType",
     "ISO646String",
+    "LenIndefForm",
     "NotEnoughData",
     "Null",
     "NumericString",
     "NotEnoughData",
     "Null",
     "NumericString",
@@ -516,13 +746,21 @@ TagClassReprs = {
     TagClassPrivate: "PRIVATE ",
     TagClassUniversal: "UNIV ",
 }
     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 "∞"
 
 
 ########################################################################
 # Errors
 ########################################################################
 
 
 
 ########################################################################
 # 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
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
         """
         :param str msg: reason of decode failing
@@ -545,7 +783,7 @@ class DecodeError(Exception):
             c for c in (
                 "" if self.klass is None else self.klass.__name__,
                 (
             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 "",
                     if len(self.decode_path) > 0 else ""
                 ),
                 ("(at %d)" % self.offset) if self.offset > 0 else "",
@@ -561,6 +799,10 @@ class NotEnoughData(DecodeError):
     pass
 
 
     pass
 
 
+class LenIndefForm(DecodeError):
+    pass
+
+
 class TagMismatch(DecodeError):
     pass
 
 class TagMismatch(DecodeError):
     pass
 
@@ -573,7 +815,7 @@ class InvalidOID(DecodeError):
     pass
 
 
     pass
 
 
-class ObjUnknown(ValueError):
+class ObjUnknown(ASN1Error):
     def __init__(self, name):
         super(ObjUnknown, self).__init__()
         self.name = name
     def __init__(self, name):
         super(ObjUnknown, self).__init__()
         self.name = name
@@ -585,7 +827,7 @@ class ObjUnknown(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class ObjNotReady(ValueError):
+class ObjNotReady(ASN1Error):
     def __init__(self, name):
         super(ObjNotReady, self).__init__()
         self.name = name
     def __init__(self, name):
         super(ObjNotReady, self).__init__()
         self.name = name
@@ -597,7 +839,7 @@ class ObjNotReady(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
         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
     def __init__(self, expected_types):
         super(InvalidValueType, self).__init__()
         self.expected_types = expected_types
@@ -611,7 +853,7 @@ class InvalidValueType(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
         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
     def __init__(self, bound_min, value, bound_max):
         super(BoundsError, self).__init__()
         self.bound_min = bound_min
@@ -752,6 +994,11 @@ def len_encode(l):
 
 
 def len_decode(data):
 
 
 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)
     if len(data) == 0:
         raise NotEnoughData("no data at all")
     first_octet = byte2int(data)
@@ -761,7 +1008,7 @@ def len_decode(data):
     if octets_num + 1 > len(data):
         raise NotEnoughData("encoded length is longer than data")
     if octets_num == 0:
     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
     if byte2int(data[1:]) == 0:
         raise DecodeError("leading zeros")
     l = 0
@@ -777,9 +1024,9 @@ def len_decode(data):
 ########################################################################
 
 class AutoAddSlots(type):
 ########################################################################
 
 class AutoAddSlots(type):
-    def __new__(mcs, name, bases, _dict):
+    def __new__(cls, name, bases, _dict):
         _dict["__slots__"] = _dict.get("__slots__", ())
         _dict["__slots__"] = _dict.get("__slots__", ())
-        return type.__new__(mcs, name, bases, _dict)
+        return type.__new__(cls, name, bases, _dict)
 
 
 @add_metaclass(AutoAddSlots)
 
 
 @add_metaclass(AutoAddSlots)
@@ -798,6 +1045,9 @@ class Obj(object):
         "offset",
         "llen",
         "vlen",
         "offset",
         "llen",
         "vlen",
+        "expl_lenindef",
+        "lenindef",
+        "ber_encoded",
     )
 
     def __init__(
     )
 
     def __init__(
@@ -811,14 +1061,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:
         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
         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
 
     @property
     def ready(self):  # pragma: no cover
@@ -830,6 +1081,12 @@ class Obj(object):
         if not self.ready:
             raise ObjNotReady(self.__class__.__name__)
 
         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?
     @property
     def decoded(self):
         """Is object decoded?
@@ -843,10 +1100,14 @@ class Obj(object):
 
     @property
     def tlen(self):
 
     @property
     def tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self.tag)
 
     @property
     def tlvlen(self):
         return len(self.tag)
 
     @property
     def tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
@@ -867,33 +1128,60 @@ class Obj(object):
     def _encode(self):  # pragma: no cover
         raise NotImplementedError()
 
     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):
         raise NotImplementedError()
 
     def encode(self):
+        """Encode the structure
+
+        :returns: DER representation
+        """
         raw = self._encode()
         if self._expl is None:
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
         raw = self._encode()
         if self._expl is None:
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
-    def decode(self, data, offset=0, leavemm=False, decode_path=(), defines_by_path=None):
+    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
         """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 ``ctx`` before using it
         :returns: (Obj, remaining data)
         :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:
         tlv = memoryview(data)
         if self._expl is None:
-            obj, tail = self._decode(
+            result = self._decode(
                 tlv,
                 offset,
                 decode_path=decode_path,
                 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)
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
@@ -912,6 +1200,36 @@ class Obj(object):
                 )
             try:
                 l, llen, v = len_decode(lv)
                 )
             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,
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -919,54 +1237,143 @@ class Obj(object):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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,
                     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())
 
     @property
     def expled(self):
         return obj, (tail if leavemm else tail.tobytes())
 
     @property
     def expled(self):
+        """See :ref:`decoding`
+        """
         return self._expl is not None
 
     @property
     def expl_tag(self):
         return self._expl is not None
 
     @property
     def expl_tag(self):
+        """See :ref:`decoding`
+        """
         return self._expl
 
     @property
     def expl_tlen(self):
         return self._expl
 
     @property
     def expl_tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self._expl)
 
     @property
     def expl_llen(self):
         return len(self._expl)
 
     @property
     def expl_llen(self):
+        """See :ref:`decoding`
+        """
+        if self.expl_lenindef:
+            return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
+        """See :ref:`decoding`
+        """
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
         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
     """
     """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)
 
 
 ########################################################################
 
 
 ########################################################################
@@ -974,6 +1381,7 @@ def decode_path_defby(defined_by):
 ########################################################################
 
 PP = namedtuple("PP", (
 ########################################################################
 
 PP = namedtuple("PP", (
+    "obj",
     "asn1_type_name",
     "obj_name",
     "decode_path",
     "asn1_type_name",
     "obj_name",
     "decode_path",
@@ -991,10 +1399,15 @@ PP = namedtuple("PP", (
     "expl_tlen",
     "expl_llen",
     "expl_vlen",
     "expl_tlen",
     "expl_llen",
     "expl_vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+    "bered",
 ))
 
 
 def _pp(
 ))
 
 
 def _pp(
+        obj=None,
         asn1_type_name="unknown",
         obj_name="unknown",
         decode_path=(),
         asn1_type_name="unknown",
         obj_name="unknown",
         decode_path=(),
@@ -1012,8 +1425,13 @@ def _pp(
         expl_tlen=None,
         expl_llen=None,
         expl_vlen=None,
         expl_tlen=None,
         expl_llen=None,
         expl_vlen=None,
+        expl_lenindef=False,
+        lenindef=False,
+        ber_encoded=False,
+        bered=False,
 ):
     return PP(
 ):
     return PP(
+        obj,
         asn1_type_name,
         obj_name,
         decode_path,
         asn1_type_name,
         obj_name,
         decode_path,
@@ -1031,94 +1449,198 @@ def _pp(
         expl_tlen,
         expl_llen,
         expl_vlen,
         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 = []
     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))
             ),
             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,
             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
     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
     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():
     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
     if pp.value is not None:
         value = pp.value
+        cols.append(_colourize(value, "white", with_colours, ("reverse",)))
         if (
         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):
             cols.append(hexenc(pp.blob))
         elif isinstance(pp.blob, tuple):
             cols.append(", ".join(pp.blob))
     if pp.optional:
     if with_blob:
         if isinstance(pp.blob, binary_type):
             cols.append(hexenc(pp.blob))
         elif isinstance(pp.blob, tuple):
             cols.append(", ".join(pp.blob))
     if pp.optional:
-        cols.append("OPTIONAL")
+        cols.append(_colourize("OPTIONAL", "red", with_colours))
     if pp.default:
     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)
 
 
     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))
+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 isinstance(pp.blob, binary_type):
         blob = hexenc(pp.blob).upper()
     if isinstance(pp.blob, 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]
             chunk = blob[i:i + 32]
-            yield " ".join(cols + [":".join(
-                chunk[j:j + 2] for j in range(0, len(chunk), 2)
-            )])
+            yield " ".join(cols + [colonize_hex(chunk)])
     elif isinstance(pp.blob, tuple):
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
     elif isinstance(pp.blob, 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
     """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 ``OID <-> humand readable string`` dictionary.
+                     When OID from it is met, then its humand readable form
+                     is printed
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
+    :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"):
     """
     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,
                 if big_blobs:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         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 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
             else:
                 for row in _pprint_pps(pp):
                     yield row
@@ -1173,10 +1695,10 @@ class Boolean(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, Boolean):
-            return value._value
         if isinstance(value, bool):
             return value
         if isinstance(value, bool):
             return value
+        if issubclass(value.__class__, Boolean):
+            return value._value
         raise InvalidValueType((self.__class__, bool))
 
     @property
         raise InvalidValueType((self.__class__, bool))
 
     @property
@@ -1193,6 +1715,9 @@ class Boolean(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __nonzero__(self):
         return obj
 
     def __nonzero__(self):
@@ -1238,7 +1763,7 @@ class Boolean(Obj):
             (b"\xFF" if self._value else b"\x00"),
         ))
 
             (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:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1254,6 +1779,8 @@ class Boolean(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -1278,10 +1805,14 @@ class Boolean(Obj):
                 offset=offset,
             )
         first_octet = byte2int(v)
                 offset=offset,
             )
         first_octet = byte2int(v)
+        ber_encoded = False
         if first_octet == 0:
             value = False
         elif first_octet == 0xFF:
             value = True
         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",
         else:
             raise DecodeError(
                 "unacceptable Boolean value",
@@ -1297,6 +1828,7 @@ class Boolean(Obj):
             optional=self.optional,
             _decoded=(offset, 1, 1),
         )
             optional=self.optional,
             _decoded=(offset, 1, 1),
         )
+        obj.ber_encoded = ber_encoded
         return obj, v[1:]
 
     def __repr__(self):
         return obj, v[1:]
 
     def __repr__(self):
@@ -1304,6 +1836,7 @@ class Boolean(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1320,7 +1853,12 @@ 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_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 Integer(Obj):
 
 
 class Integer(Obj):
@@ -1406,10 +1944,10 @@ class Integer(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
                 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
             pass
+        elif issubclass(value.__class__, Integer):
+            value = value._value
         elif isinstance(value, str):
             value = self.specs.get(value)
             if value is None:
         elif isinstance(value, str):
             value = self.specs.get(value)
             if value is None:
@@ -1436,6 +1974,9 @@ class Integer(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __int__(self):
         return obj
 
     def __int__(self):
@@ -1466,9 +2007,10 @@ class Integer(Obj):
 
     @property
     def named(self):
 
     @property
     def named(self):
-        for name, value in self.specs.items():
+        for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
             if value == self._value:
                 return name
+        return None
 
     def __call__(
             self,
 
     def __call__(
             self,
@@ -1531,7 +2073,7 @@ class Integer(Obj):
                     break
         return b"".join((self.tag, len_encode(len(octets)), octets))
 
                     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:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1547,6 +2089,8 @@ class Integer(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return None
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -1624,6 +2168,7 @@ class Integer(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1640,7 +2185,14 @@ 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_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
+
+
+SET01 = frozenset(("0", "1"))
 
 
 class BitString(Obj):
 
 
 class BitString(Obj):
@@ -1655,6 +2207,8 @@ class BitString(Obj):
     >>> b.bit_len
     88
 
     >>> b.bit_len
     88
 
+    >>> BitString("'0A3B5F291CD'H")
+    BIT STRING 44 bits 0a3b5f291cd0
     >>> b = BitString("'010110000000'B")
     BIT STRING 12 bits 5800
     >>> b.bit_len
     >>> b = BitString("'010110000000'B")
     BIT STRING 12 bits 5800
     >>> b.bit_len
@@ -1670,19 +2224,27 @@ class BitString(Obj):
 
         class KeyUsage(BitString):
             schema = (
 
         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}
     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", "defined")
+    __slots__ = ("tag_constructed", "specs", "defined")
     tag_default = tag_encode(3)
     asn1_type_name = "BIT STRING"
 
     tag_default = tag_encode(3)
     asn1_type_name = "BIT STRING"
 
@@ -1720,6 +2282,12 @@ class BitString(Obj):
             if value is None:
                 self._value = default
         self.defined = None
             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:
 
     def _bits2octets(self, bits):
         if len(self.specs) > 0:
@@ -1732,26 +2300,25 @@ class BitString(Obj):
         return bit_len, bytes(octets)
 
     def _value_sanitize(self, value):
         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
         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 isinstance(value, binary_type):
                 return (len(value) * 8, value)
                 return (len(value) * 8, value)
-            else:
-                raise InvalidValueType((
-                    self.__class__,
-                    string_types,
-                    binary_type,
-                ))
+            raise InvalidValueType((self.__class__, string_types, binary_type))
         if isinstance(value, tuple):
             if (
                     len(value) == 2 and
         if isinstance(value, tuple):
             if (
                     len(value) == 2 and
@@ -1767,11 +2334,13 @@ class BitString(Obj):
                 bits.append(bit)
             if len(bits) == 0:
                 return self._bits2octets("")
                 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)
             ))
             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
         raise InvalidValueType((self.__class__, binary_type, string_types))
 
     @property
@@ -1791,6 +2360,9 @@ class BitString(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __iter__(self):
         return obj
 
     def __iter__(self):
@@ -1820,7 +2392,7 @@ class BitString(Obj):
 
     @property
     def named(self):
 
     @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,
 
     def __call__(
             self,
@@ -1865,22 +2437,7 @@ class BitString(Obj):
             octets,
         ))
 
             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_chunk(self, lv, offset, decode_path, ctx):
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -1919,7 +2476,7 @@ class BitString(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
-        if byte2int(v[-1:]) & ((1 << pad_size) - 1) != 0:
+        if byte2int(v[l - 1:l]) & ((1 << pad_size) - 1) != 0:
             raise DecodeError(
                 "invalid pad",
                 klass=self.__class__,
             raise DecodeError(
                 "invalid pad",
                 klass=self.__class__,
@@ -1938,6 +2495,135 @@ class BitString(Obj):
         )
         return obj, tail
 
         )
         return obj, tail
 
+    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,
+            )
+        if t == self.tag:
+            if tag_only:  # pragma: no cover
+                return None
+            return self._decode_chunk(lv, offset, decode_path, ctx)
+        if t == self.tag_constructed:
+            if not ctx.get("bered", False):
+                raise DecodeError(
+                    "unallowed BER constructed encoding",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if tag_only:  # pragma: no cover
+                return None
+            lenindef = False
+            try:
+                l, llen, v = len_decode(lv)
+            except LenIndefForm:
+                llen, l, v = 1, 0, lv[1:]
+                lenindef = True
+            except DecodeError as err:
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if l > len(v):
+                raise NotEnoughData(
+                    "encoded length is longer than data",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if not lenindef and l == 0:
+                raise NotEnoughData(
+                    "zero length",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            chunks = []
+            sub_offset = offset + tlen + llen
+            vlen = 0
+            while True:
+                if lenindef:
+                    if v[:EOC_LEN].tobytes() == EOC:
+                        break
+                else:
+                    if vlen == l:
+                        break
+                    if vlen > l:
+                        raise DecodeError(
+                            "chunk out of bounds",
+                            klass=self.__class__,
+                            decode_path=decode_path + (str(len(chunks) - 1),),
+                            offset=chunks[-1].offset,
+                        )
+                sub_decode_path = decode_path + (str(len(chunks)),)
+                try:
+                    chunk, v_tail = BitString().decode(
+                        v,
+                        offset=sub_offset,
+                        decode_path=sub_decode_path,
+                        leavemm=True,
+                        ctx=ctx,
+                        _ctx_immutable=False,
+                    )
+                except TagMismatch:
+                    raise DecodeError(
+                        "expected BitString encoded chunk",
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
+                chunks.append(chunk)
+                sub_offset += chunk.tlvlen
+                vlen += chunk.tlvlen
+                v = v_tail
+            if len(chunks) == 0:
+                raise DecodeError(
+                    "no chunks",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            values = []
+            bit_len = 0
+            for chunk_i, chunk in enumerate(chunks[:-1]):
+                if chunk.bit_len % 8 != 0:
+                    raise DecodeError(
+                        "BitString chunk is not multiple of 8 bits",
+                        klass=self.__class__,
+                        decode_path=decode_path + (str(chunk_i),),
+                        offset=chunk.offset,
+                    )
+                values.append(bytes(chunk))
+                bit_len += chunk.bit_len
+            chunk_last = chunks[-1]
+            values.append(bytes(chunk_last))
+            bit_len += chunk_last.bit_len
+            obj = self.__class__(
+                value=(bit_len, b"".join(values)),
+                impl=self.tag,
+                expl=self._expl,
+                default=self.default,
+                optional=self.optional,
+                _specs=self.specs,
+                _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+            )
+            obj.lenindef = lenindef
+            obj.ber_encoded = True
+            return obj, (v[EOC_LEN:] if lenindef else v)
+        raise TagMismatch(
+            klass=self.__class__,
+            decode_path=decode_path,
+            offset=offset,
+        )
+
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
@@ -1950,6 +2636,7 @@ class BitString(Obj):
             if len(self.specs) > 0:
                 blob = tuple(self.named)
         yield _pp(
             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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1967,12 +2654,18 @@ 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_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(
         )
         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
 
 
 class OctetString(Obj):
 
 
 class OctetString(Obj):
@@ -1990,8 +2683,16 @@ class OctetString(Obj):
     pyderasn.BoundsError: unsatisfied bounds: 4 <= 5 <= 4
     >>> OctetString(b"hell", bounds=(4, 4))
     OCTET STRING 4 bytes 68656c6c
     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"
 
     tag_default = tag_encode(4)
     asn1_type_name = "OCTET STRING"
 
@@ -2040,12 +2741,18 @@ class OctetString(Obj):
             if self._value is None:
                 self._value = default
         self.defined = None
             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):
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, OctetString):
-            value = value._value
-        elif isinstance(value, binary_type):
+        if isinstance(value, binary_type):
             pass
             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:
         else:
             raise InvalidValueType((self.__class__, bytes))
         if not self._bound_min <= len(value) <= self._bound_max:
@@ -2068,6 +2775,9 @@ class OctetString(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __bytes__(self):
         return obj
 
     def __bytes__(self):
@@ -2117,22 +2827,7 @@ class OctetString(Obj):
             self._value,
         ))
 
             self._value,
         ))
 
-    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_chunk(self, lv, offset, decode_path, ctx):
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2160,6 +2855,13 @@ class OctetString(Obj):
                 optional=self.optional,
                 _decoded=(offset, llen, l),
             )
                 optional=self.optional,
                 _decoded=(offset, llen, l),
             )
+        except DecodeError as err:
+            raise DecodeError(
+                msg=err.msg,
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
         except BoundsError as err:
             raise DecodeError(
                 msg=str(err),
         except BoundsError as err:
             raise DecodeError(
                 msg=str(err),
@@ -2169,11 +2871,127 @@ class OctetString(Obj):
             )
         return obj, tail
 
             )
         return obj, tail
 
+    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,
+            )
+        if t == self.tag:
+            if tag_only:
+                return None
+            return self._decode_chunk(lv, offset, decode_path, ctx)
+        if t == self.tag_constructed:
+            if not ctx.get("bered", False):
+                raise DecodeError(
+                    "unallowed BER constructed encoding",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if tag_only:
+                return None
+            lenindef = False
+            try:
+                l, llen, v = len_decode(lv)
+            except LenIndefForm:
+                llen, l, v = 1, 0, lv[1:]
+                lenindef = True
+            except DecodeError as err:
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if l > len(v):
+                raise NotEnoughData(
+                    "encoded length is longer than data",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            chunks = []
+            sub_offset = offset + tlen + llen
+            vlen = 0
+            while True:
+                if lenindef:
+                    if v[:EOC_LEN].tobytes() == EOC:
+                        break
+                else:
+                    if vlen == l:
+                        break
+                    if vlen > l:
+                        raise DecodeError(
+                            "chunk out of bounds",
+                            klass=self.__class__,
+                            decode_path=decode_path + (str(len(chunks) - 1),),
+                            offset=chunks[-1].offset,
+                        )
+                sub_decode_path = decode_path + (str(len(chunks)),)
+                try:
+                    chunk, v_tail = OctetString().decode(
+                        v,
+                        offset=sub_offset,
+                        decode_path=sub_decode_path,
+                        leavemm=True,
+                        ctx=ctx,
+                        _ctx_immutable=False,
+                    )
+                except TagMismatch:
+                    raise DecodeError(
+                        "expected OctetString encoded chunk",
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
+                chunks.append(chunk)
+                sub_offset += chunk.tlvlen
+                vlen += chunk.tlvlen
+                v = v_tail
+            try:
+                obj = self.__class__(
+                    value=b"".join(bytes(chunk) for chunk in chunks),
+                    bounds=(self._bound_min, self._bound_max),
+                    impl=self.tag,
+                    expl=self._expl,
+                    default=self.default,
+                    optional=self.optional,
+                    _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+                )
+            except DecodeError as err:
+                raise DecodeError(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            except BoundsError as err:
+                raise DecodeError(
+                    msg=str(err),
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            obj.lenindef = lenindef
+            obj.ber_encoded = True
+            return obj, (v[EOC_LEN:] if lenindef else v)
+        raise TagMismatch(
+            klass=self.__class__,
+            decode_path=decode_path,
+            offset=offset,
+        )
+
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
         yield _pp(
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2191,12 +3009,18 @@ 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_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(
         )
         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
 
 
 class Null(Obj):
 
 
 class Null(Obj):
@@ -2240,6 +3064,9 @@ class Null(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __eq__(self, their):
         return obj
 
     def __eq__(self, their):
@@ -2266,7 +3093,7 @@ class Null(Obj):
     def _encode(self):
         return self.tag + len_encode(0)
 
     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:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2282,6 +3109,8 @@ class Null(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:  # pragma: no cover
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -2311,6 +3140,7 @@ class Null(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2325,7 +3155,11 @@ 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_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
 
 
 class ObjectIdentifier(Obj):
 
 
 class ObjectIdentifier(Obj):
@@ -2353,7 +3187,7 @@ class ObjectIdentifier(Obj):
     def __init__(
             self,
             value=None,
     def __init__(
             self,
             value=None,
-            defines=None,
+            defines=(),
             impl=None,
             expl=None,
             default=None,
             impl=None,
             expl=None,
             default=None,
@@ -2364,12 +3198,14 @@ class ObjectIdentifier(Obj):
         :param value: set the value. Either tuples of integers,
                       string of "."-concatenated integers, or
                       :py:class:`pyderasn.ObjectIdentifier` object
         :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
                         :ref:`Read about DEFINED BY <definedby>`
         :param bytes impl: override default tag with ``IMPLICIT`` one
         :param bytes expl: override default tag with ``EXPLICIT`` one
@@ -2441,6 +3277,9 @@ class ObjectIdentifier(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __iter__(self):
         return obj
 
     def __iter__(self):
@@ -2509,7 +3348,7 @@ class ObjectIdentifier(Obj):
         v = b"".join(octets)
         return b"".join((self.tag, len_encode(len(v)), v))
 
         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:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2525,6 +3364,8 @@ class ObjectIdentifier(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 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:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2550,11 +3391,17 @@ class ObjectIdentifier(Obj):
             )
         v, tail = v[:l], v[l:]
         arcs = []
             )
         v, tail = v[:l], v[l:]
         arcs = []
+        ber_encoded = False
         while len(v) > 0:
             i = 0
             arc = 0
             while True:
                 octet = indexbytes(v, i)
         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)
                 arc = (arc << 7) | (octet & 0x7F)
                 if octet & 0x80 == 0:
                     arcs.append(arc)
@@ -2586,6 +3433,8 @@ class ObjectIdentifier(Obj):
             optional=self.optional,
             _decoded=(offset, llen, l),
         )
             optional=self.optional,
             _decoded=(offset, llen, l),
         )
+        if ber_encoded:
+            obj.ber_encoded = True
         return obj, tail
 
     def __repr__(self):
         return obj, tail
 
     def __repr__(self):
@@ -2593,6 +3442,7 @@ class ObjectIdentifier(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2609,7 +3459,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_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):
 
 
 class Enumerated(Integer):
@@ -2649,7 +3504,10 @@ class Enumerated(Integer):
         if isinstance(value, self.__class__):
             value = value._value
         elif isinstance(value, integer_types):
         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__,
                 raise DecodeError(
                     "unknown integer value: %s" % value,
                     klass=self.__class__,
@@ -2674,6 +3532,9 @@ class Enumerated(Integer):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __call__(
         return obj
 
     def __call__(
@@ -2714,7 +3575,7 @@ class CommonString(OctetString):
 
     >>> PrintableString("привет мир")
     Traceback (most recent call last):
 
     >>> 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):
 
     >>> BMPString("ада", bounds=(2, 2))
     Traceback (most recent call last):
@@ -2770,14 +3631,17 @@ class CommonString(OctetString):
             value_raw = value
         else:
             raise InvalidValueType((self.__class__, text_type, 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,
         if not self._bound_min <= len(value_decoded) <= self._bound_max:
             raise BoundsError(
                 self._bound_min,
@@ -2812,6 +3676,7 @@ class CommonString(OctetString):
         if self.ready:
             value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
         yield _pp(
         if self.ready:
             value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2824,7 +3689,16 @@ class CommonString(OctetString):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
             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):
 
 
 class UTF8String(CommonString):
@@ -2834,18 +3708,57 @@ class UTF8String(CommonString):
     asn1_type_name = "UTF8String"
 
 
     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"
     __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
+
 
 
+class PrintableString(AllowableCharsMixin, CommonString):
+    """Printable string
 
 
-class PrintableString(CommonString):
+    Its value is properly sanitized: see X.680 41.4 table 10.
+
+    >>> PrintableString().allowable_chars
+    frozenset([' ', "'", ..., 'z'])
+    """
     __slots__ = ()
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
     __slots__ = ()
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
+    _allowable_chars = frozenset(
+        (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+    )
+
+    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
 
 
 class TeletexString(CommonString):
 
 
 class TeletexString(CommonString):
@@ -2892,14 +3805,16 @@ 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)
     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)
+
+    .. warning::
+
+       BER encoding is unsupported.
     """
     __slots__ = ()
     tag_default = tag_encode(23)
     encoding = "ascii"
     asn1_type_name = "UTCTime"
 
     """
     __slots__ = ()
     tag_default = tag_encode(23)
     encoding = "ascii"
     asn1_type_name = "UTCTime"
 
-    fmt = "%y%m%d%H%M%SZ"
-
     def __init__(
             self,
             value=None,
     def __init__(
             self,
             value=None,
@@ -2938,21 +3853,36 @@ class UTCTime(CommonString):
             if self._value is None:
                 self._value = default
 
             if self._value is None:
                 self._value = default
 
+    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")
+        return datetime(
+            2000 + int(value[:2]),  # %y
+            int(value[2:4]),  # %m
+            int(value[4:6]),  # %d
+            int(value[6:8]),  # %H
+            int(value[8:10]),  # %M
+            int(value[10:12]),  # %S
+        )
+
     def _value_sanitize(self, value):
     def _value_sanitize(self, value):
+        if isinstance(value, binary_type):
+            try:
+                value_decoded = value.decode("ascii")
+            except (UnicodeEncodeError, UnicodeDecodeError) as err:
+                raise DecodeError("invalid UTCTime encoding: %r" % err)
+            try:
+                self._strptime(value_decoded)
+            except (TypeError, ValueError) as err:
+                raise DecodeError("invalid UTCTime format: %r" % err)
+            return value
         if isinstance(value, self.__class__):
             return value._value
         if isinstance(value, datetime):
         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
-            else:
-                raise DecodeError("invalid UTCTime length")
+            return value.strftime("%y%m%d%H%M%SZ").encode("ascii")
         raise InvalidValueType((self.__class__, datetime))
 
     def __eq__(self, their):
         raise InvalidValueType((self.__class__, datetime))
 
     def __eq__(self, their):
@@ -2977,7 +3907,7 @@ class UTCTime(CommonString):
         having < 50 years are treated as 20xx, 19xx otherwise, according
         to X.509 recomendation.
         """
         having < 50 years are treated as 20xx, 19xx otherwise, according
         to X.509 recomendation.
         """
-        value = datetime.strptime(self._value.decode("ascii"), self.fmt)
+        value = self._strptime(self._value.decode("ascii"))
         year = value.year % 100
         return datetime(
             year=(2000 + year) if year < 50 else (1900 + year),
         year = value.year % 100
         return datetime(
             year=(2000 + year) if year < 50 else (1900 + year),
@@ -2993,6 +3923,7 @@ class UTCTime(CommonString):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3005,7 +3936,16 @@ class UTCTime(CommonString):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
             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):
 
 
 class GeneralizedTime(UTCTime):
@@ -3019,51 +3959,85 @@ class GeneralizedTime(UTCTime):
     '20170930220750.000123Z'
     >>> t = GeneralizedTime(datetime(2057, 9, 30, 22, 7, 50))
     GeneralizedTime GeneralizedTime 2057-09-30T22:07:50
     '20170930220750.000123Z'
     >>> t = GeneralizedTime(datetime(2057, 9, 30, 22, 7, 50))
     GeneralizedTime GeneralizedTime 2057-09-30T22:07:50
+
+    .. warning::
+
+       BER encoding is unsupported.
+
+    .. warning::
+
+       Only microsecond fractions are supported.
+       :py:exc:`pyderasn.DecodeError` will be raised during decoding of
+       higher precision values.
     """
     __slots__ = ()
     tag_default = tag_encode(24)
     asn1_type_name = "GeneralizedTime"
 
     """
     __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 _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(
+                int(value[:4]),  # %Y
+                int(value[4:6]),  # %m
+                int(value[6:8]),  # %d
+                int(value[8:10]),  # %H
+                int(value[10:12]),  # %M
+                int(value[12:14]),  # %S
+            )
+        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 = int(us + ("0" * (6 - us_len)))
+            decoded = datetime(
+                int(value[:4]),  # %Y
+                int(value[4:6]),  # %m
+                int(value[6:8]),  # %d
+                int(value[8:10]),  # %H
+                int(value[10:12]),  # %M
+                int(value[12:14]),  # %S
+                us,  # %f
+            )
+            return decoded
+        raise ValueError("invalid GeneralizedTime length")
 
     def _value_sanitize(self, 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):
         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:
+            try:
+                value_decoded = value.decode("ascii")
+            except (UnicodeEncodeError, UnicodeDecodeError) as err:
+                raise DecodeError("invalid GeneralizedTime encoding: %r" % err)
+            try:
+                self._strptime(value_decoded)
+            except (TypeError, ValueError) as err:
                 raise DecodeError(
                 raise DecodeError(
-                    "invalid GeneralizedTime length",
+                    "invalid GeneralizedTime format: %r" % err,
                     klass=self.__class__,
                 )
                     klass=self.__class__,
                 )
+            return value
+        if isinstance(value, self.__class__):
+            return value._value
+        if isinstance(value, datetime):
+            encoded = value.strftime("%Y%m%d%H%M%S")
+            if value.microsecond > 0:
+                encoded = encoded + (".%06d" % value.microsecond).rstrip("0")
+            return (encoded + "Z").encode("ascii")
         raise InvalidValueType((self.__class__, datetime))
 
     def todatetime(self):
         raise InvalidValueType((self.__class__, datetime))
 
     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)
+        return self._strptime(self._value.decode("ascii"))
 
 
 class GraphicString(CommonString):
 
 
 class GraphicString(CommonString):
@@ -3113,8 +4087,8 @@ class Choice(Obj):
 
         class GeneralName(Choice):
             schema = (
 
         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()
             )
 
     >>> gn = GeneralName()
@@ -3182,8 +4156,6 @@ class Choice(Obj):
                 self._value = default_obj.copy()._value
 
     def _value_sanitize(self, value):
                 self._value = default_obj.copy()._value
 
     def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
         if isinstance(value, tuple) and len(value) == 2:
             choice, obj = value
             spec = self.specs.get(choice)
         if isinstance(value, tuple) and len(value) == 2:
             choice, obj = value
             spec = self.specs.get(choice)
@@ -3192,12 +4164,21 @@ class Choice(Obj):
             if not isinstance(obj, spec.__class__):
                 raise InvalidValueType((spec,))
             return (choice, spec(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
 
         raise InvalidValueType((self.__class__, tuple))
 
     @property
     def ready(self):
         return self._value is not None and self._value[1].ready
 
+    @property
+    def bered(self):
+        return self.expl_lenindef or (
+            (self._value is not None) and
+            self._value[1].bered
+        )
+
     def copy(self):
         obj = self.__class__(schema=self.specs)
         obj._expl = self._expl
     def copy(self):
         obj = self.__class__(schema=self.specs)
         obj._expl = self._expl
@@ -3206,6 +4187,9 @@ class Choice(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         value = self._value
         if value is not None:
             obj._value = (value[0], value[1].copy())
         value = self._value
         if value is not None:
             obj._value = (value[0], value[1].copy())
@@ -3276,32 +4260,47 @@ class Choice(Obj):
         self._assert_ready()
         return self._value[1].encode()
 
         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:
             try:
-                value, tail = spec.decode(
+                spec.decode(
                     tlv,
                     offset=offset,
                     leavemm=True,
                     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
                 )
             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,
             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()))
 
     def __repr__(self):
         value = pp_console_row(next(self.pps()))
@@ -3311,6 +4310,7 @@ class Choice(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3323,9 +4323,13 @@ class Choice(Obj):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
             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,))
         )
         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):
 
 
 class PrimitiveTypes(Choice):
@@ -3397,18 +4401,26 @@ class Any(Obj):
         self.defined = None
 
     def _value_sanitize(self, value):
         self.defined = None
 
     def _value_sanitize(self, value):
+        if isinstance(value, binary_type):
+            return value
         if isinstance(value, self.__class__):
             return value._value
         if isinstance(value, Obj):
             return value.encode()
         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
 
         raise InvalidValueType((self.__class__, Obj, binary_type))
 
     @property
     def ready(self):
         return self._value is not None
 
+    @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 copy(self):
         obj = self.__class__()
         obj._value = self._value
     def copy(self):
         obj = self.__class__()
         obj._value = self._value
@@ -3418,6 +4430,9 @@ class Any(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __eq__(self, their):
         return obj
 
     def __eq__(self, their):
@@ -3451,10 +4466,51 @@ class Any(Obj):
         self._assert_ready()
         return self._value
 
         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)
         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)
             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
+            return obj, v[EOC_LEN:]
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -3485,6 +4541,7 @@ class Any(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3501,12 +4558,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_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(
         )
         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
 
 
 ########################################################################
 
 
 ########################################################################
@@ -3526,6 +4588,31 @@ def get_def_by_path(defines_by_path, sub_decode_path):
             return define
 
 
             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
+
+
 class Sequence(Obj):
     """``SEQUENCE`` structure type
 
 class Sequence(Obj):
     """``SEQUENCE`` structure type
 
@@ -3552,7 +4639,7 @@ class Sequence(Obj):
     pyderasn.InvalidValueType: invalid value type, expected: <class 'pyderasn.ObjectIdentifier'>
     >>> ext["extnID"] = ObjectIdentifier("1.2.3")
 
     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
 
     >>> ext.ready
     False
@@ -3576,7 +4663,17 @@ class Sequence(Obj):
     >>> tbs = TBSCertificate()
     >>> tbs["version"] = Version("v2") # no need to explicitly add ``expl``
 
     >>> 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)
 
     >>> "extnID" in ext, "extnValue" in ext, "critical" in ext
     (True, True, False)
@@ -3595,13 +4692,14 @@ class Sequence(Obj):
 
     All defaulted values are always optional.
 
 
     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.
 
     Two sequences are equal if they have equal specification (schema),
     implicit/explicit tagging and the same values.
@@ -3628,9 +4726,17 @@ class Sequence(Obj):
         )
         self._value = {}
         if value is not None:
         )
         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:
         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
             default_obj = self.__class__(impl=self.tag, expl=self._expl)
             default_obj.specs = self.specs
             default_obj._value = default_value
@@ -3638,24 +4744,24 @@ class Sequence(Obj):
             if value is None:
                 self._value = default_obj.copy()._value
 
             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
-
     @property
     def ready(self):
     @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
             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
 
         return True
 
+    @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 copy(self):
         obj = self.__class__(schema=self.specs)
         obj.tag = self.tag
     def copy(self):
         obj = self.__class__(schema=self.specs)
         obj.tag = self.tag
@@ -3665,7 +4771,10 @@ class Sequence(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
-        obj._value = {k: v.copy() for k, v in self._value.items()}
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
+        obj._value = {k: v.copy() for k, v in iteritems(self._value)}
         return obj
 
     def __eq__(self, their):
         return obj
 
     def __eq__(self, their):
@@ -3726,7 +4835,7 @@ class Sequence(Obj):
 
     def _encoded_values(self):
         raws = []
 
     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:
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
@@ -3739,7 +4848,7 @@ class Sequence(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
         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:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3755,8 +4864,22 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 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)
         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,
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -3771,12 +4894,18 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 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 = {}
         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:
                 continue
             sub_decode_path = decode_path + (name,)
             try:
@@ -3785,28 +4914,33 @@ class Sequence(Obj):
                     sub_offset,
                     leavemm=True,
                     decode_path=sub_decode_path,
                     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
 
                     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),
             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)),
                         )
                         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,
                             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(
                         )
                         if len(defined_tail) > 0:
                             raise DecodeError(
@@ -3819,37 +4953,64 @@ class Sequence(Obj):
                 else:
                     defined_value, defined_tail = defined_spec.decode(
                         memoryview(bytes(value)),
                 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,
                         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__,
                     )
                     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)
 
                             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:
             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
 
             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__,
             raise DecodeError(
                 "remaining data",
                 klass=self.__class__,
@@ -3862,9 +5023,11 @@ class Sequence(Obj):
             expl=self._expl,
             default=self.default,
             optional=self.optional,
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, llen, l),
+            _decoded=(offset, llen, vlen),
         )
         obj._value = values
         )
         obj._value = values
+        obj.lenindef = lenindef
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
     def __repr__(self):
         return obj, tail
 
     def __repr__(self):
@@ -3874,11 +5037,12 @@ class Sequence(Obj):
             _value = self._value.get(name)
             if _value is None:
                 continue
             _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(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3894,18 +5058,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_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 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`.
 
 
 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)
     """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
@@ -3917,7 +5095,10 @@ class Set(Sequence):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
         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:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3933,8 +5114,22 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
                 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)
         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,
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -3948,29 +5143,33 @@ class Set(Sequence):
                 klass=self.__class__,
                 offset=offset,
             )
                 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 = {}
         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:
         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:
                 try:
-                    value, v_tail = spec.decode(
+                    spec.decode(
                         v,
                         sub_offset,
                         leavemm=True,
                         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
                     )
                 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(
                 break
             else:
                 raise TagMismatch(
@@ -3978,15 +5177,68 @@ class Set(Sequence):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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,
         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
         obj._value = values
+        if not obj.ready:
+            raise DecodeError(
+                "not all values are ready",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
 
         return obj, tail
 
 
@@ -4085,6 +5337,12 @@ class SequenceOf(Obj):
     def ready(self):
         return all(v.ready for v in self._value)
 
     def ready(self):
         return all(v.ready for v in self._value)
 
+    @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 copy(self):
         obj = self.__class__(schema=self.spec)
         obj._bound_min = self._bound_min
     def copy(self):
         obj = self.__class__(schema=self.spec)
         obj._bound_min = self._bound_min
@@ -4096,6 +5354,9 @@ class SequenceOf(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         obj._value = [v.copy() for v in self._value]
         return obj
 
         obj._value = [v.copy() for v in self._value]
         return obj
 
@@ -4170,7 +5431,7 @@ class SequenceOf(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
         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:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4186,8 +5447,22 @@ class SequenceOf(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 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)
         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,
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -4202,31 +5477,73 @@ class SequenceOf(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 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 = []
         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:
         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,
             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)
             _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):
         return obj, tail
 
     def __repr__(self):
@@ -4237,6 +5554,7 @@ class SequenceOf(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
 
     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,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -4252,9 +5570,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_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 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):
 
 
 class SetOf(SequenceOf):
@@ -4272,6 +5596,16 @@ class SetOf(SequenceOf):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
         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
 
 def obj_by_path(pypath):  # pragma: no cover
     """Import object specified as string Python path
@@ -4296,7 +5630,7 @@ def generic_decoder():  # pragma: no cover
     choice = PrimitiveTypes()
     choice.specs["SequenceOf"] = SequenceOf(schema=choice)
     choice.specs["SetOf"] = SetOf(schema=choice)
     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),
         choice.specs["SequenceOf%d" % i] = SequenceOf(
             schema=choice,
             expl=tag_ctxc(i),
@@ -4308,10 +5642,21 @@ def generic_decoder():  # pragma: no cover
         __slots__ = ()
         schema = choice
 
         __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"):
         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()
                     if pp.asn1_type_name == Choice.asn1_type_name:
                         continue
                     pp_kwargs = pp._asdict()
@@ -4319,11 +5664,17 @@ def generic_decoder():  # pragma: no cover
                     pp = _pp(**pp_kwargs)
                     yield pp_console_row(
                         pp,
                     pp = _pp(**pp_kwargs)
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         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):
                         yield row
                 else:
                     for row in _pprint_pps(pp):
@@ -4334,7 +5685,7 @@ def generic_decoder():  # pragma: no cover
 
 def main():  # pragma: no cover
     import argparse
 
 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,
     parser.add_argument(
         "--skip",
         type=int,
@@ -4343,7 +5694,7 @@ def main():  # pragma: no cover
     )
     parser.add_argument(
         "--oids",
     )
     parser.add_argument(
         "--oids",
-        help="Python path to dictionary with OIDs",
+        help="Python paths to dictionary with OIDs, comma separated",
     )
     parser.add_argument(
         "--schema",
     )
     parser.add_argument(
         "--schema",
@@ -4353,6 +5704,25 @@ def main():  # pragma: no cover
         "--defines-by-path",
         help="Python path to decoder's defines_by_path",
     )
         "--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"),
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
@@ -4362,21 +5732,33 @@ def main():  # pragma: no cover
     args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
     args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
-    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()
     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,
-        defines_by_path=(
-            None if args.defines_by_path is None
-            else obj_by_path(args.defines_by_path)
+    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=True if environ.get("NO_COLOR") is None else False,
+        with_decode_path=args.print_decode_path,
+        decode_path_only=(
+            () if args.decode_path_only is None else
+            tuple(args.decode_path_only.split(":"))
         ),
         ),
-    )
-    print(pprinter(obj, oids=oids))
+    ))
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))
 
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))