]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Store OIDs as an array.array
[pyderasn.git] / pyderasn.py
index 2e74bda53a344f683d7c0548a572aefbfa77d41e..7bb1799863c454bb22bdbdc70c75411c47635f04 100755 (executable)
@@ -1,12 +1,12 @@
 #!/usr/bin/env python
 # coding: utf-8
+# cython: language_level=3
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -14,8 +14,7 @@
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
-# License along with this program.  If not, see
-# <http://www.gnu.org/licenses/>.
+# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """Python ASN.1 DER/BER codec with abstract structures
 
 This library allows you to marshal various structures in ASN.1 DER
@@ -23,7 +22,7 @@ format, unmarshal them in BER/CER/DER ones.
 
     >>> i = Integer(123)
     >>> raw = i.encode()
-    >>> Integer().decode(raw) == i
+    >>> Integer().decod(raw) == i
     True
 
 There are primitive types, holding single values
@@ -67,17 +66,26 @@ ____
 
 Most types in ASN.1 has specific tag for them. ``Obj.tag_default`` is
 the default tag used during coding process. You can override it with
-either ``IMPLICIT`` (using ``impl`` keyword argument), or
-``EXPLICIT`` one (using ``expl`` keyword argument). Both arguments take
-raw binary string, containing that tag. You can **not** set implicit and
-explicit tags simultaneously.
+either ``IMPLICIT`` (using either ``impl`` keyword argument or ``impl``
+class attribute), or ``EXPLICIT`` one (using either ``expl`` keyword
+argument or ``expl`` class attribute). Both arguments take raw binary
+string, containing that tag. You can **not** set implicit and explicit
+tags simultaneously.
 
 There are :py:func:`pyderasn.tag_ctxp` and :py:func:`pyderasn.tag_ctxc`
 functions, allowing you to easily create ``CONTEXT``
 ``PRIMITIVE``/``CONSTRUCTED`` tags, by specifying only the required tag
-number. Pay attention that explicit tags always have *constructed* tag
-(``tag_ctxc``), but implicit tags for primitive types are primitive
-(``tag_ctxp``).
+number.
+
+.. note::
+
+   EXPLICIT tags always have **constructed** tag. PyDERASN does not
+   explicitly check correctness of schema input here.
+
+.. note::
+
+   Implicit tags have **primitive** (``tag_ctxp``) encoding for
+   primitive values.
 
 ::
 
@@ -163,21 +171,27 @@ All objects have ``ready`` boolean property, that tells if object is
 ready to be encoded. If that kind of action is performed on unready
 object, then :py:exc:`pyderasn.ObjNotReady` exception will be raised.
 
-All objects have ``copy()`` method, that returns their copy, that can be
+All objects are friendly to ``copy.copy()`` and copied objects can be
 safely mutated.
 
+Also all objects can be safely ``pickle``-d, but pay attention that
+pickling among different PyDERASN versions is prohibited.
+
 .. _decoding:
 
 Decoding
 --------
 
-Decoding is performed using ``decode()`` method. ``offset`` optional
-argument could be used to set initial object's offset in the binary
-data, for convenience. It returns decoded object and remaining
-unmarshalled data (tail). Internally all work is done on
+Decoding is performed using :py:meth:`pyderasn.Obj.decode` method.
+``offset`` optional argument could be used to set initial object's
+offset in the binary data, for convenience. It returns decoded object
+and remaining unmarshalled data (tail). Internally all work is done on
 ``memoryview(data)``, and you can leave returning tail as a memoryview,
 by specifying ``leavemm=True`` argument.
 
+Also note convenient :py:meth:`pyderasn.Obj.decod` method, that
+immediately checks and raises if there is non-empty tail.
+
 When object is decoded, ``decoded`` property is true and you can safely
 use following properties:
 
@@ -207,9 +221,9 @@ When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
 Context
 _______
 
-You can specify so called context keyword argument during ``decode()``
-invocation. It is dictionary containing various options governing
-decoding process.
+You can specify so called context keyword argument during
+:py:meth:`pyderasn.Obj.decode` invocation. It is dictionary containing
+various options governing decoding process.
 
 Currently available context options:
 
@@ -218,6 +232,7 @@ Currently available context options:
 * :ref:`allow_unordered_set <allow_unordered_set_ctx>`
 * :ref:`bered <bered_ctx>`
 * :ref:`defines_by_path <defines_by_path_ctx>`
+* :ref:`evgen_mode_upto <evgen_mode_upto_ctx>`
 
 .. _pprinting:
 
@@ -239,6 +254,105 @@ all object ``repr``. But it is easy to write custom formatters.
     >>> print(pprint(obj))
         0   [1,1,   2] INTEGER -12345
 
+.. _pprint_example:
+
+Example certificate::
+
+    >>> print(pprint(crt))
+        0   [1,3,1604] Certificate SEQUENCE
+        4   [1,3,1453]  . tbsCertificate: TBSCertificate SEQUENCE
+       10-2 [1,1,   1]  . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL
+       13   [1,1,   3]  . . serialNumber: CertificateSerialNumber INTEGER 61595
+       18   [1,1,  13]  . . signature: AlgorithmIdentifier SEQUENCE
+       20   [1,1,   9]  . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+       31   [0,0,   2]  . . . parameters: [UNIV 5] ANY OPTIONAL
+                        . . . . 05:00
+       33   [0,0, 278]  . . issuer: Name CHOICE rdnSequence
+       33   [1,3, 274]  . . . rdnSequence: RDNSequence SEQUENCE OF
+       37   [1,1,  11]  . . . . 0: RelativeDistinguishedName SET OF
+       39   [1,1,   9]  . . . . . 0: AttributeTypeAndValue SEQUENCE
+       41   [1,1,   3]  . . . . . . type: AttributeType OBJECT IDENTIFIER 2.5.4.6
+       46   [0,0,   4]  . . . . . . value: [UNIV 19] AttributeValue ANY
+                        . . . . . . . 13:02:45:53
+    [...]
+     1461   [1,1,  13]  . signatureAlgorithm: AlgorithmIdentifier SEQUENCE
+     1463   [1,1,   9]  . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+     1474   [0,0,   2]  . . parameters: [UNIV 5] ANY OPTIONAL
+                        . . . 05:00
+     1476   [1,2, 129]  . signatureValue: BIT STRING 1024 bits
+                        . . 68:EE:79:97:97:DD:3B:EF:16:6A:06:F2:14:9A:6E:CD
+                        . . 9E:12:F7:AA:83:10:BD:D1:7C:98:FA:C7:AE:D4:0E:2C
+     [...]
+
+    Trailing data: 0a
+
+Let's parse that output, human::
+
+       10-2 [1,1,   1]    . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL
+       ^  ^  ^ ^    ^     ^   ^        ^            ^       ^       ^  ^
+       0  1  2 3    4     5   6        7            8       9       10 11
+
+::
+
+       20   [1,1,   9]    . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+       ^     ^ ^    ^     ^     ^          ^                 ^
+       0     2 3    4     5     6          9                 10
+
+::
+
+       33   [0,0, 278]    . . issuer: Name CHOICE rdnSequence
+       ^     ^ ^    ^     ^   ^       ^    ^      ^
+       0     2 3    4     5   6       8    9      10
+
+::
+
+       52-2∞ B [1,1,1054]∞  . . . . eContent: [0] EXPLICIT BER OCTET STRING 1046 bytes
+             ^           ^                                 ^   ^            ^
+            12          13                                14   9            10
+
+:0:
+ Offset of the object, where its DER/BER encoding begins.
+ Pay attention that it does **not** include explicit tag.
+:1:
+ If explicit tag exists, then this is its length (tag + encoded length).
+:2:
+ Length of object's tag. For example CHOICE does not have its own tag,
+ so it is zero.
+:3:
+ Length of encoded length.
+:4:
+ Length of encoded value.
+:5:
+ Visual indentation to show the depth of object in the hierarchy.
+:6:
+ Object's name inside SEQUENCE/CHOICE.
+:7:
+ If either IMPLICIT or EXPLICIT tag is set, then it will be shown
+ here. "IMPLICIT" is omitted.
+:8:
+ Object's class name, if set. Omitted if it is just an ordinary simple
+ value (like with ``algorithm`` in example above).
+:9:
+ Object's ASN.1 type.
+:10:
+ Object's value, if set. Can consist of multiple words (like OCTET/BIT
+ STRINGs above). We see ``v3`` value in Version, because it is named.
+ ``rdnSequence`` is the choice of CHOICE type.
+:11:
+ Possible other flags like OPTIONAL and DEFAULT, if value equals to the
+ default one, specified in the schema.
+:12:
+ Shows does object contains any kind of BER encoded data (possibly
+ Sequence holding BER-encoded underlying value).
+:13:
+ Only applicable to BER encoded data. Indefinite length encoding mark.
+:14:
+ Only applicable to BER encoded data. If object has BER-specific
+ encoding, then ``BER`` will be shown. It does not depend on indefinite
+ length encoding. ``EOC``, ``BOOLEAN``, ``BIT STRING``, ``OCTET STRING``
+ (and its derivatives), ``SET``, ``SET OF``, ``UTCTime``, ``GeneralizedTime``
+ could be BERed.
+
 .. _definedby:
 
 DEFINED BY
@@ -249,6 +363,8 @@ DEFINED BY some previously met ObjectIdentifier. This library provides
 ability to specify mapping between some OID and field that must be
 decoded with specific specification.
 
+.. _defines:
+
 defines kwarg
 _____________
 
@@ -312,8 +428,8 @@ 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.
+in the schema. You can dynamically apply those definitions when calling
+:py:meth:`pyderasn.Obj.decode` method.
 
 Specify ``defines_by_path`` key in the :ref:`decode context <ctx>`. Its
 value must be sequence of following tuples::
@@ -322,15 +438,15 @@ value must be sequence of following tuples::
 
 where ``decode_path`` is a tuple holding so-called decode path to the
 exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
-``defines``, holding exactly the same value as accepted in its keyword
-argument.
+``defines``, holding exactly the same value as accepted in its
+:ref:`keyword argument <defines>`.
 
 For example, again for CMS, you want to automatically decode
 ``SignedData`` and CMC's (:rfc:`5272`) ``PKIData`` and ``PKIResponse``
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
-    content_info, tail = ContentInfo().decode(data, defines_by_path=(
+    content_info = ContentInfo().decod(data, ctx={"defines_by_path": (
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
@@ -365,7 +481,7 @@ of ``PKIResponse``::
                 id_cmc_transactionId: TransactionId(),
             })),
         ),
-    ))
+    )})
 
 Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
 First function is useful for path construction when some automatic
@@ -377,14 +493,15 @@ useful for SEQUENCE/SET OF-s.
 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
+By default PyDERASN accepts only DER encoded data. By default it 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.
+  STRING``, ``OBJECT IDENTIFIER``, ``SEQUENCE``, ``SET``, ``SET OF``,
+  ``UTCTime``, ``GeneralizedTime`` can contain it.
 * If object has an indefinite length encoding, then its ``lenindef``
   attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``,
   ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can
@@ -417,6 +534,319 @@ lengths will be invalid in that case.
    This option should be used only for skipping some decode errors, just
    to see the decoded structure somehow.
 
