]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Strict PrintableString sanitizing
[pyderasn.git] / pyderasn.py
index acf5fbe8174b3007e9399783dc3c996d4e505d00..9a8ccddfa319df841e98a94ac4950f9b6f308c3d 100755 (executable)
@@ -1,7 +1,7 @@
 #!/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-2018 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
 # 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/>.
 # 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
+"""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()
@@ -68,7 +68,7 @@ ____
 Most types in ASN.1 has specific tag for them. ``Obj.tag_default`` is
 the default tag used during coding process. You can override it with
 either ``IMPLICIT`` (using ``impl`` keyword argument), or
 Most types in ASN.1 has specific tag for them. ``Obj.tag_default`` is
 the default tag used during coding process. You can override it with
 either ``IMPLICIT`` (using ``impl`` keyword argument), or
-``EXPLICIT`` one (using ``expl`` keyword argument). Both arguments takes
+``EXPLICIT`` one (using ``expl`` keyword argument). Both arguments take
 raw binary string, containing that tag. You can **not** set implicit and
 explicit tags simultaneously.
 
 raw binary string, containing that tag. You can **not** set implicit and
 explicit tags simultaneously.
 
@@ -88,10 +88,10 @@ number. Pay attention that explicit tags always have *constructed* tag
 
 Implicit tag is not explicitly shown.
 
 
 Implicit tag is not explicitly shown.
 
-Two object of the same type, but with different implicit/explicit tags
+Two objects of the same type, but with different implicit/explicit tags
 are **not** equal.
 
 are **not** equal.
 
-You can get objects effective tag (either default or implicited) through
+You can get object's effective tag (either default or implicited) through
 ``tag`` property. You can decode it using :py:func:`pyderasn.tag_decode`
 function::
 
 ``tag`` property. You can decode it using :py:func:`pyderasn.tag_decode`
 function::
 
@@ -135,6 +135,8 @@ example ``TBSCertificate`` sequence holds defaulted, explicitly tagged
 When default argument is used and value is not specified, then it equals
 to default one.
 
 When default argument is used and value is not specified, then it equals
 to default one.
 
+.. _bounds:
+
 Size constraints
 ________________
 
 Size constraints
 ________________
 
@@ -157,15 +159,17 @@ raised.
 Common methods
 ______________
 
 Common methods
 ______________
 
-All objects have ``ready`` boolean property, that tells if it is ready
-to be encoded. If that kind of action is performed on unready object,
-then :py:exc:`pyderasn.ObjNotReady` exception will be raised.
+All objects have ``ready`` boolean property, that tells if object is
+ready to be encoded. If that kind of action is performed on unready
+object, then :py:exc:`pyderasn.ObjNotReady` exception will be raised.
+
+All objects have ``copy()`` method, that returns their copy, that can be
+safely mutated.
 
 
-All objects have ``copy()`` method, returning its copy, that can be safely
-mutated.
+.. _decoding:
 
 Decoding
 
 Decoding
-________
+--------
 
 Decoding is performed using ``decode()`` method. ``offset`` optional
 argument could be used to set initial object's offset in the binary
 
 Decoding is performed using ``decode()`` method. ``offset`` optional
 argument could be used to set initial object's offset in the binary
@@ -177,7 +181,7 @@ by specifying ``leavemm=True`` argument.
 When object is decoded, ``decoded`` property is true and you can safely
 use following properties:
 
 When object is decoded, ``decoded`` property is true and you can safely
 use following properties:
 
-* ``offset`` -- position from initial offset where object's tag is started
+* ``offset`` -- position including initial offset where object's tag starts
 * ``tlen`` -- length of object's tag
 * ``llen`` -- length of object's length value
 * ``vlen`` -- length of object's value
 * ``tlen`` -- length of object's tag
 * ``llen`` -- length of object's length value
 * ``vlen`` -- length of object's value
@@ -185,14 +189,40 @@ 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:
 
 Pretty printing
 
 Pretty printing
-_______________
+---------------
 
 All objects have ``pps()`` method, that is a generator of
 :py:class:`pyderasn.PP` namedtuple, holding various raw information
 
 All objects have ``pps()`` method, that is a generator of
 :py:class:`pyderasn.PP` namedtuple, holding various raw information
@@ -209,6 +239,184 @@ 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
 
+.. _definedby:
+
+DEFINED BY
+----------
+
+ASN.1 structures often have ANY and OCTET STRING fields, that are
+DEFINED BY some previously met ObjectIdentifier. This library provides
+ability to specify mapping between some OID and field that must be
+decoded with specific specification.
+
+defines kwarg
+_____________
+
+:py:class:`pyderasn.ObjectIdentifier` field inside
+:py:class:`pyderasn.Sequence` can hold mapping between OIDs and
+necessary for decoding structures. For example, CMS (:rfc:`5652`)
+container::
+
+    class ContentInfo(Sequence):
+        schema = (
+            ("contentType", ContentType(defines=((("content",), {
+                id_digestedData: DigestedData(),
+                id_signedData: SignedData(),
+            }),))),
+            ("content", Any(expl=tag_ctxc(0))),
+        )
+
+``contentType`` field tells that it defines that ``content`` must be
+decoded with ``SignedData`` specification, if ``contentType`` equals to
+``id-signedData``. The same applies to ``DigestedData``. If
+``contentType`` contains unknown OID, then no automatic decoding is
+done.
+
+You can specify multiple fields, that will be autodecoded -- that is why
+``defines`` kwarg is a sequence. You can specify defined field
+relatively or absolutely to current decode path. For example ``defines``
+for AlgorithmIdentifier of X.509's
+``tbsCertificate:subjectPublicKeyInfo:algorithm:algorithm``::
+
+        (
+            (("parameters",), {
+                id_ecPublicKey: ECParameters(),
+                id_GostR3410_2001: GostR34102001PublicKeyParameters(),
+            }),
+            (("..", "subjectPublicKey"), {
+                id_rsaEncryption: RSAPublicKey(),
+                id_GostR3410_2001: OctetString(),
+            }),
+        ),
+
+tells that if certificate's SPKI algorithm is GOST R 34.10-2001, then
+autodecode its parameters inside SPKI's algorithm and its public key
+itself.
+
+Following types can be automatically decoded (DEFINED BY):
+
+* :py:class:`pyderasn.Any`
+* :py:class:`pyderasn.BitString` (that is multiple of 8 bits)
+* :py:class:`pyderasn.OctetString`
+* :py:class:`pyderasn.SequenceOf`/:py:class:`pyderasn.SetOf`
+  ``Any``/``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
+above, ``content_info["content"].defined == (id_signedData, signed_data)``.
+
+.. _defines_by_path_ctx:
+
+defines_by_path context option
+______________________________
+
+Sometimes you either can not or do not want to explicitly set *defines*
+in the scheme. You can dynamically apply those definitions when calling
+``.decode()`` method.
+
+Specify ``defines_by_path`` key in the :ref:`decode context <ctx>`. Its
+value must be sequence of following tuples::
+
+    (decode_path, defines)
+
+where ``decode_path`` is a tuple holding so-called decode path to the
+exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
+``defines``, holding exactly the same value as accepted in its keyword
+argument.
+
+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=(
+        (
+            ("contentType",),
+            ((("content",), {id_signedData: SignedData()}),),
+        ),
+        (
+            (
+                "content",
+                DecodePathDefBy(id_signedData),
+                "encapContentInfo",
+                "eContentType",
+            ),
+            ((("eContent",), {
+                id_cct_PKIData: PKIData(),
+                id_cct_PKIResponse: PKIResponse(),
+            })),
+        ),
+        (
+            (
+                "content",
+                DecodePathDefBy(id_signedData),
+                "encapContentInfo",
+                "eContent",
+                DecodePathDefBy(id_cct_PKIResponse),
+                "controlSequence",
+                any,
+                "attrType",
+            ),
+            ((("attrValues",), {
+                id_cmc_recipientNonce: RecipientNonce(),
+                id_cmc_senderNonce: SenderNonce(),
+                id_cmc_statusInfoV2: CMCStatusInfoV2(),
+                id_cmc_transactionId: TransactionId(),
+            })),
+        ),
+    ))
+
+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.
+
+.. _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``, ``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.
+
 Primitive types
 ---------------
 
 Primitive types
 ---------------
 
@@ -250,6 +458,10 @@ CommonString
 ____________
 .. autoclass:: pyderasn.CommonString
 
 ____________
 .. autoclass:: pyderasn.CommonString
 
+NumericString
+_____________
+.. autoclass:: pyderasn.NumericString
+
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
@@ -302,6 +514,7 @@ _____
 Various
 -------
 
 Various
 -------
 
+.. autofunction:: pyderasn.abs_decode_path
 .. autofunction:: pyderasn.hexenc
 .. autofunction:: pyderasn.hexdec
 .. autofunction:: pyderasn.tag_encode
 .. autofunction:: pyderasn.hexenc
 .. autofunction:: pyderasn.hexdec
 .. autofunction:: pyderasn.tag_encode
@@ -309,6 +522,17 @@ Various
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
 .. autoclass:: pyderasn.Obj
 .. 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 getdecoder
@@ -317,6 +541,9 @@ from collections import namedtuple
 from collections import OrderedDict
 from datetime import datetime
 from math import ceil
 from collections import OrderedDict
 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
@@ -331,6 +558,13 @@ from six import text_type
 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):
+        return what
+
+
 __all__ = (
     "Any",
     "BitString",
 __all__ = (
     "Any",
     "BitString",
@@ -339,6 +573,7 @@ __all__ = (
     "BoundsError",
     "Choice",
     "DecodeError",
     "BoundsError",
     "Choice",
     "DecodeError",
+    "DecodePathDefBy",
     "Enumerated",
     "GeneralizedTime",
     "GeneralString",
     "Enumerated",
     "GeneralizedTime",
     "GeneralString",
@@ -351,6 +586,7 @@ __all__ = (
     "InvalidOID",
     "InvalidValueType",
     "ISO646String",
     "InvalidOID",
     "InvalidValueType",
     "ISO646String",
+    "LenIndefForm",
     "NotEnoughData",
     "Null",
     "NumericString",
     "NotEnoughData",
     "Null",
     "NumericString",
@@ -396,6 +632,10 @@ 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 "∞"
 
 
 ########################################################################
 
 
 ########################################################################
@@ -425,7 +665,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 "",
@@ -441,6 +681,10 @@ class NotEnoughData(DecodeError):
     pass
 
 
     pass
 
 
+class LenIndefForm(DecodeError):
+    pass
+
+
 class TagMismatch(DecodeError):
     pass
 
 class TagMismatch(DecodeError):
     pass
 
@@ -632,6 +876,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)
@@ -641,7 +890,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
@@ -657,9 +906,9 @@ def len_decode(data):
 ########################################################################
 
 class AutoAddSlots(type):
 ########################################################################
 
 class AutoAddSlots(type):
-    def __new__(cls, name, bases, _dict):
+    def __new__(mcs, name, bases, _dict):
         _dict["__slots__"] = _dict.get("__slots__", ())
         _dict["__slots__"] = _dict.get("__slots__", ())
-        return type.__new__(cls, name, bases, _dict)
+        return type.__new__(mcs, name, bases, _dict)
 
 
 @add_metaclass(AutoAddSlots)
 
 
 @add_metaclass(AutoAddSlots)
@@ -678,6 +927,9 @@ class Obj(object):
         "offset",
         "llen",
         "vlen",
         "offset",
         "llen",
         "vlen",
+        "expl_lenindef",
+        "lenindef",
+        "ber_encoded",
     )
 
     def __init__(
     )
 
     def __init__(
@@ -688,20 +940,18 @@ class Obj(object):
             optional=False,
             _decoded=(0, 0, 0),
     ):
             optional=False,
             _decoded=(0, 0, 0),
     ):
-        if impl is None:
-            self.tag = getattr(self, "impl", self.tag_default)
-        else:
-            self.tag = impl
+        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._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
@@ -713,6 +963,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?
@@ -750,7 +1006,7 @@ 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=()):  # 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):
@@ -759,22 +1015,41 @@ class Obj(object):
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
-    def decode(self, data, offset=0, leavemm=False, decode_path=()):
+    def decode(
+            self,
+            data,
+            offset=0,
+            leavemm=False,
+            decode_path=(),
+            ctx=None,
+            tag_only=False,
+    ):
         """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 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)
         :returns: (Obj, remaining data)
         """
         :returns: (Obj, remaining data)
         """
