# 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-2021 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2022 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
* :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:
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
____________
UTCTime
_______
.. autoclass:: pyderasn.UTCTime
- :members: __init__, todatetime
+ :members: __init__, todatetime, totzdatetime
GeneralizedTime
_______________
.. autoclass:: pyderasn.GeneralizedTime
- :members: __init__, todatetime
+ :members: __init__, todatetime, totzdatetime
Special types
-------------
"""
from array import array
-from codecs import getdecoder
-from codecs import getencoder
from collections import namedtuple
from collections import OrderedDict
from copy import copy
def colored(what, *args, **kwargs):
return what
-__version__ = "9.0"
+try:
+ from dateutil.tz import UTC as tzUTC
+except ImportError: # pragma: no cover
+ tzUTC = "missing"
+
+
+__version__ = "9.3"
__all__ = (
"agg_octet_string",
# 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):
tag_default = tag_encode(4)
asn1_type_name = "OCTET STRING"
evgen_mode_skip_value = True
+ memoryview_safe = True
def __init__(
self,
self._assert_ready()
return bytes(self._value)
+ def memoryview(self):
+ self._assert_ready()
+ return memoryview(self._value)
+
def __eq__(self, their):
if their.__class__ == bytes:
return self._value == their
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,
- utf-16-be
"""
__slots__ = ()
+ memoryview_safe = False
def _value_sanitize(self, value):
value_raw = None
return self._value.decode(self.encoding)
return str(self._value)
+ def memoryview(self):
+ raise ValueError("CommonString does not support .memoryview()")
+
def __repr__(self):
return pp_console_row(next(self.pps()))
class AllowableCharsMixin:
+ __slots__ = ()
+
@property
def allowable_chars(self):
return frozenset(chr(c) for c in self._allowable_chars)
return value
+NUMERIC_ALLOWABLE_CHARS = frozenset(digits.encode("ascii") + b" ")
+
+
class NumericString(AllowableCharsMixin, CommonString):
"""Numeric string
>>> 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(
)
+PRINTABLE_ALLOWABLE_CHARS = frozenset(
+ (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+)
+
+
class PrintableString(AllowableCharsMixin, CommonString):
"""Printable string
>>> 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"))
: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,
)
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
>>> 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(
- chr(c).encode("ascii") for c in range(128)
- ))
+
+ def __init__(self, *args, **kwargs):
+ self._allowable_chars = IA5_ALLOWABLE_CHARS
+ super().__init__(*args, **kwargs)
LEN_YYMMDDHHMMSSZ = len("YYMMDDHHMMSSZ")
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
>>> VisibleString().allowable_chars
frozenset([" ", ... "~"])
"""
- __slots__ = ()
+ __slots__ = ("_allowable_chars",)
tag_default = tag_encode(26)
encoding = "ascii"
asn1_type_name = "VisibleString"
- _allowable_chars = frozenset(b"".join(
- chr(c).encode("ascii") for c in range(ord(" "), ord("~") + 1)
- ))
+
+ def __init__(self, *args, **kwargs):
+ self._allowable_chars = VISIBLE_ALLOWABLE_CHARS
+ super().__init__(*args, **kwargs)
class ISO646String(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.
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()))
value = self._value_sanitize(value)
self._value = value
if self._expl is None:
- if value.__class__ == bytes:
+ 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
self.defined = None
def _value_sanitize(self, value):
- if value.__class__ == bytes:
+ 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
self.defined = state.defined
def __eq__(self, their):
- if their.__class__ == bytes:
- if self._value.__class__ == bytes:
+ 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
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):
return 0
def _encode(self):
self._assert_ready()
value = self._value
- if value.__class__ == bytes:
- 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__ == bytes:
+ 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__ == bytes:
+ 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:
+ if value.__class__ == bytes or value.__class__ == memoryview:
write_full(writer, value)
else:
value.encode_cer(writer)
)
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),
value = self._value
if value is None:
pass
- elif value.__class__ == bytes:
+ elif value.__class__ == bytes or value.__class__ == memoryview:
value = None
else:
value = repr(value)
obj_name=self.__class__.__name__,
decode_path=decode_path,
value=value,
- blob=self._value if self._value.__class__ == bytes 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),
)
-class SequenceEncode1stMixing:
+class SequenceEncode1stMixin:
+ __slots__ = ()
+
def _encode1st(self, state):
state.append(0)
idx = len(state) - 1
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::
yield pp
-class Set(Sequence, SequenceEncode1stMixing):
+class Set(Sequence, SequenceEncode1stMixin):
"""``SET`` structure type
Its usage is identical to :py:class:`pyderasn.Sequence`.
)
-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