+.. _streaming:
+
+Streaming and dealing with huge structures
+------------------------------------------
+
+.. _evgen_mode:
+
+evgen mode
+__________
+
+ASN.1 structures can be huge, they can hold millions of objects inside
+(for example Certificate Revocation Lists (CRL), holding revocation
+state for every previously issued X.509 certificate). CACert.org's 8 MiB
+CRL file takes more than half a gigabyte of memory to hold the decoded
+structure.
+
+If you just simply want to check the signature over the ``tbsCertList``,
+you can create specialized schema with that field represented as
+OctetString for example::
+
+    class TBSCertListFast(Sequence):
+        schema = (
+            [...]
+            ("revokedCertificates", OctetString(
+                impl=SequenceOf.tag_default,
+                optional=True,
+            )),
+            [...]
+        )
+
+This allows you to quickly decode a few fields and check the signature
+over the ``tbsCertList`` bytes.
+
+But how can you get all certificate's serial number from it, after you
+trust that CRL after signature validation? You can use so called
+``evgen`` (event generation) mode, to catch the events/facts of some
+successful object decoding. Let's use command line capabilities::
+
+    $ python -m pyderasn --schema tests.test_crl:CertificateList --evgen revoke.crl
+         10     [1,1,   1]   . . version: Version INTEGER v2 (01) OPTIONAL
+         15     [1,1,   9]   . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.13
+         26     [0,0,   2]   . . . parameters: [UNIV 5] ANY OPTIONAL
+         13     [1,1,  13]   . . signature: AlgorithmIdentifier SEQUENCE
+         34     [1,1,   3]   . . . . . . type: AttributeType OBJECT IDENTIFIER 2.5.4.10
+         39     [0,0,   9]   . . . . . . value: [UNIV 19] AttributeValue ANY
+         32     [1,1,  14]   . . . . . 0: AttributeTypeAndValue SEQUENCE
+         30     [1,1,  16]   . . . . 0: RelativeDistinguishedName SET OF
+    [...]
+        188     [1,1,   1]   . . . . userCertificate: CertificateSerialNumber INTEGER 17 (11)
+        191     [1,1,  13]   . . . . . utcTime: UTCTime UTCTime 2003-04-01T14:25:08
+        191     [0,0,  15]   . . . . revocationDate: Time CHOICE utcTime
+        191     [1,1,  13]   . . . . . utcTime: UTCTime UTCTime 2003-04-01T14:25:08
+        186     [1,1,  18]   . . . 0: RevokedCertificate SEQUENCE
+        208     [1,1,   1]   . . . . userCertificate: CertificateSerialNumber INTEGER 20 (14)
+        211     [1,1,  13]   . . . . . utcTime: UTCTime UTCTime 2002-10-01T02:18:01
+        211     [0,0,  15]   . . . . revocationDate: Time CHOICE utcTime
+        211     [1,1,  13]   . . . . . utcTime: UTCTime UTCTime 2002-10-01T02:18:01
+        206     [1,1,  18]   . . . 1: RevokedCertificate SEQUENCE
+    [...]
+    9144992     [0,0,  15]   . . . . revocationDate: Time CHOICE utcTime
+    9144992     [1,1,  13]   . . . . . utcTime: UTCTime UTCTime 2020-02-08T07:25:06
+    9144985     [1,1,  20]   . . . 415755: RevokedCertificate SEQUENCE
+      181     [1,4,9144821]   . . revokedCertificates: RevokedCertificates SEQUENCE OF OPTIONAL
+        5     [1,4,9144997]   . tbsCertList: TBSCertList SEQUENCE
+    9145009     [1,1,   9]   . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.13
+    9145020     [0,0,   2]   . . parameters: [UNIV 5] ANY OPTIONAL
+    9145007     [1,1,  13]   . signatureAlgorithm: AlgorithmIdentifier SEQUENCE
+    9145022     [1,3, 513]   . signatureValue: BIT STRING 4096 bits
+        0     [1,4,9145534]  CertificateList SEQUENCE
+
+Here we see how decoder works: it decodes SEQUENCE's tag, length, then
+decodes underlying values. It can not tell if SEQUENCE is decoded, so
+the event of the upper level SEQUENCE is the last one we see.
+``version`` field is just a single INTEGER -- it is decoded and event is
+fired immediately. Then we see that ``algorithm`` and ``parameters``
+fields are decoded and only after them the ``signature`` SEQUENCE is
+fired as a successfully decoded. There are 4 events for each revoked
+certificate entry in that CRL: ``userCertificate`` serial number,
+``utcTime`` of ``revocationDate`` CHOICE, ``RevokedCertificate`` itself
+as a one of entity in ``revokedCertificates`` SEQUENCE OF.
+
+We can do that in our ordinary Python code and understand where we are
+by looking at deterministically generated decode paths (do not forget
+about useful ``--print-decode-path`` CLI option). We must use
+:py:meth:`pyderasn.Obj.decode_evgen` method, instead of ordinary
+:py:meth:`pyderasn.Obj.decode`. It is generator yielding ``(decode_path,
+obj, tail)`` tuples::
+
+    for decode_path, obj, _ in CertificateList().decode_evgen(crl_raw):
+        if (
+            len(decode_path) == 4 and
+            decode_path[:2] == ("tbsCertList", "revokedCertificates"),
+            decode_path[3] == "userCertificate"
+        ):
+            print("serial number:", int(obj))
+
+Virtually it does not take any memory except at least needed for single
+object storage. You can easily use that mode to determine required
+object ``.offset`` and ``.*len`` to be able to decode it separately, or
+maybe verify signature upon it just by taking bytes by ``.offset`` and
+``.tlvlen``.
+
+.. _evgen_mode_upto_ctx:
+
+evgen_mode_upto
+_______________
+
+There is full ability to get any kind of data from the CRL in the
+example above. However it is not too convenient to get the whole
+``RevokedCertificate`` structure, that is pretty lightweight and one may
+do not want to disassemble it. You can use ``evgen_mode_upto``
+:ref:`ctx <ctx>` option that semantically equals to
+:ref:`defines_by_path <defines_by_path_ctx>` -- list of decode paths
+mapped to any non-None value. If specified decode path is met, then any
+subsequent objects won't be decoded in evgen mode. That allows us to
+parse the CRL above with fully assembled ``RevokedCertificate``::
+
+    for decode_path, obj, _ in CertificateList().decode_evgen(
+        crl_raw,
+        ctx={"evgen_mode_upto": (
+            (("tbsCertList", "revokedCertificates", any), True),
+        )},
+    ):
+        if (
+            len(decode_path) == 3 and
+            decode_path[:2] == ("tbsCertList", "revokedCertificates"),
+        ):
+            print("serial number:", int(obj["userCertificate"]))
+
+.. _mmap:
+
+mmap-ed file
+____________
+
+POSIX compliant systems have ``mmap`` syscall, giving ability to work
+the memory mapped file. You can deal with the file like it was an
+ordinary binary string, allowing you not to load it to the memory first.
+Also you can use them as an input for OCTET STRING, taking no Python
+memory for their storage.
+
+There is convenient :py:func:`pyderasn.file_mmaped` function that
+creates read-only memoryview on the file contents::
+
+    with open("huge", "rb") as fd:
+        raw = file_mmaped(fd)
+        obj = Something.decode(raw)
+
+.. warning::
+
+   mmap-ed files in Python2.7 does not implement buffer protocol, so
+   memoryview won't work on them.
+
+.. warning::
+
+   mmap maps the **whole** file. So it plays no role if you seek-ed it
+   before. Take the slice of the resulting memoryview with required
+   offset instead.
+
+.. note::
+
+   If you use ZFS as underlying storage, then pay attention that
+   currently most platforms does not deal good with ZFS ARC and ordinary
+   page cache used for mmaps. It can take twice the necessary size in
+   the memory: both in page cache and ZFS ARC.
+
+CER encoding
+____________
+
+We can parse any kind of data now, but how can we produce files
+streamingly, without storing their encoded representation in memory?
+SEQUENCE by default encodes in memory all its values, joins them in huge
+binary string, just to know the exact size of SEQUENCE's value for
+encoding it in TLV. DER requires you to know all exact sizes of the
+objects.
+
+You can use CER encoding mode, that slightly differs from the DER, but
+does not require exact sizes knowledge, allowing streaming encoding
+directly to some writer/buffer. Just use
+:py:meth:`pyderasn.Obj.encode_cer` method, providing the writer where
+encoded data will flow::
+
+    opener = io.open if PY2 else open
+    with opener("result", "wb") as fd:
+        obj.encode_cer(fd.write)
+
+::
+
+    buf = io.BytesIO()
+    obj.encode_cer(buf.write)
+
+If you do not want to create in-memory buffer every time, then you can
+use :py:func:`pyderasn.encode_cer` function::
+
+    data = encode_cer(obj)
+
+Remember that CER is **not valid** DER in most cases, so you **have to**
+use :ref:`bered <bered_ctx>` :ref:`ctx <ctx>` option during its
+decoding. Also currently there is **no** validation that provided CER is
+valid one -- you are sure that it has only valid BER encoding.
+
+.. warning::
+
+   SET OF values can not be streamingly encoded, because they are
+   required to be sorted byte-by-byte. Big SET OF values still will take
+   much memory. Use neither SET nor SET OF values, as modern ASN.1
+   also recommends too.
+
+Do not forget about using :ref:`mmap-ed <mmap>` memoryviews for your
+OCTET STRINGs! They will be streamingly copied from underlying file to
+the buffer using 1 KB chunks.
+
+Some structures require that some of the elements have to be forcefully
+DER encoded. For example ``SignedData`` CMS requires you to encode
+``SignedAttributes`` and X.509 certificates in DER form, allowing you to
+encode everything else in BER. You can tell any of the structures to be
+forcefully encoded in DER during CER encoding, by specifying
+``der_forced=True`` attribute::
+
+    class Certificate(Sequence):
+        schema = (...)
+        der_forced = True
+
+    class SignedAttributes(SetOf):
+        schema = Attribute()
+        bounds = (1, 32)
+        der_forced = True
+
+agg_octet_string
+________________
+
+In most cases, huge quantity of binary data is stored as OCTET STRING.
+CER encoding splits it on 1 KB chunks. BER allows splitting on various
+levels of chunks inclusion::
+
+    SOME STRING[CONSTRUCTED]
+        OCTET STRING[CONSTRUCTED]
+            OCTET STRING[PRIMITIVE]
+                DATA CHUNK
+            OCTET STRING[PRIMITIVE]
+                DATA CHUNK
+            OCTET STRING[PRIMITIVE]
+                DATA CHUNK
+        OCTET STRING[PRIMITIVE]
+            DATA CHUNK
+        OCTET STRING[CONSTRUCTED]
+            OCTET STRING[PRIMITIVE]
+                DATA CHUNK
+            OCTET STRING[PRIMITIVE]
+                DATA CHUNK
+        OCTET STRING[CONSTRUCTED]
+            OCTET STRING[CONSTRUCTED]
+                OCTET STRING[PRIMITIVE]
+                    DATA CHUNK
+
+You can not just take the offset and some ``.vlen`` of the STRING and
+treat it as the payload. If you decode it without
+:ref:`evgen mode <evgen_mode>`, then it will be automatically aggregated
+and ``bytes()`` will give the whole payload contents.
+
+You are forced to use :ref:`evgen mode <evgen_mode>` for decoding for
+small memory footprint. There is convenient
+:py:func:`pyderasn.agg_octet_string` helper for reconstructing the
+payload. Let's assume you have got BER/CER encoded ``ContentInfo`` with
+huge ``SignedData`` and ``EncapsulatedContentInfo``. Let's calculate the
+SHA512 digest of its ``eContent``::
+
+    fd = open("data.p7m", "rb")
+    raw = file_mmaped(fd)
+    ctx = {"bered": True}
+    for decode_path, obj, _ in ContentInfo().decode_evgen(raw, ctx=ctx):
+        if decode_path == ("content",):
+            content = obj
+            break
+    else:
+        raise ValueError("no content found")
+    hasher_state = sha512()
+    def hasher(data):
+        hasher_state.update(data)
+        return len(data)
+    evgens = SignedData().decode_evgen(
+        raw[content.offset:],
+        offset=content.offset,
+        ctx=ctx,
+    )
+    agg_octet_string(evgens, ("encapContentInfo", "eContent"), raw, hasher)
+    fd.close()
+    digest = hasher_state.digest()
+
+Simply replace ``hasher`` with some writeable file's ``fd.write`` to
+copy the payload (without BER/CER encoding interleaved overhead) in it.
+Virtually it won't take memory more than for keeping small structures
+and 1 KB binary chunks.
+
+SEQUENCE OF iterators
+_____________________
+
+You can use iterators as a value in :py:class:`pyderasn.SequenceOf`
+classes. The only difference with providing the full list of objects, is
+that type and bounds checking is done during encoding process. Also
+sequence's value will be emptied after encoding, forcing you to set its
+value again.
+
+This is very useful when you have to create some huge objects, like
+CRLs, with thousands and millions of entities inside. You can write the
+generator taking necessary data from the database and giving the
+``RevokedCertificate`` objects. Only binary representation of that
+objects will take memory during DER encoding.
+
+Base Obj
+--------
+.. autoclass:: pyderasn.Obj
+   :members:
+
 Primitive types
 ---------------
 
@@ -428,12 +858,12 @@ _______
 Integer
 _______
 .. autoclass:: pyderasn.Integer
-   :members: __init__
+   :members: __init__, named
 
 BitString
 _________
 .. autoclass:: pyderasn.BitString
-   :members: __init__
+   :members: __init__, bit_len, named
 
 OctetString
 ___________
@@ -462,6 +892,11 @@ NumericString
 _____________
 .. autoclass:: pyderasn.NumericString
 
+PrintableString
+_______________
+.. autoclass:: pyderasn.PrintableString
+   :members: __init__, allow_asterisk, allow_ampersand
+
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
@@ -470,6 +905,7 @@ _______
 GeneralizedTime
 _______________
 .. autoclass:: pyderasn.GeneralizedTime
+   :members: __init__, todatetime
 
 Special types
 -------------
@@ -477,7 +913,7 @@ Special types
 Choice
 ______
 .. autoclass:: pyderasn.Choice
-   :members: __init__
+   :members: __init__, choice, value
 
 PrimitiveTypes
 ______________
@@ -515,16 +951,20 @@ Various
 -------
 
 .. autofunction:: pyderasn.abs_decode_path
+.. autofunction:: pyderasn.agg_octet_string
+.. autofunction:: pyderasn.colonize_hex
+.. autofunction:: pyderasn.encode_cer
+.. autofunction:: pyderasn.file_mmaped
 .. autofunction:: pyderasn.hexenc
 .. autofunction:: pyderasn.hexdec
 .. autofunction:: pyderasn.tag_encode
 .. autofunction:: pyderasn.tag_decode
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
-.. autoclass:: pyderasn.Obj
 .. autoclass:: pyderasn.DecodeError
    :members: __init__
 .. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.ExceedingData
 .. autoclass:: pyderasn.LenIndefForm
 .. autoclass:: pyderasn.TagMismatch
 .. autoclass:: pyderasn.InvalidLength
@@ -533,17 +973,134 @@ Various
 .. autoclass:: pyderasn.ObjNotReady
 .. autoclass:: pyderasn.InvalidValueType
 .. autoclass:: pyderasn.BoundsError
+
+.. _cmdline:
+
+Command-line usage
+------------------
+
+You can decode DER/BER files using command line abilities::
+
+    $ python -m pyderasn --schema tests.test_crts:Certificate path/to/file
+
+If there is no schema for your file, then you can try parsing it without,
+but of course IMPLICIT tags will often make it impossible. But result is
+good enough for the certificate above::
+
+    $ python -m pyderasn path/to/file
+        0   [1,3,1604]  . >: SEQUENCE OF
+        4   [1,3,1453]  . . >: SEQUENCE OF
+        8   [0,0,   5]  . . . . >: [0] ANY
+                        . . . . . A0:03:02:01:02
+       13   [1,1,   3]  . . . . >: INTEGER 61595
+       18   [1,1,  13]  . . . . >: SEQUENCE OF
+       20   [1,1,   9]  . . . . . . >: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+       31   [1,1,   0]  . . . . . . >: NULL
+       33   [1,3, 274]  . . . . >: SEQUENCE OF
+       37   [1,1,  11]  . . . . . . >: SET OF
+       39   [1,1,   9]  . . . . . . . . >: SEQUENCE OF
+       41   [1,1,   3]  . . . . . . . . . . >: OBJECT IDENTIFIER 2.5.4.6
+       46   [1,1,   2]  . . . . . . . . . . >: PrintableString PrintableString ES
+    [...]
+     1409   [1,1,  50]  . . . . . . >: SEQUENCE OF
+     1411   [1,1,   8]  . . . . . . . . >: OBJECT IDENTIFIER 1.3.6.1.5.5.7.1.1
+     1421   [1,1,  38]  . . . . . . . . >: OCTET STRING 38 bytes
+                        . . . . . . . . . 30:24:30:22:06:08:2B:06:01:05:05:07:30:01:86:16
+                        . . . . . . . . . 68:74:74:70:3A:2F:2F:6F:63:73:70:2E:69:70:73:63
+                        . . . . . . . . . 61:2E:63:6F:6D:2F
+     1461   [1,1,  13]  . . >: SEQUENCE OF
+     1463   [1,1,   9]  . . . . >: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+     1474   [1,1,   0]  . . . . >: NULL
+     1476   [1,2, 129]  . . >: BIT STRING 1024 bits
+                        . . . 68:EE:79:97:97:DD:3B:EF:16:6A:06:F2:14:9A:6E:CD
+                        . . . 9E:12:F7:AA:83:10:BD:D1:7C:98:FA:C7:AE:D4:0E:2C
+    [...]
+
+Human readable OIDs
+___________________
+
+If you have got dictionaries with ObjectIdentifiers, like example one
+from ``tests/test_crts.py``::
+
+    stroid2name = {
+        "1.2.840.113549.1.1.1": "id-rsaEncryption",
+        "1.2.840.113549.1.1.5": "id-sha1WithRSAEncryption",
+        [...]
+        "2.5.4.10": "id-at-organizationName",
+        "2.5.4.11": "id-at-organizationalUnitName",
+    }
+
+then you can pass it to pretty printer to see human readable OIDs::
+
+    $ python -m pyderasn --oids tests.test_crts:stroid2name path/to/file
+    [...]
+       37   [1,1,  11]  . . . . . . >: SET OF
+       39   [1,1,   9]  . . . . . . . . >: SEQUENCE OF
+       41   [1,1,   3]  . . . . . . . . . . >: OBJECT IDENTIFIER id-at-countryName (2.5.4.6)
+       46   [1,1,   2]  . . . . . . . . . . >: PrintableString PrintableString ES
+       50   [1,1,  18]  . . . . . . >: SET OF
+       52   [1,1,  16]  . . . . . . . . >: SEQUENCE OF
+       54   [1,1,   3]  . . . . . . . . . . >: OBJECT IDENTIFIER id-at-stateOrProvinceName (2.5.4.8)
+       59   [1,1,   9]  . . . . . . . . . . >: PrintableString PrintableString Barcelona
+       70   [1,1,  18]  . . . . . . >: SET OF
+       72   [1,1,  16]  . . . . . . . . >: SEQUENCE OF
+       74   [1,1,   3]  . . . . . . . . . . >: OBJECT IDENTIFIER id-at-localityName (2.5.4.7)
+       79   [1,1,   9]  . . . . . . . . . . >: PrintableString PrintableString Barcelona
+    [...]
+
+Decode paths
+____________
+
+Each decoded element has so-called decode path: sequence of structure
+names it is passing during the decode process. Each element has its own
+unique path inside the whole ASN.1 tree. You can print it out with
+``--print-decode-path`` option::
+
+    $ python -m pyderasn --schema path.to:Certificate --print-decode-path path/to/file
+       0    [1,3,1604]  Certificate SEQUENCE []
+       4    [1,3,1453]   . tbsCertificate: TBSCertificate SEQUENCE [tbsCertificate]
+      10-2  [1,1,   1]   . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL [tbsCertificate:version]
+      13    [1,1,   3]   . . serialNumber: CertificateSerialNumber INTEGER 61595 [tbsCertificate:serialNumber]
+      18    [1,1,  13]   . . signature: AlgorithmIdentifier SEQUENCE [tbsCertificate:signature]
+      20    [1,1,   9]   . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5 [tbsCertificate:signature:algorithm]
+      31    [0,0,   2]   . . . parameters: [UNIV 5] ANY OPTIONAL [tbsCertificate:signature:parameters]
+                         . . . . 05:00
+      33    [0,0, 278]   . . issuer: Name CHOICE rdnSequence [tbsCertificate:issuer]
+      33    [1,3, 274]   . . . rdnSequence: RDNSequence SEQUENCE OF [tbsCertificate:issuer:rdnSequence]
+      37    [1,1,  11]   . . . . 0: RelativeDistinguishedName SET OF [tbsCertificate:issuer:rdnSequence:0]
+      39    [1,1,   9]   . . . . . 0: AttributeTypeAndValue SEQUENCE [tbsCertificate:issuer:rdnSequence:0:0]
+      41    [1,1,   3]   . . . . . . type: AttributeType OBJECT IDENTIFIER 2.5.4.6 [tbsCertificate:issuer:rdnSequence:0:0:type]
+      46    [0,0,   4]   . . . . . . value: [UNIV 19] AttributeValue ANY [tbsCertificate:issuer:rdnSequence:0:0:value]
+                         . . . . . . . 13:02:45:53
+      46    [1,1,   2]   . . . . . . . DEFINED BY 2.5.4.6: CountryName PrintableString ES [tbsCertificate:issuer:rdnSequence:0:0:value:DEFINED BY 2.5.4.6]
+    [...]
+
+Now you can print only the specified tree, for example signature algorithm::
+
+    $ python -m pyderasn --schema path.to:Certificate --decode-path-only tbsCertificate:signature path/to/file
+      18    [1,1,  13]  AlgorithmIdentifier SEQUENCE
+      20    [1,1,   9]   . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+      31    [0,0,   2]   . parameters: [UNIV 5] ANY OPTIONAL
+                         . . 05:00
 """
 
+from array import array
 from codecs import getdecoder
 from codecs import getencoder
 from collections import namedtuple
 from collections import OrderedDict
+from copy import copy
 from datetime import datetime
+from datetime import timedelta
+from io import BytesIO
 from math import ceil
-from os import environ
+from mmap import mmap
+from mmap import PROT_READ
+from operator import attrgetter
 from string import ascii_letters
 from string import digits
+from sys import version_info
+from unicodedata import category as unicat
 
 from six import add_metaclass
 from six import binary_type
@@ -552,20 +1109,25 @@ from six import indexbytes
 from six import int2byte
 from six import integer_types
 from six import iterbytes
+from six import iteritems
+from six import itervalues
 from six import PY2
 from six import string_types
 from six import text_type
+from six import unichr as six_unichr
 from six.moves import xrange as six_xrange
 
 
 try:
     from termcolor import colored
 except ImportError:  # pragma: no cover
-    def colored(what, *args):
+    def colored(what, *args, **kwargs):
         return what
 
+__version__ = "7.0"
 
 __all__ = (
+    "agg_octet_string",
     "Any",
     "BitString",
     "BMPString",
@@ -574,7 +1136,10 @@ __all__ = (
     "Choice",
     "DecodeError",
     "DecodePathDefBy",
+    "encode_cer",
     "Enumerated",
+    "ExceedingData",
+    "file_mmaped",
     "GeneralizedTime",
     "GeneralString",
     "GraphicString",
@@ -636,6 +1201,42 @@ EOC = b"\x00\x00"
 EOC_LEN = len(EOC)
 LENINDEF = b"\x80"  # length indefinite mark
 LENINDEF_PP_CHAR = "I" if PY2 else "∞"
+NAMEDTUPLE_KWARGS = {} if version_info < (3, 6) else {"module": __name__}
+SET01 = frozenset("01")
+DECIMALS = frozenset(digits)
+DECIMAL_SIGNS = ".,"
+NEXT_ATTR_NAME = "next" if PY2 else "__next__"
+
+
+def file_mmaped(fd):
+    """Make mmap-ed memoryview for reading from file
+
+    :param fd: file object
+    :returns: memoryview over read-only mmap-ing of the whole file
+    """
+    return memoryview(mmap(fd.fileno(), 0, prot=PROT_READ))
+
+def pureint(value):
+    if not set(value) <= DECIMALS:
+        raise ValueError("non-pure integer")
+    return int(value)
+
+def fractions2float(fractions_raw):
+    pureint(fractions_raw)
+    return float("0." + fractions_raw)
+
+
+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 (not p1 is any) and (p1 != p2):
+                break
+        else:
+            return define
 
 
 ########################################################################
@@ -645,6 +1246,7 @@ LENINDEF_PP_CHAR = "I" if PY2 else "∞"
 class ASN1Error(ValueError):
     pass
 
+
 class DecodeError(ASN1Error):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
         """
