]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Raise copyright years
[pyderasn.git] / pyderasn.py
index 0b04f4eda76be9921b6de95d90e54d6c297e6e16..54242a3bd537fc9360217174cb79b9ccf832a73a 100755 (executable)
@@ -1,7 +1,10 @@
 #!/usr/bin/env python
 # coding: utf-8
 #!/usr/bin/env python
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
+# cython: language_level=3
+# pylint: disable=line-too-long,superfluous-parens,protected-access,too-many-lines
+# pylint: disable=too-many-return-statements,too-many-branches,too-many-statements
+# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
+# Copyright (C) 2017-2024 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""Python ASN.1 DER/BER codec with abstract structures
+"""Python ASN.1 DER/CER/BER codec with abstract structures
 
 
-This library allows you to marshal various structures in ASN.1 DER
-format, unmarshal them in BER/CER/DER ones.
+This library allows you to marshal various structures in ASN.1 DER/CER
+format, unmarshal BER/CER/DER ones.
 
     >>> i = Integer(123)
     >>> raw = i.encode()
 
     >>> i = Integer(123)
     >>> raw = i.encode()
-    >>> Integer().decode(raw) == i
+    >>> Integer().decod(raw) == i
     True
 
 There are primitive types, holding single values
     True
 
 There are primitive types, holding single values
@@ -65,17 +68,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
 
 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
 
 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.
 
 ::
 
 
 ::
 
@@ -161,21 +173,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.
 
 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.
 
 safely mutated.
 
+Also all objects can be safely ``pickle``-d, but pay attention that
+pickling among different PyDERASN versions is prohibited.
+
 .. _decoding:
 
 Decoding
 --------
 
 .. _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.
 
 ``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:
 
 When object is decoded, ``decoded`` property is true and you can safely
 use following properties:
 
@@ -205,9 +223,9 @@ When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
 Context
 _______
 
 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:
 
 
 Currently available context options:
 
@@ -216,6 +234,8 @@ 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:`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>`
+* :ref:`keep_memoryview <keep_memoryview_ctx>`
 
 .. _pprinting:
 
 
 .. _pprinting:
 
@@ -333,8 +353,13 @@ Let's parse that output, human::
  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``
  Only applicable to BER encoded data. If object has BER-specific
  encoding, then ``BER`` will be shown. It does not depend on indefinite
  length encoding. ``EOC``, ``BOOLEAN``, ``BIT STRING``, ``OCTET STRING``
- (and its derivatives), ``SET``, ``SET OF`` could be BERed.
+ (and its derivatives), ``SET``, ``SET OF``, ``UTCTime``, ``GeneralizedTime``
+ could be BERed.
 
 
+Also it could be helpful to add quick ASN.1 pprinting command in your
+pdb's configuration file::
+
+    alias pp1 import pyderasn ;; print(pyderasn.pprint(%1, oid_maps=(locals().get("OID_STR_TO_NAME", {}),)))
 
 .. _definedby:
 
 
 .. _definedby:
 
@@ -411,8 +436,8 @@ defines_by_path context option
 ______________________________
 
 Sometimes you either can not or do not want to explicitly set *defines*
 ______________________________
 
 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::
 
 Specify ``defines_by_path`` key in the :ref:`decode context <ctx>`. Its
 value must be sequence of following tuples::
@@ -429,7 +454,7 @@ For example, again for CMS, you want to automatically decode
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
-    content_info, tail = ContentInfo().decode(data, ctx={"defines_by_path": (
+    content_info = ContentInfo().decod(data, ctx={"defines_by_path": (
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
@@ -476,15 +501,15 @@ useful for SEQUENCE/SET OF-s.
 BER encoding
 ------------
 
 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
 constructed primitive types should be parsed successfully.
 
 * If object is encoded in BER form (not the DER one), then ``ber_encoded``
   attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET
-  STRING``, ``OBJECT IDENTIFIER``, ``SEQUENCE``, ``SET``, ``SET OF``
-  can contain it.
+  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
 * 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
@@ -517,6 +542,384 @@ 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.
 
    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"]))
+
+.. note::
+
+   SEQUENCE/SET values with DEFAULT specified are automatically decoded
+   without evgen mode.
+
+.. _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 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.
+
+.. _keep_memoryview_ctx:
+
+That read-only memoryview could be safe to be used as a value inside
+decoded :py:class:`pyderasn.OctetString` and :py:class:`pyderasn.Any`
+objects. You can enable that by setting `"keep_memoryview": True` in
+:ref:`decode context <ctx>`. No OCTET STRING and ANY values will be
+copied to memory. Of course that works only in DER encoding, where the
+value is continuously encoded.
+
+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::
+
+    with open("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, float("+inf"))
+        der_forced = True
+
+.. _agg_octet_string:
+
+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.
+
+.. _seqof-iterators:
+
+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.
+
+2-pass DER encoding
+-------------------
+
+There is ability to do 2-pass encoding to DER, writing results directly
+to specified writer (buffer, file, whatever). It could be 1.5+ times
+slower than ordinary encoding, but it takes little memory for 1st pass
+state storing. For example, 1st pass state for CACert.org's CRL with
+~416K of certificate entries takes nearly 3.5 MB of memory.
+``SignedData`` with several gigabyte ``EncapsulatedContentInfo`` takes
+nearly 0.5 KB of memory.
+
+If you use :ref:`mmap-ed <mmap>` memoryviews, :ref:`SEQUENCE OF
+iterators <seqof-iterators>` and write directly to opened file, then
+there is very small memory footprint.
+
+1st pass traverses through all the objects of the structure and returns
+the size of DER encoded structure, together with 1st pass state object.
+That state contains precalculated lengths for various objects inside the
+structure.
+
+::
+
+    fulllen, state = obj.encode1st()
+
+2nd pass takes the writer and 1st pass state. It traverses through all
+the objects again, but writes their encoded representation to the writer.
+
+::
+
+    with open("result", "wb") as fd:
+        obj.encode2nd(fd.write, iter(state))
+
+.. warning::
+
+   You **MUST NOT** use 1st pass state if anything is changed in the
+   objects. It is intended to be used immediately after 1st pass is
+   done!
+
+If you use :ref:`SEQUENCE OF iterators <seqof-iterators>`, then you
+have to reinitialize the values after the 1st pass. And you **have to**
+be sure that the iterator gives exactly the same values as previously.
+Yes, you have to run your iterator twice -- because this is two pass
+encoding mode.
+
+If you want to encode to the memory, then you can use convenient
+:py:func:`pyderasn.encode2pass` helper.
+
+.. _browser:
+
+ASN.1 browser
+-------------
+.. autofunction:: pyderasn.browse
+
+Base Obj
+--------
+.. autoclass:: pyderasn.Obj
+   :members:
+
 Primitive types
 ---------------
 
 Primitive types
 ---------------
 
@@ -528,12 +931,12 @@ _______
 Integer
 _______
 .. autoclass:: pyderasn.Integer
 Integer
 _______
 .. autoclass:: pyderasn.Integer
-   :members: __init__
+   :members: __init__, named, tohex
 
 BitString
 _________
 .. autoclass:: pyderasn.BitString
 
 BitString
 _________
 .. autoclass:: pyderasn.BitString
-   :members: __init__
+   :members: __init__, bit_len, named
 
 OctetString
 ___________
 
 OctetString
 ___________
@@ -565,15 +968,25 @@ _____________
 PrintableString
 _______________
 .. autoclass:: pyderasn.PrintableString
 PrintableString
 _______________
 .. autoclass:: pyderasn.PrintableString
+   :members: __init__, allow_asterisk, allow_ampersand
+
+IA5String
+_________
+.. autoclass:: pyderasn.IA5String
+
+VisibleString
+_____________
+.. autoclass:: pyderasn.VisibleString
 
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
 
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
-   :members: __init__, todatetime
+   :members: __init__, todatetime, totzdatetime
 
 GeneralizedTime
 _______________
 .. autoclass:: pyderasn.GeneralizedTime
 
 GeneralizedTime
 _______________
 .. autoclass:: pyderasn.GeneralizedTime
+   :members: __init__, todatetime, totzdatetime
 
 Special types
 -------------
 
 Special types
 -------------
@@ -581,7 +994,7 @@ Special types
 Choice
 ______
 .. autoclass:: pyderasn.Choice
 Choice
 ______
 .. autoclass:: pyderasn.Choice
-   :members: __init__
+   :members: __init__, choice, value
 
 PrimitiveTypes
 ______________
 
 PrimitiveTypes
 ______________
@@ -619,17 +1032,23 @@ Various
 -------
 
 .. autofunction:: pyderasn.abs_decode_path
 -------
 
 .. autofunction:: pyderasn.abs_decode_path
+.. autofunction:: pyderasn.agg_octet_string
+.. autofunction:: pyderasn.ascii_visualize
 .. autofunction:: pyderasn.colonize_hex
 .. autofunction:: pyderasn.colonize_hex
+.. autofunction:: pyderasn.encode2pass
+.. autofunction:: pyderasn.encode_cer
+.. autofunction:: pyderasn.file_mmaped
 .. autofunction:: pyderasn.hexenc
 .. autofunction:: pyderasn.hexdec
 .. autofunction:: pyderasn.hexenc
 .. autofunction:: pyderasn.hexdec
+.. autofunction:: pyderasn.hexdump
 .. autofunction:: pyderasn.tag_encode
 .. autofunction:: pyderasn.tag_decode
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
 .. 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.DecodeError
    :members: __init__
 .. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.ExceedingData
 .. autoclass:: pyderasn.LenIndefForm
 .. autoclass:: pyderasn.TagMismatch
 .. autoclass:: pyderasn.InvalidLength
 .. autoclass:: pyderasn.LenIndefForm
 .. autoclass:: pyderasn.TagMismatch
 .. autoclass:: pyderasn.InvalidLength
@@ -638,34 +1057,132 @@ Various
 .. autoclass:: pyderasn.ObjNotReady
 .. autoclass:: pyderasn.InvalidValueType
 .. autoclass:: pyderasn.BoundsError
 .. 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 codecs import getdecoder
-from codecs import getencoder
+from array import array
 from collections import namedtuple
 from collections import OrderedDict
 from copy import copy
 from datetime import datetime
 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 math import ceil
-from os import environ
+from operator import attrgetter
 from string import ascii_letters
 from string import digits
 from string import ascii_letters
 from string import digits
-
-from six import add_metaclass
-from six import binary_type
-from six import byte2int
-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
-
+from struct import Struct as struct_Struct
+from sys import maxsize as sys_maxsize
+from sys import version_info
+from unicodedata import category as unicat
 
 try:
     from termcolor import colored
 
 try:
     from termcolor import colored
@@ -673,18 +1190,30 @@ except ImportError:  # pragma: no cover
     def colored(what, *args, **kwargs):
         return what
 
     def colored(what, *args, **kwargs):
         return what
 
-__version__ = "5.5"
+try:
+    from dateutil.tz import UTC as tzUTC
+except ImportError:  # pragma: no cover
+    tzUTC = "missing"
+
+
+__version__ = "9.3"
 
 __all__ = (
 
 __all__ = (
+    "agg_octet_string",
     "Any",
     "BitString",
     "BMPString",
     "Boolean",
     "BoundsError",
     "Choice",
     "Any",
     "BitString",
     "BMPString",
     "Boolean",
     "BoundsError",
     "Choice",
+    "colonize_hex",
     "DecodeError",
     "DecodePathDefBy",
     "DecodeError",
     "DecodePathDefBy",
+    "encode2pass",
+    "encode_cer",
     "Enumerated",
     "Enumerated",
+    "ExceedingData",
+    "file_mmaped",
     "GeneralizedTime",
     "GeneralString",
     "GraphicString",
     "GeneralizedTime",
     "GeneralString",
     "GraphicString",
@@ -745,7 +1274,50 @@ TagClassReprs = {
 EOC = b"\x00\x00"
 EOC_LEN = len(EOC)
 LENINDEF = b"\x80"  # length indefinite mark
 EOC = b"\x00\x00"
 EOC_LEN = len(EOC)
 LENINDEF = b"\x80"  # length indefinite mark
-LENINDEF_PP_CHAR = "I" if PY2 else "∞"
+LENINDEF_PP_CHAR = "∞"
+NAMEDTUPLE_KWARGS = {} if version_info < (3, 6) else {"module": __name__}
+SET01 = frozenset("01")
+DECIMALS = frozenset(digits)
+DECIMAL_SIGNS = ".,"
+NEXT_ATTR_NAME = "__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
+
+    .. warning::
+
+       It does not work under Windows.
+    """
+    import mmap
+    return memoryview(mmap.mmap(fd.fileno(), length=0, prot=mmap.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 (p1 is not any) and (p1 != p2):
+                break
+        else:
+            return define
 
 
 ########################################################################
 
 
 ########################################################################
@@ -768,7 +1340,7 @@ class DecodeError(ASN1Error):
                             decoding process has passed
         :param int offset: binary offset where failure happened
         """
                             decoding process has passed
         :param int offset: binary offset where failure happened
         """
-        super(DecodeError, self).__init__()
+        super().__init__()
         self.msg = msg
         self.klass = klass
         self.decode_path = decode_path
         self.msg = msg
         self.klass = klass
         self.decode_path = decode_path
@@ -795,6 +1367,18 @@ class NotEnoughData(DecodeError):
     pass
 
 
     pass
 
 
+class ExceedingData(ASN1Error):
+    def __init__(self, nbytes):
+        super().__init__()
+        self.nbytes = nbytes
+
+    def __str__(self):
+        return "%d trailing bytes" % self.nbytes
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, self)
+
+
 class LenIndefForm(DecodeError):
     pass
 
 class LenIndefForm(DecodeError):
     pass
 
@@ -813,7 +1397,7 @@ class InvalidOID(DecodeError):
 
 class ObjUnknown(ASN1Error):
     def __init__(self, name):
 
 class ObjUnknown(ASN1Error):
     def __init__(self, name):
-        super(ObjUnknown, self).__init__()
+        super().__init__()
         self.name = name
 
     def __str__(self):
         self.name = name
 
     def __str__(self):
@@ -825,7 +1409,7 @@ class ObjUnknown(ASN1Error):
 
 class ObjNotReady(ASN1Error):
     def __init__(self, name):
 
 class ObjNotReady(ASN1Error):
     def __init__(self, name):
-        super(ObjNotReady, self).__init__()
+        super().__init__()
         self.name = name
 
     def __str__(self):
         self.name = name
 
     def __str__(self):
@@ -837,7 +1421,7 @@ class ObjNotReady(ASN1Error):
 
 class InvalidValueType(ASN1Error):
     def __init__(self, expected_types):
 
 class InvalidValueType(ASN1Error):
     def __init__(self, expected_types):
-        super(InvalidValueType, self).__init__()
+        super().__init__()
         self.expected_types = expected_types
 
     def __str__(self):
         self.expected_types = expected_types
 
     def __str__(self):
@@ -851,7 +1435,7 @@ class InvalidValueType(ASN1Error):
 
 class BoundsError(ASN1Error):
     def __init__(self, bound_min, value, bound_max):
 
 class BoundsError(ASN1Error):
     def __init__(self, bound_min, value, bound_max):
-        super(BoundsError, self).__init__()
+        super().__init__()
         self.bound_min = bound_min
         self.value = value
         self.bound_max = bound_max
         self.bound_min = bound_min
         self.value = value
         self.bound_max = bound_max
@@ -871,20 +1455,16 @@ class BoundsError(ASN1Error):
 # Basic coders
 ########################################################################
 
 # Basic coders
 ########################################################################
 
-_hexdecoder = getdecoder("hex")
-_hexencoder = getencoder("hex")
-
-
 def hexdec(data):
     """Binary data to hexadecimal string convert
     """
 def hexdec(data):
     """Binary data to hexadecimal string convert
     """
-    return _hexdecoder(data)[0]
+    return bytes.fromhex(data)
 
 
 def hexenc(data):
     """Hexadecimal string to binary data convert
     """
 
 
 def hexenc(data):
     """Hexadecimal string to binary data convert
     """
-    return _hexencoder(data)[0].decode("ascii")
+    return data.hex()
 
 
 def int_bytes_len(num, byte_len=8):
 
 
 def int_bytes_len(num, byte_len=8):
@@ -906,6 +1486,9 @@ def zero_ended_encode(num):
     return bytes(octets)
 
 
     return bytes(octets)
 
 
+int2byte = struct_Struct(">B").pack
+
+
 def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
     """Encode tag to binary form
 
 def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
     """Encode tag to binary form
 
@@ -934,13 +1517,13 @@ def tag_decode(tag):
     It returns tuple with three integers, as
     :py:func:`pyderasn.tag_encode` accepts.
     """
     It returns tuple with three integers, as
     :py:func:`pyderasn.tag_encode` accepts.
     """
-    first_octet = byte2int(tag)
+    first_octet = tag[0]
     klass = first_octet & 0xC0
     form = first_octet & 0x20
     if first_octet & 0x1F < 0x1F:
         return (klass, form, first_octet & 0x1F)
     num = 0
     klass = first_octet & 0xC0
     form = first_octet & 0x20
     if first_octet & 0x1F < 0x1F:
         return (klass, form, first_octet & 0x1F)
     num = 0
-    for octet in iterbytes(tag[1:]):
+    for octet in tag[1:]:
         num <<= 7
         num |= octet & 0x7F
     return (klass, form, num)
         num <<= 7
         num |= octet & 0x7F
     return (klass, form, num)
@@ -965,15 +1548,19 @@ def tag_strip(data):
     """
     if len(data) == 0:
         raise NotEnoughData("no data at all")
     """
     if len(data) == 0:
         raise NotEnoughData("no data at all")
-    if byte2int(data) & 0x1F < 31:
+    if data[0] & 0x1F < 31:
         return data[:1], 1, data[1:]
     i = 0
     while True:
         i += 1
         if i == len(data):
             raise DecodeError("unfinished tag")
         return data[:1], 1, data[1:]
     i = 0
     while True:
         i += 1
         if i == len(data):
             raise DecodeError("unfinished tag")
-        if indexbytes(data, i) & 0x80 == 0:
+        if data[i] & 0x80 == 0:
             break
             break
+    if i == 1 and data[1] < 0x1F:
+        raise DecodeError("unexpected long form")
+    if i > 1 and data[1] & 0x7F == 0:
+        raise DecodeError("leading zero byte in tag value")
     i += 1
     return data[:i], i, data[i:]
 
     i += 1
     return data[:i], i, data[i:]
 
@@ -983,7 +1570,7 @@ def len_encode(l):
         return int2byte(l)
     octets = bytearray(int_bytes_len(l) + 1)
     octets[0] = 0x80 | (len(octets) - 1)
         return int2byte(l)
     octets = bytearray(int_bytes_len(l) + 1)
     octets[0] = 0x80 | (len(octets) - 1)
-    for i in six_xrange(len(octets) - 1, 0, -1):
+    for i in range(len(octets) - 1, 0, -1):
         octets[i] = l & 0xFF
         l >>= 8
     return bytes(octets)
         octets[i] = l & 0xFF
         l >>= 8
     return bytes(octets)
@@ -997,7 +1584,7 @@ def len_decode(data):
     """
     if len(data) == 0:
         raise NotEnoughData("no data at all")
     """
     if len(data) == 0:
         raise NotEnoughData("no data at all")
-    first_octet = byte2int(data)
+    first_octet = data[0]
     if first_octet & 0x80 == 0:
         return first_octet, 1, data[1:]
     octets_num = first_octet & 0x7F
     if first_octet & 0x80 == 0:
         return first_octet, 1, data[1:]
     octets_num = first_octet & 0x7F
@@ -1005,28 +1592,99 @@ def len_decode(data):
         raise NotEnoughData("encoded length is longer than data")
     if octets_num == 0:
         raise LenIndefForm()
         raise NotEnoughData("encoded length is longer than data")
     if octets_num == 0:
         raise LenIndefForm()
-    if byte2int(data[1:]) == 0:
+    if data[1] == 0:
         raise DecodeError("leading zeros")
     l = 0
         raise DecodeError("leading zeros")
     l = 0
-    for v in iterbytes(data[1:1 + octets_num]):
+    for v in data[1:1 + octets_num]:
         l = (l << 8) | v
     if l <= 127:
         raise DecodeError("long form instead of short one")
     return l, 1 + octets_num, data[1 + octets_num:]
 
 
         l = (l << 8) | v
     if l <= 127:
         raise DecodeError("long form instead of short one")
     return l, 1 + octets_num, data[1 + octets_num:]
 
 
+LEN0 = len_encode(0)
+LEN1 = len_encode(1)
+LEN1K = len_encode(1000)
+
+
+def len_size(l):
+    """How many bytes length field will take
+    """
+    if l < 128:
+        return 1
+    if l < 256:  # 1 << 8
+        return 2
+    if l < 65536:  # 1 << 16
+        return 3
+    if l < 16777216:  # 1 << 24
+        return 4
+    if l < 4294967296:  # 1 << 32
+        return 5
+    if l < 1099511627776:  # 1 << 40
+        return 6
+    if l < 281474976710656:  # 1 << 48
+        return 7
+    if l < 72057594037927936:  # 1 << 56
+        return 8
+    raise OverflowError("too big length")
+
+
+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
+
+
+# If it is 64-bit system, then use compact 64-bit array of unsigned
+# longs. Use an ordinary list with universal integers otherwise, that
+# is slower.
+if sys_maxsize > 2 ** 32:
+    def state_2pass_new():
+        return array("L")
+else:
+    def state_2pass_new():
+        return []
+
+
 ########################################################################
 # Base class
 ########################################################################
 
 class AutoAddSlots(type):
 ########################################################################
 # Base class
 ########################################################################
 
 class AutoAddSlots(type):
-    def __new__(mcs, name, bases, _dict):
+    def __new__(cls, name, bases, _dict):
         _dict["__slots__"] = _dict.get("__slots__", ())
         _dict["__slots__"] = _dict.get("__slots__", ())
-        return type.__new__(mcs, name, bases, _dict)
+        return super().__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)
-class Obj(object):
+class Obj(metaclass=AutoAddSlots):
     """Common ASN.1 object class
 
     All ASN.1 types are inherited from it. It has metaclass that
     """Common ASN.1 object class
 
     All ASN.1 types are inherited from it. It has metaclass that
