]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Ability to seek in command line utility
[pyderasn.git] / pyderasn.py
index b1f029cdd89e00ec6d99f5400e9bb79ba3f7a2f3..3afaf60fde261bd6a985d06acd63ec4ca0e55fbd 100755 (executable)
@@ -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
 ________________
 
@@ -164,8 +166,10 @@ then :py:exc:`pyderasn.ObjNotReady` exception will be raised.
 All objects have ``copy()`` method, returning its 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
@@ -191,8 +195,10 @@ lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen``
 
 When error occurs, then :py:exc:`pyderasn.DecodeError` is raised.
 
 
 When error occurs, then :py:exc:`pyderasn.DecodeError` is raised.
 
+.. _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 +215,118 @@ 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 structrures. 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.
+
+Following types can be automatically decoded (DEFINED BY):
+
+* :py:class:`pyderasn.Any`
+* :py:class:`pyderasn.OctetString`
+* :py:class:`pyderasn.SequenceOf`/:py:class:`pyderasn.SetOf`
+  ``Any``/``OctetString``-s
+
+When any of those fields is automatically decoded, then ``.defined``
+attribute contains ``(OID, value)`` tuple. OID tell by which OID it was
+defined, ``value`` contains corresponding decoded value. For example
+above, ``content_info["content"].defined == (id_signedData,
+signed_data)``.
+
+.. _defines_by_path_kwarg:
+
+defines_by_path kwarg
+_____________________
+
+Sometimes you either can not or do not want to explicitly set *defines*
+in the scheme. You can dynamically apply those definitions when calling
+``.decode()`` method.
+
+Decode method takes optional ``defines_by_path`` keyword argument that
+must be sequence of following tuples::
+
+    (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",
+                decode_path_defby(id_signedData),
+                "encapContentInfo",
+                "eContentType",
+            ),
+            ("eContent", {
+                id_cct_PKIData: PKIData(),
+                id_cct_PKIResponse: PKIResponse(),
+            }),
+        ),
+        (
+            (
+                "content",
+                decode_path_defby(id_signedData),
+                "encapContentInfo",
+                "eContent",
+                decode_path_defby(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:func:`pyderasn.decode_path_defby` and ``any``.
+First function is useful for path construction when some automatic
+decoding is already done. ``any`` is used for human readability and
+means literally any value it meet -- useful for sequence and set of-s.
+
 Primitive types
 ---------------
 
 Primitive types
 ---------------
 
@@ -298,6 +416,17 @@ SetOf
 _____
 .. autoclass:: pyderasn.SetOf
    :members: __init__
 _____
 .. autoclass:: pyderasn.SetOf
    :members: __init__
+
+Various
+-------
+
+.. autofunction:: pyderasn.hexenc
+.. autofunction:: pyderasn.hexdec
+.. autofunction:: pyderasn.tag_encode
+.. autofunction:: pyderasn.tag_decode
+.. autofunction:: pyderasn.tag_ctxp
+.. autofunction:: pyderasn.tag_ctxc
+.. autoclass:: pyderasn.Obj
 """
 
 from codecs import getdecoder
 """
 
 from codecs import getdecoder
@@ -307,6 +436,7 @@ from collections import OrderedDict
 from datetime import datetime
 from math import ceil
 
 from datetime import datetime
 from math import ceil
 
+from six import add_metaclass
 from six import binary_type
 from six import byte2int
 from six import indexbytes
 from six import binary_type
 from six import byte2int
 from six import indexbytes
@@ -326,6 +456,7 @@ __all__ = (
     "Boolean",
     "BoundsError",
     "Choice",
     "Boolean",
     "BoundsError",
     "Choice",
+    "decode_path_defby",
     "DecodeError",
     "Enumerated",
     "GeneralizedTime",
     "DecodeError",
     "Enumerated",
     "GeneralizedTime",
@@ -392,6 +523,16 @@ TagClassReprs = {
 
 class DecodeError(Exception):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
 
 class DecodeError(Exception):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
+        """
+        :param str msg: reason of decode failing
+        :param klass: optional exact DecodeError inherited class (like
+                      :py:exc:`NotEnoughData`, :py:exc:`TagMismatch`,
+                      :py:exc:`InvalidLength`)
+        :param decode_path: tuple of strings. It contains human
+                            readable names of the fields through which
+                            decoding process has passed
+        :param int offset: binary offset where failure happened
+        """
         super(DecodeError, self).__init__()
         self.msg = msg
         self.klass = klass
         super(DecodeError, self).__init__()
         self.msg = msg
         self.klass = klass
@@ -496,10 +637,14 @@ _hexencoder = getencoder("hex")
 
 
 def hexdec(data):
 
 
 def hexdec(data):
+    """Binary data to hexadecimal string convert
+    """
     return _hexdecoder(data)[0]
 
 
 def hexenc(data):
     return _hexdecoder(data)[0]
 
 
 def hexenc(data):
+    """Hexadecimal string to binary data convert
+    """
     return _hexencoder(data)[0].decode("ascii")
 
 
     return _hexencoder(data)[0].decode("ascii")
 
 
@@ -523,6 +668,16 @@ def zero_ended_encode(num):
 
 
 def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
 
 
 def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
+    """Encode tag to binary form
+
+    :param int num: tag's number
+    :param int klass: tag's class (:py:data:`pyderasn.TagClassUniversal`,
+                      :py:data:`pyderasn.TagClassContext`,
+                      :py:data:`pyderasn.TagClassApplication`,
+                      :py:data:`pyderasn.TagClassPrivate`)
+    :param int form: tag's form (:py:data:`pyderasn.TagFormPrimitive`,
+                     :py:data:`pyderasn.TagFormConstructed`)
+    """
     if num < 31:
         # [XX|X|.....]
         return int2byte(klass | form | num)
     if num < 31:
         # [XX|X|.....]
         return int2byte(klass | form | num)
@@ -531,8 +686,14 @@ def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
 
 
 def tag_decode(tag):
 
 
 def tag_decode(tag):
-    """
-    assume that data is validated
+    """Decode tag from binary form
+
+    .. warning::
+
+       No validation is performed, assuming that it has already passed.
+
+    It returns tuple with three integers, as
+    :py:func:`pyderasn.tag_encode` accepts.
     """
     first_octet = byte2int(tag)
     klass = first_octet & 0xC0
     """
     first_octet = byte2int(tag)
     klass = first_octet & 0xC0
@@ -547,10 +708,14 @@ def tag_decode(tag):
 
 
 def tag_ctxp(num):
 
 
 def tag_ctxp(num):
+    """Create CONTEXT PRIMITIVE tag
+    """
     return tag_encode(num=num, klass=TagClassContext, form=TagFormPrimitive)
 
 
 def tag_ctxc(num):
     return tag_encode(num=num, klass=TagClassContext, form=TagFormPrimitive)
 
 
 def tag_ctxc(num):
+    """Create CONTEXT CONSTRUCTED tag
+    """
     return tag_encode(num=num, klass=TagClassContext, form=TagFormConstructed)
 
 
     return tag_encode(num=num, klass=TagClassContext, form=TagFormConstructed)
 
 
@@ -610,8 +775,18 @@ def len_decode(data):
 # Base class
 ########################################################################
 
 # Base class
 ########################################################################
 
+class AutoAddSlots(type):
+    def __new__(cls, name, bases, _dict):
+        _dict["__slots__"] = _dict.get("__slots__", ())
+        return type.__new__(cls, name, bases, _dict)
+
+
+@add_metaclass(AutoAddSlots)
 class Obj(object):
     """Common ASN.1 object class
 class Obj(object):
     """Common ASN.1 object class
+
+    All ASN.1 types are inherited from it. It has metaclass that
+    automatically adds ``__slots__`` to all inherited classes.
     """
     __slots__ = (
         "tag",
     """
     __slots__ = (
         "tag",
@@ -632,10 +807,7 @@ 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:
             raise ValueError(
         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(
@@ -649,6 +821,8 @@ class Obj(object):
 
     @property
     def ready(self):  # pragma: no cover
 
     @property
     def ready(self):  # pragma: no cover
+        """Is object ready to be encoded?
+        """
         raise NotImplementedError()
 
     def _assert_ready(self):
         raise NotImplementedError()
 
     def _assert_ready(self):
@@ -657,9 +831,13 @@ class Obj(object):
 
     @property
     def decoded(self):
 
     @property
     def decoded(self):
-        return self.llen > 0
+        """Is object decoded?
+        """
+        return (self.llen + self.vlen) > 0
 
     def copy(self):  # pragma: no cover
 
     def copy(self):  # pragma: no cover
+        """Make a copy of object, safe to be mutated
+        """
         raise NotImplementedError()
 
     @property
         raise NotImplementedError()
 
     @property
@@ -673,10 +851,22 @@ class Obj(object):
     def __str__(self):  # pragma: no cover
         return self.__bytes__() if PY2 else self.__unicode__()
 
     def __str__(self):  # pragma: no cover
         return self.__bytes__() if PY2 else self.__unicode__()
 
+    def __ne__(self, their):
+        return not(self == their)
+
+    def __gt__(self, their):  # pragma: no cover
+        return not(self < their)
+
+    def __le__(self, their):  # pragma: no cover
+        return (self == their) or (self < their)
+
+    def __ge__(self, their):  # pragma: no cover
+        return (self == their) or (self > their)
+
     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=0, decode_path=(), defines_by_path=None):  # pragma: no cover
         raise NotImplementedError()
 
     def encode(self):
         raise NotImplementedError()
 
     def encode(self):
@@ -685,13 +875,23 @@ 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=(), defines_by_path=None):
+        """Decode the data
+
+        :param data: either binary or memoryview
+        :param int offset: initial data's offset
+        :param bool leavemm: do we need to leave memoryview of remaining
+                    data as is, or convert it to bytes otherwise
+        :param defines_by_path: :ref:`Read about DEFINED BY <definedby>`
+        :returns: (Obj, remaining data)
+        """
         tlv = memoryview(data)
         if self._expl is None:
             obj, tail = self._decode(
                 tlv,
                 offset,
                 decode_path=decode_path,
         tlv = memoryview(data)
         if self._expl is None:
             obj, tail = self._decode(
                 tlv,
                 offset,
                 decode_path=decode_path,
+                defines_by_path=defines_by_path,
             )
         else:
             try:
             )
         else:
             try:
@@ -728,7 +928,8 @@ class Obj(object):
             obj, tail = self._decode(
                 v,
                 offset=offset + tlen + llen,
             obj, tail = self._decode(
                 v,
                 offset=offset + tlen + llen,
-                decode_path=(),
+                decode_path=decode_path,
+                defines_by_path=defines_by_path,
             )
         return obj, (tail if leavemm else tail.tobytes())
 
             )
         return obj, (tail if leavemm else tail.tobytes())
 
@@ -761,6 +962,12 @@ class Obj(object):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
 
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
 
+def decode_path_defby(defined_by):
+    """DEFINED BY representation inside decode path
+    """
+    return "DEFINED BY (%s)" % defined_by
+
+
 ########################################################################
 # Pretty printing
 ########################################################################
 ########################################################################
 # Pretty printing
 ########################################################################
@@ -888,6 +1095,15 @@ def pp_console_blob(pp):
 
 
 def pprint(obj, oids=None, big_blobs=False):
 
 
 def pprint(obj, oids=None, big_blobs=False):
+    """Pretty print object
+
+    :param Obj obj: object you want to pretty print
+    :param oids: ``OID <-> humand readable string`` dictionary. When OID
+                 from it is met, then its humand readable form is printed
+    :param big_blobs: if large binary objects are met (like OctetString
+                      values), do we need to print them too, on separate
+                      lines
+    """
     def _pprint_pps(pps):
         for pp in pps:
             if hasattr(pp, "_fields"):
     def _pprint_pps(pps):
         for pp in pps:
             if hasattr(pp, "_fields"):
@@ -921,10 +1137,6 @@ class Boolean(Obj):
     True
     >>> bool(b)
     True
     True
     >>> bool(b)
     True
-    >>> Boolean(optional=True)
-    BOOLEAN OPTIONAL
-    >>> Boolean(impl=tag_ctxp(1), default=False)
-    [1] BOOLEAN False OPTIONAL DEFAULT
     """
     __slots__ = ()
     tag_default = tag_encode(1)
     """
     __slots__ = ()
     tag_default = tag_encode(1)
@@ -1025,7 +1237,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=0, decode_path=(), defines_by_path=None):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1119,10 +1331,6 @@ class Integer(Obj):
     True
     >>> int(b)
     -123
     True
     >>> int(b)
     -123
-    >>> Integer(optional=True)
-    INTEGER OPTIONAL
-    >>> Integer(impl=tag_ctxp(1), default=123)
-    [1] INTEGER 123 OPTIONAL DEFAULT
 
     >>> Integer(2, bounds=(1, 3))
     INTEGER 2
 
     >>> Integer(2, bounds=(1, 3))
     INTEGER 2
@@ -1178,14 +1386,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:
@@ -1256,10 +1461,7 @@ class Integer(Obj):
         )
 
     def __lt__(self, their):
         )
 
     def __lt__(self, their):
-        return self._value < their
-
-    def __gt__(self, their):
-        return self._value > their
+        return self._value < their._value
 
     @property
     def named(self):
 
     @property
     def named(self):
@@ -1328,7 +1530,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=0, decode_path=(), defines_by_path=None):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1576,7 +1778,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
@@ -1658,7 +1863,7 @@ class BitString(Obj):
             octets,
         ))
 
             octets,
         ))
 