+        if ctx is None:
+            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,
+                ctx=ctx,
+                tag_only=tag_only,
             )
             )
+            if tag_only:
+                return
+            obj, tail = result
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
@@ -793,6 +1068,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
+                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,
@@ -800,18 +1105,31 @@ 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=(),
-            )
+                if tag_only:  # pragma: no cover
+                    return
+                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
         return obj, (tail if leavemm else tail.tobytes())
 
     @property
@@ -828,6 +1146,8 @@ class Obj(object):
 
     @property
     def expl_llen(self):
 
     @property
     def expl_llen(self):
+        if self.expl_lenindef:
+            return 1
         return len(len_encode(self.tlvlen))
 
     @property
         return len(len_encode(self.tlvlen))
 
     @property
@@ -842,6 +1162,69 @@ class Obj(object):
     def expl_tlvlen(self):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     def expl_tlvlen(self):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
+    @property
+    def fulloffset(self):
+        return self.expl_offset if self.expled else self.offset
+
+    @property
+    def fulllen(self):
+        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,
+            )
+
+
+class DecodePathDefBy(object):
+    """DEFINED BY representation inside decode path
+    """
+    __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)
+
 
 ########################################################################
 # Pretty printing
 
 ########################################################################
 # Pretty printing
@@ -865,6 +1248,10 @@ PP = namedtuple("PP", (
     "expl_tlen",
     "expl_llen",
     "expl_vlen",
     "expl_tlen",
     "expl_llen",
     "expl_vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+    "bered",
 ))
 
 
 ))
 
 