@@ -1034,6 +1692,7 @@ class Obj(object):
     """
     __slots__ = (
         "tag",
     """
     __slots__ = (
         "tag",
+        "_tag_order",
         "_value",
         "_expl",
         "default",
         "_value",
         "_expl",
         "default",
@@ -1058,6 +1717,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")
         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
         if default is not None:
             optional = True
         self.optional = optional
@@ -1089,21 +1755,50 @@ class Obj(object):
         """
         return (self.llen + self.vlen) > 0
 
         """
         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()
 
         """
         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):
     @property
     def tlen(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return len(self.tag)
 
     @property
     def tlvlen(self):
         return len(self.tag)
 
     @property
     def tlvlen(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
-        return self.__bytes__() if PY2 else self.__unicode__()
+        return self.__unicode__()
 
     def __ne__(self, their):
         return not(self == their)
 
     def __ne__(self, their):
         return not(self == their)
@@ -1120,15 +1815,80 @@ class Obj(object):
     def _encode(self):  # pragma: no cover
         raise NotImplementedError()
 
     def _encode(self):  # pragma: no cover
         raise NotImplementedError()
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):  # pragma: no cover
+    def _encode_cer(self, writer):
+        write_full(writer, self._encode())
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):  # pragma: no cover
+        yield NotImplemented
+
+    def _encode1st(self, state):
+        raise NotImplementedError()
+
+    def _encode2nd(self, writer, state_iter):
         raise NotImplementedError()
 
     def encode(self):
         raise NotImplementedError()
 
     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))
 
         raw = self._encode()
         if self._expl is None:
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
+    def encode1st(self, state=None):
+        """Do the 1st pass of 2-pass encoding
+
+        :rtype: (int, array("L"))
+        :returns: full length of encoded data and precalculated various
+                  objects lengths
+        """
+        if state is None:
+            state = state_2pass_new()
+        if self._expl is None:
+            return self._encode1st(state)
+        state.append(0)
+        idx = len(state) - 1
+        vlen, _ = self._encode1st(state)
+        state[idx] = vlen
+        fulllen = len(self._expl) + len_size(vlen) + vlen
+        return fulllen, state
+
+    def encode2nd(self, writer, state_iter):
+        """Do the 2nd pass of 2-pass encoding
+
+        :param writer: must comply with ``io.RawIOBase.write`` behaviour
+        :param state_iter: iterator over the 1st pass state (``iter(state)``)
+        """
+        if self._expl is None:
+            self._encode2nd(writer, state_iter)
+        else:
+            write_full(writer, self._expl + len_encode(next(state_iter)))
+            self._encode2nd(writer, state_iter)
+
+    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 hexencode(self):
+        """Do hexadecimal encoded :py:meth:`pyderasn.Obj.encode`
+        """
+        return hexenc(self.encode())
+
     def decode(
             self,
             data,
     def decode(
             self,
             data,
@@ -1145,29 +1905,78 @@ 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 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 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 ctx: optional :ref:`context <ctx>` governing decoding process
-        :param tag_only: decode only the tag, without length and contents
-                         (used only in Choice and Set structures, trying to
-                         determine if tag satisfies the scheme)
-        :param _ctx_immutable: do we need to copy ``ctx`` before using it
+        :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)
         :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.
+        .. seealso:: :ref:`evgen mode <evgen_mode>`.
         """
         if ctx is None:
             ctx = {}
         elif _ctx_immutable:
             ctx = copy(ctx)
         tlv = memoryview(data)
         """
         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:
         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 _decode_path is not decode_path:
+                    yield result
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
@@ -1196,16 +2005,20 @@ class Obj(object):
                     )
                 llen, v = 1, lv[1:]
                 offset += tlen + llen
                     )
                 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 _decode_path is not decode_path:
+                        yield result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
                     raise DecodeError(
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
                     raise DecodeError(
@@ -1231,16 +2044,20 @@ class Obj(object):
                         decode_path=decode_path,
                         offset=offset,
                     )
                         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 _decode_path is not 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",
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
                     raise DecodeError(
                         "explicit tag out-of-bound, longer than data",
@@ -1248,50 +2065,98 @@ class Obj(object):
                         decode_path=decode_path,
                         offset=offset,
                     )
                         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):
 
     @property
     def expled(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self._expl is not None
 
     @property
     def expl_tag(self):
         return self._expl is not None
 
     @property
     def expl_tag(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self._expl
 
     @property
     def expl_tlen(self):
         return self._expl
 
     @property
     def expl_tlen(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return len(self._expl)
 
     @property
     def expl_llen(self):
         return len(self._expl)
 
     @property
     def expl_llen(self):
+        """.. seealso:: :ref:`decoding`
+        """
         if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
         if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     @property
     def fulloffset(self):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     @property
     def fulloffset(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self.expl_offset if self.expled else self.offset
 
     @property
     def fulllen(self):
         return self.expl_offset if self.expled else self.offset
 
     @property
     def fulllen(self):
+        """.. seealso:: :ref:`decoding`
+        """
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
         if self.lenindef and not (
         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",
         ):
             yield _pp(
                 asn1_type_name="EOC",
@@ -1321,7 +2186,28 @@ class Obj(object):
             )
 
 
             )
 
 
-class DecodePathDefBy(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()
+
+
+def encode2pass(obj):
+    """Encode (2-pass mode) to DER in memory buffer
+
+    :returns bytes: memory buffer contents
+    """
+    buf = BytesIO()
+    _, state = obj.encode1st()
+    obj.encode2nd(buf.write, iter(state))
+    return buf.getvalue()
+
+
+class DecodePathDefBy:
     """DEFINED BY representation inside decode path
     """
     __slots__ = ("defined_by",)
     """DEFINED BY representation inside decode path
     """
     __slots__ = ("defined_by",)
@@ -1371,7 +2257,7 @@ PP = namedtuple("PP", (
     "lenindef",
     "ber_encoded",
     "bered",
     "lenindef",
     "ber_encoded",
     "bered",
-))
+), **NAMEDTUPLE_KWARGS)
 
 
 def _pp(
 
 
 def _pp(
@@ -1431,7 +2317,16 @@ def _colourize(what, colour, with_colours, attrs=("bold",)):
 def colonize_hex(hexed):
     """Separate hexadecimal string with colons
     """
 def colonize_hex(hexed):
     """Separate hexadecimal string with colons
     """
-    return ":".join(hexed[i:i + 2] for i in six_xrange(0, len(hexed), 2))
+    return ":".join(hexed[i:i + 2] for i in range(0, len(hexed), 2))
+
+
+def find_oid_name(asn1_type_name, oid_maps, value):
+    if len(oid_maps) > 0 and asn1_type_name == ObjectIdentifier.asn1_type_name:
+        for oid_map in oid_maps:
+            oid_name = oid_map.get(value)
+            if oid_name is not None:
+                return oid_name
+    return None
 
 
 def pp_console_row(
 
 
 def pp_console_row(
@@ -1457,9 +2352,7 @@ def pp_console_row(
         col += _colourize("B", "red", with_colours) if pp.bered else " "
         cols.append(col)
         col = "[%d,%d,%4d]%s" % (
         col += _colourize("B", "red", with_colours) if pp.bered else " "
         cols.append(col)
         col = "[%d,%d,%4d]%s" % (
-            pp.tlen,
-            pp.llen,
-            pp.vlen,
+            pp.tlen, pp.llen, pp.vlen,
             LENINDEF_PP_CHAR if pp.lenindef else " "
         )
         col = _colourize(col, "green", with_colours, ())
             LENINDEF_PP_CHAR if pp.lenindef else " "
         )
         col = _colourize(col, "green", with_colours, ())
@@ -1471,19 +2364,11 @@ def pp_console_row(
         if isinstance(ent, DecodePathDefBy):
             cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
             value = str(ent.defined_by)
         if isinstance(ent, DecodePathDefBy):
             cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
             value = str(ent.defined_by)
-            oid_name = None
-            if (
-                    len(oid_maps) > 0 and
-                    ent.defined_by.asn1_type_name ==
-                    ObjectIdentifier.asn1_type_name
-            ):
-                for oid_map in oid_maps:
-                    oid_name = oid_map.get(value)
-                    if oid_name is not None:
-                        cols.append(_colourize("%s:" % oid_name, "green", with_colours))
-                        break
+            oid_name = find_oid_name(ent.defined_by.asn1_type_name, oid_maps, value)
             if oid_name is None:
                 cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
             if oid_name is None:
                 cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
+            else:
+                cols.append(_colourize("%s:" % oid_name, "green", with_colours))
         else:
             cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
     if pp.expl is not None:
         else:
             cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
     if pp.expl is not None:
@@ -1502,28 +2387,17 @@ def pp_console_row(
     if pp.value is not None:
         value = pp.value
         cols.append(_colourize(value, "white", with_colours, ("reverse",)))
     if pp.value is not None:
         value = pp.value
         cols.append(_colourize(value, "white", with_colours, ("reverse",)))
-        if (
-                len(oid_maps) > 0 and
-                pp.asn1_type_name == ObjectIdentifier.asn1_type_name
-        ):
-            for oid_map in oid_maps:
-                oid_name = oid_map.get(value)
-                if oid_name is not None:
-                    cols.append(_colourize("(%s)" % oid_name, "green", with_colours))
-                    break
+        oid_name = find_oid_name(pp.asn1_type_name, oid_maps, pp.value)
+        if oid_name is not None:
+            cols.append(_colourize("(%s)" % oid_name, "green", with_colours))
         if pp.asn1_type_name == Integer.asn1_type_name:
         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(
             cols.append(_colourize(
-                "(%s)" % colonize_hex(hex_repr),
-                "green",
-                with_colours,
+                "(%s)" % colonize_hex(pp.obj.tohex()), "green", with_colours,
             ))
     if with_blob:
             ))
     if with_blob:
-        if isinstance(pp.blob, binary_type):
+        if pp.blob.__class__ == bytes:
             cols.append(hexenc(pp.blob))
             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))
             cols.append(", ".join(pp.blob))
     if pp.optional:
         cols.append(_colourize("OPTIONAL", "red", with_colours))
@@ -1543,12 +2417,12 @@ 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))
     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__ == bytes:
         blob = hexenc(pp.blob).upper()
         blob = hexenc(pp.blob).upper()
-        for i in six_xrange(0, len(blob), 32):
+        for i in range(0, len(blob), 32):
             chunk = blob[i:i + 32]
             yield " ".join(cols + [colonize_hex(chunk)])
             chunk = blob[i:i + 32]
             yield " ".join(cols + [colonize_hex(chunk)])
-    elif isinstance(pp.blob, tuple):
+    elif pp.blob.__class__ == tuple:
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
@@ -1559,13 +2433,13 @@ def pprint(
         with_colours=False,
         with_decode_path=False,
         decode_path_only=(),
         with_colours=False,
         with_decode_path=False,
         decode_path_only=(),
+        decode_path=(),
 ):
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
 ):
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
-    :param oid_maps: list of ``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`` dictionaries.
+                     Its human readable form is printed when OID is met
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
@@ -1578,10 +2452,10 @@ def pprint(
         for pp in pps:
             if hasattr(pp, "_fields"):
                 if (
         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:
                 ):
                     continue
                 if big_blobs:
@@ -1595,8 +2469,8 @@ def pprint(
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
                         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 row
                 else:
@@ -1612,13 +2486,20 @@ def pprint(
             else:
                 for row in _pprint_pps(pp):
                     yield row
             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
 ########################################################################
 
 
 
 ########################################################################
 # ASN.1 primitive types
 ########################################################################
 
+BooleanState = namedtuple(
+    "BooleanState",
+    BasicState._fields + ("value",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Boolean(Obj):
     """``BOOLEAN`` boolean type
 
 class Boolean(Obj):
     """``BOOLEAN`` boolean type
 
@@ -1650,7 +2531,7 @@ class Boolean(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(Boolean, self).__init__(impl, expl, default, optional, _decoded)
+        super().__init__(impl, expl, default, optional, _decoded)
         self._value = None if value is None else self._value_sanitize(value)
         if default is not None:
             default = self._value_sanitize(default)
         self._value = None if value is None else self._value_sanitize(value)
         if default is not None:
             default = self._value_sanitize(default)
@@ -1663,7 +2544,7 @@ class Boolean(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if isinstance(value, bool):
+        if value.__class__ == bool:
             return value
         if issubclass(value.__class__, Boolean):
             return value._value
             return value
         if issubclass(value.__class__, Boolean):
             return value._value
@@ -1673,20 +2554,26 @@ class Boolean(Obj):
     def ready(self):
         return self._value is not None
 
     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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        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().__setstate__(state)
+        self._value = state.value
 
     def __nonzero__(self):
         self._assert_ready()
 
     def __nonzero__(self):
         self._assert_ready()
@@ -1697,7 +2584,7 @@ class Boolean(Obj):
         return self._value
 
     def __eq__(self, their):
         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
             return self._value == their
         if not issubclass(their.__class__, Boolean):
             return False
@@ -1725,13 +2612,16 @@ class Boolean(Obj):
 
     def _encode(self):
         self._assert_ready()
 
     def _encode(self):
         self._assert_ready()
-        return b"".join((
-            self.tag,
-            len_encode(1),
-            (b"\xFF" if self._value else b"\x00"),
-        ))
+        return b"".join((self.tag, LEN1, (b"\xFF" if self._value else b"\x00")))
+
+    def _encode1st(self, state):
+        return len(self.tag) + 2, state
+
+    def _encode2nd(self, writer, state_iter):
+        self._assert_ready()
+        write_full(writer, self._encode())
 
 
-    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:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1748,6 +2638,7 @@ class Boolean(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         try:
             l, _, v = len_decode(lv)
             return
         try:
             l, _, v = len_decode(lv)
@@ -1772,7 +2663,7 @@ class Boolean(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 decode_path=decode_path,
                 offset=offset,
             )
-        first_octet = byte2int(v)
+        first_octet = v[0]
         ber_encoded = False
         if first_octet == 0:
             value = False
         ber_encoded = False
         if first_octet == 0:
             value = False
@@ -1797,7 +2688,7 @@ class Boolean(Obj):
             _decoded=(offset, 1, 1),
         )
         obj.ber_encoded = ber_encoded
             _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 __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -1829,6 +2720,13 @@ class Boolean(Obj):
             yield pp
 
 
             yield pp
 
 
+IntegerState = namedtuple(
+    "IntegerState",
+    BasicState._fields + ("specs", "value", "bound_min", "bound_max"),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Integer(Obj):
     """``INTEGER`` integer type
 
 class Integer(Obj):
     """``INTEGER`` integer type
 
@@ -1889,10 +2787,10 @@ class Integer(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(Integer, self).__init__(impl, expl, default, optional, _decoded)
+        super().__init__(impl, expl, default, optional, _decoded)
         self._value = value
         specs = getattr(self, "schema", {}) if _specs is None else _specs
         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",
         self._bound_min, self._bound_max = getattr(
             self,
             "bounds",
@@ -1912,11 +2810,11 @@ class Integer(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if isinstance(value, integer_types):
+        if isinstance(value, int):
             pass
         elif issubclass(value.__class__, Integer):
             value = value._value
             pass
         elif issubclass(value.__class__, Integer):
             value = value._value
-        elif isinstance(value, str):
+        elif value.__class__ == str:
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
@@ -1930,37 +2828,57 @@ class Integer(Obj):
     def ready(self):
         return self._value is not None
 
     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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        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().__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()
         return int(self._value)
 
 
     def __int__(self):
         self._assert_ready()
         return int(self._value)
 
+    def tohex(self):
+        """Hexadecimal representation
+
+        Use :py:func:`pyderasn.colonize_hex` for colonizing it.
+        """
+        hex_repr = hex(int(self))[2:].upper()
+        if len(hex_repr) % 2 != 0:
+            hex_repr = "0" + hex_repr
+        return hex_repr
+
     def __hash__(self):
         self._assert_ready()
     def __hash__(self):
         self._assert_ready()
-        return hash(
-            self.tag +
-            bytes(self._expl or b"") +
+        return hash(b"".join((
+            self.tag,
+            bytes(self._expl or b""),
             str(self._value).encode("ascii"),
             str(self._value).encode("ascii"),
-        )
+        )))
 
     def __eq__(self, their):
 
     def __eq__(self, their):
-        if isinstance(their, integer_types):
+        if isinstance(their, int):
             return self._value == their
         if not issubclass(their.__class__, Integer):
             return False
             return self._value == their
         if not issubclass(their.__class__, Integer):
             return False
@@ -1975,9 +2893,12 @@ class Integer(Obj):
 
     @property
     def named(self):
 
     @property
     def named(self):
-        for name, value in iteritems(self.specs):
+        """Return named representation (if exists) of the value
+        """
+        for name, value in self.specs.items():
             if value == self._value:
                 return name
             if value == self._value:
                 return name
+        return None
 
     def __call__(
             self,
 
     def __call__(
             self,
@@ -2001,46 +2922,31 @@ class Integer(Obj):
             _specs=self.specs,
         )
 
             _specs=self.specs,
         )
 
-    def _encode(self):
+    def _encode_payload(self):
         self._assert_ready()
         value = self._value
         self._assert_ready()
         value = self._value
-        if PY2:
-            if value == 0:
-                octets = bytearray([0])
-            elif value < 0:
-                value = -value
-                value -= 1
-                octets = bytearray()
-                while value > 0:
-                    octets.append((value & 0xFF) ^ 0xFF)
-                    value >>= 8
-                if len(octets) == 0 or octets[-1] & 0x80 == 0:
-                    octets.append(0xFF)
+        bytes_len = ceil(value.bit_length() / 8) or 1
+        while True:
+            try:
+                octets = value.to_bytes(bytes_len, byteorder="big", signed=True)
+            except OverflowError:
+                bytes_len += 1
             else:
             else:
-                octets = bytearray()
-                while value > 0:
-                    octets.append(value & 0xFF)
-                    value >>= 8
-                if octets[-1] & 0x80 > 0:
-                    octets.append(0x00)
-            octets.reverse()
-            octets = bytes(octets)
-        else:
-            bytes_len = ceil(value.bit_length() / 8) or 1
-            while True:
-                try:
-                    octets = value.to_bytes(
-                        bytes_len,
-                        byteorder="big",
-                        signed=True,
-                    )
-                except OverflowError:
-                    bytes_len += 1
-                else:
-                    break
+                break
+        return octets
+
+    def _encode(self):
+        octets = self._encode_payload()
         return b"".join((self.tag, len_encode(len(octets)), octets))
 
         return b"".join((self.tag, len_encode(len(octets)), octets))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode1st(self, state):
+        l = len(self._encode_payload())
+        return len(self.tag) + len_size(l) + l, state
+
+    def _encode2nd(self, writer, state_iter):
+        write_full(writer, self._encode())
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2057,6 +2963,7 @@ class Integer(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         try:
             l, llen, v = len_decode(lv)
             return
         try:
             l, llen, v = len_decode(lv)
@@ -2082,9 +2989,9 @@ class Integer(Obj):
                 offset=offset,
             )
         v, tail = v[:l], v[l:]
                 offset=offset,
             )
         v, tail = v[:l], v[l:]
-        first_octet = byte2int(v)
+        first_octet = v[0]
         if l > 1:
         if l > 1:
-            second_octet = byte2int(v[1:])
+            second_octet = v[1]
             if (
                     ((first_octet == 0x00) and (second_octet & 0x80 == 0)) or
                     ((first_octet == 0xFF) and (second_octet & 0x80 != 0))
             if (
                     ((first_octet == 0x00) and (second_octet & 0x80 == 0)) or
                     ((first_octet == 0xFF) and (second_octet & 0x80 != 0))
@@ -2095,21 +3002,7 @@ class Integer(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     decode_path=decode_path,
                     offset=offset,
                 )
-        if PY2:
-            value = 0
-            if first_octet & 0x80 > 0:
-                octets = bytearray()
-                for octet in bytearray(v):
-                    octets.append(octet ^ 0xFF)
-                for octet in octets:
-                    value = (value << 8) | octet
-                value += 1
-                value = -value
-            else:
-                for octet in bytearray(v):
-                    value = (value << 8) | octet
-        else:
-            value = int.from_bytes(v, byteorder="big", signed=True)
+        value = int.from_bytes(v, byteorder="big", signed=True)
         try:
             obj = self.__class__(
                 value=value,
         try:
             obj = self.__class__(
                 value=value,
@@ -2128,7 +3021,7 @@ class Integer(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
                 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 __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -2159,7 +3052,11 @@ class Integer(Obj):
             yield pp
 
 
             yield pp
 
 
-SET01 = frozenset(("0", "1"))
+BitStringState = namedtuple(
+    "BitStringState",
+    BasicState._fields + ("specs", "value", "tag_constructed", "defined"),
+    **NAMEDTUPLE_KWARGS
+)
 
 
 class BitString(Obj):
 
 
 class BitString(Obj):
@@ -2235,9 +3132,9 @@ class BitString(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(BitString, self).__init__(impl, expl, default, optional, _decoded)
+        super().__init__(impl, expl, default, optional, _decoded)
         specs = getattr(self, "schema", {}) if _specs is None else _specs
         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)
         self._value = None if value is None else self._value_sanitize(value)
         if default is not None:
             default = self._value_sanitize(default)
@@ -2262,14 +3159,14 @@ class BitString(Obj):
         bit_len = len(bits)
         bits += "0" * ((8 - (bit_len % 8)) % 8)
         octets = bytearray(len(bits) // 8)
         bit_len = len(bits)
         bits += "0" * ((8 - (bit_len % 8)) % 8)
         octets = bytearray(len(bits) // 8)
-        for i in six_xrange(len(octets)):
+        for i in range(len(octets)):
             octets[i] = int(bits[i * 8:(i * 8) + 8], 2)
         return bit_len, bytes(octets)
 
     def _value_sanitize(self, value):
             octets[i] = int(bits[i * 8:(i * 8) + 8], 2)
         return bit_len, bytes(octets)
 
     def _value_sanitize(self, value):
-        if isinstance(value, (string_types, binary_type)):
+        if isinstance(value, (str, bytes)):
             if (
             if (
-                    isinstance(value, string_types) and
+                    isinstance(value, str) and
                     value.startswith("'")
             ):
                 if value.endswith("'B"):
                     value.startswith("'")
             ):
                 if value.endswith("'B"):
@@ -2277,21 +3174,20 @@ class BitString(Obj):
                     if not frozenset(value) <= SET01:
                         raise ValueError("B's coding contains unacceptable chars")
                     return self._bits2octets(value)
                     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")),
                     )
                     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__ == bytes:
                 return (len(value) * 8, value)
                 return (len(value) * 8, value)
-            else:
-                raise InvalidValueType((self.__class__, string_types, binary_type))
-        if isinstance(value, tuple):
+            raise InvalidValueType((self.__class__, str, bytes))
+        if value.__class__ == tuple:
             if (
                     len(value) == 2 and
             if (
                     len(value) == 2 and
-                    isinstance(value[0], integer_types) and
-                    isinstance(value[1], binary_type)
+                    isinstance(value[0], int) and
+                    value[1].__class__ == bytes
             ):
                 return value
             bits = []
             ):
                 return value
             bits = []
@@ -2305,41 +3201,52 @@ class BitString(Obj):
             bits = frozenset(bits)
             return self._bits2octets("".join(
                 ("1" if bit in bits else "0")
             bits = frozenset(bits)
             return self._bits2octets("".join(
                 ("1" if bit in bits else "0")
-                for bit in six_xrange(max(bits) + 1)
+                for bit in range(max(bits) + 1)
             ))
         if issubclass(value.__class__, BitString):
             return value._value
             ))
         if issubclass(value.__class__, BitString):
             return value._value
-        raise InvalidValueType((self.__class__, binary_type, string_types))
+        raise InvalidValueType((self.__class__, bytes, str))
 
     @property
     def ready(self):
         return self._value is not None
 
 
     @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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        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().__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()
 
     def __iter__(self):
         self._assert_ready()
-        for i in six_xrange(self._value[0]):
+        for i in range(self._value[0]):
             yield self[i]
 
     @property
     def bit_len(self):
             yield self[i]
 
     @property
     def bit_len(self):
+        """Returns number of bits in the string
+        """
         self._assert_ready()
         return self._value[0]
 
         self._assert_ready()
         return self._value[0]
 
@@ -2348,7 +3255,7 @@ class BitString(Obj):
         return self._value[1]
 
     def __eq__(self, their):
         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
             return self._value[1] == their
         if not issubclass(their.__class__, BitString):
             return False
@@ -2360,7 +3267,11 @@ class BitString(Obj):
 
     @property
     def named(self):
 
     @property
     def named(self):
-        return [name for name, bit in iteritems(self.specs) if self[bit]]
+        """Named representation (if exists) of the bits
+
+        :returns: [str(name), ...]
+        """
+        return [name for name, bit in self.specs.items() if self[bit]]
 
     def __call__(
             self,
 
     def __call__(
             self,
@@ -2380,15 +3291,12 @@ class BitString(Obj):
         )
 
     def __getitem__(self, key):
         )
 
     def __getitem__(self, key):
-        if isinstance(key, int):
+        if key.__class__ == int:
             bit_len, octets = self._value
             if key >= bit_len:
                 return False
             bit_len, octets = self._value
             if key >= bit_len:
                 return False
-            return (
-                byte2int(memoryview(octets)[key // 8:]) >>
-                (7 - (key % 8))
-            ) & 1 == 1
-        if isinstance(key, string_types):
+            return memoryview(octets)[key // 8] >> (7 - (key % 8)) & 1 == 1
+        if isinstance(key, str):
             value = self.specs.get(key)
             if value is None:
                 raise ObjUnknown("BitString value: %s" % key)
             value = self.specs.get(key)
             if value is None:
                 raise ObjUnknown("BitString value: %s" % key)
@@ -2405,65 +3313,46 @@ class BitString(Obj):
             octets,
         ))
 
             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 _encode1st(self, state):
+        self._assert_ready()
+        _, octets = self._value
+        l = len(octets) + 1
+        return len(self.tag) + len_size(l) + l, state
+
+    def _encode2nd(self, writer, state_iter):
+        bit_len, octets = self._value
+        write_full(writer, b"".join((
+            self.tag,
+            len_encode(len(octets) + 1),
+            int2byte((8 - bit_len % 8) % 8),
+        )))
+        write_full(writer, octets)
 
 
-    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 range(0, (len(octets) // 999) * 999, 999):
+            write_full(writer, b"".join((
+                BitString.tag_default,
+                LEN1K,
+                b"\x00",
+                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:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2475,24 +3364,10 @@ class BitString(Obj):
             )
         if t == self.tag:
             if tag_only:  # pragma: no cover
             )
         if t == self.tag:
             if tag_only:  # pragma: no cover
+                yield None
                 return
                 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)
             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,
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -2507,126 +3382,217 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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,
                 )
                 raise NotEnoughData(
                     "zero length",
                     klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
-            chunks = []
-            sub_offset = offset + tlen + llen
-            vlen = 0
-            while True:
-                if lenindef:
-                    if v[:EOC_LEN].tobytes() == EOC:
-                        break
-                else:
-                    if vlen == l:
-                        break
-                    if vlen > l:
-                        raise DecodeError(
-                            "chunk out of bounds",
-                            klass=self.__class__,
-                            decode_path=decode_path + (str(len(chunks) - 1),),
-                            offset=chunks[-1].offset,
-                        )
-                sub_decode_path = decode_path + (str(len(chunks)),)
-                try:
-                    chunk, v_tail = BitString().decode(
-                        v,
-                        offset=sub_offset,
-                        decode_path=sub_decode_path,
-                        leavemm=True,
-                        ctx=ctx,
-                        _ctx_immutable=False,
-                    )
-                except TagMismatch:
-                    raise DecodeError(
-                        "expected BitString encoded chunk",
-                        klass=self.__class__,
-                        decode_path=sub_decode_path,
-                        offset=sub_offset,
-                    )
-                chunks.append(chunk)
-                sub_offset += chunk.tlvlen
-                vlen += chunk.tlvlen
-                v = v_tail
-            if len(chunks) == 0:
+            pad_size = v[0]
+            if l == 1 and pad_size != 0:
                 raise DecodeError(
                 raise DecodeError(
-                    "no chunks",
+                    "invalid empty value",
                     klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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 v[l - 1] & ((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__(
             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,
                 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,
-        )
-
-    def __repr__(self):
-        return pp_console_row(next(self.pps()))
-
-    def pps(self, decode_path=()):
-        value = None
-        blob = None
-        if self.ready:
-            bit_len, blob = self._value
-            value = "%d bits" % bit_len
-            if len(self.specs) > 0:
-                blob = tuple(self.named)
-        yield _pp(
-            obj=self,
-            asn1_type_name=self.asn1_type_name,
-            obj_name=self.__class__.__name__,
-            decode_path=decode_path,
-            value=value,
-            blob=blob,
-            optional=self.optional,
-            default=self == self.default,
-            impl=None if self.tag == self.tag_default else tag_decode(self.tag),
-            expl=None if self._expl is None else tag_decode(self._expl),
-            offset=self.offset,
-            tlen=self.tlen,
-            llen=self.llen,
-            vlen=self.vlen,
-            expl_offset=self.expl_offset if self.expled else None,
-            expl_tlen=self.expl_tlen if self.expled else None,
-            expl_llen=self.expl_llen if self.expled else None,
-            expl_vlen=self.expl_vlen if self.expled else None,
-            expl_lenindef=self.expl_lenindef,
-            lenindef=self.lenindef,
-            ber_encoded=self.ber_encoded,
-            bered=self.bered,
-        )
+            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()))
+
+    def pps(self, decode_path=()):
+        value = None
+        blob = None
+        if self.ready:
+            bit_len, blob = self._value
+            value = "%d bits" % bit_len
+            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,
+            value=value,
+            blob=blob,
+            optional=self.optional,
+            default=self == self.default,
+            impl=None if self.tag == self.tag_default else tag_decode(self.tag),
+            expl=None if self._expl is None else tag_decode(self._expl),
+            offset=self.offset,
+            tlen=self.tlen,
+            llen=self.llen,
+            vlen=self.vlen,
+            expl_offset=self.expl_offset if self.expled else None,
+            expl_tlen=self.expl_tlen if self.expled else None,
+            expl_llen=self.expl_llen if self.expled else None,
+            expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
+        )
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
             yield defined.pps(
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
             yield defined.pps(
@@ -2636,6 +3602,19 @@ class BitString(Obj):
             yield pp
 
 
             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
 
 class OctetString(Obj):
     """``OCTET STRING`` binary string type
 
@@ -2652,17 +3631,16 @@ class OctetString(Obj):
     >>> OctetString(b"hell", bounds=(4, 4))
     OCTET STRING 4 bytes 68656c6c
 
     >>> OctetString(b"hell", bounds=(4, 4))
     OCTET STRING 4 bytes 68656c6c
 
-    .. 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"
     """
     __slots__ = ("tag_constructed", "_bound_min", "_bound_max", "defined")
     tag_default = tag_encode(4)
     asn1_type_name = "OCTET STRING"
+    evgen_mode_skip_value = True
+    memoryview_safe = True
 
     def __init__(
             self,
 
     def __init__(
             self,
@@ -2673,6 +3651,7 @@ class OctetString(Obj):
             default=None,
             optional=False,
             _decoded=(0, 0, 0),
             default=None,
             optional=False,
             _decoded=(0, 0, 0),
+            ctx=None,
     ):
         """
         :param value: set the value. Either binary type, or
     ):
         """
         :param value: set the value. Either binary type, or
@@ -2684,13 +3663,7 @@ class OctetString(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :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().__init__(impl, expl, default, optional, _decoded)
         self._value = value
         self._bound_min, self._bound_max = getattr(
             self,
         self._value = value
         self._bound_min, self._bound_max = getattr(
             self,
@@ -2717,12 +3690,12 @@ class OctetString(Obj):
         )
 
     def _value_sanitize(self, value):
         )
 
     def _value_sanitize(self, value):
-        if isinstance(value, binary_type):
+        if value.__class__ == bytes or value.__class__ == memoryview:
             pass
         elif issubclass(value.__class__, OctetString):
             value = value._value
         else:
             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
         if not self._bound_min <= len(value) <= self._bound_max:
             raise BoundsError(self._bound_min, len(value), self._bound_max)
         return value
@@ -2731,29 +3704,45 @@ class OctetString(Obj):
     def ready(self):
         return self._value is not None
 
     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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        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().__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()
 
     def __bytes__(self):
         self._assert_ready()
-        return self._value
+        return bytes(self._value)
+
+    def memoryview(self):
+        self._assert_ready()
+        return memoryview(self._value)
 
     def __eq__(self, their):
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == bytes:
             return self._value == their
         if not issubclass(their.__class__, OctetString):
             return False
             return self._value == their
         if not issubclass(their.__class__, OctetString):
             return False
@@ -2795,51 +3784,39 @@ class OctetString(Obj):
             self._value,
         ))
 
             self._value,
         ))
 
-    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,
-            )
-        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 _encode1st(self, state):
+        self._assert_ready()
+        l = len(self._value)
+        return len(self.tag) + len_size(l) + l, state
+
+    def _encode2nd(self, writer, state_iter):
+        value = self._value
+        write_full(writer, self.tag + len_encode(len(value)))
+        write_full(writer, value)
 
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    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 range(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:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2851,24 +3828,10 @@ class OctetString(Obj):
             )
         if t == self.tag:
             if tag_only:
             )
         if t == self.tag:
             if tag_only:
+                yield None
                 return
                 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)
             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,
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -2883,53 +3846,30 @@ class OctetString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     decode_path=decode_path,
                     offset=offset,
                 )
-            chunks = []
-            sub_offset = offset + tlen + llen
-            vlen = 0
-            while True:
-                if lenindef:
-                    if v[:EOC_LEN].tobytes() == EOC:
-                        break
-                else:
-                    if vlen == l:
-                        break
-                    if vlen > l:
-                        raise DecodeError(
-                            "chunk out of bounds",
-                            klass=self.__class__,
-                            decode_path=decode_path + (str(len(chunks) - 1),),
-                            offset=chunks[-1].offset,
-                        )
-                sub_decode_path = decode_path + (str(len(chunks)),)
-                try:
-                    chunk, v_tail = OctetString().decode(
-                        v,
-                        offset=sub_offset,
-                        decode_path=sub_decode_path,
-                        leavemm=True,
-                        ctx=ctx,
-                        _ctx_immutable=False,
-                    )
-                except TagMismatch:
-                    raise DecodeError(
-                        "expected OctetString encoded chunk",
-                        klass=self.__class__,
-                        decode_path=sub_decode_path,
-                        offset=sub_offset,
-                    )
-                chunks.append(chunk)
-                sub_offset += chunk.tlvlen
-                vlen += chunk.tlvlen
-                v = v_tail
+            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,
+                )
+            if evgen_mode and self.evgen_mode_skip_value:
+                value = None
+            elif self.memoryview_safe and ctx.get("keep_memoryview", False):
+                value = v
+            else:
+                value = v.tobytes()
             try:
                 obj = self.__class__(
             try:
                 obj = self.__class__(
-                    value=b"".join(bytes(chunk) for chunk in chunks),
+                    value=value,
                     bounds=(self._bound_min, self._bound_max),
                     impl=self.tag,
                     expl=self._expl,
                     default=self.default,
                     optional=self.optional,
                     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(
                 )
             except DecodeError as err:
                 raise DecodeError(
@@ -2945,14 +3885,138 @@ class OctetString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     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 __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -2991,6 +4055,36 @@ class OctetString(Obj):
             yield pp
 
 
             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
+
+    .. seealso:: :ref:`agg_octet_string`
+    """
+    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
 
 class Null(Obj):
     """``NULL`` null object
 
@@ -3016,26 +4110,28 @@ class Null(Obj):
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(Null, self).__init__(impl, expl, None, optional, _decoded)
+        super().__init__(impl, expl, None, optional, _decoded)
         self.default = None
 
     @property
     def ready(self):
         return True
 
         self.default = None
 
     @property
     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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        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):
 
     def __eq__(self, their):
         if not issubclass(their.__class__, Null):
@@ -3059,9 +4155,15 @@ class Null(Obj):
         )
 
     def _encode(self):
         )
 
     def _encode(self):
-        return self.tag + len_encode(0)
+        return self.tag + LEN0
+
+    def _encode1st(self, state):
+        return len(self.tag) + 1, state
 
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode2nd(self, writer, state_iter):
+        write_full(writer, self.tag + LEN0)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3078,6 +4180,7 @@ class Null(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
         try:
             l, _, v = len_decode(lv)
             return
         try:
             l, _, v = len_decode(lv)
@@ -3101,7 +4204,7 @@ class Null(Obj):
             optional=self.optional,
             _decoded=(offset, 1, 0),
         )
             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 __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -3130,6 +4233,13 @@ class Null(Obj):
             yield pp
 
 
             yield pp
 
 
+ObjectIdentifierState = namedtuple(
+    "ObjectIdentifierState",
+    BasicState._fields + ("value", "defines"),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class ObjectIdentifier(Obj):
     """``OBJECT IDENTIFIER`` OID type
 
 class ObjectIdentifier(Obj):
     """``OBJECT IDENTIFIER`` OID type
 
@@ -3174,19 +4284,15 @@ class ObjectIdentifier(Obj):
                         tuple element is ``{OID: pyderasn.Obj()}``
                         dictionary, mapping between current OID value
                         and structure applied to defined field.
                         tuple element is ``{OID: pyderasn.Obj()}``
                         dictionary, mapping between current OID value
                         and structure applied to defined field.
-                        :ref:`Read about DEFINED BY <definedby>`
+
+                        .. seealso:: :ref:`definedby`
+
         :param bytes impl: override default tag with ``IMPLICIT`` one
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param bytes impl: override default tag with ``IMPLICIT`` one
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(ObjectIdentifier, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super().__init__(impl, expl, default, optional, _decoded)
         self._value = value
         if value is not None:
             self._value = self._value_sanitize(value)
         self._value = value
         if value is not None:
             self._value = self._value_sanitize(value)
@@ -3202,21 +4308,26 @@ class ObjectIdentifier(Obj):
         self.defines = defines
 
     def __add__(self, their):
         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, 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):
         if issubclass(value.__class__, ObjectIdentifier):
             return value._value
         raise InvalidValueType((self.__class__, tuple))
 
     def _value_sanitize(self, value):
         if issubclass(value.__class__, ObjectIdentifier):
             return value._value
-        if isinstance(value, string_types):
+        if isinstance(value, str):
             try:
             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")
             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]
             if len(value) < 2:
                 raise InvalidOID("less than 2 arcs")
             first_arc = value[0]
@@ -3227,6 +4338,8 @@ class ObjectIdentifier(Obj):
                 pass
             else:
                 raise InvalidOID("unacceptable first arc value")
                 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))
 
             return value
         raise InvalidValueType((self.__class__, str, tuple))
 
@@ -3234,21 +4347,28 @@ class ObjectIdentifier(Obj):
     def ready(self):
         return self._value is not None
 
     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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        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().__setstate__(state)
+        self._value = state.value
+        self.defines = state.defines
 
     def __iter__(self):
         self._assert_ready()
 
     def __iter__(self):
         self._assert_ready()
@@ -3259,15 +4379,15 @@ class ObjectIdentifier(Obj):
 
     def __hash__(self):
         self._assert_ready()
 
     def __hash__(self):
         self._assert_ready()
-        return hash(
-            self.tag +
-            bytes(self._expl or b"") +
+        return hash(b"".join((
+            self.tag,
+            bytes(self._expl or b""),
             str(self._value).encode("ascii"),
             str(self._value).encode("ascii"),
-        )
+        )))
 
     def __eq__(self, their):
 
     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 (
         if not issubclass(their.__class__, ObjectIdentifier):
             return False
         return (
@@ -3297,7 +4417,7 @@ class ObjectIdentifier(Obj):
             optional=self.optional if optional is None else optional,
         )
 
             optional=self.optional if optional is None else optional,
         )
 
-    def _encode(self):
+    def _encode_octets(self):
         self._assert_ready()
         value = self._value
         first_value = value[1]
         self._assert_ready()
         value = self._value
         first_value = value[1]
@@ -3313,10 +4433,20 @@ class ObjectIdentifier(Obj):
         octets = [zero_ended_encode(first_value)]
         for arc in value[2:]:
             octets.append(zero_ended_encode(arc))
         octets = [zero_ended_encode(first_value)]
         for arc in value[2:]:
             octets.append(zero_ended_encode(arc))
-        v = b"".join(octets)
+        return b"".join(octets)
+
+    def _encode(self):
+        v = self._encode_octets()
         return b"".join((self.tag, len_encode(len(v)), v))
 
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode1st(self, state):
+        l = len(self._encode_octets())
+        return len(self.tag) + len_size(l) + l, state
+
+    def _encode2nd(self, writer, state_iter):
+        write_full(writer, self._encode())
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3333,6 +4463,7 @@ class ObjectIdentifier(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
         try:
             l, llen, v = len_decode(lv)
             return
         try:
             l, llen, v = len_decode(lv)
@@ -3358,21 +4489,34 @@ class ObjectIdentifier(Obj):
                 offset=offset,
             )
         v, tail = v[:l], v[l:]
                 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:
         ber_encoded = False
         while len(v) > 0:
             i = 0
             arc = 0
             while True:
-                octet = indexbytes(v, i)
+                octet = v[i]
                 if i == 0 and octet == 0x80:
                     if ctx.get("bered", False):
                         ber_encoded = True
                     else:
                 if i == 0 and octet == 0x80:
                     if ctx.get("bered", False):
                         ber_encoded = True
                     else:
-                        raise DecodeError("non normalized arc encoding")
+                        raise DecodeError(
+                            "non normalized arc encoding",
+                            klass=self.__class__,
+                            decode_path=decode_path,
+                            offset=offset,
+                        )
                 arc = (arc << 7) | (octet & 0x7F)
                 if octet & 0x80 == 0:
                 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
                     v = v[i + 1:]
                     break
                 i += 1
@@ -3394,7 +4538,7 @@ class ObjectIdentifier(Obj):
             first_arc = 2
             second_arc -= 80
         obj = self.__class__(
             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,
             impl=self.tag,
             expl=self._expl,
             default=self.default,
@@ -3403,7 +4547,7 @@ class ObjectIdentifier(Obj):
         )
         if ber_encoded:
             obj.ber_encoded = True
         )
         if ber_encoded:
             obj.ber_encoded = True
-        return obj, tail
+        yield decode_path, obj, tail
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -3456,14 +4600,8 @@ class Enumerated(Integer):
             _decoded=(0, 0, 0),
             bounds=None,  # dummy argument, workability for Integer.decode
     ):
             _decoded=(0, 0, 0),
             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,
+        super().__init__(
+            value, bounds, impl, expl, default, optional, _specs, _decoded,
         )
         if len(self.specs) == 0:
             raise ValueError("schema must be specified")
         )
         if len(self.specs) == 0:
             raise ValueError("schema must be specified")
@@ -3471,8 +4609,8 @@ class Enumerated(Integer):
     def _value_sanitize(self, value):
         if isinstance(value, self.__class__):
             value = value._value
     def _value_sanitize(self, value):
         if isinstance(value, self.__class__):
             value = value._value
-        elif isinstance(value, integer_types):
-            for _value in itervalues(self.specs):
+        elif isinstance(value, int):
+            for _value in self.specs.values():
                 if _value == value:
                     break
             else:
                 if _value == value:
                     break
             else:
@@ -3480,7 +4618,7 @@ class Enumerated(Integer):
                     "unknown integer value: %s" % value,
                     klass=self.__class__,
                 )
                     "unknown integer value: %s" % value,
                     klass=self.__class__,
                 )
-        elif isinstance(value, string_types):
+        elif isinstance(value, str):
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
             value = self.specs.get(value)
             if value is None:
                 raise ObjUnknown("integer value: %s" % value)
@@ -3488,23 +4626,6 @@ class Enumerated(Integer):
             raise InvalidValueType((self.__class__, int, str))
         return value
 
             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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        return obj
-
     def __call__(
             self,
             value=None,
     def __call__(
             self,
             value=None,
@@ -3524,6 +4645,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
 
 class CommonString(OctetString):
     """Common class for all strings
 