-    def _decode(self, tlv, offset=0, decode_path=()):
+    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1779,7 +1984,7 @@ class OctetString(Obj):
     >>> OctetString(b"hell", bounds=(4, 4))
     OCTET STRING 4 bytes 68656c6c
     """
     >>> OctetString(b"hell", bounds=(4, 4))
     OCTET STRING 4 bytes 68656c6c
     """
-    __slots__ = ("_bound_min", "_bound_max")
+    __slots__ = ("_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"
 
@@ -1811,14 +2016,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:
@@ -1830,6 +2032,7 @@ class OctetString(Obj):
             )
             if self._value is None:
                 self._value = default
             )
             if self._value is None:
                 self._value = default
+        self.defined = None
 
     def _value_sanitize(self, value):
         if issubclass(value.__class__, OctetString):
 
     def _value_sanitize(self, value):
         if issubclass(value.__class__, OctetString):
@@ -1875,6 +2078,9 @@ class OctetString(Obj):
             self._expl == their._expl
         )
 
             self._expl == their._expl
         )
 
+    def __lt__(self, their):
+        return self._value < their._value
+
     def __call__(
             self,
             value=None,
     def __call__(
             self,
             value=None,
@@ -1904,7 +2110,7 @@ class OctetString(Obj):
             self._value,
         ))
 
             self._value,
         ))
 