@@ -886,6 +1273,10 @@ 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(
         asn1_type_name,
 ):
     return PP(
         asn1_type_name,
@@ -905,59 +1296,110 @@ 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 pp_console_row(
+        pp,
+        oids=None,
+        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)
+            if (
+                    oids is not None and
+                    ent.defined_by.asn1_type_name ==
+                    ObjectIdentifier.asn1_type_name and
+                    value in oids
+            ):
+                cols.append(_colourize("%s:" % oids[value], "green", with_colours))
+            else:
+                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 (
                 oids is not None and
                 pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
                 value in oids
         ):
         if (
                 oids is not None and
                 pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
                 value in oids
         ):
-            value = "%s (%s)" % (oids[value], pp.value)
-        cols.append(value)
+            cols.append(_colourize("(%s)" % oids[value], "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()
         for i in range(0, len(blob), 32):
     if isinstance(pp.blob, binary_type):
         blob = hexenc(pp.blob).upper()
         for i in range(0, len(blob), 32):
@@ -969,7 +1411,14 @@ def pp_console_blob(pp):
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
-def pprint(obj, oids=None, big_blobs=False):
+def pprint(
+        obj,
+        oids=None,
+        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
@@ -978,21 +1427,46 @@ def pprint(obj, oids=None, big_blobs=False):
     :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,
                         oids=oids,
                         with_offsets=True,
                         with_blob=False,
                 if big_blobs:
                     yield pp_console_row(
                         pp,
                         oids=oids,
                         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,
+                        oids=oids,
+                        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
@@ -1112,7 +1586,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=()):
+    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:
@@ -1128,6 +1602,8 @@ class Boolean(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -1152,10 +1628,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",
@@ -1171,6 +1651,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):
@@ -1194,7 +1675,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):
@@ -1261,14 +1747,11 @@ class Integer(Obj):
         self._value = value
         specs = getattr(self, "schema", {}) if _specs is None else _specs
         self.specs = specs if isinstance(specs, dict) else dict(specs)
         self._value = value
         specs = getattr(self, "schema", {}) if _specs is None else _specs
         self.specs = specs if isinstance(specs, dict) else dict(specs)
-        if bounds is None:
-            self._bound_min, self._bound_max = getattr(
-                self,
-                "bounds",
-                (float("-inf"), float("+inf")),
-            )
-        else:
-            self._bound_min, self._bound_max = bounds
+        self._bound_min, self._bound_max = getattr(
+            self,
+            "bounds",
+            (float("-inf"), float("+inf")),
+        ) if bounds is None else bounds
         if value is not None:
             self._value = self._value_sanitize(value)
         if default is not None:
         if value is not None:
             self._value = self._value_sanitize(value)
         if default is not None:
@@ -1408,7 +1891,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=()):
+    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:
@@ -1424,6 +1907,8 @@ class Integer(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -1517,7 +2002,11 @@ 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
 
 
 class BitString(Obj):
 
 
 class BitString(Obj):
@@ -1532,6 +2021,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
@@ -1547,19 +2038,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",)
+    __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"
 
@@ -1596,6 +2095,13 @@ class BitString(Obj):
             )
             if value is None:
                 self._value = default
             )
             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:
@@ -1613,21 +2119,23 @@ class BitString(Obj):
         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 set(value) <= set(("0", "1")):
+                        raise ValueError("B's coding contains unacceptable chars")
+                    return self._bits2octets(value)
+                elif 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)
             else:
                 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
