]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Raise copyright years
[pyderasn.git] / pyderasn.py
index 7ca25aab5779972c785a10c46bb4da9df8933ed1..54242a3bd537fc9360217174cb79b9ccf832a73a 100755 (executable)
@@ -4,7 +4,7 @@
 # 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-2020 Sergey Matveev <stargrave@stargrave.org>
+# 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
@@ -17,9 +17,9 @@
 #
 # 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
+This library allows you to marshal various structures in ASN.1 DER/CER
 format, unmarshal BER/CER/DER ones.
 
     >>> i = Integer(123)
@@ -235,6 +235,7 @@ Currently available context options:
 * :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:
 
@@ -693,11 +694,6 @@ creates read-only memoryview on the file contents::
         raw = file_mmaped(fd)
         obj = Something.decode(raw)
 
-.. warning::
-
-   mmap-ed files in Python2.7 does not implement buffer protocol, so
-   memoryview won't work on them.
-
 .. warning::
 
    mmap maps the **whole** file. So it plays no role if you seek-ed it
@@ -711,6 +707,15 @@ creates read-only memoryview on the file contents::
    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
 ____________
 
@@ -727,8 +732,7 @@ directly to some writer/buffer. Just use
 :py:meth:`pyderasn.Obj.encode_cer` method, providing the writer where
 encoded data will flow::
 
-    opener = io.open if PY2 else open
-    with opener("result", "wb") as fd:
+    with open("result", "wb") as fd:
         obj.encode_cer(fd.write)
 
 ::
@@ -770,7 +774,7 @@ forcefully encoded in DER during CER encoding, by specifying
 
     class SignedAttributes(SetOf):
         schema = Attribute()
-        bounds = (1, 32)
+        bounds = (1, float("+inf"))
         der_forced = True
 
 .. _agg_octet_string:
@@ -887,8 +891,7 @@ the objects again, but writes their encoded representation to the writer.
 
 ::
 
-    opener = io.open if PY2 else open
-    with opener("result", "wb") as fd:
+    with open("result", "wb") as fd:
         obj.encode2nd(fd.write, iter(state))
 
 .. warning::
@@ -978,12 +981,12 @@ _____________
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
-   :members: __init__, todatetime
+   :members: __init__, todatetime, totzdatetime
 
 GeneralizedTime
 _______________
 .. autoclass:: pyderasn.GeneralizedTime
-   :members: __init__, todatetime
+   :members: __init__, todatetime, totzdatetime
 
 Special types
 -------------
@@ -1166,8 +1169,6 @@ Now you can print only the specified tree, for example signature algorithm::
 """
 
 from array import array
-from codecs import getdecoder
-from codecs import getencoder
 from collections import namedtuple
 from collections import OrderedDict
 from copy import copy
@@ -1178,33 +1179,24 @@ from math import ceil
 from operator import attrgetter
 from string import ascii_letters
 from string import digits
+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
 
-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
-
-
 try:
     from termcolor import colored
 except ImportError:  # pragma: no cover
     def colored(what, *args, **kwargs):
         return what
 
-__version__ = "8.1"
+try:
+    from dateutil.tz import UTC as tzUTC
+except ImportError:  # pragma: no cover
+    tzUTC = "missing"
+
+
+__version__ = "9.3"
 
 __all__ = (
     "agg_octet_string",
@@ -1282,12 +1274,12 @@ TagClassReprs = {
 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" if PY2 else "__next__"
+NEXT_ATTR_NAME = "__next__"
 
 
 def file_mmaped(fd):
@@ -1298,7 +1290,7 @@ def file_mmaped(fd):
 
     .. warning::
 
-       It is known to work under neither Python 2.x nor Windows.
+       It does not work under Windows.
     """
     import mmap
     return memoryview(mmap.mmap(fd.fileno(), length=0, prot=mmap.PROT_READ))
@@ -1348,7 +1340,7 @@ class DecodeError(ASN1Error):
                             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
@@ -1377,7 +1369,7 @@ class NotEnoughData(DecodeError):
 
 class ExceedingData(ASN1Error):
     def __init__(self, nbytes):
-        super(ExceedingData, self).__init__()
+        super().__init__()
         self.nbytes = nbytes
 
     def __str__(self):
@@ -1405,7 +1397,7 @@ class InvalidOID(DecodeError):
 
 class ObjUnknown(ASN1Error):
     def __init__(self, name):
-        super(ObjUnknown, self).__init__()
+        super().__init__()
         self.name = name
 
     def __str__(self):
@@ -1417,7 +1409,7 @@ class ObjUnknown(ASN1Error):
 
 class ObjNotReady(ASN1Error):
     def __init__(self, name):
-        super(ObjNotReady, self).__init__()
+        super().__init__()
         self.name = name
 
     def __str__(self):