-    def _decode(self, tlv, offset=0, decode_path=()):
+    def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1979,6 +2185,11 @@ class OctetString(Obj):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen 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,
         )
+        defined_by, defined = self.defined or (None, None)
+        if defined_by is not None:
+            yield defined.pps(
+                decode_path=decode_path + (decode_path_defby(defined_by),)
+            )
 
 
 class Null(Obj):
 
 
 class Null(Obj):
@@ -2048,7 +2259,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=0, decode_path=(), defines_by_path=None):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2128,13 +2339,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=None,
             impl=None,
             expl=None,
             default=None,
             impl=None,
             expl=None,
             default=None,
@@ -2145,6 +2357,13 @@ class ObjectIdentifier(Obj):
         :param value: set the value. Either tuples of integers,
                       string of "."-concatenated integers, or
                       :py:class:`pyderasn.ObjectIdentifier` object
         :param value: set the value. Either tuples of integers,
                       string of "."-concatenated integers, or
                       :py:class:`pyderasn.ObjectIdentifier` object
+        :param defines: tuple of two elements. First one is a name of
+                        field inside :py:class:`pyderasn.Sequence`,
+                        defining with that OID. Second element is a
+                        ``{OID: pyderasn.Obj()}`` dictionary, mapping
+                        between current OID value and structure applied
+                        to defined field.
+                        :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``
@@ -2169,6 +2388,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__):
@@ -2206,6 +2426,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
@@ -2242,14 +2463,12 @@ class ObjectIdentifier(Obj):
         )
 
     def __lt__(self, their):
         )
 
     def __lt__(self, their):