@@ -1656,7 +2164,10 @@ class BitString(Obj):
 
     def copy(self):
         obj = self.__class__(_specs=self.specs)
 
     def copy(self):
         obj = self.__class__(_specs=self.specs)
-        obj._value = self._value
+        value = self._value
+        if value is not None:
+            value = (value[0], value[1])
+        obj._value = value
         obj.tag = self.tag
         obj._expl = self._expl
         obj.default = self.default
         obj.tag = self.tag
         obj._expl = self._expl
         obj.default = self.default
@@ -1738,22 +2249,7 @@ class BitString(Obj):
             octets,
         ))
 
             octets,
         ))
 
-    def _decode(self, tlv, offset=0, decode_path=()):
-        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:
@@ -1792,7 +2288,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__,
@@ -1811,6 +2307,134 @@ 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
+            return self._decode_chunk(lv, offset, decode_path, ctx)
+        if t == self.tag_constructed:
+            if not ctx.get("bered", False):
+                raise DecodeError(
+                    "unallowed BER constructed encoding",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if tag_only:  # pragma: no cover
+                return
+            lenindef = False
+            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,
+                    )
+                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()))
 
@@ -1840,7 +2464,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(
+                decode_path=decode_path + (DecodePathDefBy(defined_by),)
+            )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class OctetString(Obj):
 
 
 class OctetString(Obj):
@@ -1858,8 +2493,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")
+    __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"
 
@@ -1891,14 +2534,11 @@ class OctetString(Obj):
             _decoded,
         )
         self._value = value
             _decoded,
         )
         self._value = value
-        if bounds is None:
-            self._bound_min, self._bound_max = getattr(
-                self,
-                "bounds",
-                (0, float("+inf")),
-            )
-        else:
-            self._bound_min, self._bound_max = bounds
+        self._bound_min, self._bound_max = getattr(
+            self,
+            "bounds",
+            (0, float("+inf")),
+        ) if bounds is None else bounds
         if value is not None:
             self._value = self._value_sanitize(value)
         if default is not None:
         if value is not None:
             self._value = self._value_sanitize(value)
         if default is not None:
@@ -1910,6 +2550,13 @@ class OctetString(Obj):
             )
             if self._value is None:
                 self._value = default
             )
             if self._value is None:
                 self._value = default
+        self.defined = None
+        tag_klass, _, tag_num = tag_decode(self.tag)
+        self.tag_constructed = tag_encode(
+            klass=tag_klass,
+            form=TagFormConstructed,
+            num=tag_num,
+        )
 
     def _value_sanitize(self, value):
         if issubclass(value.__class__, OctetString):
 
     def _value_sanitize(self, value):
         if issubclass(value.__class__, OctetString):
@@ -1987,22 +2634,7 @@ class OctetString(Obj):
             self._value,
         ))
 
             self._value,
         ))
 
-    def _decode(self, tlv, offset=0, decode_path=()):
-        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:
@@ -2030,6 +2662,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),
@@ -2039,6 +2678,120 @@ 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
+            return self._decode_chunk(lv, offset, decode_path, ctx)
+        if t == self.tag_constructed:
+            if not ctx.get("bered", False):
+                raise DecodeError(
+                    "unallowed BER constructed encoding",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if tag_only:
+                return
+            lenindef = False
+            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,
+                    )
+                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 __repr__(self):
         return pp_console_row(next(self.pps()))
 
@@ -2061,7 +2814,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(
+                decode_path=decode_path + (DecodePathDefBy(defined_by),)
+            )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class Null(Obj):
 
 
 class Null(Obj):