@@ -3558,27 +4685,25 @@ class CommonString(OctetString):
        :header-rows: 1
 
        * - Class
        :header-rows: 1
 
        * - Class
-         - Text Encoding
+         - Text Encoding, validation
        * - :py:class:`pyderasn.UTF8String`
          - utf-8
        * - :py:class:`pyderasn.NumericString`
        * - :py:class:`pyderasn.UTF8String`
          - utf-8
        * - :py:class:`pyderasn.NumericString`
-         - ascii
+         - proper alphabet validation
        * - :py:class:`pyderasn.PrintableString`
        * - :py:class:`pyderasn.PrintableString`
-         - ascii
+         - proper alphabet validation
        * - :py:class:`pyderasn.TeletexString`
        * - :py:class:`pyderasn.TeletexString`
-         - ascii
+         - iso-8859-1
        * - :py:class:`pyderasn.T61String`
        * - :py:class:`pyderasn.T61String`
-         - ascii
+         - iso-8859-1
        * - :py:class:`pyderasn.VideotexString`
          - iso-8859-1
        * - :py:class:`pyderasn.IA5String`
        * - :py:class:`pyderasn.VideotexString`
          - iso-8859-1
        * - :py:class:`pyderasn.IA5String`
-         - ascii
+         - proper alphabet validation
        * - :py:class:`pyderasn.GraphicString`
          - iso-8859-1
        * - :py:class:`pyderasn.GraphicString`
          - iso-8859-1