@@ -1429,7 +1421,7 @@ class ObjNotReady(ASN1Error):
 
 class InvalidValueType(ASN1Error):
     def __init__(self, expected_types):
-        super(InvalidValueType, self).__init__()
+        super().__init__()
         self.expected_types = expected_types
 
     def __str__(self):
@@ -1443,7 +1435,7 @@ class InvalidValueType(ASN1Error):
 
 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
@@ -1463,20 +1455,16 @@ class BoundsError(ASN1Error):
 # Basic coders
 ########################################################################
 
-_hexdecoder = getdecoder("hex")
-_hexencoder = getencoder("hex")
-
-
 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
     """
-    return _hexencoder(data)[0].decode("ascii")
+    return data.hex()
 
 
 def int_bytes_len(num, byte_len=8):
@@ -1498,6 +1486,9 @@ def zero_ended_encode(num):
     return bytes(octets)
 
 
+int2byte = struct_Struct(">B").pack
+
+
 def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
     """Encode tag to binary form
 
@@ -1526,13 +1517,13 @@ def tag_decode(tag):
     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
-    for octet in iterbytes(tag[1:]):
+    for octet in tag[1:]:
         num <<= 7
         num |= octet & 0x7F
     return (klass, form, num)
@@ -1557,18 +1548,18 @@ def tag_strip(data):
     """
     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")
-        if indexbytes(data, i) & 0x80 == 0:
+        if data[i] & 0x80 == 0:
             break
-    if i == 1 and indexbytes(data, 1) < 0x1F:
+    if i == 1 and data[1] < 0x1F:
         raise DecodeError("unexpected long form")
-    if i > 1 and indexbytes(data, 1) & 0x7F == 0:
+    if i > 1 and data[1] & 0x7F == 0:
         raise DecodeError("leading zero byte in tag value")
     i += 1
     return data[:i], i, data[i:]
@@ -1579,7 +1570,7 @@ def len_encode(l):
         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)
@@ -1593,7 +1584,7 @@ def len_decode(data):
     """
     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
@@ -1601,10 +1592,10 @@ def len_decode(data):
         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
-    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")
@@ -1674,7 +1665,7 @@ else:
 class AutoAddSlots(type):
     def __new__(cls, name, bases, _dict):
         _dict["__slots__"] = _dict.get("__slots__", ())
-        return type.__new__(cls, name, bases, _dict)
+        return super().__new__(cls, name, bases, _dict)
 
 
 BasicState = namedtuple("BasicState", (
@@ -1693,8 +1684,7 @@ BasicState = namedtuple("BasicState", (
 ), **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
@@ -1808,7 +1798,7 @@ class Obj(object):
         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)
@@ -2217,7 +2207,7 @@ def encode2pass(obj):
     return buf.getvalue()
 
 
-class DecodePathDefBy(object):
+class DecodePathDefBy:
     """DEFINED BY representation inside decode path
     """
     __slots__ = ("defined_by",)
@@ -2327,7 +2317,7 @@ def _colourize(what, colour, with_colours, attrs=("bold",)):
 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):
@@ -2405,7 +2395,7 @@ def pp_console_row(
                 "(%s)" % colonize_hex(pp.obj.tohex()), "green", with_colours,
             ))
     if with_blob:
-        if pp.blob.__class__ == binary_type:
+        if pp.blob.__class__ == bytes:
             cols.append(hexenc(pp.blob))
         elif pp.blob.__class__ == tuple:
             cols.append(", ".join(pp.blob))
@@ -2427,9 +2417,9 @@ def pp_console_blob(pp, decode_path_len_decrease=0):
     decode_path_len = len(pp.decode_path) - decode_path_len_decrease
     if decode_path_len > 0:
         cols.append(" ." * (decode_path_len + 1))
-    if pp.blob.__class__ == binary_type:
+    if pp.blob.__class__ == bytes:
         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)])
     elif pp.blob.__class__ == tuple:
@@ -2541,7 +2531,7 @@ class Boolean(Obj):
         :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)
@@ -2582,7 +2572,7 @@ class Boolean(Obj):
         )
 
     def __setstate__(self, state):
-        super(Boolean, self).__setstate__(state)
+        super().__setstate__(state)
         self._value = state.value
 
     def __nonzero__(self):
@@ -2673,7 +2663,7 @@ class Boolean(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        first_octet = byte2int(v)
+        first_octet = v[0]
         ber_encoded = False
         if first_octet == 0:
             value = False
@@ -2797,7 +2787,7 @@ class Integer(Obj):
         :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.specs = specs if specs.__class__ == dict else dict(specs)
@@ -2820,7 +2810,7 @@ class Integer(Obj):
                 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
@@ -2859,7 +2849,7 @@ class Integer(Obj):
         )
 
     def __setstate__(self, state):
-        super(Integer, self).__setstate__(state)
+        super().__setstate__(state)
         self.specs = state.specs
         self._value = state.value
         self._bound_min = state.bound_min