@@ -2131,7 +2895,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=()):
+    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:
@@ -2147,6 +2911,8 @@ class Null(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:  # pragma: no cover
+            return
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -2190,7 +2956,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):
@@ -2211,13 +2981,14 @@ class ObjectIdentifier(Obj):
     Traceback (most recent call last):
     pyderasn.InvalidOID: unacceptable first arc value
     """
     Traceback (most recent call last):
     pyderasn.InvalidOID: unacceptable first arc value
     """
-    __slots__ = ()
+    __slots__ = ("defines",)
     tag_default = tag_encode(6)
     asn1_type_name = "OBJECT IDENTIFIER"
 
     def __init__(
             self,
             value=None,
     tag_default = tag_encode(6)
     asn1_type_name = "OBJECT IDENTIFIER"
 
     def __init__(
             self,
             value=None,
+            defines=(),
             impl=None,
             expl=None,
             default=None,
             impl=None,
             expl=None,
             default=None,
@@ -2228,6 +2999,15 @@ 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: sequence of tuples. Each tuple has two elements.
+                        First one is relative to current one decode
+                        path, aiming to the field defined by that OID.
+                        Read about relative path in
+                        :py:func:`pyderasn.abs_decode_path`. Second
+                        tuple element is ``{OID: pyderasn.Obj()}``
+                        dictionary, mapping between current OID value
+                        and structure applied to defined field.
+                        :ref:`Read about DEFINED BY <definedby>`
         :param bytes impl: override default tag with ``IMPLICIT`` one
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param default: set default value. Type same as in ``value``
         :param bytes impl: override default tag with ``IMPLICIT`` one
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param default: set default value. Type same as in ``value``
@@ -2252,6 +3032,7 @@ class ObjectIdentifier(Obj):
             )
             if self._value is None:
                 self._value = default
             )
             if self._value is None:
                 self._value = default
+        self.defines = defines
 
     def __add__(self, their):
         if isinstance(their, self.__class__):
 
     def __add__(self, their):
         if isinstance(their, self.__class__):
@@ -2289,6 +3070,7 @@ class ObjectIdentifier(Obj):
     def copy(self):
         obj = self.__class__()
         obj._value = self._value
     def copy(self):
         obj = self.__class__()
         obj._value = self._value
+        obj.defines = self.defines
         obj.tag = self.tag
         obj._expl = self._expl
         obj.default = self.default
         obj.tag = self.tag
         obj._expl = self._expl
         obj.default = self.default
@@ -2330,6 +3112,7 @@ class ObjectIdentifier(Obj):
     def __call__(
             self,
             value=None,
     def __call__(
             self,
             value=None,
+            defines=None,
             impl=None,
             expl=None,
             default=None,
             impl=None,
             expl=None,
             default=None,
@@ -2337,6 +3120,7 @@ class ObjectIdentifier(Obj):
     ):
         return self.__class__(
             value=value,
     ):
         return self.__class__(
             value=value,
+            defines=self.defines if defines is None else defines,
             impl=self.tag if impl is None else impl,
             expl=self._expl if expl is None else expl,
             default=self.default if default is None else default,
             impl=self.tag if impl is None else impl,
             expl=self._expl if expl is None else expl,
             default=self.default if default is None else default,
@@ -2362,7 +3146,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=()):
+    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:
@@ -2378,6 +3162,8 @@ class ObjectIdentifier(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:  # pragma: no cover
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2462,7 +3248,11 @@ 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,
+            bered=self.bered,
         )
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class Enumerated(Integer):
 
 
 class Enumerated(Integer):
@@ -2567,7 +3357,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):
@@ -2623,14 +3413,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,
@@ -2677,7 +3470,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):
@@ -2688,10 +3490,21 @@ class UTF8String(CommonString):
 
 
 class NumericString(CommonString):
 
 
 class NumericString(CommonString):
+    """Numeric string
+
+    Its value is properly sanitized: only ASCII digits can be stored.
+    """
     __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 = set(digits.encode("ascii") + b" ")
+
+    def _value_sanitize(self, value):
+        value = super(NumericString, self)._value_sanitize(value)
+        if not set(value) <= self.allowable_chars:
+            raise DecodeError("non-numeric value")
+        return value
 
 
 class PrintableString(CommonString):
 
 
 class PrintableString(CommonString):
@@ -2699,6 +3512,13 @@ class PrintableString(CommonString):
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
+    allowable_chars = set((ascii_letters + digits + " '()+,-./:=?").encode("ascii"))
+
+    def _value_sanitize(self, value):
+        value = super(PrintableString, self)._value_sanitize(value)
+        if not set(value) <= self.allowable_chars:
+            raise DecodeError("non-printable value")
+        return value
 
 
 class TeletexString(CommonString):
 
 
 class TeletexString(CommonString):
@@ -2727,6 +3547,11 @@ class IA5String(CommonString):
     asn1_type_name = "IA5"
 
 
     asn1_type_name = "IA5"
 
 
+LEN_YYMMDDHHMMSSZ = len("YYMMDDHHMMSSZ")
+LEN_YYYYMMDDHHMMSSDMZ = len("YYYYMMDDHHMMSSDMZ")
+LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
+
+
 class UTCTime(CommonString):
     """``UTCTime`` datetime type
 
 class UTCTime(CommonString):
     """``UTCTime`` datetime type
 