-       * - :py:class:`pyderasn.VisibleString`
-         - ascii
-       * - :py:class:`pyderasn.ISO646String`
-         - ascii
+       * - :py:class:`pyderasn.VisibleString`, :py:class:`pyderasn.ISO646String`
+         - proper alphabet validation
        * - :py:class:`pyderasn.GeneralString`
          - iso-8859-1
        * - :py:class:`pyderasn.UniversalString`
        * - :py:class:`pyderasn.GeneralString`
          - iso-8859-1
        * - :py:class:`pyderasn.UniversalString`
@@ -3586,19 +4711,20 @@ class CommonString(OctetString):
        * - :py:class:`pyderasn.BMPString`
          - utf-16-be
     """
        * - :py:class:`pyderasn.BMPString`
          - utf-16-be
     """
-    __slots__ = ("encoding",)
+    __slots__ = ()
+    memoryview_safe = False
 
     def _value_sanitize(self, value):
         value_raw = None
         value_decoded = None
         if isinstance(value, self.__class__):
             value_raw = value._value
 
     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__ == str:
             value_decoded = value
             value_decoded = value
-        elif isinstance(value, binary_type):
+        elif value.__class__ == bytes:
             value_raw = value
         else:
             value_raw = value
         else:
-            raise InvalidValueType((self.__class__, text_type, binary_type))
+            raise InvalidValueType((self.__class__, str, bytes))
         try:
             value_raw = (
                 value_decoded.encode(self.encoding)
         try:
             value_raw = (
                 value_decoded.encode(self.encoding)
@@ -3619,9 +4745,9 @@ class CommonString(OctetString):
         return value_raw
 
     def __eq__(self, their):
         return value_raw
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
+        if their.__class__ == bytes:
             return self._value == their
             return self._value == their
-        if isinstance(their, text_type):
+        if their.__class__ == str:
             return self._value == their.encode(self.encoding)
         if not isinstance(their, self.__class__):
             return False
             return self._value == their.encode(self.encoding)
         if not isinstance(their, self.__class__):
             return False
@@ -3634,15 +4760,18 @@ class CommonString(OctetString):
     def __unicode__(self):
         if self.ready:
             return self._value.decode(self.encoding)
     def __unicode__(self):
         if self.ready:
             return self._value.decode(self.encoding)
-        return text_type(self._value)
+        return str(self._value)
+
+    def memoryview(self):
+        raise ValueError("CommonString does not support .memoryview()")
 
     def __repr__(self):
 
     def __repr__(self):
-        return pp_console_row(next(self.pps(no_unicode=PY2)))
+        return pp_console_row(next(self.pps()))
 
 
-    def pps(self, decode_path=(), no_unicode=False):
+    def pps(self, decode_path=()):
         value = None
         if self.ready:
         value = None
         if self.ready:
-            value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
+            value = "".join(escape_control_unicode(c) for c in self.__unicode__())
         yield _pp(
             obj=self,
             asn1_type_name=self.asn1_type_name,
         yield _pp(
             obj=self,
             asn1_type_name=self.asn1_type_name,
@@ -3676,12 +4805,21 @@ class UTF8String(CommonString):
     asn1_type_name = "UTF8String"
 
 
     asn1_type_name = "UTF8String"
 
 
-class AllowableCharsMixin(object):
+class AllowableCharsMixin:
+    __slots__ = ()
+
     @property
     def allowable_chars(self):
     @property
     def allowable_chars(self):
-        if PY2:
-            return self._allowable_chars
-        return frozenset(six_unichr(c) for c in self._allowable_chars)
+        return frozenset(chr(c) for c in self._allowable_chars)
+
+    def _value_sanitize(self, value):
+        value = super()._value_sanitize(value)
+        if not frozenset(value) <= self._allowable_chars:
+            raise DecodeError("non satisfying alphabet value")
+        return value
+
+
+NUMERIC_ALLOWABLE_CHARS = frozenset(digits.encode("ascii") + b" ")
 
 
 class NumericString(AllowableCharsMixin, CommonString):
 
 
 class NumericString(AllowableCharsMixin, CommonString):
@@ -3693,17 +4831,26 @@ class NumericString(AllowableCharsMixin, CommonString):
     >>> NumericString().allowable_chars
     frozenset(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' '])
     """
     >>> NumericString().allowable_chars
     frozenset(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' '])
     """
-    __slots__ = ()
+    __slots__ = ("_allowable_chars",)
     tag_default = tag_encode(18)
     encoding = "ascii"
     asn1_type_name = "NumericString"
     tag_default = tag_encode(18)
     encoding = "ascii"
     asn1_type_name = "NumericString"
-    _allowable_chars = frozenset(digits.encode("ascii") + b" ")
 
 
-    def _value_sanitize(self, value):
-        value = super(NumericString, self)._value_sanitize(value)
-        if not frozenset(value) <= self._allowable_chars:
-            raise DecodeError("non-numeric value")
-        return value
+    def __init__(self, *args, **kwargs):
+        self._allowable_chars = NUMERIC_ALLOWABLE_CHARS
+        super().__init__(*args, **kwargs)
+
+
+PrintableStringState = namedtuple(
+    "PrintableStringState",
+    OctetStringState._fields + ("allowable_chars",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
+PRINTABLE_ALLOWABLE_CHARS = frozenset(
+    (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+)
 
 
 class PrintableString(AllowableCharsMixin, CommonString):
 
 
 class PrintableString(AllowableCharsMixin, CommonString):
@@ -3713,26 +4860,95 @@ class PrintableString(AllowableCharsMixin, CommonString):
 
     >>> PrintableString().allowable_chars
     frozenset([' ', "'", ..., 'z'])
 
     >>> 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__ = ()
+    __slots__ = ("_allowable_chars",)
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
-    _allowable_chars = frozenset(
-        (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
-    )
+    _asterisk = frozenset("*".encode("ascii"))
+    _ampersand = frozenset("&".encode("ascii"))
 
 
-    def _value_sanitize(self, value):
-        value = super(PrintableString, self)._value_sanitize(value)
-        if not frozenset(value) <= self._allowable_chars:
-            raise DecodeError("non-printable value")
-        return value
+    def __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
+        """
+        allowable_chars = PRINTABLE_ALLOWABLE_CHARS
+        if allow_asterisk:
+            allowable_chars |= self._asterisk
+        if allow_ampersand:
+            allowable_chars |= self._ampersand
+        self._allowable_chars = allowable_chars
+        super().__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 __getstate__(self):
+        return PrintableStringState(
+            *super().__getstate__(),
+            **{"allowable_chars": self._allowable_chars}
+        )
+
+    def __setstate__(self, state):
+        super().__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__ = ()
     tag_default = tag_encode(20)
 
 
 class TeletexString(CommonString):
     __slots__ = ()
     tag_default = tag_encode(20)
-    encoding = "ascii"
+    encoding = "iso-8859-1"
     asn1_type_name = "TeletexString"
 
 
     asn1_type_name = "TeletexString"
 
 
@@ -3748,19 +4964,90 @@ class VideotexString(CommonString):
     asn1_type_name = "VideotexString"
 
 
     asn1_type_name = "VideotexString"
 
 
-class IA5String(CommonString):
-    __slots__ = ()
+IA5_ALLOWABLE_CHARS = frozenset(b"".join(
+    chr(c).encode("ascii") for c in range(128)
+))
+
+
+class IA5String(AllowableCharsMixin, CommonString):
+    """IA5 string
+
+    Its value is properly sanitized: it is a mix of
+
+    * http://www.itscj.ipsj.or.jp/iso-ir/006.pdf (G)
+    * http://www.itscj.ipsj.or.jp/iso-ir/001.pdf (C0)
+    * DEL character (0x7F)
+
+    It is just 7-bit ASCII.
+
+    >>> IA5String().allowable_chars
+    frozenset(["NUL", ... "DEL"])
+    """
+    __slots__ = ("_allowable_chars",)
     tag_default = tag_encode(22)
     encoding = "ascii"
     asn1_type_name = "IA5"
 
     tag_default = tag_encode(22)
     encoding = "ascii"
     asn1_type_name = "IA5"
 
+    def __init__(self, *args, **kwargs):
+        self._allowable_chars = IA5_ALLOWABLE_CHARS
+        super().__init__(*args, **kwargs)
+
 
 LEN_YYMMDDHHMMSSZ = len("YYMMDDHHMMSSZ")
 
 LEN_YYMMDDHHMMSSZ = len("YYMMDDHHMMSSZ")
+LEN_LEN_YYMMDDHHMMSSZ = len_encode(LEN_YYMMDDHHMMSSZ)
+LEN_YYMMDDHHMMSSZ_WITH_LEN = len(LEN_LEN_YYMMDDHHMMSSZ) + LEN_YYMMDDHHMMSSZ
 LEN_YYYYMMDDHHMMSSDMZ = len("YYYYMMDDHHMMSSDMZ")
 LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
 LEN_YYYYMMDDHHMMSSDMZ = len("YYYYMMDDHHMMSSDMZ")
 LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
+LEN_LEN_YYYYMMDDHHMMSSZ = len_encode(LEN_YYYYMMDDHHMMSSZ)
+
+
+VISIBLE_ALLOWABLE_CHARS = frozenset(b"".join(
+    chr(c).encode("ascii") for c in range(ord(" "), ord("~") + 1)
+))
+
+
+class VisibleString(AllowableCharsMixin, CommonString):
+    """Visible string
+
+    Its value is properly sanitized. ASCII subset from space to tilde is
+    allowed: http://www.itscj.ipsj.or.jp/iso-ir/006.pdf
+
+    >>> VisibleString().allowable_chars
+    frozenset([" ", ... "~"])
+    """
+    __slots__ = ("_allowable_chars",)
+    tag_default = tag_encode(26)
+    encoding = "ascii"
+    asn1_type_name = "VisibleString"
+
+    def __init__(self, *args, **kwargs):
+        self._allowable_chars = VISIBLE_ALLOWABLE_CHARS
+        super().__init__(*args, **kwargs)
+
+
+class ISO646String(VisibleString):
+    __slots__ = ()
+    asn1_type_name = "ISO646String"
+
+
+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(CommonString):
+
+class UTCTime(VisibleString):
     """``UTCTime`` datetime type
 
     >>> t = UTCTime(datetime(2017, 9, 30, 22, 7, 50, 123))
     """``UTCTime`` datetime type
 
     >>> t = UTCTime(datetime(2017, 9, 30, 22, 7, 50, 123))
@@ -3773,15 +5060,37 @@ class UTCTime(CommonString):
     datetime.datetime(2017, 9, 30, 22, 7, 50)
     >>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).todatetime()
     datetime.datetime(1957, 9, 30, 22, 7, 50)
     datetime.datetime(2017, 9, 30, 22, 7, 50)
     >>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).todatetime()
     datetime.datetime(1957, 9, 30, 22, 7, 50)
+    >>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).totzdatetime()
+    datetime.datetime(1957, 9, 30, 22, 7, 50, tzinfo=tzutc())
+
+    If BER encoded value was met, then ``ber_raw`` attribute will hold
+    its raw representation.
+
+    .. warning::
+
+       Only **naive** ``datetime`` objects are supported.
+       Library assumes that all work is done in UTC.
+
+    .. 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. Use ``GeneralizedTime`` instead for
+       removing ambiguity.
 
     .. warning::
 
 
     .. warning::
 
-       BER encoding is unsupported.
+       No strict validation of UTC offsets are made (only applicable to
+       **BER**), 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"
     tag_default = tag_encode(23)
     encoding = "ascii"
     asn1_type_name = "UTCTime"
+    evgen_mode_skip_value = False
 
     def __init__(
             self,
 
     def __init__(
             self,
@@ -3792,6 +5101,7 @@ class UTCTime(CommonString):
             optional=False,
             _decoded=(0, 0, 0),
             bounds=None,  # dummy argument, workability for OctetString.decode
             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
     ):
         """
         :param value: set the value. Either datetime type, or
@@ -3801,18 +5111,14 @@ class UTCTime(CommonString):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(UTCTime, self).__init__(
-            impl=impl,
-            expl=expl,
-            default=default,
-            optional=optional,
-            _decoded=_decoded,
-        )
+        super().__init__(None, None, impl, expl, None, optional, _decoded, ctx)
         self._value = value
         self._value = value
+        self.ber_raw = None
         if value is not 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:
         if default is not None:
-            default = self._value_sanitize(default)
+            default, _ = self._value_sanitize(default)
             self.default = self.__class__(
                 value=default,
                 impl=self.tag,
             self.default = self.__class__(
                 value=default,
                 impl=self.tag,
@@ -3820,6 +5126,45 @@ class UTCTime(CommonString):
             )
             if self._value is None:
                 self._value = default
             )
             if self._value is None:
                 self._value = default
+            optional = True
+        self.optional = optional
+
+    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
 
     def _strptime(self, value):
         # datetime.strptime's format: %y%m%d%H%M%SZ
@@ -3827,36 +5172,76 @@ class UTCTime(CommonString):
             raise ValueError("invalid UTCTime length")
         if value[-1] != "Z":
             raise ValueError("non UTC timezone")
             raise ValueError("invalid UTCTime length")
         if value[-1] != "Z":
             raise ValueError("non UTC timezone")
-        return datetime(
-            2000 + int(value[:2]),  # %y
-            int(value[2:4]),  # %m
-            int(value[4:6]),  # %d
-            int(value[6:8]),  # %H
-            int(value[8:10]),  # %M
-            int(value[10:12]),  # %S
-        )
+        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 _value_sanitize(self, value):
-        if isinstance(value, binary_type):
+    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__ == bytes:
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
                 raise DecodeError("invalid UTCTime encoding: %r" % err)
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
                 raise DecodeError("invalid UTCTime encoding: %r" % err)
+            err = None
             try:
             try:
-                self._strptime(value_decoded)
-            except (TypeError, ValueError) as err:
-                raise DecodeError("invalid UTCTime format: %r" % err)
-            return value
+                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__):
         if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime("%y%m%d%H%M%SZ").encode("ascii")
+            return value._value, None
+        if value.__class__ == datetime:
+            if value.tzinfo is not None:
+                raise ValueError("only naive datetime supported")
+            return self._dt_sanitize(value), None
         raise InvalidValueType((self.__class__, datetime))
 
         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
+        return None
+
+    def __unicode__(self):
+        if self.ready:
+            value = self._value.isoformat()
+            if self.ber_encoded:
+                value += " (%s)" % self.ber_raw
+            return value
+        return str(self._pp_value())
+
+    def __getstate__(self):
+        return UTCTimeState(*super().__getstate__(), **{"ber_raw": self.ber_raw})
+
+    def __setstate__(self, state):
+        super().__setstate__(state)
+        self.ber_raw = state.ber_raw
+
+    def __bytes__(self):
+        self._assert_ready()
+        return self._encode_time()
+
     def __eq__(self, their):
     def __eq__(self, their):
-        if isinstance(their, binary_type):
-            return self._value == their
-        if isinstance(their, datetime):
+        if their.__class__ == bytes:
+            return self._encode_time() == their
+        if their.__class__ == datetime:
             return self.todatetime() == their
         if not isinstance(their, self.__class__):
             return False
             return self.todatetime() == their
         if not isinstance(their, self.__class__):
             return False
@@ -3866,25 +5251,31 @@ class UTCTime(CommonString):
             self._expl == their._expl
         )
 
             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()
+        return b"".join((self.tag, LEN_LEN_YYMMDDHHMMSSZ, self._encode_time()))
 
 
-        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 = self._strptime(self._value.decode("ascii"))
-        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 _encode1st(self, state):
+        return len(self.tag) + LEN_YYMMDDHHMMSSZ_WITH_LEN, state
+
+    def _encode2nd(self, writer, state_iter):
+        self._assert_ready()
+        write_full(writer, self._encode())
+
+    def _encode_cer(self, writer):
+        write_full(writer, self._encode())
+
+    def todatetime(self):
+        return self._value
+
+    def totzdatetime(self):
+        try:
+            return self._value.replace(tzinfo=tzUTC)
+        except TypeError as err:
+            raise NotImplementedError("Missing dateutil.tz") from err
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
 
     def __repr__(self):
         return pp_console_row(next(self.pps()))
@@ -3895,7 +5286,7 @@ class UTCTime(CommonString):
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
-            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),
             optional=self.optional,
             default=self == self.default,
             impl=None if self.tag == self.tag_default else tag_decode(self.tag),
@@ -3930,32 +5321,96 @@ class GeneralizedTime(UTCTime):
 
     .. warning::
 
 
     .. warning::
 
-       BER encoding is unsupported.
+       Only **naive** datetime objects are supported.
+       Library assumes that all work is done in UTC.
 
     .. warning::
 
 
     .. warning::
 
-       Only microsecond fractions are supported.
+       Only **microsecond** fractions are supported in DER encoding.
        :py:exc:`pyderasn.DecodeError` will be raised during decoding of
        higher precision values.
        :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::
+
+       **Zero** year is unsupported.
     """
     __slots__ = ()
     tag_default = tag_encode(24)
     asn1_type_name = "GeneralizedTime"
 
     """
     __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:
     def _strptime(self, value):
         l = len(value)
         if l == LEN_YYYYMMDDHHMMSSZ:
-            # datetime.strptime's format: %y%m%d%H%M%SZ
+            # datetime.strptime's format: %Y%m%d%H%M%SZ
             if value[-1] != "Z":
                 raise ValueError("non UTC timezone")
             if value[-1] != "Z":
                 raise ValueError("non UTC timezone")
-            return datetime(
-                int(value[:4]),  # %Y
-                int(value[4:6]),  # %m
-                int(value[6:8]),  # %d
-                int(value[8:10]),  # %H
-                int(value[10:12]),  # %M
-                int(value[12:14]),  # %S
-            )
+            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":
         if l >= LEN_YYYYMMDDHHMMSSDMZ:
             # datetime.strptime's format: %Y%m%d%H%M%S.%fZ
             if value[-1] != "Z":
@@ -3968,44 +5423,33 @@ class GeneralizedTime(UTCTime):
             us_len = len(us)
             if us_len > 6:
                 raise ValueError("only microsecond fractions are supported")
             us_len = len(us)
             if us_len > 6:
                 raise ValueError("only microsecond fractions are supported")
-            us = int(us + ("0" * (6 - us_len)))
-            decoded = datetime(
-                int(value[:4]),  # %Y
-                int(value[4:6]),  # %m
-                int(value[6:8]),  # %d
-                int(value[8:10]),  # %H
-                int(value[10:12]),  # %M
-                int(value[12:14]),  # %S
-                us,  # %f
-            )
-            return decoded
+            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")
 
         raise ValueError("invalid GeneralizedTime length")
 
-    def _value_sanitize(self, value):
-        if isinstance(value, binary_type):
-            try:
-                value_decoded = value.decode("ascii")
-            except (UnicodeEncodeError, UnicodeDecodeError) as err:
-                raise DecodeError("invalid GeneralizedTime encoding: %r" % err)
-            try:
-                self._strptime(value_decoded)
-            except (TypeError, ValueError) as err:
-                raise DecodeError(
-                    "invalid GeneralizedTime format: %r" % err,
-                    klass=self.__class__,
-                )
-            return value
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            encoded = value.strftime("%Y%m%d%H%M%S")
-            if value.microsecond > 0:
-                encoded = encoded + (".%06d" % value.microsecond).rstrip("0")
-            return (encoded + "Z").encode("ascii")
-        raise InvalidValueType((self.__class__, datetime))
+    def _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")
 
 
-    def todatetime(self):
-        return self._strptime(self._value.decode("ascii"))
+    def _encode(self):
+        self._assert_ready()
+        value = self._value
+        if value.microsecond > 0:
+            encoded = self._encode_time()
+            return b"".join((self.tag, len_encode(len(encoded)), encoded))
+        return b"".join((self.tag, LEN_LEN_YYYYMMDDHHMMSSZ, self._encode_time()))
+
+    def _encode1st(self, state):
+        self._assert_ready()
+        vlen = len(self._encode_time())
+        return len(self.tag) + len_size(vlen) + vlen, state
+
+    def _encode2nd(self, writer, state_iter):
+        write_full(writer, self._encode())
 
 
 class GraphicString(CommonString):
 
 
 class GraphicString(CommonString):
@@ -4015,18 +5459,6 @@ class GraphicString(CommonString):
     asn1_type_name = "GraphicString"
 
 
     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"
-
-
 class GeneralString(CommonString):
     __slots__ = ()
     tag_default = tag_encode(27)
 class GeneralString(CommonString):
     __slots__ = ()
     tag_default = tag_encode(27)
@@ -4048,6 +5480,13 @@ class BMPString(CommonString):
     asn1_type_name = "BMPString"
 
 
     asn1_type_name = "BMPString"
 
 
+ChoiceState = namedtuple(
+    "ChoiceState",
+    BasicState._fields + ("specs", "value",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Choice(Obj):
     """``CHOICE`` special type
 
 class Choice(Obj):
     """``CHOICE`` special type
 
@@ -4103,13 +5542,13 @@ class Choice(Obj):
         """
         if impl is not None:
             raise ValueError("no implicit tag allowed for CHOICE")
         """
         if impl is not None:
             raise ValueError("no implicit tag allowed for CHOICE")
-        super(Choice, self).__init__(None, expl, default, optional, _decoded)
+        super().__init__(None, expl, default, optional, _decoded)
         if schema is None:
             schema = getattr(self, "schema", ())
         if len(schema) == 0:
             raise ValueError("schema must be specified")
         self.specs = (
         if schema is None:
             schema = getattr(self, "schema", ())
         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:
         )
         self._value = None
         if value is not None:
@@ -4121,10 +5560,13 @@ class Choice(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
             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):
 
     def _value_sanitize(self, 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:
             choice, obj = value
             spec = self.specs.get(choice)
             if spec is None:
@@ -4147,24 +5589,31 @@ class Choice(Obj):
             self._value[1].bered
         )
 
             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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        value = self._value
-        if value is not None:
-            obj._value = (value[0], value[1].copy())
-        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().__setstate__(state)
+        self.specs = state.specs
+        self._value = state.value
 
     def __eq__(self, their):
 
     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
             return self._value == their
         if not isinstance(their, self.__class__):
             return False
@@ -4190,14 +5639,27 @@ class Choice(Obj):
 
     @property
     def choice(self):
 
     @property
     def choice(self):
+        """Name of the choice
+        """
         self._assert_ready()
         return self._value[0]
 
     @property
     def value(self):
         self._assert_ready()
         return self._value[0]
 
     @property
     def value(self):
+        """Value of underlying choice
+        """
         self._assert_ready()
         return self._value[1]
 
         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 self.specs.values())
+
     def __getitem__(self, key):
         if key not in self.specs:
             raise ObjUnknown(key)
     def __getitem__(self, key):
         if key not in self.specs:
             raise ObjUnknown(key)
@@ -4228,8 +5690,19 @@ class Choice(Obj):
         self._assert_ready()
         return self._value[1].encode()
 
         self._assert_ready()
         return self._value[1].encode()
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
-        for choice, spec in iteritems(self.specs):
+    def _encode1st(self, state):
+        self._assert_ready()
+        return self._value[1].encode1st(state)
+
+    def _encode2nd(self, writer, state_iter):
+        self._value[1].encode2nd(writer, state_iter)
+
+    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 self.specs.items():
             sub_decode_path = decode_path + (choice,)
             try:
                 spec.decode(
             sub_decode_path = decode_path + (choice,)
             try:
                 spec.decode(
@@ -4251,15 +5724,28 @@ class Choice(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
             return
-        value, tail = spec.decode(
-            tlv,
-            offset=offset,
-            leavemm=True,
-            decode_path=sub_decode_path,
-            ctx=ctx,
-            _ctx_immutable=False,
-        )
+        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,
         obj = self.__class__(
             schema=self.specs,
             expl=self._expl,
@@ -4268,7 +5754,7 @@ class Choice(Obj):
             _decoded=(offset, 0, value.fulllen),
         )
         obj._value = (choice, value)
             _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()))
 
     def __repr__(self):
         value = pp_console_row(next(self.pps()))
@@ -4305,9 +5791,9 @@ class PrimitiveTypes(Choice):
 
     It could be useful for general decoding of some unspecified values:
 
 
     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
     OCTET STRING 3 bytes 666f6f
-    >>> PrimitiveTypes().decode(hexdec("0203123456"))[0].value
+    >>> PrimitiveTypes().decod(hexdec("0203123456")).value
     INTEGER 1193046
     """
     __slots__ = ()
     INTEGER 1193046
     """
     __slots__ = ()
@@ -4335,11 +5821,18 @@ class PrimitiveTypes(Choice):
     ))
 
 
     ))
 
 
+AnyState = namedtuple(
+    "AnyState",
+    BasicState._fields + ("value", "defined"),
+    **NAMEDTUPLE_KWARGS
+)
+
+
 class Any(Obj):
     """``ANY`` special type
 
     >>> Any(Integer(-123))
 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))
     >>> a = Any(OctetString(b"hello world").encode())
     ANY 040b68656c6c6f20776f726c64
     >>> hexenc(bytes(a))
@@ -4359,28 +5852,47 @@ class Any(Obj):
         """
         :param value: set the value. Either any kind of pyderasn's
                       **ready** object, or bytes. Pay attention that
         """
         :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
         """
         :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)
+        super().__init__(None, expl, None, optional, _decoded)
+        if value is None:
+            self._value = None
+        else:
+            value = self._value_sanitize(value)
+            self._value = value
+            if self._expl is None:
+                if value.__class__ == bytes or value.__class__ == memoryview:
+                    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):
         self.defined = None
 
     def _value_sanitize(self, value):