@@ -2888,7 +2878,7 @@ class Integer(Obj):
         )))
 
     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
@@ -2905,7 +2895,7 @@ class Integer(Obj):
     def named(self):
         """Return named representation (if exists) of the value
         """
-        for name, value in iteritems(self.specs):
+        for name, value in self.specs.items():
             if value == self._value:
                 return name
         return None
@@ -2935,40 +2925,14 @@ class Integer(Obj):
     def _encode_payload(self):
         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:
-                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):
@@ -3025,9 +2989,9 @@ class Integer(Obj):
                 offset=offset,
             )
         v, tail = v[:l], v[l:]
-        first_octet = byte2int(v)
+        first_octet = v[0]
         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))
@@ -3038,21 +3002,7 @@ class Integer(Obj):
                     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,
@@ -3182,7 +3132,7 @@ class BitString(Obj):
         :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
         self.specs = specs if specs.__class__ == dict else dict(specs)
         self._value = None if value is None else self._value_sanitize(value)
@@ -3209,14 +3159,14 @@ class BitString(Obj):
         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):
-        if isinstance(value, (string_types, binary_type)):
+        if isinstance(value, (str, bytes)):
             if (
-                    isinstance(value, string_types) and
+                    isinstance(value, str) and
                     value.startswith("'")
             ):
                 if value.endswith("'B"):
@@ -3230,14 +3180,14 @@ class BitString(Obj):
                         len(value) * 4,
                         hexdec(value + ("" if len(value) % 2 == 0 else "0")),
                     )
-            if value.__class__ == binary_type:
+            if value.__class__ == bytes:
                 return (len(value) * 8, value)
-            raise InvalidValueType((self.__class__, string_types, binary_type))
+            raise InvalidValueType((self.__class__, str, bytes))
         if value.__class__ == tuple:
             if (
                     len(value) == 2 and
-                    isinstance(value[0], integer_types) and
-                    value[1].__class__ == binary_type
+                    isinstance(value[0], int) and
+                    value[1].__class__ == bytes
             ):
                 return value
             bits = []
@@ -3251,11 +3201,11 @@ class BitString(Obj):
             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
-        raise InvalidValueType((self.__class__, binary_type, string_types))
+        raise InvalidValueType((self.__class__, bytes, str))
 
     @property
     def ready(self):
@@ -3282,7 +3232,7 @@ class BitString(Obj):
         )
 
     def __setstate__(self, state):
-        super(BitString, self).__setstate__(state)
+        super().__setstate__(state)
         self.specs = state.specs
         self._value = state.value
         self.tag_constructed = state.tag_constructed
@@ -3290,7 +3240,7 @@ class BitString(Obj):
 
     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
