Pay attention that those values do **not** include anything related to
explicit tag. If you want to know information about it, then use:
-``expled`` (to know if explicit tag is set), ``expl_offset`` (it is
-lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen``
-(that actually equals to ordinary ``tlvlen``).
+
+* ``expled`` -- to know if explicit tag is set
+* ``expl_offset`` (it is lesser than ``offset``)
+* ``expl_tlen``,
+* ``expl_llen``
+* ``expl_vlen`` (that actually equals to ordinary ``tlvlen``)
+* ``fulloffset`` -- it equals to ``expl_offset`` if explicit tag is set,
+ ``offset`` otherwise
+* ``fulllen`` -- it equals to ``expl_len`` if explicit tag is set,
+ ``tlvlen`` otherwise
When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
Currently available context options:
+* :ref:`allow_default_values <allow_default_values_ctx>`
+* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
+* :ref:`allow_unordered_set <allow_unordered_set_ctx>`
* :ref:`bered <bered_ctx>`
* :ref:`defines_by_path <defines_by_path_ctx>`
-* :ref:`strict_default_existence <strict_default_existence_ctx>`
.. _pprinting:
``defines`` kwarg is a sequence. You can specify defined field
relatively or absolutely to current decode path. For example ``defines``
for AlgorithmIdentifier of X.509's
-``tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm``::
+``tbsCertificate:subjectPublicKeyInfo:algorithm:algorithm``::
(
(("parameters",), {
When any of those fields is automatically decoded, then ``.defined``
attribute contains ``(OID, value)`` tuple. ``OID`` tells by which OID it
was defined, ``value`` contains corresponding decoded value. For example
-above, ``content_info["content"].defined == (id_signedData,
-signed_data)``.
+above, ``content_info["content"].defined == (id_signedData, signed_data)``.
.. _defines_by_path_ctx:
: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 ``bered``
+* 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`` can contain it.
+ STRING``, ``SEQUENCE``, ``SET``, ``SET OF`` 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
contain it.
* If object has an indefinite length encoded explicit tag, then
``expl_lenindef`` is set to True.
+* If object has either any of BER-related encoding (explicit tag
+ indefinite length, object's indefinite length, BER-encoding) or any
+ underlying component has that kind of encoding, then ``bered``
+ attribute is set to True. For example SignedData CMS can have
+ ``ContentInfo:content:signerInfos:*`` ``bered`` value set to True, but
+ ``ContentInfo:content:signerInfos:*:signedAttrs`` won't.
EOC (end-of-contents) token's length is taken in advance in object's
value length.
+.. _allow_expl_oob_ctx:
+
+Allow explicit tag out-of-bound
+-------------------------------
+
+Invalid BER encoding could contain ``EXPLICIT`` tag containing more than
+one value, more than one object. If you set ``allow_expl_oob`` context
+option to True, then no error will be raised and that invalid encoding
+will be silently further processed. But pay attention that offsets and
+lengths will be invalid in that case.
+
+.. warning::
+
+ This option should be used only for skipping some decode errors, just
+ to see the decoded structure somehow.
+
Primitive types
---------------
c for c in (
"" if self.klass is None else self.klass.__name__,
(
- ("(%s)" % ".".join(str(dp) for dp in self.decode_path))
+ ("(%s)" % ":".join(str(dp) for dp in self.decode_path))
if len(self.decode_path) > 0 else ""
),
("(at %d)" % self.offset) if self.offset > 0 else "",
"vlen",
"expl_lenindef",
"lenindef",
- "bered",
+ "ber_encoded",
)
def __init__(
self.default = None
self.expl_lenindef = False
self.lenindef = False
- self.bered = False
+ self.ber_encoded = False
@property
def ready(self): # pragma: no cover
"""
raise NotImplementedError()
+ @property
+ def bered(self):
+ """Is either object or any elements inside is BER encoded?
+ """
+ return self.expl_lenindef or self.lenindef or self.ber_encoded
+
def _assert_ready(self):
if not self.ready:
raise ObjNotReady(self.__class__.__name__)
if tag_only:
return
obj, tail = result
+ if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
+ raise DecodeError(
+ "explicit tag out-of-bound, longer than data",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
return obj, (tail if leavemm else tail.tobytes())
@property
def expl_tlvlen(self):
return self.expl_tlen + self.expl_llen + self.expl_vlen
+ @property
+ def fulloffset(self):
+ return self.expl_offset if self.expled else self.offset
+
+ @property
+ def fulllen(self):
+ return self.expl_tlvlen if self.expled else self.tlvlen
+
def pps_lenindef(self, decode_path):
if self.lenindef:
yield _pp(
tlen=1,
llen=1,
vlen=0,
+ ber_encoded=True,
bered=True,
)
if self.expl_lenindef:
tlen=1,
llen=1,
vlen=0,
+ ber_encoded=True,
bered=True,
)
"expl_vlen",
"expl_lenindef",
"lenindef",
+ "ber_encoded",
"bered",
))
expl_vlen=None,
expl_lenindef=False,
lenindef=False,
+ ber_encoded=False,
bered=False,
):
return PP(
expl_vlen,
expl_lenindef,
lenindef,
+ ber_encoded,
bered,
)
-def _colorize(what, colour, with_colours, attrs=("bold",)):
+def _colourize(what, colour, with_colours, attrs=("bold",)):
return colored(what, colour, attrs=attrs) if with_colours else what
with_offsets=False,
with_blob=True,
with_colours=False,
+ with_decode_path=False,
+ decode_path_len_decrease=0,
):
cols = []
if with_offsets:
),
LENINDEF_PP_CHAR if pp.expl_lenindef else " ",
)
- cols.append(_colorize(col, "red", with_colours, ()))
+ col = _colourize(col, "red", with_colours, ())
+ col += _colourize("B", "red", with_colours) if pp.bered else " "
+ cols.append(col)
col = "[%d,%d,%4d]%s" % (
pp.tlen,
pp.llen,
pp.vlen,
LENINDEF_PP_CHAR if pp.lenindef else " "
)
- col = _colorize(col, "green", with_colours, ())
+ col = _colourize(col, "green", with_colours, ())
cols.append(col)
- if len(pp.decode_path) > 0:
- cols.append(" ." * (len(pp.decode_path)))
+ decode_path_len = len(pp.decode_path) - decode_path_len_decrease
+ if decode_path_len > 0:
+ cols.append(" ." * decode_path_len)
ent = pp.decode_path[-1]
if isinstance(ent, DecodePathDefBy):
- cols.append(_colorize("DEFINED BY", "red", with_colours, ("reverse",)))
+ cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
value = str(ent.defined_by)
if (
oids is not None and
ObjectIdentifier.asn1_type_name and
value in oids
):
- cols.append(_colorize("%s:" % oids[value], "green", with_colours))
+ cols.append(_colourize("%s:" % oids[value], "green", with_colours))
else:
- cols.append(_colorize("%s:" % value, "white", with_colours, ("reverse",)))
+ cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
else:
- cols.append(_colorize("%s:" % ent, "yellow", with_colours, ("reverse",)))
+ cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
if pp.expl is not None:
klass, _, num = pp.expl
col = "[%s%d] EXPLICIT" % (TagClassReprs[klass], num)
- cols.append(_colorize(col, "blue", with_colours))
+ cols.append(_colourize(col, "blue", with_colours))
if pp.impl is not None:
klass, _, num = pp.impl
col = "[%s%d]" % (TagClassReprs[klass], num)
- cols.append(_colorize(col, "blue", with_colours))
+ cols.append(_colourize(col, "blue", with_colours))
if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
- cols.append(_colorize(pp.obj_name, "magenta", with_colours))
- if pp.bered:
- cols.append(_colorize("BER", "red", with_colours))
- cols.append(_colorize(pp.asn1_type_name, "cyan", with_colours))
+ cols.append(_colourize(pp.obj_name, "magenta", with_colours))
+ if pp.ber_encoded:
+ cols.append(_colourize("BER", "red", with_colours))
+ cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours))
if pp.value is not None:
value = pp.value
- cols.append(_colorize(value, "white", with_colours, ("reverse",)))
+ cols.append(_colourize(value, "white", with_colours, ("reverse",)))
if (
oids is not None and
pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
value in oids
):
- cols.append(_colorize("(%s)" % oids[value], "green", with_colours))
+ cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
if with_blob:
if isinstance(pp.blob, binary_type):
cols.append(hexenc(pp.blob))
elif isinstance(pp.blob, tuple):
cols.append(", ".join(pp.blob))
if pp.optional:
- cols.append(_colorize("OPTIONAL", "red", with_colours))
+ cols.append(_colourize("OPTIONAL", "red", with_colours))
if pp.default:
- cols.append(_colorize("DEFAULT", "red", with_colours))
+ cols.append(_colourize("DEFAULT", "red", with_colours))
+ if with_decode_path:
+ cols.append(_colourize(
+ "[%s]" % ":".join(str(p) for p in pp.decode_path),
+ "grey",
+ with_colours,
+ ))
return " ".join(cols)
-def pp_console_blob(pp):
- cols = [" " * len("XXXXXYYZ [X,X,XXXX]Z")]
- if len(pp.decode_path) > 0:
- cols.append(" ." * (len(pp.decode_path) + 1))
+def pp_console_blob(pp, decode_path_len_decrease=0):
+ cols = [" " * len("XXXXXYYZZ [X,X,XXXX]Z")]
+ 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):
blob = hexenc(pp.blob).upper()
for i in range(0, len(blob), 32):
yield " ".join(cols + [", ".join(pp.blob)])
-def pprint(obj, oids=None, big_blobs=False, with_colours=False):
+def pprint(
+ obj,
+ oids=None,
+ big_blobs=False,
+ with_colours=False,
+ with_decode_path=False,
+ decode_path_only=(),
+):
"""Pretty print object
:param Obj obj: object you want to pretty print
lines
:param with_colours: colourize output, if ``termcolor`` library
is available
+ :param with_decode_path: print decode path
+ :param decode_path_only: print only that specified decode path
"""
def _pprint_pps(pps):
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
+ ):
+ continue
if big_blobs:
yield pp_console_row(
pp,
with_offsets=True,
with_blob=False,
with_colours=with_colours,
+ with_decode_path=with_decode_path,
+ decode_path_len_decrease=len(decode_path_only),
)
- for row in pp_console_blob(pp):
+ for row in pp_console_blob(
+ pp,
+ decode_path_len_decrease=len(decode_path_only),
+ ):
yield row
else:
yield pp_console_row(
with_offsets=True,
with_blob=True,
with_colours=with_colours,
+ with_decode_path=with_decode_path,
+ decode_path_len_decrease=len(decode_path_only),
)
else:
for row in _pprint_pps(pp):
offset=offset,
)
first_octet = byte2int(v)
- bered = False
+ ber_encoded = False
if first_octet == 0:
value = False
elif first_octet == 0xFF:
value = True
elif ctx.get("bered", False):
value = True
- bered = True
+ ber_encoded = True
else:
raise DecodeError(
"unacceptable Boolean value",
optional=self.optional,
_decoded=(offset, 1, 1),
)
- obj.bered = bered
+ obj.ber_encoded = ber_encoded
return obj, v[1:]
def __repr__(self):
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
+ ber_encoded=self.ber_encoded,
bered=self.bered,
)
for pp in self.pps_lenindef(decode_path):
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,
+ bered=self.bered,
)
for pp in self.pps_lenindef(decode_path):
yield pp
_decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
obj.lenindef = lenindef
- obj.bered = True
+ obj.ber_encoded = True
return obj, (v[EOC_LEN:] if lenindef else v)
raise TagMismatch(
klass=self.__class__,
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)
offset=offset,
)
obj.lenindef = lenindef
- obj.bered = True
+ obj.ber_encoded = True
return obj, (v[EOC_LEN:] if lenindef else v)
raise TagMismatch(
klass=self.__class__,
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)
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,
+ bered=self.bered,
)
for pp in self.pps_lenindef(decode_path):
yield pp
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,
+ bered=self.bered,
)
for pp in self.pps_lenindef(decode_path):
yield pp
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
for pp in self.pps_lenindef(decode_path):
yield pp
if isinstance(value, datetime):
return value.strftime(self.fmt).encode("ascii")
if isinstance(value, binary_type):
- value_decoded = value.decode("ascii")
+ try:
+ value_decoded = value.decode("ascii")
+ except (UnicodeEncodeError, UnicodeDecodeError) as err:
+ raise DecodeError("invalid UTCTime encoding")
if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
try:
datetime.strptime(value_decoded, self.fmt)
- except ValueError:
+ except (TypeError, ValueError):
raise DecodeError("invalid UTCTime format")
return value
else:
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
for pp in self.pps_lenindef(decode_path):
yield pp
self.fmt_ms if value.microsecond > 0 else self.fmt
).encode("ascii")
if isinstance(value, binary_type):
- value_decoded = value.decode("ascii")
+ try:
+ value_decoded = value.decode("ascii")
+ except (UnicodeEncodeError, UnicodeDecodeError) as err:
+ raise DecodeError("invalid GeneralizedTime encoding")
if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ:
try:
datetime.strptime(value_decoded, self.fmt)
- except ValueError:
+ except (TypeError, ValueError):
raise DecodeError(
"invalid GeneralizedTime (without ms) format",
)
elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
try:
datetime.strptime(value_decoded, self.fmt_ms)
- except ValueError:
+ except (TypeError, ValueError):
raise DecodeError(
"invalid GeneralizedTime (with ms) format",
)
def ready(self):
return self._value is not None and self._value[1].ready
+ @property
+ def bered(self):
+ return self.expl_lenindef or (
+ (self._value is not None) and
+ self._value[1].bered
+ )
+
def copy(self):
obj = self.__class__(schema=self.specs)
obj._expl = self._expl
expl=self._expl,
default=self.default,
optional=self.optional,
- _decoded=(offset, 0, value.tlvlen),
+ _decoded=(offset, 0, value.fulllen),
)
obj._value = (choice, value)
return obj, tail
llen=self.llen,
vlen=self.vlen,
expl_lenindef=self.expl_lenindef,
+ bered=self.bered,
)
if self.ready:
yield self.value.pps(decode_path=decode_path + (self.choice,))
def ready(self):
return self._value is not None
+ @property
+ def bered(self):
+ if self.expl_lenindef or self.lenindef:
+ return True
+ if self.defined is None:
+ return False
+ return self.defined[1].bered
+
def copy(self):
obj = self.__class__()
obj._value = self._value
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
+ bered=self.bered,
)
defined_by, defined = self.defined or (None, None)
if defined_by is not None:
All defaulted values are always optional.
- .. _strict_default_existence_ctx:
+ .. _allow_default_values_ctx:
- .. warning::
-
- When decoded DER contains defaulted value inside, then
- technically this is not valid DER encoding. But we allow and pass
- it **by default**. Of course reencoding of that kind of DER will
- result in different binary representation (validly without
- defaulted value inside). You can enable strict defaulted values
- existence validation by setting ``"strict_default_existence":
- True`` :ref:`context <ctx>` option -- decoding process will raise
- an exception if defaulted value is met.
+ DER prohibits default value encoding and will raise an error if
+ default value is unexpectedly met during decode.
+ If :ref:`bered <bered_ctx>` context option is set, then no error
+ will be raised, but ``bered`` attribute set. You can disable strict
+ defaulted values existence validation by setting
+ ``"allow_default_values": True`` :ref:`context <ctx>` option.
Two sequences are equal if they have equal specification (schema),
implicit/explicit tagging and the same values.
return False
return True
+ @property
+ def bered(self):
+ if self.expl_lenindef or self.lenindef or self.ber_encoded:
+ return True
+ return any(value.bered for value in self._value.values())
+
def copy(self):
obj = self.__class__(schema=self.specs)
obj.tag = self.tag
if tag_only:
return
lenindef = False
+ ctx_bered = ctx.get("bered", False)
try:
l, llen, v = len_decode(lv)
except LenIndefForm as err:
- if not ctx.get("bered", False):
+ if not ctx_bered:
raise err.__class__(
msg=err.msg,
klass=self.__class__,
vlen = 0
sub_offset = offset + tlen + llen
values = {}
+ ber_encoded = False
+ ctx_allow_default_values = ctx.get("allow_default_values", False)
for name, spec in self.specs.items():
if spec.optional and (
(lenindef and v[:EOC_LEN].tobytes() == EOC) or
continue
raise
- defined = get_def_by_path(ctx.get("defines", ()), sub_decode_path)
+ defined = get_def_by_path(ctx.get("_defines", ()), sub_decode_path)
if defined is not None:
defined_by, defined_spec = defined
if issubclass(value.__class__, SequenceOf):
)
value.defined = (defined_by, defined_value)
- value_len = value.expl_tlvlen if value.expled else value.tlvlen
+ value_len = value.fulllen
vlen += value_len
sub_offset += value_len
v = v_tail
if spec.default is not None and value == spec.default:
- if ctx.get("strict_default_existence", False):
+ 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,
)
- else:
- continue
values[name] = value
spec_defines = getattr(spec, "defines", ())
for rel_path, schema in spec_defines:
defined = schema.get(value, None)
if defined is not None:
- ctx.setdefault("defines", []).append((
+ ctx.setdefault("_defines", []).append((
abs_decode_path(sub_decode_path[:-1], rel_path),
(value, defined),
))
)
obj._value = values
obj.lenindef = lenindef
+ obj.ber_encoded = ber_encoded
return obj, tail
def __repr__(self):
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,
)
for name in self.specs:
value = self._value.get(name)
"""``SET`` structure type
Its usage is identical to :py:class:`pyderasn.Sequence`.
+
+ .. _allow_unordered_set_ctx:
+
+ DER prohibits unordered values encoding and will raise an error
+ during decode. If If :ref:`bered <bered_ctx>` context option is set,
+ then no error will occure. Also you can disable strict values
+ ordering check by setting ``"allow_unordered_set": True``
+ :ref:`context <ctx>` option.
"""
__slots__ = ()
tag_default = tag_encode(form=TagFormConstructed, num=17)
if tag_only:
return
lenindef = False
+ ctx_bered = ctx.get("bered", False)
try:
l, llen, v = len_decode(lv)
except LenIndefForm as err:
- if not ctx.get("bered", False):
+ if not ctx_bered:
raise err.__class__(
msg=err.msg,
klass=self.__class__,
vlen = 0
sub_offset = offset + tlen + llen
values = {}
+ ber_encoded = False
+ ctx_allow_default_values = ctx.get("allow_default_values", False)
+ ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+ value_prev = memoryview(v[:0])
specs_items = self.specs.items
while len(v) > 0:
if lenindef and v[:EOC_LEN].tobytes() == EOC:
decode_path=sub_decode_path,
ctx=ctx,
)
- value_len = value.expl_tlvlen if value.expled else value.tlvlen
+ value_len = value.fulllen
+ if value_prev.tobytes() > v[:value_len].tobytes():
+ if ctx_bered or ctx_allow_unordered_set:
+ ber_encoded = True
+ else:
+ raise DecodeError(
+ "unordered " + self.asn1_type_name,
+ klass=self.__class__,
+ 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,
+ )
+ values[name] = value
+ value_prev = v[:value_len]
sub_offset += value_len
vlen += value_len
v = v_tail
- if spec.default is None or value != spec.default: # pragma: no cover
- # SeqMixing.test_encoded_default_accepted covers that place
- values[name] = value
obj = self.__class__(
schema=self.specs,
impl=self.tag,
optional=self.optional,
_decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
- obj._value = values
if lenindef:
if v[:EOC_LEN].tobytes() != EOC:
raise DecodeError(
)
tail = v[EOC_LEN:]
obj.lenindef = True
+ obj._value = values
if not obj.ready:
raise DecodeError(
"not all values are ready",
decode_path=decode_path,
offset=offset,
)
+ obj.ber_encoded = ber_encoded
return obj, tail
def ready(self):
return all(v.ready for v in self._value)
+ @property
+ def bered(self):
+ if self.expl_lenindef or self.lenindef or self.ber_encoded:
+ 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
v = b"".join(self._encoded_values())
return b"".join((self.tag, len_encode(len(v)), v))
- def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
try:
t, tlen, lv = tag_strip(tlv)
except DecodeError as err:
if tag_only:
return
lenindef = False
+ ctx_bered = ctx.get("bered", False)
try:
l, llen, v = len_decode(lv)
except LenIndefForm as err:
- if not ctx.get("bered", False):
+ if not ctx_bered:
raise err.__class__(
msg=err.msg,
klass=self.__class__,
vlen = 0
sub_offset = offset + tlen + llen
_value = []
+ ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+ value_prev = memoryview(v[:0])
+ ber_encoded = False
spec = self.spec
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=decode_path + (str(len(_value)),),
+ decode_path=sub_decode_path,
ctx=ctx,
)
- value_len = value.expl_tlvlen if value.expled else value.tlvlen
+ value_len = value.fulllen
+ if ordering_check:
+ if value_prev.tobytes() > v[:value_len].tobytes():
+ if ctx_bered or ctx_allow_unordered_set:
+ ber_encoded = True
+ else:
+ raise DecodeError(
+ "unordered " + self.asn1_type_name,
+ klass=self.__class__,
+ decode_path=sub_decode_path,
+ offset=sub_offset,
+ )
+ value_prev = v[:value_len]
+ _value.append(value)
sub_offset += value_len
vlen += value_len
v = v_tail
- _value.append(value)
- obj = self.__class__(
- value=_value,
- schema=spec,
- 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)),
- )
+ try:
+ obj = self.__class__(
+ value=_value,
+ schema=spec,
+ 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)),
+ )
+ except BoundsError as err:
+ raise DecodeError(
+ msg=str(err),
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
if lenindef:
if v[:EOC_LEN].tobytes() != EOC:
raise DecodeError(
)
obj.lenindef = True
tail = v[EOC_LEN:]
+ obj.ber_encoded = ber_encoded
return obj, tail
def __repr__(self):
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,
)
for i, value in enumerate(self._value):
yield value.pps(decode_path=decode_path + (str(i),))
v = b"".join(raws)
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(
+ tlv,
+ offset,
+ decode_path,
+ ctx,
+ tag_only,
+ ordering_check=True,
+ )
+
def obj_by_path(pypath): # pragma: no cover
"""Import object specified as string Python path
__slots__ = ()
schema = choice
- def pprint_any(obj, oids=None, with_colours=False):
+ def pprint_any(
+ obj,
+ oids=None,
+ with_colours=False,
+ with_decode_path=False,
+ decode_path_only=(),
+ ):
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
+ ):
+ continue
if pp.asn1_type_name == Choice.asn1_type_name:
continue
pp_kwargs = pp._asdict()
with_offsets=True,
with_blob=False,
with_colours=with_colours,
+ with_decode_path=with_decode_path,
+ decode_path_len_decrease=len(decode_path_only),
)
- for row in pp_console_blob(pp):
+ for row in pp_console_blob(
+ pp,
+ decode_path_len_decrease=len(decode_path_only),
+ ):
yield row
else:
for row in _pprint_pps(pp):
)
parser.add_argument(
"--nobered",
- action='store_true',
+ action="store_true",
help="Disallow BER encoding",
)
+ parser.add_argument(
+ "--print-decode-path",
+ action="store_true",
+ help="Print decode paths",
+ )
+ parser.add_argument(
+ "--decode-path-only",
+ help="Print only specified decode path",
+ )
+ parser.add_argument(
+ "--allow-expl-oob",
+ action="store_true",
+ help="Allow explicit tag out-of-bound",
+ )
parser.add_argument(
"DERFile",
type=argparse.FileType("rb"),
pprinter = partial(pprint, big_blobs=True)
else:
schema, pprinter = generic_decoder()
- ctx = {"bered": not args.nobered}
+ ctx = {
+ "bered": not args.nobered,
+ "allow_expl_oob": args.allow_expl_oob,
+ }
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)
obj,
oids=oids,
with_colours=True if environ.get("NO_COLOR") is None else False,
+ 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 tail != b"":
print("\nTrailing data: %s" % hexenc(tail))