@@ -684,6 +1286,18 @@ class NotEnoughData(DecodeError):
     pass
 
 
+class ExceedingData(ASN1Error):
+    def __init__(self, nbytes):
+        super(ExceedingData, self).__init__()
+        self.nbytes = nbytes
+
+    def __str__(self):
+        return "%d trailing bytes" % self.nbytes
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, self)
+
+
 class LenIndefForm(DecodeError):
     pass
 
@@ -904,14 +1518,51 @@ def len_decode(data):
     return l, 1 + octets_num, data[1 + octets_num:]
 
 
+LEN1K = len_encode(1000)
+
+
+def write_full(writer, data):
+    """Fully write provided data
+
+    :param writer: must comply with ``io.RawIOBase.write`` behaviour
+
+    BytesIO does not guarantee that the whole data will be written at
+    once. That function write everything provided, raising an error if
+    ``writer`` returns None.
+    """
+    data = memoryview(data)
+    written = 0
+    while written != len(data):
+        n = writer(data[written:])
+        if n is None:
+            raise ValueError("can not write to buf")
+        written += n
+
+
 ########################################################################
 # Base class
 ########################################################################
 
 class AutoAddSlots(type):
-    def __new__(mcs, name, bases, _dict):
+    def __new__(cls, name, bases, _dict):
         _dict["__slots__"] = _dict.get("__slots__", ())
-        return type.__new__(mcs, name, bases, _dict)
+        return type.__new__(cls, name, bases, _dict)
+
+
+BasicState = namedtuple("BasicState", (
+    "version",
+    "tag",
+    "tag_order",
+    "expl",
+    "default",
+    "optional",
+    "offset",
+    "llen",
+    "vlen",
+    "expl_lenindef",
+    "lenindef",
+    "ber_encoded",
+), **NAMEDTUPLE_KWARGS)
 
 
 @add_metaclass(AutoAddSlots)
@@ -923,6 +1574,7 @@ class Obj(object):
     """
     __slots__ = (
         "tag",
+        "_tag_order",
         "_value",
         "_expl",
         "default",
@@ -947,6 +1599,13 @@ class Obj(object):
         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")
+        if self.tag is None:
+            self._tag_order = None
+        else:
+            tag_class, _, tag_num = tag_decode(
+                self.tag if self._expl is None else self._expl
+            )
+            self._tag_order = (tag_class, tag_num)
         if default is not None:
             optional = True
         self.optional = optional
@@ -978,17 +1637,46 @@ class Obj(object):
         """
         return (self.llen + self.vlen) > 0
 
-    def copy(self):  # pragma: no cover
-        """Make a copy of object, safe to be mutated
+    def __getstate__(self):  # pragma: no cover
+        """Used for making safe to be mutable pickleable copies
         """
         raise NotImplementedError()
 
+    def __setstate__(self, state):
+        if state.version != __version__:
+            raise ValueError("data is pickled by different PyDERASN version")
+        self.tag = state.tag
+        self._tag_order = state.tag_order
+        self._expl = state.expl
+        self.default = state.default
+        self.optional = state.optional
+        self.offset = state.offset
+        self.llen = state.llen
+        self.vlen = state.vlen
+        self.expl_lenindef = state.expl_lenindef
+        self.lenindef = state.lenindef
+        self.ber_encoded = state.ber_encoded
+
+    @property
+    def tag_order(self):
+        """Tag's (class, number) used for DER/CER sorting
+        """
+        return self._tag_order
+
+    @property
+    def tag_order_cer(self):
+        return self.tag_order
+
     @property
     def tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self.tag)
 
     @property
     def tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
@@ -1009,15 +1697,44 @@ class Obj(object):
     def _encode(self):  # pragma: no cover
         raise NotImplementedError()
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):  # pragma: no cover
-        raise NotImplementedError()
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):  # pragma: no cover
+        yield NotImplemented
 
     def encode(self):
+        """DER encode the structure
+
+        :returns: DER representation
+        """
         raw = self._encode()
         if self._expl is None:
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
+    def encode_cer(self, writer):
+        """CER encode the structure to specified writer
+
+        :param writer: must comply with ``io.RawIOBase.write``
+                       behaviour. It takes slice to be written and
+                       returns number of bytes processed. If it returns
+                       None, then exception will be raised
+        """
+        if self._expl is not None:
+            write_full(writer, self._expl + LENINDEF)
+        if getattr(self, "der_forced", False):
+            write_full(writer, self._encode())
+        else:
+            self._encode_cer(writer)
+        if self._expl is not None:
+            write_full(writer, EOC)
+
+    def _encode_cer(self, writer):
+        write_full(writer, self._encode())
+
+    def hexencode(self):
+        """Do hexadecimal encoded :py:meth:`pyderasn.Obj.encode`
+        """
+        return hexenc(self.encode())
+
     def decode(
             self,
             data,
@@ -1026,6 +1743,7 @@ class Obj(object):
             decode_path=(),
             ctx=None,
             tag_only=False,
+            _ctx_immutable=True,
     ):
         """Decode the data
 
@@ -1033,26 +1751,77 @@ class Obj(object):
         :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)
+        :param decode_path: current decode path (tuples of strings,
+                            possibly with DecodePathDefBy) with will be
+                            the root for all underlying objects
+        :param ctx: optional :ref:`context <ctx>` governing decoding process
+        :param bool tag_only: decode only the tag, without length and
+                              contents (used only in Choice and Set
+                              structures, trying to determine if tag satisfies
+                              the schema)
+        :param bool _ctx_immutable: do we need to ``copy.copy()`` ``ctx``
+                                    before using it?
         :returns: (Obj, remaining data)
+
+        .. seealso:: :ref:`decoding`
+        """
+        result = next(self.decode_evgen(
+            data,
+            offset,
+            leavemm,
+            decode_path,
+            ctx,
+            tag_only,
+            _ctx_immutable,
+            _evgen_mode=False,
+        ))
+        if result is None:
+            return None
+        _, obj, tail = result
+        return obj, tail
+
+    def decode_evgen(
+            self,
+            data,
+            offset=0,
+            leavemm=False,
+            decode_path=(),
+            ctx=None,
+            tag_only=False,
+            _ctx_immutable=True,
+            _evgen_mode=True,
+    ):
+        """Decode with evgen mode on
+
+        That method is identical to :py:meth:`pyderasn.Obj.decode`, but
+        it returns the generator producing ``(decode_path, obj, tail)``
+        values. See :ref:`evgen mode <evgen_mode>`.
         """
         if ctx is None:
             ctx = {}
+        elif _ctx_immutable:
+            ctx = copy(ctx)
         tlv = memoryview(data)
+        if (
+                _evgen_mode and
+                get_def_by_path(ctx.get("evgen_mode_upto", ()), decode_path) is not None
+        ):
+            _evgen_mode = False
         if self._expl is None:
-            result = self._decode(
-                tlv,
-                offset,
-                decode_path=decode_path,
-                ctx=ctx,
-                tag_only=tag_only,
-            )
-            if tag_only:
-                return
-            obj, tail = result
+            for result in self._decode(
+                    tlv,
+                    offset=offset,
+                    decode_path=decode_path,
+                    ctx=ctx,
+                    tag_only=tag_only,
+                    evgen_mode=_evgen_mode,
+            ):
+                if tag_only:
+                    yield None
+                    return
+                _decode_path, obj, tail = result
+                if not _decode_path is decode_path:
+                    yield result
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
@@ -1081,16 +1850,20 @@ class Obj(object):
                     )
                 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
+                for result in self._decode(
+                        v,
+                        offset=offset,
+                        decode_path=decode_path,
+                        ctx=ctx,
+                        tag_only=tag_only,
+                        evgen_mode=_evgen_mode,
+                ):
+                    if tag_only:  # pragma: no cover
+                        yield None
+                        return
+                    _decode_path, obj, tail = result
+                    if not _decode_path is decode_path:
+                        yield result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
                     raise DecodeError(
@@ -1116,16 +1889,20 @@ class Obj(object):
                         decode_path=decode_path,
                         offset=offset,
                     )
-                result = self._decode(
-                    v,
-                    offset=offset + tlen + llen,
-                    decode_path=decode_path,
-                    ctx=ctx,
-                    tag_only=tag_only,
-                )
-                if tag_only:  # pragma: no cover
-                    return
-                obj, tail = result
+                for result in self._decode(
+                        v,
+                        offset=offset + tlen + llen,
+                        decode_path=decode_path,
+                        ctx=ctx,
+                        tag_only=tag_only,
+                        evgen_mode=_evgen_mode,
+                ):
+                    if tag_only:  # pragma: no cover
+                        yield None
+                        return
+                    _decode_path, obj, tail = result
+                    if not _decode_path is decode_path:
+                        yield result
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
                     raise DecodeError(
                         "explicit tag out-of-bound, longer than data",
@@ -1133,50 +1910,98 @@ class Obj(object):
                         decode_path=decode_path,
                         offset=offset,
                     )
-        return obj, (tail if leavemm else tail.tobytes())
+        yield decode_path, obj, (tail if leavemm else tail.tobytes())
+
+    def decod(self, data, offset=0, decode_path=(), ctx=None):
+        """Decode the data, check that tail is empty
+
+        :raises ExceedingData: if tail is not empty
+
+        This is just a wrapper over :py:meth:`pyderasn.Obj.decode`
+        (decode without tail) that also checks that there is no
+        trailing data left.
+        """
+        obj, tail = self.decode(
+            data,
+            offset=offset,
+            decode_path=decode_path,
+            ctx=ctx,
+            leavemm=True,
+        )
+        if len(tail) > 0:
+            raise ExceedingData(len(tail))
+        return obj
+
+    def hexdecode(self, data, *args, **kwargs):
+        """Do :py:meth:`pyderasn.Obj.decode` with hexadecimal decoded data
+        """
+        return self.decode(hexdec(data), *args, **kwargs)
+
+    def hexdecod(self, data, *args, **kwargs):
+        """Do :py:meth:`pyderasn.Obj.decod` with hexadecimal decoded data
+        """
+        return self.decod(hexdec(data), *args, **kwargs)
 
     @property
     def expled(self):
+        """See :ref:`decoding`
+        """
         return self._expl is not None
 
     @property
     def expl_tag(self):
+        """See :ref:`decoding`
+        """
         return self._expl
 
     @property
     def expl_tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self._expl)
 
     @property
     def expl_llen(self):
+        """See :ref:`decoding`
+        """
         if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
+        """See :ref:`decoding`
+        """
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     @property
     def fulloffset(self):
+        """See :ref:`decoding`
+        """
         return self.expl_offset if self.expled else self.offset
 
     @property
     def fulllen(self):
+        """See :ref:`decoding`
+        """
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
         if self.lenindef and not (
-            getattr(self, "defined", None) is not None and
-            self.defined[1].lenindef
+                getattr(self, "defined", None) is not None and
+                self.defined[1].lenindef
         ):
             yield _pp(
                 asn1_type_name="EOC",
@@ -1206,6 +2031,16 @@ class Obj(object):
             )
 
 
+def encode_cer(obj):
+    """Encode to CER in memory buffer
+
+    :returns bytes: memory buffer contents
+    """
+    buf = BytesIO()
+    obj.encode_cer(buf.write)
+    return buf.getvalue()
+
+
 class DecodePathDefBy(object):
     """DEFINED BY representation inside decode path
     """
@@ -1234,6 +2069,7 @@ class DecodePathDefBy(object):
 ########################################################################
 
 PP = namedtuple("PP", (
+    "obj",
     "asn1_type_name",
     "obj_name",
     "decode_path",
@@ -1255,10 +2091,11 @@ PP = namedtuple("PP", (
     "lenindef",
     "ber_encoded",
     "bered",
-))
+), **NAMEDTUPLE_KWARGS)
 
 
 def _pp(
+        obj=None,
         asn1_type_name="unknown",
         obj_name="unknown",
         decode_path=(),
@@ -1282,6 +2119,7 @@ def _pp(
         bered=False,
 ):
     return PP(
+        obj,
         asn1_type_name,
         obj_name,
         decode_path,
@@ -1310,9 +2148,15 @@ def _colourize(what, colour, with_colours, attrs=("bold",)):
     return colored(what, colour, attrs=attrs) if with_colours else what
 
 
+def colonize_hex(hexed):
+    """Separate hexadecimal string with colons
+    """
+    return ":".join(hexed[i:i + 2] for i in six_xrange(0, len(hexed), 2))
+
+
 def pp_console_row(
         pp,
-        oids=None,
+        oid_maps=(),
         with_offsets=False,
         with_blob=True,
         with_colours=False,
@@ -1347,14 +2191,18 @@ def pp_console_row(
         if isinstance(ent, DecodePathDefBy):
             cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
             value = str(ent.defined_by)
+            oid_name = None
             if (
-                    oids is not None and
+                    len(oid_maps) > 0 and
                     ent.defined_by.asn1_type_name ==
-                    ObjectIdentifier.asn1_type_name and
-                    value in oids
+                    ObjectIdentifier.asn1_type_name
             ):
-                cols.append(_colourize("%s:" % oids[value], "green", with_colours))
-            else:
+                for oid_map in oid_maps:
+                    oid_name = oid_map.get(value)
+                    if oid_name is not None:
+                        cols.append(_colourize("%s:" % oid_name, "green", with_colours))
+                        break
+            if oid_name is None:
                 cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
         else:
             cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
@@ -1375,15 +2223,27 @@ def pp_console_row(
         value = pp.value
         cols.append(_colourize(value, "white", with_colours, ("reverse",)))
         if (
-                oids is not None and
-                pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
-                value in oids
+                len(oid_maps) > 0 and
+                pp.asn1_type_name == ObjectIdentifier.asn1_type_name
         ):
-            cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
+            for oid_map in oid_maps:
+                oid_name = oid_map.get(value)
+                if oid_name is not None:
+                    cols.append(_colourize("(%s)" % oid_name, "green", with_colours))
+                    break
+        if pp.asn1_type_name == Integer.asn1_type_name:
+            hex_repr = hex(int(pp.obj._value))[2:].upper()
+            if len(hex_repr) % 2 != 0:
+                hex_repr = "0" + hex_repr
+            cols.append(_colourize(
+                "(%s)" % colonize_hex(hex_repr),
+                "green",
+                with_colours,
+            ))
     if with_blob:
-        if isinstance(pp.blob, binary_type):
+        if pp.blob.__class__ == binary_type:
             cols.append(hexenc(pp.blob))
-        elif isinstance(pp.blob, tuple):
+        elif pp.blob.__class__ == tuple:
             cols.append(", ".join(pp.blob))
     if pp.optional:
         cols.append(_colourize("OPTIONAL", "red", with_colours))
@@ -1403,30 +2263,29 @@ def pp_console_blob(pp, decode_path_len_decrease=0):
     decode_path_len = len(pp.decode_path) - decode_path_len_decrease
     if decode_path_len > 0:
         cols.append(" ." * (decode_path_len + 1))
-    if isinstance(pp.blob, binary_type):
+    if pp.blob.__class__ == binary_type:
         blob = hexenc(pp.blob).upper()
-        for i in range(0, len(blob), 32):
+        for i in six_xrange(0, len(blob), 32):
             chunk = blob[i:i + 32]
-            yield " ".join(cols + [":".join(
-                chunk[j:j + 2] for j in range(0, len(chunk), 2)
-            )])
-    elif isinstance(pp.blob, tuple):
+            yield " ".join(cols + [colonize_hex(chunk)])
+    elif pp.blob.__class__ == tuple:
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
 def pprint(
         obj,
-        oids=None,
+        oid_maps=(),
         big_blobs=False,
         with_colours=False,
         with_decode_path=False,
         decode_path_only=(),
+        decode_path=(),
 ):
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
-    :param oids: ``OID <-> humand readable string`` dictionary. When OID
-                 from it is met, then its humand readable form is printed
+    :param oid_maps: list of ``str(OID) <-> human readable string`` dictionary.
+                     Its human readable form is printed when OID is met
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
@@ -1439,16 +2298,16 @@ def pprint(
         for pp in pps:
             if hasattr(pp, "_fields"):
                 if (
-                    decode_path_only != () and
-                    tuple(
-                        str(p) for p in pp.decode_path[:len(decode_path_only)]
-                    ) != decode_path_only
+                        decode_path_only != () and
+                        tuple(
+                            str(p) for p in pp.decode_path[:len(decode_path_only)]
+                        ) != decode_path_only
                 ):
                     continue
                 if big_blobs:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
@@ -1456,14 +2315,14 @@ def pprint(
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
-                        pp,
-                        decode_path_len_decrease=len(decode_path_only),
+                            pp,
+                            decode_path_len_decrease=len(decode_path_only),
                     ):
                         yield row
                 else:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=True,
                         with_colours=with_colours,
@@ -1473,14 +2332,21 @@ def pprint(
             else:
                 for row in _pprint_pps(pp):
                     yield row
-    return "\n".join(_pprint_pps(obj.pps()))
+    return "\n".join(_pprint_pps(obj.pps(decode_path)))
 
 
 ########################################################################
 # ASN.1 primitive types
 ########################################################################
 
-class Boolean(Obj):
+BooleanState = namedtuple(
+    "BooleanState",
+    BasicState._fields + ("value",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
+class Boolean(Obj):
     """``BOOLEAN`` boolean type
 
     >>> b = Boolean(True)