@@ -3321,7 +3271,7 @@ class BitString(Obj):
 
         :returns: [str(name), ...]
         """
-        return [name for name, bit in iteritems(self.specs) if self[bit]]
+        return [name for name, bit in self.specs.items() if self[bit]]
 
     def __call__(
             self,
@@ -3345,11 +3295,8 @@ class BitString(Obj):
             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)
@@ -3388,11 +3335,11 @@ class BitString(Obj):
             return
         write_full(writer, self.tag_constructed)
         write_full(writer, LENINDEF)
-        for offset in six_xrange(0, (len(octets) // 999) * 999, 999):
+        for offset in range(0, (len(octets) // 999) * 999, 999):
             write_full(writer, b"".join((
                 BitString.tag_default,
                 LEN1K,
-                int2byte(0),
+                b"\x00",
                 octets[offset:offset + 999],
             )))
         tail = octets[offset + 999:]
@@ -3442,7 +3389,7 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            pad_size = byte2int(v)
+            pad_size = v[0]
             if l == 1 and pad_size != 0:
                 raise DecodeError(
                     "invalid empty value",
@@ -3457,7 +3404,7 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if byte2int(v[l - 1:l]) & ((1 << pad_size) - 1) != 0:
+            if v[l - 1] & ((1 << pad_size) - 1) != 0:
                 raise DecodeError(
                     "invalid pad",
                     klass=self.__class__,
@@ -3693,6 +3640,7 @@ class OctetString(Obj):
     tag_default = tag_encode(4)
     asn1_type_name = "OCTET STRING"
     evgen_mode_skip_value = True
+    memoryview_safe = True
 
     def __init__(
             self,
@@ -3715,7 +3663,7 @@ class OctetString(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(OctetString, self).__init__(impl, expl, default, optional, _decoded)
+        super().__init__(impl, expl, default, optional, _decoded)
         self._value = value
         self._bound_min, self._bound_max = getattr(
             self,
@@ -3742,7 +3690,7 @@ class OctetString(Obj):
         )
 
     def _value_sanitize(self, value):
-        if value.__class__ == binary_type or value.__class__ == memoryview:
+        if value.__class__ == bytes or value.__class__ == memoryview:
             pass
         elif issubclass(value.__class__, OctetString):
             value = value._value
@@ -3778,7 +3726,7 @@ class OctetString(Obj):
         )
 
     def __setstate__(self, state):
-        super(OctetString, self).__setstate__(state)
+        super().__setstate__(state)
         self._value = state.value
         self._bound_min = state.bound_min
         self._bound_max = state.bound_max
@@ -3789,8 +3737,12 @@ class OctetString(Obj):
         self._assert_ready()
         return bytes(self._value)
 
+    def memoryview(self):
+        self._assert_ready()
+        return memoryview(self._value)
+
     def __eq__(self, their):
-        if their.__class__ == binary_type:
+        if their.__class__ == bytes:
             return self._value == their
         if not issubclass(their.__class__, OctetString):
             return False
@@ -3849,7 +3801,7 @@ class OctetString(Obj):
             return
         write_full(writer, self.tag_constructed)
         write_full(writer, LENINDEF)
-        for offset in six_xrange(0, (len(octets) // 1000) * 1000, 1000):
+        for offset in range(0, (len(octets) // 1000) * 1000, 1000):
             write_full(writer, b"".join((
                 OctetString.tag_default,
                 LEN1K,
@@ -3902,12 +3854,15 @@ class OctetString(Obj):
                     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__(
-                    value=(
-                        None if (evgen_mode and self.evgen_mode_skip_value)
-                        else v.tobytes()
-                    ),
+                    value=value,
                     bounds=(self._bound_min, self._bound_max),
                     impl=self.tag,
                     expl=self._expl,
@@ -4155,7 +4110,7 @@ class Null(Obj):
         :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
@@ -4337,7 +4292,7 @@ class ObjectIdentifier(Obj):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(ObjectIdentifier, self).__init__(impl, expl, default, optional, _decoded)
+        super().__init__(impl, expl, default, optional, _decoded)
         self._value = value
         if value is not None:
             self._value = self._value_sanitize(value)
@@ -4362,7 +4317,7 @@ class ObjectIdentifier(Obj):
     def _value_sanitize(self, value):
         if issubclass(value.__class__, ObjectIdentifier):
             return value._value
-        if isinstance(value, string_types):
+        if isinstance(value, str):
             try:
                 value = array("L", (pureint(arc) for arc in value.split(".")))
             except ValueError:
@@ -4411,7 +4366,7 @@ class ObjectIdentifier(Obj):
         )
 
     def __setstate__(self, state):
-        super(ObjectIdentifier, self).__setstate__(state)
+        super().__setstate__(state)
         self._value = state.value
         self.defines = state.defines
 
@@ -4540,7 +4495,7 @@ class ObjectIdentifier(Obj):
             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
@@ -4645,7 +4600,7 @@ class Enumerated(Integer):
             _decoded=(0, 0, 0),
             bounds=None,  # dummy argument, workability for Integer.decode
     ):
-        super(Enumerated, self).__init__(
+        super().__init__(
             value, bounds, impl, expl, default, optional, _specs, _decoded,
         )
         if len(self.specs) == 0:
@@ -4654,8 +4609,8 @@ class Enumerated(Integer):
     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:
@@ -4663,7 +4618,7 @@ class Enumerated(Integer):
                     "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)
@@ -4757,18 +4712,19 @@ class CommonString(OctetString):
          - utf-16-be
     """
     __slots__ = ()
+    memoryview_safe = False
 
     def _value_sanitize(self, value):
         value_raw = None
         value_decoded = None
         if isinstance(value, self.__class__):
             value_raw = value._value
-        elif value.__class__ == text_type:
+        elif value.__class__ == str:
             value_decoded = value
-        elif value.__class__ == binary_type:
+        elif value.__class__ == bytes:
             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)
@@ -4789,9 +4745,9 @@ class CommonString(OctetString):
         return value_raw
 
     def __eq__(self, their):
-        if their.__class__ == binary_type:
+        if their.__class__ == bytes:
             return self._value == their
-        if their.__class__ == text_type:
+        if their.__class__ == str:
             return self._value == their.encode(self.encoding)
         if not isinstance(their, self.__class__):
             return False
@@ -4804,18 +4760,18 @@ class CommonString(OctetString):
     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):