-        return self._value < their
-
-    def __gt__(self, their):
-        return self._value > their
+        return self._value < their._value
 
     def __call__(
             self,
             value=None,
 
     def __call__(
             self,
             value=None,
+            defines=None,
             impl=None,
             expl=None,
             default=None,
             impl=None,
             expl=None,
             default=None,
@@ -2257,6 +2476,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,
@@ -2282,7 +2502,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=0, decode_path=(), defines_by_path=None):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2647,6 +2867,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
 
@@ -2713,7 +2938,7 @@ class UTCTime(CommonString):
             return value.strftime(self.fmt).encode("ascii")
         if isinstance(value, binary_type):
             value_decoded = value.decode("ascii")
             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:
+            if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
                 except ValueError:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
                 except ValueError:
@@ -2804,7 +3029,7 @@ class GeneralizedTime(UTCTime):
             ).encode("ascii")
         if isinstance(value, binary_type):
             value_decoded = value.decode("ascii")
             ).encode("ascii")
         if isinstance(value, binary_type):
             value_decoded = value.decode("ascii")
-            if len(value_decoded) == 4 + 2 + 2 + 2 + 2 + 2 + 1:
+            if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
                 except ValueError:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
                 except ValueError:
@@ -2812,7 +3037,7 @@ class GeneralizedTime(UTCTime):
                         "invalid GeneralizedTime (without ms) format",
                     )
                 return value
                         "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)
                 except ValueError:
                 try:
                     datetime.strptime(value_decoded, self.fmt_ms)
                 except ValueError:
@@ -2829,7 +3054,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)
 
@@ -3044,7 +3269,7 @@ 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=0, decode_path=(), defines_by_path=None):
         for choice, spec in self.specs.items():
             try:
                 value, tail = spec.decode(
         for choice, spec in self.specs.items():
             try:
                 value, tail = spec.decode(
@@ -3052,6 +3277,7 @@ class Choice(Obj):
                     offset=offset,
                     leavemm=True,
                     decode_path=decode_path + (choice,),
                     offset=offset,
                     leavemm=True,
                     decode_path=decode_path + (choice,),
+                    defines_by_path=defines_by_path,
                 )
             except TagMismatch:
                 continue
                 )
             except TagMismatch:
                 continue
@@ -3140,7 +3366,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"
 
@@ -3161,6 +3387,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__):
@@ -3217,7 +3444,7 @@ 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=0, decode_path=(), defines_by_path=None):
         try:
             t, tlen, lv = tag_strip(tlv)
             l, llen, v = len_decode(lv)
         try:
             t, tlen, lv = tag_strip(tlv)
             l, llen, v = len_decode(lv)
@@ -3268,13 +3495,110 @@ class Any(Obj):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen 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,
         )
+        defined_by, defined = self.defined or (None, None)
+        if defined_by is not None:
+            yield defined.pps(
+                decode_path=decode_path + (decode_path_defby(defined_by),)
+            )
 
 
 ########################################################################
 # 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
+
+
 class Sequence(Obj):
 class Sequence(Obj):
