>>> i = Integer(123)
>>> raw = i.encode()
- >>> Integer().decode(raw) == i
+ >>> Integer().decod(raw) == i
True
There are primitive types, holding single values
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``
Decoding
--------
-Decoding is performed using ``decode()`` method. ``offset`` optional
-argument could be used to set initial object's offset in the binary
-data, for convenience. It returns decoded object and remaining
-unmarshalled data (tail). Internally all work is done on
+Decoding is performed using :py:meth:`pyderasn.Obj.decode` method.
+``offset`` optional argument could be used to set initial object's
+offset in the binary data, for convenience. It returns decoded object
+and remaining unmarshalled data (tail). Internally all work is done on
``memoryview(data)``, and you can leave returning tail as a memoryview,
by specifying ``leavemm=True`` argument.
+Also note convenient :py:meth:`pyderasn.Obj.decod` method, that
+immediately checks and raises if there is non-empty tail.
+
When object is decoded, ``decoded`` property is true and you can safely
use following properties:
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:
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()}),),
PrintableString
_______________
.. autoclass:: pyderasn.PrintableString
+ :members: __init__
UTCTime
_______
.. autoclass:: pyderasn.DecodeError
:members: __init__
.. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.ExceedingData
.. autoclass:: pyderasn.LenIndefForm
.. autoclass:: pyderasn.TagMismatch
.. autoclass:: pyderasn.InvalidLength
from os import environ
from string import ascii_letters
from string import digits
+from unicodedata import category as unicat
from six import add_metaclass
from six import binary_type
def colored(what, *args, **kwargs):
return what
-__version__ = "5.5"
+__version__ = "5.6"
__all__ = (
"Any",
"DecodeError",
"DecodePathDefBy",
"Enumerated",
+ "ExceedingData",
"GeneralizedTime",
"GeneralString",
"GraphicString",
pass
+class ExceedingData(ASN1Error):
+ def __init__(self, nbytes):
+ super(ExceedingData, self).__init__()
+ self.nbytes = nbytes
+
+ def __str__(self):
+ return "%d trailing bytes" % self.nbytes
+
+ def __repr__(self):
+ return "%s(%s)" % (self.__class__.__name__, self)
+
+
class LenIndefForm(DecodeError):
pass
)
return 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
+
@property
def expled(self):
"""See :ref:`decoding`
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",
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:
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:
octets,
))
- def _decode_chunk(self, lv, offset, decode_path, ctx):
+ def _decode_chunk(self, lv, offset, decode_path):
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
if t == self.tag:
if tag_only: # pragma: no cover
return None
- return self._decode_chunk(lv, offset, decode_path, ctx)
+ return self._decode_chunk(lv, offset, decode_path)
if t == self.tag_constructed:
if not ctx.get("bered", False):
raise DecodeError(
:param default: set default value. Type same as in ``value``
:param bool optional: is object ``OPTIONAL`` in sequence
"""
- super(OctetString, self).__init__(
- impl,
- expl,
- default,
- optional,
- _decoded,
- )
+ super(OctetString, self).__init__(impl, expl, default, optional, _decoded)
self._value = value
self._bound_min, self._bound_max = getattr(
self,
self._value,
))
- def _decode_chunk(self, lv, offset, decode_path, ctx):
+ def _decode_chunk(self, lv, offset, decode_path):
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
if t == self.tag:
if tag_only:
return None
- return self._decode_chunk(lv, offset, decode_path, ctx)
+ return self._decode_chunk(lv, offset, decode_path)
if t == self.tag_constructed:
if not ctx.get("bered", False):
raise DecodeError(
:param default: set default value. Type same as in ``value``
:param bool optional: is object ``OPTIONAL`` in sequence
"""
- super(ObjectIdentifier, self).__init__(
- impl,
- expl,
- default,
- optional,
- _decoded,
- )
+ super(ObjectIdentifier, self).__init__(impl, expl, default, optional, _decoded)
self._value = value
if value is not None:
self._value = self._value_sanitize(value)
bounds=None, # dummy argument, workability for Integer.decode
):
super(Enumerated, self).__init__(
- value=value,
- impl=impl,
- expl=expl,
- default=default,
- optional=optional,
- _specs=_specs,
- _decoded=_decoded,
+ value, bounds, impl, expl, default, optional, _specs, _decoded,
)
if len(self.specs) == 0:
raise ValueError("schema must be specified")
)
+def escape_control_unicode(c):
+ if unicat(c).startswith("C"):
+ c = repr(c).lstrip("u").strip("'")
+ return c
+
+
class CommonString(OctetString):
"""Common class for all strings
* - :py:class:`pyderasn.BMPString`
- utf-16-be
"""
- __slots__ = ("encoding",)
+ __slots__ = ()
def _value_sanitize(self, value):
value_raw = None
def pps(self, decode_path=(), no_unicode=False):
value = None
if self.ready:
- value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
+ value = (
+ hexenc(bytes(self)) if no_unicode else
+ "".join(escape_control_unicode(c) for c in self.__unicode__())
+ )
yield _pp(
obj=self,
asn1_type_name=self.asn1_type_name,
_allowable_chars = frozenset(
(ascii_letters + digits + " '()+,-./:=?").encode("ascii")
)
+ _asterisk = frozenset("*".encode("ascii"))
+ _ampersand = frozenset("&".encode("ascii"))
+
+ def __init__(
+ self,
+ value=None,
+ bounds=None,
+ impl=None,
+ expl=None,
+ default=None,
+ optional=False,
+ _decoded=(0, 0, 0),
+ allow_asterisk=False,
+ allow_ampersand=False,
+ ):
+ """
+ :param allow_asterisk: allow asterisk character
+ :param allow_ampersand: allow ampersand character
+ """
+ if allow_asterisk:
+ self._allowable_chars |= self._asterisk
+ if allow_ampersand:
+ self._allowable_chars |= self._ampersand
+ super(PrintableString, self).__init__(
+ value, bounds, impl, expl, default, optional, _decoded,
+ )
def _value_sanitize(self, value):
value = super(PrintableString, self)._value_sanitize(value)
raise DecodeError("non-printable value")
return value
+ def copy(self):
+ obj = super(PrintableString, self).copy()
+ obj._allowable_chars = self._allowable_chars
+ return obj
+
+ 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._asterisk <= self._allowable_chars,
+ allow_ampersand=self._ampersand <= self._allowable_chars,
+ )
+
class TeletexString(CommonString):
__slots__ = ()
:param bool optional: is object ``OPTIONAL`` in sequence
"""
super(UTCTime, self).__init__(
- impl=impl,
- expl=expl,
- default=default,
- optional=optional,
- _decoded=_decoded,
+ None, None, impl, expl, default, optional, _decoded,
)
self._value = value
if value is not None:
It could be useful for general decoding of some unspecified values:
- >>> PrimitiveTypes().decode(hexdec("0403666f6f"))[0].value
+ >>> PrimitiveTypes().decod(hexdec("0403666f6f")).value
OCTET STRING 3 bytes 666f6f
- >>> PrimitiveTypes().decode(hexdec("0203123456"))[0].value
+ >>> PrimitiveTypes().decod(hexdec("0203123456")).value
INTEGER 1193046
"""
__slots__ = ()
_decoded=(offset, 0, tlvlen),
)
obj.lenindef = True
- obj.tag = t
+ obj.tag = t.tobytes()
return obj, v[EOC_LEN:]
except DecodeError as err:
raise err.__class__(
optional=self.optional,
_decoded=(offset, 0, tlvlen),
)
- obj.tag = t
+ obj.tag = t.tobytes()
return obj, tail
def __repr__(self):
optional=False,
_decoded=(0, 0, 0),
):
- super(SequenceOf, self).__init__(
- impl,
- expl,
- default,
- optional,
- _decoded,
- )
+ super(SequenceOf, self).__init__(impl, expl, default, optional, _decoded)
if schema is None:
schema = getattr(self, "schema", None)
if schema is None:
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:
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:
print(pprinter(
obj,
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