@@ -2792,11 +3617,14 @@ class UTCTime(CommonString):
         if isinstance(value, datetime):
             return value.strftime(self.fmt).encode("ascii")
         if isinstance(value, binary_type):
         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) == 2 + 2 + 2 + 2 + 2 + 2 + 1:
+            try:
+                value_decoded = value.decode("ascii")
+            except (UnicodeEncodeError, UnicodeDecodeError) as err:
+                raise DecodeError("invalid UTCTime encoding")
+            if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
                 try:
                     datetime.strptime(value_decoded, self.fmt)
-                except ValueError:
+                except (TypeError, ValueError):
                     raise DecodeError("invalid UTCTime format")
                 return value
             else:
                     raise DecodeError("invalid UTCTime format")
                 return value
             else:
@@ -2853,7 +3681,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):
@@ -2883,19 +3720,22 @@ class GeneralizedTime(UTCTime):
                 self.fmt_ms if value.microsecond > 0 else self.fmt
             ).encode("ascii")
         if isinstance(value, binary_type):
                 self.fmt_ms if value.microsecond > 0 else self.fmt
             ).encode("ascii")
         if isinstance(value, binary_type):
-            value_decoded = value.decode("ascii")
-            if len(value_decoded) == 4 + 2 + 2 + 2 + 2 + 2 + 1:
+            try:
+                value_decoded = value.decode("ascii")
+            except (UnicodeEncodeError, UnicodeDecodeError) as err:
+                raise DecodeError("invalid GeneralizedTime encoding")
+            if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
                 try:
                     datetime.strptime(value_decoded, self.fmt)
-                except ValueError:
+                except (TypeError, ValueError):
                     raise DecodeError(
                         "invalid GeneralizedTime (without ms) format",
                     )
                 return value
                     raise DecodeError(
                         "invalid GeneralizedTime (without ms) format",
                     )
                 return value
-            elif len(value_decoded) >= 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 1:
+            elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt_ms)
                 try:
                     datetime.strptime(value_decoded, self.fmt_ms)
-                except ValueError:
+                except (TypeError, ValueError):
                     raise DecodeError(
                         "invalid GeneralizedTime (with ms) format",
                     )
                     raise DecodeError(
                         "invalid GeneralizedTime (with ms) format",
                     )
@@ -2909,7 +3749,7 @@ class GeneralizedTime(UTCTime):
 
     def todatetime(self):
         value = self._value.decode("ascii")
 
     def todatetime(self):
         value = self._value.decode("ascii")
-        if len(value) == 4 + 2 + 2 + 2 + 2 + 2 + 1:
+        if len(value) == LEN_YYYYMMDDHHMMSSZ:
             return datetime.strptime(value, self.fmt)
         return datetime.strptime(value, self.fmt_ms)
 
             return datetime.strptime(value, self.fmt)
         return datetime.strptime(value, self.fmt_ms)
 
@@ -2961,8 +3801,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()
@@ -3046,6 +3886,13 @@ class Choice(Obj):
     def ready(self):
         return self._value is not None and self._value[1].ready
 
     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
@@ -3124,31 +3971,45 @@ 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=()):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         for choice, spec in self.specs.items():
         for choice, spec in self.specs.items():
+            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,),
+                    decode_path=sub_decode_path,
+                    ctx=ctx,
+                    tag_only=True,
                 )
             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
+        value, tail = spec.decode(
+            tlv,
             offset=offset,
             offset=offset,
+            leavemm=True,
+            decode_path=sub_decode_path,
+            ctx=ctx,
+        )
+        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()))
@@ -3170,9 +4031,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):
@@ -3220,7 +4085,7 @@ class Any(Obj):
     >>> hexenc(bytes(a))
     b'0x040x0bhello world'
     """
     >>> hexenc(bytes(a))
     b'0x040x0bhello world'
     """
-    __slots__ = ()
+    __slots__ = ("defined",)
     tag_default = tag_encode(0)
     asn1_type_name = "ANY"
 
     tag_default = tag_encode(0)
     asn1_type_name = "ANY"
 
@@ -3241,6 +4106,7 @@ class Any(Obj):
         """
         super(Any, self).__init__(None, expl, None, optional, _decoded)
         self._value = None if value is None else self._value_sanitize(value)
         """
         super(Any, self).__init__(None, expl, None, optional, _decoded)
         self._value = None if value is None else self._value_sanitize(value)
+        self.defined = None
 
     def _value_sanitize(self, value):
         if isinstance(value, self.__class__):
 
     def _value_sanitize(self, value):
         if isinstance(value, self.__class__):
@@ -3255,6 +4121,14 @@ class Any(Obj):
     def ready(self):
         return self._value is not None
 
     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
@@ -3297,10 +4171,50 @@ class Any(Obj):
         self._assert_ready()
         return self._value
 
         self._assert_ready()
         return self._value
 
-    def _decode(self, tlv, offset=0, decode_path=()):
+    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,
+                )
+                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,
@@ -3347,20 +4261,68 @@ 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(
+                decode_path=decode_path + (DecodePathDefBy(defined_by),)
+            )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 ########################################################################
 # ASN.1 constructed types
 ########################################################################
 
 
 
 ########################################################################
 # ASN.1 constructed types
 ########################################################################
 