-        if isinstance(value, binary_type):
+        if value.__class__ == bytes or value.__class__ == memoryview:
+            if len(value) == 0:
+                raise ValueError("%s value can not be empty" % self.__class__.__name__)
             return value
         if isinstance(value, self.__class__):
             return value._value
             return value
         if isinstance(value, self.__class__):
             return value._value
-        if isinstance(value, Obj):
-            return value.encode()
-        raise InvalidValueType((self.__class__, Obj, binary_type))
+        if not isinstance(value, Obj):
+            raise InvalidValueType((self.__class__, Obj, bytes))
+        return value
 
     @property
     def ready(self):
         return self._value is not None
 
 
     @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:
     @property
     def bered(self):
         if self.expl_lenindef or self.lenindef:
@@ -4389,25 +5901,38 @@ class Any(Obj):
             return False
         return self.defined[1].bered
 
             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
-        obj.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        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().__setstate__(state)
+        self._value = state.value
+        self.defined = state.defined
 
     def __eq__(self, their):
 
     def __eq__(self, their):
-        if isinstance(their, binary_type):
-            return self._value == their
+        if their.__class__ == bytes or their.__class__ == memoryview:
+            if self._value.__class__ == bytes or their.__class__ == memoryview:
+                return self._value == their
+            return self._value.encode() == their
         if issubclass(their.__class__, Any):
         if issubclass(their.__class__, Any):