+    """``SEQUENCE`` structure type
+
+    You have to make specification of sequence::
+
+        class Extension(Sequence):
+            schema = (
+                ("extnID", ObjectIdentifier()),
+                ("critical", Boolean(default=False)),
+                ("extnValue", OctetString()),
+            )
+
+    Then, you can work with it as with dictionary.
+
+    >>> ext = Extension()
+    >>> Extension().specs
+    OrderedDict([
+        ('extnID', OBJECT IDENTIFIER),
+        ('critical', BOOLEAN False OPTIONAL DEFAULT),
+        ('extnValue', OCTET STRING),
+    ])
+    >>> ext["extnID"] = "1.2.3"
+    Traceback (most recent call last):
+    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:
+
+    >>> ext.ready
+    False
+    >>> ext.encode()
+    Traceback (most recent call last):
+    pyderasn.ObjNotReady: object is not ready: extnValue
+    >>> ext["extnValue"] = OctetString(b"foobar")
+    >>> ext.ready
+    True
+
+    Value you want to assign, must have the same **type** as in
+    corresponding specification, but it can have different tags,
+    optional/default attributes -- they will be taken from specification
+    automatically::
+
+        class TBSCertificate(Sequence):
+            schema = (
+                ("version", Version(expl=tag_ctxc(0), default="v1")),
+            [...]
+
+    >>> 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:
+
+    >>> "extnID" in ext, "extnValue" in ext, "critical" in ext
+    (True, True, False)
+    >>> ext["extnID"]
+    OBJECT IDENTIFIER 1.2.3
+
+    But pay attention that if value has default, then it won't be (not
+    in) in the sequence (because ``DEFAULT`` must not be encoded in
+    DER), but you can read its value:
+
+    >>> "critical" in ext, ext["critical"]
+    (False, BOOLEAN False)
+    >>> ext["critical"] = Boolean(True)
+    >>> "critical" in ext, ext["critical"]
+    (True, BOOLEAN True)
+
+    All defaulted values are always optional.
+
+    .. warning::
+
+       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).
+
+    Two sequences are equal if they have equal specification (schema),
+    implicit/explicit tagging and the same values.
+    """
     __slots__ = ("specs",)
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE"
     __slots__ = ("specs",)
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE"
@@ -3408,7 +3732,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=0, decode_path=(), defines_by_path=None):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3443,27 +3767,81 @@ class Sequence(Obj):
         v, tail = v[:l], v[l:]
         sub_offset = offset + tlen + llen
         values = {}
         v, tail = v[:l], v[l:]
         sub_offset = offset + tlen + llen
         values = {}
+        defines = {}
         for name, spec in self.specs.items():
             if len(v) == 0 and spec.optional:
                 continue
         for name, spec in self.specs.items():
             if len(v) == 0 and spec.optional:
                 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,
+                    defines_by_path=defines_by_path,
                 )
             except TagMismatch:
                 if spec.optional:
                     continue
                 raise
                 )
             except TagMismatch:
                 if spec.optional:
                     continue
                 raise
+
+            defined = defines.pop(name, None)
+            if defined is not None:
+                defined_by, defined_spec = defined
+                if issubclass(value.__class__, SequenceOf):
+                    for i, _value in enumerate(value):
+                        sub_sub_decode_path = sub_decode_path + (
+                            str(i),
+                            decode_path_defby(defined_by),
+                        )
+                        defined_value, defined_tail = defined_spec.decode(
+                            memoryview(bytes(_value)),
+                            sub_offset + value.tlen + value.llen,
+                            leavemm=True,
+                            decode_path=sub_sub_decode_path,
+                            defines_by_path=defines_by_path,
+                        )
+                        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,
+                        leavemm=True,
+                        decode_path=sub_decode_path + (decode_path_defby(defined_by),),
+                        defines_by_path=defines_by_path,
+                    )
+                    if len(defined_tail) > 0:
+                        raise DecodeError(
+                            "remaining data",
+                            klass=self.__class__,
+                            decode_path=sub_decode_path + (decode_path_defby(defined_by),),
+                            offset=offset,
+                        )
+                    value.defined = (defined_by, defined_value)
+
             sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
             v = v_tail
             if spec.default is not None and value == spec.default:
                 # Encoded default values are not valid in DER,
             sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
             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
+                # but we allow that anyway
                 continue
             values[name] = value
                 continue
             values[name] = value