-        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 = (
-                hexenc(bytes(self)) if no_unicode else
-                "".join(escape_control_unicode(c) for c in self.__unicode__())
-            )
+            value = "".join(escape_control_unicode(c) for c in self.__unicode__())
         yield _pp(
             obj=self,
             asn1_type_name=self.asn1_type_name,
@@ -4849,20 +4805,23 @@ class UTF8String(CommonString):
     asn1_type_name = "UTF8String"
 
 
-class AllowableCharsMixin(object):
+class AllowableCharsMixin:
+    __slots__ = ()
+
     @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(AllowableCharsMixin, self)._value_sanitize(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):
     """Numeric string
 
@@ -4872,11 +4831,14 @@ class NumericString(AllowableCharsMixin, CommonString):
     >>> 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"
-    _allowable_chars = frozenset(digits.encode("ascii") + b" ")
+
+    def __init__(self, *args, **kwargs):
+        self._allowable_chars = NUMERIC_ALLOWABLE_CHARS
+        super().__init__(*args, **kwargs)
 
 
 PrintableStringState = namedtuple(
@@ -4886,6 +4848,11 @@ PrintableStringState = namedtuple(
 )
 
 
+PRINTABLE_ALLOWABLE_CHARS = frozenset(
+    (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+)
+
+
 class PrintableString(AllowableCharsMixin, CommonString):
     """Printable string
 
@@ -4898,13 +4865,10 @@ class PrintableString(AllowableCharsMixin, CommonString):
     >>> obj.allow_asterisk, obj.allow_ampersand
     (True, False)
     """
-    __slots__ = ()
+    __slots__ = ("_allowable_chars",)
     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"))
 
@@ -4925,11 +4889,13 @@ class PrintableString(AllowableCharsMixin, CommonString):
         :param allow_asterisk: allow asterisk character
         :param allow_ampersand: allow ampersand character
         """
+        allowable_chars = PRINTABLE_ALLOWABLE_CHARS
         if allow_asterisk:
-            self._allowable_chars |= self._asterisk
+            allowable_chars |= self._asterisk
         if allow_ampersand:
-            self._allowable_chars |= self._ampersand
-        super(PrintableString, self).__init__(
+            allowable_chars |= self._ampersand
+        self._allowable_chars = allowable_chars
+        super().__init__(
             value, bounds, impl, expl, default, optional, _decoded, ctx,
         )
 
@@ -4947,12 +4913,12 @@ class PrintableString(AllowableCharsMixin, CommonString):
 
     def __getstate__(self):
         return PrintableStringState(
-            *super(PrintableString, self).__getstate__(),
+            *super().__getstate__(),
             **{"allowable_chars": self._allowable_chars}
         )
 
     def __setstate__(self, state):
-        super(PrintableString, self).__setstate__(state)
+        super().__setstate__(state)
         self._allowable_chars = state.allowable_chars
 
     def __call__(
@@ -4998,6 +4964,11 @@ class VideotexString(CommonString):
     asn1_type_name = "VideotexString"
 
 
+IA5_ALLOWABLE_CHARS = frozenset(b"".join(
+    chr(c).encode("ascii") for c in range(128)
+))
+
+
 class IA5String(AllowableCharsMixin, CommonString):
     """IA5 string
 
@@ -5012,13 +4983,14 @@ class IA5String(AllowableCharsMixin, CommonString):
     >>> IA5String().allowable_chars
     frozenset(["NUL", ... "DEL"])
     """
-    __slots__ = ()
+    __slots__ = ("_allowable_chars",)
     tag_default = tag_encode(22)
     encoding = "ascii"
     asn1_type_name = "IA5"
-    _allowable_chars = frozenset(b"".join(
-        six_unichr(c).encode("ascii") for c in six_xrange(128)
-    ))
+
+    def __init__(self, *args, **kwargs):
+        self._allowable_chars = IA5_ALLOWABLE_CHARS
+        super().__init__(*args, **kwargs)
 
 
 LEN_YYMMDDHHMMSSZ = len("YYMMDDHHMMSSZ")
@@ -5029,6 +5001,11 @@ 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
 
@@ -5038,13 +5015,14 @@ class VisibleString(AllowableCharsMixin, CommonString):
     >>> VisibleString().allowable_chars
     frozenset([" ", ... "~"])
     """
-    __slots__ = ()
+    __slots__ = ("_allowable_chars",)
     tag_default = tag_encode(26)
     encoding = "ascii"
     asn1_type_name = "VisibleString"
-    _allowable_chars = frozenset(b"".join(
-        six_unichr(c).encode("ascii") for c in six_xrange(ord(" "), ord("~") + 1)
-    ))
+
+    def __init__(self, *args, **kwargs):
+        self._allowable_chars = VISIBLE_ALLOWABLE_CHARS
+        super().__init__(*args, **kwargs)
 
 
 class ISO646String(VisibleString):
@@ -5082,6 +5060,8 @@ class UTCTime(VisibleString):
     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.
@@ -5131,9 +5111,7 @@ class UTCTime(VisibleString):
         :param default: set default value. Type same as in ``value``
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
-        super(UTCTime, self).__init__(
-            None, None, impl, expl, None, optional, _decoded, ctx,
-        )
+        super().__init__(None, None, impl, expl, None, optional, _decoded, ctx)
         self._value = value
         self.ber_raw = None
         if value is not None:
@@ -5204,7 +5182,7 @@ class UTCTime(VisibleString):
         return value.replace(microsecond=0)
 
     def _value_sanitize(self, value, ctx=None):
-        if value.__class__ == binary_type:
+        if value.__class__ == bytes:
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
@@ -5247,16 +5225,13 @@ class UTCTime(VisibleString):
             if self.ber_encoded:
                 value += " (%s)" % self.ber_raw
             return value
-        return text_type(self._pp_value())
+        return str(self._pp_value())
 
     def __getstate__(self):
-        return UTCTimeState(
-            *super(UTCTime, self).__getstate__(),
-            **{"ber_raw": self.ber_raw}
-        )
+        return UTCTimeState(*super().__getstate__(), **{"ber_raw": self.ber_raw})
 
     def __setstate__(self, state):
-        super(UTCTime, self).__setstate__(state)
+        super().__setstate__(state)
         self.ber_raw = state.ber_raw
 
     def __bytes__(self):
@@ -5264,7 +5239,7 @@ class UTCTime(VisibleString):
         return self._encode_time()
 
     def __eq__(self, their):
-        if their.__class__ == binary_type:
+        if their.__class__ == bytes:
             return self._encode_time() == their
         if their.__class__ == datetime:
             return self.todatetime() == their
@@ -5296,6 +5271,12 @@ class UTCTime(VisibleString):
     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()))
 
@@ -5561,7 +5542,7 @@ class Choice(Obj):
         """
         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:
@@ -5627,7 +5608,7 @@ class Choice(Obj):
         )
 
     def __setstate__(self, state):
-        super(Choice, self).__setstate__(state)
+        super().__setstate__(state)
         self.specs = state.specs
         self._value = state.value
 
@@ -5677,7 +5658,7 @@ class Choice(Obj):
 
     @property
     def tag_order_cer(self):
-        return min(v.tag_order_cer for v in itervalues(self.specs))
+        return min(v.tag_order_cer for v in self.specs.values())
 
     def __getitem__(self, key):
         if key not in self.specs:
@@ -5721,7 +5702,7 @@ class Choice(Obj):
         self._value[1].encode_cer(writer)
 
     def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
-        for choice, spec in iteritems(self.specs):
+        for choice, spec in self.specs.items():
             sub_decode_path = decode_path + (choice,)
             try:
                 spec.decode(
@@ -5876,14 +5857,14 @@ class Any(Obj):
         :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)
+        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__ == binary_type:
+                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
@@ -5893,14 +5874,14 @@ class Any(Obj):
         self.defined = None
 
     def _value_sanitize(self, value):
-        if value.__class__ == 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
         if not isinstance(value, Obj):
-            raise InvalidValueType((self.__class__, Obj, binary_type))
+            raise InvalidValueType((self.__class__, Obj, bytes))
         return value
 
     @property
@@ -5939,18 +5920,18 @@ class Any(Obj):
         )
 
     def __setstate__(self, state):
-        super(Any, self).__setstate__(state)
+        super().__setstate__(state)
         self._value = state.value
         self.defined = state.defined
 
     def __eq__(self, their):
-        if their.__class__ == binary_type:
-            if self._value.__class__ == binary_type:
+        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 self.ready and their.ready:
-                return bytes(self) == bytes(their)
+                return self.memoryview() == their.memoryview()
             return self.ready == their.ready
         return False
 
@@ -5969,10 +5950,19 @@ class Any(Obj):
     def __bytes__(self):
         self._assert_ready()
         value = self._value
-        if value.__class__ == binary_type:
+        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):
         return 0