+def get_def_by_path(defines_by_path, sub_decode_path):
+    """Get define by decode path
+    """
+    for path, define in defines_by_path:
+        if len(path) != len(sub_decode_path):
+            continue
+        for p1, p2 in zip(path, sub_decode_path):
+            if (p1 != any) and (p1 != p2):
+                break
+        else:
+            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
 
     You have to make specification of sequence::
 
         class Extension(Sequence):
 class Sequence(Obj):
     """``SEQUENCE`` structure type
 
     You have to make specification of sequence::
 
         class Extension(Sequence):
-            __slots__ = ()
             schema = (
                 ("extnID", ObjectIdentifier()),
                 ("critical", Boolean(default=False)),
             schema = (
                 ("extnID", ObjectIdentifier()),
                 ("critical", Boolean(default=False)),
@@ -3381,7 +4343,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
@@ -3405,7 +4367,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)
@@ -3424,13 +4396,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.
@@ -3457,9 +4430,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
@@ -3467,11 +4448,6 @@ 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):
         for name, spec in self.specs.items():
     @property
     def ready(self):
         for name, spec in self.specs.items():
@@ -3485,6 +4461,12 @@ class Sequence(Obj):
                     return False
         return True
 
                     return False
         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 self._value.values())
+
     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
@@ -3568,7 +4550,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=()):
+    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:
@@ -3584,8 +4566,22 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:  # pragma: no cover
+            return
+        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,
@@ -3600,31 +4596,120 @@ 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 = {}
+        ber_encoded = False
+        ctx_allow_default_values = ctx.get("allow_default_values", False)
         for name, spec in self.specs.items():
         for name, spec in self.specs.items():
-            if len(v) == 0 and spec.optional:
+            if spec.optional and (
+                    (lenindef and v[:EOC_LEN].tobytes() == EOC) or
+                    len(v) == 0
+            ):
                 continue
                 continue
+            sub_decode_path = decode_path + (name,)
             try:
                 value, v_tail = spec.decode(
                     v,
                     sub_offset,
                     leavemm=True,
             try:
                 value, v_tail = spec.decode(
                     v,
                     sub_offset,
                     leavemm=True,
-                    decode_path=decode_path + (name,),
+                    decode_path=sub_decode_path,
+                    ctx=ctx,
                 )
             except TagMismatch:
                 if spec.optional:
                     continue
                 raise
                 )
             except TagMismatch:
                 if spec.optional:
                     continue
                 raise
-            sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
+
+            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),
+                            DecodePathDefBy(defined_by),
+                        )
+                        defined_value, defined_tail = defined_spec.decode(
+                            memoryview(bytes(_value)),
+                            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,
+                            ctx=ctx,
+                        )
+                        if len(defined_tail) > 0:
+                            raise DecodeError(
+                                "remaining data",
+                                klass=self.__class__,
+                                decode_path=sub_sub_decode_path,
+                                offset=offset,
+                            )
+                        _value.defined = (defined_by, defined_value)
+                else:
+                    defined_value, defined_tail = defined_spec.decode(
+                        memoryview(bytes(value)),
+                        sub_offset + (
+                            (value.tlen + value.llen + value.expl_tlen + value.expl_llen)
+                            if value.expled else (value.tlen + value.llen)
+                        ),
+                        leavemm=True,
+                        decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
+                        ctx=ctx,
+                    )
+                    if len(defined_tail) > 0:
+                        raise DecodeError(
+                            "remaining data",
+                            klass=self.__class__,
+                            decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
+                            offset=offset,
+                        )
+                    value.defined = (defined_by, defined_value)
+
+            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 still allow that
-                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
-        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__,
@@ -3637,9 +4722,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):
@@ -3649,8 +4736,8 @@ 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(
@@ -3669,18 +4756,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)
@@ -3692,7 +4793,7 @@ 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=()):
+    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:
@@ -3708,8 +4809,22 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
+        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,
@@ -3723,28 +4838,32 @@ 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 = {}
+        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])
         specs_items = self.specs.items
         while len(v) > 0:
         specs_items = self.specs.items
         while len(v) > 0:
+            if lenindef and v[:EOC_LEN].tobytes() == EOC:
+                break
             for name, spec in specs_items():
             for name, spec in 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,),
+                        decode_path=sub_decode_path,
+                        ctx=ctx,
+                        tag_only=True,
                     )
                 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(
@@ -3752,15 +4871,67 @@ 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,
+            )
+            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
 
 
@@ -3821,14 +4992,11 @@ class SequenceOf(Obj):
         if schema is None:
             raise ValueError("schema must be specified")
         self.spec = schema
         if schema is None:
             raise ValueError("schema must be specified")
         self.spec = schema
-        if bounds is None:
-            self._bound_min, self._bound_max = getattr(
-                self,
-                "bounds",
-                (0, float("+inf")),
-            )
-        else:
-            self._bound_min, self._bound_max = bounds
+        self._bound_min, self._bound_max = getattr(
+            self,
+            "bounds",
+            (0, float("+inf")),
+        ) if bounds is None else bounds
         self._value = []
         if value is not None:
             self._value = self._value_sanitize(value)
         self._value = []
         if value is not None:
             self._value = self._value_sanitize(value)
@@ -3862,6 +5030,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
@@ -3947,7 +5121,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=()):
+    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:
@@ -3963,8 +5137,22 @@ class SequenceOf(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
+        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,
@@ -3979,30 +5167,72 @@ 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)),),
+                decode_path=sub_decode_path,
+                ctx=ctx,
             )
             )
-            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):
@@ -4028,9 +5258,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):
@@ -4048,6 +5284,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
@@ -4067,9 +5313,73 @@ def obj_by_path(pypath):  # pragma: no cover
     return obj
 
 
     return obj
 
 
+def generic_decoder():  # pragma: no cover
+    # All of this below is a big hack with self references
+    choice = PrimitiveTypes()
+    choice.specs["SequenceOf"] = SequenceOf(schema=choice)
+    choice.specs["SetOf"] = SetOf(schema=choice)
+    for i in range(31):
+        choice.specs["SequenceOf%d" % i] = SequenceOf(
+            schema=choice,
+            expl=tag_ctxc(i),
+        )
+    choice.specs["Any"] = Any()
+
+    # Class name equals to type name, to omit it from output
+    class SEQUENCEOF(SequenceOf):
+        __slots__ = ()
+        schema = choice
+
+    def pprint_any(
+            obj,
+            oids=None,
+            with_colours=False,
+            with_decode_path=False,
+            decode_path_only=(),
+    ):
+        def _pprint_pps(pps):
+            for pp in pps:
+                if hasattr(pp, "_fields"):
+                    if (
+                        decode_path_only != () and
+                        pp.decode_path[:len(decode_path_only)] != decode_path_only
+                    ):
+                        continue
+                    if pp.asn1_type_name == Choice.asn1_type_name:
+                        continue
+                    pp_kwargs = pp._asdict()
+                    pp_kwargs["decode_path"] = pp.decode_path[:-1] + (">",)
+                    pp = _pp(**pp_kwargs)
+                    yield pp_console_row(
+                        pp,
+                        oids=oids,
+                        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,
+                        decode_path_len_decrease=len(decode_path_only),
+                    ):
+                        yield row
+                else:
+                    for row in _pprint_pps(pp):
+                        yield row
+        return "\n".join(_pprint_pps(obj.pps()))
+    return SEQUENCEOF(), pprint_any
+
+
 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,
+        default=0,
+        help="Skip that number of bytes from the beginning",
+    )
     parser.add_argument(
         "--oids",
         help="Python path to dictionary with OIDs",
     parser.add_argument(
         "--oids",
         help="Python path to dictionary with OIDs",
@@ -4078,12 +5388,36 @@ def main():  # pragma: no cover
         "--schema",
         help="Python path to schema definition to use",
     )
         "--schema",
         help="Python path to schema definition to use",
     )
+    parser.add_argument(
+        "--defines-by-path",
+        help="Python path to decoder's defines_by_path",
+    )
+    parser.add_argument(
+        "--nobered",
+        action="store_true",
+        help="Disallow BER encoding",
+    )
+    parser.add_argument(
+        "--print-decode-path",
+        action="store_true",
+        help="Print decode paths",
+    )
+    parser.add_argument(
+        "--decode-path-only",
+        help="Print only specified decode path",
+    )
+    parser.add_argument(
+        "--allow-expl-oob",
+        action="store_true",
+        help="Allow explicit tag out-of-bound",
+    )
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
         help="Path to DER file you want to decode",
     )
     args = parser.parse_args()
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
         help="Path to DER file you want to decode",
     )
     args = parser.parse_args()
+    args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
     oids = obj_by_path(args.oids) if args.oids else {}
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
     oids = obj_by_path(args.oids) if args.oids else {}
@@ -4092,47 +5426,24 @@ def main():  # pragma: no cover
         from functools import partial
         pprinter = partial(pprint, big_blobs=True)
     else:
         from functools import partial
         pprinter = partial(pprint, big_blobs=True)
     else:
-        # All of this below is a big hack with self references
-        choice = PrimitiveTypes()
-        choice.specs["SequenceOf"] = SequenceOf(schema=choice)
-        choice.specs["SetOf"] = SetOf(schema=choice)
-        for i in range(31):
-            choice.specs["SequenceOf%d" % i] = SequenceOf(
-                schema=choice,
-                expl=tag_ctxc(i),
-            )
-        choice.specs["Any"] = Any()
-
-        # Class name equals to type name, to omit it from output
-        class SEQUENCEOF(SequenceOf):
-            __slots__ = ()
-            schema = choice
-        schema = SEQUENCEOF()
-
-        def pprint_any(obj, oids=None):
-            def _pprint_pps(pps):
-                for pp in pps:
-                    if hasattr(pp, "_fields"):
-                        if pp.asn1_type_name == Choice.asn1_type_name:
-                            continue
-                        pp_kwargs = pp._asdict()
-                        pp_kwargs["decode_path"] = pp.decode_path[:-1] + (">",)
-                        pp = _pp(**pp_kwargs)
-                        yield pp_console_row(
-                            pp,
-                            oids=oids,
-                            with_offsets=True,
-                            with_blob=False,
-                        )
-                        for row in pp_console_blob(pp):
-                            yield row
-                    else:
-                        for row in _pprint_pps(pp):
-                            yield row
-            return "\n".join(_pprint_pps(obj.pps()))
-        pprinter = pprint_any
-    obj, tail = schema().decode(der)
-    print(pprinter(obj, oids=oids))
+        schema, pprinter = generic_decoder()
+    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,
+        oids=oids,
+        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(":"))
+        ),
+    ))
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))
 
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))