+
+            spec_defines = getattr(spec, "defines", None)
+            if defines_by_path is not None and spec_defines is None:
+                spec_defines = get_def_by_path(defines_by_path, sub_decode_path)
+            if spec_defines is not None:
+                what, schema = spec_defines
+                defined = schema.get(value, None)
+                if defined is not None:
+                    defines[what] = (value, defined)
         if len(v) > 0:
             raise DecodeError(
                 "remaining data",
         if len(v) > 0:
             raise DecodeError(
                 "remaining data",
@@ -3518,6 +3896,10 @@ class Sequence(Obj):
 
 
 class Set(Sequence):
 
 
 class Set(Sequence):
+    """``SET`` structure type
+
+    Its usage is identical to :py:class:`pyderasn.Sequence`.
+    """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET"
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET"
@@ -3528,7 +3910,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=0, decode_path=(), defines_by_path=None):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3571,6 +3953,7 @@ class Set(Sequence):
                         sub_offset,
                         leavemm=True,
                         decode_path=decode_path + (name,),
                         sub_offset,
                         leavemm=True,
                         decode_path=decode_path + (name,),
+                        defines_by_path=defines_by_path,
                     )
                 except TagMismatch:
                     continue
                     )
                 except TagMismatch:
                     continue
@@ -3601,6 +3984,35 @@ class Set(Sequence):
 
 
 class SequenceOf(Obj):
 
 
 class SequenceOf(Obj):
+    """``SEQUENCE OF`` sequence type
+
+    For that kind of type you must specify the object it will carry on
+    (bounds are for example here, not required)::
+
+        class Ints(SequenceOf):
+            schema = Integer()
+            bounds = (0, 2)
+
+    >>> ints = Ints()
+    >>> ints.append(Integer(123))
+    >>> ints.append(Integer(234))
+    >>> ints
+    Ints SEQUENCE OF[INTEGER 123, INTEGER 234]
+    >>> [int(i) for i in ints]
+    [123, 234]
+    >>> ints.append(Integer(345))
+    Traceback (most recent call last):
+    pyderasn.BoundsError: unsatisfied bounds: 0 <= 3 <= 2
+    >>> ints[1]
+    INTEGER 234
+    >>> ints[1] = Integer(345)
+    >>> ints
+    Ints SEQUENCE OF[INTEGER 123, INTEGER 345]
+
+    Also you can initialize sequence with preinitialized values:
+
+    >>> ints = Ints([Integer(123), Integer(234)])
+    """
     __slots__ = ("spec", "_bound_min", "_bound_max")
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE OF"
     __slots__ = ("spec", "_bound_min", "_bound_max")
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE OF"
@@ -3628,14 +4040,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)
@@ -3754,7 +4163,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=0, decode_path=(), defines_by_path=None):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3796,6 +4205,7 @@ class SequenceOf(Obj):
                 sub_offset,
                 leavemm=True,
                 decode_path=decode_path + (str(len(_value)),),
                 sub_offset,
                 leavemm=True,
                 decode_path=decode_path + (str(len(_value)),),
+                defines_by_path=defines_by_path,
             )
             sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
             v = v_tail
             )
             sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
             v = v_tail
@@ -3841,6 +4251,10 @@ class SequenceOf(Obj):
 
 
 class SetOf(SequenceOf):
 
 
 class SetOf(SequenceOf):
+    """``SET OF`` sequence type
+
+    Its usage is identical to :py:class:`pyderasn.SequenceOf`.
+    """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET OF"
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET OF"
@@ -3870,9 +4284,56 @@ 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):
+        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()))
+    return SEQUENCEOF(), pprint_any
+
+
 def main():  # pragma: no cover
     import argparse
     parser = argparse.ArgumentParser(description="PyDERASN ASN.1 DER decoder")
 def main():  # pragma: no cover
     import argparse
     parser = argparse.ArgumentParser(description="PyDERASN ASN.1 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",
@@ -3881,12 +4342,17 @@ 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(
         "DERFile",
         type=argparse.FileType("rb"),
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
-        help="Python path to schema definition to use",
+        help="Path to DER file you want to decode",
     )
     args = parser.parse_args()
     )
     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 {}
@@ -3895,46 +4361,14 @@ 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)
+        schema, pprinter = generic_decoder()
+    obj, tail = schema().decode(
+        der,
+        defines_by_path=(
+            None if args.defines_by_path is None
+            else obj_by_path(args.defines_by_path)
+        ),
+    )
     print(pprinter(obj, oids=oids))
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))
     print(pprinter(obj, oids=oids))
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))