@@ -5980,20 +5970,20 @@ class Any(Obj):
     def _encode(self):
         self._assert_ready()
         value = self._value
-        if value.__class__ == binary_type:
-            return 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__ == binary_type:
+        if value.__class__ == bytes or value.__class__ == memoryview:
             return len(value), state
         return value.encode1st(state)
 
     def _encode2nd(self, writer, state_iter):
         value = self._value
-        if value.__class__ == binary_type:
+        if value.__class__ == bytes or value.__class__ == memoryview:
             write_full(writer, value)
         else:
             value.encode2nd(writer, state_iter)
@@ -6001,7 +5991,7 @@ class Any(Obj):
     def _encode_cer(self, writer):
         self._assert_ready()
         value = self._value
-        if value.__class__ == binary_type:
+        if value.__class__ == bytes or value.__class__ == memoryview:
             write_full(writer, value)
         else:
             value.encode_cer(writer)
@@ -6068,8 +6058,14 @@ class Any(Obj):
             )
         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__(
-            value=None if evgen_mode else v.tobytes(),
+            value=value,
             expl=self._expl,
             optional=self.optional,
             _decoded=(offset, 0, tlvlen),
@@ -6084,7 +6080,7 @@ class Any(Obj):
         value = self._value
         if value is None:
             pass
-        elif value.__class__ == binary_type:
+        elif value.__class__ == bytes or value.__class__ == memoryview:
             value = None
         else:
             value = repr(value)
@@ -6094,7 +6090,10 @@ class Any(Obj):
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
             value=value,
-            blob=self._value if self._value.__class__ == binary_type else None,
+            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),
@@ -6156,7 +6155,9 @@ SequenceState = namedtuple(
 )
 
 