-            return self._value == their._value
+            if self.ready and their.ready:
+                return self.memoryview() == their.memoryview()
+            return self.ready == their.ready
         return False
 
     def __call__(
         return False
 
     def __call__(
@@ -4424,7 +5949,19 @@ class Any(Obj):
 
     def __bytes__(self):
         self._assert_ready()
 
     def __bytes__(self):
         self._assert_ready()
-        return self._value
+        value = self._value
+        if value.__class__ == bytes:
+            return value
+        if value.__class__ == memoryview:
+            return bytes(value)
+        return self._value.encode()
+
+    def memoryview(self):
+        self._assert_ready()
+        value = self._value
+        if value.__class__ == memoryview:
+            return memoryview(value)
+        return memoryview(bytes(self))
 
     @property
     def tlen(self):
 
     @property
     def tlen(self):
@@ -4432,9 +5969,34 @@ class Any(Obj):
 
     def _encode(self):
         self._assert_ready()
 
     def _encode(self):
         self._assert_ready()
-        return self._value
+        value = self._value
+        if value.__class__ == bytes or value.__class__ == memoryview:
+            return bytes(self)
+        return value.encode()
+
+    def _encode1st(self, state):
+        self._assert_ready()
+        value = self._value
+        if value.__class__ == bytes or value.__class__ == memoryview:
+            return len(value), state
+        return value.encode1st(state)
 
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode2nd(self, writer, state_iter):
+        value = self._value
+        if value.__class__ == bytes or value.__class__ == memoryview:
+            write_full(writer, value)
+        else:
+            value.encode2nd(writer, state_iter)
+
+    def _encode_cer(self, writer):
+        self._assert_ready()
+        value = self._value
+        if value.__class__ == bytes or value.__class__ == memoryview:
+            write_full(writer, value)
+        else:
+            value.encode_cer(writer)
+
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4471,14 +6033,15 @@ class Any(Obj):
                 chunk_i += 1
             tlvlen = tlen + llen + vlen + EOC_LEN
             obj = self.__class__(
                 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
                 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,
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -4495,25 +6058,42 @@ class Any(Obj):
             )
         tlvlen = tlen + llen + l
         v, tail = tlv[:tlvlen], v[l:]
             )
         tlvlen = tlen + llen + l
         v, tail = tlv[:tlvlen], v[l:]
+        if evgen_mode:
+            value = None
+        elif ctx.get("keep_memoryview", False):
+            value = v
+        else:
+            value = v.tobytes()
         obj = self.__class__(
         obj = self.__class__(
-            value=v.tobytes(),
+            value=value,
             expl=self._expl,
             optional=self.optional,
             _decoded=(offset, 0, tlvlen),
         )
             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=()):
 
     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__ == bytes or value.__class__ == memoryview:
+            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,
         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__ == bytes or
+                value.__class__ == memoryview
+            ) else None,
             optional=self.optional,
             default=self == self.default,
             impl=None if self.tag == self.tag_default else tag_decode(self.tag),
             optional=self.optional,
             default=self == self.default,
             impl=None if self.tag == self.tag_default else tag_decode(self.tag),
@@ -4543,19 +6123,6 @@ class Any(Obj):
 # ASN.1 constructed types
 ########################################################################
 
 # ASN.1 constructed types
 ########################################################################
 
-def get_def_by_path(defines_by_path, sub_decode_path):
-    """Get define by decode path
-    """
-    for path, define in defines_by_path:
-        if len(path) != len(sub_decode_path):
-            continue
-        for p1, p2 in zip(path, sub_decode_path):
-            if (p1 != any) and (p1 != p2):
-                break
-        else:
-            return define
-
-
 def abs_decode_path(decode_path, rel_path):
     """Create an absolute decode path from current and relative ones
 
 def abs_decode_path(decode_path, rel_path):
     """Create an absolute decode path from current and relative ones
 
@@ -4581,7 +6148,28 @@ def abs_decode_path(decode_path, rel_path):
     return decode_path + rel_path
 
 
     return decode_path + rel_path
 
 
-class Sequence(Obj):
+SequenceState = namedtuple(
+    "SequenceState",
+    BasicState._fields + ("specs", "value",),
+    **NAMEDTUPLE_KWARGS
+)
+
+
+class SequenceEncode1stMixin:
+    __slots__ = ()
+
+    def _encode1st(self, state):
+        state.append(0)
+        idx = len(state) - 1
+        vlen = 0
+        for v in self._values_for_encoding():
+            l, _ = v.encode1st(state)
+            vlen += l
+        state[idx] = vlen
+        return len(self.tag) + len_size(vlen) + vlen, state
+
+
+class Sequence(SequenceEncode1stMixin, Obj):
     """``SEQUENCE`` structure type
 
     You have to make specification of sequence::
     """``SEQUENCE`` structure type
 
     You have to make specification of sequence::
@@ -4669,6 +6257,11 @@ class Sequence(Obj):
     defaulted values existence validation by setting
     ``"allow_default_values": True`` :ref:`context <ctx>` option.
 
     defaulted values existence validation by setting
     ``"allow_default_values": True`` :ref:`context <ctx>` option.
 
+    All values with DEFAULT specified are decoded atomically in
+    :ref:`evgen mode <evgen_mode>`. If DEFAULT value is some kind of
+    SEQUENCE, then it will be yielded as a single element, not
+    disassembled. That is required for DEFAULT existence check.
+
     Two sequences are equal if they have equal specification (schema),
     implicit/explicit tagging and the same values.
     """
     Two sequences are equal if they have equal specification (schema),
     implicit/explicit tagging and the same values.
     """