@@ -1524,27 +2390,36 @@ class Boolean(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
+        if value.__class__ == bool:
+            return value
         if issubclass(value.__class__, Boolean):
             return value._value
-        if isinstance(value, bool):
-            return value
         raise InvalidValueType((self.__class__, bool))
 
     @property
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    def __getstate__(self):
+        return BooleanState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self._value,
+        )
+
+    def __setstate__(self, state):
+        super(Boolean, self).__setstate__(state)
+        self._value = state.value
 
     def __nonzero__(self):
         self._assert_ready()
@@ -1555,7 +2430,7 @@ class Boolean(Obj):
         return self._value
 
     def __eq__(self, their):
-        if isinstance(their, bool):
+        if their.__class__ == bool:
             return self._value == their
         if not issubclass(their.__class__, Boolean):
             return False
@@ -1589,7 +2464,7 @@ class Boolean(Obj):
             (b"\xFF" if self._value else b"\x00"),
         ))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1606,6 +2481,7 @@ class Boolean(Obj):
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         try:
             l, _, v = len_decode(lv)
@@ -1655,13 +2531,14 @@ class Boolean(Obj):
             _decoded=(offset, 1, 1),
         )
         obj.ber_encoded = ber_encoded
-        return obj, v[1:]
+        yield decode_path, obj, v[1:]
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1686,6 +2563,13 @@ class Boolean(Obj):
             yield pp
 
 
+IntegerState = namedtuple(
+    "IntegerState",
+    BasicState._fields + ("specs", "value", "bound_min", "bound_max"),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Integer(Obj):
     """``INTEGER`` integer type
 
@@ -1749,7 +2633,7 @@ class Integer(Obj):
         super(Integer, self).__init__(impl, expl, default, optional, _decoded)
         self._value = value
         specs = getattr(self, "schema", {}) if _specs is None else _specs
-        self.specs = specs if isinstance(specs, dict) else dict(specs)
+        self.specs = specs if specs.__class__ == dict else dict(specs)
         self._bound_min, self._bound_max = getattr(
             self,
             "bounds",
@@ -1769,11 +2653,11 @@ class Integer(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, Integer):
-            value = value._value
-        elif isinstance(value, integer_types):
+        if isinstance(value, integer_types):
             pass
-        elif isinstance(value, str):
+        elif issubclass(value.__class__, Integer):
+            value = value._value
+        elif value.__class__ == str:
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
@@ -1787,19 +2671,32 @@ class Integer(Obj):
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__(_specs=self.specs)
-        obj._value = self._value
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    def __getstate__(self):
+        return IntegerState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self.specs,
+            self._value,
+            self._bound_min,
+            self._bound_max,
+        )
+
+    def __setstate__(self, state):
+        super(Integer, self).__setstate__(state)
+        self.specs = state.specs
+        self._value = state.value
+        self._bound_min = state.bound_min
+        self._bound_max = state.bound_max
 
     def __int__(self):
         self._assert_ready()
@@ -1829,9 +2726,12 @@ class Integer(Obj):
 
     @property
     def named(self):
-        for name, value in self.specs.items():
+        """Return named representation (if exists) of the value
+        """
+        for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
+        return None
 
     def __call__(
             self,
@@ -1894,7 +2794,7 @@ class Integer(Obj):
                     break
         return b"".join((self.tag, len_encode(len(octets)), octets))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1911,6 +2811,7 @@ class Integer(Obj):
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         try:
             l, llen, v = len_decode(lv)
@@ -1982,13 +2883,14 @@ class Integer(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        return obj, tail
+        yield decode_path, obj, tail
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2012,6 +2914,13 @@ class Integer(Obj):
             yield pp
 
 
+BitStringState = namedtuple(
+    "BitStringState",
+    BasicState._fields + ("specs", "value", "tag_constructed", "defined"),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class BitString(Obj):
     """``BIT STRING`` bit string type
 
@@ -2087,7 +2996,7 @@ class BitString(Obj):
         """
         super(BitString, self).__init__(impl, expl, default, optional, _decoded)
         specs = getattr(self, "schema", {}) if _specs is None else _specs
-        self.specs = specs if isinstance(specs, dict) else dict(specs)
+        self.specs = specs if specs.__class__ == dict else dict(specs)
         self._value = None if value is None else self._value_sanitize(value)
         if default is not None:
             default = self._value_sanitize(default)
@@ -2117,8 +3026,6 @@ class BitString(Obj):
         return bit_len, bytes(octets)
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, BitString):
-            return value._value
         if isinstance(value, (string_types, binary_type)):
             if (
                     isinstance(value, string_types) and
@@ -2126,24 +3033,23 @@ class BitString(Obj):
             ):
                 if value.endswith("'B"):
                     value = value[1:-2]
-                    if not set(value) <= set(("0", "1")):
+                    if not frozenset(value) <= SET01:
                         raise ValueError("B's coding contains unacceptable chars")
                     return self._bits2octets(value)
-                elif value.endswith("'H"):
+                if value.endswith("'H"):
                     value = value[1:-2]
                     return (
                         len(value) * 4,
                         hexdec(value + ("" if len(value) % 2 == 0 else "0")),
                     )
-            if isinstance(value, binary_type):
+            if value.__class__ == binary_type:
                 return (len(value) * 8, value)
-            else:
-                raise InvalidValueType((self.__class__, string_types, binary_type))
-        if isinstance(value, tuple):
+            raise InvalidValueType((self.__class__, string_types, binary_type))
+        if value.__class__ == tuple:
             if (
                     len(value) == 2 and
                     isinstance(value[0], integer_types) and
-                    isinstance(value[1], binary_type)
+                    value[1].__class__ == binary_type
             ):
                 return value
             bits = []
@@ -2154,31 +3060,45 @@ class BitString(Obj):
                 bits.append(bit)
             if len(bits) == 0:
                 return self._bits2octets("")
-            bits = set(bits)
+            bits = frozenset(bits)
             return self._bits2octets("".join(
                 ("1" if bit in bits else "0")
                 for bit in six_xrange(max(bits) + 1)
             ))
+        if issubclass(value.__class__, BitString):
+            return value._value
         raise InvalidValueType((self.__class__, binary_type, string_types))
 
     @property
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__(_specs=self.specs)
-        value = self._value
-        if value is not None:
-            value = (value[0], value[1])
-        obj._value = value
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    def __getstate__(self):
+        return BitStringState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self.specs,
+            self._value,
+            self.tag_constructed,
+            self.defined,
+        )
+
+    def __setstate__(self, state):
+        super(BitString, self).__setstate__(state)
+        self.specs = state.specs
+        self._value = state.value
+        self.tag_constructed = state.tag_constructed
+        self.defined = state.defined
 
     def __iter__(self):
         self._assert_ready()
@@ -2187,6 +3107,8 @@ class BitString(Obj):
 
     @property
     def bit_len(self):
+        """Returns number of bits in the string
+        """
         self._assert_ready()
         return self._value[0]
 
@@ -2195,7 +3117,7 @@ class BitString(Obj):
         return self._value[1]
 
     def __eq__(self, their):
-        if isinstance(their, bytes):
+        if their.__class__ == bytes:
             return self._value[1] == their
         if not issubclass(their.__class__, BitString):
             return False
@@ -2207,7 +3129,11 @@ class BitString(Obj):
 
     @property
     def named(self):
-        return [name for name, bit in self.specs.items() if self[bit]]
+        """Named representation (if exists) of the bits
+
+        :returns: [str(name), ...]
+        """
+        return [name for name, bit in iteritems(self.specs) if self[bit]]
 
     def __call__(
             self,
@@ -2227,7 +3153,7 @@ class BitString(Obj):
         )
 
     def __getitem__(self, key):
-        if isinstance(key, int):
+        if key.__class__ == int:
             bit_len, octets = self._value
             if key >= bit_len:
                 return False
@@ -2252,65 +3178,31 @@ class BitString(Obj):
             octets,
         ))
 
-    def _decode_chunk(self, lv, offset, decode_path, ctx):
-        try:
-            l, llen, v = len_decode(lv)
-        except DecodeError as err:
-            raise err.__class__(
-                msg=err.msg,
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if l > len(v):
-            raise NotEnoughData(
-                "encoded length is longer than data",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if l == 0:
-            raise NotEnoughData(
-                "zero length",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        pad_size = byte2int(v)
-        if l == 1 and pad_size != 0:
-            raise DecodeError(
-                "invalid empty value",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if pad_size > 7:
-            raise DecodeError(
-                "too big pad",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if byte2int(v[l - 1:l]) & ((1 << pad_size) - 1) != 0:
-            raise DecodeError(
-                "invalid pad",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        v, tail = v[:l], v[l:]
-        obj = self.__class__(
-            value=((len(v) - 1) * 8 - pad_size, v[1:].tobytes()),
-            impl=self.tag,
-            expl=self._expl,
-            default=self.default,
-            optional=self.optional,
-            _specs=self.specs,
-            _decoded=(offset, llen, l),
-        )
-        return obj, tail
-
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode_cer(self, writer):
+        bit_len, octets = self._value
+        if len(octets) + 1 <= 1000:
+            write_full(writer, self._encode())
+            return
+        write_full(writer, self.tag_constructed)
+        write_full(writer, LENINDEF)
+        for offset in six_xrange(0, (len(octets) // 999) * 999, 999):
+            write_full(writer, b"".join((
+                BitString.tag_default,
+                LEN1K,
+                int2byte(0),
+                octets[offset:offset + 999],
+            )))
+        tail = octets[offset+999:]
+        if len(tail) > 0:
+            tail = int2byte((8 - bit_len % 8) % 8) + tail
+            write_full(writer, b"".join((
+                BitString.tag_default,
+                len_encode(len(tail)),
+                tail,
+            )))
+        write_full(writer, EOC)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2322,24 +3214,10 @@ class BitString(Obj):
             )
         if t == self.tag:
             if tag_only:  # pragma: no cover
+                yield None
                 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,
@@ -2354,89 +3232,181 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if not lenindef and l == 0:
+            if l == 0:
                 raise NotEnoughData(
                     "zero length",
                     klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
-            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:
+            pad_size = byte2int(v)
+            if l == 1 and pad_size != 0:
                 raise DecodeError(
-                    "no chunks",
+                    "invalid empty value",
                     klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
-            values = []
-            bit_len = 0
-            for chunk_i, chunk in enumerate(chunks[:-1]):
-                if chunk.bit_len % 8 != 0:
-                    raise DecodeError(
-                        "BitString chunk is not multiple of 8 bits",
-                        klass=self.__class__,
-                        decode_path=decode_path + (str(chunk_i),),
-                        offset=chunk.offset,
-                    )
-                values.append(bytes(chunk))
-                bit_len += chunk.bit_len
-            chunk_last = chunks[-1]
-            values.append(bytes(chunk_last))
-            bit_len += chunk_last.bit_len
+            if pad_size > 7:
+                raise DecodeError(
+                    "too big pad",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            if byte2int(v[l - 1:l]) & ((1 << pad_size) - 1) != 0:
+                raise DecodeError(
+                    "invalid pad",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            v, tail = v[:l], v[l:]
+            bit_len = (len(v) - 1) * 8 - pad_size
             obj = self.__class__(
-                value=(bit_len, b"".join(values)),
+                value=None if evgen_mode else (bit_len, v[1:].tobytes()),
                 impl=self.tag,
                 expl=self._expl,
                 default=self.default,
                 optional=self.optional,
                 _specs=self.specs,
-                _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+                _decoded=(offset, llen, l),
             )
-            obj.lenindef = lenindef
-            obj.ber_encoded = True
-            return obj, (v[EOC_LEN:] if lenindef else v)
-        raise TagMismatch(
-            klass=self.__class__,
-            decode_path=decode_path,
-            offset=offset,
+            if evgen_mode:
+                obj._value = (bit_len, None)
+            yield decode_path, obj, tail
+            return
+        if t != self.tag_constructed:
+            raise TagMismatch(
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if not ctx.get("bered", False):
+            raise DecodeError(
+                "unallowed BER constructed encoding",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if tag_only:  # pragma: no cover
+            yield None
+            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:
+                if evgen_mode:
+                    for _decode_path, chunk, v_tail in BitString().decode_evgen(
+                            v,
+                            offset=sub_offset,
+                            decode_path=sub_decode_path,
+                            leavemm=True,
+                            ctx=ctx,
+                            _ctx_immutable=False,
+                    ):
+                        yield _decode_path, chunk, v_tail
+                else:
+                    _, chunk, v_tail = next(BitString().decode_evgen(
+                        v,
+                        offset=sub_offset,
+                        decode_path=sub_decode_path,
+                        leavemm=True,
+                        ctx=ctx,
+                        _ctx_immutable=False,
+                        _evgen_mode=False,
+                    ))
+            except TagMismatch:
+                raise DecodeError(
+                    "expected BitString encoded chunk",
+                    klass=self.__class__,
+                    decode_path=sub_decode_path,
+                    offset=sub_offset,
+                )
+            chunks.append(chunk)
+            sub_offset += chunk.tlvlen
+            vlen += chunk.tlvlen
+            v = v_tail
+        if len(chunks) == 0:
+            raise DecodeError(
+                "no chunks",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        values = []
+        bit_len = 0
+        for chunk_i, chunk in enumerate(chunks[:-1]):
+            if chunk.bit_len % 8 != 0:
+                raise DecodeError(
+                    "BitString chunk is not multiple of 8 bits",
+                    klass=self.__class__,
+                    decode_path=decode_path + (str(chunk_i),),
+                    offset=chunk.offset,
+                )
+            if not evgen_mode:
+                values.append(bytes(chunk))
+            bit_len += chunk.bit_len
+        chunk_last = chunks[-1]
+        if not evgen_mode:
+            values.append(bytes(chunk_last))
+        bit_len += chunk_last.bit_len
+        obj = self.__class__(
+            value=None if evgen_mode else (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)),
         )
+        if evgen_mode:
+            obj._value = (bit_len, None)
+        obj.lenindef = lenindef
+        obj.ber_encoded = True
+        yield decode_path, obj, (v[EOC_LEN:] if lenindef else v)
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -2447,9 +3417,10 @@ class BitString(Obj):
         if self.ready:
             bit_len, blob = self._value
             value = "%d bits" % bit_len
-            if len(self.specs) > 0:
+            if len(self.specs) > 0 and blob is not None:
                 blob = tuple(self.named)
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2481,6 +3452,19 @@ class BitString(Obj):
             yield pp
 
 
+OctetStringState = namedtuple(
+    "OctetStringState",
+    BasicState._fields + (
+        "value",
+        "bound_min",
+        "bound_max",
+        "tag_constructed",
+        "defined",
+    ),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class OctetString(Obj):
     """``OCTET STRING`` binary string type
 
@@ -2497,17 +3481,15 @@ class OctetString(Obj):
     >>> 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.
+    Memoryviews can be used as a values. If memoryview is made on
+    mmap-ed file, then it does not take storage inside OctetString
+    itself. In CER encoding mode it will be streamed to the specified
+    writer, copying 1 KB chunks.
     """
     __slots__ = ("tag_constructed", "_bound_min", "_bound_max", "defined")
     tag_default = tag_encode(4)
     asn1_type_name = "OCTET STRING"
+    evgen_mode_skip_value = True
 
     def __init__(
             self,
@@ -2518,6 +3500,7 @@ class OctetString(Obj):
             default=None,
             optional=False,
             _decoded=(0, 0, 0),
+            ctx=None,
     ):
         """
         :param value: set the value. Either binary type, or
@@ -2529,13 +3512,7 @@ class OctetString(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(OctetString, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super(OctetString, self).__init__(impl, expl, default, optional, _decoded)
         self._value = value
         self._bound_min, self._bound_max = getattr(
             self,
@@ -2562,12 +3539,12 @@ class OctetString(Obj):
         )
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, OctetString):
-            value = value._value
-        elif isinstance(value, binary_type):
+        if value.__class__ == binary_type or value.__class__ == memoryview:
             pass
+        elif issubclass(value.__class__, OctetString):
+            value = value._value
         else:
-            raise InvalidValueType((self.__class__, bytes))
+            raise InvalidValueType((self.__class__, bytes, memoryview))
         if not self._bound_min <= len(value) <= self._bound_max:
             raise BoundsError(self._bound_min, len(value), self._bound_max)
         return value
@@ -2576,26 +3553,41 @@ class OctetString(Obj):
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    def __getstate__(self):
+        return OctetStringState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self._value,
+            self._bound_min,
+            self._bound_max,
+            self.tag_constructed,
+            self.defined,
+        )
+
+    def __setstate__(self, state):
+        super(OctetString, self).__setstate__(state)
+        self._value = state.value
+        self._bound_min = state.bound_min
+        self._bound_max = state.bound_max
+        self.tag_constructed = state.tag_constructed
+        self.defined = state.defined
 
     def __bytes__(self):
         self._assert_ready()
-        return self._value
+        return bytes(self._value)
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             return self._value == their
         if not issubclass(their.__class__, OctetString):
             return False
@@ -2637,53 +3629,31 @@ class OctetString(Obj):
             self._value,
         ))
 