-class SequenceEncode1stMixing(object):
+class SequenceEncode1stMixin:
+    __slots__ = ()
+
     def _encode1st(self, state):
         state.append(0)
         idx = len(state) - 1
@@ -6168,7 +6169,7 @@ class SequenceEncode1stMixing(object):
         return len(self.tag) + len_size(vlen) + vlen, state
 
 
-class Sequence(SequenceEncode1stMixing, Obj):
+class Sequence(SequenceEncode1stMixin, Obj):
     """``SEQUENCE`` structure type
 
     You have to make specification of sequence::
@@ -6278,7 +6279,7 @@ class Sequence(SequenceEncode1stMixing, Obj):
             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 = (
@@ -6306,7 +6307,7 @@ class Sequence(SequenceEncode1stMixing, Obj):
 
     @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:
@@ -6320,7 +6321,7 @@ class Sequence(SequenceEncode1stMixing, Obj):
     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))
+        return any(value.bered for value in self._value.values())
 
     def __getstate__(self):
         return SequenceState(
@@ -6337,11 +6338,11 @@ class Sequence(SequenceEncode1stMixing, Obj):
             self.lenindef,
             self.ber_encoded,
             self.specs,
-            {k: copy(v) for k, v in iteritems(self._value)},
+            {k: copy(v) for k, v in self._value.items()},
         )
 
     def __setstate__(self, state):
-        super(Sequence, self).__setstate__(state)
+        super().__setstate__(state)
         self.specs = state.specs
         self._value = state.value
 
@@ -6402,7 +6403,7 @@ class Sequence(SequenceEncode1stMixing, Obj):
         return None
 
     def _values_for_encoding(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:
@@ -6479,7 +6480,7 @@ class Sequence(SequenceEncode1stMixing, Obj):
         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
@@ -6666,7 +6667,7 @@ class Sequence(SequenceEncode1stMixing, Obj):
             yield pp
 
 
-class Set(Sequence, SequenceEncode1stMixing):
+class Set(Sequence, SequenceEncode1stMixin):
     """``SET`` structure type
 
     Its usage is identical to :py:class:`pyderasn.Sequence`.
@@ -6684,15 +6685,12 @@ class Set(Sequence, SequenceEncode1stMixing):
     asn1_type_name = "SET"
 
     def _values_for_encoding(self):
-        return sorted(
-            super(Set, self)._values_for_encoding(),
-            key=attrgetter("tag_order"),
-        )
+        return sorted(super()._values_for_encoding(), key=attrgetter("tag_order"))
 
     def _encode_cer(self, writer):
         write_full(writer, self.tag + LENINDEF)
         for v in sorted(
-                super(Set, self)._values_for_encoding(),
+                super()._values_for_encoding(),
                 key=attrgetter("tag_order_cer"),
         ):
             v.encode_cer(writer)
@@ -6758,7 +6756,7 @@ class Set(Sequence, SequenceEncode1stMixing):
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
-            for name, spec in iteritems(_specs_items):
+            for name, spec in _specs_items.items():
                 sub_decode_path = decode_path + (name,)
                 try:
                     spec.decode(
@@ -6851,7 +6849,7 @@ class Set(Sequence, SequenceEncode1stMixing):
                 )
             tail = v[EOC_LEN:]
             obj.lenindef = True