@@ -4686,11 +6279,11 @@ class Sequence(Obj):
             optional=False,
             _decoded=(0, 0, 0),
     ):
             optional=False,
             _decoded=(0, 0, 0),
     ):
-        super(Sequence, self).__init__(impl, expl, default, optional, _decoded)
+        super().__init__(impl, expl, default, optional, _decoded)
         if schema is None:
             schema = getattr(self, "schema", ())
         self.specs = (
         if schema is None:
             schema = getattr(self, "schema", ())
         self.specs = (
-            schema if isinstance(schema, OrderedDict) else OrderedDict(schema)
+            schema if schema.__class__ == OrderedDict else OrderedDict(schema)
         )
         self._value = {}
         if value is not None:
         )
         self._value = {}
         if value is not None:
@@ -4710,41 +6303,48 @@ class Sequence(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
             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):
 
     @property
     def ready(self):
-        for name, spec in iteritems(self.specs):
+        for name, spec in self.specs.items():
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
                     continue
                 return False
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
                     continue
                 return False
-            else:
-                if not value.ready:
-                    return False
+            if not value.ready:
+                return False
         return True
 
     @property
     def bered(self):
         if self.expl_lenindef or self.lenindef or self.ber_encoded:
             return True
         return True
 
     @property
     def bered(self):
         if self.expl_lenindef or self.lenindef or self.ber_encoded:
             return True
-        return any(value.bered for value in itervalues(self._value))
-
-    def copy(self):
-        obj = self.__class__(schema=self.specs)
-        obj.tag = self.tag
-        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.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        obj._value = {k: v.copy() for k, v in iteritems(self._value)}
-        return obj
+        return any(value.bered for value in self._value.values())
+
+    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 self._value.items()},
+        )
+
+    def __setstate__(self, state):
+        super().__setstate__(state)
+        self.specs = state.specs
+        self._value = state.value
 
     def __eq__(self, their):
         if not isinstance(their, self.__class__):
 
     def __eq__(self, their):
         if not isinstance(their, self.__class__):
@@ -4802,22 +6402,31 @@ class Sequence(Obj):
             return spec.default
         return None
 
             return spec.default
         return None
 
-    def _encoded_values(self):
-        raws = []
-        for name, spec in iteritems(self.specs):
+    def _values_for_encoding(self):
+        for name, spec in self.specs.items():
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
                     continue
                 raise ObjNotReady(name)
             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):
 
     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))
 
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _encode2nd(self, writer, state_iter):
+        write_full(writer, self.tag + len_encode(next(state_iter)))
+        for v in self._values_for_encoding():
+            v.encode2nd(writer, state_iter)
+
+    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:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4834,6 +6443,7 @@ class Sequence(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
+            yield None
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
@@ -4870,29 +6480,42 @@ class Sequence(Obj):
         values = {}
         ber_encoded = False
         ctx_allow_default_values = ctx.get("allow_default_values", False)
         values = {}
         ber_encoded = False
         ctx_allow_default_values = ctx.get("allow_default_values", False)
-        for name, spec in iteritems(self.specs):
+        for name, spec in self.specs.items():
             if spec.optional and (
                     (lenindef and v[:EOC_LEN].tobytes() == EOC) or
                     len(v) == 0
             ):
                 continue
             if spec.optional and (
                     (lenindef and v[:EOC_LEN].tobytes() == EOC) or
                     len(v) == 0
             ):
                 continue
+            spec_defaulted = spec.default is not None
             sub_decode_path = decode_path + (name,)
             try:
             sub_decode_path = decode_path + (name,)
             try:
-                value, v_tail = spec.decode(
-                    v,
-                    sub_offset,
-                    leavemm=True,
-                    decode_path=sub_decode_path,
-                    ctx=ctx,
-                    _ctx_immutable=False,
-                )
+                if evgen_mode and not spec_defaulted:
+                    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)
             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):
                 defined_by, defined_spec = defined
                 if issubclass(value.__class__, SequenceOf):
                     for i, _value in enumerate(value):
@@ -4944,31 +6567,34 @@ class Sequence(Obj):
             vlen += value_len
             sub_offset += value_len
             v = v_tail
             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 spec_defaulted:
+                if evgen_mode:
+                    yield sub_decode_path, value, v_tail
+                if 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,
+                        )
+            if not evgen_mode:
+                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(
         if lenindef:
             if v[:EOC_LEN].tobytes() != EOC:
                 raise DecodeError(
@@ -4997,7 +6623,7 @@ class Sequence(Obj):
         obj._value = values
         obj.lenindef = lenindef
         obj.ber_encoded = ber_encoded
         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()))
 
     def __repr__(self):
         value = pp_console_row(next(self.pps()))
@@ -5041,7 +6667,7 @@ class Sequence(Obj):
             yield pp
 
 
             yield pp
 
 
-class Set(Sequence):
+class Set(Sequence, SequenceEncode1stMixin):
     """``SET`` structure type
 
     Its usage is identical to :py:class:`pyderasn.Sequence`.
     """``SET`` structure type
 
     Its usage is identical to :py:class:`pyderasn.Sequence`.
@@ -5049,8 +6675,8 @@ class Set(Sequence):
     .. _allow_unordered_set_ctx:
 
     DER prohibits unordered values encoding and will raise an error
     .. _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.
     """
     ordering check by setting ``"allow_unordered_set": True``
     :ref:`context <ctx>` option.
     """
@@ -5058,16 +6684,19 @@ class Set(Sequence):
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET"
 
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET"
 
-    def _encode(self):
-        raws = self._encoded_values()
-        raws.sort()
-        v = b"".join(raws)
-        return b"".join((self.tag, len_encode(len(v)), v))
+    def _values_for_encoding(self):
+        return sorted(super()._values_for_encoding(), key=attrgetter("tag_order"))
 
 
-    def _specs_items(self):
-        return iteritems(self.specs)
+    def _encode_cer(self, writer):
+        write_full(writer, self.tag + LENINDEF)
+        for v in sorted(
+                super()._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):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -5084,6 +6713,7 @@ class Set(Sequence):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
@@ -5120,12 +6750,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)
         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])
+        tag_order_prev = (0, 0)
+        _specs_items = copy(self.specs)
 
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
 
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
-            for name, spec in self._specs_items():
+            for name, spec in _specs_items.items():
                 sub_decode_path = decode_path + (name,)
                 try:
                     spec.decode(
                 sub_decode_path = decode_path + (name,)
                 try:
                     spec.decode(
@@ -5146,16 +6777,30 @@ class Set(Sequence):
                     decode_path=decode_path,
                     offset=offset,
                 )
                     decode_path=decode_path,
                     offset=offset,
                 )
-            value, v_tail = spec.decode(
-                v,
-                sub_offset,
-                leavemm=True,
-                decode_path=sub_decode_path,
-                ctx=ctx,
-                _ctx_immutable=False,
-            )
+            spec_defaulted = spec.default is not None
+            if evgen_mode and not spec_defaulted:
+                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
             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:
                 if ctx_bered or ctx_allow_unordered_set:
                     ber_encoded = True
                 else:
@@ -5165,22 +6810,27 @@ class Set(Sequence):
                         decode_path=sub_decode_path,
                         offset=sub_offset,
                     )
                         decode_path=sub_decode_path,
                         offset=sub_offset,
                     )
-            if spec.default is None or value != spec.default:
-                pass
-            elif ctx_bered or ctx_allow_default_values:
-                ber_encoded = True
-            else:
-                raise DecodeError(
-                    "DEFAULT value met",
-                    klass=self.__class__,
-                    decode_path=sub_decode_path,
-                    offset=sub_offset,
-                )
+            if spec_defaulted:
+                if evgen_mode:
+                    yield sub_decode_path, value, v_tail
+                if value != spec.default:
+                    pass
+                elif ctx_bered or ctx_allow_default_values:
+                    ber_encoded = True
+                else:
+                    raise DecodeError(
+                        "DEFAULT value met",
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
             values[name] = value
             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
             sub_offset += value_len
             vlen += value_len
             v = v_tail
+
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
@@ -5199,19 +6849,28 @@ class Set(Sequence):
                 )
             tail = v[EOC_LEN:]
             obj.lenindef = True
                 )
             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 self.specs.items():
+            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
         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):
+class SequenceOf(SequenceEncode1stMixin, Obj):
     """``SEQUENCE OF`` sequence type
 
     For that kind of type you must specify the object it will carry on
     """``SEQUENCE OF`` sequence type
 
     For that kind of type you must specify the object it will carry on