-    def _decode_chunk(self, lv, offset, decode_path, ctx):
+    def _encode_cer(self, writer):
+        octets = self._value
+        if len(octets) <= 1000:
+            write_full(writer, self._encode())
+            return
+        write_full(writer, self.tag_constructed)
+        write_full(writer, LENINDEF)
+        for offset in six_xrange(0, (len(octets) // 1000) * 1000, 1000):
+            write_full(writer, b"".join((
+                OctetString.tag_default,
+                LEN1K,
+                octets[offset:offset + 1000],
+            )))
+        tail = octets[offset+1000:]
+        if len(tail) > 0:
+            write_full(writer, b"".join((
+                OctetString.tag_default,
+                len_encode(len(tail)),
+                tail,
+            )))
+        write_full(writer, EOC)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
-            l, llen, v = len_decode(lv)
-        except DecodeError as err:
-            raise err.__class__(
-                msg=err.msg,
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        if l > len(v):
-            raise NotEnoughData(
-                "encoded length is longer than data",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        v, tail = v[:l], v[l:]
-        try:
-            obj = self.__class__(
-                value=v.tobytes(),
-                bounds=(self._bound_min, self._bound_max),
-                impl=self.tag,
-                expl=self._expl,
-                default=self.default,
-                optional=self.optional,
-                _decoded=(offset, llen, l),
-            )
-        except DecodeError as err:
-            raise DecodeError(
-                msg=err.msg,
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        except BoundsError as err:
-            raise DecodeError(
-                msg=str(err),
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
-        return obj, tail
-
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
-        try:
-            t, tlen, lv = tag_strip(tlv)
+            t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -2693,24 +3663,10 @@ class OctetString(Obj):
             )
         if t == self.tag:
             if tag_only:
+                yield None
                 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,
@@ -2725,52 +3681,27 @@ class OctetString(Obj):
                     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
+            v, tail = v[:l], v[l:]
+            if evgen_mode and not self._bound_min <= len(v) <= self._bound_max:
+                raise DecodeError(
+                    msg=str(BoundsError(self._bound_min, len(v), self._bound_max)),
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
             try:
                 obj = self.__class__(
-                    value=b"".join(bytes(chunk) for chunk in chunks),
+                    value=(
+                        None if (evgen_mode and self.evgen_mode_skip_value)
+                        else v.tobytes()
+                    ),
                     bounds=(self._bound_min, self._bound_max),
                     impl=self.tag,
                     expl=self._expl,
                     default=self.default,
                     optional=self.optional,
-                    _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+                    _decoded=(offset, llen, l),
+                    ctx=ctx,
                 )
             except DecodeError as err:
                 raise DecodeError(
@@ -2786,20 +3717,145 @@ class OctetString(Obj):
                     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,
-        )
+            yield decode_path, obj, tail
+            return
+        if t != self.tag_constructed:
+            raise TagMismatch(
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if not ctx.get("bered", False):
+            raise DecodeError(
+                "unallowed BER constructed encoding",
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        if tag_only:
+            yield None
+            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 = []
+        chunks_count = 0
+        sub_offset = offset + tlen + llen
+        vlen = 0
+        payload_len = 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,
+                    )
+            try:
+                if evgen_mode:
+                    sub_decode_path = decode_path + (str(chunks_count),)
+                    for _decode_path, chunk, v_tail in OctetString().decode_evgen(
+                            v,
+                            offset=sub_offset,
+                            decode_path=sub_decode_path,
+                            leavemm=True,
+                            ctx=ctx,
+                            _ctx_immutable=False,
+                    ):
+                        yield _decode_path, chunk, v_tail
+                        if not chunk.ber_encoded:
+                            payload_len += chunk.vlen
+                    chunks_count += 1
+                else:
+                    sub_decode_path = decode_path + (str(len(chunks)),)
+                    _, chunk, v_tail = next(OctetString().decode_evgen(
+                        v,
+                        offset=sub_offset,
+                        decode_path=sub_decode_path,
+                        leavemm=True,
+                        ctx=ctx,
+                        _ctx_immutable=False,
+                        _evgen_mode=False,
+                    ))
+                    chunks.append(chunk)
+            except TagMismatch:
+                raise DecodeError(
+                    "expected OctetString encoded chunk",
+                    klass=self.__class__,
+                    decode_path=sub_decode_path,
+                    offset=sub_offset,
+                )
+            sub_offset += chunk.tlvlen
+            vlen += chunk.tlvlen
+            v = v_tail
+        if evgen_mode and not self._bound_min <= payload_len <= self._bound_max:
+            raise DecodeError(
+                msg=str(BoundsError(self._bound_min, payload_len, self._bound_max)),
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        try:
+            obj = self.__class__(
+                value=(
+                    None if evgen_mode else
+                    b"".join(bytes(chunk) for chunk in chunks)
+                ),
+                bounds=(self._bound_min, self._bound_max),
+                impl=self.tag,
+                expl=self._expl,
+                default=self.default,
+                optional=self.optional,
+                _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+                ctx=ctx,
+            )
+        except DecodeError as err:
+            raise DecodeError(
+                msg=err.msg,
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        except BoundsError as err:
+            raise DecodeError(
+                msg=str(err),
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        obj.lenindef = lenindef
+        obj.ber_encoded = True
+        yield decode_path, obj, (v[EOC_LEN:] if lenindef else v)
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2831,6 +3887,34 @@ class OctetString(Obj):
             yield pp
 
 
+def agg_octet_string(evgens, decode_path, raw, writer):
+    """Aggregate constructed string (OctetString and its derivatives)
+
+    :param evgens: iterator of generated events
+    :param decode_path: points to the string we want to decode
+    :param raw: slicebable (memoryview, bytearray, etc) with
+                the data evgens are generated on
+    :param writer: buffer.write where string is going to be saved
+    :param writer: where string is going to be saved. Must comply
+                   with ``io.RawIOBase.write`` behaviour
+    """
+    decode_path_len = len(decode_path)
+    for dp, obj, _ in evgens:
+        if dp[:decode_path_len] != decode_path:
+            continue
+        if not obj.ber_encoded:
+            write_full(writer, raw[
+                obj.offset + obj.tlen + obj.llen:
+                obj.offset + obj.tlen + obj.llen + obj.vlen -
+                (EOC_LEN if obj.expl_lenindef else 0)
+            ])
+        if len(dp) == decode_path_len:
+            break
+
+
+NullState = namedtuple("NullState", BasicState._fields, **NAMEDTUPLE_KWARGS)
+
+
 class Null(Obj):
     """``NULL`` null object
 
@@ -2863,16 +3947,21 @@ class Null(Obj):
     def ready(self):
         return True
 
-    def copy(self):
-        obj = self.__class__()
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    def __getstate__(self):
+        return NullState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+        )
 
     def __eq__(self, their):
         if not issubclass(their.__class__, Null):
@@ -2898,7 +3987,7 @@ class Null(Obj):
     def _encode(self):
         return self.tag + len_encode(0)
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2915,6 +4004,7 @@ class Null(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
         try:
             l, _, v = len_decode(lv)
@@ -2938,13 +4028,14 @@ class Null(Obj):
             optional=self.optional,
             _decoded=(offset, 1, 0),
         )
-        return obj, v
+        yield decode_path, obj, v
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2966,6 +4057,13 @@ class Null(Obj):
             yield pp
 
 
+ObjectIdentifierState = namedtuple(
+    "ObjectIdentifierState",
+    BasicState._fields + ("value", "defines"),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class ObjectIdentifier(Obj):
     """``OBJECT IDENTIFIER`` OID type
 
@@ -3016,13 +4114,7 @@ class ObjectIdentifier(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(ObjectIdentifier, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super(ObjectIdentifier, self).__init__(impl, expl, default, optional, _decoded)
         self._value = value
         if value is not None:
             self._value = self._value_sanitize(value)
@@ -3038,10 +4130,10 @@ class ObjectIdentifier(Obj):
         self.defines = defines
 
     def __add__(self, their):
+        if their.__class__ == tuple:
+            return self.__class__(self._value + array("L", their))
         if isinstance(their, self.__class__):
             return self.__class__(self._value + their._value)
-        if isinstance(their, tuple):
-            return self.__class__(self._value + their)
         raise InvalidValueType((self.__class__, tuple))
 
     def _value_sanitize(self, value):
@@ -3049,10 +4141,15 @@ class ObjectIdentifier(Obj):
             return value._value
         if isinstance(value, string_types):
             try:
-                value = tuple(int(arc) for arc in value.split("."))
+                value = array("L", (pureint(arc) for arc in value.split(".")))
             except ValueError:
                 raise InvalidOID("unacceptable arcs values")
-        if isinstance(value, tuple):
+        if value.__class__ == tuple:
+            try:
+                value = array("L", value)
+            except OverflowError as err:
+                raise InvalidOID(repr(err))
+        if value.__class__ is array:
             if len(value) < 2:
                 raise InvalidOID("less than 2 arcs")
             first_arc = value[0]
@@ -3063,6 +4160,8 @@ class ObjectIdentifier(Obj):
                 pass
             else:
                 raise InvalidOID("unacceptable first arc value")
+            if not all(arc >= 0 for arc in value):
+                raise InvalidOID("negative arc value")
             return value
         raise InvalidValueType((self.__class__, str, tuple))
 
@@ -3070,18 +4169,28 @@ class ObjectIdentifier(Obj):
     def ready(self):
         return self._value is not None
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj.defines = self.defines
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    def __getstate__(self):
+        return ObjectIdentifierState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self._value,
+            self.defines,
+        )
+
+    def __setstate__(self, state):
+        super(ObjectIdentifier, self).__setstate__(state)
+        self._value = state.value
+        self.defines = state.defines
 
     def __iter__(self):
         self._assert_ready()
@@ -3099,8 +4208,8 @@ class ObjectIdentifier(Obj):
         )
 
     def __eq__(self, their):
-        if isinstance(their, tuple):
-            return self._value == their
+        if their.__class__ == tuple:
+            return self._value == array("L", their)
         if not issubclass(their.__class__, ObjectIdentifier):
             return False
         return (
@@ -3149,7 +4258,7 @@ class ObjectIdentifier(Obj):
         v = b"".join(octets)
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3166,6 +4275,7 @@ class ObjectIdentifier(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
         try:
             l, llen, v = len_decode(lv)
@@ -3191,15 +4301,34 @@ class ObjectIdentifier(Obj):
                 offset=offset,
             )
         v, tail = v[:l], v[l:]
-        arcs = []
+        arcs = array("L")
+        ber_encoded = False
         while len(v) > 0:
             i = 0
             arc = 0
             while True:
                 octet = indexbytes(v, i)
+                if i == 0 and octet == 0x80:
+                    if ctx.get("bered", False):
+                        ber_encoded = True
+                    else:
+                        raise DecodeError(
+                            "non normalized arc encoding",
+                            klass=self.__class__,
+                            decode_path=decode_path,
+                            offset=offset,
+                        )
                 arc = (arc << 7) | (octet & 0x7F)
                 if octet & 0x80 == 0:
-                    arcs.append(arc)
+                    try:
+                        arcs.append(arc)
+                    except OverflowError:
+                        raise DecodeError(
+                            "too huge value for local unsigned long",
+                            klass=self.__class__,
+                            decode_path=decode_path,
+                            offset=offset,
+                        )
                     v = v[i + 1:]
                     break
                 i += 1
@@ -3221,20 +4350,23 @@ class ObjectIdentifier(Obj):
             first_arc = 2
             second_arc -= 80
         obj = self.__class__(
-            value=tuple([first_arc, second_arc] + arcs[1:]),
+            value=array("L", (first_arc, second_arc)) + arcs[1:],
             impl=self.tag,
             expl=self._expl,
             default=self.default,
             optional=self.optional,
             _decoded=(offset, llen, l),
         )
-        return obj, tail
+        if ber_encoded:
+            obj.ber_encoded = True
+        yield decode_path, obj, tail
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3252,6 +4384,7 @@ class ObjectIdentifier(Obj):
             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):
@@ -3280,13 +4413,7 @@ class Enumerated(Integer):
             bounds=None,  # dummy argument, workability for Integer.decode
     ):
         super(Enumerated, self).__init__(
-            value=value,
-            impl=impl,
-            expl=expl,
-            default=default,
-            optional=optional,
-            _specs=_specs,
-            _decoded=_decoded,
+            value, bounds, impl, expl, default, optional, _specs, _decoded,
         )
         if len(self.specs) == 0:
             raise ValueError("schema must be specified")
@@ -3295,7 +4422,10 @@ class Enumerated(Integer):
         if isinstance(value, self.__class__):
             value = value._value
         elif isinstance(value, integer_types):
-            if value not in list(self.specs.values()):
+            for _value in itervalues(self.specs):
+                if _value == value:
+                    break
+            else:
                 raise DecodeError(
                     "unknown integer value: %s" % value,
                     klass=self.__class__,
@@ -3308,20 +4438,6 @@ class Enumerated(Integer):
             raise InvalidValueType((self.__class__, int, str))
         return value
 
-    def copy(self):
-        obj = self.__class__(_specs=self.specs)
-        obj._value = self._value
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
-
     def __call__(
             self,
             value=None,
@@ -3341,6 +4457,12 @@ class Enumerated(Integer):
         )
 
 
+def escape_control_unicode(c):
+    if unicat(c)[0] == "C":
+        c = repr(c).lstrip("u").strip("'")
+    return c
+
+
 class CommonString(OctetString):
     """Common class for all strings
 
@@ -3403,16 +4525,16 @@ class CommonString(OctetString):
        * - :py:class:`pyderasn.BMPString`
          - utf-16-be
     """
-    __slots__ = ("encoding",)
+    __slots__ = ()
 
     def _value_sanitize(self, value):
         value_raw = None
         value_decoded = None
         if isinstance(value, self.__class__):
             value_raw = value._value
-        elif isinstance(value, text_type):
+        elif value.__class__ == text_type:
             value_decoded = value
-        elif isinstance(value, binary_type):
+        elif value.__class__ == binary_type:
             value_raw = value
         else:
             raise InvalidValueType((self.__class__, text_type, binary_type))
@@ -3436,9 +4558,9 @@ class CommonString(OctetString):
         return value_raw
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == binary_type:
             return self._value == their
-        if isinstance(their, text_type):
+        if their.__class__ == text_type:
             return self._value == their.encode(self.encoding)
         if not isinstance(their, self.__class__):
             return False
@@ -3459,8 +4581,12 @@ class CommonString(OctetString):
     def pps(self, decode_path=(), no_unicode=False):
         value = None
         if self.ready:
-            value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
+            value = (
+                hexenc(bytes(self)) if no_unicode else
+                "".join(escape_control_unicode(c) for c in self.__unicode__())
+            )
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3492,42 +4618,141 @@ class UTF8String(CommonString):
     asn1_type_name = "UTF8String"
 
 
-class NumericString(CommonString):
+class AllowableCharsMixin(object):
+    @property
+    def allowable_chars(self):
+        if PY2:
+            return self._allowable_chars
+        return frozenset(six_unichr(c) for c in self._allowable_chars)
+
+
+class NumericString(AllowableCharsMixin, CommonString):
     """Numeric string
 
     Its value is properly sanitized: only ASCII digits with spaces can
     be stored.
+
+    >>> NumericString().allowable_chars
+    frozenset(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' '])
     """
     __slots__ = ()
     tag_default = tag_encode(18)
     encoding = "ascii"
     asn1_type_name = "NumericString"
-    allowable_chars = set(digits.encode("ascii") + b" ")
+    _allowable_chars = frozenset(digits.encode("ascii") + b" ")
 
     def _value_sanitize(self, value):
         value = super(NumericString, self)._value_sanitize(value)
-        if not set(value) <= self.allowable_chars:
+        if not frozenset(value) <= self._allowable_chars:
             raise DecodeError("non-numeric value")
         return value
 
 
-class PrintableString(CommonString):
+PrintableStringState = namedtuple(
+    "PrintableStringState",
+    OctetStringState._fields + ("allowable_chars",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
+class PrintableString(AllowableCharsMixin, CommonString):
     """Printable string
 
     Its value is properly sanitized: see X.680 41.4 table 10.
+
+    >>> PrintableString().allowable_chars
+    frozenset([' ', "'", ..., 'z'])
+    >>> obj = PrintableString("foo*bar", allow_asterisk=True)
+    PrintableString PrintableString foo*bar
+    >>> obj.allow_asterisk, obj.allow_ampersand
+    (True, False)
     """
     __slots__ = ()
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
-    allowable_chars = set((ascii_letters + digits + " '()+,-./:=?").encode("ascii"))
+    _allowable_chars = frozenset(
+        (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+    )
+    _asterisk = frozenset("*".encode("ascii"))
+    _ampersand = frozenset("&".encode("ascii"))
+
+    def __init__(
+            self,
+            value=None,
+            bounds=None,
+            impl=None,
+            expl=None,
+            default=None,
+            optional=False,
+            _decoded=(0, 0, 0),
+            ctx=None,
+            allow_asterisk=False,
+            allow_ampersand=False,
+    ):
+        """
+        :param allow_asterisk: allow asterisk character
+        :param allow_ampersand: allow ampersand character
+        """
+        if allow_asterisk:
+            self._allowable_chars |= self._asterisk
+        if allow_ampersand:
+            self._allowable_chars |= self._ampersand
+        super(PrintableString, self).__init__(
+            value, bounds, impl, expl, default, optional, _decoded, ctx,
+        )
+
+    @property
+    def allow_asterisk(self):
+        """Is asterisk character allowed?
+        """
+        return self._asterisk <= self._allowable_chars
+
+    @property
+    def allow_ampersand(self):
+        """Is ampersand character allowed?
+        """
+        return self._ampersand <= self._allowable_chars
 
     def _value_sanitize(self, value):
         value = super(PrintableString, self)._value_sanitize(value)
-        if not set(value) <= self.allowable_chars:
+        if not frozenset(value) <= self._allowable_chars:
             raise DecodeError("non-printable value")
         return value
 
+    def __getstate__(self):
+        return PrintableStringState(
+            *super(PrintableString, self).__getstate__(),
+            **{"allowable_chars": self._allowable_chars}
+        )
+
+    def __setstate__(self, state):
+        super(PrintableString, self).__setstate__(state)
+        self._allowable_chars = state.allowable_chars
+
+    def __call__(
+            self,
+            value=None,
+            bounds=None,
+            impl=None,
+            expl=None,
+            default=None,
+            optional=None,
+    ):
+        return self.__class__(
+            value=value,
+            bounds=(
+                (self._bound_min, self._bound_max)
+                if bounds is None else bounds
+            ),
+            impl=self.tag if impl is None else impl,
+            expl=self._expl if expl is None else expl,
+            default=self.default if default is None else default,
+            optional=self.optional if optional is None else optional,
+            allow_asterisk=self.allow_asterisk,
+            allow_ampersand=self.allow_ampersand,
+        )
+
 
 class TeletexString(CommonString):
     __slots__ = ()
@@ -3560,7 +4785,31 @@ LEN_YYYYMMDDHHMMSSDMZ = len("YYYYMMDDHHMMSSDMZ")
 LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
 
 
-class UTCTime(CommonString):
+class VisibleString(CommonString):
+    __slots__ = ()
+    tag_default = tag_encode(26)
+    encoding = "ascii"
+    asn1_type_name = "VisibleString"
+
+
+UTCTimeState = namedtuple(
+    "UTCTimeState",
+    OctetStringState._fields + ("ber_raw",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
+def str_to_time_fractions(value):
+    v = pureint(value)
+    year, v = (v // 10**10), (v % 10**10)
+    month, v = (v // 10**8), (v % 10**8)
+    day, v = (v // 10**6), (v % 10**6)
+    hour, v = (v // 10**4), (v % 10**4)
+    minute, second = (v // 100), (v % 100)
+    return year, month, day, hour, minute, second
+
+
+class UTCTime(VisibleString):
     """``UTCTime`` datetime type
 
     >>> t = UTCTime(datetime(2017, 9, 30, 22, 7, 50, 123))
@@ -3573,13 +4822,28 @@ class UTCTime(CommonString):
     datetime.datetime(2017, 9, 30, 22, 7, 50)
     >>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).todatetime()
     datetime.datetime(1957, 9, 30, 22, 7, 50)
+
+    If BER encoded value was met, then ``ber_raw`` attribute will hold
+    its raw representation.
+
+    .. warning::
+
+       Pay attention that UTCTime can not hold full year, so all years
+       having < 50 years are treated as 20xx, 19xx otherwise, according
+       to X.509 recommendation.
+
+    .. warning::
+
+       No strict validation of UTC offsets are made, but very crude:
+
+       * minutes are not exceeding 60
+       * offset value is not exceeding 14 hours
     """
-    __slots__ = ()
+    __slots__ = ("ber_raw",)
     tag_default = tag_encode(23)
     encoding = "ascii"
     asn1_type_name = "UTCTime"
-
-    fmt = "%y%m%d%H%M%SZ"
+    evgen_mode_skip_value = False
 
     def __init__(
             self,
@@ -3590,6 +4854,7 @@ class UTCTime(CommonString):
             optional=False,
             _decoded=(0, 0, 0),
             bounds=None,  # dummy argument, workability for OctetString.decode
+            ctx=None,
     ):
         """
         :param value: set the value. Either datetime type, or
@@ -3600,17 +4865,15 @@ class UTCTime(CommonString):
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         super(UTCTime, self).__init__(
-            impl=impl,
-            expl=expl,
-            default=default,
-            optional=optional,
-            _decoded=_decoded,
+            None, None, impl, expl, None, optional, _decoded, ctx,
         )
         self._value = value
+        self.ber_raw = None
         if value is not None:
-            self._value = self._value_sanitize(value)
+            self._value, self.ber_raw = self._value_sanitize(value, ctx)
+            self.ber_encoded = self.ber_raw is not None
         if default is not None:
-            default = self._value_sanitize(default)
+            default, _ = self._value_sanitize(default)
             self.default = self.__class__(
                 value=default,
                 impl=self.tag,
@@ -3618,31 +4881,122 @@ class UTCTime(CommonString):
             )
             if self._value is None:
                 self._value = default
+            optional = True
+        self.optional = optional
 
-    def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime(self.fmt).encode("ascii")
-        if isinstance(value, binary_type):
+    def _strptime_bered(self, value):
+        year, month, day, hour, minute, _ = str_to_time_fractions(value[:10] + "00")
+        value = value[10:]
+        if len(value) == 0:
+            raise ValueError("no timezone")
+        year += 2000 if year < 50 else 1900
+        decoded = datetime(year, month, day, hour, minute)
+        offset = 0
+        if value[-1] == "Z":
+            value = value[:-1]
+        else:
+            if len(value) < 5:
+                raise ValueError("invalid UTC offset")
+            if value[-5] == "-":
+                sign = -1
+            elif value[-5] == "+":
+                sign = 1
+            else:
+                raise ValueError("invalid UTC offset")
+            v = pureint(value[-4:])
+            offset, v = (60 * (v % 100)), v // 100
+            if offset >= 3600:
+                raise ValueError("invalid UTC offset minutes")
+            offset += 3600 * v
+            if offset > 14 * 3600:
+                raise ValueError("too big UTC offset")
+            offset *= sign
+            value = value[:-5]
+        if len(value) == 0:
+            return offset, decoded
+        if len(value) != 2:
+            raise ValueError("invalid UTC offset seconds")
+        seconds = pureint(value)
+        if seconds >= 60:
+            raise ValueError("invalid seconds value")
+        return offset, decoded + timedelta(seconds=seconds)
+
+    def _strptime(self, value):
+        # datetime.strptime's format: %y%m%d%H%M%SZ
+        if len(value) != LEN_YYMMDDHHMMSSZ:
+            raise ValueError("invalid UTCTime length")
+        if value[-1] != "Z":
+            raise ValueError("non UTC timezone")
+        year, month, day, hour, minute, second = str_to_time_fractions(value[:-1])
+        year += 2000 if year < 50 else 1900
+        return datetime(year, month, day, hour, minute, second)
+
+    def _dt_sanitize(self, value):
+        if value.year < 1950 or value.year > 2049:
+            raise ValueError("UTCTime can hold only 1950-2049 years")
+        return value.replace(microsecond=0)
+
+    def _value_sanitize(self, value, ctx=None):
+        if value.__class__ == binary_type:
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
-                raise DecodeError("invalid UTCTime encoding")
-            if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
-                try:
-                    datetime.strptime(value_decoded, self.fmt)
-                except (TypeError, ValueError):
-                    raise DecodeError("invalid UTCTime format")
-                return value
-            else:
-                raise DecodeError("invalid UTCTime length")
+                raise DecodeError("invalid UTCTime encoding: %r" % err)
+            err = None
+            try:
+                return self._strptime(value_decoded), None
+            except (TypeError, ValueError) as _err:
+                err = _err
+                if (ctx is not None) and ctx.get("bered", False):
+                    try:
+                        offset, _value = self._strptime_bered(value_decoded)
+                        _value = _value - timedelta(seconds=offset)
+                        return self._dt_sanitize(_value), value
+                    except (TypeError, ValueError, OverflowError) as _err:
+                        err = _err
+            raise DecodeError(
+                "invalid %s format: %r" % (self.asn1_type_name, err),
+                klass=self.__class__,
+            )
+        if isinstance(value, self.__class__):
+            return value._value, None
+        if value.__class__ == datetime:
+            return self._dt_sanitize(value), None
         raise InvalidValueType((self.__class__, datetime))
 
+    def _pp_value(self):
+        if self.ready:
+            value = self._value.isoformat()
+            if self.ber_encoded:
+                value += " (%s)" % self.ber_raw
+            return value
+
+    def __unicode__(self):
+        if self.ready:
+            value = self._value.isoformat()
+            if self.ber_encoded:
+                value += " (%s)" % self.ber_raw
+            return value
+        return text_type(self._pp_value())
+
+    def __getstate__(self):
+        return UTCTimeState(
+            *super(UTCTime, self).__getstate__(),
+            **{"ber_raw": self.ber_raw}
+        )
+
+    def __setstate__(self, state):
+        super(UTCTime, self).__setstate__(state)
+        self.ber_raw = state.ber_raw
+
+    def __bytes__(self):
+        self._assert_ready()
+        return self._encode_time()
+
     def __eq__(self, their):
-        if isinstance(their, binary_type):
-            return self._value == their
-        if isinstance(their, datetime):
+        if their.__class__ == binary_type:
+            return self._encode_time() == their
+        if their.__class__ == datetime:
             return self.todatetime() == their
         if not isinstance(their, self.__class__):
             return False
@@ -3652,35 +5006,30 @@ class UTCTime(CommonString):
             self._expl == their._expl
         )
 
-    def todatetime(self):
-        """Convert to datetime
+    def _encode_time(self):
+        return self._value.strftime("%y%m%d%H%M%SZ").encode("ascii")
 
-        :returns: datetime
+    def _encode(self):
+        self._assert_ready()
+        value = self._encode_time()
+        return b"".join((self.tag, len_encode(len(value)), value))
 
-        Pay attention that UTCTime can not hold full year, so all years
-        having < 50 years are treated as 20xx, 19xx otherwise, according
-        to X.509 recomendation.
-        """
-        value = datetime.strptime(self._value.decode("ascii"), self.fmt)
-        year = value.year % 100
-        return datetime(
-            year=(2000 + year) if year < 50 else (1900 + year),
-            month=value.month,
-            day=value.day,
-            hour=value.hour,
-            minute=value.minute,
-            second=value.second,
-        )
+    def _encode_cer(self, writer):
+        write_full(writer, self._encode())
+
+    def todatetime(self):
+        return self._value
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
-            value=self.todatetime().isoformat() if self.ready else None,
+            value=self._pp_value(),
             optional=self.optional,
             default=self == self.default,
             impl=None if self.tag == self.tag_default else tag_decode(self.tag),
@@ -3712,54 +5061,122 @@ class GeneralizedTime(UTCTime):
     '20170930220750.000123Z'
     >>> t = GeneralizedTime(datetime(2057, 9, 30, 22, 7, 50))
     GeneralizedTime GeneralizedTime 2057-09-30T22:07:50
-    """
-    __slots__ = ()
-    tag_default = tag_encode(24)
-    asn1_type_name = "GeneralizedTime"
 
-    fmt = "%Y%m%d%H%M%SZ"
-    fmt_ms = "%Y%m%d%H%M%S.%fZ"
+    .. warning::
 
-    def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime(
-                self.fmt_ms if value.microsecond > 0 else self.fmt
-            ).encode("ascii")
-        if isinstance(value, binary_type):
-            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)
-                except (TypeError, ValueError):
-                    raise DecodeError(
-                        "invalid GeneralizedTime (without ms) format",
-                    )
-                return value
-            elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
-                try:
-                    datetime.strptime(value_decoded, self.fmt_ms)
-                except (TypeError, ValueError):
-                    raise DecodeError(
-                        "invalid GeneralizedTime (with ms) format",
-                    )
-                return value
-            else:
-                raise DecodeError(
-                    "invalid GeneralizedTime length",
-                    klass=self.__class__,
-                )
-        raise InvalidValueType((self.__class__, datetime))
+       Only microsecond fractions are supported in DER encoding.
+       :py:exc:`pyderasn.DecodeError` will be raised during decoding of
+       higher precision values.
+
+    .. warning::
+
+       BER encoded data can loss information (accuracy) during decoding
+       because of float transformations.
+
+    .. warning::
+
+       Local times (without explicit timezone specification) are treated
+       as UTC one, no transformations are made.
 
-    def todatetime(self):
-        value = self._value.decode("ascii")
-        if len(value) == LEN_YYYYMMDDHHMMSSZ:
-            return datetime.strptime(value, self.fmt)
-        return datetime.strptime(value, self.fmt_ms)
+    .. warning::
+
+       Zero year is unsupported.
+    """
+    __slots__ = ()
+    tag_default = tag_encode(24)
+    asn1_type_name = "GeneralizedTime"
+
+    def _dt_sanitize(self, value):
+        return value
+
+    def _strptime_bered(self, value):
+        if len(value) < 4 + 3 * 2:
+            raise ValueError("invalid GeneralizedTime")
+        year, month, day, hour, _, _ = str_to_time_fractions(value[:10] + "0000")
+        decoded = datetime(year, month, day, hour)
+        offset, value = 0, value[10:]
+        if len(value) == 0:
+            return offset, decoded
+        if value[-1] == "Z":
+            value = value[:-1]
+        else:
+            for char, sign in (("-", -1), ("+", 1)):
+                idx = value.rfind(char)
+                if idx == -1:
+                    continue
+                offset_raw, value = value[idx + 1:].replace(":", ""), value[:idx]
+                v = pureint(offset_raw)
+                if len(offset_raw) == 4:
+                    offset, v = (60 * (v % 100)), v // 100
+                    if offset >= 3600:
+                        raise ValueError("invalid UTC offset minutes")
+                elif len(offset_raw) == 2:
+                    pass
+                else:
+                    raise ValueError("invalid UTC offset")
+                offset += 3600 * v
+                if offset > 14 * 3600:
+                    raise ValueError("too big UTC offset")
+                offset *= sign
+                break
+        if len(value) == 0:
+            return offset, decoded
+        if value[0] in DECIMAL_SIGNS:
+            return offset, (
+                decoded + timedelta(seconds=3600 * fractions2float(value[1:]))
+            )
+        if len(value) < 2:
+            raise ValueError("stripped minutes")
+        decoded += timedelta(seconds=60 * pureint(value[:2]))
+        value = value[2:]
+        if len(value) == 0:
+            return offset, decoded
+        if value[0] in DECIMAL_SIGNS:
+            return offset, (
+                decoded + timedelta(seconds=60 * fractions2float(value[1:]))
+            )
+        if len(value) < 2:
+            raise ValueError("stripped seconds")
+        decoded += timedelta(seconds=pureint(value[:2]))
+        value = value[2:]
+        if len(value) == 0:
+            return offset, decoded
+        if value[0] not in DECIMAL_SIGNS:
+            raise ValueError("invalid format after seconds")
+        return offset, (
+            decoded + timedelta(microseconds=10**6 * fractions2float(value[1:]))
+        )
+
+    def _strptime(self, value):
+        l = len(value)
+        if l == LEN_YYYYMMDDHHMMSSZ:
+            # datetime.strptime's format: %Y%m%d%H%M%SZ
+            if value[-1] != "Z":
+                raise ValueError("non UTC timezone")
+            return datetime(*str_to_time_fractions(value[:-1]))
+        if l >= LEN_YYYYMMDDHHMMSSDMZ:
+            # datetime.strptime's format: %Y%m%d%H%M%S.%fZ
+            if value[-1] != "Z":
+                raise ValueError("non UTC timezone")
+            if value[14] != ".":
+                raise ValueError("no fractions separator")
+            us = value[15:-1]
+            if us[-1] == "0":
+                raise ValueError("trailing zero")
+            us_len = len(us)
+            if us_len > 6:
+                raise ValueError("only microsecond fractions are supported")
+            us = pureint(us + ("0" * (6 - us_len)))
+            year, month, day, hour, minute, second = str_to_time_fractions(value[:14])
+            return datetime(year, month, day, hour, minute, second, us)
+        raise ValueError("invalid GeneralizedTime length")
+
+    def _encode_time(self):
+        value = self._value
+        encoded = value.strftime("%Y%m%d%H%M%S")
+        if value.microsecond > 0:
+            encoded += (".%06d" % value.microsecond).rstrip("0")
+        return (encoded + "Z").encode("ascii")
 
 
 class GraphicString(CommonString):
@@ -3769,13 +5186,6 @@ class GraphicString(CommonString):
     asn1_type_name = "GraphicString"
 
 
-class VisibleString(CommonString):
-    __slots__ = ()
-    tag_default = tag_encode(26)
-    encoding = "ascii"
-    asn1_type_name = "VisibleString"
-
-
 class ISO646String(VisibleString):
     __slots__ = ()
     asn1_type_name = "ISO646String"
@@ -3802,6 +5212,13 @@ class BMPString(CommonString):
     asn1_type_name = "BMPString"
 
 
+ChoiceState = namedtuple(
+    "ChoiceState",
+    BasicState._fields + ("specs", "value",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Choice(Obj):
     """``CHOICE`` special type
 
@@ -3863,7 +5280,7 @@ class Choice(Obj):
         if len(schema) == 0:
             raise ValueError("schema must be specified")
         self.specs = (
-            schema if isinstance(schema, OrderedDict) else OrderedDict(schema)
+            schema if schema.__class__ == OrderedDict else OrderedDict(schema)
         )
         self._value = None
         if value is not None:
@@ -3875,12 +5292,13 @@ class Choice(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
-                self._value = default_obj.copy()._value
+                self._value = copy(default_obj._value)
+        if self._expl is not None:
+            tag_class, _, tag_num = tag_decode(self._expl)
+            self._tag_order = (tag_class, tag_num)
 
     def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, tuple) and len(value) == 2:
+        if (value.__class__ == tuple) and len(value) == 2:
             choice, obj = value
             spec = self.specs.get(choice)
             if spec is None:
@@ -3888,6 +5306,8 @@ class Choice(Obj):
             if not isinstance(obj, spec.__class__):
                 raise InvalidValueType((spec,))
             return (choice, spec(obj))
+        if isinstance(value, self.__class__):
+            return value._value
         raise InvalidValueType((self.__class__, tuple))
 
     @property
@@ -3901,21 +5321,31 @@ class Choice(Obj):
             self._value[1].bered
         )
 
-    def copy(self):
-        obj = self.__class__(schema=self.specs)
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        value = self._value
-        if value is not None:
-            obj._value = (value[0], value[1].copy())
-        return obj
+    def __getstate__(self):
+        return ChoiceState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self.specs,
+            copy(self._value),
+        )
+
+    def __setstate__(self, state):
+        super(Choice, self).__setstate__(state)
+        self.specs = state.specs
+        self._value = state.value
 
     def __eq__(self, their):
-        if isinstance(their, tuple) and len(their) == 2:
+        if (their.__class__ == tuple) and len(their) == 2:
             return self._value == their
         if not isinstance(their, self.__class__):
             return False
@@ -3941,14 +5371,27 @@ class Choice(Obj):
 
     @property
     def choice(self):
+        """Name of the choice
+        """
         self._assert_ready()
         return self._value[0]
 
     @property
     def value(self):
+        """Value of underlying choice
+        """
         self._assert_ready()
         return self._value[1]
 
+    @property
+    def tag_order(self):
+        self._assert_ready()
+        return self._value[1].tag_order if self._tag_order is None else self._tag_order
+
+    @property
+    def tag_order_cer(self):
+        return min(v.tag_order_cer for v in itervalues(self.specs))
+
     def __getitem__(self, key):
         if key not in self.specs:
             raise ObjUnknown(key)
@@ -3979,8 +5422,12 @@ class Choice(Obj):
         self._assert_ready()
         return self._value[1].encode()
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
-        for choice, spec in self.specs.items():
+    def _encode_cer(self, writer):
+        self._assert_ready()
+        self._value[1].encode_cer(writer)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
+        for choice, spec in iteritems(self.specs):
             sub_decode_path = decode_path + (choice,)
             try:
                 spec.decode(
@@ -3990,6 +5437,7 @@ class Choice(Obj):
                     decode_path=sub_decode_path,
                     ctx=ctx,
                     tag_only=True,
+                    _ctx_immutable=False,
                 )
             except TagMismatch:
                 continue
@@ -4001,14 +5449,28 @@ class Choice(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
-        value, tail = spec.decode(
-            tlv,
-            offset=offset,
-            leavemm=True,
-            decode_path=sub_decode_path,
-            ctx=ctx,
-        )
+        if evgen_mode:
+            for _decode_path, value, tail in spec.decode_evgen(
+                    tlv,
+                    offset=offset,
+                    leavemm=True,
+                    decode_path=sub_decode_path,
+                    ctx=ctx,
+                    _ctx_immutable=False,
+            ):
+                yield _decode_path, value, tail
+        else:
+            _, value, tail = next(spec.decode_evgen(
+                tlv,
+                offset=offset,
+                leavemm=True,
+                decode_path=sub_decode_path,
+                ctx=ctx,
+                _ctx_immutable=False,
+                _evgen_mode=False,
+            ))
         obj = self.__class__(
             schema=self.specs,
             expl=self._expl,
@@ -4017,7 +5479,7 @@ class Choice(Obj):
             _decoded=(offset, 0, value.fulllen),
         )
         obj._value = (choice, value)
-        return obj, tail
+        yield decode_path, obj, tail
 
     def __repr__(self):
         value = pp_console_row(next(self.pps()))
@@ -4027,6 +5489,7 @@ class Choice(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -4053,9 +5516,9 @@ class PrimitiveTypes(Choice):
 
     It could be useful for general decoding of some unspecified values:
 
-    >>> PrimitiveTypes().decode(hexdec("0403666f6f"))[0].value
+    >>> PrimitiveTypes().decod(hexdec("0403666f6f")).value
     OCTET STRING 3 bytes 666f6f
-    >>> PrimitiveTypes().decode(hexdec("0203123456"))[0].value
+    >>> PrimitiveTypes().decod(hexdec("0203123456")).value
     INTEGER 1193046
     """
     __slots__ = ()
@@ -4083,11 +5546,18 @@ class PrimitiveTypes(Choice):
     ))
 
 
+AnyState = namedtuple(
+    "AnyState",
+    BasicState._fields + ("value", "defined"),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Any(Obj):
     """``ANY`` special type
 
     >>> Any(Integer(-123))
-    ANY 020185
+    ANY INTEGER -123 (0X:7B)
     >>> a = Any(OctetString(b"hello world").encode())
     ANY 040b68656c6c6f20776f726c64
     >>> hexenc(bytes(a))
@@ -4107,28 +5577,47 @@ class Any(Obj):
         """
         :param value: set the value. Either any kind of pyderasn's
                       **ready** object, or bytes. Pay attention that
-                      **no** validation is performed is raw binary value
-                      is valid TLV
+                      **no** validation is performed if raw binary value
+                      is valid TLV, except just tag decoding
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         super(Any, self).__init__(None, expl, None, optional, _decoded)
-        self._value = None if value is None else self._value_sanitize(value)
+        if value is None:
+            self._value = None
+        else:
+            value = self._value_sanitize(value)
+            self._value = value
+            if self._expl is None:
+                if value.__class__ == binary_type:
+                    tag_class, _, tag_num = tag_decode(tag_strip(value)[0])
+                else:
+                    tag_class, tag_num = value.tag_order
+            else:
+                tag_class, _, tag_num = tag_decode(self._expl)
+            self._tag_order = (tag_class, tag_num)
         self.defined = None
 
     def _value_sanitize(self, value):
+        if value.__class__ == binary_type:
+            if len(value) == 0:
+                raise ValueError("Any value can not be empty")
+            return value
         if isinstance(value, self.__class__):
             return value._value
-        if isinstance(value, Obj):
-            return value.encode()
-        if isinstance(value, binary_type):
-            return value
-        raise InvalidValueType((self.__class__, Obj, binary_type))
+        if not isinstance(value, Obj):
+            raise InvalidValueType((self.__class__, Obj, binary_type))
+        return value
 
     @property
     def ready(self):
         return self._value is not None
 
+    @property
+    def tag_order(self):
+        self._assert_ready()
+        return self._tag_order
+
     @property
     def bered(self):
         if self.expl_lenindef or self.lenindef:
@@ -4137,22 +5626,38 @@ class Any(Obj):
             return False
         return self.defined[1].bered
 
-    def copy(self):
-        obj = self.__class__()
-        obj._value = self._value
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        return obj
+    def __getstate__(self):
+        return AnyState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            None,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self._value,
+            self.defined,
+        )
+
+    def __setstate__(self, state):
+        super(Any, self).__setstate__(state)
+        self._value = state.value
+        self.defined = state.defined
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
-            return self._value == their
+        if their.__class__ == binary_type:
+            if self._value.__class__ == binary_type:
+                return self._value == their
+            return self._value.encode() == their
         if issubclass(their.__class__, Any):
-            return self._value == their._value
+            if self.ready and their.ready:
+                return bytes(self) == bytes(their)
+            return self.ready == their.ready
         return False
 
     def __call__(
@@ -4169,7 +5674,10 @@ class Any(Obj):
 
     def __bytes__(self):
         self._assert_ready()
-        return self._value
+        value = self._value
+        if value.__class__ == binary_type:
+            return value
+        return self._value.encode()
 
     @property
     def tlen(self):
@@ -4177,9 +5685,20 @@ class Any(Obj):
 
     def _encode(self):
         self._assert_ready()
-        return self._value
+        value = self._value
+        if value.__class__ == binary_type:
+            return value
+        return value.encode()
+
+    def _encode_cer(self, writer):
+        self._assert_ready()
+        value = self._value
+        if value.__class__ == binary_type:
+            write_full(writer, value)
+        else:
+            value.encode_cer(writer)
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4209,20 +5728,22 @@ class Any(Obj):
                     decode_path=decode_path + (str(chunk_i),),
                     leavemm=True,
                     ctx=ctx,
+                    _ctx_immutable=False,
                 )
                 vlen += chunk.tlvlen
                 sub_offset += chunk.tlvlen
                 chunk_i += 1
             tlvlen = tlen + llen + vlen + EOC_LEN
             obj = self.__class__(
-                value=tlv[:tlvlen].tobytes(),
+                value=None if evgen_mode else tlv[:tlvlen].tobytes(),
                 expl=self._expl,
                 optional=self.optional,
                 _decoded=(offset, 0, tlvlen),
             )
             obj.lenindef = True
-            obj.tag = t
-            return obj, v[EOC_LEN:]
+            obj.tag = t.tobytes()
+            yield decode_path, obj, v[EOC_LEN:]
+            return
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -4240,23 +5761,32 @@ class Any(Obj):
         tlvlen = tlen + llen + l
         v, tail = tlv[:tlvlen], v[l:]
         obj = self.__class__(
-            value=v.tobytes(),
+            value=None if evgen_mode else v.tobytes(),
             expl=self._expl,
             optional=self.optional,
             _decoded=(offset, 0, tlvlen),
         )
-        obj.tag = t
-        return obj, tail
+        obj.tag = t.tobytes()
+        yield decode_path, obj, tail
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def pps(self, decode_path=()):
+        value = self._value
+        if value is None:
+            pass
+        elif value.__class__ == binary_type:
+            value = None
+        else:
+            value = repr(value)
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
-            blob=self._value if self.ready else None,
+            value=value,
+            blob=self._value if self._value.__class__ == binary_type else None,
             optional=self.optional,
             default=self == self.default,
             impl=None if self.tag == self.tag_default else tag_decode(self.tag),
@@ -4293,7 +5823,7 @@ def get_def_by_path(defines_by_path, sub_decode_path):
         if len(path) != len(sub_decode_path):
             continue
         for p1, p2 in zip(path, sub_decode_path):
-            if (p1 != any) and (p1 != p2):
+            if (not p1 is any) and (p1 != p2):
                 break
         else:
             return define
@@ -4302,8 +5832,7 @@ def get_def_by_path(defines_by_path, sub_decode_path):
 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 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
@@ -4325,6 +5854,13 @@ def abs_decode_path(decode_path, rel_path):
     return decode_path + rel_path
 
 
+SequenceState = namedtuple(
+    "SequenceState",
+    BasicState._fields + ("specs", "value",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Sequence(Obj):
     """``SEQUENCE`` structure type
 
@@ -4413,6 +5949,12 @@ class Sequence(Obj):
     defaulted values existence validation by setting
     ``"allow_default_values": True`` :ref:`context <ctx>` option.
 
+    .. warning::
+
+       Check for default value existence is not performed in
+       ``evgen_mode``, because previously decoded values are not stored
+       in memory, to be able to compare them.
+
     Two sequences are equal if they have equal specification (schema),
     implicit/explicit tagging and the same values.
     """
@@ -4434,7 +5976,7 @@ class Sequence(Obj):
         if schema is None:
             schema = getattr(self, "schema", ())
         self.specs = (
-            schema if isinstance(schema, OrderedDict) else OrderedDict(schema)
+            schema if schema.__class__ == OrderedDict else OrderedDict(schema)
         )
         self._value = {}
         if value is not None:
@@ -4454,38 +5996,48 @@ class Sequence(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
-                self._value = default_obj.copy()._value
+                self._value = copy(default_obj._value)
 
     @property
     def ready(self):
-        for name, spec in self.specs.items():
+        for name, spec in iteritems(self.specs):
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
                     continue
                 return False
-            else:
-                if not value.ready:
-                    return False
+            if not value.ready:
+                return False
         return True
 
     @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
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj._value = {k: v.copy() for k, v in self._value.items()}
-        return obj
+        return any(value.bered for value in itervalues(self._value))
+
+    def __getstate__(self):
+        return SequenceState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self.specs,
+            {k: copy(v) for k, v in iteritems(self._value)},
+        )
+
+    def __setstate__(self, state):
+        super(Sequence, self).__setstate__(state)
+        self.specs = state.specs
+        self._value = state.value
 
     def __eq__(self, their):
         if not isinstance(their, self.__class__):
@@ -4543,22 +6095,26 @@ class Sequence(Obj):
             return spec.default
         return None
 
-    def _encoded_values(self):
-        raws = []
-        for name, spec in self.specs.items():
+    def _values_for_encoding(self):
+        for name, spec in iteritems(self.specs):
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
                     continue
                 raise ObjNotReady(name)
-            raws.append(value.encode())
-        return raws
+            yield value
 
     def _encode(self):
-        v = b"".join(self._encoded_values())
+        v = b"".join(v.encode() for v in self._values_for_encoding())
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode_cer(self, writer):
+        write_full(writer, self.tag + LENINDEF)
+        for v in self._values_for_encoding():
+            v.encode_cer(writer)
+        write_full(writer, EOC)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4575,6 +6131,7 @@ class Sequence(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
@@ -4611,7 +6168,7 @@ class Sequence(Obj):
         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 iteritems(self.specs):
             if spec.optional and (
                     (lenindef and v[:EOC_LEN].tobytes() == EOC) or
                     len(v) == 0
@@ -4619,20 +6176,33 @@ class Sequence(Obj):
                 continue
             sub_decode_path = decode_path + (name,)
             try:
-                value, v_tail = spec.decode(
-                    v,
-                    sub_offset,
-                    leavemm=True,
-                    decode_path=sub_decode_path,
-                    ctx=ctx,
-                )
-            except TagMismatch:
-                if spec.optional:
+                if evgen_mode:
+                    for _decode_path, value, v_tail in spec.decode_evgen(
+                            v,
+                            sub_offset,
+                            leavemm=True,
+                            decode_path=sub_decode_path,
+                            ctx=ctx,
+                            _ctx_immutable=False,
+                    ):
+                        yield _decode_path, value, v_tail
+                else:
+                    _, value, v_tail = next(spec.decode_evgen(
+                        v,
+                        sub_offset,
+                        leavemm=True,
+                        decode_path=sub_decode_path,
+                        ctx=ctx,
+                        _ctx_immutable=False,
+                        _evgen_mode=False,
+                    ))
+            except TagMismatch as err:
+                if (len(err.decode_path) == len(decode_path) + 1) and spec.optional:
                     continue
                 raise
 
             defined = get_def_by_path(ctx.get("_defines", ()), sub_decode_path)
-            if defined is not None:
+            if not evgen_mode and defined is not None:
                 defined_by, defined_spec = defined
                 if issubclass(value.__class__, SequenceOf):
                     for i, _value in enumerate(value):
@@ -4649,6 +6219,7 @@ class Sequence(Obj):
                             leavemm=True,
                             decode_path=sub_sub_decode_path,
                             ctx=ctx,
+                            _ctx_immutable=False,
                         )
                         if len(defined_tail) > 0:
                             raise DecodeError(
@@ -4668,6 +6239,7 @@ class Sequence(Obj):
                         leavemm=True,
                         decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
                         ctx=ctx,
+                        _ctx_immutable=False,
                     )
                     if len(defined_tail) > 0:
                         raise DecodeError(
@@ -4682,31 +6254,32 @@ class Sequence(Obj):
             vlen += value_len
             sub_offset += value_len
             v = v_tail
-            if spec.default is not None and value == spec.default:
-                if ctx_bered or ctx_allow_default_values:
-                    ber_encoded = True
-                else:
-                    raise DecodeError(
-                        "DEFAULT value met",
-                        klass=self.__class__,
-                        decode_path=sub_decode_path,
-                        offset=sub_offset,
-                    )
-            values[name] = value
-
-            spec_defines = getattr(spec, "defines", ())
-            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 not evgen_mode:
+                if spec.default is not None and value == spec.default:
+                    # This will not work in evgen_mode
+                    if ctx_bered or ctx_allow_default_values:
+                        ber_encoded = True
+                    else:
+                        raise DecodeError(
+                            "DEFAULT value met",
+                            klass=self.__class__,
+                            decode_path=sub_decode_path,
+                            offset=sub_offset,
+                        )
+                values[name] = value
+                spec_defines = getattr(spec, "defines", ())
+                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(
@@ -4735,7 +6308,7 @@ class Sequence(Obj):
         obj._value = values
         obj.lenindef = lenindef
         obj.ber_encoded = ber_encoded
-        return obj, tail
+        yield decode_path, obj, tail
 
     def __repr__(self):
         value = pp_console_row(next(self.pps()))
@@ -4749,6 +6322,7 @@ class Sequence(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -4786,8 +6360,8 @@ class Set(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
+    during decode. If :ref:`bered <bered_ctx>` context option is set,
+    then no error will occur. Also you can disable strict values
     ordering check by setting ``"allow_unordered_set": True``
     :ref:`context <ctx>` option.
     """
@@ -4796,12 +6370,22 @@ class Set(Sequence):
     asn1_type_name = "SET"
 
     def _encode(self):
-        raws = self._encoded_values()
-        raws.sort()
-        v = b"".join(raws)
+        v = b"".join(value.encode() for value in sorted(
+            self._values_for_encoding(),
+            key=attrgetter("tag_order"),
+        ))
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode_cer(self, writer):
+        write_full(writer, self.tag + LENINDEF)
+        for v in sorted(
+                self._values_for_encoding(),
+                key=attrgetter("tag_order_cer"),
+        ):
+            v.encode_cer(writer)
+        write_full(writer, EOC)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4818,6 +6402,7 @@ class Set(Sequence):
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
@@ -4854,12 +6439,13 @@ class Set(Sequence):
         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
+        tag_order_prev = (0, 0)
+        _specs_items = copy(self.specs)
+
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
-            for name, spec in specs_items():
+            for name, spec in iteritems(_specs_items):
                 sub_decode_path = decode_path + (name,)
                 try:
                     spec.decode(
@@ -4869,6 +6455,7 @@ class Set(Sequence):
                         decode_path=sub_decode_path,
                         ctx=ctx,
                         tag_only=True,
+                        _ctx_immutable=False,
                     )
                 except TagMismatch:
                     continue
@@ -4879,15 +6466,29 @@ class Set(Sequence):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            value, v_tail = spec.decode(
-                v,
-                sub_offset,
-                leavemm=True,
-                decode_path=sub_decode_path,
-                ctx=ctx,
-            )
+            if evgen_mode:
+                for _decode_path, value, v_tail in spec.decode_evgen(
+                        v,
+                        sub_offset,
+                        leavemm=True,
+                        decode_path=sub_decode_path,
+                        ctx=ctx,
+                        _ctx_immutable=False,
+                ):
+                    yield _decode_path, value, v_tail
+            else:
+                _, value, v_tail = next(spec.decode_evgen(
+                    v,
+                    sub_offset,
+                    leavemm=True,
+                    decode_path=sub_decode_path,
+                    ctx=ctx,
+                    _ctx_immutable=False,
+                    _evgen_mode=False,
+                ))
+            value_tag_order = value.tag_order
             value_len = value.fulllen
-            if value_prev.tobytes() > v[:value_len].tobytes():
+            if tag_order_prev >= value_tag_order:
                 if ctx_bered or ctx_allow_unordered_set:
                     ber_encoded = True
                 else:
@@ -4909,10 +6510,12 @@ class Set(Sequence):
                     offset=sub_offset,
                 )
             values[name] = value
-            value_prev = v[:value_len]
+            del _specs_items[name]
+            tag_order_prev = value_tag_order
             sub_offset += value_len
             vlen += value_len
             v = v_tail
+
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
@@ -4931,16 +6534,25 @@ class Set(Sequence):
                 )
             tail = v[EOC_LEN:]
             obj.lenindef = True
-        obj._value = values
-        if not obj.ready:
-            raise DecodeError(
-                "not all values are ready",
-                klass=self.__class__,
-                decode_path=decode_path,
-                offset=offset,
-            )
+        for name, spec in iteritems(self.specs):
+            if name not in values and not spec.optional:
+                raise DecodeError(
+                    "%s value is not ready" % name,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+        if not evgen_mode:
+            obj._value = values
         obj.ber_encoded = ber_encoded
-        return obj, tail
+        yield decode_path, obj, tail
+
+
+SequenceOfState = namedtuple(
+    "SequenceOfState",
+    BasicState._fields + ("spec", "value", "bound_min", "bound_max"),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class SequenceOf(Obj):
@@ -4969,9 +6581,21 @@ class SequenceOf(Obj):
     >>> ints
     Ints SEQUENCE OF[INTEGER 123, INTEGER 345]
 
-    Also you can initialize sequence with preinitialized values:
+    You can initialize sequence with preinitialized values:
 
     >>> ints = Ints([Integer(123), Integer(234)])
+
+    Also you can use iterator as a value:
+
+    >>> ints = Ints(iter(Integer(i) for i in range(1000000)))
+
+    And it won't be iterated until encoding process. Pay attention that
+    bounds and required schema checks are done only during the encoding
+    process in that case! After encode was called, then value is zeroed
+    back to empty list and you have to set it again. That mode is useful
+    mainly with CER encoding mode, where all objects from the iterable
+    will be streamed to the buffer, without copying all of them to
+    memory first.
     """
     __slots__ = ("spec", "_bound_min", "_bound_max")
     tag_default = tag_encode(form=TagFormConstructed, num=16)
@@ -4988,13 +6612,7 @@ class SequenceOf(Obj):
             optional=False,
             _decoded=(0, 0, 0),
     ):
-        super(SequenceOf, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super(SequenceOf, self).__init__(impl, expl, default, optional, _decoded)
         if schema is None:
             schema = getattr(self, "schema", None)
         if schema is None:
@@ -5018,24 +6636,34 @@ class SequenceOf(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
-                self._value = default_obj.copy()._value
+                self._value = copy(default_obj._value)
 
     def _value_sanitize(self, value):
+        iterator = False
         if issubclass(value.__class__, SequenceOf):
             value = value._value
+        elif hasattr(value, NEXT_ATTR_NAME):
+            iterator = True
+            value = value
         elif hasattr(value, "__iter__"):
             value = list(value)
         else:
-            raise InvalidValueType((self.__class__, iter))
-        if not self._bound_min <= len(value) <= self._bound_max:
-            raise BoundsError(self._bound_min, len(value), self._bound_max)
-        for v in value:
-            if not isinstance(v, self.spec.__class__):
-                raise InvalidValueType((self.spec.__class__,))
+            raise InvalidValueType((self.__class__, iter, "iterator"))
+        if not iterator:
+            if not self._bound_min <= len(value) <= self._bound_max:
+                raise BoundsError(self._bound_min, len(value), self._bound_max)
+            class_expected = self.spec.__class__
+            for v in value:
+                if not isinstance(v, class_expected):
+                    raise InvalidValueType((class_expected,))
         return value
 
     @property
     def ready(self):
+        if hasattr(self._value, NEXT_ATTR_NAME):
+            return True
+        if self._bound_min > 0 and len(self._value) == 0:
+            return False
         return all(v.ready for v in self._value)
 
     @property
@@ -5044,19 +6672,34 @@ class SequenceOf(Obj):
             return True
         return any(v.bered for v in self._value)
 
-    def copy(self):
-        obj = self.__class__(schema=self.spec)
-        obj._bound_min = self._bound_min
-        obj._bound_max = self._bound_max
-        obj.tag = self.tag
-        obj._expl = self._expl
-        obj.default = self.default
-        obj.optional = self.optional
-        obj.offset = self.offset
-        obj.llen = self.llen
-        obj.vlen = self.vlen
-        obj._value = [v.copy() for v in self._value]
-        return obj
+    def __getstate__(self):
+        if hasattr(self._value, NEXT_ATTR_NAME):
+            raise ValueError("can not pickle SequenceOf with iterator")
+        return SequenceOfState(
+            __version__,
+            self.tag,
+            self._tag_order,
+            self._expl,
+            self.default,
+            self.optional,
+            self.offset,
+            self.llen,
+            self.vlen,
+            self.expl_lenindef,
+            self.lenindef,
+            self.ber_encoded,
+            self.spec,
+            [copy(v) for v in self._value],
+            self._bound_min,
+            self._bound_max,
+        )
+
+    def __setstate__(self, state):
+        super(SequenceOf, self).__setstate__(state)
+        self.spec = state.spec
+        self._value = state.value
+        self._bound_min = state.bound_min
+        self._bound_max = state.bound_max
 
     def __eq__(self, their):
         if isinstance(their, self.__class__):
@@ -5107,11 +6750,9 @@ class SequenceOf(Obj):
         self._value.append(value)
 
     def __iter__(self):
-        self._assert_ready()
         return iter(self._value)
 
     def __len__(self):
-        self._assert_ready()
         return len(self._value)
 
     def __setitem__(self, key, value):
@@ -5122,14 +6763,58 @@ class SequenceOf(Obj):
     def __getitem__(self, key):
         return self._value[key]
 
-    def _encoded_values(self):
-        return [v.encode() for v in self._value]
+    def _values_for_encoding(self):
+        return iter(self._value)
 
     def _encode(self):
-        v = b"".join(self._encoded_values())
-        return b"".join((self.tag, len_encode(len(v)), v))
+        iterator = hasattr(self._value, NEXT_ATTR_NAME)
+        if iterator:
+            values = []
+            values_append = values.append
+            class_expected = self.spec.__class__
+            values_for_encoding = self._values_for_encoding()
+            self._value = []
+            for v in values_for_encoding:
+                if not isinstance(v, class_expected):
+                    raise InvalidValueType((class_expected,))
+                values_append(v.encode())
+            if not self._bound_min <= len(values) <= self._bound_max:
+                raise BoundsError(self._bound_min, len(values), self._bound_max)
+            value = b"".join(values)
+        else:
+            value = b"".join(v.encode() for v in self._values_for_encoding())
+        return b"".join((self.tag, len_encode(len(value)), value))
+
+    def _encode_cer(self, writer):
+        write_full(writer, self.tag + LENINDEF)
+        iterator = hasattr(self._value, NEXT_ATTR_NAME)
+        if iterator:
+            class_expected = self.spec.__class__
+            values_count = 0
+            values_for_encoding = self._values_for_encoding()
+            self._value = []
+            for v in values_for_encoding:
+                if not isinstance(v, class_expected):
+                    raise InvalidValueType((class_expected,))
+                v.encode_cer(writer)
+                values_count += 1
+            if not self._bound_min <= values_count <= self._bound_max:
+                raise BoundsError(self._bound_min, values_count, self._bound_max)
+        else:
+            for v in self._values_for_encoding():
+                v.encode_cer(writer)
+        write_full(writer, EOC)
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
+    def _decode(
+            self,
+            tlv,
+            offset,
+            decode_path,
+            ctx,
+            tag_only,
+            evgen_mode,
+            ordering_check=False,
+    ):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -5146,6 +6831,7 @@ class SequenceOf(Obj):
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
@@ -5180,6 +6866,7 @@ class SequenceOf(Obj):
         vlen = 0
         sub_offset = offset + tlen + llen
         _value = []
+        _value_count = 0
         ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
         value_prev = memoryview(v[:0])
         ber_encoded = False
@@ -5187,14 +6874,27 @@ class SequenceOf(Obj):
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
-            sub_decode_path = decode_path + (str(len(_value)),)
-            value, v_tail = spec.decode(
-                v,
-                sub_offset,
-                leavemm=True,
-                decode_path=sub_decode_path,
-                ctx=ctx,
-            )
+            sub_decode_path = decode_path + (str(_value_count),)
+            if evgen_mode:
+                for _decode_path, value, v_tail in spec.decode_evgen(
+                        v,
+                        sub_offset,
+                        leavemm=True,
+                        decode_path=sub_decode_path,
+                        ctx=ctx,
+                        _ctx_immutable=False,
+                ):
+                    yield _decode_path, value, v_tail
+            else:
+                _, value, v_tail = next(spec.decode_evgen(
+                    v,
+                    sub_offset,
+                    leavemm=True,
+                    decode_path=sub_decode_path,
+                    ctx=ctx,
+                    _ctx_immutable=False,
+                    _evgen_mode=False,
+                ))
             value_len = value.fulllen
             if ordering_check:
                 if value_prev.tobytes() > v[:value_len].tobytes():
@@ -5208,13 +6908,22 @@ class SequenceOf(Obj):
                             offset=sub_offset,
                         )
                 value_prev = v[:value_len]
-            _value.append(value)
+            _value_count += 1
+            if not evgen_mode:
+                _value.append(value)
             sub_offset += value_len
             vlen += value_len
             v = v_tail
+        if evgen_mode and not self._bound_min <= _value_count <= self._bound_max:
+            raise DecodeError(
+                msg=str(BoundsError(self._bound_min, _value_count, self._bound_max)),
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
         try:
             obj = self.__class__(
-                value=_value,
+                value=None if evgen_mode else _value,
                 schema=spec,
                 bounds=(self._bound_min, self._bound_max),
                 impl=self.tag,
@@ -5241,7 +6950,7 @@ class SequenceOf(Obj):
             obj.lenindef = True
             tail = v[EOC_LEN:]
         obj.ber_encoded = ber_encoded
-        return obj, tail
+        yield decode_path, obj, tail
 
     def __repr__(self):
         return "%s[%s]" % (
@@ -5251,6 +6960,7 @@ class SequenceOf(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -5286,19 +6996,32 @@ class SetOf(SequenceOf):
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET OF"
 
+    def _value_sanitize(self, value):
+        value = super(SetOf, self)._value_sanitize(value)
+        if hasattr(value, NEXT_ATTR_NAME):
+            raise ValueError(
+                "SetOf does not support iterator values, as no sense in them"
+            )
+        return value
+
     def _encode(self):
-        raws = self._encoded_values()
-        raws.sort()
-        v = b"".join(raws)
+        v = b"".join(sorted(v.encode() for v in self._values_for_encoding()))
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode_cer(self, writer):
+        write_full(writer, self.tag + LENINDEF)
+        for v in sorted(encode_cer(v) for v in self._values_for_encoding()):
+            write_full(writer, v)
+        write_full(writer, EOC)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         return super(SetOf, self)._decode(
             tlv,
             offset,
             decode_path,
             ctx,
             tag_only,
+            evgen_mode,
             ordering_check=True,
         )
 
@@ -5326,7 +7049,7 @@ def generic_decoder():  # pragma: no cover
     choice = PrimitiveTypes()
     choice.specs["SequenceOf"] = SequenceOf(schema=choice)
     choice.specs["SetOf"] = SetOf(schema=choice)
-    for i in range(31):
+    for i in six_xrange(31):
         choice.specs["SequenceOf%d" % i] = SequenceOf(
             schema=choice,
             expl=tag_ctxc(i),
@@ -5340,7 +7063,7 @@ def generic_decoder():  # pragma: no cover
 
     def pprint_any(
             obj,
-            oids=None,
+            oid_maps=(),
             with_colours=False,
             with_decode_path=False,
             decode_path_only=(),
@@ -5349,8 +7072,8 @@ def generic_decoder():  # pragma: no cover
             for pp in pps:
                 if hasattr(pp, "_fields"):
                     if (
-                        decode_path_only != () and
-                        pp.decode_path[:len(decode_path_only)] != decode_path_only
+                            decode_path_only != () and
+                            pp.decode_path[:len(decode_path_only)] != decode_path_only
                     ):
                         continue
                     if pp.asn1_type_name == Choice.asn1_type_name:
@@ -5360,7 +7083,7 @@ def generic_decoder():  # pragma: no cover
                     pp = _pp(**pp_kwargs)
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
@@ -5368,8 +7091,8 @@ def generic_decoder():  # pragma: no cover
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
-                        pp,
-                        decode_path_len_decrease=len(decode_path_only),
+                            pp,
+                            decode_path_len_decrease=len(decode_path_only),
                     ):
                         yield row
                 else:
@@ -5390,7 +7113,7 @@ def main():  # pragma: no cover
     )
     parser.add_argument(
         "--oids",
-        help="Python path to dictionary with OIDs",
+        help="Python paths to dictionary with OIDs, comma separated",
     )
     parser.add_argument(
         "--schema",
@@ -5420,15 +7143,26 @@ def main():  # pragma: no cover
         help="Allow explicit tag out-of-bound",
     )
     parser.add_argument(
-        "DERFile",
+        "--evgen",
+        action="store_true",
+        help="Turn on event generation mode",
+    )
+    parser.add_argument(
+        "RAWFile",
         type=argparse.FileType("rb"),
-        help="Path to DER file you want to decode",
+        help="Path to BER/CER/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 {}
+    if PY2:
+        args.RAWFile.seek(args.skip)
+        raw = memoryview(args.RAWFile.read())
+        args.RAWFile.close()
+    else:
+        raw = file_mmaped(args.RAWFile)[args.skip:]
+    oid_maps = (
+        [obj_by_path(_path) for _path in (args.oids or "").split(",")]
+        if args.oids else ()
+    )
     if args.schema:
         schema = obj_by_path(args.schema)
         from functools import partial
@@ -5441,17 +7175,23 @@ def main():  # pragma: no cover
     }
     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,
+    from os import environ
+    pprinter = partial(
+        pprinter,
+        oid_maps=oid_maps,
+        with_colours=environ.get("NO_COLOR") is None,
         with_decode_path=args.print_decode_path,
         decode_path_only=(
             () if args.decode_path_only is None else
             tuple(args.decode_path_only.split(":"))
         ),
-    ))
+    )
+    if args.evgen:
+        for decode_path, obj, tail in schema().decode_evgen(raw, ctx=ctx):
+            print(pprinter(obj, decode_path=decode_path))
+    else:
+        obj, tail = schema().decode(raw, ctx=ctx)
+        print(pprinter(obj))
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))