-        for name, spec in iteritems(self.specs):
+        for name, spec in self.specs.items():
             if name not in values and not spec.optional:
                 raise DecodeError(
                     "%s value is not ready" % name,
@@ -6872,7 +6870,7 @@ SequenceOfState = namedtuple(
 )
 
 
-class SequenceOf(SequenceEncode1stMixing, Obj):
+class SequenceOf(SequenceEncode1stMixin, Obj):
     """``SEQUENCE OF`` sequence type
 
     For that kind of type you must specify the object it will carry on
@@ -6929,7 +6927,7 @@ class SequenceOf(SequenceEncode1stMixing, Obj):
             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:
@@ -7011,7 +7009,7 @@ class SequenceOf(SequenceEncode1stMixing, Obj):
         )
 
     def __setstate__(self, state):
-        super(SequenceOf, self).__setstate__(state)
+        super().__setstate__(state)
         self.spec = state.spec
         self._value = state.value
         self._bound_min = state.bound_min
@@ -7102,7 +7100,7 @@ class SequenceOf(SequenceEncode1stMixing, Obj):
         return b"".join((self.tag, len_encode(len(value)), value))
 
     def _encode1st(self, state):
-        state = super(SequenceOf, self)._encode1st(state)
+        state = super()._encode1st(state)
         if hasattr(self._value, NEXT_ATTR_NAME):
             self._value = []
         return state
@@ -7338,7 +7336,7 @@ class SetOf(SequenceOf):
     asn1_type_name = "SET OF"
 
     def _value_sanitize(self, value):
-        value = super(SetOf, self)._value_sanitize(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"
@@ -7367,7 +7365,7 @@ class SetOf(SequenceOf):
         write_full(writer, EOC)
 
     def _decode(self, tlv, offset, decode_path, ctx, tag_only, evgen_mode):
-        return super(SetOf, self)._decode(
+        return super()._decode(
             tlv,
             offset,
             decode_path,
@@ -7401,7 +7399,7 @@ def generic_decoder():  # pragma: no cover
     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),
@@ -7463,7 +7461,7 @@ def ascii_visualize(ba):
         92 2b 39 20 65 91 e6 8e  95 93 1a 58 df 02 78 ea  |.+9 e......X..x.|
                                                            ^^^^^^^^^^^^^^^^
     """
-    return "".join((six_unichr(b) if 0x20 <= b <= 0x7E else ".") for b in ba)
+    return "".join((chr(b) if 0x20 <= b <= 0x7E else ".") for b in ba)
 
 
 def hexdump(raw):
@@ -7487,17 +7485,17 @@ def hexdump(raw):
     """
     hexed = hexenc(raw).upper()
     addr, cols = 0, ["%08x " % 0]
-    for i in six_xrange(0, len(hexed), 2):
+    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(bytearray(raw[addr:addr + 16])))
+            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(bytearray(raw[addr:])))
+        cols.append(" |%s|" % ascii_visualize(bytes(raw[addr:])))
         yield cols
 
 
@@ -7547,7 +7545,7 @@ def browse(raw, obj, oid_maps=()):
         def __init__(self, state, *args, **kwargs):
             self.state = state
             self.scrolled = {"info": False, "hexdump": False}
-            super(TW, self).__init__(*args, **kwargs)
+            super().__init__(*args, **kwargs)
 
         def _get_pp(self):
             pp = self.get_node().get_value()
@@ -7567,12 +7565,12 @@ def browse(raw, obj, oid_maps=()):
                 line[idx] = (attr, line[idx])
 
             if pp.expl_offset is not None:
-                for i in six_xrange(
+                for i in range(
                         pp.expl_offset,
                         pp.expl_offset + pp.expl_tlen + pp.expl_llen,
                 ):
                     attr_set(i, "select-expl")
-            for i in six_xrange(pp.offset, pp.offset + pp.tlen + pp.llen + pp.vlen):
+            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)
@@ -7658,9 +7656,9 @@ def browse(raw, obj, oid_maps=()):
                     lines.append([
                         ("header", "Hexadecimal: "), colonize_hex(pp.obj.tohex()),
                     ])
-            if pp.blob.__class__ == binary_type:
+            if pp.blob.__class__ == bytes:
                 blob = hexenc(pp.blob).upper()
-                for i in six_xrange(0, len(blob), 32):
+                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)])
@@ -7673,9 +7671,9 @@ def browse(raw, obj, oid_maps=()):
                 self.scrolled["info"] = False
                 self.scrolled["hexdump"] = False
                 self._state_update()
-            return super(TW, self).selectable()
+            return super().selectable()
 
-        def get_display_text(self):
+        def _get_display_text_without_offset(self):
             pp, constructed = self._get_pp()
             style = "constructed" if constructed else ""
             if len(pp.decode_path) == 0:
@@ -7693,6 +7691,11 @@ def browse(raw, obj, oid_maps=()):
                 ))
             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
@@ -7758,14 +7761,14 @@ def browse(raw, obj, oid_maps=()):
                     ("warning", "Saved to: " + dp)
                 )
                 return None
-            return super(TW, self).keypress(size, key)
+            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(PN, self).__init__(value, *args, **kwargs)
+            super().__init__(value, *args, **kwargs)
 
         def load_widget(self):
             return TW(self.state, self)
@@ -7788,10 +7791,10 @@ def browse(raw, obj, oid_maps=()):
     class LabeledPG(urwid.ProgressBar):
         def __init__(self, label, *args, **kwargs):
             self.label = label
-            super(LabeledPG, self).__init__(*args, **kwargs)
+            super().__init__(*args, **kwargs)
 
         def get_text(self):
-            return "%s: %s" % (self.label, super(LabeledPG, self).get_text())
+            return "%s: %s" % (self.label, super().get_text())
 
     WinHexdump = urwid.ListBox([urwid.Text("")])
     WinInfo = urwid.ListBox([urwid.Text("")])