@@ -5237,9 +6896,21 @@ class SequenceOf(Obj):
     >>> ints
     Ints SEQUENCE OF[INTEGER 123, INTEGER 345]
 
     >>> 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)])
 
     >>> 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)
     """
     __slots__ = ("spec", "_bound_min", "_bound_max")
     tag_default = tag_encode(form=TagFormConstructed, num=16)
@@ -5256,13 +6927,7 @@ class SequenceOf(Obj):
             optional=False,
             _decoded=(0, 0, 0),
     ):
             optional=False,
             _decoded=(0, 0, 0),
     ):
-        super(SequenceOf, self).__init__(
-            impl,
-            expl,
-            default,
-            optional,
-            _decoded,
-        )
+        super().__init__(impl, expl, default, optional, _decoded)
         if schema is None:
             schema = getattr(self, "schema", None)
         if schema is None:
         if schema is None:
             schema = getattr(self, "schema", None)
         if schema is None:
@@ -5286,24 +6951,33 @@ class SequenceOf(Obj):
             default_obj._value = default_value
             self.default = default_obj
             if value is None:
             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):
 
     def _value_sanitize(self, value):
+        iterator = False
         if issubclass(value.__class__, SequenceOf):
             value = value._value
         if issubclass(value.__class__, SequenceOf):
             value = value._value
+        elif hasattr(value, NEXT_ATTR_NAME):
+            iterator = True
         elif hasattr(value, "__iter__"):
             value = list(value)
         else:
         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):
         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
         return all(v.ready for v in self._value)
 
     @property
@@ -5312,22 +6986,34 @@ class SequenceOf(Obj):
             return True
         return any(v.bered for v in self._value)
 
             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.expl_lenindef = self.expl_lenindef
-        obj.lenindef = self.lenindef
-        obj.ber_encoded = self.ber_encoded
-        obj._value = [v.copy() for v in self._value]
-        return obj
+    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().__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__):
 
     def __eq__(self, their):
         if isinstance(their, self.__class__):
@@ -5378,11 +7064,9 @@ class SequenceOf(Obj):
         self._value.append(value)
 
     def __iter__(self):
         self._value.append(value)
 
     def __iter__(self):
-        self._assert_ready()
         return iter(self._value)
 
     def __len__(self):
         return iter(self._value)
 
     def __len__(self):
-        self._assert_ready()
         return len(self._value)
 
     def __setitem__(self, key, value):
         return len(self._value)
 
     def __setitem__(self, key, value):
@@ -5393,14 +7077,83 @@ class SequenceOf(Obj):
     def __getitem__(self, key):
         return self._value[key]
 
     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):
 
     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 _encode1st(self, state):
+        state = super()._encode1st(state)
+        if hasattr(self._value, NEXT_ATTR_NAME):
+            self._value = []
+        return state
+
+    def _encode2nd(self, writer, state_iter):
+        write_full(writer, self.tag + len_encode(next(state_iter)))
+        iterator = hasattr(self._value, NEXT_ATTR_NAME)
+        if iterator:
+            values_count = 0
+            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,))
+                v.encode2nd(writer, state_iter)
+                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.encode2nd(writer, state_iter)
+
+    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:
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -5417,6 +7170,7 @@ class SequenceOf(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
+            yield None
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)
@@ -5451,6 +7205,7 @@ class SequenceOf(Obj):
         vlen = 0
         sub_offset = offset + tlen + llen
         _value = []
         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
         ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
         value_prev = memoryview(v[:0])
         ber_encoded = False
@@ -5458,15 +7213,27 @@ class SequenceOf(Obj):
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
         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,
-                _ctx_immutable=False,
-            )
+            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():
             value_len = value.fulllen
             if ordering_check:
                 if value_prev.tobytes() > v[:value_len].tobytes():
@@ -5480,13 +7247,22 @@ class SequenceOf(Obj):
                             offset=sub_offset,
                         )
                 value_prev = v[:value_len]
                             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
             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__(
         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,
                 schema=spec,
                 bounds=(self._bound_min, self._bound_max),
                 impl=self.tag,
@@ -5513,7 +7289,7 @@ class SequenceOf(Obj):
             obj.lenindef = True
             tail = v[EOC_LEN:]
         obj.ber_encoded = ber_encoded
             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]" % (
 
     def __repr__(self):
         return "%s[%s]" % (
@@ -5559,19 +7335,43 @@ class SetOf(SequenceOf):
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET OF"
 
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET OF"
 
+    def _value_sanitize(self, value):
+        value = super()._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):
     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))
 
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
-        return super(SetOf, self)._decode(
+    def _encode2nd(self, writer, state_iter):
+        write_full(writer, self.tag + len_encode(next(state_iter)))
+        values = []
+        for v in self._values_for_encoding():
+            buf = BytesIO()
+            v.encode2nd(buf.write, state_iter)
+            values.append(buf.getvalue())
+        values.sort()
+        for v in values:
+            write_full(writer, v)
+
+    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()._decode(
             tlv,
             offset,
             decode_path,
             ctx,
             tag_only,
             tlv,
             offset,
             decode_path,
             ctx,
             tag_only,
+            evgen_mode,
             ordering_check=True,
         )
 
             ordering_check=True,
         )
 
@@ -5599,7 +7399,7 @@ def generic_decoder():  # pragma: no cover
     choice = PrimitiveTypes()
     choice.specs["SequenceOf"] = SequenceOf(schema=choice)
     choice.specs["SetOf"] = SetOf(schema=choice)
     choice = PrimitiveTypes()
     choice.specs["SequenceOf"] = SequenceOf(schema=choice)
     choice.specs["SetOf"] = SetOf(schema=choice)
-    for i in six_xrange(31):
+    for i in range(31):
         choice.specs["SequenceOf%d" % i] = SequenceOf(
             schema=choice,
             expl=tag_ctxc(i),
         choice.specs["SequenceOf%d" % i] = SequenceOf(
             schema=choice,
             expl=tag_ctxc(i),
@@ -5617,13 +7417,14 @@ def generic_decoder():  # pragma: no cover
             with_colours=False,
             with_decode_path=False,
             decode_path_only=(),
             with_colours=False,
             with_decode_path=False,
             decode_path_only=(),
+            decode_path=(),
     ):
         def _pprint_pps(pps):
             for pp in pps:
                 if hasattr(pp, "_fields"):
                     if (
     ):
         def _pprint_pps(pps):
             for pp in pps:
                 if hasattr(pp, "_fields"):
                     if (
-                        decode_path_only != () and
-                        pp.decode_path[:len(decode_path_only)] != decode_path_only
+                            decode_path_only != () and
+                            pp.decode_path[:len(decode_path_only)] != decode_path_only
                     ):
                         continue
                     if pp.asn1_type_name == Choice.asn1_type_name:
                     ):
                         continue
                     if pp.asn1_type_name == Choice.asn1_type_name:
@@ -5641,20 +7442,419 @@ def generic_decoder():  # pragma: no cover
                         decode_path_len_decrease=len(decode_path_only),
                     )
                     for row in pp_console_blob(
                         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:
                     for row in _pprint_pps(pp):
                         yield row
                     ):
                         yield row
                 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)))
     return SEQUENCEOF(), pprint_any
 
 
     return SEQUENCEOF(), pprint_any
 
 
+def ascii_visualize(ba):
+    """Output only ASCII printable characters, like in hexdump -C
+
+    Example output for given binary string (right part)::
+
+        92 2b 39 20 65 91 e6 8e  95 93 1a 58 df 02 78 ea  |.+9 e......X..x.|
+                                                           ^^^^^^^^^^^^^^^^
+    """
+    return "".join((chr(b) if 0x20 <= b <= 0x7E else ".") for b in ba)
+
+
+def hexdump(raw):
+    """Generate ``hexdump -C`` like output
+
+    Rendered example::
+
+        00000000  30 80 30 80 a0 80 02 01  02 00 00 02 14 54 a5 18  |0.0..........T..|
+        00000010  69 ef 8b 3f 15 fd ea ad  bd 47 e0 94 81 6b 06 6a  |i..?.....G...k.j|
+
+    Result of that function is a generator of lines, where each line is
+    a list of columns::
+
+        [
+            [...],
+            ["00000010 ", " 69", " ef", " 8b", " 3f", " 15", " fd", " ea", " ad ",
+                          " bd", " 47", " e0", " 94", " 81", " 6b", " 06", " 6a ",
+                          " |i..?.....G...k.j|"]
+            [...],
+        ]
+    """
+    hexed = hexenc(raw).upper()
+    addr, cols = 0, ["%08x " % 0]
+    for i in range(0, len(hexed), 2):
+        if i != 0 and i // 2 % 8 == 0:
+            cols[-1] += " "
+        if i != 0 and i // 2 % 16 == 0:
+            cols.append(" |%s|" % ascii_visualize(bytes(raw[addr:addr + 16])))
+            yield cols
+            addr += 16
+            cols = ["%08x " % addr]
+        cols.append(" " + hexed[i:i + 2])
+    if len(cols) > 0:
+        cols.append(" |%s|" % ascii_visualize(bytes(raw[addr:])))
+        yield cols
+
+
+def browse(raw, obj, oid_maps=()):
+    """Interactive browser
+
+    :param bytes raw: binary data you decoded
+    :param obj: decoded :py:class:`pyderasn.Obj`
+    :param oid_maps: list of ``str(OID) <-> human readable string`` dictionaries.
+                     Its human readable form is printed when OID is met
+
+    .. note:: `urwid <http://urwid.org/>`__ dependency required
+
+    This browser is an interactive terminal application for browsing
+    structures of your decoded ASN.1 objects. You can quit it with **q**
+    key. It consists of three windows:
+
+    :tree:
+     View of ASN.1 elements hierarchy. You can navigate it using **Up**,
+     **Down**, **PageUp**, **PageDown**, **Home**, **End** keys.
+     **Left** key goes to constructed element above. **Plus**/**Minus**
+     keys collapse/uncollapse constructed elements. **Space** toggles it
+    :info:
+     window with various information about element. You can scroll it
+     with **h**/**l** (down, up) (**H**/**L** for triple speed) keys
+    :hexdump:
+     window with raw data hexdump and highlighted current element's
+     contents. It automatically focuses on element's data. You can
+     scroll it with **j**/**k** (down, up) (**J**/**K** for triple
+     speed) keys. If element has explicit tag, then it also will be
+     highlighted with different colour
+
+    Window's header contains current decode path and progress bars with
+    position in *info* and *hexdump* windows.
+
+    If you press **d**, then current element will be saved in the
+    current directory under its decode path name (adding ".0", ".1", etc
+    suffix if such file already exists). **D** will save it with explicit tag.
+
+    You can also invoke it with ``--browse`` command line argument.
+    """
+    from copy import deepcopy
+    from os.path import exists as path_exists
+    import urwid
+
+    class TW(urwid.TreeWidget):
+        def __init__(self, state, *args, **kwargs):
+            self.state = state
+            self.scrolled = {"info": False, "hexdump": False}
+            super().__init__(*args, **kwargs)
+
+        def _get_pp(self):
+            pp = self.get_node().get_value()
+            constructed = len(pp) > 1
+            return (pp if hasattr(pp, "_fields") else pp[0]), constructed
+
+        def _state_update(self):
+            pp, _ = self._get_pp()
+            self.state["decode_path"].set_text(
+                ":".join(str(p) for p in pp.decode_path)
+            )
+            lines = deepcopy(self.state["hexed"])
+
+            def attr_set(i, attr):
+                line = lines[i // 16]
+                idx = 1 + (i - 16 * (i // 16))
+                line[idx] = (attr, line[idx])
+
+            if pp.expl_offset is not None:
+                for i in range(
+                        pp.expl_offset,
+                        pp.expl_offset + pp.expl_tlen + pp.expl_llen,
+                ):
+                    attr_set(i, "select-expl")
+            for i in range(pp.offset, pp.offset + pp.tlen + pp.llen + pp.vlen):
+                attr_set(i, "select-value")
+            self.state["hexdump"]._set_body([urwid.Text(line) for line in lines])
+            self.state["hexdump"].set_focus(pp.offset // 16)
+            self.state["hexdump"].set_focus_valign("middle")
+            self.state["hexdump_bar"].set_completion(
+                (100 * pp.offset // 16) //
+                len(self.state["hexdump"]._body.positions())
+            )
+
+            lines = [
+                [("header", "Name: "), pp.obj_name],
+                [("header", "Type: "), pp.asn1_type_name],
+                [("header", "Offset: "), "%d (0x%x)" % (pp.offset, pp.offset)],
+                [("header", "[TLV]len: "), "%d/%d/%d" % (
+                    pp.tlen, pp.llen, pp.vlen,
+                )],
+                [("header", "TLVlen: "), "%d" % sum((
+                    pp.tlen, pp.llen, pp.vlen,
+                ))],
+                [("header", "Slice: "), "[%d:%d]" % (
+                    pp.offset, pp.offset + pp.tlen + pp.llen + pp.vlen,
+                )],
+            ]
+            if pp.lenindef:
+                lines.append([("warning", "LENINDEF")])
+            if pp.ber_encoded:
+                lines.append([("warning", "BER encoded")])
+            if pp.bered:
+                lines.append([("warning", "BERed")])
+            if pp.expl is not None:
+                lines.append([("header", "EXPLICIT")])
+                klass, _, num = pp.expl
+                lines.append(["  Tag: %s%d" % (TagClassReprs[klass], num)])
+                if pp.expl_offset is not None:
+                    lines.append(["  Offset: %d" % pp.expl_offset])
+                    lines.append(["  [TLV]len: %d/%d/%d" % (
+                        pp.expl_tlen, pp.expl_llen, pp.expl_vlen,
+                    )])
+                    lines.append(["  TLVlen: %d" % sum((
+                        pp.expl_tlen, pp.expl_llen, pp.expl_vlen,
+                    ))])
+                    lines.append(["  Slice: [%d:%d]" % (
+                        pp.expl_offset,
+                        pp.expl_offset + pp.expl_tlen + pp.expl_llen + pp.expl_vlen,
+                    )])
+            if pp.impl is not None:
+                klass, _, num = pp.impl
+                lines.append([
+                    ("header", "IMPLICIT: "), "%s%d" % (TagClassReprs[klass], num),
+                ])
+            if pp.optional:
+                lines.append(["OPTIONAL"])
+            if pp.default:
+                lines.append(["DEFAULT"])
+            if len(pp.decode_path) > 0:
+                ent = pp.decode_path[-1]
+                if isinstance(ent, DecodePathDefBy):
+                    lines.append([""])
+                    value = str(ent.defined_by)
+                    oid_name = find_oid_name(
+                        ent.defined_by.asn1_type_name, oid_maps, value,
+                    )
+                    lines.append([("header", "DEFINED BY: "), "%s" % (
+                        value if oid_name is None
+                        else "%s (%s)" % (oid_name, value)
+                    )])
+            lines.append([""])
+            if pp.value is not None:
+                lines.append([("header", "Value: "), pp.value])
+                if (
+                        len(oid_maps) > 0 and
+                        pp.asn1_type_name == ObjectIdentifier.asn1_type_name
+                ):
+                    for oid_map in oid_maps:
+                        oid_name = oid_map.get(pp.value)
+                        if oid_name is not None:
+                            lines.append([("header", "Human: "), oid_name])
+                            break
+                if pp.asn1_type_name == Integer.asn1_type_name:
+                    lines.append([
+                        ("header", "Decimal: "), "%d" % int(pp.obj),
+                    ])
+                    lines.append([
+                        ("header", "Hexadecimal: "), colonize_hex(pp.obj.tohex()),
+                    ])
+            if pp.blob.__class__ == bytes:
+                blob = hexenc(pp.blob).upper()
+                for i in range(0, len(blob), 32):
+                    lines.append([colonize_hex(blob[i:i + 32])])
+            elif pp.blob.__class__ == tuple:
+                lines.append([", ".join(pp.blob)])
+            self.state["info"]._set_body([urwid.Text(line) for line in lines])
+            self.state["info_bar"].set_completion(0)
+
+        def selectable(self):
+            if self.state["widget_current"] != self:
+                self.state["widget_current"] = self
+                self.scrolled["info"] = False
+                self.scrolled["hexdump"] = False
+                self._state_update()
+            return super().selectable()
+
+        def _get_display_text_without_offset(self):
+            pp, constructed = self._get_pp()
+            style = "constructed" if constructed else ""
+            if len(pp.decode_path) == 0:
+                return (style, pp.obj_name)
+            if pp.asn1_type_name == "EOC":
+                return ("eoc", "EOC")
+            ent = pp.decode_path[-1]
+            if isinstance(ent, DecodePathDefBy):
+                value = str(ent.defined_by)
+                oid_name = find_oid_name(
+                    ent.defined_by.asn1_type_name, oid_maps, value,
+                )
+                return ("defby", "DEFBY:" + (
+                    value if oid_name is None else oid_name
+                ))
+            return (style, ent)
+
+        def get_display_text(self):
+            pp, _ = self._get_pp()
+            style, ent = self._get_display_text_without_offset()
+            return [(style, ent), " [%d]" % pp.offset]
+
+        def _scroll(self, what, step):
+            self.state[what]._invalidate()
+            pos = self.state[what].focus_position
+            if not self.scrolled[what]:
+                self.scrolled[what] = True
+                pos -= 2
+            pos = max(0, pos + step)
+            pos = min(pos, len(self.state[what]._body.positions()) - 1)
+            self.state[what].set_focus(pos)
+            self.state[what].set_focus_valign("top")
+            self.state[what + "_bar"].set_completion(
+                (100 * pos) // len(self.state[what]._body.positions())
+            )
+
+        def keypress(self, size, key):
+            if key == "q":
+                raise urwid.ExitMainLoop()
+
+            if key == " ":
+                self.expanded = not self.expanded
+                self.update_expanded_icon()
+                return None
+
+            hexdump_steps = {"j": 1, "k": -1, "J": 5, "K": -5}
+            if key in hexdump_steps:
+                self._scroll("hexdump", hexdump_steps[key])
+                return None
+
+            info_steps = {"h": 1, "l": -1, "H": 5, "L": -5}
+            if key in info_steps:
+                self._scroll("info", info_steps[key])
+                return None
+
+            if key in ("d", "D"):
+                pp, _ = self._get_pp()
+                dp = ":".join(str(p) for p in pp.decode_path)
+                dp = dp.replace(" ", "_")
+                if dp == "":
+                    dp = "root"
+                if key == "d" or pp.expl_offset is None:
+                    data = self.state["raw"][pp.offset:(
+                        pp.offset + pp.tlen + pp.llen + pp.vlen
+                    )]
+                else:
+                    data = self.state["raw"][pp.expl_offset:(
+                        pp.expl_offset + pp.expl_tlen + pp.expl_llen + pp.expl_vlen
+                    )]
+                ctr = 0
+
+                def duplicate_path(dp, ctr):
+                    if ctr == 0:
+                        return dp
+                    return "%s.%d" % (dp, ctr)
+
+                while True:
+                    if not path_exists(duplicate_path(dp, ctr)):
+                        break
+                    ctr += 1
+                dp = duplicate_path(dp, ctr)
+                with open(dp, "wb") as fd:
+                    fd.write(data)
+                self.state["decode_path"].set_text(
+                    ("warning", "Saved to: " + dp)
+                )
+                return None
+            return super().keypress(size, key)
+
+    class PN(urwid.ParentNode):
+        def __init__(self, state, value, *args, **kwargs):
+            self.state = state
+            if not hasattr(value, "_fields"):
+                value = list(value)
+            super().__init__(value, *args, **kwargs)
+
+        def load_widget(self):
+            return TW(self.state, self)
+
+        def load_child_keys(self):
+            value = self.get_value()
+            if hasattr(value, "_fields"):
+                return []
+            return range(len(value[1:]))
+
+        def load_child_node(self, key):
+            return PN(
+                self.state,
+                self.get_value()[key + 1],
+                parent=self,
+                key=key,
+                depth=self.get_depth() + 1,
+            )
+
+    class LabeledPG(urwid.ProgressBar):
+        def __init__(self, label, *args, **kwargs):
+            self.label = label
+            super().__init__(*args, **kwargs)
+
+        def get_text(self):
+            return "%s: %s" % (self.label, super().get_text())
+
+    WinHexdump = urwid.ListBox([urwid.Text("")])
+    WinInfo = urwid.ListBox([urwid.Text("")])
+    WinDecodePath = urwid.Text("", "center")
+    WinInfoBar = LabeledPG("info", "pg-normal", "pg-complete")
+    WinHexdumpBar = LabeledPG("hexdump", "pg-normal", "pg-complete")
+    WinTree = urwid.TreeListBox(urwid.TreeWalker(PN(
+        {
+            "raw": raw,
+            "hexed": list(hexdump(raw)),
+            "widget_current": None,
+            "info": WinInfo,
+            "info_bar": WinInfoBar,
+            "hexdump": WinHexdump,
+            "hexdump_bar": WinHexdumpBar,
+            "decode_path": WinDecodePath,
+        },
+        list(obj.pps()),
+    )))
+    help_text = " ".join((
+        "q:quit",
+        "space:(un)collapse",
+        "(pg)up/down/home/end:nav",
+        "jkJK:hexdump hlHL:info",
+        "dD:dump",
+    ))
+    urwid.MainLoop(
+        urwid.Frame(
+            urwid.Columns([
+                ("weight", 1, WinTree),
+                ("weight", 2, urwid.Pile([
+                    urwid.LineBox(WinInfo),
+                    urwid.LineBox(WinHexdump),
+                ])),
+            ]),
+            header=urwid.Columns([
+                ("weight", 2, urwid.AttrWrap(WinDecodePath, "header")),
+                ("weight", 1, WinInfoBar),
+                ("weight", 1, WinHexdumpBar),
+            ]),
+            footer=urwid.AttrWrap(urwid.Text(help_text), "help")
+        ),
+        [
+            ("header", "bold", ""),
+            ("constructed", "bold", ""),
+            ("help", "light magenta", ""),
+            ("warning", "light red", ""),
+            ("defby", "light red", ""),
+            ("eoc", "dark red", ""),
+            ("select-value", "light green", ""),
+            ("select-expl", "light red", ""),
+            ("pg-normal", "", "light blue"),
+            ("pg-complete", "black", "yellow"),
+        ],
+    ).run()
+
+
 def main():  # pragma: no cover
     import argparse
 def main():  # pragma: no cover
     import argparse
-    parser = argparse.ArgumentParser(description="PyDERASN ASN.1 BER/DER decoder")
+    parser = argparse.ArgumentParser(description="PyDERASN ASN.1 BER/CER/DER decoder")
     parser.add_argument(
         "--skip",
         type=int,
     parser.add_argument(
         "--skip",
         type=int,
@@ -5693,21 +7893,34 @@ def main():  # pragma: no cover
         help="Allow explicit tag out-of-bound",
     )
     parser.add_argument(
         help="Allow explicit tag out-of-bound",
     )
     parser.add_argument(
-        "DERFile",
+        "--evgen",
+        action="store_true",
+        help="Turn on event generation mode",
+    )
+    parser.add_argument(
+        "--browse",
+        action="store_true",
+        help="Start ASN.1 browser",
+    )
+    parser.add_argument(
+        "RAWFile",
         type=argparse.FileType("rb"),
         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 = parser.parse_args()
-    args.DERFile.seek(args.skip)
-    der = memoryview(args.DERFile.read())
-    args.DERFile.close()
+    try:
+        raw = file_mmaped(args.RAWFile)[args.skip:]
+    except:
+        args.RAWFile.seek(args.skip)
+        raw = memoryview(args.RAWFile.read())
+        args.RAWFile.close()
     oid_maps = (
         [obj_by_path(_path) for _path in (args.oids or "").split(",")]
         if args.oids else ()
     )
     oid_maps = (
         [obj_by_path(_path) for _path in (args.oids or "").split(",")]
         if args.oids else ()
     )
+    from functools import partial
     if args.schema:
         schema = obj_by_path(args.schema)
     if args.schema:
         schema = obj_by_path(args.schema)
-        from functools import partial
         pprinter = partial(pprint, big_blobs=True)
     else:
         schema, pprinter = generic_decoder()
         pprinter = partial(pprint, big_blobs=True)
     else:
         schema, pprinter = generic_decoder()
@@ -5717,20 +7930,32 @@ def main():  # pragma: no cover
     }
     if args.defines_by_path is not None:
         ctx["defines_by_path"] = obj_by_path(args.defines_by_path)
     }
     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,
+    if args.browse:
+        obj, _ = schema().decode(raw, ctx=ctx)
+        browse(raw, obj, oid_maps)
+        from sys import exit as sys_exit
+        sys_exit(0)
+    from os import environ
+    pprinter = partial(
+        pprinter,
         oid_maps=oid_maps,
         oid_maps=oid_maps,
-        with_colours=True if environ.get("NO_COLOR") is None else False,
+        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(":"))
         ),
         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))
 
 
 if __name__ == "__main__":
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))
 
 
 if __name__ == "__main__":
+    from pyderasn import *
     main()
     main()