diff --git a/.config/codespell_ignore.txt b/.config/codespell_ignore.txt index e5d65af4708..98e33583f15 100644 --- a/.config/codespell_ignore.txt +++ b/.config/codespell_ignore.txt @@ -54,3 +54,8 @@ wan wanna webp widgits +uper +UPER +uPER +acn +ACN diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 530ce185bd9..418dde6e42f 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -126,19 +126,17 @@ def register_stem(cls, stem): # type: (Type[BERcodec_Object[Any]]) -> None cls._stem = stem - def register_tagging(cls, enc, dec): - # type: (Any, Any) -> None - # Codec-level implicit/explicit tagging (BER/OER) or identity (UPER/PER). - cls._tagging_enc = enc - cls._tagging_dec = dec - - def tagging_enc(cls, s, **kwargs): - # type: (bytes, **Any) -> bytes - return cls._tagging_enc(s, **kwargs) # type: ignore - - def tagging_dec(cls, s, **kwargs): - # type: (bytes, **Any) -> Tuple[Optional[int], bytes] - return cls._tagging_dec(s, **kwargs) # type: ignore + def register_hooks(cls, **hooks): + # type: (**Any) -> None + # Field operations a codec does its own way: the tagging of a field + # (BER) and the compound fields (SEQUENCE/CHOICE/… in OER and PER). + ASN1_Codecs.hooks.setdefault(cls, {}).update(hooks) + + def hook(cls, name): + # type: (str) -> Any + # Hooks are optional and may be partial: missing entries mean that + # asn1fields keeps its default implementation. + return ASN1_Codecs.hooks.get(cls, {}).get(name) def dec(cls, s, context=None, _depth=0): # type: (bytes, Optional[Type[ASN1_Class]], int) -> ASN1_Object[Any] @@ -168,6 +166,9 @@ class ASN1_Codecs(metaclass=ASN1_Codecs_metaclass): SER = cast(ASN1Codec, 8) XER = cast(ASN1Codec, 9) + # The field hooks of every codec, by codec then by field operation. + hooks = {} # type: Dict[ASN1Codec, Dict[str, Any]] + class ASN1Tag(EnumElement): def __init__(self, @@ -315,6 +316,9 @@ def __new__(cls, class ASN1_Object(Generic[_K], metaclass=ASN1_Object_metaclass): tag = ASN1_Class_UNIVERSAL.ANY + # Alternative a value was dissected as, when it comes from a CHOICE that + # holds several of its type. See ASN1F_CHOICE.record_alternative. + asn1_choice_index = None # type: Optional[int] def __init__(self, val): # type: (_K) -> None diff --git a/scapy/asn1/ber.py b/scapy/asn1/ber.py index c3da0f15b5d..bc0e4ba817a 100644 --- a/scapy/asn1/ber.py +++ b/scapy/asn1/ber.py @@ -424,7 +424,11 @@ def enc(cls, s, size_len=0, **_kwargs): ASN1_Codecs.BER.register_stem(BERcodec_Object) -ASN1_Codecs.BER.register_tagging(BER_tagging_enc, BER_tagging_dec) +# BER is the one codec that puts the tag of a field on the wire. +ASN1_Codecs.BER.register_hooks( + tagging_enc=BER_tagging_enc, + tagging_dec=BER_tagging_dec, +) ########################## diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 0d92161153e..a7fa2165d02 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -70,6 +70,13 @@ class ASN1F_element(object): pass +def _field_hook(pkt, name): + # type: (Any, str) -> Any + # Contrib codecs (OER/UPER/…) may override compound field operations. + # Returns None when the codec keeps the default BER behaviour. + return pkt.ASN1_codec.hook(name) + + ########################## # Basic ASN1 Field # ########################## @@ -92,6 +99,7 @@ def __init__(self, explicit_tag=None, # type: Optional[int] flexible_tag=False, # type: Optional[bool] size_len=None, # type: Optional[int] + **codec_opts # type: Any ): # type: (...) -> None if context is not None: @@ -104,6 +112,9 @@ def __init__(self, else: self.default = self.ASN1_tag.asn1_object(default) # type: ignore self.size_len = size_len + # Contrib codecs (OER/UPER/…) pass constraints here, e.g. + # oer_unsigned=, uper_min=/uper_max=, uper_extensible=. + self.codec_opts = codec_opts # type: Dict[str, Any] self.flexible_tag = flexible_tag if (implicit_tag is not None) and (explicit_tag is not None): err_msg = "field cannot be both implicitly and explicitly tagged" @@ -129,13 +140,19 @@ def _apply_diff_tag(self, diff_tag): def _tagging_dec(self, pkt, s, **kwargs): # type: (ASN1_Packet, bytes, **Any) -> Tuple[Optional[int], bytes] - # Codec provides tagging_*; OER implements real tags, UPER/PER use - # identity helpers (no BER-style tagging). - return pkt.ASN1_codec.tagging_dec(s, **kwargs) # type: ignore + # Only BER puts the tag of a field on the wire: a codec that does not + # hook the tagging leaves the encoding alone. + hook = _field_hook(pkt, "tagging_dec") + if hook is None: + return None, s + return cast(Tuple[Optional[int], bytes], hook(s, **kwargs)) def _tagging_enc(self, pkt, s, **kwargs): # type: (ASN1_Packet, bytes, **Any) -> bytes - return pkt.ASN1_codec.tagging_enc(s, **kwargs) # type: ignore + hook = _field_hook(pkt, "tagging_enc") + if hook is None: + return s + return cast(bytes, hook(s, **kwargs)) def _apply_tagging_dec(self, s, pkt, hidden_tag=None, **kwargs): # type: (bytes, ASN1_Packet, Optional[Any], **Any) -> bytes @@ -156,15 +173,19 @@ def _apply_tagging_dec(self, s, pkt, hidden_tag=None, **kwargs): def _codec_kwargs(self, pkt): # type: (ASN1_Packet) -> Dict[str, Any] - # OER/UPER need extra constraints (oer_unsigned, uper_min/max, …) on - # every enc/dec call; override this instead of hardcoding BER size_len. - return {"size_len": self.size_len} + # BER ignores unknown keys via **_kwargs. Contrib codecs read + # constraints from field.codec_opts. + kwargs = {"size_len": self.size_len} # type: Dict[str, Any] + kwargs.update(self.codec_opts) + return kwargs def _use_object_enc(self, pkt, item): # type: (ASN1_Packet, ASN1_Object[Any]) -> bool - # BER/LDAP: item.enc() when size_len is unset. UPER must override to - # False so constrained integers go through codec.enc(**kwargs). - return self.size_len is None + # Contrib codecs may force codec.enc(**kwargs) via field hooks. + hook = _field_hook(pkt, "use_object_enc") + if hook is not None: + return cast(bool, hook(self, pkt, item)) + return self.size_len is None and not self.codec_opts def _encode_item(self, pkt, item): # type: (ASN1_Packet, Any) -> bytes @@ -326,12 +347,14 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None super(ASN1F_enum_INTEGER, self).__init__( name, default, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts ) i2s = self.i2s = {} # type: Dict[int, str] s2i = self.s2i = {} # type: Dict[str, int] @@ -378,12 +401,14 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] + **codec_opts # type: Any ): # type: (...) -> None super(ASN1F_BIT_STRING, self).__init__( name, None, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts, ) if isinstance(default, (bytes, str)): self.default = ASN1_BIT_STRING(default, @@ -499,6 +524,11 @@ def __init__(self, *seq, **kwargs): name, default, **kwargs ) self.seq = seq + # Codecs that describe presence out of band (OER/PER preambles) need + # the optional components in declaration order. + self.optionals = tuple( + f for f in seq if isinstance(f, ASN1F_optional) + ) # type: Tuple[ASN1F_optional, ...] self.islist = len(seq) > 1 def __repr__(self): @@ -514,6 +544,19 @@ def get_fields_list(self): return reduce(lambda x, y: x + y.get_fields_list(), self.seq, []) + def _dissect_sequence_children(self, pkt, s): + # type: (Any, bytes) -> bytes + if len(s) == 0: + for obj in self.seq: + obj.set_val(pkt, None) + return s + for obj in self.seq: + try: + s = obj.dissect(pkt, s) + except ASN1F_badsequence: + break + return s + def m2i(self, pkt, s): # type: (Any, bytes) -> Tuple[Any, bytes] """ @@ -524,23 +567,18 @@ def m2i(self, pkt, s): Thus m2i returns an empty list (along with the proper remainder). It is discarded by dissect() and should not be missed elsewhere. """ + hook = _field_hook(pkt, "sequence_m2i") + if hook is not None: + return cast(Tuple[Any, bytes], hook(self, pkt, s)) s = self._apply_tagging_dec(s, pkt, _fname=pkt.name) codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) i, s, remain = codec.check_type_check_len(s) - if len(s) == 0: - for obj in self.seq: - obj.set_val(pkt, None) - else: - for obj in self.seq: - try: - s = obj.dissect(pkt, s) - except ASN1F_badsequence: - break - if len(s) > 0: - raise BER_Decoding_Error( - "unexpected remainder in %s" % pkt.name, - remaining=s, - ) + s = self._dissect_sequence_children(pkt, s) + if len(s) > 0: + raise BER_Decoding_Error( + "unexpected remainder in %s" % pkt.name, + remaining=s, + ) return [], remain def dissect(self, pkt, s): @@ -550,6 +588,9 @@ def dissect(self, pkt, s): def build(self, pkt): # type: (ASN1_Packet) -> bytes + hook = _field_hook(pkt, "sequence_build") + if hook is not None: + return cast(bytes, hook(self, pkt)) s = reduce(lambda x, y: x + y.build(pkt), self.seq, b"") return super(ASN1F_SEQUENCE, self).i2m(pkt, s) @@ -582,6 +623,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None if isinstance(cls, type) and issubclass(cls, ASN1F_field) or \ @@ -601,7 +643,8 @@ def __init__(self, raise ValueError("cls should be an ASN1_Packet or ASN1_field") super(ASN1F_SEQUENCE_OF, self).__init__( name, None, context=context, - implicit_tag=implicit_tag, explicit_tag=explicit_tag + implicit_tag=implicit_tag, explicit_tag=explicit_tag, + **codec_opts, ) self.default = default @@ -616,6 +659,9 @@ def m2i(self, s, # type: bytes ): # type: (...) -> Tuple[List[Any], bytes] + hook = _field_hook(pkt, "sequence_of_m2i") + if hook is not None: + return cast(Tuple[List[Any], bytes], hook(self, pkt, s)) s = self._apply_tagging_dec(s, pkt) codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) i, s, remain = codec.check_type_check_len(s) @@ -633,6 +679,9 @@ def m2i(self, def build(self, pkt): # type: (ASN1_Packet) -> bytes + hook = _field_hook(pkt, "sequence_of_build") + if hook is not None: + return cast(bytes, hook(self, pkt)) val = getattr(pkt, self.name) if isinstance(val, ASN1_Object) and \ val.tag == ASN1_Class_UNIVERSAL.RAW: @@ -712,12 +761,22 @@ def dissect(self, pkt, s): try: return self._field.dissect(pkt, s) except (ASN1_Error, ASN1F_badsequence, ASN1_Decoding_Error): - self._field.set_val(pkt, None) + self.set_absent(pkt) return s + def set_absent(self, pkt): + # type: (ASN1_Packet) -> None + """Called when the encoding does not carry the component.""" + self._field.set_val(pkt, None) + + def is_empty(self, pkt): + # type: (ASN1_Packet) -> bool + return self._field.is_empty(pkt) + def build(self, pkt): # type: (ASN1_Packet) -> bytes - if self._field.is_empty(pkt): + # Through self, so that a DEFAULT component omits its default value. + if self.is_empty(pkt): return b"" return self._field.build(pkt) @@ -730,6 +789,37 @@ def i2repr(self, pkt, x): return self._field.i2repr(pkt, x) +class ASN1F_DEFAULT(ASN1F_optional): + """ + ASN.1 field holding a DEFAULT value: it is omitted from the encoding while + it holds that value, and restored when the encoding does not carry it. + + As with OPTIONAL components, a BER encoding only tells the component apart + from the one that follows it by its tag, so the schema must give it a + distinct one. OER and PER describe presence in the preamble instead. + """ + def __init__(self, field, default): + # type: (ASN1F_field[Any, Any], Any) -> None + super(ASN1F_DEFAULT, self).__init__(field) + self._default = default + + def is_empty(self, pkt): + # type: (ASN1_Packet) -> bool + val = getattr(pkt, self._field.name, None) + if val is None: + return True + if isinstance(val, ASN1_Object): + val = val.val + default = self._default + if isinstance(default, ASN1_Object): + default = default.val + return bool(val == default) + + def set_absent(self, pkt): + # type: (ASN1_Packet) -> None + self._field.set_val(pkt, self._default) + + class ASN1F_omit(ASN1F_field[None, None]): """ ASN.1 field that is not specified. This is simply omitted on the network. @@ -751,7 +841,8 @@ class ASN1F_CHOICE(ASN1F_field[_CHOICE_T, ASN1_Object[Any]]): """ Multiple types are allowed: ASN1_Packet, ASN1F_field and ASN1F_PACKET(), See layers/x509.py for examples. - Other ASN1F_field instances than ASN1F_PACKET instances must not be used. + An ASN1F_field instance is allowed as well, to give an alternative the + constraints that OER and PER encode it with. """ holds_packets = 1 ASN1_tag = ASN1_Class_UNIVERSAL.ANY @@ -762,15 +853,22 @@ def __init__(self, name, default, *args, **kwargs): err_msg = "ASN1F_CHOICE has been called with an implicit_tag" raise ASN1_Error(err_msg) self.implicit_tag = None - for kwarg in ["context", "explicit_tag"]: - setattr(self, kwarg, kwargs.get(kwarg)) + context = kwargs.pop("context", None) + explicit_tag = kwargs.pop("explicit_tag", None) + # Remaining kwargs are codec constraints (e.g. uper_extensible=). super(ASN1F_CHOICE, self).__init__( - name, None, context=self.context, - explicit_tag=self.explicit_tag + name, None, context=context, + explicit_tag=explicit_tag, + **kwargs ) self.default = default self.current_choice = None + # BER looks an alternative up by its tag, while OER and PER encode + # its position. Alternatives may therefore share a tag, so keep them + # in declaration order next to the tag lookup. self.choices = {} # type: Dict[int, _CHOICE_T] + self.choice_order = [] # type: List[int] + self.choice_list = [] # type: List[_CHOICE_T] self.pktchoices = {} for p in args: if hasattr(p, "ASN1_root"): @@ -778,22 +876,67 @@ def __init__(self, name, default, *args, **kwargs): # should be ASN1_Packet if hasattr(p.ASN1_root, "choices"): root = cast(ASN1F_CHOICE, p.ASN1_root) - for k, v in root.choices.items(): + for k, v in zip(root.choice_order, root.choice_list): # ASN1F_CHOICE recursion - self.choices[k] = v + self._register_choice(k, v) else: - self.choices[p.ASN1_root.network_tag] = p + self._register_choice(p.ASN1_root.network_tag, p) elif hasattr(p, "ASN1_tag"): if isinstance(p, type): # should be ASN1F_field class - self.choices[int(p.ASN1_tag)] = p + self._register_choice(int(p.ASN1_tag), p) else: - # should be ASN1F_PACKET instance - self.choices[p.network_tag] = p - self.pktchoices[hash(p.cls)] = (p.implicit_tag, p.explicit_tag) # noqa: E501 + # ASN1F_PACKET or plain ASN1F_field instance + self._register_choice(p.network_tag, p) + if hasattr(p, "cls"): + self.pktchoices[hash(p.cls)] = (p.implicit_tag, p.explicit_tag) # noqa: E501 else: raise ASN1_Error("ASN1F_CHOICE: no tag found for one field") + def _register_choice(self, tag, choice): + # type: (int, _CHOICE_T) -> None + self.choices[tag] = choice + self.choice_order.append(tag) + self.choice_list.append(choice) + + @staticmethod + def _alternative_carries(choice, x): + # type: (_CHOICE_T, Any) -> bool + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + # ASN1_Packet subclass + return isinstance(x, choice) + if hasattr(choice, "cls"): + # ASN1F_PACKET instance, holding a tagged packet + return isinstance(x, choice.cls) + # ASN1F_field, as a class or as a constrained instance + return isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag + + def record_alternative(self, x, index): + # type: (Any, int) -> Any + """Remember the alternative a dissected value was read as. + + Two alternatives of the same type are told apart by their position + alone, which the value does not carry: without this, rebuilding a + dissected value would pick the first alternative of its type. + """ + if isinstance(x, ASN1_Object): + x.asn1_choice_index = index + return x + + def alternative_index(self, x): + # type: (Any) -> Optional[int] + """Position in choice_order of the alternative that carries x.""" + index = getattr(x, "asn1_choice_index", None) + if ( + index is not None and index < len(self.choice_list) and + self._alternative_carries(self.choice_list[index], x) + ): + return cast(int, index) + for index, choice in enumerate(self.choice_list): + if self._alternative_carries(choice, x): + return index + return None + def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] """ @@ -802,6 +945,9 @@ def m2i(self, pkt, s): """ if len(s) == 0: raise ASN1_Error("ASN1F_CHOICE: got empty string") + hook = _field_hook(pkt, "choice_m2i") + if hook is not None: + return cast(Tuple[ASN1_Object[Any], bytes], hook(self, pkt, s)) s = self._apply_tagging_dec(s, pkt) tag, _ = BER_id_dec(s) if tag in self.choices: @@ -827,6 +973,9 @@ def m2i(self, pkt, s): def i2m(self, pkt, x): # type: (ASN1_Packet, Any) -> bytes + hook = _field_hook(pkt, "choice_i2m") + if hook is not None: + return cast(bytes, hook(self, pkt, x)) if x is None: s = b"" else: @@ -886,12 +1035,15 @@ def __init__(self, self.network_tag = 16 | 0x20 # 16 + CONSTRUCTED self.default = default + def _resolve_cls(self, pkt): + # type: (ASN1_Packet) -> Type[ASN1_Packet] + if self.next_cls_cb: + return self.next_cls_cb(pkt) or self.cls + return self.cls + def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[Any, bytes] - if self.next_cls_cb: - cls = self.next_cls_cb(pkt) or self.cls - else: - cls = self.cls + cls = self._resolve_cls(pkt) if not hasattr(cls, "ASN1_root"): # A normal Packet (!= ASN1) return self.extract_packet(cls, s, _underlayer=pkt) @@ -909,6 +1061,9 @@ def i2m(self, x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> bytes + hook = _field_hook(pkt, "packet_i2m") + if hook is not None: + return cast(bytes, hook(self, pkt, x)) if x is None: s = b"" elif isinstance(x, bytes): @@ -1003,6 +1158,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None self.mapping = mapping @@ -1011,7 +1167,8 @@ def __init__(self, default_readable=False, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts, ) def any2i(self, pkt, x): diff --git a/scapy/contrib/automotive/v2x/__init__.py b/scapy/contrib/automotive/v2x/__init__.py new file mode 100644 index 00000000000..b51f8e983f2 --- /dev/null +++ b/scapy/contrib/automotive/v2x/__init__.py @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +# scapy.contrib.description = ETSI ITS V2X ASN.1 messages (UPER) +# scapy.contrib.status = library + +""" +ETSI ITS V2X ASN.1 messages (UPER). + +Implements CAM, DENM, IVIM, SPATEM and MAPEM from ETSI TS 102 637 / TS 103 301. + +Load explicitly:: + + load_contrib("automotive.v2x") +""" + +from scapy.contrib.automotive.v2x.packets import ( + CAM, + DENM, + IVIM, + MAPEM, + SPATEM, +) + +__all__ = ["CAM", "DENM", "IVIM", "SPATEM", "MAPEM"] diff --git a/scapy/contrib/automotive/v2x/packets.py b/scapy/contrib/automotive/v2x/packets.py new file mode 100644 index 00000000000..a2c459ad84b --- /dev/null +++ b/scapy/contrib/automotive/v2x/packets.py @@ -0,0 +1,1961 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information +# AUTO-GENERATED by scapy/tools/generate_its_asn1.py - DO NOT EDIT + +# scapy.contrib.status = skip +""" +ETSI ITS ASN.1 packets (UPER): CAM, DENM, IVIM, SPATEM, MAPEM. +""" + +from scapy.asn1.asn1 import ASN1_Codecs +from scapy.asn1fields import ( + ASN1F_BIT_STRING, + ASN1F_BOOLEAN, + ASN1F_CHOICE, + ASN1F_ENUMERATED, + ASN1F_FLAGS, + ASN1F_IA5_STRING, + ASN1F_INTEGER, + ASN1F_NULL, + ASN1F_NUMERIC_STRING, + ASN1F_PACKET, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_STRING, + ASN1F_UTF8_STRING, + ASN1F_DEFAULT, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet + +# Registers the PER codec that ASN1_Codecs.PER refers to +import scapy.contrib.uper # noqa: F401 + +class ItsPduHeader(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("protocolVersion", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("messageID", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("stationID", 0, uper_min=0, uper_max=4294967295, oer_unsigned=True) + ) + + +class DeltaReferencePosition(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("deltaLatitude", 0, uper_min=-131071, uper_max=131072), + ASN1F_INTEGER("deltaLongitude", 0, uper_min=-131071, uper_max=131072), + ASN1F_INTEGER("deltaAltitude", 0, uper_min=-12700, uper_max=12800) + ) + + +class Altitude(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("altitudeValue", 0, uper_min=-100000, uper_max=800001), + ASN1F_ENUMERATED("altitudeConfidence", 0, {0: 'alt-000-01', 1: 'alt-000-02', 2: 'alt-000-05', 3: 'alt-000-10', 4: 'alt-000-20', 5: 'alt-000-50', 6: 'alt-001-00', 7: 'alt-002-00', 8: 'alt-005-00', 9: 'alt-010-00', 10: 'alt-020-00', 11: 'alt-050-00', 12: 'alt-100-00', 13: 'alt-200-00', 14: 'outOfRange', 15: 'unavailable'}) + ) + + +class PosConfidenceEllipse(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("semiMajorConfidence", 0, uper_min=0, uper_max=4095, oer_unsigned=True), + ASN1F_INTEGER("semiMinorConfidence", 0, uper_min=0, uper_max=4095, oer_unsigned=True), + ASN1F_INTEGER("semiMajorOrientation", 0, uper_min=0, uper_max=3601, oer_unsigned=True) + ) + + +class PathPoint(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("pathPosition", DeltaReferencePosition(), DeltaReferencePosition), + ASN1F_optional(ASN1F_INTEGER("pathDeltaTime", None, uper_min=1, uper_max=65535, oer_unsigned=True)) + ) + + +class PathHistory(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "pathPoints", [], PathPoint, uper_min=0, uper_max=40 + ) + + +class PtActivation(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("ptActivationType", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_STRING("ptActivationData", b'\x00', uper_min=1, uper_max=20) + ) + + +class CauseCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("causeCode", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("subCauseCode", 0, uper_min=0, uper_max=255, oer_unsigned=True), uper_extensible=True + ) + + +class Curvature(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("curvatureValue", 0, uper_min=-1023, uper_max=1023), + ASN1F_ENUMERATED("curvatureConfidence", 0, {0: 'onePerMeter-0-00002', 1: 'onePerMeter-0-0001', 2: 'onePerMeter-0-0005', 3: 'onePerMeter-0-002', 4: 'onePerMeter-0-01', 5: 'onePerMeter-0-1', 6: 'outOfRange', 7: 'unavailable'}) + ) + + +class Heading(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("headingValue", 0, uper_min=0, uper_max=3601, oer_unsigned=True), + ASN1F_INTEGER("headingConfidence", 1, uper_min=1, uper_max=127, oer_unsigned=True) + ) + + +class ClosedLanes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_ENUMERATED("innerhardShoulderStatus", 0, {0: 'availableForStopping', 1: 'closed', 2: 'availableForDriving'})), + ASN1F_optional(ASN1F_ENUMERATED("outerhardShoulderStatus", 0, {0: 'availableForStopping', 1: 'closed', 2: 'availableForDriving'})), + ASN1F_optional(ASN1F_BIT_STRING("drivingLaneStatus", None, uper_min=1, uper_max=13)), uper_extensible=True + ) + + +class Speed(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("speedValue", 0, uper_min=0, uper_max=16383, oer_unsigned=True), + ASN1F_INTEGER("speedConfidence", 1, uper_min=1, uper_max=127, oer_unsigned=True) + ) + + +class LongitudinalAcceleration(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("longitudinalAccelerationValue", 0, uper_min=-160, uper_max=161), + ASN1F_INTEGER("longitudinalAccelerationConfidence", 0, uper_min=0, uper_max=102, oer_unsigned=True) + ) + + +class LateralAcceleration(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("lateralAccelerationValue", 0, uper_min=-160, uper_max=161), + ASN1F_INTEGER("lateralAccelerationConfidence", 0, uper_min=0, uper_max=102, oer_unsigned=True) + ) + + +class VerticalAcceleration(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("verticalAccelerationValue", 0, uper_min=-160, uper_max=161), + ASN1F_INTEGER("verticalAccelerationConfidence", 0, uper_min=0, uper_max=102, oer_unsigned=True) + ) + + +class DangerousGoodsExtended(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("dangerousGoodsType", 0, {0: 'explosives1', 1: 'explosives2', 2: 'explosives3', 3: 'explosives4', 4: 'explosives5', 5: 'explosives6', 6: 'flammableGases', 7: 'nonFlammableGases', 8: 'toxicGases', 9: 'flammableLiquids', 10: 'flammableSolids', 11: 'substancesLiableToSpontaneousCombustion', 12: 'substancesEmittingFlammableGasesUponContactWithWater', 13: 'oxidizingSubstances', 14: 'organicPeroxides', 15: 'toxicSubstances', 16: 'infectiousSubstances', 17: 'radioactiveMaterial', 18: 'corrosiveSubstances', 19: 'miscellaneousDangerousSubstances'}), + ASN1F_INTEGER("unNumber", 0, uper_min=0, uper_max=9999, oer_unsigned=True), + ASN1F_BOOLEAN("elevatedTemperature", False), + ASN1F_BOOLEAN("tunnelsRestricted", False), + ASN1F_BOOLEAN("limitedQuantity", False), + ASN1F_optional(ASN1F_IA5_STRING("emergencyActionCode", None, uper_min=1, uper_max=24)), + ASN1F_optional(ASN1F_NUMERIC_STRING("phoneNumber", None, uper_min=1, uper_max=16)), + ASN1F_optional(ASN1F_UTF8_STRING("companyName", None, uper_min=1, uper_max=24)), uper_extensible=True + ) + + +class VehicleIdentification(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_IA5_STRING("wMInumber", None, uper_min=1, uper_max=3)), + ASN1F_optional(ASN1F_IA5_STRING("vDS", None, size_len=6)), uper_extensible=True + ) + + +class VehicleLength(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("vehicleLengthValue", 1, uper_min=1, uper_max=1023, oer_unsigned=True), + ASN1F_ENUMERATED("vehicleLengthConfidenceIndication", 0, {0: 'noTrailerPresent', 1: 'trailerPresentWithKnownLength', 2: 'trailerPresentWithUnknownLength', 3: 'trailerPresenceIsUnknown', 4: 'unavailable'}) + ) + + +class SteeringWheelAngle(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("steeringWheelAngleValue", 0, uper_min=-511, uper_max=512), + ASN1F_INTEGER("steeringWheelAngleConfidence", 1, uper_min=1, uper_max=127, oer_unsigned=True) + ) + + +class YawRate(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("yawRateValue", 0, uper_min=-32766, uper_max=32767), + ASN1F_ENUMERATED("yawRateConfidence", 0, {0: 'degSec-000-01', 1: 'degSec-000-05', 2: 'degSec-000-10', 3: 'degSec-001-00', 4: 'degSec-005-00', 5: 'degSec-010-00', 6: 'degSec-100-00', 7: 'outOfRange', 8: 'unavailable'}) + ) + + +class ActionID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("originatingStationID", 0, uper_min=0, uper_max=4294967295, oer_unsigned=True), + ASN1F_INTEGER("sequenceNumber", 0, uper_min=0, uper_max=65535, oer_unsigned=True) + ) + + +class ProtectedCommunicationZone(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("protectedZoneType", 0, {0: 'permanentCenDsrcTolling', 1: 'temporaryCenDsrcTolling'}), + ASN1F_optional(ASN1F_INTEGER("expiryTime", None, uper_min=0, uper_max=4398046511103, oer_unsigned=True)), + ASN1F_INTEGER("protectedZoneLatitude", 0, uper_min=-900000000, uper_max=900000001), + ASN1F_INTEGER("protectedZoneLongitude", 0, uper_min=-1800000000, uper_max=1800000001), + ASN1F_optional(ASN1F_INTEGER("protectedZoneRadius", None, uper_min=1, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("protectedZoneID", None, uper_min=0, uper_max=134217727, oer_unsigned=True)), uper_extensible=True + ) + + +class EventPoint(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("eventPosition", DeltaReferencePosition(), DeltaReferencePosition), + ASN1F_optional(ASN1F_INTEGER("eventDeltaTime", None, uper_min=1, uper_max=65535, oer_unsigned=True, uper_extensible=True)), + ASN1F_INTEGER("informationQuality", 0, uper_min=0, uper_max=7, oer_unsigned=True) + ) + + +class CenDsrcTollingZone(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("protectedZoneLatitude", 0, uper_min=-900000000, uper_max=900000001), + ASN1F_INTEGER("protectedZoneLongitude", 0, uper_min=-1800000000, uper_max=1800000001), + ASN1F_optional(ASN1F_INTEGER("cenDsrcTollingZoneID", None, uper_min=0, uper_max=134217727, oer_unsigned=True)), uper_extensible=True + ) + + +class BasicVehicleContainerHighFrequency(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("heading", Heading(), Heading), + ASN1F_PACKET("speed", Speed(), Speed), + ASN1F_ENUMERATED("driveDirection", 0, {0: 'forward', 1: 'backward', 2: 'unavailable'}), + ASN1F_PACKET("vehicleLength", VehicleLength(), VehicleLength), + ASN1F_INTEGER("vehicleWidth", 1, uper_min=1, uper_max=62, oer_unsigned=True), + ASN1F_PACKET("longitudinalAcceleration", LongitudinalAcceleration(), LongitudinalAcceleration), + ASN1F_PACKET("curvature", Curvature(), Curvature), + ASN1F_ENUMERATED("curvatureCalculationMode", 0, {0: 'yawRateUsed', 1: 'yawRateNotUsed', 2: 'unavailable'}), + ASN1F_PACKET("yawRate", YawRate(), YawRate), + ASN1F_optional(ASN1F_FLAGS("accelerationControl", None, ['brakePedalEngaged', 'gasPedalEngaged', 'emergencyBrakeEngaged', 'collisionWarningEngaged', 'accEngaged', 'cruiseControlEngaged', 'speedLimiterEngaged'], uper_min=7, uper_max=7)), + ASN1F_optional(ASN1F_INTEGER("lanePosition", None, uper_min=-1, uper_max=14)), + ASN1F_optional(ASN1F_PACKET("steeringWheelAngle", None, SteeringWheelAngle)), + ASN1F_optional(ASN1F_PACKET("lateralAcceleration", None, LateralAcceleration)), + ASN1F_optional(ASN1F_PACKET("verticalAcceleration", None, VerticalAcceleration)), + ASN1F_optional(ASN1F_INTEGER("performanceClass", None, uper_min=0, uper_max=7, oer_unsigned=True)), + ASN1F_optional(ASN1F_PACKET("cenDsrcTollingZone", None, CenDsrcTollingZone)) + ) + + +class BasicVehicleContainerLowFrequency(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("vehicleRole", 0, {0: 'default', 1: 'publicTransport', 2: 'specialTransport', 3: 'dangerousGoods', 4: 'roadWork', 5: 'rescue', 6: 'emergency', 7: 'safetyCar', 8: 'agriculture', 9: 'commercial', 10: 'military', 11: 'roadOperator', 12: 'taxi', 13: 'reserved1', 14: 'reserved2', 15: 'reserved3'}), + ASN1F_FLAGS("exteriorLights", '00000000', ['lowBeamHeadlightsOn', 'highBeamHeadlightsOn', 'leftTurnSignalOn', 'rightTurnSignalOn', 'daytimeRunningLightsOn', 'reverseLightOn', 'fogLightOn', 'parkingLightsOn'], uper_min=8, uper_max=8), + ASN1F_SEQUENCE_OF("pathHistory", [], PathPoint, uper_min=0, uper_max=40) + ) + + +class PublicTransportContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("embarkationStatus", False), + ASN1F_optional(ASN1F_PACKET("ptActivation", None, PtActivation)) + ) + + +class SpecialTransportContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("specialTransportType", '0000', ['heavyLoad', 'excessWidth', 'excessLength', 'excessHeight'], uper_min=4, uper_max=4), + ASN1F_FLAGS("lightBarSirenInUse", '00', ['lightBarActivated', 'sirenActivated'], uper_min=2, uper_max=2) + ) + + +class DangerousGoodsContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("dangerousGoodsBasic", 0, {0: 'explosives1', 1: 'explosives2', 2: 'explosives3', 3: 'explosives4', 4: 'explosives5', 5: 'explosives6', 6: 'flammableGases', 7: 'nonFlammableGases', 8: 'toxicGases', 9: 'flammableLiquids', 10: 'flammableSolids', 11: 'substancesLiableToSpontaneousCombustion', 12: 'substancesEmittingFlammableGasesUponContactWithWater', 13: 'oxidizingSubstances', 14: 'organicPeroxides', 15: 'toxicSubstances', 16: 'infectiousSubstances', 17: 'radioactiveMaterial', 18: 'corrosiveSubstances', 19: 'miscellaneousDangerousSubstances'}) + ) + + +class RoadWorksContainerBasic(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("roadworksSubCauseCode", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_FLAGS("lightBarSirenInUse", '00', ['lightBarActivated', 'sirenActivated'], uper_min=2, uper_max=2), + ASN1F_optional(ASN1F_PACKET("closedLanes", None, ClosedLanes)) + ) + + +class RescueContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("lightBarSirenInUse", '00', ['lightBarActivated', 'sirenActivated'], uper_min=2, uper_max=2) + ) + + +class EmergencyContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("lightBarSirenInUse", '00', ['lightBarActivated', 'sirenActivated'], uper_min=2, uper_max=2), + ASN1F_optional(ASN1F_PACKET("incidentIndication", None, CauseCode)), + ASN1F_optional(ASN1F_FLAGS("emergencyPriority", None, ['requestForRightOfWay', 'requestForFreeCrossingAtATrafficLight'], uper_min=2, uper_max=2)) + ) + + +class SafetyCarContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("lightBarSirenInUse", '00', ['lightBarActivated', 'sirenActivated'], uper_min=2, uper_max=2), + ASN1F_optional(ASN1F_PACKET("incidentIndication", None, CauseCode)), + ASN1F_optional(ASN1F_ENUMERATED("trafficRule", 0, {0: 'noPassing', 1: 'noPassingForTrucks', 2: 'passToRight', 3: 'passToLeft'})), + ASN1F_optional(ASN1F_INTEGER("speedLimit", None, uper_min=1, uper_max=255, oer_unsigned=True)) + ) + + +class RSUContainerHighFrequency(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("protectedCommunicationZonesRSU", None, ProtectedCommunicationZone, uper_min=1, uper_max=16)), uper_extensible=True + ) + + +class SituationContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("informationQuality", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_PACKET("eventType", CauseCode(), CauseCode), + ASN1F_optional(ASN1F_PACKET("linkedCause", None, CauseCode)), + ASN1F_optional(ASN1F_SEQUENCE_OF("eventHistory", None, EventPoint, uper_min=1, uper_max=23)), uper_extensible=True + ) + + +class LocationContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_PACKET("eventSpeed", None, Speed)), + ASN1F_optional(ASN1F_PACKET("eventPositionHeading", None, Heading)), + ASN1F_SEQUENCE_OF("traces", [], PathHistory, uper_min=1, uper_max=7), + ASN1F_optional(ASN1F_ENUMERATED("roadType", None, {0: 'urban-NoStructuralSeparationToOppositeLanes', 1: 'urban-WithStructuralSeparationToOppositeLanes', 2: 'nonUrban-NoStructuralSeparationToOppositeLanes', 3: 'nonUrban-WithStructuralSeparationToOppositeLanes'})), uper_extensible=True + ) + + +class ImpactReductionContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("heightLonCarrLeft", 1, uper_min=1, uper_max=100, oer_unsigned=True), + ASN1F_INTEGER("heightLonCarrRight", 1, uper_min=1, uper_max=100, oer_unsigned=True), + ASN1F_INTEGER("posLonCarrLeft", 1, uper_min=1, uper_max=127, oer_unsigned=True), + ASN1F_INTEGER("posLonCarrRight", 1, uper_min=1, uper_max=127, oer_unsigned=True), + ASN1F_SEQUENCE_OF("positionOfPillars", [], ASN1F_INTEGER, uper_min=1, uper_max=3, uper_extensible=True), + ASN1F_INTEGER("posCentMass", 1, uper_min=1, uper_max=63, oer_unsigned=True), + ASN1F_INTEGER("wheelBaseVehicle", 1, uper_min=1, uper_max=127, oer_unsigned=True), + ASN1F_INTEGER("turningRadius", 1, uper_min=1, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("posFrontAx", 1, uper_min=1, uper_max=20, oer_unsigned=True), + ASN1F_FLAGS("positionOfOccupants", '00000000000000000000', ['row1LeftOccupied', 'row1RightOccupied', 'row1MidOccupied', 'row1NotDetectable', 'row1NotPresent', 'row2LeftOccupied', 'row2RightOccupied', 'row2MidOccupied', 'row2NotDetectable', 'row2NotPresent', 'row3LeftOccupied', 'row3RightOccupied', 'row3MidOccupied', 'row3NotDetectable', 'row3NotPresent', 'row4LeftOccupied', 'row4RightOccupied', 'row4MidOccupied', 'row4NotDetectable', 'row4NotPresent'], uper_min=20, uper_max=20), + ASN1F_INTEGER("vehicleMass", 1, uper_min=1, uper_max=1024, oer_unsigned=True), + ASN1F_ENUMERATED("requestResponseIndication", 0, {0: 'request', 1: 'response'}) + ) + + +class StationaryVehicleContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_ENUMERATED("stationarySince", 0, {0: 'lessThan1Minute', 1: 'lessThan2Minutes', 2: 'lessThan15Minutes', 3: 'equalOrGreater15Minutes'})), + ASN1F_optional(ASN1F_PACKET("stationaryCause", None, CauseCode)), + ASN1F_optional(ASN1F_PACKET("carryingDangerousGoods", None, DangerousGoodsExtended)), + ASN1F_optional(ASN1F_INTEGER("numberOfOccupants", None, uper_min=0, uper_max=127, oer_unsigned=True)), + ASN1F_optional(ASN1F_PACKET("vehicleIdentification", None, VehicleIdentification)), + ASN1F_optional(ASN1F_FLAGS("energyStorageType", None, ['hydrogenStorage', 'electricEnergyStorage', 'liquidPropaneGas', 'compressedNaturalGas', 'diesel', 'gasoline', 'ammonia'], uper_min=7, uper_max=7)) + ) + + +class EuVehicleCategoryCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_ENUMERATED("euVehicleCategoryL", 0, {0: 'l1', 1: 'l2', 2: 'l3', 3: 'l4', 4: 'l5', 5: 'l6', 6: 'l7'}), + ASN1F_ENUMERATED("euVehicleCategoryM", 0, {0: 'm1', 1: 'm2', 2: 'm3'}), + ASN1F_ENUMERATED("euVehicleCategoryN", 0, {0: 'n1', 1: 'n2', 2: 'n3'}), + ASN1F_ENUMERATED("euVehicleCategoryO", 0, {0: 'o1', 1: 'o2', 2: 'o3', 3: 'o4'}), + ASN1F_NULL("euVehilcleCategoryT", None), + ASN1F_NULL("euVehilcleCategoryG", None) + ) + + +class InternationalSign_speedLimits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("speedLimitMax", None, uper_min=0, uper_max=250, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("speedLimitMin", None, uper_min=0, uper_max=250, oer_unsigned=True)), + ASN1F_INTEGER("unit", 0, uper_min=0, uper_max=15, oer_unsigned=True) + ) + + +class DestinationRoad(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("derType", 0, uper_min=0, uper_max=15, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("roadNumberIdentifier", None, uper_min=1, uper_max=999, oer_unsigned=True)), + ASN1F_optional(ASN1F_UTF8_STRING("roadNumberText", None)) + ) + + +class Distance(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("value", 1, uper_min=1, uper_max=16384, oer_unsigned=True), + ASN1F_INTEGER("unit", 0, uper_min=0, uper_max=15, oer_unsigned=True) + ) + + +class DistanceOrDuration(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("value", 1, uper_min=1, uper_max=16384, oer_unsigned=True), + ASN1F_INTEGER("unit", 0, uper_min=0, uper_max=15, oer_unsigned=True) + ) + + +class HoursMinutes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("hours", 0, uper_min=0, uper_max=23, oer_unsigned=True), + ASN1F_INTEGER("mins", 0, uper_min=0, uper_max=59, oer_unsigned=True) + ) + + +class MonthDay(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("month", 1, uper_min=1, uper_max=12, oer_unsigned=True), + ASN1F_INTEGER("day", 1, uper_min=1, uper_max=31, oer_unsigned=True) + ) + + +class Weight(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("value", 1, uper_min=1, uper_max=16384, oer_unsigned=True), + ASN1F_INTEGER("unit", 0, uper_min=0, uper_max=15, oer_unsigned=True) + ) + + +class ITS_Inline_InternationalSign_applicablePeriod_year(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("yearRangeStartYear", 2000, uper_min=2000, uper_max=2127, oer_unsigned=True), + ASN1F_INTEGER("yearRangeEndYear", 2000, uper_min=2000, uper_max=2127, oer_unsigned=True) + ) + + +class ITS_Inline_InternationalSign_applicablePeriod_month_day(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("dateRangeStartMonthDay", MonthDay(), MonthDay), + ASN1F_PACKET("dateRangeEndMonthDay", MonthDay(), MonthDay) + ) + + +class ITS_Inline_InternationalSign_applicablePeriod_hourMinutes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("timeRangeStartTime", HoursMinutes(), HoursMinutes), + ASN1F_PACKET("timeRangeEndTime", HoursMinutes(), HoursMinutes) + ) + + +class ITS_Inline_ITS_Inline_GddStructure_pictogramCode_serviceCategoryCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_ENUMERATED("trafficSignPictogram", 0, {0: 'dangerWarning', 1: 'regulatory', 2: 'informative'}), + ASN1F_ENUMERATED("publicFacilitiesPictogram", 0, {0: 'publicFacilities'}), + ASN1F_ENUMERATED("ambientOrRoadConditionPictogram", 0, {0: 'ambientCondition', 1: 'roadCondition'}), uper_extensible=True + ) + + +class ITS_Inline_ITS_Inline_GddStructure_pictogramCode_pictogramCategoryCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("nature", 1, uper_min=1, uper_max=9, oer_unsigned=True), + ASN1F_INTEGER("serialNumber", 0, uper_min=0, uper_max=99, oer_unsigned=True) + ) + + +class AxleWeightLimits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("maxLadenweightOnAxle1", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("maxLadenweightOnAxle2", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("maxLadenweightOnAxle3", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("maxLadenweightOnAxle4", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("maxLadenweightOnAxle5", 0, uper_min=0, uper_max=65535, oer_unsigned=True) + ) + + +class EnvironmentalCharacteristics(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("euroValue", 0, {0: 'noEntry', 1: 'euro-1', 2: 'euro-2', 3: 'euro-3', 4: 'euro-4', 5: 'euro-5', 6: 'euro-6', 7: 'reservedForUse1', 8: 'reservedForUse2', 9: 'reservedForUse3', 10: 'reservedForUse4', 11: 'reservedForUse5', 12: 'reservedForUse6', 13: 'reservedForUse7', 14: 'reservedForUse8', 15: 'eev'}), + ASN1F_ENUMERATED("copValue", 0, {0: 'noEntry', 1: 'co2class1', 2: 'co2class2', 3: 'co2class3', 4: 'co2class4', 5: 'co2class5', 6: 'co2class6', 7: 'co2class7', 8: 'reservedforUse'}) + ) + + +class ExhaustEmissionValues(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("unitType", 0, {0: 'mg-km', 1: 'mg-kWh'}), + ASN1F_INTEGER("emissionCO", 0, uper_min=0, uper_max=32767, oer_unsigned=True), + ASN1F_INTEGER("emissionHC", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("emissionNOX", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("emissionHCNOX", 0, uper_min=0, uper_max=65535, oer_unsigned=True) + ) + + +class PassengerCapacity(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("numberOfSeats", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("numberOfStandingPlaces", 0, uper_min=0, uper_max=255, oer_unsigned=True) + ) + + +class Provider(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING("countryCode", '0000000000', uper_min=10, uper_max=10, default_readable=False), + ASN1F_INTEGER("providerIdentifier", 0, uper_min=0, uper_max=16383, oer_unsigned=True) + ) + + +class SoundLevel(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("soundstationary", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("sounddriveby", 0, uper_min=0, uper_max=255, oer_unsigned=True) + ) + + +class VehicleDimensions(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("vehicleLengthOverall", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("vehicleHeigthOverall", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("vehicleWidthOverall", 0, uper_min=0, uper_max=255, oer_unsigned=True) + ) + + +class VehicleWeightLimits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("vehicleMaxLadenWeight", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("vehicleTrainMaximumWeight", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_INTEGER("vehicleWeightUnladen", 0, uper_min=0, uper_max=65535, oer_unsigned=True) + ) + + +class ITS_Inline_DieselEmissionValues_particulate(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("unitType", 0, {0: 'mg-km', 1: 'mg-kWh'}), + ASN1F_INTEGER("value", 0, uper_min=0, uper_max=32767, oer_unsigned=True) + ) + + +class AdvisorySpeed(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("type", 0, {0: 'none', 1: 'greenwave', 2: 'ecoDrive', 3: 'transit'}), + ASN1F_optional(ASN1F_INTEGER("speed", None, uper_min=0, uper_max=500, oer_unsigned=True)), + ASN1F_optional(ASN1F_ENUMERATED("confidence", 0, {0: 'unavailable', 1: 'prec100ms', 2: 'prec10ms', 3: 'prec5ms', 4: 'prec1ms', 5: 'prec0-1ms', 6: 'prec0-05ms', 7: 'prec0-01ms'})), + ASN1F_optional(ASN1F_INTEGER("distance", None, uper_min=0, uper_max=10000, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("class_", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class ConnectingLane(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("lane", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_optional(ASN1F_FLAGS("maneuver", None, ['maneuverStraightAllowed', 'maneuverLeftAllowed', 'maneuverRightAllowed', 'maneuverUTurnAllowed', 'maneuverLeftTurnOnRedAllowed', 'maneuverRightTurnOnRedAllowed', 'maneuverLaneChangeAllowed', 'maneuverNoStoppingAllowed', 'yieldAllwaysRequired', 'goWithHalt', 'caution', 'reserved1'], uper_min=12, uper_max=12)) + ) + + +class ConnectionManeuverAssist(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("connectionID", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("queueLength", None, uper_min=0, uper_max=10000, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("availableStorageLength", None, uper_min=0, uper_max=10000, oer_unsigned=True)), + ASN1F_optional(ASN1F_BOOLEAN("waitOnStop", None)), + ASN1F_optional(ASN1F_BOOLEAN("pedBicycleDetect", None)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class DataParameters(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_IA5_STRING("processMethod", None, uper_min=1, uper_max=255)), + ASN1F_optional(ASN1F_IA5_STRING("processAgency", None, uper_min=1, uper_max=255)), + ASN1F_optional(ASN1F_IA5_STRING("lastCheckedDate", None, uper_min=1, uper_max=255)), + ASN1F_optional(ASN1F_IA5_STRING("geoidUsed", None, uper_min=1, uper_max=255)), uper_extensible=True + ) + + +class IntersectionReferenceID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("region", None, uper_min=0, uper_max=65535, oer_unsigned=True)), + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=65535, oer_unsigned=True) + ) + + +class LaneTypeAttributes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_FLAGS("vehicle", '00000000', ['isVehicleRevocableLane', 'isVehicleFlyOverLane', 'hovLaneUseOnly', 'restrictedToBusUse', 'restrictedToTaxiUse', 'restrictedFromPublicUse', 'hasIRbeaconCoverage', 'permissionOnRequest'], uper_min=8, uper_max=8), + ASN1F_FLAGS("crosswalk", '0000000000000000', ['crosswalkRevocableLane', 'bicyleUseAllowed', 'isXwalkFlyOverLane', 'fixedCycleTime', 'biDirectionalCycleTimes', 'hasPushToWalkButton', 'audioSupport', 'rfSignalRequestPresent', 'unsignalizedSegmentsPresent'], uper_min=16, uper_max=16), + ASN1F_FLAGS("bikeLane", '0000000000000000', ['bikeRevocableLane', 'pedestrianUseAllowed', 'isBikeFlyOverLane', 'fixedCycleTime', 'biDirectionalCycleTimes', 'isolatedByBarrier', 'unsignalizedSegmentsPresent'], uper_min=16, uper_max=16), + ASN1F_FLAGS("sidewalk", '0000000000000000', ['sidewalk-RevocableLane', 'bicyleUseAllowed', 'isSidewalkFlyOverLane', 'walkBikes'], uper_min=16, uper_max=16), + ASN1F_FLAGS("median", '0000000000000000', ['median-RevocableLane', 'median', 'whiteLineHashing', 'stripedLines', 'doubleStripedLines', 'trafficCones', 'constructionBarrier', 'trafficChannels', 'lowCurbs', 'highCurbs'], uper_min=16, uper_max=16), + ASN1F_FLAGS("striping", '0000000000000000', ['stripeToConnectingLanesRevocableLane', 'stripeDrawOnLeft', 'stripeDrawOnRight', 'stripeToConnectingLanesLeft', 'stripeToConnectingLanesRight', 'stripeToConnectingLanesAhead'], uper_min=16, uper_max=16), + ASN1F_FLAGS("trackedVehicle", '0000000000000000', ['spec-RevocableLane', 'spec-commuterRailRoadTrack', 'spec-lightRailRoadTrack', 'spec-heavyRailRoadTrack', 'spec-otherRailType'], uper_min=16, uper_max=16), + ASN1F_FLAGS("parking", '0000000000000000', ['parkingRevocableLane', 'parallelParkingInUse', 'headInParkingInUse', 'doNotParkZone', 'parkingForBusUse', 'parkingForTaxiUse', 'noPublicParkingUse'], uper_min=16, uper_max=16), uper_extensible=True + ) + + +class Node_LLmD_64b(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("lon", 0, uper_min=-1800000000, uper_max=1800000001), + ASN1F_INTEGER("lat", 0, uper_min=-900000000, uper_max=900000001) + ) + + +class Node_XY_20b(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=-512, uper_max=511), + ASN1F_INTEGER("y", 0, uper_min=-512, uper_max=511) + ) + + +class Node_XY_22b(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=-1024, uper_max=1023), + ASN1F_INTEGER("y", 0, uper_min=-1024, uper_max=1023) + ) + + +class Node_XY_24b(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=-2048, uper_max=2047), + ASN1F_INTEGER("y", 0, uper_min=-2048, uper_max=2047) + ) + + +class Node_XY_26b(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=-4096, uper_max=4095), + ASN1F_INTEGER("y", 0, uper_min=-4096, uper_max=4095) + ) + + +class Node_XY_28b(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=-8192, uper_max=8191), + ASN1F_INTEGER("y", 0, uper_min=-8192, uper_max=8191) + ) + + +class Node_XY_32b(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=-32768, uper_max=32767), + ASN1F_INTEGER("y", 0, uper_min=-32768, uper_max=32767) + ) + + +class NodeOffsetPointXY(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", Node_XY_20b(), + Node_XY_20b, + Node_XY_22b, + Node_XY_24b, + Node_XY_26b, + Node_XY_28b, + Node_XY_32b, + Node_LLmD_64b + ) + + +class Position3D(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("lat", 0, uper_min=-900000000, uper_max=900000001), + ASN1F_INTEGER("long", 0, uper_min=-1800000000, uper_max=1800000001), + ASN1F_optional(ASN1F_INTEGER("elevation", None, uper_min=-4096, uper_max=61439)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class RegulatorySpeedLimit(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("type", 0, {0: 'unknown', 1: 'maxSpeedInSchoolZone', 2: 'maxSpeedInSchoolZoneWhenChildrenArePresent', 3: 'maxSpeedInConstructionZone', 4: 'vehicleMinSpeed', 5: 'vehicleMaxSpeed', 6: 'vehicleNightMaxSpeed', 7: 'truckMinSpeed', 8: 'truckMaxSpeed', 9: 'truckNightMaxSpeed', 10: 'vehiclesWithTrailersMinSpeed', 11: 'vehiclesWithTrailersMaxSpeed', 12: 'vehiclesWithTrailersNightMaxSpeed'}), + ASN1F_INTEGER("speed", 0, uper_min=0, uper_max=8191, oer_unsigned=True) + ) + + +class RestrictionUserType(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_ENUMERATED("basicType", 0, {0: 'none', 1: 'equippedTransit', 2: 'equippedTaxis', 3: 'equippedOther', 4: 'emissionCompliant', 5: 'equippedBicycle', 6: 'weightCompliant', 7: 'heightCompliant', 8: 'pedestrians', 9: 'slowMovingPersons', 10: 'wheelchairUsers', 11: 'visualDisabilities', 12: 'audioDisabilities', 13: 'otherUnknownDisabilities'}), uper_extensible=True + ) + + +class RoadSegmentReferenceID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("region", None, uper_min=0, uper_max=65535, oer_unsigned=True)), + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=65535, oer_unsigned=True) + ) + + +class SignalControlZone(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING("zone", b""), uper_extensible=True + ) + + +class TimeChangeDetails(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("startTime", None, uper_min=0, uper_max=36001, oer_unsigned=True)), + ASN1F_INTEGER("minEndTime", 0, uper_min=0, uper_max=36001, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("maxEndTime", None, uper_min=0, uper_max=36001, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("likelyTime", None, uper_min=0, uper_max=36001, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("confidence", None, uper_min=0, uper_max=15, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("nextTime", None, uper_min=0, uper_max=36001, oer_unsigned=True)) + ) + + +class ITS_Inline_ComputedLane_offsetXaxis(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("small", 0, uper_min=-2047, uper_max=2047), + ASN1F_INTEGER("large", 0, uper_min=-32767, uper_max=32767) + ) + + +class ITS_Inline_ComputedLane_offsetYaxis(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("small", 0, uper_min=-2047, uper_max=2047), + ASN1F_INTEGER("large", 0, uper_min=-32767, uper_max=32767) + ) + + +class IviManagementContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("serviceProviderId", Provider(), Provider), + ASN1F_INTEGER("iviIdentificationNumber", 1, uper_min=1, uper_max=32767, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("timeStamp", None, uper_min=0, uper_max=4398046511103, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("validFrom", None, uper_min=0, uper_max=4398046511103, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("validTo", None, uper_min=0, uper_max=4398046511103, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("connectedIviStructures", None, ASN1F_INTEGER, uper_min=1, uper_max=8)), + ASN1F_INTEGER("iviStatus", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_optional(ASN1F_SEQUENCE_OF("connectedDenms", None, ActionID, uper_min=1, uper_max=8, uper_extensible=True)), uper_extensible=True + ) + + +class MlcPart(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("zoneId", 1, uper_min=1, uper_max=32, oer_unsigned=True), + ASN1F_optional(ASN1F_SEQUENCE_OF("laneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=16, uper_extensible=True)) + ) + + +class AbsolutePosition(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("latitude", 0, uper_min=-900000000, uper_max=900000001), + ASN1F_INTEGER("longitude", 0, uper_min=-1800000000, uper_max=1800000001) + ) + + +class AbsolutePositionWAltitude(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("latitude", 0, uper_min=-900000000, uper_max=900000001), + ASN1F_INTEGER("longitude", 0, uper_min=-1800000000, uper_max=1800000001), + ASN1F_PACKET("altitude", Altitude(), Altitude) + ) + + +class ComputedSegment(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("zoneId", 1, uper_min=1, uper_max=32, oer_unsigned=True), + ASN1F_INTEGER("laneNumber", 0, uper_min=-1, uper_max=14), + ASN1F_INTEGER("laneWidth", 0, uper_min=0, uper_max=1023, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("offsetDistance", None, uper_min=-32768, uper_max=32767)), + ASN1F_optional(ASN1F_PACKET("offsetPosition", None, DeltaReferencePosition)) + ) + + +class DeltaPosition(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("deltaLatitude", 0, uper_min=-131071, uper_max=131072), + ASN1F_INTEGER("deltaLongitude", 0, uper_min=-131071, uper_max=131072) + ) + + +class LaneCharacteristics(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("zoneDefinitionAccuracy", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_BOOLEAN("existinglaneMarkingStatus", False), + ASN1F_INTEGER("newlaneMarkingColour", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_INTEGER("laneDelimitationLeft", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_INTEGER("laneDelimitationRight", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_INTEGER("mergingWith", 1, uper_min=1, uper_max=32, oer_unsigned=True) + ) + + +class LayoutComponent(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("layoutComponentId", 1, uper_min=1, uper_max=8, oer_unsigned=True), + ASN1F_INTEGER("height", 10, uper_min=10, uper_max=73, oer_unsigned=True), + ASN1F_INTEGER("width", 10, uper_min=10, uper_max=265, oer_unsigned=True), + ASN1F_INTEGER("x", 10, uper_min=10, uper_max=265, oer_unsigned=True), + ASN1F_INTEGER("y", 10, uper_min=10, uper_max=73, oer_unsigned=True), + ASN1F_INTEGER("textScripting", 0, uper_min=0, uper_max=1, oer_unsigned=True) + ) + + +class LoadType(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("goodsType", 0, uper_min=0, uper_max=15, oer_unsigned=True), + ASN1F_ENUMERATED("dangerousGoodsType", 0, {0: 'explosives1', 1: 'explosives2', 2: 'explosives3', 3: 'explosives4', 4: 'explosives5', 5: 'explosives6', 6: 'flammableGases', 7: 'nonFlammableGases', 8: 'toxicGases', 9: 'flammableLiquids', 10: 'flammableSolids', 11: 'substancesLiableToSpontaneousCombustion', 12: 'substancesEmittingFlammableGasesUponContactWithWater', 13: 'oxidizingSubstances', 14: 'organicPeroxides', 15: 'toxicSubstances', 16: 'infectiousSubstances', 17: 'radioactiveMaterial', 18: 'corrosiveSubstances', 19: 'miscellaneousDangerousSubstances'}), + ASN1F_FLAGS("specialTransportType", '0000', ['heavyLoad', 'excessWidth', 'excessLength', 'excessHeight'], uper_min=4, uper_max=4) + ) + + +class MapReference(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", RoadSegmentReferenceID(), + RoadSegmentReferenceID, + IntersectionReferenceID + ) + + +class PolygonalLine(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_STRING("deltaPositions", 0, uper_min=1, uper_max=32, size_len=100), + ASN1F_STRING("deltaPositionsWithAltitude", 0, uper_min=1, uper_max=32, size_len=100), + ASN1F_STRING("absolutePositions", 0, uper_min=1, uper_max=8), + ASN1F_STRING("absolutePositionsWithAltitude", 0, uper_min=1, uper_max=8), uper_extensible=True + ) + + +class RoadSurfaceDynamicCharacteristics(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("condition", 0, uper_min=0, uper_max=15, oer_unsigned=True), + ASN1F_INTEGER("temperature", 0, uper_min=-100, uper_max=151), + ASN1F_INTEGER("iceOrWaterDepth", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("treatment", 0, uper_min=0, uper_max=7, oer_unsigned=True) + ) + + +class RoadSurfaceStaticCharacteristics(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("frictionCoefficient", 0, uper_min=0, uper_max=101, oer_unsigned=True), + ASN1F_INTEGER("material", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_INTEGER("wear", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_INTEGER("avBankingAngle", 0, uper_min=-20, uper_max=21) + ) + + +class Segment(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("line", PolygonalLine(), PolygonalLine), + ASN1F_optional(ASN1F_INTEGER("laneWidth", None, uper_min=0, uper_max=1023, oer_unsigned=True)) + ) + + +class Text(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("layoutComponentId", None, uper_min=1, uper_max=4, oer_unsigned=True)), + ASN1F_BIT_STRING("language", '0000000000', uper_min=10, uper_max=10, default_readable=False), + ASN1F_UTF8_STRING("textContent", '') + ) + + +class VehicleCharacteristicsFixValues(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("simpleVehicleType", 0, uper_min=0, uper_max=255, oer_unsigned=True), + EuVehicleCategoryCode, + ASN1F_INTEGER("iso3833VehicleType", 0, uper_min=0, uper_max=255, oer_unsigned=True), + EnvironmentalCharacteristics, + ASN1F_INTEGER("engineCharacteristics", 0, uper_min=0, uper_max=255, oer_unsigned=True), + LoadType, + ASN1F_ENUMERATED("usage", 0, {0: 'default', 1: 'publicTransport', 2: 'specialTransport', 3: 'dangerousGoods', 4: 'roadWork', 5: 'rescue', 6: 'emergency', 7: 'safetyCar', 8: 'agriculture', 9: 'commercial', 10: 'military', 11: 'roadOperator', 12: 'taxi', 13: 'reserved1', 14: 'reserved2', 15: 'reserved3'}), uper_extensible=True + ) + + +class Zone(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", Segment(), + Segment, + PolygonalLine, + ComputedSegment, uper_extensible=True + ) + + +class ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_serviceCategoryCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_ENUMERATED("trafficSignPictogram", 0, {0: 'dangerWarning', 1: 'regulatory', 2: 'informative'}), + ASN1F_ENUMERATED("publicFacilitiesPictogram", 0, {0: 'publicFacilities'}), + ASN1F_ENUMERATED("ambientOrRoadConditionPictogram", 0, {0: 'ambientCondition', 1: 'roadCondition'}), uper_extensible=True + ) + + +class ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_pictogramCategoryCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("nature", 1, uper_min=1, uper_max=9, oer_unsigned=True), + ASN1F_INTEGER("serialNumber", 0, uper_min=0, uper_max=99, oer_unsigned=True) + ) + + +class Ext2(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("content", 16512, uper_min=16512, uper_max=2113663, oer_unsigned=True), + ASN1F_INTEGER("extension", 2113664, uper_min=2113664, uper_max=270549119, oer_unsigned=True) + ) + + +class ReferencePosition(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("latitude", 0, uper_min=-900000000, uper_max=900000001), + ASN1F_INTEGER("longitude", 0, uper_min=-1800000000, uper_max=1800000001), + ASN1F_PACKET("positionConfidenceEllipse", PosConfidenceEllipse(), PosConfidenceEllipse), + ASN1F_PACKET("altitude", Altitude(), Altitude) + ) + + +class HighFrequencyContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", BasicVehicleContainerHighFrequency(), + BasicVehicleContainerHighFrequency, + RSUContainerHighFrequency, uper_extensible=True + ) + + +class LowFrequencyContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", BasicVehicleContainerLowFrequency(), + BasicVehicleContainerLowFrequency, uper_extensible=True + ) + + +class SpecialVehicleContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", PublicTransportContainer(), + PublicTransportContainer, + SpecialTransportContainer, + DangerousGoodsContainer, + RoadWorksContainerBasic, + RescueContainer, + EmergencyContainer, + SafetyCarContainer, uper_extensible=True + ) + + +class BasicContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("stationType", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_PACKET("referencePosition", ReferencePosition(), ReferencePosition), uper_extensible=True + ) + + +class ManagementContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("actionID", ActionID(), ActionID), + ASN1F_INTEGER("detectionTime", 0, uper_min=0, uper_max=4398046511103, oer_unsigned=True), + ASN1F_INTEGER("referenceTime", 0, uper_min=0, uper_max=4398046511103, oer_unsigned=True), + ASN1F_optional(ASN1F_ENUMERATED("termination", 0, {0: 'isCancellation', 1: 'isNegation'})), + ASN1F_PACKET("eventPosition", ReferencePosition(), ReferencePosition), + ASN1F_optional(ASN1F_ENUMERATED("relevanceDistance", 0, {0: 'lessThan50m', 1: 'lessThan100m', 2: 'lessThan200m', 3: 'lessThan500m', 4: 'lessThan1000m', 5: 'lessThan5km', 6: 'lessThan10km', 7: 'over10km'})), + ASN1F_optional(ASN1F_ENUMERATED("relevanceTrafficDirection", 0, {0: 'allTrafficDirections', 1: 'upstreamTraffic', 2: 'downstreamTraffic', 3: 'oppositeTraffic'})), + ASN1F_DEFAULT( + ASN1F_INTEGER("validityDuration", 600, uper_min=0, uper_max=86400, oer_unsigned=True), + 600, + ), + ASN1F_optional(ASN1F_INTEGER("transmissionInterval", None, uper_min=1, uper_max=10000, oer_unsigned=True)), + ASN1F_INTEGER("stationType", 0, uper_min=0, uper_max=255, oer_unsigned=True), uper_extensible=True + ) + + +class RoadWorksContainerExtended(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_FLAGS("lightBarSirenInUse", None, ['lightBarActivated', 'sirenActivated'], uper_min=2, uper_max=2)), + ASN1F_optional(ASN1F_PACKET("closedLanes", None, ClosedLanes)), + ASN1F_optional(ASN1F_SEQUENCE_OF("restriction", None, ASN1F_INTEGER, uper_min=1, uper_max=3, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("speedLimit", None, uper_min=1, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_PACKET("incidentIndication", None, CauseCode)), + ASN1F_optional(ASN1F_SEQUENCE_OF("recommendedPath", None, ReferencePosition, uper_min=1, uper_max=40)), + ASN1F_optional(ASN1F_PACKET("startingPointSpeedLimit", None, DeltaReferencePosition)), + ASN1F_optional(ASN1F_ENUMERATED("trafficFlowRule", 0, {0: 'noPassing', 1: 'noPassingForTrucks', 2: 'passToRight', 3: 'passToLeft'})), + ASN1F_optional(ASN1F_SEQUENCE_OF("referenceDenms", None, ActionID, uper_min=1, uper_max=8, uper_extensible=True)) + ) + + +class AlacarteContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("lanePosition", None, uper_min=-1, uper_max=14)), + ASN1F_optional(ASN1F_PACKET("impactReduction", None, ImpactReductionContainer)), + ASN1F_optional(ASN1F_INTEGER("externalTemperature", None, uper_min=-60, uper_max=67)), + ASN1F_optional(ASN1F_PACKET("roadWorks", None, RoadWorksContainerExtended)), + ASN1F_optional(ASN1F_ENUMERATED("positioningSolution", 0, {0: 'noPositioningSolution', 1: 'sGNSS', 2: 'dGNSS', 3: 'sGNSSplusDR', 4: 'dGNSSplusDR', 5: 'dR'})), + ASN1F_optional(ASN1F_PACKET("stationaryVehicle", None, StationaryVehicleContainer)), uper_extensible=True + ) + + +class InternationalSign_applicablePeriod(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_PACKET("year", None, ITS_Inline_InternationalSign_applicablePeriod_year)), + ASN1F_optional(ASN1F_PACKET("month_day", None, ITS_Inline_InternationalSign_applicablePeriod_month_day)), + ASN1F_optional(ASN1F_FLAGS("repeatingPeriodDayTypes", None, ['national-holiday', 'even-days', 'odd-days', 'market-day'], uper_min=4, uper_max=4)), + ASN1F_optional(ASN1F_PACKET("hourMinutes", None, ITS_Inline_InternationalSign_applicablePeriod_hourMinutes)), + ASN1F_optional(ASN1F_FLAGS("dateRangeOfWeek", None, ['unused', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'], uper_min=8, uper_max=8)), + ASN1F_optional(ASN1F_PACKET("durationHourMinute", None, HoursMinutes)) + ) + + +class InternationalSign_applicableVehicleDimensions(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_PACKET("vehicleHeight", None, Distance)), + ASN1F_optional(ASN1F_PACKET("vehicleWidth", None, Distance)), + ASN1F_optional(ASN1F_PACKET("vehicleLength", None, Distance)), + ASN1F_optional(ASN1F_PACKET("vehicleWeight", None, Weight)) + ) + + +class InternationalSign_section(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_PACKET("startingPointLength", None, Distance)), + ASN1F_optional(ASN1F_PACKET("continuityLength", None, Distance)) + ) + + +class ITS_Inline_GddStructure_pictogramCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_STRING("countryCode", None, size_len=2)), + ASN1F_PACKET("serviceCategoryCode", ITS_Inline_ITS_Inline_GddStructure_pictogramCode_serviceCategoryCode(), ITS_Inline_ITS_Inline_GddStructure_pictogramCode_serviceCategoryCode), + ASN1F_PACKET("pictogramCategoryCode", ITS_Inline_ITS_Inline_GddStructure_pictogramCode_pictogramCategoryCode(), ITS_Inline_ITS_Inline_GddStructure_pictogramCode_pictogramCategoryCode) + ) + + +class DieselEmissionValues(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("particulate", ITS_Inline_DieselEmissionValues_particulate(), ITS_Inline_DieselEmissionValues_particulate), + ASN1F_INTEGER("absorptionCoeff", 0, uper_min=0, uper_max=65535, oer_unsigned=True) + ) + + +class ComputedLane(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("referenceLaneId", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_PACKET("offsetXaxis", ITS_Inline_ComputedLane_offsetXaxis(), ITS_Inline_ComputedLane_offsetXaxis), + ASN1F_PACKET("offsetYaxis", ITS_Inline_ComputedLane_offsetYaxis(), ITS_Inline_ComputedLane_offsetYaxis), + ASN1F_optional(ASN1F_INTEGER("rotateXY", None, uper_min=0, uper_max=28800, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("scaleXaxis", None, uper_min=-2048, uper_max=2047)), + ASN1F_optional(ASN1F_INTEGER("scaleYaxis", None, uper_min=-2048, uper_max=2047)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class Connection(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("connectingLane", ConnectingLane(), ConnectingLane), + ASN1F_optional(ASN1F_PACKET("remoteIntersection", None, IntersectionReferenceID)), + ASN1F_optional(ASN1F_INTEGER("signalGroup", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("userClass", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("connectionID", None, uper_min=0, uper_max=255, oer_unsigned=True)) + ) + + +class LaneAttributes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("directionalUse", '00', ['ingressPath', 'egressPath'], uper_min=2, uper_max=2), + ASN1F_FLAGS("sharedWith", '0000000000', ['overlappingLaneDescriptionProvided', 'multipleLanesTreatedAsOneLane', 'otherNonMotorizedTrafficTypes', 'individualMotorizedVehicleTraffic', 'busVehicleTraffic', 'taxiVehicleTraffic', 'pedestriansTraffic', 'cyclistVehicleTraffic', 'trackedVehicleTraffic', 'pedestrianTraffic'], uper_min=10, uper_max=10), + ASN1F_PACKET("laneType", LaneTypeAttributes(), LaneTypeAttributes), + ASN1F_optional(ASN1F_STRING("regional", b"")) + ) + + +class LaneDataAttribute(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("pathEndPointAngle", 0, uper_min=-150, uper_max=150), + ASN1F_INTEGER("laneCrownPointCenter", 0, uper_min=-128, uper_max=127), + ASN1F_INTEGER("laneCrownPointLeft", 0, uper_min=-128, uper_max=127), + ASN1F_INTEGER("laneCrownPointRight", 0, uper_min=-128, uper_max=127), + ASN1F_INTEGER("laneAngle", 0, uper_min=-180, uper_max=180), + ASN1F_STRING("speedLimits", 0, uper_min=1, uper_max=9), uper_extensible=True + ) + + +class MovementEvent(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("eventState", 0, {0: 'unavailable', 1: 'dark', 2: 'stop-Then-Proceed', 3: 'stop-And-Remain', 4: 'pre-Movement', 5: 'permissive-Movement-Allowed', 6: 'protected-Movement-Allowed', 7: 'permissive-clearance', 8: 'protected-clearance', 9: 'caution-Conflicting-Traffic'}), + ASN1F_optional(ASN1F_PACKET("timing", None, TimeChangeDetails)), + ASN1F_optional(ASN1F_SEQUENCE_OF("speeds", None, AdvisorySpeed, uper_min=1, uper_max=16)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class MovementState(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_IA5_STRING("movementName", None, uper_min=1, uper_max=63)), + ASN1F_INTEGER("signalGroup", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_SEQUENCE_OF("state_time_speed", [MovementEvent()], MovementEvent, uper_min=1, uper_max=16), + ASN1F_optional(ASN1F_SEQUENCE_OF("maneuverAssistList", None, ConnectionManeuverAssist, uper_min=1, uper_max=16)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class NodeAttributeSetXY(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("localNode", None, ASN1F_ENUMERATED("item", 0, {0: 'reserved', 1: 'stopLine', 2: 'roundedCapStyleA', 3: 'roundedCapStyleB', 4: 'mergePoint', 5: 'divergePoint', 6: 'downstreamStopLine', 7: 'downstreamStartNode', 8: 'closedToTraffic', 9: 'safeIsland', 10: 'curbPresentAtStepOff', 11: 'hydrantPresent'}), uper_min=1, uper_max=8)), + ASN1F_optional(ASN1F_SEQUENCE_OF("disabled", None, ASN1F_ENUMERATED("item", 0, {0: 'reserved', 1: 'doNotBlock', 2: 'whiteLine', 3: 'mergingLaneLeft', 4: 'mergingLaneRight', 5: 'curbOnLeft', 6: 'curbOnRight', 7: 'loadingzoneOnLeft', 8: 'loadingzoneOnRight', 9: 'turnOutPointOnLeft', 10: 'turnOutPointOnRight', 11: 'adjacentParkingOnLeft', 12: 'adjacentParkingOnRight', 13: 'adjacentBikeLaneOnLeft', 14: 'adjacentBikeLaneOnRight', 15: 'sharedBikeLane', 16: 'bikeBoxInFront', 17: 'transitStopOnLeft', 18: 'transitStopOnRight', 19: 'transitStopInLane', 20: 'sharedWithTrackedVehicle', 21: 'safeIsland', 22: 'lowCurbsPresent', 23: 'rumbleStripPresent', 24: 'audibleSignalingPresent', 25: 'adaptiveTimingPresent', 26: 'rfSignalRequestPresent', 27: 'partialCurbIntrusion', 28: 'taperToLeft', 29: 'taperToRight', 30: 'taperToCenterLine', 31: 'parallelParking', 32: 'headInParking', 33: 'freeParking', 34: 'timeRestrictionsOnParking', 35: 'costToPark', 36: 'midBlockCurbPresent', 37: 'unEvenPavementPresent'}), uper_min=1, uper_max=8)), + ASN1F_optional(ASN1F_SEQUENCE_OF("enabled", None, ASN1F_ENUMERATED("item", 0, {0: 'reserved', 1: 'doNotBlock', 2: 'whiteLine', 3: 'mergingLaneLeft', 4: 'mergingLaneRight', 5: 'curbOnLeft', 6: 'curbOnRight', 7: 'loadingzoneOnLeft', 8: 'loadingzoneOnRight', 9: 'turnOutPointOnLeft', 10: 'turnOutPointOnRight', 11: 'adjacentParkingOnLeft', 12: 'adjacentParkingOnRight', 13: 'adjacentBikeLaneOnLeft', 14: 'adjacentBikeLaneOnRight', 15: 'sharedBikeLane', 16: 'bikeBoxInFront', 17: 'transitStopOnLeft', 18: 'transitStopOnRight', 19: 'transitStopInLane', 20: 'sharedWithTrackedVehicle', 21: 'safeIsland', 22: 'lowCurbsPresent', 23: 'rumbleStripPresent', 24: 'audibleSignalingPresent', 25: 'adaptiveTimingPresent', 26: 'rfSignalRequestPresent', 27: 'partialCurbIntrusion', 28: 'taperToLeft', 29: 'taperToRight', 30: 'taperToCenterLine', 31: 'parallelParking', 32: 'headInParking', 33: 'freeParking', 34: 'timeRestrictionsOnParking', 35: 'costToPark', 36: 'midBlockCurbPresent', 37: 'unEvenPavementPresent'}), uper_min=1, uper_max=8)), + ASN1F_optional(ASN1F_SEQUENCE_OF("data", None, LaneDataAttribute, uper_min=1, uper_max=8)), + ASN1F_optional(ASN1F_INTEGER("dWidth", None, uper_min=-512, uper_max=511)), + ASN1F_optional(ASN1F_INTEGER("dElevation", None, uper_min=-512, uper_max=511)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class NodeXY(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("delta", NodeOffsetPointXY(), NodeOffsetPointXY), + ASN1F_optional(ASN1F_PACKET("attributes", None, NodeAttributeSetXY)), uper_extensible=True + ) + + +class RestrictionClassAssignment(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_SEQUENCE_OF("users", [RestrictionUserType()], RestrictionUserType, uper_min=1, uper_max=16) + ) + + +class GlcPart(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("zoneId", 1, uper_min=1, uper_max=32, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("laneNumber", None, uper_min=-1, uper_max=14)), + ASN1F_optional(ASN1F_INTEGER("zoneExtension", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("zoneHeading", None, uper_min=0, uper_max=3601, oer_unsigned=True)), + ASN1F_optional(ASN1F_PACKET("zone", None, Zone)), uper_extensible=True + ) + + +class RscPart(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("detectionZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_SEQUENCE_OF("relevanceZoneIds", [], ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True), + ASN1F_optional(ASN1F_INTEGER("direction", None, uper_min=0, uper_max=3, oer_unsigned=True)), + ASN1F_optional(ASN1F_PACKET("roadSurfaceStaticCharacteristics", None, RoadSurfaceStaticCharacteristics)), + ASN1F_optional(ASN1F_PACKET("roadSurfaceDynamicCharacteristics", None, RoadSurfaceDynamicCharacteristics)) + ) + + +class LayoutContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("layoutId", 1, uper_min=1, uper_max=4, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("height", None, uper_min=10, uper_max=73, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("width", None, uper_min=10, uper_max=265, oer_unsigned=True)), + ASN1F_SEQUENCE_OF("layoutComponents", [LayoutComponent()], LayoutComponent, uper_min=1, uper_max=4, uper_extensible=True), uper_extensible=True + ) + + +class MapLocationContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("reference", MapReference(), MapReference), + ASN1F_SEQUENCE_OF("parts", [MlcPart()], MlcPart, uper_min=1, uper_max=16, uper_extensible=True) + ) + + +class VcCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("roadSignClass", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_INTEGER("roadSignCode", 1, uper_min=1, uper_max=64, oer_unsigned=True), + ASN1F_INTEGER("vcOption", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_optional(ASN1F_SEQUENCE_OF("validity", None, InternationalSign_applicablePeriod, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("value", None, uper_min=0, uper_max=65535, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("unit", None, uper_min=0, uper_max=15, oer_unsigned=True)) + ) + + +class ITS_Inline_ISO14823Code_pictogramCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_STRING("countryCode", None, size_len=2)), + ASN1F_PACKET("serviceCategoryCode", ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_serviceCategoryCode(), ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_serviceCategoryCode), + ASN1F_PACKET("pictogramCategoryCode", ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_pictogramCategoryCode(), ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_pictogramCategoryCode) + ) + + +class ITS_Inline_VehicleCharacteristicsRanges_limits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("numberOfAxles", 0, uper_min=0, uper_max=7, oer_unsigned=True), + VehicleDimensions, + VehicleWeightLimits, + AxleWeightLimits, + PassengerCapacity, + ExhaustEmissionValues, + DieselEmissionValues, + SoundLevel, uper_extensible=True + ) + + +class Ext1(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("content", 128, uper_min=128, uper_max=16511, oer_unsigned=True), + Ext2 + ) + + +class CamParameters(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("basicContainer", BasicContainer(), BasicContainer), + ASN1F_PACKET("highFrequencyContainer", HighFrequencyContainer(), HighFrequencyContainer), + ASN1F_optional(ASN1F_PACKET("lowFrequencyContainer", None, LowFrequencyContainer)), + ASN1F_optional(ASN1F_PACKET("specialVehicleContainer", None, SpecialVehicleContainer)), uper_extensible=True + ) + + +class DecentralizedEnvironmentalNotificationMessage(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("management", ManagementContainer(), ManagementContainer), + ASN1F_optional(ASN1F_PACKET("situation", None, SituationContainer)), + ASN1F_optional(ASN1F_PACKET("location", None, LocationContainer)), + ASN1F_optional(ASN1F_PACKET("alacarte", None, AlacarteContainer)) + ) + + +class IntersectionState(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_IA5_STRING("name_", None, uper_min=1, uper_max=63)), + ASN1F_PACKET("id", IntersectionReferenceID(), IntersectionReferenceID), + ASN1F_INTEGER("revision", 0, uper_min=0, uper_max=127, oer_unsigned=True), + ASN1F_FLAGS("status", '0000000000000000', ['manualControlIsEnabled', 'stopTimeIsActivated', 'failureFlash', 'preemptIsActive', 'signalPriorityIsActive', 'fixedTimeOperation', 'trafficDependentOperation', 'standbyOperation', 'failureMode', 'off', 'recentMAPmessageUpdate', 'recentChangeInMAPassignedLanesIDsUsed', 'noValidMAPisAvailableAtThisTime', 'noValidSPATisAvailableAtThisTime'], uper_min=16, uper_max=16), + ASN1F_optional(ASN1F_INTEGER("moy", None, uper_min=0, uper_max=527040, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("timeStamp", None, uper_min=0, uper_max=65535, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("enabledLanes", None, ASN1F_INTEGER, uper_min=1, uper_max=16)), + ASN1F_SEQUENCE_OF("states", [MovementState()], MovementState, uper_min=1, uper_max=255), + ASN1F_optional(ASN1F_SEQUENCE_OF("maneuverAssistList", None, ConnectionManeuverAssist, uper_min=1, uper_max=16)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class NodeListXY(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_STRING("nodes", 0, uper_min=2, uper_max=63), + ComputedLane, uper_extensible=True + ) + + +class GeographicLocationContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("referencePosition", ReferencePosition(), ReferencePosition), + ASN1F_optional(ASN1F_INTEGER("referencePositionTime", None, uper_min=0, uper_max=4398046511103, oer_unsigned=True)), + ASN1F_optional(ASN1F_PACKET("referencePositionHeading", None, Heading)), + ASN1F_optional(ASN1F_PACKET("referencePositionSpeed", None, Speed)), + ASN1F_SEQUENCE_OF("parts", [GlcPart()], GlcPart, uper_min=1, uper_max=16, uper_extensible=True), uper_extensible=True + ) + + +class VehicleCharacteristicsRanges(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("comparisonOperator", 0, uper_min=0, uper_max=3, oer_unsigned=True), + ASN1F_PACKET("limits", ITS_Inline_VehicleCharacteristicsRanges_limits(), ITS_Inline_VehicleCharacteristicsRanges_limits) + ) + + +class VarLengthNumber(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", None, + ASN1F_INTEGER("content", 0, uper_min=0, uper_max=127, oer_unsigned=True), + Ext1 + ) + + +class CoopAwareness(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("generationDeltaTime", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_PACKET("camParameters", CamParameters(), CamParameters) + ) + + +class SPAT(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("timeStamp", None, uper_min=0, uper_max=527040, oer_unsigned=True)), + ASN1F_optional(ASN1F_IA5_STRING("name_", None, uper_min=1, uper_max=63)), + ASN1F_SEQUENCE_OF("intersections", [IntersectionState()], IntersectionState, uper_min=1, uper_max=32), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class GenericLane(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("laneID", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_optional(ASN1F_IA5_STRING("name_", None, uper_min=1, uper_max=63)), + ASN1F_optional(ASN1F_INTEGER("ingressApproach", None, uper_min=0, uper_max=15, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("egressApproach", None, uper_min=0, uper_max=15, oer_unsigned=True)), + ASN1F_PACKET("laneAttributes", LaneAttributes(), LaneAttributes), + ASN1F_optional(ASN1F_FLAGS("maneuvers", None, ['maneuverStraightAllowed', 'maneuverLeftAllowed', 'maneuverRightAllowed', 'maneuverUTurnAllowed', 'maneuverLeftTurnOnRedAllowed', 'maneuverRightTurnOnRedAllowed', 'maneuverLaneChangeAllowed', 'maneuverNoStoppingAllowed', 'yieldAllwaysRequired', 'goWithHalt', 'caution', 'reserved1'], uper_min=12, uper_max=12)), + ASN1F_PACKET("nodeList", NodeListXY(), NodeListXY), + ASN1F_optional(ASN1F_SEQUENCE_OF("connectsTo", None, Connection, uper_min=1, uper_max=16)), + ASN1F_optional(ASN1F_SEQUENCE_OF("overlays", None, ASN1F_INTEGER, uper_min=1, uper_max=5)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class IntersectionGeometry(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_IA5_STRING("name_", None, uper_min=1, uper_max=63)), + ASN1F_PACKET("id", IntersectionReferenceID(), IntersectionReferenceID), + ASN1F_INTEGER("revision", 0, uper_min=0, uper_max=127, oer_unsigned=True), + ASN1F_PACKET("refPoint", Position3D(), Position3D), + ASN1F_optional(ASN1F_INTEGER("laneWidth", None, uper_min=0, uper_max=32767, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("speedLimits", None, RegulatorySpeedLimit, uper_min=1, uper_max=9)), + ASN1F_SEQUENCE_OF("laneSet", [GenericLane()], GenericLane, uper_min=1, uper_max=255), + ASN1F_optional(ASN1F_SEQUENCE_OF("preemptPriorityData", None, SignalControlZone, uper_min=1, uper_max=32)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class RoadSegment(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_IA5_STRING("name_", None, uper_min=1, uper_max=63)), + ASN1F_PACKET("id", RoadSegmentReferenceID(), RoadSegmentReferenceID), + ASN1F_INTEGER("revision", 0, uper_min=0, uper_max=127, oer_unsigned=True), + ASN1F_PACKET("refPoint", Position3D(), Position3D), + ASN1F_optional(ASN1F_INTEGER("laneWidth", None, uper_min=0, uper_max=32767, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("speedLimits", None, RegulatorySpeedLimit, uper_min=1, uper_max=9)), + ASN1F_SEQUENCE_OF("roadLaneSet", [GenericLane()], GenericLane, uper_min=1, uper_max=255), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class TractorCharacteristics(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("equalTo", None, VehicleCharacteristicsFixValues, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("notEqualTo", None, VehicleCharacteristicsFixValues, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("ranges", None, VehicleCharacteristicsRanges, uper_min=1, uper_max=4, uper_extensible=True)) + ) + + +class IVI_TrailerCharacteristics(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("equalTo", None, VehicleCharacteristicsFixValues, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("notEqualTo", None, VehicleCharacteristicsFixValues, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("ranges", None, VehicleCharacteristicsRanges, uper_min=1, uper_max=4, uper_extensible=True)) + ) + + +class MapData(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("timeStamp", None, uper_min=0, uper_max=527040, oer_unsigned=True)), + ASN1F_INTEGER("msgIssueRevision", 0, uper_min=0, uper_max=127, oer_unsigned=True), + ASN1F_optional(ASN1F_ENUMERATED("layerType", 0, {0: 'none', 1: 'mixedContent', 2: 'generalMapData', 3: 'intersectionData', 4: 'curveData', 5: 'roadwaySectionData', 6: 'parkingAreaData', 7: 'sharedLaneData'})), + ASN1F_optional(ASN1F_INTEGER("layerID", None, uper_min=0, uper_max=100, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("intersections", None, IntersectionGeometry, uper_min=1, uper_max=32)), + ASN1F_optional(ASN1F_SEQUENCE_OF("roadSegments", None, RoadSegment, uper_min=1, uper_max=32)), + ASN1F_optional(ASN1F_PACKET("dataParameters", None, DataParameters)), + ASN1F_optional(ASN1F_SEQUENCE_OF("restrictionList", None, RestrictionClassAssignment, uper_min=1, uper_max=254)), + ASN1F_optional(ASN1F_SEQUENCE_OF("regional", None, ASN1F_STRING)), uper_extensible=True + ) + + +class CompleteVehicleCharacteristics(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_PACKET("tractor", None, TractorCharacteristics)), + ASN1F_optional(ASN1F_SEQUENCE_OF("trailer", None, IVI_TrailerCharacteristics, uper_min=1, uper_max=3)), + ASN1F_optional(ASN1F_PACKET("train", None, TractorCharacteristics)) + ) + + +class LaneInformation(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("laneNumber", 0, uper_min=-1, uper_max=14), + ASN1F_INTEGER("direction", 0, uper_min=0, uper_max=3, oer_unsigned=True), + ASN1F_optional(ASN1F_PACKET("validity", None, InternationalSign_applicablePeriod)), + ASN1F_INTEGER("laneType", 0, uper_min=0, uper_max=31, oer_unsigned=True), + ASN1F_optional(ASN1F_PACKET("laneTypeQualifier", None, CompleteVehicleCharacteristics)), + ASN1F_INTEGER("laneStatus", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("laneWidth", None, uper_min=0, uper_max=1023, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("detectionZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("relevanceZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_PACKET("laneCharacteristics", None, LaneCharacteristics)), + ASN1F_optional(ASN1F_PACKET("laneSurfaceStaticCharacteristics", None, RoadSurfaceStaticCharacteristics)), + ASN1F_optional(ASN1F_PACKET("laneSurfaceDynamicCharacteristics", None, RoadSurfaceDynamicCharacteristics)), uper_extensible=True + ) + + +class RccPart(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("relevanceZoneIds", [], ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True), + ASN1F_ENUMERATED("roadType", 0, {0: 'urban-NoStructuralSeparationToOppositeLanes', 1: 'urban-WithStructuralSeparationToOppositeLanes', 2: 'nonUrban-NoStructuralSeparationToOppositeLanes', 3: 'nonUrban-WithStructuralSeparationToOppositeLanes'}), + ASN1F_SEQUENCE_OF("laneConfiguration", [LaneInformation()], LaneInformation, uper_min=1, uper_max=16, uper_extensible=True), uper_extensible=True + ) + + +class TcPart(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("detectionZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_SEQUENCE_OF("relevanceZoneIds", [], ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True), + ASN1F_optional(ASN1F_INTEGER("direction", None, uper_min=0, uper_max=3, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("driverAwarenessZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("minimumAwarenessTime", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("applicableLanes", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("layoutId", None, uper_min=1, uper_max=4, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("preStoredlayoutId", None, uper_min=1, uper_max=64, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("text", None, Text, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_STRING("data", b''), + ASN1F_INTEGER("iviType", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("laneStatus", None, uper_min=0, uper_max=7, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("vehicleCharacteristics", None, CompleteVehicleCharacteristics, uper_min=1, uper_max=8, uper_extensible=True)), uper_extensible=True + ) + + +class IviContainer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", GeographicLocationContainer(), + GeographicLocationContainer, + ASN1F_STRING("giv", 0, uper_min=1, uper_max=16), + ASN1F_STRING("rcc", 0, uper_min=1, uper_max=16), + ASN1F_STRING("tc", 0, uper_min=1, uper_max=16), + LayoutContainer, + ASN1F_STRING("avc", 0, uper_min=1, uper_max=16), + MapLocationContainer, + ASN1F_STRING("rsc", 0, uper_min=1, uper_max=16), uper_extensible=True + ) + + +class IviStructure(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("mandatory", IviManagementContainer(), IviManagementContainer), + ASN1F_optional(ASN1F_SEQUENCE_OF("optional", None, IviContainer, uper_min=1, uper_max=8, uper_extensible=True)) + ) + + +class GddAttribute(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", InternationalSign_applicablePeriod(), + InternationalSign_applicablePeriod, + InternationalSign_applicablePeriod, + ASN1F_INTEGER("dfl", 1, uper_min=1, uper_max=8, oer_unsigned=True), + InternationalSign_applicableVehicleDimensions, + InternationalSign_speedLimits, + ASN1F_INTEGER("roi", 1, uper_min=1, uper_max=32, oer_unsigned=True), + Distance, + ASN1F_STRING("ddd", b''), + InternationalSign_section, + ASN1F_INTEGER("nol", 0, uper_min=0, uper_max=99, oer_unsigned=True) + ) + + +class GddStructure(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("pictogramCode", ITS_Inline_GddStructure_pictogramCode(), ITS_Inline_GddStructure_pictogramCode), + ASN1F_optional(ASN1F_SEQUENCE_OF("attributes", None, GddAttribute, uper_min=1, uper_max=8, uper_extensible=True)) + ) + + +class DestinationPlace(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("destType", 0, uper_min=0, uper_max=15, oer_unsigned=True), + ASN1F_optional(ASN1F_PACKET("destRSCode", None, GddStructure)), + ASN1F_optional(ASN1F_STRING("destBlob", None)), + ASN1F_optional(ASN1F_INTEGER("placeNameIdentification", None, uper_min=1, uper_max=999, oer_unsigned=True)), + ASN1F_optional(ASN1F_UTF8_STRING("placeNameText", None)) + ) + + +class DDD_IO(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("arrowDirection", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_optional(ASN1F_SEQUENCE_OF("destPlace", None, DestinationPlace, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("destRoad", None, DestinationRoad, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("roadNumberIdentifier", None, uper_min=1, uper_max=999, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("streetName", None, uper_min=1, uper_max=999, oer_unsigned=True)), + ASN1F_optional(ASN1F_UTF8_STRING("streetNameText", None)), + ASN1F_optional(ASN1F_PACKET("distanceToDivergingPoint", None, DistanceOrDuration)), + ASN1F_optional(ASN1F_PACKET("distanceToDestinationPlace", None, DistanceOrDuration)) + ) + + +class InternationalSign_destinationInformation(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("junctionDirection", None, uper_min=1, uper_max=128, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("roundaboutCwDirection", None, uper_min=1, uper_max=128, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("roundaboutCcwDirection", None, uper_min=1, uper_max=128, oer_unsigned=True)), + ASN1F_SEQUENCE_OF("ioList", [DDD_IO()], DDD_IO, uper_min=1, uper_max=8, uper_extensible=True) + ) + + +class ISO14823Attribute(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", InternationalSign_applicablePeriod(), + InternationalSign_applicablePeriod, + InternationalSign_applicablePeriod, + ASN1F_INTEGER("dfl", 1, uper_min=1, uper_max=8, oer_unsigned=True), + InternationalSign_applicableVehicleDimensions, + InternationalSign_speedLimits, + ASN1F_INTEGER("roi", 1, uper_min=1, uper_max=32, oer_unsigned=True), + Distance, + InternationalSign_destinationInformation + ) + + +class ISO14823Code(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("pictogramCode", ITS_Inline_ISO14823Code_pictogramCode(), ITS_Inline_ISO14823Code_pictogramCode), + ASN1F_optional(ASN1F_SEQUENCE_OF("attributes", None, ISO14823Attribute, uper_min=1, uper_max=8, uper_extensible=True)) + ) + + +class AnyCatalogue(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("owner", Provider(), Provider), + ASN1F_INTEGER("version", 0, uper_min=0, uper_max=255, oer_unsigned=True), + ASN1F_INTEGER("pictogramCode", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("value", None, uper_min=0, uper_max=65535, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("unit", None, uper_min=0, uper_max=15, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("attributes", None, ISO14823Attribute, uper_min=1, uper_max=8, uper_extensible=True)) + ) + + +class ITS_Inline_RSCode_code(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "root", VcCode(), + VcCode, + ISO14823Code, + ASN1F_INTEGER("itisCodes", 0, uper_min=0, uper_max=65535, oer_unsigned=True), + AnyCatalogue, uper_extensible=True + ) + + +class RSCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("layoutComponentId", None, uper_min=1, uper_max=4, oer_unsigned=True)), + ASN1F_PACKET("code", ITS_Inline_RSCode_code(), ITS_Inline_RSCode_code) + ) + + +class GicPart(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("detectionZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_PACKET("its_Rrid", None, VarLengthNumber)), + ASN1F_optional(ASN1F_SEQUENCE_OF("relevanceZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("direction", None, uper_min=0, uper_max=3, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("driverAwarenessZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("minimumAwarenessTime", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("applicableLanes", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_INTEGER("iviType", 0, uper_min=0, uper_max=7, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("iviPurpose", None, uper_min=0, uper_max=3, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("laneStatus", None, uper_min=0, uper_max=7, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("vehicleCharacteristics", None, CompleteVehicleCharacteristics, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_INTEGER("driverCharacteristics", None, uper_min=0, uper_max=3, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("layoutId", None, uper_min=1, uper_max=4, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("preStoredlayoutId", None, uper_min=1, uper_max=64, oer_unsigned=True)), + ASN1F_SEQUENCE_OF("roadSignCodes", [RSCode()], RSCode, uper_min=1, uper_max=4, uper_extensible=True), + ASN1F_optional(ASN1F_SEQUENCE_OF("extraText", None, Text, uper_min=1, uper_max=4, uper_extensible=True)), uper_extensible=True + ) + + +class AutomatedVehicleRule(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("priority", 0, uper_min=0, uper_max=2, oer_unsigned=True), + ASN1F_SEQUENCE_OF("allowedSaeAutomationLevels", [], ASN1F_INTEGER, uper_min=1, uper_max=5), + ASN1F_optional(ASN1F_INTEGER("minGapBetweenVehicles", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("recGapBetweenVehicles", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("automatedVehicleMaxSpeedLimit", None, uper_min=0, uper_max=16383, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("automatedVehicleMinSpeedLimit", None, uper_min=0, uper_max=16383, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("automatedVehicleSpeedRecommendation", None, uper_min=0, uper_max=16383, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("roadSignCodes", None, RSCode, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("extraText", None, Text, uper_min=1, uper_max=4, uper_extensible=True)), uper_extensible=True + ) + + +class PlatooningRule(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("priority", 0, uper_min=0, uper_max=2, oer_unsigned=True), + ASN1F_SEQUENCE_OF("allowedSaeAutomationLevels", [], ASN1F_INTEGER, uper_min=1, uper_max=5), + ASN1F_optional(ASN1F_INTEGER("maxNoOfVehicles", None, uper_min=2, uper_max=64, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("maxLenghtOfPlatoon", None, uper_min=1, uper_max=64, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("minGapBetweenVehicles", None, uper_min=0, uper_max=255, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("platoonMaxSpeedLimit", None, uper_min=0, uper_max=16383, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("platoonMinSpeedLimit", None, uper_min=0, uper_max=16383, oer_unsigned=True)), + ASN1F_optional(ASN1F_INTEGER("platoonSpeedRecommendation", None, uper_min=0, uper_max=16383, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("roadSignCodes", None, RSCode, uper_min=1, uper_max=4, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("extraText", None, Text, uper_min=1, uper_max=4, uper_extensible=True)), uper_extensible=True + ) + + +class AvcPart(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_SEQUENCE_OF("detectionZoneIds", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_SEQUENCE_OF("relevanceZoneIds", [], ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True), + ASN1F_optional(ASN1F_INTEGER("direction", None, uper_min=0, uper_max=3, oer_unsigned=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("applicableLanes", None, ASN1F_INTEGER, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("vehicleCharacteristics", None, CompleteVehicleCharacteristics, uper_min=1, uper_max=8, uper_extensible=True)), + ASN1F_optional(ASN1F_SEQUENCE_OF("automatedVehicleRules", None, AutomatedVehicleRule, uper_min=1, uper_max=5)), + ASN1F_optional(ASN1F_SEQUENCE_OF("platooningRules", None, PlatooningRule, uper_min=1, uper_max=5)), uper_extensible=True + ) + + +class CAM(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("header", ItsPduHeader(), ItsPduHeader), + ASN1F_PACKET("cam", CoopAwareness(), CoopAwareness) + ) + + +class DENM(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("header", ItsPduHeader(), ItsPduHeader), + ASN1F_PACKET("denm", DecentralizedEnvironmentalNotificationMessage(), DecentralizedEnvironmentalNotificationMessage) + ) + + +class IVIM(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("header", ItsPduHeader(), ItsPduHeader), + ASN1F_PACKET("ivi", IviStructure(), IviStructure) + ) + + +class SPATEM(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("header", ItsPduHeader(), ItsPduHeader), + ASN1F_PACKET("spat", SPAT(), SPAT) + ) + + +class MAPEM(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("header", ItsPduHeader(), ItsPduHeader), + ASN1F_PACKET("map", MapData(), MapData) + ) + + +CAM = CAM +DENM = DENM +IVIM = IVIM +SPATEM = SPATEM +MAPEM = MAPEM + +__all__ = [ + "CAM", + "DENM", + "IVIM", + "SPATEM", + "MAPEM", + "ItsPduHeader", + "DeltaReferencePosition", + "Altitude", + "PosConfidenceEllipse", + "PathPoint", + "PathHistory", + "PtActivation", + "CauseCode", + "Curvature", + "Heading", + "ClosedLanes", + "Speed", + "LongitudinalAcceleration", + "LateralAcceleration", + "VerticalAcceleration", + "DangerousGoodsExtended", + "VehicleIdentification", + "VehicleLength", + "SteeringWheelAngle", + "YawRate", + "ActionID", + "ProtectedCommunicationZone", + "EventPoint", + "CenDsrcTollingZone", + "BasicVehicleContainerHighFrequency", + "BasicVehicleContainerLowFrequency", + "PublicTransportContainer", + "SpecialTransportContainer", + "DangerousGoodsContainer", + "RoadWorksContainerBasic", + "RescueContainer", + "EmergencyContainer", + "SafetyCarContainer", + "RSUContainerHighFrequency", + "SituationContainer", + "LocationContainer", + "ImpactReductionContainer", + "StationaryVehicleContainer", + "EuVehicleCategoryCode", + "InternationalSign_speedLimits", + "DestinationRoad", + "Distance", + "DistanceOrDuration", + "HoursMinutes", + "MonthDay", + "Weight", + "ITS_Inline_InternationalSign_applicablePeriod_year", + "ITS_Inline_InternationalSign_applicablePeriod_month_day", + "ITS_Inline_InternationalSign_applicablePeriod_hourMinutes", + "ITS_Inline_ITS_Inline_GddStructure_pictogramCode_serviceCategoryCode", + "ITS_Inline_ITS_Inline_GddStructure_pictogramCode_pictogramCategoryCode", + "AxleWeightLimits", + "EnvironmentalCharacteristics", + "ExhaustEmissionValues", + "PassengerCapacity", + "Provider", + "SoundLevel", + "VehicleDimensions", + "VehicleWeightLimits", + "ITS_Inline_DieselEmissionValues_particulate", + "AdvisorySpeed", + "ConnectingLane", + "ConnectionManeuverAssist", + "DataParameters", + "IntersectionReferenceID", + "LaneTypeAttributes", + "Node_LLmD_64b", + "Node_XY_20b", + "Node_XY_22b", + "Node_XY_24b", + "Node_XY_26b", + "Node_XY_28b", + "Node_XY_32b", + "NodeOffsetPointXY", + "Position3D", + "RegulatorySpeedLimit", + "RestrictionUserType", + "RoadSegmentReferenceID", + "SignalControlZone", + "TimeChangeDetails", + "ITS_Inline_ComputedLane_offsetXaxis", + "ITS_Inline_ComputedLane_offsetYaxis", + "IviManagementContainer", + "MlcPart", + "AbsolutePosition", + "AbsolutePositionWAltitude", + "ComputedSegment", + "DeltaPosition", + "LaneCharacteristics", + "LayoutComponent", + "LoadType", + "MapReference", + "PolygonalLine", + "RoadSurfaceDynamicCharacteristics", + "RoadSurfaceStaticCharacteristics", + "Segment", + "Text", + "VehicleCharacteristicsFixValues", + "Zone", + "ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_serviceCategoryCode", + "ITS_Inline_ITS_Inline_ISO14823Code_pictogramCode_pictogramCategoryCode", + "Ext2", + "ReferencePosition", + "HighFrequencyContainer", + "LowFrequencyContainer", + "SpecialVehicleContainer", + "BasicContainer", + "ManagementContainer", + "RoadWorksContainerExtended", + "AlacarteContainer", + "InternationalSign_applicablePeriod", + "InternationalSign_applicableVehicleDimensions", + "InternationalSign_section", + "ITS_Inline_GddStructure_pictogramCode", + "DieselEmissionValues", + "ComputedLane", + "Connection", + "LaneAttributes", + "LaneDataAttribute", + "MovementEvent", + "MovementState", + "NodeAttributeSetXY", + "NodeXY", + "RestrictionClassAssignment", + "GlcPart", + "RscPart", + "LayoutContainer", + "MapLocationContainer", + "VcCode", + "ITS_Inline_ISO14823Code_pictogramCode", + "ITS_Inline_VehicleCharacteristicsRanges_limits", + "Ext1", + "CamParameters", + "DecentralizedEnvironmentalNotificationMessage", + "IntersectionState", + "NodeListXY", + "GeographicLocationContainer", + "VehicleCharacteristicsRanges", + "VarLengthNumber", + "CoopAwareness", + "SPAT", + "GenericLane", + "IntersectionGeometry", + "RoadSegment", + "TractorCharacteristics", + "IVI_TrailerCharacteristics", + "MapData", + "CompleteVehicleCharacteristics", + "LaneInformation", + "RccPart", + "TcPart", + "IviContainer", + "IviStructure", + "GddAttribute", + "GddStructure", + "DestinationPlace", + "DDD_IO", + "InternationalSign_destinationInformation", + "ISO14823Attribute", + "ISO14823Code", + "AnyCatalogue", + "ITS_Inline_RSCode_code", + "RSCode", + "GicPart", + "AutomatedVehicleRule", + "PlatooningRule", + "AvcPart", + "CAM", + "DENM", + "IVIM", + "SPATEM", + "MAPEM", +] diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py new file mode 100644 index 00000000000..6195ce2fbff --- /dev/null +++ b/scapy/contrib/oer.py @@ -0,0 +1,975 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +# scapy.contrib.description = ASN.1 Octet Encoding Rules (OER) +# scapy.contrib.status = loads + +""" +Octet Encoding Rules (OER) for ASN.1 + +Basic-OER as specified in ITU-T X.696 | ISO/IEC 8825-7. + +``ASN1F_SEQUENCE`` emits the preamble required by 16.2.2: a presence bit per +``ASN1F_optional``/``ASN1F_DEFAULT`` component, preceded by an extension bit +for sequences declared with ``oer_extensible=True``. Fixed size constraints +are expressed with ``size_len=`` (octets for strings, bits for BIT STRING). + +Tags declared on a field are not encoded: OER only puts a tag on the wire for +the chosen alternative of an ``ASN1F_CHOICE`` (20.2), so the ``implicit_tag=`` +and ``explicit_tag=`` of the alternatives are what selects it. + +Not supported yet: extension additions (an encoding that carries them is +refused rather than misparsed), SET, REAL, and the canonical variant (C-OER). +""" + +import struct + +from scapy.error import warning +from scapy.compat import chb, orb, bytes_encode +from scapy.utils import binrepr, inet_aton, inet_ntoa +from scapy.asn1.ber import BER_num_dec, BER_num_enc +from scapy.asn1.asn1 import ( + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) +# Re-exported: DEFAULT components are what the preamble bits describe. +from scapy.asn1fields import ASN1F_DEFAULT # noqa: F401 + +from typing import ( + Any, + AnyStr, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +################## +# OER encoding # +################## + + +class OER_Exception(Exception): + pass + + +class OER_Encoding_Error(ASN1_Encoding_Error): + def __init__(self, + msg, # type: str + encoded=None, # type: Optional[Union['OERcodec_Object[Any]', str]] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.encoded = encoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.encoded, ASN1_Object): + s += "\n### Already encoded ###\n%s" % self.encoded.strshow() + else: + s += "\n### Already encoded ###\n%r" % self.encoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +class OER_Decoding_Error(ASN1_Decoding_Error): + def __init__(self, + msg, # type: str + decoded=None, # type: Optional[Any] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.decoded = decoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.decoded, ASN1_Object): + s += "\n### Already decoded ###\n%s" % self.decoded.strshow() + else: + s += "\n### Already decoded ###\n%r" % self.decoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +# OER tag classes (bits 8-7 of the first identifier octet) +OER_CLASS_UNIVERSAL = 0x00 +OER_CLASS_APPLICATION = 0x40 +OER_CLASS_CONTEXT = 0x80 +OER_CLASS_PRIVATE = 0xc0 + + +def _OER_check_len(name, s, number_of_bytes, offset=0): + # type: (str, bytes, int, int) -> None + """Raise unless s carries number_of_bytes octets past its first offset.""" + available = len(s) - offset + if available < number_of_bytes: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % + (name, available, number_of_bytes), + remaining=s + ) + + +def OER_len_enc(ll): + # type: (int) -> bytes + if ll < 128: + return chb(ll) + encoded = [] + value = ll + while value > 0: + encoded.insert(0, value & 0xff) + value >>= 8 + if len(encoded) > 127: + raise OER_Exception( + "OER_len_enc: Length too long (%i) to be encoded" % len(encoded) + ) + return chb(0x80 | len(encoded)) + bytes(encoded) + + +def OER_len_dec(s): + # type: (bytes) -> Tuple[int, bytes] + if not s: + raise OER_Decoding_Error("OER_len_dec: got empty string", remaining=s) + tmp_len = orb(s[0]) + if not tmp_len & 0x80: + return tmp_len, s[1:] + tmp_len &= 0x7f + _OER_check_len("OER_len_dec", s, tmp_len, offset=1) + ll = 0 + for c in s[1:tmp_len + 1]: + ll <<= 8 + ll |= orb(c) + return ll, s[tmp_len + 1:] + + +def OER_signed_integer_enc(i): + # type: (int) -> bytes + # X.696 10.4: the shortest two's complement encoding. A negative value + # needs one bit less than its magnitude suggests, as -2**(8n-1) still + # fits in n octets, hence the increment before measuring. + magnitude = i + 1 if i < 0 else i + number_of_bytes = (magnitude.bit_length() + 8) // 8 + value = i & ((1 << (8 * number_of_bytes)) - 1) + return OER_len_enc(number_of_bytes) + value.to_bytes(number_of_bytes, "big") + + +def OER_signed_integer_dec(s): + # type: (bytes) -> Tuple[int, bytes] + number_of_bytes, s = OER_len_dec(s) + _OER_check_len("OER_signed_integer_dec", s, number_of_bytes) + if number_of_bytes == 0: + raise OER_Decoding_Error( + "OER_signed_integer_dec: got an empty length determinant", + remaining=s + ) + value = int.from_bytes(s[:number_of_bytes], "big") + number_of_bits = 8 * number_of_bytes + if value & (1 << (number_of_bits - 1)): + value -= (1 << number_of_bits) - 1 + value -= 1 + return value, s[number_of_bytes:] + + +def OER_unsigned_integer_enc(i): + # type: (int) -> bytes + if i < 0: + raise OER_Encoding_Error( + "OER_unsigned_integer_enc: %i is negative" % i + ) + number_of_bits = max(i.bit_length(), 1) + number_of_bytes = (number_of_bits + 7) // 8 + return OER_len_enc(number_of_bytes) + i.to_bytes(number_of_bytes, "big") + + +def OER_unsigned_integer_dec(s): + # type: (bytes) -> Tuple[int, bytes] + number_of_bytes, s = OER_len_dec(s) + _OER_check_len("OER_unsigned_integer_dec", s, number_of_bytes) + value = int.from_bytes(s[:number_of_bytes], "big") + return value, s[number_of_bytes:] + + +_OER_FIXED_FORMATS = { + True: {1: ">b", 2: ">h", 4: ">i", 8: ">q"}, + False: {1: ">B", 2: ">H", 4: ">I", 8: ">Q"}, +} + + +def OER_fixed_integer_enc(i, length, signed=True): + # type: (int, int, bool) -> bytes + fmt = _OER_FIXED_FORMATS[signed] + try: + return struct.pack(fmt[length], i) + except KeyError: + raise OER_Encoding_Error( + "OER_fixed_integer_enc: invalid length %i" % length + ) + except struct.error: + raise OER_Encoding_Error( + "OER_fixed_integer_enc: %i does not fit in %i %s octet(s)" % + (i, length, "signed" if signed else "unsigned") + ) + + +def OER_fixed_integer_dec(s, length, signed=True): + # type: (bytes, int, bool) -> Tuple[int, bytes] + _OER_check_len("OER_fixed_integer_dec", s, length) + fmt = _OER_FIXED_FORMATS[signed] + try: + return struct.unpack(fmt[length], s[:length])[0], s[length:] + except KeyError: + raise OER_Decoding_Error( + "OER_fixed_integer_dec: invalid length %i" % length, + remaining=s + ) + + +def OER_enumerated_enc(i): + # type: (int) -> bytes + if 0 <= i <= 127: + return chb(i) + body = OER_signed_integer_enc(i)[1:] + return chb(0x80 | len(body)) + body + + +def OER_enumerated_dec(s): + # type: (bytes) -> Tuple[int, bytes] + if not s: + raise OER_Decoding_Error("OER_enumerated_dec: got empty string", + remaining=s) + first = orb(s[0]) + if not (first & 0x80): + return first, s[1:] + length = first & 0x7f + _OER_check_len("OER_enumerated_dec", s, length, offset=1) + value = int.from_bytes(s[1:length + 1], "big", signed=True) + return value, s[length + 1:] + + +def OER_preamble_enc(extensible, presence): + # type: (bool, List[bool]) -> bytes + # X.696 16.2.2: an extension bit (extensible types only) followed by one + # presence bit per OPTIONAL/DEFAULT component, zero-padded to a whole + # number of octets. A type with neither has no preamble at all. + bits = [0] if extensible else [] + bits += [1 if present else 0 for present in presence] + if not bits: + return b"" + number_of_bytes = (len(bits) + 7) // 8 + value = 0 + for bit in bits: + value = (value << 1) | bit + value <<= 8 * number_of_bytes - len(bits) + return value.to_bytes(number_of_bytes, "big") + + +def OER_preamble_dec(s, extensible, number_of_optionals): + # type: (bytes, bool, int) -> Tuple[List[bool], bytes] + number_of_bits = (1 if extensible else 0) + number_of_optionals + if number_of_bits == 0: + return [], s + number_of_bytes = (number_of_bits + 7) // 8 + _OER_check_len("OER_preamble_dec", s, number_of_bytes) + value = int.from_bytes(s[:number_of_bytes], "big") + bits = [ + bool((value >> (8 * number_of_bytes - 1 - i)) & 1) + for i in range(number_of_bits) + ] + if extensible: + if bits[0]: + raise OER_Decoding_Error( + "OER_preamble_dec: extension additions are not supported", + remaining=s + ) + bits = bits[1:] + return bits, s[number_of_bytes:] + + +def OER_tag_enc(n, tag_class=OER_CLASS_CONTEXT): + # type: (int, int) -> bytes + if n < 63: + return chb(tag_class | n) + tag = bytearray([tag_class | 0x3f]) + encoded = [] + value = n + while value > 0: + encoded.append(0x80 | (value & 0x7f)) + value >>= 7 + encoded[0] &= 0x7f + encoded.reverse() + tag.extend(encoded) + return bytes(tag) + + +def OER_tag_dec(s): + # type: (bytes) -> Tuple[int, int, bytes] + if not s: + raise OER_Decoding_Error("OER_tag_dec: got empty string", remaining=s) + first = orb(s[0]) + tag_class = first & 0xc0 + tag_number = first & 0x3f + if tag_number != 0x3f: + return tag_class, tag_number, s[1:] + tag_number = 0 + i = 1 + while i < len(s): + c = orb(s[i]) + tag_number <<= 7 + tag_number |= c & 0x7f + i += 1 + if not (c & 0x80): + break + else: + raise OER_Decoding_Error("OER_tag_dec: unfinished tag", remaining=s) + return tag_class, tag_number, s[i:] + + +def _OER_tag_parts(identifier): + # type: (int) -> Tuple[int, int] + # ASN1F_* fields describe tags as BER identifier octets: class in the top + # two bits, constructed flag in 0x20 and tag number in the low five bits. + # X.696 8.7 only keeps the class and the number, so the constructed flag + # must not leak into the encoded tag number. + return identifier & 0xc0, identifier & 0x1f + + +class OERcodec_metaclass(type): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type['OERcodec_Object[Any]'] + c = cast('Type[OERcodec_Object[Any]]', + super(OERcodec_metaclass, cls).__new__(cls, name, bases, dct)) + try: + c.tag.register(c.codec, c) + except Exception: + warning("Error registering %r for %r" % (c.tag, c.codec)) + return c + + +_K = TypeVar('_K') + + +class OERcodec_Object(Generic[_K], metaclass=OERcodec_metaclass): + codec = ASN1_Codecs.OER + tag = ASN1_Class_UNIVERSAL.ANY + + @classmethod + def asn1_object(cls, val): + # type: (_K) -> ASN1_Object[_K] + return cls.tag.asn1_object(val) + + @classmethod + def check_string(cls, s): + # type: (bytes) -> None + if not s: + raise OER_Decoding_Error( + "%s: Got empty object while expecting %r" % + (cls.__name__, cls.tag), remaining=s + ) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + raise OER_Decoding_Error( + "%s: Cannot decode unknown OER type without context" % + cls.__name__, remaining=s + ) + + @classmethod + def dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + # Ignore unknown kwargs so shared field._codec_kwargs() dicts (UPER + # keys) do not TypeError on OER packets. + if not safe: + return cls.do_dec(s, context, safe, size_len, oer_unsigned) + try: + return cls.do_dec(s, context, safe, size_len, oer_unsigned) + except OER_Decoding_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + except ASN1_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + + @classmethod + def safedec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + return cls.dec( + s, context, safe=True, + size_len=size_len, oer_unsigned=oer_unsigned, + ) + + @classmethod + def enc(cls, s, size_len=0, **_kwargs): + # type: (_K, Optional[int], **Any) -> bytes + if isinstance(s, (str, bytes)): + return OERcodec_STRING.enc(s, size_len=size_len) + else: + try: + return OERcodec_INTEGER.enc(int(s), size_len=size_len) # type: ignore + except TypeError: + raise TypeError("Trying to encode an invalid value !") + + +# No tagging hook: X.696 encodes no tag for a component, whatever the tagging +# environment of the module, so a field is left alone. The only tag on the +# wire is the one of a chosen CHOICE alternative, which the CHOICE hooks below +# write themselves. +ASN1_Codecs.OER.register_stem(OERcodec_Object) + + +########################## +# OERcodec objects # +########################## + +class OERcodec_INTEGER(OERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.INTEGER + + @classmethod + def enc(cls, i, size_len=0, oer_unsigned=False, **_kwargs): + # type: (int, Optional[int], bool, **Any) -> bytes + # X.696 10: the width and the signedness follow the declared bounds of + # the type, never the value at hand, otherwise the decoder (which only + # knows the type) reads something else back. + if size_len in (1, 2, 4, 8): + return OER_fixed_integer_enc(i, size_len, signed=not oer_unsigned) + if oer_unsigned: + return OER_unsigned_integer_enc(i) + return OER_signed_integer_enc(i) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + if size_len in (1, 2, 4, 8): + x, t = OER_fixed_integer_dec( + s, size_len, signed=not oer_unsigned + ) + return cls.asn1_object(x), t + if oer_unsigned: + x, t = OER_unsigned_integer_dec(s) + else: + x, t = OER_signed_integer_dec(s) + return cls.asn1_object(x), t + + +class OERcodec_BOOLEAN(OERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + @classmethod + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes + return chb(0xff if i else 0x00) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + cls.check_string(s) + return cls.asn1_object(0 if orb(s[0]) == 0 else 1), s[1:] + + +def _oer_bitstr_to_bytes(bitstr): + # type: (bytes) -> bytes + padded = bitstr + b"0" * (-len(bitstr) % 8) + return bytes([int(padded[i:i + 8], 2) for i in range(0, len(padded), 8)]) + + +def _oer_bytes_to_bitstr(data): + # type: (bytes) -> str + return "".join(binrepr(orb(x)).zfill(8) for x in data) + + +class OERcodec_BIT_STRING(OERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[str], bytes] + if size_len: + number_of_bytes = (size_len + 7) // 8 + _OER_check_len(cls.__name__, s, number_of_bytes) + return ( + cls.tag.asn1_object( + _oer_bytes_to_bitstr(s[:number_of_bytes])[:size_len] + ), + s[number_of_bytes:], + ) + length, s = OER_len_dec(s) + if length == 0: + return cls.tag.asn1_object(""), s + _OER_check_len(cls.__name__, s, length) + unused_bits = orb(s[0]) + if safe and unused_bits > 7: + raise OER_Decoding_Error( + "OERcodec_BIT_STRING: too many unused_bits advertised", + remaining=s + ) + fs = _oer_bytes_to_bitstr(s[1:length]) + if unused_bits > 0: + fs = fs[:-unused_bits] + return cls.tag.asn1_object(fs), s[length:] + + @classmethod + def enc(cls, _s, size_len=0, **_kwargs): + # type: (AnyStr, Optional[int], **Any) -> bytes + s = bytes_encode(_s) + if size_len: + # X.696 13.3: a fixed size means the bits are written padded to a + # whole number of octets, without length or unused-bit count. + if len(s) != size_len: + raise OER_Encoding_Error( + "%s: got %i bits while expecting %i" % + (cls.__name__, len(s), size_len), + encoded=_s + ) + return _oer_bitstr_to_bytes(s) + body = chb(-len(s) % 8) + _oer_bitstr_to_bytes(s) + return OER_len_enc(len(body)) + body + + +class OERcodec_STRING(OERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.STRING + + @classmethod + def enc(cls, _s, size_len=0, **_kwargs): + # type: (Union[str, bytes], Optional[int], **Any) -> bytes + s = bytes_encode(_s) + if size_len: + # X.696 16.1: a fixed size means no length determinant. + if len(s) != size_len: + raise OER_Encoding_Error( + "%s: got %i bytes while expecting %i" % + (cls.__name__, len(s), size_len), + encoded=_s + ) + return s + return OER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + if size_len: + _OER_check_len(cls.__name__, s, size_len) + return cls.tag.asn1_object(s[:size_len]), s[size_len:] + length, s = OER_len_dec(s) + _OER_check_len(cls.__name__, s, length) + return cls.tag.asn1_object(s[:length]), s[length:] + + +class OERcodec_NULL(OERcodec_Object[None]): + tag = ASN1_Class_UNIVERSAL.NULL + + @classmethod + def enc(cls, i, **_kwargs): + # type: (Any, **Any) -> bytes + return b"" + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[None], bytes] + return cls.asn1_object(None), s + + +class OERcodec_OID(OERcodec_Object[bytes]): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def enc(cls, _oid, **_kwargs): + # type: (AnyStr, **Any) -> bytes + oid = bytes_encode(_oid) + if oid: + lst = [int(x) for x in oid.strip(b".").split(b".")] + else: + lst = list() + if len(lst) >= 2: + lst[1] += 40 * lst[0] + del lst[0] + body = b"".join(BER_num_enc(k) for k in lst) + return OER_len_enc(len(body)) + body + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[bytes], bytes] + length, s = OER_len_dec(s) + _OER_check_len(cls.__name__, s, length) + content, t = s[:length], s[length:] + lst = [] + while content: + val, content = BER_num_dec(content) + lst.append(val) + if len(lst) > 0: + lst.insert(0, lst[0] // 40) + lst[1] %= 40 + return ( + cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)), + t, + ) + + +class OERcodec_ENUMERATED(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + @classmethod + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes + return OER_enumerated_enc(i) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + x, t = OER_enumerated_dec(s) + return cls.asn1_object(x), t + + +class OERcodec_UTF8_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class OERcodec_NUMERIC_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class OERcodec_PRINTABLE_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class OERcodec_T61_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class OERcodec_VIDEOTEX_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class OERcodec_IA5_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class OERcodec_GENERAL_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERAL_STRING + + +class OERcodec_UTC_TIME(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + +class OERcodec_GENERALIZED_TIME(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class OERcodec_ISO646_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class OERcodec_UNIVERSAL_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class OERcodec_BMP_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +class OERcodec_SEQUENCE(OERcodec_Object[Union[bytes, List['OERcodec_Object[Any]']]]): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def enc(cls, _ll, **_kwargs): + # type: (Union[bytes, List[OERcodec_Object[Any]]], **Any) -> bytes + if isinstance(_ll, bytes): + return _ll + return b"".join(x.enc(cls.codec) for x in _ll) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[Union[bytes, List[Any]]], bytes] + raise OER_Decoding_Error( + "OERcodec_SEQUENCE: decoding requires schema-defined field order", + remaining=s + ) + + +class OERcodec_SET(OERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class OERcodec_IPADDRESS(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def enc(cls, ipaddr_ascii, size_len=0, **_kwargs): # type: ignore + # type: (str, Optional[int], **Any) -> bytes + try: + s = inet_aton(ipaddr_ascii) + except Exception: + raise OER_Encoding_Error("IPv4 address could not be encoded") + if size_len == len(s): + return s + return OER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, s, context=None, safe=False, + size_len=0, oer_unsigned=False): + # type: (bytes, Optional[Any], bool, Optional[int], bool) -> Tuple[ASN1_Object[str], bytes] # noqa: E501 + if size_len == 4: + raw, remain = s[:4], s[4:] + else: + length, remain = OER_len_dec(s) + if len(remain) < length: + raise OER_Decoding_Error("IP address could not be decoded", + remaining=s) + raw, remain = remain[:length], remain[length:] + try: + ipaddr_ascii = inet_ntoa(raw) + except Exception: + raise OER_Decoding_Error("IP address could not be decoded", + remaining=s) + return cls.asn1_object(ipaddr_ascii), remain + + +class OERcodec_COUNTER32(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class OERcodec_COUNTER64(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER64 + + +class OERcodec_GAUGE32(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class OERcodec_TIME_TICKS(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +########################## +# ASN1F field hooks # +########################## + +def _field_extensible(field): + # type: (Any) -> bool + return bool(getattr(field, "codec_opts", {}).get("oer_extensible", False)) + + +def _oer_sequence_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + from scapy.asn1fields import ASN1F_badsequence, ASN1F_optional + s = field._apply_tagging_dec(s, pkt, _fname=pkt.name) + if not s: + for obj in field.seq: + obj.set_val(pkt, None) + return [], s + presence, s = OER_preamble_dec( + s, _field_extensible(field), + len(field.optionals), + ) + opt_index = 0 + for obj in field.seq: + target = obj + if isinstance(obj, ASN1F_optional): + present = presence[opt_index] + opt_index += 1 + if not present: + obj.set_absent(pkt) + continue + # The preamble already said the component is there, so dissect + # it directly: a failure is an error, not an absence. + target = obj._field + try: + s = target.dissect(pkt, s) + except ASN1F_badsequence: + break + return [], s + + +def _oer_sequence_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1fields import ASN1F_field, ASN1F_optional + optionals = field.optionals + s = OER_preamble_enc( + _field_extensible(field), + [not opt.is_empty(pkt) for opt in optionals], + ) + for obj in field.seq: + if isinstance(obj, ASN1F_optional) and obj.is_empty(pkt): + continue + s += obj.build(pkt) + # Through ASN1F_field, as ASN1F_SEQUENCE.i2m is the hook above + return ASN1F_field.i2m(field, pkt, s) + + +def _oer_sequence_of_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[list, bytes] + s = field._apply_tagging_dec(s, pkt) + count, s = OER_unsigned_integer_dec(s) + lst = [] + for _ in range(count): + c, s = field._extract_packet(s, pkt) + if c: + lst.append(c) + return lst, s + + +def _oer_sequence_of_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Object + val = getattr(pkt, field.name) + if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: + s = val # type: Any + else: + items = [ + bytes(item) if field.holds_packets else field.fld.i2m(pkt, item) + for item in val or [] + ] + s = OER_unsigned_integer_enc(len(items)) + b"".join(items) + return field.i2m(pkt, s) + + +def _oer_choice_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + from scapy.asn1fields import ASN1F_field + from scapy.asn1.asn1 import ASN1_Error + s = field._apply_tagging_dec(s, pkt) + tag_class, tag_number, payload = OER_tag_dec(s) + choice = None + for key, alternative in field.choices.items(): + if _OER_tag_parts(key) == (tag_class, tag_number): + choice = alternative + break + if choice is None: + if not field.flexible_tag: + raise ASN1_Error( + "ASN1F_CHOICE: unexpected field in '%s' " + "(tag %s not in possible tags %s)" % ( + field.name, tag_class | tag_number, + list(field.choices.keys()) + ) + ) + choice = ASN1F_field + if hasattr(choice, "ASN1_root"): + return field.extract_packet(choice, payload, _underlayer=pkt) + if isinstance(choice, type): + return choice(field.name, b"").m2i(pkt, payload) + # ASN1F_PACKET instance: X.696 20.2 puts the alternative tag in front + # of the value, so it was consumed above and must not be looked for + # again by the field itself. + return field.extract_packet( + choice._resolve_cls(pkt), payload, _underlayer=pkt, + ) + + +def _oer_choice_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + from scapy.asn1.asn1 import ASN1_Object + if x is None: + s = b"" + else: + if isinstance(x, ASN1_Object): + s = x.enc(pkt.ASN1_codec) + else: + s = bytes(x) + index = field.alternative_index(x) + if index is not None: + # X.696 20.2: the chosen alternative is prefixed with its tag + tag_class, tag_number = _OER_tag_parts( + field.choice_order[index] + ) + s = OER_tag_enc(tag_number, tag_class) + s + return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) + + +ASN1_Codecs.OER.register_hooks( + sequence_m2i=_oer_sequence_m2i, + sequence_build=_oer_sequence_build, + sequence_of_m2i=_oer_sequence_of_m2i, + sequence_of_build=_oer_sequence_of_build, + choice_m2i=_oer_choice_m2i, + choice_i2m=_oer_choice_i2m, +) diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py new file mode 100644 index 00000000000..6c0e53878d3 --- /dev/null +++ b/scapy/contrib/uper.py @@ -0,0 +1,1423 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +# scapy.contrib.description = ASN.1 Unaligned Packed Encoding Rules (UPER) +# scapy.contrib.status = loads + +""" +Unaligned Packed Encoding Rules (UPER) for ASN.1 + +As specified in ITU-T X.691 | ISO/IEC 8825-2. + +UPER is registered on ``ASN1_Codecs.PER``. Schema-driven encoding and decoding +(``ASN1F_SEQUENCE``, ``ASN1F_CHOICE``, ``ASN1F_SEQUENCE_OF``, +``ASN1F_ENUMERATED``) is supported for common field types. Value ranges are +declared with ``uper_min=``/``uper_max=``, fixed sizes with ``size_len=``, and +an extension marker with ``uper_extensible=True``. Content of 16K units or +more is fragmented as required by 11.9.3.8. + +Not supported yet: extension additions (an encoding that carries them is +refused rather than misparsed), SET, REAL, and the known-multiplier character +string encodings, which are emitted as plain octets rather than 7 or 4 bits +per character. + +``ASN1F_CHOICE`` alternatives are indexed in declaration order, where 10.2 +asks for the canonical order of their tags. The two coincide for a schema +compiled with AUTOMATIC TAGS, which assigns the tags in declaration order; +declare the alternatives in ascending tag order otherwise. +""" + +from scapy.error import warning +from scapy.compat import orb, bytes_encode +from scapy.utils import binrepr, inet_aton, inet_ntoa +from scapy.asn1.ber import BER_num_dec, BER_num_enc +from scapy.asn1.asn1 import ( + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) +# Re-exported: DEFAULT components are what the preamble bits describe. +from scapy.asn1fields import ASN1F_DEFAULT # noqa: F401 + +from typing import ( + Any, + AnyStr, + Callable, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + + +################### +# UPER encoding # +################### + + +class UPER_Encoding_Error(ASN1_Encoding_Error): + def __init__(self, + msg, # type: str + encoded=None, # type: Optional[Union['UPERcodec_Object[Any]', str]] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.encoded = encoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.encoded, ASN1_Object): + s += "\n### Already encoded ###\n%s" % self.encoded.strshow() + else: + s += "\n### Already encoded ###\n%r" % self.encoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +class UPER_Decoding_Error(ASN1_Decoding_Error): + def __init__(self, + msg, # type: str + decoded=None, # type: Optional[Any] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.decoded = decoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.decoded, ASN1_Object): + s += "\n### Already decoded ###\n%s" % self.decoded.strshow() + else: + s += "\n### Already decoded ###\n%r" % self.decoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +def UPER_bits_for_range(size): + # type: (int) -> int + if size <= 0: + return 0 + return size.bit_length() + + +# X.691 11.9.3.8: content of 16K units or more is split into fragments, each +# one holding a multiple of this many units. +UPER_FRAGMENT_SIZE = 16384 + + +def _uper_bits_to_bytes(value, number_of_bits): + # type: (int, int) -> bytes + # X.691 11.1: an encoding is padded with zero bits up to an octet + # boundary. + if number_of_bits == 0: + return b"" + padding = -number_of_bits % 8 + return (value << padding).to_bytes((number_of_bits + padding) // 8, "big") + + +class UPER_Encoder(object): + def __init__(self): + # type: () -> None + self.number_of_bits = 0 + self.value = 0 + self.chunks_number_of_bits = 0 + self.chunks = [] # type: List[List[int]] + + def append_bit(self, bit): + # type: (int) -> None + self.number_of_bits += 1 + self.value <<= 1 + self.value |= 1 if bit else 0 + + def append_bits(self, data, number_of_bits): + # type: (bytes, int) -> None + if number_of_bits == 0: + return + value = int.from_bytes(data, "big") + value >>= (8 * len(data) - number_of_bits) + self.append_non_negative_binary_integer(value, number_of_bits) + + def append_non_negative_binary_integer(self, value, number_of_bits): + # type: (int, int) -> None + if number_of_bits == 0: + return + if self.number_of_bits > 4096: + self.chunks.append([self.value, self.number_of_bits]) + self.chunks_number_of_bits += self.number_of_bits + self.number_of_bits = 0 + self.value = 0 + self.number_of_bits += number_of_bits + self.value <<= number_of_bits + self.value |= value & ((1 << number_of_bits) - 1) + + def append_bytes(self, data): + # type: (bytes) -> None + self.append_bits(data, 8 * len(data)) + + def append_length_determinant(self, length): + # type: (int) -> None + # X.691 11.9.3.6/11.9.3.7 only define the one and two octet forms up + # to 16K. Longer content has to be fragmented, which requires slicing + # the content itself, so leave that to append_fragmented rather than + # silently emitting a determinant that does not match what follows. + if length >= UPER_FRAGMENT_SIZE: + raise UPER_Encoding_Error( + "UPER_Encoder: length %i requires fragmentation" % length + ) + if length < 128: + encoded = bytes([length]) + else: + encoded = bytes([(0x80 | (length >> 8)), (length & 0xff)]) + self.append_bytes(encoded) + + def append_fragmented(self, count, append_units): + # type: (int, Callable[[int, int], None]) -> None + # X.691 11.9.3.8: emit the content as fragments of at most 4 * 16K + # units, each preceded by its own determinant, and always terminate + # with a determinant below 16K (possibly zero). append_units(offset, + # size) appends the units of one fragment. + offset = 0 + remaining = count + while remaining >= UPER_FRAGMENT_SIZE: + number_of_fragments = min(remaining // UPER_FRAGMENT_SIZE, 4) + size = number_of_fragments * UPER_FRAGMENT_SIZE + self.append_bytes(bytes([0xc0 | number_of_fragments])) + append_units(offset, size) + offset += size + remaining -= size + self.append_length_determinant(remaining) + append_units(offset, remaining) + + def append_unconstrained_whole_number(self, value): + # type: (int) -> None + # X.691 11.4: the shortest two's complement encoding. A negative value + # needs one bit less than its magnitude suggests, as -2**(8n-1) still + # fits in n octets, hence the increment before measuring. + magnitude = value + 1 if value < 0 else value + number_of_bytes = (magnitude.bit_length() + 8) // 8 + self.append_length_determinant(number_of_bytes) + self.append_non_negative_binary_integer( + value & ((1 << (8 * number_of_bytes)) - 1), 8 * number_of_bytes + ) + + def as_bytes(self): + # type: () -> bytes + value = 0 + number_of_bits = 0 + for chunk_value, chunk_number_of_bits in self.chunks: + value <<= chunk_number_of_bits + value |= chunk_value + number_of_bits += chunk_number_of_bits + value <<= self.number_of_bits + value |= self.value + number_of_bits += self.number_of_bits + return _uper_bits_to_bytes(value, number_of_bits) + + +def UPER_has_unexpected_remainder(dec): + # type: (UPER_Decoder) -> bool + if dec.number_of_bits == 0: + return False + mask = (1 << dec.number_of_bits) - 1 + return (dec._bits & mask) != 0 + + +class UPER_Decoder(object): + def __init__(self, encoded): + # type: (bytes) -> None + self.total_number_of_bits = 8 * len(encoded) + self.number_of_bits = self.total_number_of_bits + if encoded: + self._bits = int.from_bytes(encoded, "big") + else: + self._bits = 0 + + def _read_offset(self): + # type: () -> int + return self.total_number_of_bits - self.number_of_bits + + def _read_bits_int(self, number_of_bits): + # type: (int) -> int + if number_of_bits == 0: + return 0 + consumed = self._read_offset() + shift = self.total_number_of_bits - consumed - number_of_bits + mask = (1 << number_of_bits) - 1 + return (self._bits >> shift) & mask + + def read_bit(self): + # type: () -> int + if self.number_of_bits == 0: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + bit = self._read_bits_int(1) + self.number_of_bits -= 1 + return bit + + def read_bits(self, number_of_bits): + # type: (int) -> bytes + if number_of_bits > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + if number_of_bits == 0: + return b"" + value = self._read_bits_int(number_of_bits) + self.number_of_bits -= number_of_bits + return _uper_bits_to_bytes(value, number_of_bits) + + def remaining(self): + # type: () -> bytes + if self.number_of_bits == 0: + return b"" + value = self._read_bits_int(self.number_of_bits) + return _uper_bits_to_bytes(value, self.number_of_bits) + + def remaining_bytes(self): + # type: () -> bytes + # A standalone UPER encoding is padded to an octet boundary, so the + # bits left over inside the current octet are padding; only whole + # octets after it are actual remaining input. + pad = -self._read_offset() % 8 + self.number_of_bits = max(0, self.number_of_bits - pad) + return self.remaining() + + def read_bytes(self, number_of_bytes): + # type: (int) -> bytes + return self.read_bits(8 * number_of_bytes) + + def read_non_negative_binary_integer(self, number_of_bits): + # type: (int) -> int + if number_of_bits > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + if number_of_bits == 0: + return 0 + value = self._read_bits_int(number_of_bits) + self.number_of_bits -= number_of_bits + return value + + def _read_length_determinant(self): + # type: () -> Tuple[int, bool] + # Returns the number of units and whether more fragments follow. + value = self.read_non_negative_binary_integer(8) + if (value & 0x80) == 0x00: + return value, False + if (value & 0xc0) == 0x80: + return ( + ((value & 0x7f) << 8) | + self.read_non_negative_binary_integer(8) + ), False + if 0xc1 <= value <= 0xc4: + return (value & 0x0f) * UPER_FRAGMENT_SIZE, True + raise UPER_Decoding_Error( + "UPER_Decoder: bad length determinant 0x%02x" % value + ) + + def read_length_determinant(self): + # type: () -> int + length, fragmented = self._read_length_determinant() + if fragmented: + raise UPER_Decoding_Error( + "UPER_Decoder: unexpected fragmented length determinant" + ) + return length + + def read_fragmented(self, read_units): + # type: (Callable[[int], None]) -> None + # Counterpart of UPER_Encoder.append_fragmented: read_units(size) is + # called once per fragment, the last one being the (possibly empty) + # fragment introduced by a determinant below 16K. + while True: + size, fragmented = self._read_length_determinant() + read_units(size) + if not fragmented: + return + + def read_unconstrained_whole_number(self): + # type: () -> int + number_of_bytes = self.read_length_determinant() + if number_of_bytes == 0: + raise UPER_Decoding_Error( + "UPER_Decoder: integer with an empty length determinant" + ) + enc = self.read_non_negative_binary_integer(8 * number_of_bytes) + sign_bit = 1 << (8 * number_of_bytes - 1) + if enc & sign_bit: + return enc - (1 << (8 * number_of_bytes)) + return enc + + +def UPER_constrained_int_enc(enc, value, minimum, maximum): + # type: (UPER_Encoder, int, int, int) -> None + # X.691 13.2.2: the field is sized after the range, so a value outside it + # cannot be expressed. Callers handle extensibility before coming here. + if not minimum <= value <= maximum: + raise UPER_Encoding_Error( + "UPER_constrained_int_enc: got %i while expecting %i..%i" % + (value, minimum, maximum) + ) + enc.append_non_negative_binary_integer( + value - minimum, UPER_bits_for_range(maximum - minimum) + ) + + +def UPER_constrained_int_dec(dec, minimum, maximum): + # type: (UPER_Decoder, int, int) -> int + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + return value + minimum + + +def _uper_check_size(name, unit, count, minimum, maximum): + # type: (str, str, int, int, int) -> None + # The determinant is sized after the constraint, so a value that violates + # it cannot be expressed: refuse rather than emit something the peer reads + # as a different length. + if not minimum <= count <= maximum: + raise UPER_Encoding_Error( + "%s: got %i %s while expecting %s" % + (name, count, unit, minimum if minimum == maximum + else "%i..%i" % (minimum, maximum)) + ) + + +def UPER_octet_string_enc(enc, data, minimum=None, maximum=None): + # type: (UPER_Encoder, bytes, Optional[int], Optional[int]) -> None + if minimum is not None and maximum is not None: + _uper_check_size( + "UPER_octet_string_enc", "octets", len(data), minimum, maximum, + ) + if minimum != maximum: + enc.append_non_negative_binary_integer( + len(data) - minimum, + UPER_bits_for_range(maximum - minimum), + ) + enc.append_bytes(data) + else: + enc.append_fragmented( + len(data), + lambda offset, size: enc.append_bytes(data[offset:offset + size]), + ) + + +def UPER_octet_string_dec(dec, minimum=None, maximum=None): + # type: (UPER_Decoder, Optional[int], Optional[int]) -> bytes + if minimum is not None and maximum is not None: + length = minimum + if minimum != maximum: + length += dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + return dec.read_bytes(length) + fragments = [] # type: List[bytes] + dec.read_fragmented(lambda size: fragments.append(dec.read_bytes(size))) + return b"".join(fragments) + + +def UPER_choice_index_enc(enc, index, number_of_choices): + # type: (UPER_Encoder, int, int) -> None + enc.append_non_negative_binary_integer( + index, UPER_bits_for_range(number_of_choices - 1) + ) + + +def UPER_choice_index_dec(dec, number_of_choices): + # type: (UPER_Decoder, int) -> int + return dec.read_non_negative_binary_integer( + UPER_bits_for_range(number_of_choices - 1) + ) + + +class UPERcodec_metaclass(type): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type['UPERcodec_Object[Any]'] + c = cast('Type[UPERcodec_Object[Any]]', + super(UPERcodec_metaclass, cls).__new__(cls, name, bases, dct)) + try: + c.tag.register(c.codec, c) + except Exception: + warning("Error registering %r for %r" % (c.tag, c.codec)) + return c + + +_K = TypeVar('_K') + + +class UPERcodec_Object(Generic[_K], metaclass=UPERcodec_metaclass): + codec = ASN1_Codecs.PER + tag = ASN1_Class_UNIVERSAL.ANY + + @classmethod + def asn1_object(cls, val): + # type: (_K) -> ASN1_Object[_K] + return cls.tag.asn1_object(val) + + # The bit-oriented encode_into()/dec_from_decoder() pair is the primitive + # every codec implements; enc()/do_dec() below are the standalone (byte + # buffer) entry points, and pass every codec option straight through. + + @classmethod + def encode_into(cls, enc, s, **kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # No schema information here (ANY): guess from the Python type. + if isinstance(s, (str, bytes)): + UPERcodec_STRING.encode_into(enc, s, **kwargs) + return + try: + UPERcodec_INTEGER.encode_into(enc, int(s), **kwargs) + except Exception: + raise UPER_Encoding_Error( + "Cannot encode value %r for %s" % (s, cls.__name__), + encoded=s + ) + + @classmethod + def dec_from_decoder(cls, dec, **kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Any] + raise UPER_Decoding_Error( + "%s: Cannot decode unknown UPER type without context" % + cls.__name__, remaining=dec.remaining() + ) + + @classmethod + def enc(cls, s, **kwargs): + # type: (Any, **Any) -> bytes + enc = UPER_Encoder() + cls.encode_into(enc, s, **kwargs) + return enc.as_bytes() + + @classmethod + def do_dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[ASN1_Object[Any], bytes] # noqa: E501 + dec = UPER_Decoder(s) + return cls.dec_from_decoder(dec, **kwargs), dec.remaining_bytes() + + @classmethod + def dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] # noqa: E501 + if not safe: + return cls.do_dec(s, context, safe, **kwargs) + try: + return cls.do_dec(s, context, safe, **kwargs) + except (UPER_Decoding_Error, ASN1_Error) as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + + @classmethod + def safedec(cls, s, context=None, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], **Any) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] # noqa: E501 + return cls.dec(s, context, safe=True, **kwargs) + + +# No tagging hook: PER encodes no tag at all, so a field is left alone. +ASN1_Codecs.PER.register_stem(UPERcodec_Object) + + +######################### +# UPERcodec objects # +######################### + + +def _uper_int_range(size_len, uper_min, uper_max, oer_unsigned=False): + # type: (Optional[int], Optional[int], Optional[int], bool) -> Tuple[Optional[int], Optional[int]] # noqa: E501 + if uper_min is not None or uper_max is not None: + return uper_min, uper_max + if size_len in (1, 2, 4, 8) and oer_unsigned: + return 0, (256 ** size_len) - 1 + return None, None + + +class UPERcodec_INTEGER(UPERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.INTEGER + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + i, # type: int + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> None + minimum, maximum = _uper_int_range(size_len, uper_min, uper_max, oer_unsigned) + if uper_extensible and minimum is not None and maximum is not None: + if minimum <= i <= maximum: + enc.append_bit(0) + else: + enc.append_bit(1) + enc.append_unconstrained_whole_number(i) + return + if minimum is not None and maximum is not None: + UPER_constrained_int_enc(enc, i, minimum, maximum) + else: + enc.append_unconstrained_whole_number(i) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[int] + minimum, maximum = _uper_int_range(size_len, uper_min, uper_max, oer_unsigned) + if uper_extensible and minimum is not None and maximum is not None: + if dec.read_bit(): + value = dec.read_unconstrained_whole_number() + return cls.asn1_object(value) + if minimum is not None and maximum is not None: + value = UPER_constrained_int_dec(dec, minimum, maximum) + else: + value = dec.read_unconstrained_whole_number() + return cls.asn1_object(value) + + +class UPERcodec_BOOLEAN(UPERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + @classmethod + def encode_into(cls, enc, i, **_kwargs): + # type: (UPER_Encoder, int, **Any) -> None + enc.append_bit(1 if i else 0) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[int] + return cls.asn1_object(dec.read_bit()) + + +def _uper_bytes_to_bitstr(data, nbits): + # type: (bytes, int) -> str + bitstr = "".join(binrepr(orb(x)).zfill(8) for x in data) + return bitstr[:nbits] + + +def _uper_bit_string_parts(_s): + # type: (Any) -> Tuple[bytes, int] + if isinstance(_s, tuple) and len(_s) == 2: + data, nbits = _s + return bytes_encode(data), nbits + if isinstance(_s, str) and _s and all(c in "01" for c in _s): + nbits = len(_s) + padded = _s + "0" * ((8 - nbits % 8) % 8) + data = int(padded or "0", 2).to_bytes( + max(1, len(padded) // 8), "big" + ) + return data, nbits + s = bytes_encode(_s) + return s, 8 * len(s) + + +def _uper_size_bounds(size_len, uper_min, uper_max): + # type: (Optional[int], Optional[int], Optional[int]) -> Tuple[Optional[int], Optional[int]] # noqa: E501 + # A SIZE constraint given as size_len is a fixed size, i.e. a range whose + # bounds coincide. + if size_len: + return size_len, size_len + return uper_min, uper_max + + +class UPERcodec_BIT_STRING(UPERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + _s, # type: Any + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> None + s, nbits = _uper_bit_string_parts(_s) + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + if minimum is not None and maximum is not None: + _uper_check_size(cls.__name__, "bits", nbits, minimum, maximum) + if minimum != maximum: + enc.append_non_negative_binary_integer( + nbits - minimum, UPER_bits_for_range(maximum - minimum) + ) + enc.append_bits(s, nbits) + else: + # X.691 16.11: the determinant counts bits, not octets, and no + # padding is inserted before whatever follows the bit string. + enc.append_fragmented( + nbits, + # Fragments hold whole multiples of 16K bits, so every chunk + # but the last starts and ends on an octet boundary. + lambda offset, size: enc.append_bits( + s[offset // 8:(offset + size + 7) // 8], size + ), + ) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[str] + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + if minimum is not None and maximum is not None: + nbits = minimum + if minimum != maximum: + nbits += dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + else: + fragments = [] # type: List[bytes] + sizes = [] # type: List[int] + + def read_fragment(size): + # type: (int) -> None + fragments.append(dec.read_bits(size)) + sizes.append(size) + + dec.read_fragmented(read_fragment) + return cls.asn1_object( + _uper_bytes_to_bitstr(b"".join(fragments), sum(sizes)) + ) + raw = dec.read_bits(nbits) + return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) + + +class UPERcodec_STRING(UPERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.STRING + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + _s, # type: Union[str, bytes] + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> None + s = bytes_encode(_s) + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + UPER_octet_string_enc(enc, s, minimum, maximum) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[Any] + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + raw = UPER_octet_string_dec(dec, minimum, maximum) + return cls.asn1_object(raw) + + +class UPERcodec_NULL(UPERcodec_Object[None]): + tag = ASN1_Class_UNIVERSAL.NULL + + @classmethod + def encode_into(cls, enc, _s, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # NULL has an empty encoding. + return + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[None] + return cls.asn1_object(None) + + @classmethod + def do_dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[ASN1_Object[None], bytes] # noqa: E501 + # NULL occupies no bits at all, so the input is left untouched. + return cls.asn1_object(None), s + + +class UPERcodec_OID(UPERcodec_Object[bytes]): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def encode_into(cls, enc, _oid, **_kwargs): + # type: (UPER_Encoder, AnyStr, **Any) -> None + oid = bytes_encode(_oid) + if oid: + lst = [int(x) for x in oid.split(b".")] + lst = [40 * lst[0] + lst[1]] + lst[2:] + else: + lst = [] + body = b"".join(BER_num_enc(k) for k in lst) + enc.append_fragmented( + len(body), + lambda offset, size: enc.append_bytes(body[offset:offset + size]), + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[bytes] + fragments = [] # type: List[bytes] + dec.read_fragmented(lambda size: fragments.append(dec.read_bytes(size))) + content = b"".join(fragments) + lst = [] + while content: + val, content = BER_num_dec(content) + lst.append(val) + if len(lst) > 0: + lst.insert(0, lst[0] // 40) + lst[1] %= 40 + return cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)) + + +def UPER_enumerated_enc(enc, value, enum_values): + # type: (UPER_Encoder, int, List[int]) -> None + if not enum_values: + raise UPER_Encoding_Error("UPER_enumerated_enc: empty enumeration") + try: + index = enum_values.index(value) + except ValueError: + raise UPER_Encoding_Error( + "UPER_enumerated_enc: unknown enumeration value %r" % value + ) + UPER_choice_index_enc(enc, index, len(enum_values)) + + +def UPER_enumerated_dec(dec, enum_values): + # type: (UPER_Decoder, List[int]) -> int + if not enum_values: + raise UPER_Decoding_Error("UPER_enumerated_dec: empty enumeration") + index = UPER_choice_index_dec(dec, len(enum_values)) + if index >= len(enum_values): + raise UPER_Decoding_Error( + "UPER_enumerated_dec: index %i out of range" % index + ) + return enum_values[index] + + +class UPERcodec_ENUMERATED(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + i, # type: int + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + uper_enum_values=None, # type: Optional[List[int]] + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> None + if uper_enum_values is not None: + if uper_extensible: + # X.691 14.3: a one bit prefix says whether the value is an + # extension addition. Only root values can be encoded. + if i not in uper_enum_values: + raise UPER_Encoding_Error( + "UPERcodec_ENUMERATED: extension additions are not " + "supported" + ) + enc.append_bit(0) + UPER_enumerated_enc(enc, i, uper_enum_values) + return + minimum, maximum = cls._range( + size_len, uper_min, uper_max, UPER_Encoding_Error + ) + UPER_constrained_int_enc(enc, i, minimum, maximum) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + uper_enum_values=None, # type: Optional[List[int]] + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[int] + if uper_enum_values is not None: + if uper_extensible and dec.read_bit(): + raise UPER_Decoding_Error( + "UPERcodec_ENUMERATED: extension additions are not " + "supported" + ) + return cls.asn1_object(UPER_enumerated_dec(dec, uper_enum_values)) + minimum, maximum = cls._range( + size_len, uper_min, uper_max, UPER_Decoding_Error + ) + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + minimum + return cls.asn1_object(value) + + @staticmethod + def _range(size_len, uper_min, uper_max, error): + # type: (Optional[int], Optional[int], Optional[int], Any) -> Tuple[int, int] # noqa: E501 + # Without the enumeration itself the index range has to come from + # the declared bounds; deriving it from the value at hand would + # make the width depend on the value, which the decoder cannot + # reproduce. + minimum = uper_min if uper_min is not None else 0 + maximum = uper_max if uper_max is not None else (size_len or None) + if maximum is None: + raise error("UPERcodec_ENUMERATED: missing range") + return minimum, maximum + + +class UPERcodec_SEQUENCE(UPERcodec_Object[Union[bytes, List[Any]]]): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def encode_into(cls, enc, _ll, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # A finished encoding is padded to an octet boundary, so its real bit + # length is lost and it cannot be spliced into a bitstream. Sequences + # are encoded through the ASN1F_SEQUENCE hooks instead. + raise UPER_Encoding_Error( + "UPERcodec_SEQUENCE: schema-defined field order required" + ) + + @classmethod + def enc(cls, _ll, **_kwargs): + # type: (Union[bytes, List[UPERcodec_Object[Any]]], **Any) -> bytes + if isinstance(_ll, bytes): + return _ll + raise UPER_Encoding_Error( + "UPERcodec_SEQUENCE: schema-defined field order required" + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Union[bytes, List[Any]]] + raise UPER_Decoding_Error( + "UPERcodec_SEQUENCE: decoding requires schema-defined field order", + remaining=dec.remaining() + ) + + +class UPERcodec_SET(UPERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class UPERcodec_IPADDRESS(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def encode_into(cls, enc, ipaddr_ascii, **_kwargs): + # type: (UPER_Encoder, str, **Any) -> None + try: + s = inet_aton(ipaddr_ascii) + except Exception: + raise UPER_Encoding_Error("IPv4 address could not be encoded") + UPER_octet_string_enc(enc, s, 4, 4) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[str] + raw = UPER_octet_string_dec(dec, 4, 4) + try: + ipaddr_ascii = inet_ntoa(raw) + except Exception: + raise UPER_Decoding_Error("IP address could not be decoded") + return cls.asn1_object(ipaddr_ascii) + + +class UPERcodec_COUNTER32(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class UPERcodec_COUNTER64(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER64 + + +class UPERcodec_GAUGE32(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class UPERcodec_TIME_TICKS(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +# string aliases +class UPERcodec_UTF8_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class UPERcodec_NUMERIC_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class UPERcodec_PRINTABLE_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class UPERcodec_T61_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class UPERcodec_VIDEOTEX_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class UPERcodec_IA5_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class UPERcodec_GENERAL_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERAL_STRING + + +class UPERcodec_UTC_TIME(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + +class UPERcodec_GENERALIZED_TIME(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class UPERcodec_ISO646_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class UPERcodec_UNIVERSAL_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class UPERcodec_BMP_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +########################## +# ASN1F field hooks # +########################## + +def _field_extensible(field): + # type: (Any) -> bool + return bool(getattr(field, "codec_opts", {}).get("uper_extensible", False)) + + +def _field_range(field): + # type: (Any) -> Tuple[Optional[int], Optional[int]] + opts = getattr(field, "codec_opts", {}) + return opts.get("uper_min"), opts.get("uper_max") + + +def _uper_decode_all(s, read): + # type: (bytes, Callable[[UPER_Decoder], Any]) -> Any + # The field owns the whole substring it was handed, so any bit left set + # beyond the octet padding means the encoding did not match the schema. + dec = UPER_Decoder(s) + value = read(dec) + if UPER_has_unexpected_remainder(dec): + raise UPER_Decoding_Error( + "unexpected remainder", + remaining=dec.remaining(), + ) + return value + + +def _uper_use_object_enc(field, pkt, item): + # type: (Any, Any, Any) -> bool + # Always pass constraints through codec.enc(**kwargs). + return False + + +def _uper_sequence_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + _uper_decode_all(s, lambda dec: ( + _uper_sequence_dissect_from_decoder(field, pkt, dec) + )) + return [], b"" + + +def _uper_sequence_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1fields import ASN1F_field + enc = UPER_Encoder() + _uper_sequence_encode_into(field, enc, pkt) + return ASN1F_field.i2m(field, pkt, enc.as_bytes()) + + +def _uper_sequence_dissect_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> None + from scapy.asn1fields import ASN1F_badsequence, ASN1F_optional + if _field_extensible(field): + if dec.read_bit(): + raise UPER_Decoding_Error( + "ASN1F_SEQUENCE: extension additions are not supported" + ) + optionals = field.optionals + presence = [dec.read_bit() for _ in optionals] + opt_idx = 0 + for obj in field.seq: + if isinstance(obj, ASN1F_optional): + if not presence[opt_idx]: + obj.set_absent(pkt) + opt_idx += 1 + continue + opt_idx += 1 + try: + obj.dissect_from_decoder(pkt, dec) + except ASN1F_badsequence: + break + + +def _uper_sequence_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1fields import ASN1F_optional + if _field_extensible(field): + enc.append_bit(0) + for opt in field.optionals: + enc.append_bit(0 if opt.is_empty(pkt) else 1) + for obj in field.seq: + if isinstance(obj, ASN1F_optional) and obj.is_empty(pkt): + continue + obj.encode_into(enc, pkt) + + +def _uper_sequence_of_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[list, bytes] + return _uper_decode_all(s, lambda dec: ( + _uper_sequence_of_m2i_from_decoder(field, pkt, dec) + )), b"" + + +def _uper_sequence_of_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Object + val = getattr(pkt, field.name) + if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: + s = val # type: Any + else: + # An unset field counts as an empty one, size constraint included + enc = UPER_Encoder() + _uper_sequence_of_encode_into(field, enc, pkt, val) + s = enc.as_bytes() + return field.i2m(pkt, s) + + +def _uper_sequence_of_m2i_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> list + lst = [] + + def read_items(count): + # type: (int) -> None + for _ in range(count): + item = _extract_packet_from_decoder(field, dec, pkt) + lst.append(item) + + if _field_extensible(field) and dec.read_bit(): + dec.read_fragmented(read_items) + else: + _uper_count_dec(field, dec, read_items) + return lst + + +def _uper_sequence_of_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + if value is None: + value = getattr(pkt, field.name) + if value is None: + _uper_count_enc(field, enc, 0, lambda offset, size: None) + return + count = len(value) + + def append_items(offset, size): + # type: (int, int) -> None + for item in value[offset:offset + size]: + if field.holds_packets: + item.ASN1_root.encode_into(enc, item) + else: + field.fld.encode_into(enc, pkt, item) + + uper_min, uper_max = _field_range(field) + if _field_extensible(field): + if ( + uper_min is not None and uper_max is not None and + uper_min <= count <= uper_max + ): + enc.append_bit(0) + else: + enc.append_bit(1) + enc.append_fragmented(count, append_items) + return + _uper_count_enc(field, enc, count, append_items) + + +def _uper_choice_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + return _uper_decode_all(s, lambda dec: ( + _uper_choice_m2i_from_decoder(field, pkt, dec) + )), b"" + + +def _uper_choice_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + if x is None: + s = b"" + else: + enc = UPER_Encoder() + _uper_choice_encode_into(field, enc, pkt, x) + s = enc.as_bytes() + return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) + + +def _uper_choice_m2i_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> Any + from scapy.asn1.asn1 import ASN1_Error + if _field_extensible(field): + if dec.read_bit(): + raise UPER_Decoding_Error( + "ASN1F_CHOICE: extension additions are not supported" + ) + order = field.choice_order + if len(order) > 1: + index = UPER_choice_index_dec(dec, len(order)) + else: + index = 0 + if index >= len(order): + raise ASN1_Error( + "ASN1F_CHOICE: unexpected index %s in '%s'" % + (index, field.name) + ) + choice = field.choice_list[index] + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + value = choice() + value.add_underlayer(pkt) + value.ASN1_root.dissect_from_decoder(value, dec) + elif isinstance(choice, type): + value = choice(field.name, b"").m2i_from_decoder(pkt, dec) + else: + value = choice.m2i_from_decoder(pkt, dec) + return field.record_alternative(value, index) + + +def _uper_choice_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1.asn1 import ASN1_Error + if value is None: + value = getattr(pkt, field.name) + index = field.alternative_index(value) + if index is None: + raise ASN1_Error( + "ASN1F_CHOICE: cannot encode unknown alternative in '%s'" % + field.name + ) + if _field_extensible(field): + enc.append_bit(0) + order = field.choice_order + if len(order) > 1: + UPER_choice_index_enc(enc, index, len(order)) + choice = field.choice_list[index] + if hasattr(choice, "ASN1_root"): + value.ASN1_root.encode_into(enc, value) + elif isinstance(choice, type): + choice(field.name, b"").encode_into(enc, pkt, value) + else: + choice.encode_into(enc, pkt, value) + + +def _uper_packet_m2i_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> Any + cls = field._resolve_cls(pkt) + p = cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p + + +def _uper_packet_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + if x is None: + s = b"" + else: + enc = UPER_Encoder() + _uper_packet_encode_into(field, enc, pkt, x) + s = enc.as_bytes() + return field._tagging_enc( + pkt, s, + implicit_tag=field.implicit_tag, + explicit_tag=field.explicit_tag, + ) + + +def _uper_packet_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1.asn1 import ASN1_Object + if value is None: + value = getattr(pkt, field.name) + if value is None: + return + if isinstance(value, ASN1_Object): + value = value.val + value.ASN1_root.encode_into(enc, value) + + +def _uper_count_enc(field, enc, count, append_items): + # type: (Any, Any, int, Callable[[int, int], None]) -> None + # The count of a SEQUENCE OF is a constrained whole number when the field + # carries a size constraint; otherwise it is a length determinant, and the + # items themselves are what gets fragmented, hence the callback. + uper_min, uper_max = _field_range(field) + if uper_min is not None and uper_max is not None: + UPER_constrained_int_enc(enc, count, uper_min, uper_max) + append_items(0, count) + else: + enc.append_fragmented(count, append_items) + + +def _uper_count_dec(field, dec, read_items): + # type: (Any, Any, Callable[[int], None]) -> None + uper_min, uper_max = _field_range(field) + if uper_min is not None and uper_max is not None: + read_items(UPER_constrained_int_dec(dec, uper_min, uper_max)) + else: + dec.read_fragmented(read_items) + + +def _extract_packet_from_decoder(field, dec, pkt): + # type: (Any, Any, Any) -> Any + if field.holds_packets: + p = field.cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p + return field.fld.m2i_from_decoder(pkt, dec) + + +def _install_uper_asn1fields(): + # type: () -> None + """Attach the UPER bitstream helpers onto the asn1fields classes.""" + from scapy import asn1fields as af + from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Error, ASN1_Object + + def m2i_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> Any + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + return codec.dec_from_decoder( # type: ignore[attr-defined] + dec, **self._codec_kwargs(pkt), + ) + + def dissect_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> None + self.set_val(pkt, self.m2i_from_decoder(pkt, dec)) + + def encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + if value is None: + value = getattr(pkt, self.name) + if value is None: + return + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + if isinstance(value, ASN1_Object): + if (self.ASN1_tag == ASN1_Class_UNIVERSAL.ANY or + value.tag == ASN1_Class_UNIVERSAL.RAW or + value.tag == ASN1_Class_UNIVERSAL.ERROR or + self.ASN1_tag == value.tag): + raw = value.val + else: + raise ASN1_Error( + "Encoding Error: got %r instead of an %r for field [%s]" % + (value, self.ASN1_tag, self.name) + ) + else: + raw = value + codec.encode_into( # type: ignore[attr-defined] + enc, raw, **self._codec_kwargs(pkt), + ) + + def opt_dissect_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> None + return self._field.dissect_from_decoder(pkt, dec) + + def opt_encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + self._field.encode_into(enc, pkt, value) + + for field_cls, methods in ( + (af.ASN1F_field, { + "m2i_from_decoder": m2i_from_decoder, + "dissect_from_decoder": dissect_from_decoder, + "encode_into": encode_into, + }), + (af.ASN1F_SEQUENCE, { + "dissect_from_decoder": _uper_sequence_dissect_from_decoder, + "encode_into": _uper_sequence_encode_into, + }), + (af.ASN1F_SEQUENCE_OF, { + "m2i_from_decoder": _uper_sequence_of_m2i_from_decoder, + "encode_into": _uper_sequence_of_encode_into, + }), + (af.ASN1F_CHOICE, { + "m2i_from_decoder": _uper_choice_m2i_from_decoder, + "encode_into": _uper_choice_encode_into, + }), + (af.ASN1F_PACKET, { + "m2i_from_decoder": _uper_packet_m2i_from_decoder, + "encode_into": _uper_packet_encode_into, + }), + (af.ASN1F_optional, { + "dissect_from_decoder": opt_dissect_from_decoder, + "encode_into": opt_encode_into, + }), + ): + for method_name, func in methods.items(): + setattr(field_cls, method_name, func) + + _orig_enum_codec_kwargs = af.ASN1F_enum_INTEGER._codec_kwargs + + def enum_codec_kwargs(self, pkt): + # type: (Any, Any) -> Any + kwargs = _orig_enum_codec_kwargs(self, pkt) + # The permitted values belong to the UPER encoding, not to the field + # definition, so they are only added for PER packets. Other codecs + # keep an empty codec_opts and their item.enc() fast path. + codec = getattr(pkt, "ASN1_codec", None) + if codec is ASN1_Codecs.PER: + # X.691 14.1: the index follows the enumeration values in + # ascending order, whatever order they were declared in. + kwargs.setdefault("uper_enum_values", sorted(self.i2s)) + return kwargs + + af.ASN1F_enum_INTEGER._codec_kwargs = enum_codec_kwargs # type: ignore[assignment] + + +_install_uper_asn1fields() +ASN1_Codecs.PER.register_hooks( + use_object_enc=_uper_use_object_enc, + sequence_m2i=_uper_sequence_m2i, + sequence_build=_uper_sequence_build, + sequence_of_m2i=_uper_sequence_of_m2i, + sequence_of_build=_uper_sequence_of_build, + choice_m2i=_uper_choice_m2i, + choice_i2m=_uper_choice_i2m, + packet_i2m=_uper_packet_i2m, +) diff --git a/scapy/tools/generate_its_asn1.py b/scapy/tools/generate_its_asn1.py new file mode 100644 index 00000000000..0e161923593 --- /dev/null +++ b/scapy/tools/generate_its_asn1.py @@ -0,0 +1,980 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Generate Scapy ASN1_Packet definitions for ETSI ITS messages. + +Requires asn1tools (dev dependency only, not needed at runtime). + +Usage: + python scapy/tools/generate_its_asn1.py +""" + +from __future__ import annotations + +import argparse +import keyword +import re +from pathlib import Path +from typing import Any, Dict, List, Optional, Set, Tuple + +try: + from asn1tools import parse_files +except ImportError as exc: + raise SystemExit( + "asn1tools is required to regenerate ITS packets " + "(pip install asn1tools)" + ) from exc + +ROOT_MESSAGES = ("CAM", "DENM", "IVIM", "SPATEM", "MAPEM") + +PRIMITIVE_TYPES = { + "INTEGER", + "ENUMERATED", + "BOOLEAN", + "NULL", + "BIT STRING", + "OCTET STRING", + "OBJECT IDENTIFIER", + "IA5String", + "UTF8String", + "NumericString", + "PrintableString", + "GeneralizedTime", + "UTCTime", + "DATE", + "EUI64", + "VisibleString", + "TeletexString", + "GraphicString", + "UniversalString", + "BMPString", + "ObjectDescriptor", + "REAL", +} + +STRING_TYPES = { + "IA5String": "ASN1F_IA5_STRING", + "UTF8String": "ASN1F_UTF8_STRING", + "NumericString": "ASN1F_NUMERIC_STRING", + "PrintableString": "ASN1F_PRINTABLE_STRING", + "GeneralizedTime": "ASN1F_GENERALIZED_TIME", + "UTCTime": "ASN1F_UTC_TIME", +} + + +def _asn_dir() -> Path: + return ( + Path(__file__).resolve().parent.parent + / "contrib" / "automotive" / "v2x" / "asn" + ) + + +def asn_file_list() -> List[Path]: + asn = _asn_dir() + return [ + asn / "ITS-Container.asn", + asn / "CAM-PDU-Descriptions.asn", + asn / "DENM-PDU-Descriptions.asn", + asn / "SPATEM-PDU-Descriptions.asn", + asn / "MAPEM-PDU-Descriptions.asn", + asn / "IVIM-PDU-Descriptions.asn", + asn / "iso-patched" / "ISO24534-3_ElectronicRegistrationIdentificationVehicleDataModule-patched.asn", + asn / "iso-patched" / "ISO14823-missing.asn", + asn / "iso-patched" / "ISO14906(2018)EfcDsrcGenericv7-patched.asn", + asn / "iso-patched" / "ISO14906(2018)EfcDsrcApplicationv6-patched.asn", + asn / "ISO-TS-19091-addgrp-C-2018-patched.asn", + asn / "ISO14816_AVIAEINumberingAndDataStructures.asn", + asn / "ISO19321IVIv2.asn", + asn / "ISO_17419_1-1.asn", + ] + + +PACKET_FIELD_RESERVED = frozenset({"name"}) + + +def field_name(name: str) -> str: + if name in PACKET_FIELD_RESERVED: + return name + "_" + return name + + +def sanitize_name(name: str) -> str: + name = re.sub(r"[^0-9a-zA-Z_]", "_", name.replace("-", "_")) + if not name or name[0].isdigit(): + name = "_" + name + if keyword.iskeyword(name): + name = name + "_" + return name + + +def flatten_members(members: List[Any]) -> List[Dict[str, Any]]: + flat: List[Dict[str, Any]] = [] + for member in members: + if member is None: + continue + if isinstance(member, list): + flat.extend(flatten_members(member)) + else: + flat.append(member) + return flat + + +def inline_type_name(parent: str, member_name: str) -> str: + return f"ITS_Inline_{sanitize_name(parent)}_{member_name}" + + +def root_members(members: List[Any]) -> List[Dict[str, Any]]: + """Members of the extension root, the ones a codec encodes in place. + + asn1tools gives an extension marker as None, so the members that follow + it, up to the marker closing the additions, are extension additions. They + are encoded out of the root, behind their own preamble, and taking them + for root members would shift every bit behind them. + """ + root: List[Any] = [] + additions = False + for member in members: + if member is None: + additions = not additions + elif not additions: + root.append(member) + return flatten_members(root) + + +def is_extensible(members: List[Any]) -> bool: + return any(m is None for m in members) + + +def py_literal(value: Any) -> str: + if isinstance(value, str): + return repr(value) + if isinstance(value, bytes): + return repr(value) + if isinstance(value, dict): + items = ", ".join(f"{py_literal(k)}: {py_literal(v)}" for k, v in value.items()) + return "{" + items + "}" + if isinstance(value, (list, tuple)): + return "[" + ", ".join(py_literal(v) for v in value) + "]" + return repr(value) + + +class ITSASN1Generator: + def __init__(self, spec: Dict[str, Any], compiled_types: Optional[Set[str]] = None) -> None: + self.spec = spec + self.compiled_types = compiled_types + self.class_names: Dict[Tuple[str, str], str] = {} + self.used_py_names: Set[str] = set() + self.import_fields: Set[str] = set() + self.import_asn1: Set[str] = set() + self._assign_class_names() + + def _assign_class_names(self) -> None: + for module, data in self.spec.items(): + for type_name, desc in data.get("types", {}).items(): + if desc.get("parameters"): + continue + if not self._needs_packet(module, type_name, desc): + continue + py_name = sanitize_name(type_name) + if py_name in self.used_py_names: + py_name = sanitize_name(module.split("-")[0]) + "_" + py_name + while py_name in self.used_py_names: + py_name = py_name + "_" + self.class_names[(module, type_name)] = py_name + self.used_py_names.add(py_name) + + def _lookup_descriptor(self, module: str, type_name: str) -> Tuple[str, str, Dict[str, Any]]: + data = self.spec.get(module, {}) + if type_name in data.get("types", {}): + return module, type_name, data["types"][type_name] + for imported_mod, imported_names in data.get("imports", {}).items(): + if type_name in imported_names: + return self._lookup_descriptor(imported_mod, type_name) + raise KeyError(f"type {type_name!r} not found from module {module!r}") + + def _resolve(self, module: str, type_name: str, seen: Optional[Set[Tuple[str, str]]] = None) -> Tuple[str, str, Dict[str, Any]]: + key = (module, type_name) + if seen is None: + seen = set() + if key in seen: + return module, type_name, self.spec[module]["types"][type_name] + seen.add(key) + mod, name, desc = self._lookup_descriptor(module, type_name) + base = desc.get("type") + if base in PRIMITIVE_TYPES or base in ("SEQUENCE", "CHOICE", "SEQUENCE OF"): + return mod, name, desc + return self._resolve(mod, base, seen) + + def _needs_packet(self, module: str, type_name: str, desc: Optional[Dict[str, Any]] = None) -> bool: + if desc is None: + desc = self.spec[module]["types"][type_name] + if desc.get("parameters"): + return False + base = desc.get("type") + if base in ("SEQUENCE", "CHOICE"): + return True + if base in PRIMITIVE_TYPES: + return False + try: + _, _, resolved = self._resolve(module, type_name) + except KeyError: + return False + return resolved.get("type") in ("SEQUENCE", "CHOICE") + + def _class_for(self, module: str, type_name: str) -> str: + mod, name, desc = self._resolve(module, type_name) + if self._needs_packet(mod, name, desc): + return self.class_names[(mod, name)] + raise KeyError(type_name) + + def _size_args(self, desc: Dict[str, Any], field_cls: str = "") -> str: + size = desc.get("size") + if not size: + return "" + parts = [] + for item in size: + if item is None: + continue + if isinstance(item, tuple): + lo, hi = item + if field_cls in ("ASN1F_BIT_STRING", "ASN1F_FLAGS"): + parts.append(f"uper_min={lo}") + parts.append(f"uper_max={hi}") + elif lo == hi: + parts.append(f"size_len={lo}") + else: + parts.append(f"uper_min={lo}") + parts.append(f"uper_max={hi}") + elif isinstance(item, int): + if field_cls in ("ASN1F_BIT_STRING", "ASN1F_FLAGS"): + parts.append(f"uper_min={item}") + parts.append(f"uper_max={item}") + else: + parts.append(f"size_len={item}") + return (", " + ", ".join(parts)) if parts else "" + + def _min_size(self, desc: Dict[str, Any]) -> int: + for item in desc.get("size") or []: + if isinstance(item, int): + return item + if isinstance(item, tuple) and item[0] is not None: + return int(item[0]) + return 0 + + def _integer_args(self, desc: Dict[str, Any], enumerated: bool = False) -> str: + args = [] + if enumerated: + return "" + restricted = desc.get("restricted-to") + if restricted: + lo, hi = restricted[0] + args.append(f"uper_min={lo}") + args.append(f"uper_max={hi}") + if lo >= 0: + args.append("oer_unsigned=True") + return (", " + ", ".join(args)) if args else "" + + def _integer_default(self, desc: Dict[str, Any]) -> int: + restricted = desc.get("restricted-to") + if restricted: + lo, hi = restricted[0] + if lo <= 0 <= hi: + return 0 + return lo + return 0 + + def _register_inline_packet(self, module: str, parent: str, member: Dict[str, Any]) -> str: + member_name = sanitize_name(member.get("name") or "inline") + type_name = inline_type_name(sanitize_name(parent), member_name) + py_name = sanitize_name(type_name) + while py_name in self.used_py_names: + py_name = py_name + "_" + key = (module, type_name) + self.class_names[key] = py_name + self.used_py_names.add(py_name) + if module not in self.spec: + self.spec[module] = {"types": {}} + self.spec[module]["types"][type_name] = member + return py_name + + def _is_forward_ref(self, mod: str, name: str, current_key: Optional[Tuple[str, str]], result_order: Optional[List[Tuple[str, str]]]) -> bool: + if current_key is None or result_order is None: + return False + key = (mod, name) + if key not in self.class_names: + return True + try: + return result_order.index(key) > result_order.index(current_key) + except ValueError: + return False + + def _packet_ref(self, member_name: str, mod: str, name: str, tag_args: str, current_key: Optional[Tuple[str, str]], result_order: Optional[List[Tuple[str, str]]], optional: bool = False) -> str: + if self._is_forward_ref(mod, name, current_key, result_order): + self.import_fields.add("ASN1F_STRING") + default = "None" if optional else 'b""' + return f'ASN1F_STRING("{member_name}", {default}{tag_args})' + cls = self.class_names[(mod, name)] + self.import_fields.add("ASN1F_PACKET") + default = "None" if optional else f"{cls}()" + return f'ASN1F_PACKET("{member_name}", {default}, {cls}{tag_args})' + + def _discover_inline_types(self) -> None: + pending = True + while pending: + pending = False + for module, data in self.spec.items(): + for type_name, desc in list(data.get("types", {}).items()): + if desc.get("type") not in ("SEQUENCE", "CHOICE"): + continue + for member in root_members(desc.get("members", [])): + if member.get("type") in ("SEQUENCE", "CHOICE"): + _, inline_name = self._inline_key( + module, type_name, member, + ) + if inline_name not in data.get("types", {}): + self._register_inline_packet(module, type_name, member) + pending = True + + def _field_for_member( + self, + module: str, + member: Dict[str, Any], + in_choice: bool = False, + parent: str = "", + current_key: Optional[Tuple[str, str]] = None, + result_order: Optional[List[Tuple[str, str]]] = None, + ) -> str: + if member is None: + return "" + member_name = field_name(sanitize_name(member.get("name") or "extension")) + type_name = member["type"] + optional = member.get("optional") + tag = member.get("tag") + tag_args = "" + if tag and not in_choice: + tag_args = f", implicit_tag={tag['number']}" + if type_name == "SEQUENCE OF": + self.import_fields.add("ASN1F_SEQUENCE_OF") + element = member.get("element", {}) + elem_type = element.get("type") + if elem_type not in PRIMITIVE_TYPES: + try: + emod, ename, edesc = self._resolve(module, elem_type) + if edesc.get("parameters"): + self.import_fields.add("ASN1F_STRING") + field = f'ASN1F_SEQUENCE_OF("{member_name}", None, ASN1F_STRING{tag_args})' + if optional: + self.import_fields.add("ASN1F_optional") + field = f"ASN1F_optional({field})" + return field + except KeyError: + pass + seq_default = self._sequence_of_default( + module, element, member, optional, parent or member_name, + ) + inner = self._sequence_of_element(module, element, parent=parent or member_name) + size_args = self._sequence_of_size_args(member) + field = f'ASN1F_SEQUENCE_OF("{member_name}", {seq_default}, {inner}{size_args}{tag_args})' + if optional: + self.import_fields.add("ASN1F_optional") + field = f"ASN1F_optional({field})" + return field + if type_name in ("SEQUENCE", "CHOICE"): + _, inline_name = self._inline_key(module, parent or member_name, member) + field = self._packet_ref( + member_name, module, inline_name, tag_args, current_key, result_order, + optional=optional, + ) + if optional: + self.import_fields.add("ASN1F_optional") + field = f"ASN1F_optional({field})" + return field + if type_name in PRIMITIVE_TYPES: + desc = member + mod = module + name = member_name + base = type_name + else: + try: + mod, name, desc = self._resolve(module, type_name) + except KeyError: + self.import_fields.add("ASN1F_STRING") + field = f'ASN1F_STRING("{member_name}", b""{tag_args})' + if optional: + self.import_fields.add("ASN1F_optional") + field = f"ASN1F_optional({field})" + return field + base = desc.get("type") + if desc.get("parameters") or ( + base in ("SEQUENCE", "CHOICE") and (mod, name) not in self.class_names + ): + self.import_fields.add("ASN1F_STRING") + field = f'ASN1F_STRING("{member_name}", b""{tag_args})' + if optional: + self.import_fields.add("ASN1F_optional") + field = f"ASN1F_optional({field})" + return field + + if base == "SEQUENCE": + field = self._packet_ref( + member_name, mod, name, tag_args, current_key, result_order, + optional=optional, + ) + elif base == "CHOICE": + field = self._packet_ref( + member_name, mod, name, tag_args, current_key, result_order, + optional=optional, + ) + elif base == "SEQUENCE OF": + element = desc.get("element", member) + elem_type = element.get("type") + if elem_type not in PRIMITIVE_TYPES: + try: + emod, ename, edesc = self._resolve(mod, elem_type) + if edesc.get("parameters"): + self.import_fields.add("ASN1F_SEQUENCE_OF") + self.import_fields.add("ASN1F_STRING") + field = f'ASN1F_SEQUENCE_OF("{member_name}", None, ASN1F_STRING{tag_args})' + if optional: + self.import_fields.add("ASN1F_optional") + field = f"ASN1F_optional({field})" + return field + except KeyError: + pass + if elem_type in PRIMITIVE_TYPES: + elem_desc = element + elem_base = elem_type + else: + elem_mod, elem_name, elem_desc = self._resolve(mod, elem_type) + elem_base = elem_desc.get("type") + self.import_fields.add("ASN1F_SEQUENCE_OF") + seq_default = self._sequence_of_default( + mod, element, desc, optional, type_name, + ) + size_args = self._sequence_of_size_args(desc) + if elem_base == "SEQUENCE": + if elem_type in PRIMITIVE_TYPES: + raise KeyError("unexpected inline SEQUENCE element") + elem_mod, elem_name, _ = self._resolve(mod, elem_type) + if self._is_forward_ref(elem_mod, elem_name, current_key, result_order): + inner = "ASN1F_STRING" + self.import_fields.add("ASN1F_STRING") + else: + inner = self.class_names[(elem_mod, elem_name)] + field = f'ASN1F_SEQUENCE_OF("{member_name}", {seq_default}, {inner}{size_args}{tag_args})' + else: + inner = self._sequence_of_element(mod, element, parent=type_name) + field = f'ASN1F_SEQUENCE_OF("{member_name}", {seq_default}, {inner}{size_args}{tag_args})' + else: + field = self._primitive_field(member_name, desc, tag_args, in_choice, optional=optional) + + if optional: + self.import_fields.add("ASN1F_optional") + field = f"ASN1F_optional({field})" + return field + + def _sequence_of_size_args(self, desc: Dict[str, Any]) -> str: + parts = [] + extensible = False + for item in desc.get("size") or []: + if item is None: + extensible = True + continue + if isinstance(item, tuple): + parts.append(f"uper_min={item[0]}") + parts.append(f"uper_max={item[1]}") + elif isinstance(item, int): + parts.append(f"uper_min={item}") + parts.append(f"uper_max={item}") + if extensible: + parts.append("uper_extensible=True") + return (", " + ", ".join(parts)) if parts else "" + + def _sequence_of_default( + self, + module: str, + element: Dict[str, Any], + desc: Dict[str, Any], + optional: bool, + parent: str, + ) -> str: + if optional: + return "None" + min_size = 0 + for item in desc.get("size") or []: + if item is None: + continue + if isinstance(item, tuple): + min_size = max(min_size, item[0]) + elif isinstance(item, int): + min_size = max(min_size, item) + if min_size <= 0: + return "[]" + inner = self._sequence_of_element(module, element, parent=parent) + if inner.startswith("ASN1F_"): + return "[]" + return f"[{inner}()]" + + def _sequence_of_element(self, module: str, element: Dict[str, Any], parent: str = "seqof") -> str: + elem_type = element.get("type") + if elem_type in ("SEQUENCE", "CHOICE"): + cls = self._register_inline_packet(module, parent, element) + return cls + if elem_type in PRIMITIVE_TYPES: + cls = self._primitive_field_class(element) + self.import_fields.add(cls) + if cls in ("ASN1F_ENUMERATED", "ASN1F_FLAGS"): + return self._primitive_field("item", element, "", False) + return cls + try: + elem_mod, elem_name, elem_desc = self._resolve(module, elem_type) + except KeyError: + self.import_fields.add("ASN1F_STRING") + return "ASN1F_STRING" + if elem_desc.get("parameters"): + self.import_fields.add("ASN1F_STRING") + return "ASN1F_STRING" + if self._needs_packet(elem_mod, elem_name, elem_desc): + return self.class_names[(elem_mod, elem_name)] + cls = self._primitive_field_class(elem_desc) + self.import_fields.add(cls) + if cls in ("ASN1F_ENUMERATED", "ASN1F_FLAGS"): + return self._primitive_field("item", elem_desc, "", False) + return cls + + def _primitive_field_class(self, desc: Dict[str, Any]) -> str: + base = desc.get("type") + if base == "INTEGER": + return "ASN1F_INTEGER" + if base == "ENUMERATED": + return "ASN1F_ENUMERATED" + if base == "BOOLEAN": + return "ASN1F_BOOLEAN" + if base == "NULL": + return "ASN1F_NULL" + if base == "BIT STRING": + if desc.get("named-bits"): + return "ASN1F_FLAGS" + return "ASN1F_BIT_STRING" + if base == "OCTET STRING": + return "ASN1F_STRING" + if base == "OBJECT IDENTIFIER": + return "ASN1F_OID" + if base in STRING_TYPES: + return STRING_TYPES[base] + return "ASN1F_STRING" + + def _enum_mapping(self, desc: Dict[str, Any]) -> Dict[str, int]: + named = desc.get("named-numbers") or desc.get("values") + if not named: + return {} + if isinstance(named, dict): + return {k: v for k, v in named.items() if v is not None} + mapping: Dict[str, int] = {} + for item in named: + if item is None: + continue + k, v = item + if k is not None: + mapping[k] = v + return mapping + + def _enum_default(self, desc: Dict[str, Any]) -> int: + mapping = self._enum_mapping(desc) + if mapping: + return min(mapping.values()) + values = [v for v in (desc.get("values") or []) if v is not None] + if values: + return values[0][1] + restricted = desc.get("restricted-to") + if restricted: + return restricted[0][0] + return 0 + + def _primitive_field(self, member_name: str, desc: Dict[str, Any], tag_args: str, in_choice: bool, optional: bool = False) -> str: + base = desc.get("type") + cls = self._primitive_field_class(desc) + self.import_fields.add(cls) + default: Any + if optional: + default = None + elif base == "BOOLEAN": + default = False + elif base == "NULL": + default = None + elif base in ("OCTET STRING", "IA5String", "UTF8String", "NumericString", "PrintableString"): + # A sized string is encoded with that many units, so an empty + # default would not even build. + size = self._min_size(desc) + default = b"\x00" * size if base == "OCTET STRING" else "0" * size + elif base == "BIT STRING": + default = "0" * self._min_size(desc) + elif base == "INTEGER": + default = self._integer_default(desc) + elif base == "ENUMERATED": + default = self._enum_default(desc) + else: + default = 0 + + extra = self._integer_args(desc, enumerated=(cls == "ASN1F_ENUMERATED")) + self._size_args(desc, cls) + if cls == "ASN1F_BIT_STRING" and not optional: + # ASN1F_FLAGS already reads its default as a bit string. + extra += ", default_readable=False" + if cls == "ASN1F_ENUMERATED": + mapping = self._enum_mapping(desc) + if mapping: + if default not in mapping.values(): + default = min(mapping.values()) + scapy_enum = {v: k for k, v in mapping.items()} + return f'{cls}("{member_name}", {py_literal(default)}, {py_literal(scapy_enum)}{extra}{tag_args})' + if cls == "ASN1F_FLAGS" and desc.get("named-bits"): + mapping = [bit[0] for bit in desc["named-bits"]] + return f'{cls}("{member_name}", {py_literal(default)}, {py_literal(mapping)}{extra}{tag_args})' + + if in_choice and cls in ("ASN1F_INTEGER", "ASN1F_ENUMERATED", "ASN1F_BOOLEAN", "ASN1F_STRING", "ASN1F_BIT_STRING", "ASN1F_NULL", "ASN1F_OID"): + if tag_args: + tag_num = tag_args.split("=")[-1] + extra += f", implicit_tag={tag_num}" + return f'{cls}("{member_name}", {py_literal(default)}{extra})' + return f'{cls}("{member_name}", {py_literal(default)}{extra}{tag_args})' + + def _choice_root( + self, + module: str, + type_name: str, + desc: Dict[str, Any], + current_key: Optional[Tuple[str, str]] = None, + result_order: Optional[List[Tuple[str, str]]] = None, + ) -> str: + self.import_fields.add("ASN1F_CHOICE") + members = root_members(desc.get("members", [])) + alts: List[str] = [] + for member in members: + member_type = member["type"] + if member_type in PRIMITIVE_TYPES: + alts.append(self._primitive_field( + sanitize_name(member.get("name") or "alt"), + member, + "", + in_choice=True, + )) + else: + try: + mod, name, member_desc = self._resolve(module, member_type) + except KeyError: + continue + if member_desc.get("parameters"): + continue + base = member_desc.get("type") + if base in ("SEQUENCE", "CHOICE"): + if (mod, name) not in self.class_names: + continue + if self._is_forward_ref(mod, name, current_key, result_order): + self.import_fields.add("ASN1F_STRING") + alts.append( + self._primitive_field( + sanitize_name(member.get("name") or "alt"), + {"type": "OCTET STRING"}, + "", + in_choice=True, + ) + ) + else: + alts.append(self.class_names[(mod, name)]) + else: + alts.append(self._primitive_field( + sanitize_name(member.get("name") or "alt"), + member_desc, + "", + in_choice=True, + )) + if not alts: + self.import_fields.add("ASN1F_NULL") + alts.append('ASN1F_NULL("unsupported", None)') + default = "None" + if alts: + first = alts[0] + if first[0].isupper() and not first.startswith("ASN1F_"): + default = f"{first}()" + ext_args = ", uper_extensible=True" if is_extensible(desc.get("members", [])) else "" + return "ASN1F_CHOICE(\n " + '"root", %s,\n ' % default + ",\n ".join(alts) + ext_args + "\n )" + + def _sequence_root( + self, + module: str, + type_name: str, + desc: Dict[str, Any], + current_key: Optional[Tuple[str, str]] = None, + result_order: Optional[List[Tuple[str, str]]] = None, + ) -> str: + self.import_fields.add("ASN1F_SEQUENCE") + fields = [] + for member in root_members(desc.get("members", [])): + field = self._field_for_member( + module, member, parent=type_name, + current_key=current_key, result_order=result_order, + ) + if field: + fields.append(field) + if not fields: + fields.append('ASN1F_NULL("placeholder", None)') + self.import_fields.add("ASN1F_NULL") + ext_args = ", uper_extensible=True" if is_extensible(desc.get("members", [])) else "" + return "ASN1F_SEQUENCE(\n " + ",\n ".join(fields) + ext_args + "\n )" + + def _packet_class( + self, + module: str, + type_name: str, + current_key: Optional[Tuple[str, str]] = None, + result_order: Optional[List[Tuple[str, str]]] = None, + ) -> str: + desc = self.spec[module]["types"][type_name] + py_name = self.class_names[(module, type_name)] + key = current_key or (module, type_name) + if desc["type"] == "CHOICE": + root = self._choice_root(module, type_name, desc, key, result_order) + else: + root = self._sequence_root(module, type_name, desc, key, result_order) + return ( + "class %s(ASN1_Packet):\n" + " ASN1_codec = ASN1_Codecs.PER\n" + " ASN1_root = %s\n" % (py_name, root) + ) + + def _reachable_type_keys(self) -> Set[Tuple[str, str]]: + roots: List[Tuple[str, str]] = [] + for root in ROOT_MESSAGES: + for module, data in self.spec.items(): + if root in data.get("types", {}): + roots.append((module, root)) + break + seen: Set[Tuple[str, str]] = set() + queue = list(roots) + while queue: + key = queue.pop(0) + if key in seen or key not in self.class_names: + continue + seen.add(key) + module, type_name = key + desc = self.spec[module]["types"][type_name] + for dep in self._collect_packet_refs(module, desc, type_name): + if dep in self.class_names and dep not in seen: + queue.append(dep) + return seen + + def generate(self) -> str: + self._discover_inline_types() + reachable = self._reachable_type_keys() + self.class_names = { + key: name for key, name in self.class_names.items() + if key in reachable + } + self.used_py_names = set(self.class_names.values()) + ordered: List[Tuple[str, str]] = [] + for module, data in self.spec.items(): + for type_name, desc in data.get("types", {}).items(): + key = (module, type_name) + if key in reachable and self._needs_packet(module, type_name, desc): + ordered.append(key) + + # Topological order: referenced packets before users + deps: Dict[Tuple[str, str], Set[Tuple[str, str]]] = {k: set() for k in ordered} + for key in ordered: + module, type_name = key + desc = self.spec[module]["types"][type_name] + refs = self._collect_packet_refs(module, desc, type_name) + deps[key] = refs + + result_order: List[Tuple[str, str]] = [] + remaining = list(ordered) + while remaining: + progressed = False + for key in list(remaining): + if all( + dep not in remaining or dep in result_order + for dep in deps.get(key, ()) + ): + result_order.append(key) + remaining.remove(key) + progressed = True + if not progressed: + ready = [ + k for k in remaining + if all(dep not in remaining for dep in deps.get(k, ())) + ] + if ready: + key = ready[0] + else: + key = max( + remaining, + key=lambda k: ( + sum(1 for dep in deps.get(k, ()) if dep in result_order), + -sum(1 for dep in deps.get(k, ()) if dep in remaining), + ), + ) + result_order.append(key) + remaining.remove(key) + + root_keys: List[Tuple[str, str]] = [] + for root in ROOT_MESSAGES: + for module, data in self.spec.items(): + if root in data.get("types", {}): + key = (module, root) + if key in result_order: + result_order.remove(key) + root_keys.append(key) + break + result_order.extend(root_keys) + + class_lines: List[str] = [] + for module, type_name in result_order: + class_lines.append( + self._packet_class(module, type_name, (module, type_name), result_order) + ) + class_lines.append("") + + lines = [ + "# SPDX-License-Identifier: GPL-2.0-only", + "# This file is part of Scapy", + "# See https://scapy.net/ for more information", + "# AUTO-GENERATED by scapy/tools/generate_its_asn1.py - DO NOT EDIT", + "", + "# scapy.contrib.status = skip", + '"""', + "ETSI ITS ASN.1 packets (UPER): CAM, DENM, IVIM, SPATEM, MAPEM.", + '"""', + "", + "from scapy.asn1.asn1 import ASN1_Codecs", + ] + field_imports = sorted(self.import_fields) + if field_imports: + lines.append("from scapy.asn1fields import (") + for name in field_imports: + lines.append(" %s," % name) + lines.append(")") + lines.append("from scapy.asn1packet import ASN1_Packet") + lines.append("") + lines.append("# Registers the PER codec that ASN1_Codecs.PER refers to") + lines.append("import scapy.contrib.uper # noqa: F401") + lines.append("") + lines.extend(class_lines) + + for root in ROOT_MESSAGES: + for module, data in self.spec.items(): + if root in data.get("types", {}): + cls = self.class_names[(module, root)] + lines.append(f"{root} = {cls}") + break + lines.append("") + lines.append("__all__ = [") + for root in ROOT_MESSAGES: + lines.append(' "%s",' % root) + for module, type_name in result_order: + lines.append(' "%s",' % self.class_names[(module, type_name)]) + lines.append("]") + lines.append("") + return "\n".join(lines) + + def _inline_key(self, module: str, parent: str, member: Dict[str, Any]) -> Tuple[str, str]: + member_name = sanitize_name(member.get("name") or "inline") + type_name = inline_type_name(sanitize_name(parent), member_name) + return module, type_name + + def _collect_packet_refs( + self, + module: str, + desc: Dict[str, Any], + parent_name: str = "", + ) -> Set[Tuple[str, str]]: + refs: Set[Tuple[str, str]] = set() + + def add_type_ref(mod: str, type_name: str) -> None: + if type_name in PRIMITIVE_TYPES or type_name in ("SEQUENCE", "CHOICE", "SEQUENCE OF"): + return + try: + rmod, rname, rdesc = self._resolve(mod, type_name) + except KeyError: + return + if rdesc.get("type") == "SEQUENCE OF": + element = rdesc.get("element", {}) + elem_type = element.get("type") + if elem_type in ("SEQUENCE", "CHOICE"): + return + add_type_ref(rmod, elem_type) + return + if self._needs_packet(rmod, rname, rdesc): + refs.add((rmod, rname)) + + def walk_members(members: List[Dict[str, Any]], mod: str, parent: str) -> None: + for member in members: + if not member: + continue + mtype = member.get("type") + if mtype in ("SEQUENCE", "CHOICE"): + key = self._inline_key(mod, parent, member) + if key in self.class_names: + refs.add(key) + inline_desc = self.spec[mod]["types"].get(key[1]) + if inline_desc: + refs.update(self._collect_packet_refs( + mod, inline_desc, key[1], + )) + continue + if mtype == "SEQUENCE OF": + element = member.get("element", {}) + elem_type = element.get("type") + if elem_type in ("SEQUENCE", "CHOICE"): + continue + add_type_ref(mod, elem_type) + elif mtype not in PRIMITIVE_TYPES: + add_type_ref(mod, mtype) + + base = desc.get("type") + if base == "SEQUENCE": + walk_members( + root_members(desc.get("members", [])), + module, + parent_name, + ) + elif base == "CHOICE": + walk_members( + root_members(desc.get("members", [])), + module, + parent_name, + ) + return refs + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "-o", + "--output", + type=Path, + default=Path(__file__).resolve().parent.parent + / "contrib" / "automotive" / "v2x" / "packets.py", + help="Output Python file", + ) + args = parser.parse_args() + + files = asn_file_list() + missing = [str(f) for f in files if not f.exists()] + if missing: + raise SystemExit("Missing ASN.1 files:\n " + "\n ".join(missing)) + + spec = parse_files([str(f) for f in files]) + import asn1tools + compiled = asn1tools.compile_files([str(f) for f in files], codec="uper") + generator = ITSASN1Generator(spec, compiled_types=set(compiled.types)) + output = generator.generate() + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(output, encoding="utf-8") + print(f"Wrote {args.output} ({output.count(chr(10))} lines)") + + +if __name__ == "__main__": + main() diff --git a/test/contrib/automotive/v2x.uts b/test/contrib/automotive/v2x.uts new file mode 100644 index 00000000000..c7e38391e54 --- /dev/null +++ b/test/contrib/automotive/v2x.uts @@ -0,0 +1,272 @@ +% Tests for the ETSI ITS V2X ASN.1 messages (UPER) + +# +# Try me with: +# bash test/run_tests -t test/contrib/automotive/v2x.uts -F + ++ ETSI ITS load + += load the layer + +from scapy.contrib.automotive.v2x import CAM, DENM, IVIM, MAPEM, SPATEM +from scapy.contrib.automotive.v2x.packets import ( + ActionID, + Altitude, + CauseCode, + DecentralizedEnvironmentalNotificationMessage, + DeltaReferencePosition, + EventPoint, + Heading, + ItsPduHeader, + LocationContainer, + ManagementContainer, + PathHistory, + PosConfidenceEllipse, + ReferencePosition, + SituationContainer, + Speed, +) +from scapy.asn1packet import ASN1_Packet +from scapy.packet import raw + +# LF Edge InstantX UPER example (DENM inside a G5/BTP payload): +# https://github.com/lf-edge/instantx/blob/main/docs/Encoding.md#examples +# The ITS PDU (ItsPduHeader + DENM) starts after the BTP-B header. +INSTANTX_DENM_ITS_HEX = ( + "0201012fd537c78097ea9b81ed9f2d8fa1a3c7cb63e868f2f19a1e6649cc65d179179" + "00018b5010546001f3056c1c0061000dff8480e018d841196eac3e4a01c780c518d326" + "1453f0077e000" +) + +def build_instantx_denm(): + return DENM( + header=ItsPduHeader( + protocolVersion=2, + messageID=1, + stationID=19911991, + ), + denm=DecentralizedEnvironmentalNotificationMessage( + management=ManagementContainer( + actionID=ActionID( + originatingStationID=19911991, + sequenceNumber=987, + ), + detectionTime=1071266991390, + referenceTime=1071266991390, + termination=None, + eventPosition=ReferencePosition( + latitude=-109791002, + longitude=-112004003, + positionConfidenceEllipse=PosConfidenceEllipse( + semiMajorConfidence=377, + semiMinorConfidence=377, + semiMajorOrientation=0, + ), + altitude=Altitude( + altitudeValue=1200, + altitudeConfidence=1, + ), + ), + relevanceDistance=0, + relevanceTrafficDirection=0, + validityDuration=86400, + transmissionInterval=500, + stationType=5, + ), + situation=SituationContainer( + informationQuality=3, + eventType=CauseCode(causeCode=14, subCauseCode=0), + linkedCause=CauseCode(causeCode=97, subCauseCode=0), + eventHistory=[ + EventPoint( + eventPosition=DeltaReferencePosition( + deltaLatitude=-123, + deltaLongitude=897, + deltaAltitude=20, + ), + eventDeltaTime=1706733817, + informationQuality=1, + ), + EventPoint( + eventPosition=DeltaReferencePosition( + deltaLatitude=456, + deltaLongitude=789, + deltaAltitude=10, + ), + informationQuality=2, + ), + ], + ), + location=LocationContainer( + eventSpeed=Speed(speedValue=1300, speedConfidence=127), + eventPositionHeading=Heading( + headingValue=14, + headingConfidence=127, + ), + traces=[PathHistory()], + ), + ), + ) + += the messages of the layer are exported + +assert [c.__name__ for c in (CAM, DENM, IVIM, SPATEM, MAPEM)] == \ + ["CAM", "DENM", "IVIM", "SPATEM", "MAPEM"] + ++ ETSI ITS messages + += every message builds with its default values + +for cls, message_id in [(CAM, 2), (DENM, 1), (IVIM, 6), (SPATEM, 4), (MAPEM, 5)]: + pkt = cls() + pkt.header = ItsPduHeader( + protocolVersion=1, + messageID=message_id, + stationID=42, + ) + data = raw(pkt) + assert data + assert raw(cls(data)) == data + += every packet class of the layer round-trips + +from scapy.contrib.automotive.v2x import packets + +classes = [ + getattr(packets, name) for name in packets.__all__ + if isinstance(getattr(packets, name), type) and + issubclass(getattr(packets, name), ASN1_Packet) +] +built = 0 +for cls in classes: + try: + data = raw(cls()) + except Exception: + # A CHOICE without an alternative and a SEQUENCE OF that may not be + # empty have no encodable default value. + continue + built += 1 + assert raw(cls(data)) == data, cls.__name__ + +assert len(classes) == 177 + +assert built == 161 + += a CHOICE between alternatives of one type round-trips + +from scapy.contrib.automotive.v2x.packets import EuVehicleCategoryCode + +# Reference (asn1tools) for the six alternatives of +# EuVehicleCategoryCode ::= CHOICE { +# euVehicleCategoryL ENUMERATED {l1, ..., l7}, +# euVehicleCategoryM ENUMERATED {m1, m2, m3}, +# euVehicleCategoryN ENUMERATED {n1, n2, n3}, +# euVehicleCategoryO ENUMERATED {o1, ..., o4}, +# euVehilcleCategoryT NULL, euVehilcleCategoryG NULL } +# Four of them are ENUMERATED and two NULL, so only their position tells them +# apart: l3, m2, n1, o4, T and G. +for encoded in [b"\x08", b"\x28", b"\x40", b"\x78", b"\x80", b"\xa0"]: + assert raw(EuVehicleCategoryCode(encoded)) == encoded + +assert EuVehicleCategoryCode(b"\x78").root.val == 3 + += a CHOICE reports the alternative it misses + +from scapy.contrib.automotive.v2x.packets import LaneAttributes + +try: + raw(LaneAttributes()) + assert False +except ASN1_Error as e: + assert "unknown alternative" in str(e) + ++ ETSI ITS DENM interoperability + += the InstantX DENM example builds byte for byte + +assert raw(build_instantx_denm()) == bytes.fromhex(INSTANTX_DENM_ITS_HEX) + += the InstantX DENM example dissects + +pkt = DENM(bytes.fromhex(INSTANTX_DENM_ITS_HEX)) +assert pkt.header.protocolVersion.val == 2 +assert pkt.header.messageID.val == 1 +assert pkt.header.stationID.val == 19911991 +mgmt = pkt.denm.management +assert mgmt.actionID.originatingStationID.val == 19911991 +assert mgmt.actionID.sequenceNumber.val == 987 +assert mgmt.detectionTime.val == 1071266991390 +assert mgmt.referenceTime.val == 1071266991390 +assert mgmt.termination is None +assert mgmt.eventPosition.latitude.val == -109791002 +assert mgmt.eventPosition.longitude.val == -112004003 +assert mgmt.eventPosition.altitude.altitudeValue.val == 1200 +assert mgmt.validityDuration.val == 86400 +assert mgmt.transmissionInterval.val == 500 +assert mgmt.stationType.val == 5 +assert pkt.denm.situation.eventType.causeCode.val == 14 +assert pkt.denm.situation.linkedCause.causeCode.val == 97 +assert len(pkt.denm.situation.eventHistory) == 2 +assert pkt.denm.situation.eventHistory[0].eventDeltaTime.val == 1706733817 +assert pkt.denm.situation.eventHistory[0].eventPosition.deltaLatitude.val == -123 +assert pkt.denm.situation.eventHistory[1].eventPosition.deltaLatitude.val == 456 +assert pkt.denm.location.eventSpeed.speedValue.val == 1300 +assert len(pkt.denm.location.traces) == 1 +assert len(pkt.denm.location.traces[0].pathPoints) == 0 + += the InstantX DENM example round-trips + +data = raw(build_instantx_denm()) +assert raw(DENM(data)) == data + ++ ETSI ITS containers + += a management container builds + +mgmt = ManagementContainer( + actionID=ActionID(originatingStationID=1, sequenceNumber=1), + detectionTime=1, + referenceTime=1, + eventPosition=ReferencePosition( + latitude=0, + longitude=0, + positionConfidenceEllipse=PosConfidenceEllipse( + semiMajorConfidence=0, + semiMinorConfidence=0, + semiMajorOrientation=0, + ), + altitude=Altitude(altitudeValue=0, altitudeConfidence=0), + ), + stationType=0, +) +assert raw(mgmt) + += a situation container round-trips its event history + +situation = SituationContainer( + informationQuality=1, + eventType=CauseCode(causeCode=1, subCauseCode=0), + eventHistory=[ + EventPoint( + eventPosition=DeltaReferencePosition( + deltaLatitude=0, + deltaLongitude=0, + deltaAltitude=0, + ), + eventDeltaTime=1706733817, + informationQuality=1, + ), + ], +) +decoded = SituationContainer(raw(situation)) +assert decoded.informationQuality.val == 1 +assert decoded.eventType.causeCode.val == 1 +assert len(decoded.eventHistory) == 1 +assert decoded.eventHistory[0].eventDeltaTime.val == 1706733817 + += a location container round-trips its traces + +location = LocationContainer(traces=[PathHistory()]) +decoded = LocationContainer(raw(location)) +assert len(decoded.traces) == 1 +assert len(decoded.traces[0].pathPoints) == 0 diff --git a/test/contrib/oer.uts b/test/contrib/oer.uts new file mode 100644 index 00000000000..7a4a61b3a40 --- /dev/null +++ b/test/contrib/oer.uts @@ -0,0 +1,1270 @@ +% Tests for ASN.1 OER encoding + +# +# Try me with: +# bash test/run_tests -t test/scapy/layers/oer.uts -F + ++ ASN.1 OER load += prepare helpers and packet classes +import scapy.contrib.oer + +from scapy.contrib.oer import * + +from scapy.packet import raw + +class OERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class OERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class OEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class OERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class OERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class OERNestedSequenceTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ASN1F_INTEGER("id", 0), + ) + +class OERSequenceOfWithTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ASN1F_INTEGER("id", 0), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +INTEGER_VECTORS = [ + ("A", 0, lambda v: OERcodec_INTEGER.enc(v), b"\x01\x00"), + ("A", 128, lambda v: OERcodec_INTEGER.enc(v), b"\x02\x00\x80"), + ("A", 100000, lambda v: OERcodec_INTEGER.enc(v), b"\x03\x01\x86\xa0"), + ("A", -255, lambda v: OERcodec_INTEGER.enc(v), b"\x02\xff\x01"), + ("A", -1234567, lambda v: OERcodec_INTEGER.enc(v), b"\x03\xed)y"), + ("B", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\xfe"), + ("C", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\xff\xfe"), + ("D", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=4), b"\xff\xff\xff\xfe"), + ( + "E", + -2, + lambda v: OERcodec_INTEGER.enc(v, size_len=8), + b"\xff\xff\xff\xff\xff\xff\xff\xfe", + ), + # F to I have a lower bound of zero, so they are unsigned: the width and + # the signedness come from the declared type, not from the value. + ( + "F", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=1, oer_unsigned=True), + b"\x80", + ), + ( + "G", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=2, oer_unsigned=True), + b"\x00\x80", + ), + ( + "G", + 1000, + lambda v: OERcodec_INTEGER.enc(v, size_len=2, oer_unsigned=True), + b"\x03\xe8", + ), + ( + "H", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=4, oer_unsigned=True), + b"\x00\x00\x00\x80", + ), + ( + "I", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=8, oer_unsigned=True), + b"\x00\x00\x00\x00\x00\x00\x00\x80", + ), + ("B", 1, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\x01"), + ("K", 1, lambda v: OER_unsigned_integer_enc(v), b"\x01\x01"), + ("K", 128, lambda v: OER_unsigned_integer_enc(v), b"\x01\x80"), + ("L", -128, lambda v: OER_signed_integer_enc(v), b"\x01\x80"), +] + +BOOLEAN_VECTORS = [ + (True, lambda v: OERcodec_BOOLEAN.enc(1 if v else 0), b"\xff"), + (False, lambda v: OERcodec_BOOLEAN.enc(1 if v else 0), b"\x00"), +] + +ENUMERATED_VECTORS = [ + ("A", "a", 1, b"\x01"), + ("B", "a", 128, b"\x82\x00\x80"), + ("C", "a", 0, b"\x00"), + ("C", "b", 127, b"\x7f"), + ("E", "a", -1, b"\x81\xff"), +] + +OID_VECTORS = [ + ("1.2", lambda v: OERcodec_OID.enc(v), b"\x01*"), + ("1.2.3321", lambda v: OERcodec_OID.enc(v), b"\x03*\x99y"), +] + +OCTET_STRING_VECTORS = [ + (b"\x12\x34", 0, b"\x02\x124"), + (b"\x12\x34\x56", 3, b"\x124V"), +] + +BIT_STRING_VECTORS = [ + ("0100", b"\x02\x04@"), + ("01000001", b"\x02\x00A"), +] + +SCAPY_DECODE_VECTORS = [ + ("A", 42, b"\x01*"), + ("F", 200, b"\xc8"), + ("B", -99, b"\x9d"), +] + +_OER_CODEC_CLASSES = ( + OERcodec_INTEGER, + OERcodec_BOOLEAN, + OERcodec_NULL, + OERcodec_STRING, + OERcodec_OID, + OERcodec_ENUMERATED, + OERcodec_BIT_STRING, +) + +_DECODE_ERRORS = ( + OER_Decoding_Error, + ASN1_Decoding_Error, + ASN1_Error, + ValueError, + IndexError, +) + +class OERFuzzRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +def _fuzz_packets(): + # type: () -> Iterable[Type[ASN1_Packet]] + return (OERFuzzRecord,) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +class _InnerRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("mode", ASN1_INTEGER(0), ["off", "on"]), + ) + +class _EncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_ENCAPS("payload", None, _InnerRecord), + ) + +class _FlagsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("f", "000", ["read", "write", "exec"]), + ) + +class _SetOfRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SET_OF("items", [], ASN1F_INTEGER) + +class _PacketFieldRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_PacketField("data", b""), + ) + +class _ExplicitPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2) + +class _BitEncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord), + ) + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + +import scapy.contrib.uper + +class OEREmptySequenceOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OEREnumField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_ENUMERATED("e", 0, {0: "a", 1: "b"}) + +class OERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_BIT_STRING("b", "0101") + +class OERNullRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_NULL("z", 0), + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ) + +class OEROidField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_OID("oid", "1.2.3") + +class OERInnerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, size_len=1, oer_unsigned=True), + ) + +class OERPacketChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE("c", None, OERInnerSeq, ASN1F_INTEGER) + +class OERAltA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("i", 0), + ) + +class OERAltB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("b", True), + ) + +class OERTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE( + "c", None, + ASN1F_PACKET("a1", None, OERAltA, explicit_tag=0xA0), + ASN1F_PACKET("a2", None, OERAltB, explicit_tag=0xA1), + ), + ) + +class OERUnsignedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, + ) + ++ ASN.1 OER codec += OER length determinant short form +OER_len_enc(3) == b"\x03" += OER length determinant long form +OER_len_enc(200) == b"\x81\xc8" += OER boolean false +OERcodec_BOOLEAN.enc(0) == b"\x00" += OER boolean true +OERcodec_BOOLEAN.enc(1) == b"\xff" += OER null +OERcodec_NULL.enc(None) == b"" += OER unconstrained integer +OERcodec_INTEGER.enc(4) == b"\x01\x04" += OER constrained unsigned integer +OERcodec_INTEGER.enc(4, size_len=1) == b"\x04" += OER constrained signed integer +OERcodec_INTEGER.enc(4, size_len=2) == b"\x00\x04" += OER enumerated short form +OERcodec_ENUMERATED.enc(6) == b"\x06" += OER octet string +OERcodec_STRING.enc(b"ABC") == b"\x03ABC" += OER OID +OERcodec_OID.enc("1.2.3") == b"\x02\x2a\x03" += OER integer roundtrip +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(12345)) +x.val == 12345 and r == b"" += OER boolean roundtrip +x, r = OERcodec_BOOLEAN.do_dec(OERcodec_BOOLEAN.enc(1)) +x.val == 1 and r == b"" += OER ASN1 object encoding +ASN1_INTEGER(42).enc(ASN1_Codecs.OER) == b"\x01*" += OER codec registration +ASN1_Class_UNIVERSAL.INTEGER.get_codec(ASN1_Codecs.OER) is OERcodec_INTEGER + ++ ASN.1 OER codec (extended) += OER length zero +OER_len_enc(0) == b"\x00" += OER length boundary short form +OER_len_enc(127) == b"\x7f" += OER length boundary long form +OER_len_enc(128) == b"\x81\x80" += OER length roundtrip +l, r = OER_len_dec(OER_len_enc(999)) +l == 999 and r == b"" += OER signed integer zero +OER_signed_integer_enc(0) == b"\x01\x00" += OER signed integer negative +OER_signed_integer_enc(-255) == b"\x02\xff\x01" += OER signed integer large +OER_signed_integer_enc(100000) == b"\x03\x01\x86\xa0" += OER signed integer roundtrip +v, r = OER_signed_integer_dec(OER_signed_integer_enc(-1234567)) +v == -1234567 and r == b"" += OER unsigned integer zero +OER_unsigned_integer_enc(0) == b"\x01\x00" += OER unsigned integer roundtrip +v, r = OER_unsigned_integer_dec(OER_unsigned_integer_enc(65535)) +v == 65535 and r == b"" += OER fixed unsigned 1 byte +OERcodec_INTEGER.enc(255, size_len=1, oer_unsigned=True) == b"\xff" += OER fixed signed 2 bytes negative +OERcodec_INTEGER.enc(-2, size_len=2) == b"\xff\xfe" += OER fixed signed 4 bytes +OERcodec_INTEGER.enc(-2, size_len=4) == b"\xff\xff\xff\xfe" += OER enumerated long form +OERcodec_ENUMERATED.enc(128) == b"\x82\x00\x80" += OER enumerated negative +OERcodec_ENUMERATED.enc(-1) == b"\x81\xff" += OER enumerated roundtrip +x, r = OERcodec_ENUMERATED.do_dec(OERcodec_ENUMERATED.enc(128)) +x.val == 128 and r == b"" += OER null roundtrip +x, r = OERcodec_NULL.do_dec(OERcodec_NULL.enc(None)) +x.val is None and r == b"" += OER octet string empty +OERcodec_STRING.enc(b"") == b"\x00" += OER octet string fixed size +OERcodec_STRING.enc(b"\x12\x34\x56", size_len=3) == b"\x12\x34\x56" += OER octet string roundtrip +x, r = OERcodec_STRING.do_dec(OERcodec_STRING.enc(b"\x12\x34")) +x.val == b"\x12\x34" and r == b"" += OER OID 1.2 +OERcodec_OID.enc("1.2") == b"\x01\x2a" += OER OID roundtrip +x, r = OERcodec_OID.do_dec(OERcodec_OID.enc("1.2.3321")) +x.val == "1.2.3321" and r == b"" += OER bit string variable size +OERcodec_BIT_STRING.enc("0100") == b"\x02\x04\x40" += OER bit string roundtrip +x, r = OERcodec_BIT_STRING.do_dec(OERcodec_BIT_STRING.enc("01000001")) +x.val == "01000001" and r == b"" += OER IA5 string +OERcodec_IA5_STRING.enc(b"ABC") == b"\x03ABC" += OER tag short form +OER_tag_enc(1, OER_CLASS_CONTEXT) == b"\x81" += OER tag roundtrip +cls, num, r = OER_tag_dec(OER_tag_enc(1, OER_CLASS_CONTEXT)) +cls == OER_CLASS_CONTEXT and num == 1 and r == b"" += OER sequence concat +OERcodec_SEQUENCE.enc([ASN1_INTEGER(4), ASN1_INTEGER(5)]) == b"\x01\x04\x01\x05" += OER ASN1 boolean object +ASN1_BOOLEAN(1).enc(ASN1_Codecs.OER) == b"\xff" += OER ASN1 null object +ASN1_NULL(None).enc(ASN1_Codecs.OER) == b"" + ++ ASN.1 OER review fixes += OER fixed integer decode roundtrip +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(128, size_len=1, oer_unsigned=True), size_len=1, oer_unsigned=True) +x.val == 128 and r == b"" += OER fixed integer signed decode +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(-2, size_len=2), size_len=2) +x.val == -2 and r == b"" += OER fixed octet string decode +x, r = OERcodec_STRING.do_dec(OERcodec_STRING.enc(b"\x12\x34\x56", size_len=3), size_len=3) +x.val == b"\x12\x34\x56" and r == b"" += OER does not encode the tag of a component +# X.696 encodes none, so OER hooks no tagging and the field is left alone +assert ASN1_Codecs.OER.hook("tagging_enc") is None + +assert ASN1_Codecs.OER.hook("tagging_dec") is None + +fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) + +pkt = OERTaggedInteger() + +assert fld._tagging_enc(pkt, b"\x05", explicit_tag=0xA0) == b"\x05" + +assert fld._tagging_dec(pkt, b"\x05", explicit_tag=0xA0) == (None, b"\x05") + +True += OER tag long form +# X.696 8.7.2.2: a tag number of 63 or more spills into continuation octets +assert OER_tag_enc(100, OER_CLASS_CONTEXT) == b"\xbf\x64" + +assert OER_tag_dec(b"\xbf\x64\x01") == (OER_CLASS_CONTEXT, 100, b"\x01") + +assert OER_tag_dec(OER_tag_enc(16384, OER_CLASS_PRIVATE)) == (OER_CLASS_PRIVATE, 16384, b"") + +_raises(OER_Decoding_Error, lambda: OER_tag_dec(b"\xbf\x81")) + +_raises(OER_Decoding_Error, lambda: OER_tag_dec(b"")) + +True + ++ ASN.1 OER packets, interop and fuzz += oer field explicit tag +pkt = OERTaggedInteger(n=5) + +# X.696 encodes no tag for a component, whatever the tagging environment +assert raw(pkt) == b"\x01\x05" + +decoded = _roundtrip(OERTaggedInteger, pkt) + +assert decoded.n.val == 5 + +True + += oer field fixed size +pkt = OERFixedFields(n=200, s=b"ABC") + +assert raw(pkt) == b"\xc8ABC" + +decoded = _roundtrip(OERFixedFields, pkt) + +assert decoded.n.val == 200 + +assert decoded.s.val == b"ABC" + +True + += oer field optional +present = OEROptionalField(id=1, extra=7) + +# \x80: preamble with the presence bit set for the single OPTIONAL component +assert raw(present) == b"\x80\x01\x01\x01\x07" + +decoded = _roundtrip(OEROptionalField, present) + +assert decoded.id.val == 1 + +assert decoded.extra.val == 7 + +absent = OEROptionalField(id=1, extra=None) + +assert raw(absent) == b"\x00\x01\x01" + +decoded = _roundtrip(OEROptionalField, absent) + +assert decoded.id.val == 1 + +assert decoded.extra is None + +True + += oer field sequence of +pkt = OERSequenceOfIntegers(values=[1, 2, 3]) + +assert raw(pkt) == b"\x01\x03\x01\x01\x01\x02\x01\x03" + +decoded = _roundtrip(OERSequenceOfIntegers, pkt) + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += oer field choice +as_int = OERChoiceField(c=ASN1_INTEGER(99)) + +assert raw(as_int) == b"\x02\x01c" + +decoded = _roundtrip(OERChoiceField, as_int) + +assert decoded.c.val == 99 + +as_str = OERChoiceField(c=ASN1_STRING("x")) + +assert raw(as_str) == b"\x04\x01x" + +decoded = _roundtrip(OERChoiceField, as_str) + +assert decoded.c.val == b"x" + +True + += oer packet record +pkt = OERRecord( + id=42, flag=True, label="hi", extra=7, values=[1, 2, 3], +) + +expected = ( + b"\x80" + b"\x01*\xff\x02hi\x01\x07" + b"\x01\x03\x01\x01\x01\x02\x01\x03" +) + +assert raw(pkt) == expected + +decoded = _roundtrip(OERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +empty = OERRecord(id=1, flag=False, label="", extra=None, values=[]) + +assert raw(empty) == b"\x00\x01\x01\x00\x00\x01\x00" + +decoded = _roundtrip(OERRecord, empty) + +assert decoded.id.val == 1 + +assert decoded.flag.val == 0 + +assert decoded.label.val == b"" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [] + +True + += oer nested sequence +pkt = OERNestedSequence(id=5, x=3, y=True) + +assert raw(pkt) == b"\x01\x05\x01\x03\xff" + +decoded = _roundtrip(OERNestedSequence, pkt) + +assert decoded.id.val == 5 + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +True + += oer nested sequence trailing +pkt = OERNestedSequenceTrailing(x=3, y=True, id=5) + +assert raw(pkt) == b"\x01\x03\xff\x01\x05" + +decoded = _roundtrip(OERNestedSequenceTrailing, pkt) + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +assert decoded.id.val == 5 + +True + += oer sequence of with trailing +pkt = OERSequenceOfWithTrailing(values=[1, 2], id=7) + +assert raw(pkt) == b"\x01\x02\x01\x01\x01\x02\x01\x07" + +decoded = _roundtrip(OERSequenceOfWithTrailing, pkt) + +assert [x.val for x in decoded.values] == [1, 2] + +assert decoded.id.val == 7 + +True + += primitive interop +for type_name, value, enc, expected in INTEGER_VECTORS: + got = enc(value) + assert got == expected, ( + "integer %s=%r: reference=%r scapy=%r" % + (type_name, value, expected, got) + ) + if type_name == "A": + dec, remain = OERcodec_INTEGER.do_dec(got) + assert remain == b"" and dec.val == value + +for value, enc, expected in BOOLEAN_VECTORS: + got = enc(value) + assert got == expected + dec, remain = OERcodec_BOOLEAN.do_dec(got) + assert remain == b"" and dec.val == (1 if value else 0) + +got = OERcodec_NULL.enc(None) + +assert got == b"" + +for type_name, _enum_name, enum_val, expected in ENUMERATED_VECTORS: + got = OERcodec_ENUMERATED.enc(enum_val) + assert got == expected + dec, remain = OERcodec_ENUMERATED.do_dec(got) + assert remain == b"" and dec.val == enum_val + +for oid, enc, expected in OID_VECTORS: + got = enc(oid) + assert got == expected + dec, remain = OERcodec_OID.do_dec(got) + assert remain == b"" and dec.val == oid + +for data, fixed_size, expected in OCTET_STRING_VECTORS: + got = OERcodec_STRING.enc(data, size_len=fixed_size or 0) + assert got == expected + dec, remain = OERcodec_STRING.do_dec(got, size_len=fixed_size or 0) + assert remain == b"" and dec.val == data + +for bitstr, expected in BIT_STRING_VECTORS: + got = OERcodec_BIT_STRING.enc(bitstr) + assert got == expected + dec, remain = OERcodec_BIT_STRING.do_dec(got) + assert remain == b"" and dec.val == bitstr + +True + += scapy encode reference decode +for type_name, value, encoded in SCAPY_DECODE_VECTORS: + if type_name == "A": + dec, remain = OERcodec_INTEGER.do_dec(encoded) + elif type_name == "F": + dec, remain = OERcodec_INTEGER.do_dec( + encoded, size_len=1, oer_unsigned=True, + ) + else: + dec, remain = OERcodec_INTEGER.do_dec(encoded, size_len=1) + assert remain == b"" and dec.val == value + +for val in [0, 1]: + encoded = OERcodec_BOOLEAN.enc(val) + dec, remain = OERcodec_BOOLEAN.do_dec(encoded) + assert remain == b"" and dec.val == val + +True + += oer fuzz encode +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = raw(fuzz(cls())) + assert isinstance(data, bytes) + +True + += oer fuzz roundtrip +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + cls(raw(fuzz(cls()))) + +True + += oer fuzz codec decode +iterations = 100 + +for codec in _OER_CODEC_CLASSES: + for _ in range(iterations): + data = os.urandom(random.randint(0, 64)) + try: + codec.safedec(data) + except _DECODE_ERRORS: + pass + +True + += oer fuzz packet decode +iterations = 100 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass + +True + ++ ASN.1 OER build and dissect += oer record build roundtrip +pkt = OERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(OERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += oer field dissect +tagged = _dissect(OERTaggedInteger, "0105") + +assert tagged.n.val == 5 + +fixed = _dissect(OERFixedFields, "c8414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(OEROptionalField, "8001010107") + +assert present.id.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(OEROptionalField, "000101") + +assert absent.id.val == 1 + +assert absent.extra is None + +seqof = _dissect(OERSequenceOfIntegers, "0103010101020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +as_int = _dissect(OERChoiceField, "020163") + +assert as_int.c.val == 99 + +as_str = _dissect(OERChoiceField, "040178") + +assert as_str.c.val == b"x" + +True + += oer record dissect +decoded = _dissect( + OERRecord, + "80" + "012aff0268690107" + "0103010101020103", +) +_assert_record(decoded) +empty = _dissect(OERRecord, "00010100000100") +_assert_record_empty(empty) + +True + + ++ ASN.1 OER coverage += oer error str +obj = ASN1_INTEGER(1) + +err = OER_Encoding_Error("enc", encoded=obj, remaining=b"z") + +assert "Already encoded" in str(err) + +err2 = OER_Decoding_Error("dec", decoded=obj, remaining=b"w") + +assert "Already decoded" in str(err2) + +True + += oer ipaddress and sequence +encoded = OERcodec_IPADDRESS.enc("127.0.0.1") + +obj, remain = OERcodec_IPADDRESS.do_dec(encoded) + +assert obj.val == "127.0.0.1" + +assert remain == b"" + +fixed = OERcodec_IPADDRESS.enc("127.0.0.1", size_len=4) + +obj2, remain2 = OERcodec_IPADDRESS.do_dec(fixed, size_len=4) + +assert obj2.val == "127.0.0.1" + +assert remain2 == b"" + +_raises(OER_Encoding_Error, lambda: OERcodec_IPADDRESS.enc("bad-ip")) + +_raises(OER_Decoding_Error, lambda: OERcodec_IPADDRESS.do_dec(b"\x01")) + +assert OERcodec_SEQUENCE.enc(b"payload") == b"payload" + +assert OERcodec_SET.enc(b"payload") == b"payload" + +_raises(OER_Decoding_Error, lambda: OERcodec_SEQUENCE.do_dec(b"\x00")) + +empty, remain = OERcodec_BIT_STRING.do_dec(OERcodec_BIT_STRING.enc("")) + +assert empty.val == "" + +assert remain == b"" + +True + + ++ ASN.1 OER field hooks and packet extras += oer field hooks registered +assert ASN1_Codecs.hooks[ASN1_Codecs.OER] + +assert ASN1_Codecs.OER.hook("sequence_m2i") is not None + +assert ASN1_Codecs.OER.hook("choice_i2m") is not None + +assert ASN1_Codecs.OER.hook("no_such_hook") is None + +True + += oer use_object_enc +fld = OERUnsignedField.ASN1_root + +assert fld.codec_opts["oer_unsigned"] is True + +assert fld._use_object_enc(OERUnsignedField(), ASN1_INTEGER(5)) is False + +assert raw(OERUnsignedField(n=5)) == b"\x05" + +assert _roundtrip(OERUnsignedField, OERUnsignedField(n=5)).n.val == 5 + +True + += oer empty sequence of +pkt = OEREmptySequenceOf(values=[]) + +assert raw(pkt) == b"\x01\x00" + +decoded = _roundtrip(OEREmptySequenceOf, pkt) + +assert decoded.values == [] + +True + += oer enumerated field +pkt = OEREnumField(e=1) + +assert raw(pkt) == b"\x01" + +decoded = _roundtrip(OEREnumField, pkt) + +assert decoded.e.val == 1 + +True + += oer bit string field +pkt = OERBitStringField(b="0101") + +assert raw(pkt) == b"\x02\x04\x50" + +decoded = _roundtrip(OERBitStringField, pkt) + +assert decoded.b.val == "0101" + +True + += oer null and oid fields +null_pkt = OERNullRecord(z=0, n=2) + +assert raw(null_pkt) == b"\x02" + +assert _roundtrip(OERNullRecord, null_pkt).n.val == 2 + +oid_pkt = OEROidField(oid="1.2.3") + +assert raw(oid_pkt) == b"\x02\x2a\x03" + +assert _roundtrip(OEROidField, oid_pkt).oid.val == "1.2.3" + +True + += oer choice with packet alternative +pkt = OERPacketChoice(c=OERInnerSeq(x=3)) + +# \x10: universal 16 (SEQUENCE), without the BER constructed bit +assert raw(pkt) == b"\x10\x03" + +decoded = _roundtrip(OERPacketChoice, pkt) + +assert isinstance(decoded.c, OERInnerSeq) + +assert decoded.c.x.val == 3 + +as_int = OERPacketChoice(c=ASN1_INTEGER(9)) + +decoded_int = _roundtrip(OERPacketChoice, as_int) + +assert decoded_int.c.val == 9 + +True + += oer choice with tagged packet alternatives +# Reference (asn1tools) for +# Ch ::= SEQUENCE { c CHOICE { a1 A, a2 B } } +# A ::= SEQUENCE { i INTEGER }, B ::= SEQUENCE { b BOOLEAN } +# in an AUTOMATIC TAGS module: the alternative tag is the only one encoded. +assert raw(OERTaggedChoice(c=OERAltA(i=4))) == b"\x80\x01\x04" + +assert raw(OERTaggedChoice(c=OERAltB(b=False))) == b"\x81\x00" + +decoded = _roundtrip(OERTaggedChoice, OERTaggedChoice(c=OERAltA(i=4))) + +assert isinstance(decoded.c, OERAltA) and decoded.c.i.val == 4 + +decoded = _roundtrip(OERTaggedChoice, OERTaggedChoice(c=OERAltB(b=False))) + +assert isinstance(decoded.c, OERAltB) and decoded.c.b.val == 0 + +True + += oer dec ignores foreign codec kwargs +# Shared field.codec_opts may include UPER keys after contrib.uper is loaded. +x, remain = OERcodec_ENUMERATED.dec( + b"\x01", uper_enum_values=[0, 1], uper_min=0, +) + +assert x.val == 1 + +assert remain == b"" + +True + + ++ ASN.1 OER X.696 conformance + += oer sequence preamble presence bits +# X.696 16.2.2: one presence bit per OPTIONAL/DEFAULT component, zero padded +# to a whole number of octets. Byte vectors checked against asn1tools. +class OERPreambleOne(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("b", 0, size_len=1, oer_unsigned=True)), + ) + +assert raw(OERPreambleOne(a=1, b=2)) == bytes.fromhex("800102") + +assert raw(OERPreambleOne(a=1, b=None)) == bytes.fromhex("0001") + +assert _dissect(OERPreambleOne, "800102").b.val == 2 + +assert _dissect(OERPreambleOne, "0001").b is None + +class OERPreambleTwo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True)), + ASN1F_optional(ASN1F_BOOLEAN("b", False)), + ) + +assert raw(OERPreambleTwo(a=1, b=None)) == bytes.fromhex("8001") + +assert raw(OERPreambleTwo(a=None, b=True)) == bytes.fromhex("40ff") + +# Nine optionals need a two-octet preamble. +class OERPreambleNine(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(*[ + ASN1F_optional(ASN1F_INTEGER(c, 0, size_len=1, oer_unsigned=True)) + for c in "abcdefghi" + ]) + +nine = OERPreambleNine(a=1, b=None, c=None, d=None, e=None, f=None, g=None, + h=None, i=9) + +assert raw(nine) == bytes.fromhex("80800109") + +assert _roundtrip(OERPreambleNine, nine).i.val == 9 + +# A sequence without OPTIONAL/DEFAULT components has no preamble at all. +class OERNoPreamble(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + ) + +assert raw(OERNoPreamble(a=1)) == bytes.fromhex("01") + +# A DEFAULT component takes a presence bit too, and is omitted when it holds +# the default value. +from scapy.asn1fields import ASN1F_DEFAULT + +class OERDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_DEFAULT( + ASN1F_INTEGER("a", 7, size_len=1, oer_unsigned=True), 7, + ), + ) + +assert raw(OERDefault(a=7)) == bytes.fromhex("00") + +assert raw(OERDefault(a=9)) == bytes.fromhex("8009") + +# An absent DEFAULT is restored as the raw default value handed to +# ASN1F_DEFAULT, while a present one is decoded into an ASN1_INTEGER. +assert _dissect(OERDefault, "00").a == 7 + +assert _dissect(OERDefault, "8009").a.val == 9 + +True + += oer extensible sequence +class OERExtSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + oer_extensible=True, + ) + +assert raw(OERExtSeq(a=1)) == bytes.fromhex("0001") + +assert _dissect(OERExtSeq, "0001").a.val == 1 + +class OERExtSeqOpt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + ASN1F_optional(ASN1F_BOOLEAN("b", False)), + oer_extensible=True, + ) + +assert raw(OERExtSeqOpt(a=1, b=True)) == bytes.fromhex("4001ff") + +# An encoding that actually carries extension additions is refused rather +# than silently misparsed. +_raises(OER_Decoding_Error, lambda: _dissect(OERExtSeq, "8001")) + +True + += oer fixed size bit string +# X.696 13.3: a fixed size drops both the length determinant and the +# unused-bit count. +class OERBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_BIT_STRING("b", "", size_len=4)) + +assert raw(OERBits(b="1010")) == bytes.fromhex("a0") + +assert _dissect(OERBits, "a0").b.val == "1010" + +for bits, size, expected in [ + ("1010", 4, "a0"), + ("10100101", 8, "a5"), + ("101001010101", 12, "a550"), + ("1010010101011010", 16, "a55a"), +]: + assert OERcodec_BIT_STRING.enc(bits, size_len=size) == bytes.fromhex(expected) + obj, remain = OERcodec_BIT_STRING.do_dec(bytes.fromhex(expected), size_len=size) + assert obj.val == bits + assert remain == b"" + +# Unconstrained bit strings keep the length and unused-bit count. +assert OERcodec_BIT_STRING.enc("101") == bytes.fromhex("0205a0") + +# A value that does not match the declared size is refused. +_raises(OER_Encoding_Error, lambda: OERcodec_BIT_STRING.enc("101", size_len=4)) + +True + += oer fixed size octet string +# X.696 16.1: a fixed size means no length determinant. Sizes 1, 2, 4 and 8 +# used to encode without one but decode expecting one. +for size in (1, 2, 3, 4, 8): + value = b"x" * size + encoded = OERcodec_STRING.enc(value, size_len=size) + assert encoded == value + obj, remain = OERcodec_STRING.do_dec(encoded, size_len=size) + assert obj.val == value + assert remain == b"" + +class OERFixedOctets(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_STRING("s", "", size_len=4)) + +pkt = OERFixedOctets(s="abcd") + +assert raw(pkt) == b"abcd" + +assert _roundtrip(OERFixedOctets, pkt).s.val == b"abcd" + +# Unconstrained octet strings keep their length determinant. +assert OERcodec_STRING.enc(b"abc") == bytes.fromhex("03616263") + +_raises(OER_Encoding_Error, lambda: OERcodec_STRING.enc(b"abc", size_len=4)) + +True + += oer integer signedness follows the declared type +# X.696 10: the encoder must not pick the width or the signedness from the +# value, or the decoder (which only knows the type) reads something else back. +class OERSignedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, size_len=1)) + +class OERUnsignedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True)) + +for cls, value, expected in [ + (OERSignedByte, 127, "7f"), + (OERSignedByte, -128, "80"), + (OERUnsignedByte, 255, "ff"), + (OERUnsignedByte, 0, "00"), +]: + pkt = cls(a=value) + assert raw(pkt) == bytes.fromhex(expected), (value, raw(pkt).hex()) + assert _roundtrip(cls, pkt).a.val == value + +# 200 used to encode as an unsigned 0xc8 and read back as -56. +_raises(OER_Encoding_Error, lambda: raw(OERSignedByte(a=200))) + +_raises(OER_Encoding_Error, lambda: raw(OERUnsignedByte(a=256))) + +_raises(OER_Encoding_Error, lambda: raw(OERUnsignedByte(a=-1))) + +True + += oer unbounded unsigned integer +# X.696 10.2: a lower bound of zero means the value is encoded unsigned, with +# no leading zero octet. Byte vectors checked against asn1tools. +class OERUnboundedUnsigned(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, oer_unsigned=True)) + +for value, expected in [ + (0, "0100"), + (127, "017f"), + (128, "0180"), + (200, "01c8"), + (65535, "02ffff"), + (100000, "030186a0"), +]: + pkt = OERUnboundedUnsigned(a=value) + assert raw(pkt) == bytes.fromhex(expected), (value, raw(pkt).hex()) + assert _roundtrip(OERUnboundedUnsigned, pkt).a.val == value + +True + += oer integer with an empty length determinant +_raises(OER_Decoding_Error, lambda: OER_signed_integer_dec(b"\x00")) + +True + += oer choice rejects an unknown alternative tag +_raises(ASN1_Error, lambda: OERPacketChoice(b"\x40\x00")) + +# An unset CHOICE encodes to nothing, as the field is then absent +assert raw(OERPacketChoice(c=None)) == b"" + +True + += oer untyped codec falls back on string and integer +assert OERcodec_Object.enc(b"hi") == b"\x02hi" + +assert OERcodec_Object.enc(5) == b"\x01\x05" + +_raises(TypeError, lambda: OERcodec_Object.enc(object())) + +# Without a schema there is nothing to tell one type from another +_raises(OER_Decoding_Error, lambda: OERcodec_Object.dec(b"\x01")) + +assert OERcodec_OID.enc(b"") == b"\x00" + +True + += oer sequence dissect of an empty encoding +# Nothing to read leaves every component unset, hence holding its default +decoded = _dissect(OERRecord, "") + +assert decoded.id.val == 0 and decoded.label.val == "" and decoded.values == [] + +True + += oer sequence of a pre-encoded value +# A RAW object is written as-is, without the quantity determinant +raw_items = ASN1_Class_UNIVERSAL.RAW.asn1_object(b"\x01\x01\x07") + +assert raw(OERSequenceOfIntegers(values=raw_items)) == b"\x01\x01\x07" + +True diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts new file mode 100644 index 00000000000..ce257a814b9 --- /dev/null +++ b/test/contrib/uper.uts @@ -0,0 +1,3246 @@ +% Tests for ASN.1 UPER encoding + +# +# Try me with: +# bash test/run_tests -t test/scapy/layers/uper.uts -F + ++ ASN.1 UPER load += prepare helpers and packet classes +import scapy.contrib.uper + +from scapy.contrib.uper import * + +from scapy.packet import raw + +class UPERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class UPERIntegerField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0) + +class UPERBooleanField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BOOLEAN("b", False) + +class UPERStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("s", "") + +class UPERConstrainedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, + ) + +class UPEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class UPERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class UPERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class UPERChoiceStringFirst(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_STRING(b""), ASN1F_STRING, ASN1F_INTEGER, + ) + +class UPERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPEREnumeratedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +class UPERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING( + "bits", "0", uper_min=1, uper_max=20, + ) + +class UPERMessagePrefix(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("msgId", 0), + ASN1F_INTEGER("myflag", 0), + ASN1F_STRING("szDescription", "", size_len=10), + ASN1F_BOOLEAN("isReady", False), + ) + +class UPERSequenceWithChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING), + ) + +class UPERNullPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_NULL("n", None) + +class UPERVariableOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("data", "", uper_min=1, uper_max=20) + +class UPERConstrainedRangeInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=15) + +class UPERSequenceWithEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_ENUMERATED("state", 1, {1: "alpha", 200: "beta"}), + ) + +class UPERSequenceOfStrings(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("items", [], ASN1F_STRING) + +class UPERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERSequenceWithNull(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_NULL("n", None), + ) + +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING("b", "0", uper_min=16, uper_max=16) + +class UPERSequenceOfConstrainedInts(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("item", 0, uper_min=0, uper_max=255), + ) + +class UPERSignedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=-128, uper_max=127) + +class UPERMultiOptional(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("a", 0)), + ASN1F_optional(ASN1F_STRING("b", "")), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +CodecRoundtrip = Tuple[ + Type[Any], + Any, + Dict[str, Any], + Any, +] + +CODEC_ROUNDTRIPS = [ + (UPERcodec_NULL, None, {}, None), + (UPERcodec_BOOLEAN, 1, {}, 1), + (UPERcodec_BOOLEAN, 0, {}, 0), + (UPERcodec_INTEGER, 42, {}, 42), + (UPERcodec_INTEGER, -1, {}, -1), + (UPERcodec_INTEGER, 68719476736, {}, 68719476736), + (UPERcodec_INTEGER, 200, {"uper_min": 0, "uper_max": 255}, 200), + (UPERcodec_INTEGER, -1, {"uper_min": -128, "uper_max": 127}, -1), + (UPERcodec_INTEGER, 127, {"uper_min": -128, "uper_max": 127}, 127), + (UPERcodec_INTEGER, -128, {"uper_min": -128, "uper_max": 127}, -128), + (UPERcodec_STRING, b"AB", {}, b"AB"), + (UPERcodec_STRING, b"\x12\x34\x56", {"size_len": 3}, b"\x12\x34\x56"), + ( + UPERcodec_STRING, + bytes.fromhex("afbc4583"), + {"uper_min": 1, "uper_max": 20}, + bytes.fromhex("afbc4583"), + ), + (UPERcodec_ENUMERATED, 1, {"uper_enum_values": [1, 200]}, 1), + (UPERcodec_ENUMERATED, 200, {"uper_enum_values": [1, 200]}, 200), + ( + UPERcodec_BIT_STRING, + (bytes.fromhex("abcd"), 16), + {"uper_min": 1, "uper_max": 20}, + "1010101111001101", + ), + ( + UPERcodec_BIT_STRING, + (bytes.fromhex("abcd"), 16), + {"uper_min": 16, "uper_max": 16}, + "1010101111001101", + ), + (UPERcodec_ENUMERATED, 1, {"uper_enum_values": [1]}, 1), +] + +DecodeVector = Tuple[ + str, + Any, + Type[Any], + Dict[str, Any], + Any, + bytes, +] + +DECODE_VECTORS = [ + ("A", True, UPERcodec_BOOLEAN, {}, 1, b"\x80"), + ("A", False, UPERcodec_BOOLEAN, {}, 0, b"\x00"), + ("B", 42, UPERcodec_INTEGER, {}, 42, b"\x01*"), + ("B", -1, UPERcodec_INTEGER, {}, -1, b"\x01\xff"), + ( + "C", + 200, + UPERcodec_INTEGER, + {"uper_min": 0, "uper_max": 255}, + 200, + b"\xc8", + ), + ( + "Signed", + -1, + UPERcodec_INTEGER, + {"uper_min": -128, "uper_max": 127}, + -1, + b"\x7f", + ), + ( + "Signed", + 127, + UPERcodec_INTEGER, + {"uper_min": -128, "uper_max": 127}, + 127, + b"\xff", + ), + ("D", b"AB", UPERcodec_STRING, {}, b"AB", b"\x02AB"), + ( + "E", + b"\x12\x34\x56", + UPERcodec_STRING, + {"size_len": 3}, + b"\x12\x34\x56", + b"\x12\x34\x56", + ), + ("G", None, UPERcodec_NULL, {}, None, b""), + ("H", "alpha", UPERcodec_ENUMERATED, {"uper_enum_values": [1, 200]}, 1, b"\x00"), + ("H", "beta", UPERcodec_ENUMERATED, {"uper_enum_values": [1, 200]}, 200, b"\x80"), +] + +OID_ENCODE_VECTORS = [ + ("1.2.3", b"\x02*\x03"), + ("2.999.3", b"\x03\x887\x03"), +] + +def _assert_codec_roundtrip(codec, value, kwargs, expected): + # type: (Type[Any], Any, Dict[str, Any], Any) -> None + data = codec.enc(value, **kwargs) + decoded, _remain = codec.do_dec(data, **kwargs) + assert decoded.val == expected + +PRIMITIVE_VECTORS = [ + ("A", True, lambda v: UPERcodec_BOOLEAN.enc(1 if v else 0), b"\x80"), + ("A", False, lambda v: UPERcodec_BOOLEAN.enc(1 if v else 0), b"\x00"), + ("B", 42, lambda v: UPERcodec_INTEGER.enc(v), b"\x01*"), + ("B", -1, lambda v: UPERcodec_INTEGER.enc(v), b"\x01\xff"), + ( + "C", + 200, + lambda v: UPERcodec_INTEGER.enc(v, uper_min=0, uper_max=255), + b"\xc8", + ), + ("D", b"AB", lambda v: UPERcodec_STRING.enc(v), b"\x02AB"), + ( + "E", + b"\x12\x34\x56", + lambda v: UPERcodec_STRING.enc(v, size_len=3), + b"\x12\x34\x56", + ), + ("G", None, lambda v: UPERcodec_NULL.enc(None), b""), + ( + "H", + "beta", + lambda v: UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), + b"\x80", + ), +] + +COMPOSITE_VECTORS = [ + ("Seq", {"id": 42, "flag": True}, b"\x00\x95@"), + ("Seq", {"id": 42, "flag": True, "extra": 7}, b"\x80\x95@A\xc0"), + ("SeqOf", [1, 2, 3], b"\x03\x01\x01\x01\x02\x01\x03"), + ("SeqOfC", [1, 200, 0], b"\x03\x01\xc8\x00"), + ("Choice", ("a", 99), b"\x00\xb1\x80"), + ("Choice", ("b", b"AB"), b"\x81 \xa1\x00"), + ("ChoiceC", ("a", 10), b"P"), + ("ChoiceC", ("b", b"AB"), b"\x81 \xa1\x00"), +] + +DECODE_PACKET_VECTORS = [ + ( + UPERNestedSequence, + {"id": 5, "x": 3, "y": True}, + bytes.fromhex("0105010380"), + ), + ( + UPERMultiOptional, + {"id": 1, "a": 2, "b": b"hi"}, + bytes.fromhex("c0404040809a1a40"), + ), +] + +PACKET_REFERENCE_VECTORS = [ + ( + UPERNestedSequence, + {"id": 5, "x": 3, "y": True}, + bytes.fromhex("0105010380"), + ), + ( + UPERMultiOptional, + {"id": 1, "a": 2, "b": b"hi"}, + bytes.fromhex("c0404040809a1a40"), + ), +] + +def _encode_composite(typename, value): + # type: (str, Any) -> bytes + enc = UPER_Encoder() + if typename == "Seq": + enc.append_bit(1 if value.get("extra") is not None else 0) + UPERcodec_INTEGER.encode_into(enc, value["id"]) + UPERcodec_BOOLEAN.encode_into(enc, 1 if value["flag"] else 0) + if value.get("extra") is not None: + UPERcodec_INTEGER.encode_into(enc, value["extra"]) + return enc.as_bytes() + if typename == "SeqOf": + enc.append_length_determinant(len(value)) + for item in value: + UPERcodec_INTEGER.encode_into(enc, item) + return enc.as_bytes() + if typename == "SeqOfC": + enc.append_length_determinant(len(value)) + for item in value: + UPERcodec_INTEGER.encode_into( + enc, item, uper_min=0, uper_max=255, + ) + return enc.as_bytes() + if typename == "Choice": + alt, payload = value + index = 0 if alt == "a" else 1 + UPER_choice_index_enc(enc, index, 2) + if alt == "a": + UPERcodec_INTEGER.encode_into(enc, payload) + else: + UPERcodec_STRING.encode_into(enc, payload) + return enc.as_bytes() + if typename == "ChoiceC": + alt, payload = value + index = 0 if alt == "a" else 1 + UPER_choice_index_enc(enc, index, 2) + if alt == "a": + UPERcodec_INTEGER.encode_into( + enc, payload, uper_min=0, uper_max=15, + ) + else: + UPERcodec_STRING.encode_into(enc, payload) + return enc.as_bytes() + raise ValueError("unknown composite type %s" % typename) + +BOOLEAN_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BOOLEAN " + "END" +) + +NULL_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= NULL " + "END" +) + +OCTET_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= OCTET STRING (SIZE(1..20)) " + "END" +) + +CHOICE_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= CHOICE { " + "int1 INTEGER(0..15), " + "int2 INTEGER(0..65535), " + "enm ENUMERATED { one(1), two(2), three(3), four(4), thousand(1000) }, " + "buf OCTET STRING (SIZE(10)), " + "gg SEQUENCE { " + "int1 INTEGER(0..15), " + "int2 INTEGER(0..65535), " + "enm ENUMERATED { pone(1), ptwo(2), pthree(3), pfour(4), pthousand(1000) }, " + "buf [APPLICATION 104] OCTET STRING (SIZE(10)) " + "} " + "} " + "END" +) + +ENUMERATED_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= ENUMERATED { alpha(1), beta(200) } " + "END" +) + +BIT_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BIT STRING (SIZE(1..20)) " + "END" +) + +README_MESSAGE_HEX = ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" +) + +README_MESSAGE_PREFIX_HEX = ( + "0101010248656c6c6f576f726c6480" +) + +ASN1SCC_VECTORS = [ + ( + "05-BOOLEAN/001 pdu1", + True, + lambda _v: UPERcodec_BOOLEAN.enc(1), + b"\x80", + ), + ( + "18-NULL/001 pdu1", + None, + lambda _v: UPERcodec_NULL.enc(None), + b"", + ), + ( + "06-OCTET-STRING/001 pdu1", + bytes.fromhex("afbc4583"), + lambda v: UPERcodec_STRING.enc(v, uper_min=1, uper_max=20), + bytes.fromhex("1d7de22c18"), + ), + ( + "05-BOOLEAN/001 pdu1 false", + False, + lambda _v: UPERcodec_BOOLEAN.enc(0), + b"\x00", + ), + ( + "04-ENUMERATED/001 pdu1 alpha", + "alpha", + lambda _v: UPERcodec_ENUMERATED.enc(1, uper_enum_values=[1, 200]), + b"\x00", + ), + ( + "04-ENUMERATED/001 pdu1 beta", + "beta", + lambda _v: UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), + b"\x80", + ), + ( + "09-CHOICE/001 pdu1 int1:10", + ("int1", 10), + lambda _v: _encode_choice_int1_10(), + b"\x14", + ), + ( + "08-BIT-STRING/001 pdu1 ABCD", + (bytes.fromhex("abcd"), 16), + lambda _v: UPERcodec_BIT_STRING.enc( + (bytes.fromhex("abcd"), 16), uper_min=1, uper_max=20, + ), + bytes.fromhex("7d5e68"), + ), +] + +def _encode_choice_int1_10(): + # type: () -> bytes + enc = UPER_Encoder() + UPER_choice_index_enc(enc, 0, 5) + UPERcodec_INTEGER.encode_into(enc, 10, uper_min=0, uper_max=15) + return enc.as_bytes() + +_UPER_CODEC_CLASSES = ( + UPERcodec_INTEGER, + UPERcodec_BOOLEAN, + UPERcodec_NULL, + UPERcodec_STRING, + UPERcodec_OID, + UPERcodec_ENUMERATED, + UPERcodec_BIT_STRING, +) + +_DECODE_ERRORS = ( + UPER_Decoding_Error, + UPER_Encoding_Error, + ASN1_Decoding_Error, + ASN1_Error, + ValueError, + IndexError, +) + +class UPERFuzzRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPERFuzzNested(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERFuzzEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +def _fuzz_packets(): + # type: () -> Iterable[Type[ASN1_Packet]] + return (UPERFuzzRecord, UPERFuzzNested, UPERFuzzEnumerated) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +from unittest import mock + +from scapy.asn1.ber import BER_Decoding_Error + +from scapy.contrib.oer import OER_Decoding_Error, OER_Encoding_Error + +from scapy.contrib.uper import ( + UPER_Decoding_Error, UPER_Encoding_Error, UPER_Decoder, UPER_Encoder, +) + +from scapy.packet import Raw, raw + +class _InnerRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("mode", ASN1_INTEGER(0), ["off", "on"]), + ) + +class _EncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_ENCAPS("payload", None, _InnerRecord), + ) + +class _FlagsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("f", "000", ["read", "write", "exec"]), + ) + +class _SetOfRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SET_OF("items", [], ASN1F_INTEGER) + +class _PacketFieldRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_PacketField("data", b""), + ) + +class _ExplicitPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2) + +class _BitEncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord), + ) + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + +import scapy.contrib.oer + +from scapy.contrib.oer import * + +from scapy.contrib.uper import ASN1F_DEFAULT + +import scapy.asn1fields as asn1fields + +def _val(x): + # type: (Any) -> Any + return x.val if hasattr(x, "val") else x + +class UPERSmallDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER("n", 5, uper_min=0, uper_max=10), + 5, + ), + ) + +class UPEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, uper_min=0, uper_max=7), + uper_min=0, + uper_max=3, + ) + +class UPERExtSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, uper_min=0, uper_max=7), + uper_min=1, + uper_max=2, + uper_extensible=True, + ) + +class UPERFlagsField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_FLAGS( + "f", "101", ["a", "b", "c"], uper_min=3, uper_max=3, + ) + +class UPERInnerPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=0, uper_max=15), + ) + +class UPERWrappedPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_PACKET("inner", None, UPERInnerPacket), + ) + +class UPERConstrainedInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=255) + ++ ASN.1 UPER codec += UPER boolean true +UPERcodec_BOOLEAN.enc(1) == b"\x80" += UPER boolean false +UPERcodec_BOOLEAN.enc(0) == b"\x00" += UPER unconstrained integer +UPERcodec_INTEGER.enc(42) == b"\x01*" += UPER constrained integer +UPERcodec_INTEGER.enc(200, uper_min=0, uper_max=255) == b"\xc8" += UPER signed constrained integer +UPERcodec_INTEGER.enc(-1, uper_min=-128, uper_max=127) == b"\x7f" += UPER octet string +UPERcodec_STRING.enc(b"AB") == b"\x02AB" += UPER fixed octet string +UPERcodec_STRING.enc(b"\x12\x34\x56", size_len=3) == b"\x12\x34\x56" += UPER null +UPERcodec_NULL.enc(None) == b"" += UPER enumerated index +UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]) == b"\x80" += UPER bit string variable size +UPERcodec_BIT_STRING.enc((b"\xab\xcd", 16), uper_min=1, uper_max=20) == bytes.fromhex("7d5e68") += UPER enumerated roundtrip +x, r = UPERcodec_ENUMERATED.do_dec(UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), uper_enum_values=[1, 200]) +x.val == 200 and r == b"" += UPER integer roundtrip +x, r = UPERcodec_INTEGER.do_dec(UPERcodec_INTEGER.enc(-1)) +x.val == -1 and r == b"" += UPER boolean roundtrip +x, r = UPERcodec_BOOLEAN.do_dec(UPERcodec_BOOLEAN.enc(1)) +x.val == 1 and r == b"" += UPER ASN1 object encoding +ASN1_INTEGER(42).enc(ASN1_Codecs.PER) == b"\x01*" += UPER codec registration +ASN1_Class_UNIVERSAL.INTEGER.get_codec(ASN1_Codecs.PER) is UPERcodec_INTEGER + ++ ASN.1 UPER packets, helpers, interop and fuzz += uper field fixed size +pkt = UPERFixedFields(n=200, s=b"ABC") + +assert raw(pkt) == b"\xc8ABC" + +decoded = _roundtrip(UPERFixedFields, pkt) + +assert decoded.n.val == 200 + +assert decoded.s.val == b"ABC" + +True + += uper field integer +pkt = UPERIntegerField(n=12345) + +assert raw(pkt) == bytes.fromhex("023039") + +decoded = _roundtrip(UPERIntegerField, pkt) + +assert decoded.n.val == 12345 + +True + += uper field boolean +true_pkt = UPERBooleanField(b=True) + +assert raw(true_pkt) == b"\x80" + +decoded = _roundtrip(UPERBooleanField, true_pkt) + +assert decoded.b.val == 1 + +false_pkt = UPERBooleanField(b=False) + +assert raw(false_pkt) == b"\x00" + +decoded = _roundtrip(UPERBooleanField, false_pkt) + +assert decoded.b.val == 0 + +True + += uper field string +pkt = UPERStringField(s=b"hi") + +assert raw(pkt) == bytes.fromhex("026869") + +decoded = _roundtrip(UPERStringField, pkt) + +assert decoded.s.val == b"hi" + +True + += uper field constrained integer +pkt = UPERConstrainedInteger(n=200) + +assert raw(pkt) == b"\xc8" + +decoded = _roundtrip(UPERConstrainedInteger, pkt) + +assert decoded.n.val == 200 + +True + += uper field optional +present = UPEROptionalField(id=42, flag=True, extra=7) + +assert raw(present) == bytes.fromhex("80954041c0") + +decoded = _roundtrip(UPEROptionalField, present) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.extra.val == 7 + +absent = UPEROptionalField(id=42, flag=True, extra=None) + +assert raw(absent) == bytes.fromhex("009540") + +decoded = _roundtrip(UPEROptionalField, absent) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.extra is None + +True + += uper field sequence of +pkt = UPERSequenceOfIntegers(values=[1, 2, 3]) + +assert raw(pkt) == bytes.fromhex("03010101020103") + +decoded = _roundtrip(UPERSequenceOfIntegers, pkt) + +assert [x.val for x in decoded.values] == [1, 2, 3] + +empty = UPERSequenceOfIntegers(values=[]) + +assert raw(empty) == b"\x00" + +decoded = _roundtrip(UPERSequenceOfIntegers, empty) + +assert [x.val for x in decoded.values] == [] + +True + += uper field choice +as_int = UPERChoiceField(c=ASN1_INTEGER(99)) + +assert raw(as_int) == bytes.fromhex("00b180") + +decoded = _roundtrip(UPERChoiceField, as_int) + +assert decoded.c.val == 99 + +as_str = UPERChoiceField(c=ASN1_STRING(b"AB")) + +assert raw(as_str) == bytes.fromhex("8120a100") + +decoded = _roundtrip(UPERChoiceField, as_str) + +assert decoded.c.val == b"AB" + +True + += uper field choice definition order +as_str = UPERChoiceStringFirst(c=ASN1_STRING(b"AB")) + +assert raw(as_str) == bytes.fromhex("0120a100") + +decoded = _roundtrip(UPERChoiceStringFirst, as_str) + +assert decoded.c.val == b"AB" + +as_int = UPERChoiceStringFirst(c=ASN1_INTEGER(99)) + +assert raw(as_int) == bytes.fromhex("80b180") + +decoded = _roundtrip(UPERChoiceStringFirst, as_int) + +assert decoded.c.val == 99 + +True + += uper packet record +full = UPERRecord( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], +) + +assert raw(full) == bytes.fromhex("8095409a1a4041c0c04040408040c0") + +decoded = _roundtrip(UPERRecord, full) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +pkt = UPERRecord( + id=42, + flag=True, + label=b"AB", + extra=None, + values=[1, 2], +) + +body = bytes.fromhex("0095409050808040404080") + +assert raw(pkt) == body + +decoded = _roundtrip(UPERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"AB" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [1, 2] + +empty = UPERRecord( + id=1, + flag=False, + label=b"", + extra=None, + values=[], +) + +assert raw(empty) == bytes.fromhex("0080800000") + +decoded = _roundtrip(UPERRecord, empty) + +assert decoded.id.val == 1 + +assert decoded.flag.val == 0 + +assert decoded.label.val == b"" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [] + +True + += uper field enumerated +alpha = UPEREnumeratedField(state=1) + +assert raw(alpha) == b"\x00" + +decoded = _roundtrip(UPEREnumeratedField, alpha) + +assert decoded.state.val == 1 + +beta = UPEREnumeratedField(state=200) + +assert raw(beta) == b"\x80" + +decoded = _roundtrip(UPEREnumeratedField, beta) + +assert decoded.state.val == 200 + +True + += uper field bit string +from scapy.asn1.asn1 import ASN1_BIT_STRING + +pkt = UPERBitStringField(bits=ASN1_BIT_STRING("1010101111001101")) + +assert raw(pkt) == bytes.fromhex("7d5e68") + +decoded = _roundtrip(UPERBitStringField, pkt) + +assert decoded.bits.val == "1010101111001101" + +True + += uper message prefix +pkt = UPERMessagePrefix( + msgId=1, + myflag=2, + szDescription=b"HelloWorld", + isReady=True, +) + +assert raw(pkt) == bytes.fromhex("0101010248656c6c6f576f726c6480") + +decoded = _roundtrip(UPERMessagePrefix, pkt) + +assert decoded.msgId.val == 1 + +assert decoded.myflag.val == 2 + +assert decoded.szDescription.val == b"HelloWorld" + +assert decoded.isReady.val == 1 + +True + += uper sequence with choice +pkt = UPERSequenceWithChoice(id=42, c=ASN1_INTEGER(99)) + +body = raw(pkt) + +decoded = UPERSequenceWithChoice(body) + +assert decoded.id.val == 42 + +assert decoded.c.val == 99 + +as_str = UPERSequenceWithChoice(id=1, c=ASN1_STRING(b"AB")) + +decoded = UPERSequenceWithChoice(raw(as_str)) + +assert decoded.id.val == 1 + +assert decoded.c.val == b"AB" + +True + += uper null packet +pkt = UPERNullPacket() + +assert raw(pkt) == b"" + +decoded = _roundtrip(UPERNullPacket, pkt) + +assert decoded.n is None + +True + += uper variable octet string +pkt = UPERVariableOctetString(data=bytes.fromhex("afbc4583")) + +assert raw(pkt) == bytes.fromhex("1d7de22c18") + +decoded = _roundtrip(UPERVariableOctetString, pkt) + +assert decoded.data.val == bytes.fromhex("afbc4583") + +True + += uper constrained range integer +pkt = UPERConstrainedRangeInt(n=10) + +assert raw(pkt) == b"\xa0" + +decoded = _roundtrip(UPERConstrainedRangeInt, pkt) + +assert decoded.n.val == 10 + +True + += uper sequence with enumerated +pkt = UPERSequenceWithEnumerated(id=1, state=200) + +assert raw(pkt) == bytes.fromhex("010180") + +decoded = _roundtrip(UPERSequenceWithEnumerated, pkt) + +assert decoded.id.val == 1 + +assert decoded.state.val == 200 + +alpha = UPERSequenceWithEnumerated(id=7, state=1) + +assert raw(alpha) == bytes.fromhex("010700") + +decoded = _roundtrip(UPERSequenceWithEnumerated, alpha) + +assert decoded.state.val == 1 + +True + += uper sequence of strings +pkt = UPERSequenceOfStrings(items=[b"A", b"BC"]) + +assert raw(pkt) == bytes.fromhex("020141024243") + +decoded = _roundtrip(UPERSequenceOfStrings, pkt) + +assert [x.val for x in decoded.items] == [b"A", b"BC"] + +empty = UPERSequenceOfStrings(items=[]) + +assert raw(empty) == b"\x00" + +decoded = _roundtrip(UPERSequenceOfStrings, empty) + +assert [x.val for x in decoded.items] == [] + +True + += uper sequence choice hex +pkt = UPERSequenceWithChoice(id=1, c=ASN1_INTEGER(99)) + +assert raw(pkt) == bytes.fromhex("010100b180") + +decoded = UPERSequenceWithChoice(raw(pkt)) + +assert decoded.id.val == 1 + +assert decoded.c.val == 99 + +True + += uper nested sequence +pkt = UPERNestedSequence(id=5, x=3, y=True) + +assert raw(pkt) == bytes.fromhex("0105010380") + +decoded = _roundtrip(UPERNestedSequence, pkt) + +assert decoded.id.val == 5 + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +True + += uper sequence with null +pkt = UPERSequenceWithNull(id=1) + +assert raw(pkt) == bytes.fromhex("0101") + +decoded = _roundtrip(UPERSequenceWithNull, pkt) + +assert decoded.id.val == 1 + +assert getattr(decoded.n, "val", decoded.n) is None + +True + += uper fixed bit string +from scapy.asn1.asn1 import ASN1_BIT_STRING + +pkt = UPERFixedBitString(b=ASN1_BIT_STRING("1010101111001101")) + +assert raw(pkt) == bytes.fromhex("abcd") + +decoded = _roundtrip(UPERFixedBitString, pkt) + +assert decoded.b.val == "1010101111001101" + +True + += uper sequence of constrained ints +pkt = UPERSequenceOfConstrainedInts(values=[1, 200, 0]) + +assert raw(pkt) == bytes.fromhex("0301c800") + +decoded = _roundtrip(UPERSequenceOfConstrainedInts, pkt) + +assert [x.val for x in decoded.values] == [1, 200, 0] + +True + += uper signed integer +for value, expected in [ + (0, b"\x80"), + (-1, b"\x7f"), + (127, b"\xff"), + (-128, b"\x00"), +]: + pkt = UPERSignedInteger(n=value) + assert raw(pkt) == expected + decoded = _roundtrip(UPERSignedInteger, pkt) + assert decoded.n.val == value + +True + += uper multi optional +both = UPERMultiOptional(id=1, a=2, b=b"hi") + +assert raw(both) == bytes.fromhex("c0404040809a1a40") + +decoded = _roundtrip(UPERMultiOptional, both) + +assert decoded.id.val == 1 + +assert decoded.a.val == 2 + +assert decoded.b.val == b"hi" + +none = UPERMultiOptional(id=1, a=None, b=None) + +assert raw(none) == bytes.fromhex("004040") + +decoded = _roundtrip(UPERMultiOptional, none) + +assert decoded.id.val == 1 + +assert decoded.a is None + +assert decoded.b is None + +only_a = UPERMultiOptional(id=3, a=9, b=None) + +assert raw(only_a) == bytes.fromhex("8040c04240") + +decoded = _roundtrip(UPERMultiOptional, only_a) + +assert decoded.id.val == 3 + +assert decoded.a.val == 9 + +assert decoded.b is None + +True + += uper length determinant +for length, expected in [ + (0, b"\x00"), + (1, b"\x01"), + (127, b"\x7f"), + (128, b"\x80\x80"), + (16383, b"\xbf\xff"), +]: + enc = UPER_Encoder() + enc.append_length_determinant(length) + assert enc.as_bytes() == expected + +True + += uper length determinant refuses lengths that need fragmentation +# X.691 11.9.3.8: the caller has to split the content, so a bare determinant +# of 16K or more would not match what follows it. +enc = UPER_Encoder() +try: + enc.append_length_determinant(16384) + assert False +except UPER_Encoding_Error: + pass + +True + += uper fragmented length determinant +# X.691 11.9.3.8: fragments of 16K units, always closed by a determinant +# below 16K. Byte vectors checked against asn1tools. +for count, expected_header in [ + (16384, b"\xc1"), + (32768, b"\xc2"), + (49152, b"\xc3"), + (65536, b"\xc4"), + (81920, b"\xc4"), +]: + enc = UPER_Encoder() + seen = [] + enc.append_fragmented(count, lambda offset, size: seen.append((offset, size))) + got = enc.as_bytes() + assert got.startswith(expected_header), (count, got[:1]) + assert sum(size for _, size in seen) == count, (count, seen) + assert got.endswith(b"\x00"), (count, got[-1:]) + +True + += uper fragmented length determinant roundtrip +for count in [0, 127, 16383, 16384, 40000, 70000]: + enc = UPER_Encoder() + enc.append_fragmented(count, lambda offset, size: None) + dec = UPER_Decoder(enc.as_bytes()) + seen = [] + dec.read_fragmented(lambda size: seen.append(size)) + assert sum(seen) == count, (count, seen) + +True + += uper count roundtrip +for count in [0, 1, 3, 127]: + enc = UPER_Encoder() + enc.append_length_determinant(count) + assert UPER_Decoder(enc.as_bytes()).read_length_determinant() == count + +True + += uper choice index roundtrip +for index, choices in [(0, 2), (1, 5), (3, 5)]: + enc = UPER_Encoder() + UPER_choice_index_enc(enc, index, choices) + got = UPER_choice_index_dec(UPER_Decoder(enc.as_bytes()), choices) + assert got == index + +True + += uper optional presence +enc = UPER_Encoder() + +for bit in [0, 1, 0]: + enc.append_bit(bit) + +assert enc.as_bytes() == b"\x40" + +True + += uper constrained integer +enc = UPER_Encoder() + +UPER_constrained_int_enc(enc, 10, 0, 15) + +assert UPER_constrained_int_dec(UPER_Decoder(enc.as_bytes()), 0, 15) == 10 + +True + += uper constrained signed integer +for value, expected in [(0, b"\x80"), (-1, b"\x7f"), (127, b"\xff"), (-128, b"\x00")]: + enc = UPER_Encoder() + UPER_constrained_int_enc(enc, value, -128, 127) + assert enc.as_bytes() == expected + dec = UPER_Decoder(enc.as_bytes()) + assert UPER_constrained_int_dec(dec, -128, 127) == value + +True + += uper octet string roundtrip +for data, minimum, maximum in [ + (b"AB", None, None), + (b"\x12\x34\x56", 3, 3), + (bytes.fromhex("afbc4583"), 1, 20), +]: + enc = UPER_Encoder() + UPER_octet_string_enc(enc, data, minimum, maximum) + dec = UPER_Decoder(enc.as_bytes()) + assert UPER_octet_string_dec(dec, minimum, maximum) == data + assert not UPER_has_unexpected_remainder(dec) + +True + += uper has unexpected remainder +assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x00")) is False + +assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x80")) is True + +True + += uper chained encode into +enc = UPER_Encoder() + +UPERcodec_INTEGER.encode_into(enc, 42) + +UPERcodec_INTEGER.encode_into(enc, -7) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == 42 + +assert dec.read_unconstrained_whole_number() == -7 + +True + += uper codec roundtrips +for codec, value, kwargs, expected in CODEC_ROUNDTRIPS: + _assert_codec_roundtrip(codec, value, kwargs, expected) + +True + += uper codec oid roundtrip +import scapy.all # noqa: F401 # loads conf.mib for ASN1_OID + +for oid in ("1.2.3", "1.2.840.113549"): + data = UPERcodec_OID.enc(oid) + decoded, remain = UPERcodec_OID.do_dec(data) + assert remain == b"" + assert decoded.val == oid + +True + += uper codec oid encode interop +for oid, expected in OID_ENCODE_VECTORS: + got = UPERcodec_OID.enc(oid) + assert got == expected, ( + "OID %r: expected %s, got %s" % + (oid, expected.hex(), got.hex()) + ) + +True + += uper codec reference decode +for _typename, _value, codec, kwargs, expected, encoded in DECODE_VECTORS: + decoded, _remain = codec.do_dec(encoded, **kwargs) + assert decoded.val == expected, ( + "%s %r: expected %r, got %r" % + (_typename, _value, expected, decoded.val) + ) + +True + += uper codec encode reference +for typename, value, encoder, expected in PRIMITIVE_VECTORS: + encoded = encoder(value) + assert encoded == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), encoded.hex()) + ) + +True + += primitive interop +for typename, value, encoder, expected in PRIMITIVE_VECTORS: + got = encoder(value) + assert got == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), got.hex()) + ) + +True + += composite interop +for typename, value, expected in COMPOSITE_VECTORS: + got = _encode_composite(typename, value) + assert got == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), got.hex()) + ) + +True + += packet reference interop +for cls, pkt_kwargs, expected in PACKET_REFERENCE_VECTORS: + got = raw(cls(**pkt_kwargs)) + assert got == expected, ( + "%s: expected %s, got %s" % + (cls.__name__, expected.hex(), got.hex()) + ) + decoded = cls(got) + for key, value in pkt_kwargs.items(): + field = getattr(decoded, key) + if value is None: + assert field is None + elif isinstance(value, bool): + assert field.val == (1 if value else 0) + else: + assert field.val == value + +True + += packet decode vectors +for cls, pkt_kwargs, data in DECODE_PACKET_VECTORS: + decoded = cls(data) + for key, value in pkt_kwargs.items(): + field = getattr(decoded, key) + if isinstance(value, bool): + assert field.val == (1 if value else 0) + else: + assert field.val == value + +True + += asn1scc vectors +for name, _value, encoder, expected in ASN1SCC_VECTORS: + got = encoder(_value) + assert got == expected, ( + "%s: expected %s, got %s" % + (name, expected.hex(), got.hex()) + ) + +True + += asn1scc readme message prefix +from scapy.packet import raw + +expected = bytes.fromhex(README_MESSAGE_PREFIX_HEX) + +pkt = UPERMessagePrefix( + msgId=1, + myflag=2, + szDescription=b"HelloWorld", + isReady=True, +) + +got = raw(pkt) + +assert got == expected + +decoded = UPERMessagePrefix(got) + +assert decoded.msgId.val == 1 + +assert decoded.myflag.val == 2 + +assert decoded.szDescription.val == b"HelloWorld" + +assert decoded.isReady.val == 1 + +True + += asn1scc readme message reference +assert README_MESSAGE_HEX == ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" +) + +True + += uper fuzz encode +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + try: + data = raw(fuzz(cls())) + except _DECODE_ERRORS: + continue + assert isinstance(data, bytes) + +True + += uper fuzz roundtrip +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + try: + cls(raw(fuzz(cls()))) + except _DECODE_ERRORS: + pass + +True + += uper fuzz codec decode +iterations = 100 + +for codec in _UPER_CODEC_CLASSES: + for _ in range(iterations): + data = os.urandom(random.randint(0, 64)) + try: + codec.safedec(data) + except _DECODE_ERRORS: + pass + +True + += uper fuzz packet decode +iterations = 100 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass + +True + ++ ASN.1 UPER build and dissect += per record build roundtrip +pkt = UPERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(UPERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += per default field build +class UPERDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + uper_min=0, uper_max=86401, oer_unsigned=True, + ), + 600, + ), + ) + +absent = UPERDefaultRecord(id=1) + +assert raw(absent) == b"\x00\x80" + +decoded = _roundtrip(UPERDefaultRecord, absent) + +assert decoded.id.val == 1 + +assert _asn1_int(decoded.count) == 600 + +present = UPERDefaultRecord(id=1, count=86400) + +assert raw(present) == bytes.fromhex("80d46000") + +decoded = _roundtrip(UPERDefaultRecord, present) + +assert decoded.id.val == 1 + +assert _asn1_int(decoded.count) == 86400 + +True + += per extensible integer build +class UPERExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER( + "n", 0, + uper_min=1, uper_max=65535, + uper_extensible=True, oer_unsigned=True, + ), + ) + +in_range = UPERExtInt(n=42) + +assert raw(in_range) == bytes.fromhex("001480") + +decoded = _roundtrip(UPERExtInt, in_range) + +assert decoded.n.val == 42 + +out_of_range = UPERExtInt(n=1706733817) + +assert raw(out_of_range) == bytes.fromhex("8232dd587c80") + +decoded = _roundtrip(UPERExtInt, out_of_range) + +assert decoded.n.val == 1706733817 + +True + += per extensible integer as a bare root +~ per +class UPERBareExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, uper_min=0, uper_max=15, uper_extensible=True, + ) + +class UPERWrappedExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=15, uper_extensible=True), + ) + +# A bare root must encode the extension bit just like the nested field does. +assert raw(UPERBareExtInt(n=5)) == bytes.fromhex("28") + +assert raw(UPERBareExtInt(n=5)) == raw(UPERWrappedExtInt(n=5)) + +assert _dissect(UPERBareExtInt, "28").n.val == 5 + +assert _roundtrip(UPERBareExtInt, UPERBareExtInt(n=99)).n.val == 99 + +True + += per constrained sequence of build +class UPERConstrainedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7), + uper_min=1, uper_max=3, + ) + +pkt = UPERConstrainedSeqOf(items=[1, 2]) + +assert raw(pkt) == bytes.fromhex("4a") + +decoded = _roundtrip(UPERConstrainedSeqOf, pkt) + +assert [x.val for x in decoded.items] == [1, 2] + +True + += per field dissect +fixed = _dissect(UPERFixedFields, "c8414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(UPEROptionalField, "80954041c0") + +assert present.id.val == 42 + +assert present.flag.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(UPEROptionalField, "009540") + +assert absent.id.val == 42 + +assert absent.flag.val == 1 + +assert absent.extra is None + +seqof = _dissect(UPERSequenceOfIntegers, "03010101020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +empty_seqof = _dissect(UPERSequenceOfIntegers, "00") + +assert [x.val for x in empty_seqof.values] == [] + +as_int = _dissect(UPERChoiceField, "00b180") + +assert as_int.c.val == 99 + +as_str = _dissect(UPERChoiceField, "8120a100") + +assert as_str.c.val == b"AB" + +True + += per record dissect +decoded = _dissect( + UPERRecord, + "8095409a1a4041c0c04040408040c0", +) + +_assert_record(decoded) + +partial = _dissect(UPERRecord, "0095409050808040404080") + +assert partial.id.val == 42 + +assert partial.flag.val == 1 + +assert partial.label.val == b"AB" + +assert partial.extra is None + +assert [x.val for x in partial.values] == [1, 2] + +empty = _dissect(UPERRecord, "0080800000") + +_assert_record_empty(empty) + +True + += per default field dissect +class UPERDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + uper_min=0, uper_max=86401, oer_unsigned=True, + ), + 600, + ), + ) + +absent = _dissect(UPERDefaultRecord, "0080") + +assert absent.id.val == 1 + +assert _asn1_int(absent.count) == 600 + +present = _dissect(UPERDefaultRecord, "80d46000") + +assert present.id.val == 1 + +assert _asn1_int(present.count) == 86400 + +True + += per extensible integer dissect +class UPERExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER( + "n", 0, + uper_min=1, uper_max=65535, + uper_extensible=True, oer_unsigned=True, + ), + ) + +in_range = _dissect(UPERExtInt, "001480") + +assert in_range.n.val == 42 + +out_of_range = _dissect(UPERExtInt, "8232dd587c80") + +assert out_of_range.n.val == 1706733817 + +True + += per constrained sequence of dissect +class UPERConstrainedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7), + uper_min=1, uper_max=3, + ) + +decoded = _dissect(UPERConstrainedSeqOf, "4a") + +assert [x.val for x in decoded.items] == [1, 2] + +True + ++ ASN.1 UPER coverage += uper error str +obj = ASN1_INTEGER(2) + +err = UPER_Encoding_Error("enc", encoded=obj, remaining=b"x") + +assert "Already encoded" in str(err) + +err2 = UPER_Decoding_Error("dec", decoded=obj, remaining=b"y") + +assert "Already decoded" in str(err2) + +True + += uper length determinant extended +# X.691 11.9.3.8: multiples of 16K units are emitted as 0xc1..0xc4 fragments +# and the sequence is closed by a determinant below 16K. +def _fragment_headers(count): + enc = UPER_Encoder() + sizes = [] + enc.append_fragmented(count, lambda offset, size: sizes.append(size)) + return enc.as_bytes(), sizes + +assert _fragment_headers(32768) == (b"\xc2\x00", [32768, 0]) + +assert _fragment_headers(49152) == (b"\xc3\x00", [49152, 0]) + +assert _fragment_headers(65535) == (b"\xc3\xbf\xff", [49152, 16383]) + +assert _fragment_headers(65536) == (b"\xc4\x00", [65536, 0]) + +assert _fragment_headers(81920) == (b"\xc4\xc1\x00", [65536, 16384, 0]) + +True + += uper unconstrained whole number +enc = UPER_Encoder() + +enc.append_unconstrained_whole_number(-256) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == -256 + +enc = UPER_Encoder() + +enc.append_unconstrained_whole_number(0) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == 0 + +True + += uper bit string paths +encoded = UPERcodec_BIT_STRING.enc("1010", uper_min=1, uper_max=20) + +obj, remain = UPERcodec_BIT_STRING.do_dec( + encoded, uper_min=1, uper_max=20, +) + +assert obj.val == "1010" + +encoded2 = UPERcodec_BIT_STRING.enc(b"\xab", uper_min=4, uper_max=8) + +obj2, _ = UPERcodec_BIT_STRING.do_dec(encoded2, uper_min=4, uper_max=8) + +assert len(obj2.val) == 8 + +fixed = UPERcodec_BIT_STRING.enc("1010101111001101", uper_min=16, uper_max=16) + +obj3, _ = UPERcodec_BIT_STRING.do_dec(fixed, uper_min=16, uper_max=16) + +assert obj3.val == "1010101111001101" + +True + += uper enumerated range +encoded = UPERcodec_ENUMERATED.enc(3, uper_min=0, uper_max=7) + +obj, remain = UPERcodec_ENUMERATED.do_dec(encoded, uper_min=0, uper_max=7) + +assert obj.val == 3 + +assert remain == b"" + +enc = UPER_Encoder() + +UPERcodec_ENUMERATED.encode_into(enc, 2, uper_min=0, uper_max=3) + +obj2 = UPERcodec_ENUMERATED.dec_from_decoder( + UPER_Decoder(enc.as_bytes()), + uper_min=0, + uper_max=3, +) + +assert obj2.val == 2 + +True + += uper enumerated without a range +# The width would otherwise follow the value, which the decoder cannot redo +_raises(UPER_Encoding_Error, lambda: UPERcodec_ENUMERATED.enc(3)) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_ENUMERATED.do_dec(b"\x60")) + +True + += uper enumerated index follows the value order +# X.691 14.1: sort the enumeration by value, whatever order it was declared in +class UPERUnsortedEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("e", 0, {2: "c", 0: "a", 1: "b"}), + ) + +# byte vectors from asn1tools for ENUMERATED { c(2), a(0), b(1) } +for value, expected in [(0, "00"), (1, "40"), (2, "80")]: + assert raw(UPERUnsortedEnum(e=value)) == bytes.fromhex(expected), value + assert _dissect(UPERUnsortedEnum, expected).e.val == value + +True + += uper extensible enumerated +# X.691 14.3: a one bit prefix, zero for a value of the extension root +class UPERExtEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("e", 0, {0: "a", 1: "b", 2: "c"}, + uper_extensible=True), + ) + +# byte vectors from asn1tools for ENUMERATED { a(0), b(1), c(2), ... } +for value, expected in [(0, "00"), (1, "20"), (2, "40")]: + assert raw(UPERExtEnum(e=value)) == bytes.fromhex(expected), value + assert _dissect(UPERExtEnum, expected).e.val == value + +enc = UPER_Encoder() + +_raises(UPER_Encoding_Error, lambda: UPERcodec_ENUMERATED.encode_into( + enc, 7, uper_enum_values=[0, 1, 2], uper_extensible=True)) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_ENUMERATED.dec_from_decoder( + UPER_Decoder(b"\x80"), uper_enum_values=[0, 1, 2], uper_extensible=True)) + +True + += uper octet string honours its size constraint +# A determinant sized after the constraint cannot express a violating length +_raises(UPER_Encoding_Error, lambda: UPERcodec_STRING.enc(b"AB", size_len=4)) + +_raises(UPER_Encoding_Error, lambda: UPERcodec_STRING.enc(b"ABCDEF", size_len=4)) + +assert UPERcodec_STRING.enc(b"ABCD", size_len=4) == b"ABCD" + +_raises(UPER_Encoding_Error, + lambda: UPERcodec_STRING.enc(b"A", uper_min=2, uper_max=4)) + +_raises(UPER_Encoding_Error, + lambda: UPERcodec_STRING.enc(b"ABCDEFGH", uper_min=2, uper_max=4)) + +True + += uper sequence errors +_raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.enc([ASN1_INTEGER(1)])) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_SEQUENCE.do_dec(b"\x00")) + +# A finished encoding is octet padded, so it cannot be spliced into a bitstream +_raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.encode_into(UPER_Encoder(), b"raw")) + +assert UPERcodec_SET.enc(b"raw") == b"raw" + +True + += uper ipaddress +encoded = UPERcodec_IPADDRESS.enc("10.0.0.1") + +obj, remain = UPERcodec_IPADDRESS.do_dec(encoded) + +assert obj.val == "10.0.0.1" + +assert remain == b"" + +_raises(UPER_Encoding_Error, lambda: UPERcodec_IPADDRESS.enc("bad-ip")) + +True + ++ ASN.1 fields coverage += asn1fields enum and flags +pkt = _InnerRecord(mode="on") + +built = raw(pkt) + +decoded = _InnerRecord(built) + +assert decoded.mode.val == 1 + +flags = _FlagsRecord(f="read+exec") + +assert flags.f.val == "101" + +assert "read, exec" in _FlagsRecord.ASN1_root.seq[0].i2repr(flags, flags.f) + +set_pkt = _SetOfRecord(items=[ASN1_INTEGER(0), ASN1_INTEGER(1)]) + +set_raw = raw(set_pkt) + +set_dec = _SetOfRecord(set_raw) + +assert [x.val for x in set_dec.items] == [0, 1] + +True + += asn1fields encaps and packet +inner = _InnerRecord(mode=1) + +enc = _EncapsRecord() + +enc.payload = inner + +enc_raw = raw(enc) + +enc_dec = _EncapsRecord(enc_raw) + +assert enc_dec.payload.mode.val == 1 + +pkt_field = _PacketFieldRecord() + +pkt_field.data = _InnerRecord(mode=0) + +pf_raw = raw(pkt_field) + +pf_dec = _PacketFieldRecord(pf_raw) + +assert isinstance(pf_dec.data.val, bytes) + +explicit = _ExplicitPacket() + +explicit.inner = _InnerRecord(mode=1) + +ex_raw = raw(explicit) + +ex_dec = _ExplicitPacket(ex_raw) + +assert ex_dec.inner.mode.val == 1 + +True + += asn1fields choice and special +class _OerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class _BerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +oer = _OerChoiceRecord(c=ASN1_INTEGER(1)) + +oer_dec = _OerChoiceRecord(raw(oer)) + +assert oer_dec.c.val == 1 + +ber = _BerChoiceRecord(c=ASN1_INTEGER(0)) + +ber_dec = _BerChoiceRecord(raw(ber)) + +assert ber_dec.c.val == 0 + +inner_bytes = raw(_InnerRecord(mode=0)) + +bit_payload = ASN1_BIT_STRING( + inner_bytes, + readable=True, +) + +bit_pkt = _BitEncapsRecord(b=bit_payload) + +bit_dec = _BitEncapsRecord(raw(bit_pkt)) + +assert bit_dec.b.mode.val == 0 + +class _TicksRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_TIME_TICKS("t", ASN1_TIME_TICKS(0)) + +class _IpRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_IPADDRESS("addr", ASN1_STRING(b"")) + +ticks = _TicksRecord(t=ASN1_TIME_TICKS(1234)) + +assert raw(ticks).endswith(b"\x04\xd2") + +ip = _IpRecord() + +ip.addr = "192.168.1.1" + +assert raw(ip) == b"\x40\x04\xc0\xa8\x01\x01" + +True + += asn1fields optional dissect +class _OptRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class _BerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +pkt = _OptRecord(id=0, extra=None) + +assert raw(pkt) + +decoded = _OptRecord(raw(pkt)) + +assert decoded.extra is None + +choice_rand = _BerChoiceRecord.ASN1_root.randval() + +assert choice_rand is not None + +True + += asn1fields default and omit +class _DefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + uper_min=0, uper_max=86401, oer_unsigned=True, + ), + 600, + ), + ) + +absent = _DefaultRecord(id=1) + +assert raw(absent) == b"\x00\x80" + +decoded = _DefaultRecord(raw(absent)) + +assert decoded.id.val == 1 + +assert decoded.count == 600 or decoded.count.val == 600 + +present = _DefaultRecord(id=1, count=86400) + +decoded = _DefaultRecord(raw(present)) + +assert decoded.count.val == 86400 + +class _OmitRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_omit("ignored", None), + ) + +omit_pkt = _OmitRecord(id=7) + +assert raw(omit_pkt) == bytes.fromhex("3003020107") + +True + += asn1fields extensible per +class _ExtSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_optional(ASN1F_INTEGER("extra", 0, uper_min=0, uper_max=7)), + uper_extensible=True, + ) + +pkt = _ExtSeq(id=2, extra=3) + +data = raw(pkt) + +decoded = _ExtSeq(data) + +assert decoded.id.val == 2 + +assert decoded.extra.val == 3 + +dec = UPER_Decoder(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtSeq.ASN1_root.dissect_from_decoder(_ExtSeq(), dec), +) + +class _ExtChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + uper_extensible=True, + ) + +choice = _ExtChoice(c=ASN1_INTEGER(4)) + +assert raw(choice) + +dec = UPER_Decoder(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtChoice.ASN1_root.m2i_from_decoder(_ExtChoice(), dec), +) + +class _InnerItem(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7) + +class _ExtSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], _InnerItem, + uper_min=1, uper_max=2, uper_extensible=True, + ) + +in_range = _ExtSeqOf(items=[_InnerItem(n=1)]) + +assert raw(in_range) + +decoded = _ExtSeqOf(raw(in_range)) + +assert decoded.items[0].n.val == 1 + +out_of_range = _ExtSeqOf( + items=[_InnerItem(n=i) for i in range(4)], +) + +assert raw(out_of_range) + +decoded = _ExtSeqOf(raw(out_of_range)) + +assert len(decoded.items) == 4 + +True + += asn1fields sequence of advanced +class _Inner(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7) + +class _SeqOfPackets(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], _Inner, uper_min=1, uper_max=3, + ) + +pkt = _SeqOfPackets(items=[_Inner(n=1), _Inner(n=2)]) + +decoded = _SeqOfPackets(raw(pkt)) + +assert [x.n.val for x in decoded.items] == [1, 2] + +class _OerSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +oer_pkt = _OerSeqOf(values=[1, 2]) + +oer_dec = _OerSeqOf(raw(oer_pkt)) + +assert [x.val for x in oer_dec.values] == [1, 2] + +class _EmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +empty = _EmptySeqOf(values=None) + +assert raw(empty) == b"\x00" + +assert _EmptySeqOf.ASN1_root.i2repr(empty, None) == "[]" + +assert _EmptySeqOf.ASN1_root.i2repr( + _EmptySeqOf(values=[ASN1_INTEGER(1)]), + [ASN1_INTEGER(1)], +).startswith("[") + +_raises(ValueError, lambda: ASN1F_SEQUENCE_OF("bad", [], object())) + +True + += asn1fields choice advanced +class _InnerChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class _NestedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), _InnerChoice, ASN1F_INTEGER, + ) + +nested = _NestedChoice(c=_InnerChoice(c=ASN1_STRING(b"xy"))) + +assert len(raw(nested)) > 0 + +nested_dec = _NestedChoice(raw(nested)) + +assert isinstance(nested_dec.c, (_InnerChoice, ASN1_STRING)) + +class _OerTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + explicit_tag=0xA1, + ) + +oer_choice = _OerTaggedChoice(c=ASN1_INTEGER(9)) + +assert raw(oer_choice) + +class _PacketChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", + ASN1_INTEGER(0), + ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2), + ASN1F_INTEGER, + ) + +packet_choice = _PacketChoice( + c=_InnerRecord(mode=ASN1_INTEGER(1)), +) + +packet_dec = _PacketChoice(raw(packet_choice)) + +assert packet_dec.c.mode.val == 1 + +class _PerChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +_raises( + ASN1_Error, + lambda: ASN1F_CHOICE( + "c", 0, ASN1F_INTEGER, implicit_tag=0xA0, + ), +) + +_raises( + ASN1_Error, + lambda: _PerChoice.ASN1_root.m2i(_PerChoice(), b""), +) + +_raises( + ASN1_Error, + lambda: _PerChoice.ASN1_root.encode_into( + UPER_Encoder(), _PerChoice(), 42, + ), +) + +True + += asn1fields enum bitstring and flags +class _NamedEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_enum_INTEGER( + "state", 0, ["off", "on", "auto"], + ) + +named = _NamedEnum(state="on") + +built = raw(named) + +decoded = _NamedEnum(built) + +assert decoded.state.val == 1 + +assert "'on'" in _NamedEnum.ASN1_root.i2repr(decoded, decoded.state) + +class _BitRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING("bits", b"\xaa") + +assert raw(_BitRecord()) + +flags = _FlagsRecord() + +flags.f = ASN1_BIT_STRING("101") + +assert "read, exec" in _FlagsRecord.ASN1_root.seq[0].i2repr(flags, flags.f) + +class _BadBitEncaps(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord) + +_raises( + BER_Decoding_Error, + lambda: _BadBitEncaps.ASN1_root.m2i( + _BadBitEncaps(), + b"\x03\x02\x01\x00", + ), +) + +True + += asn1fields packet and sequence errors +class _PerInner(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("mode", 0, uper_min=0, uper_max=1) + +class _PacketWrap(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_PACKET("inner", None, _PerInner) + +inner = _PerInner(mode=1) + +wrap = _PacketWrap(inner=inner) + +decoded = _PacketWrap(raw(wrap)) + +assert decoded.inner.mode.val == 1 + +class _DynamicPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_PACKET( + "inner", None, _PerInner, + next_cls_cb=lambda pkt: _PerInner, + ) + +dyn = _DynamicPacket(inner=_PerInner(mode=0)) + +assert _DynamicPacket.ASN1_root._resolve_cls(dyn) is _PerInner + +empty_packet = _PacketWrap(inner=None) + +assert raw(empty_packet) == b"" + +class _BerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_INTEGER("extra", 0), + ) + +_raises( + BER_Decoding_Error, + lambda: _BerSeq.ASN1_root.m2i( + _BerSeq(), + bytes.fromhex("300702010102010200ff"), + ), +) + +class _OerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, size_len=1, oer_unsigned=True), + ) + +_, remain = _OerSeq.ASN1_root.m2i(_OerSeq(), b"\x01\xff") + +assert remain == b"\xff" + +class _PerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ) + +_raises( + UPER_Decoding_Error, + lambda: _PerSeq.ASN1_root.m2i(_PerSeq(), b"\x80\xff"), +) + +empty_seq = _BerSeq() + +_BerSeq.ASN1_root._dissect_sequence_children(empty_seq, b"") + +assert empty_seq.id is None + +assert empty_seq.extra is None + +class _OptListRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_optional( + ASN1F_SEQUENCE_OF("items", [], ASN1F_INTEGER), + ), + ) + +opt_list = _OptListRecord(id=1, items=None) + +assert raw(opt_list) + +field = ASN1F_INTEGER("n", 0) + +with mock.patch.object( + _InnerRecord, "__init__", side_effect=ASN1F_badsequence, +): + pkt_obj, remain = field.extract_packet( + _InnerRecord, b"\xab\xcd", _underlayer=None, + ) + +assert isinstance(pkt_obj, Raw) + +assert pkt_obj.load == b"\xab\xcd" + +assert remain == b"\xab\xcd" + +True + += asn1fields more coverage +_raises( + ASN1_Error, + lambda: ASN1F_INTEGER("x", 0, implicit_tag=1, explicit_tag=2), +) + +class _IntRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0) + +field = _IntRecord.ASN1_root + +_raises( + ASN1_Error, + lambda: field.i2m(_IntRecord(), ASN1_STRING(b"bad")), +) + +flex_field = ASN1F_INTEGER("n", 0, flexible_tag=True, explicit_tag=0xA0) + +obj, remain = flex_field.m2i(_IntRecord(), bytes.fromhex("a1020101")) + +assert obj.tag != ASN1_Class_UNIVERSAL.INTEGER or remain == b"" + +class _FlexSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + explicit_tag=0xA1, + flexible_tag=True, + ) + +flex_seq = _FlexSeq(id=1) + +assert raw(flex_seq) + +decoded = _FlexSeq(raw(flex_seq)) + +assert decoded.id.val == 1 + +assert ASN1F_BOOLEAN("b", False).randval() is not None + +assert ASN1F_BIT_STRING("b", b"").randval() is not None + +assert ASN1F_OID("o", None).randval() is not None + +assert ASN1F_UTC_TIME("t", "").randval() is not None + +assert " 0 + +empty_inner, remain = packet_field.m2i(_FlexPacket(), b"") + +assert empty_inner is None and remain == b"" + +obj_val = packet_field.i2m(_FlexPacket(), _InnerRecord(mode=0)) + +assert len(obj_val) > 0 + +flags_field = _FlagsRecord.ASN1_root.seq[0] + +assert flags_field.i2repr(_FlagsRecord(), None) == "None" + +class _OerFlexSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER, + explicit_tag=0xA1, + ) + +_OerFlexSeqOf.ASN1_root.flexible_tag = True + +oer_seq = _OerFlexSeqOf(values=[1]) + +data = raw(oer_seq) + +decoded = _OerFlexSeqOf(data) + +assert decoded.values[0].val == 1 + +class _BerFlexSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER, + explicit_tag=0xA1, + ) + +_BerFlexSeqOf.ASN1_root.flexible_tag = True + +ber_seq = _BerFlexSeqOf(values=[2]) + +assert raw(ber_seq) + +class _ExtChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + uper_extensible=True, + ) + +dec = UPER_Decoder(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtChoice.ASN1_root.m2i_from_decoder(_ExtChoice(), dec), +) + +class _SingleChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER) + +single = _SingleChoice(c=ASN1_INTEGER(3)) + +assert raw(single) + +class _FlexChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + flexible_tag=True, + ) + +flex_choice = _FlexChoice(c=ASN1_INTEGER(4)) + +assert raw(flex_choice) + +class _OerPktChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +oer_pkt_choice = _OerPktChoice(c=ASN1_STRING(b"hi")) + +assert raw(oer_pkt_choice) + +True + + ++ ASN.1 UPER field hooks and packet extras += uper field hooks registered +assert ASN1_Codecs.hooks[ASN1_Codecs.PER] + +assert ASN1_Codecs.PER.hook("sequence_m2i") is not None + +use_object_enc = ASN1_Codecs.PER.hook("use_object_enc") + +assert use_object_enc( + UPERConstrainedInt.ASN1_root, UPERConstrainedInt(), ASN1_INTEGER(1), +) is False + +True + += uper DEFAULT published on asn1fields +assert ASN1F_DEFAULT is asn1fields.ASN1F_DEFAULT + +assert issubclass(ASN1F_DEFAULT, ASN1F_optional) + +enum_fld = ASN1F_ENUMERATED("e", 0, {1: "a", 2: "b"}) + +# ENUMERATED values are added per-codec, so BER packets keep an empty +# codec_opts while PER packets get the permitted values. +assert enum_fld.codec_opts == {} + +assert "uper_enum_values" not in enum_fld._codec_kwargs( + type("P", (), {"ASN1_codec": ASN1_Codecs.BER})() +) + +assert enum_fld._codec_kwargs( + type("P", (), {"ASN1_codec": ASN1_Codecs.PER})() +)["uper_enum_values"] == [1, 2] + +assert hasattr(ASN1F_field, "encode_into") + +assert hasattr(ASN1F_SEQUENCE, "dissect_from_decoder") + +True + += uper use_object_enc and codec_opts +fld = UPERConstrainedInt.ASN1_root + +assert fld.codec_opts == {"uper_min": 0, "uper_max": 255} + +assert fld._use_object_enc(UPERConstrainedInt(), ASN1_INTEGER(5)) is False + +assert raw(UPERConstrainedInt(n=5)) == b"\x05" + +assert _val(_roundtrip(UPERConstrainedInt, UPERConstrainedInt(n=5)).n) == 5 + +True + += uper DEFAULT presence bit +absent = UPERSmallDefaultRecord(id=1, n=5) + +present = UPERSmallDefaultRecord(id=1, n=7) + +assert raw(absent) == bytes.fromhex("0080") + +assert raw(present) == bytes.fromhex("80b8") + +assert raw(absent) != raw(present) + +decoded_absent = _roundtrip(UPERSmallDefaultRecord, absent) + +decoded_present = _roundtrip(UPERSmallDefaultRecord, present) + +assert _val(decoded_absent.n) == 5 + +assert _val(decoded_present.n) == 7 + +True + += uper empty constrained sequence of +pkt = UPEREmptySeqOf(values=[]) + +assert raw(pkt) == b"\x00" + +decoded = _roundtrip(UPEREmptySeqOf, pkt) + +assert decoded.values == [] + +True + += uper extensible sequence of outside range +pkt = UPERExtSeqOf(values=[1, 2, 3]) + +assert raw(pkt) == bytes.fromhex("8194c0") + +decoded = _roundtrip(UPERExtSeqOf, pkt) + +assert [_val(x) for x in decoded.values] == [1, 2, 3] + +True + += uper FLAGS field +pkt = UPERFlagsField(f="101") + +assert raw(pkt) == bytes.fromhex("a0") + +decoded = _roundtrip(UPERFlagsField, pkt) + +assert decoded.f.val == "101" + +assert UPERFlagsField.ASN1_root.get_flags(decoded) == ["a", "c"] + +True + += uper nested ASN1F_PACKET +pkt = UPERWrappedPacket(id=1, inner=UPERInnerPacket(x=7)) + +assert raw(pkt) == bytes.fromhex("0170") + +decoded = _roundtrip(UPERWrappedPacket, pkt) + +assert _val(decoded.id) == 1 + +assert _val(decoded.inner.x) == 7 + +True + += uper field encode_into nesting +built = UPERWrappedPacket(id=1, inner=UPERInnerPacket(x=7)) + +enc = UPER_Encoder() + +UPERWrappedPacket.ASN1_root.encode_into(enc, built) + +assert enc.as_bytes() == raw(built) + +empty = UPERWrappedPacket() + +UPERWrappedPacket.ASN1_root.dissect_from_decoder( + empty, UPER_Decoder(raw(built)), +) + +assert _val(empty.id) == 1 + +assert _val(empty.inner.x) == 7 + +True + += uper unconstrained bit string counts bits +# X.691 16.11: the length determinant of an unconstrained BIT STRING counts +# bits, not octets, and nothing is padded before the next field. Byte vectors +# checked against asn1tools. +class UPERFreeBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING("b", ""), + ASN1F_INTEGER("tail", 0, uper_min=0, uper_max=255), + ) + +for bits, expected in [ + ("", "00a5"), + ("1", "01d280"), + ("10110", "05b528"), + ("10110011", "08b3a5"), + ("1" * 20, "14fffffa50"), +]: + pkt = UPERFreeBitString(b=bits, tail=0xa5) + assert raw(pkt) == bytes.fromhex(expected), (bits, raw(pkt).hex()) + decoded = _roundtrip(UPERFreeBitString, pkt) + assert decoded.b.val == bits, (bits, decoded.b.val) + assert _val(decoded.tail) == 0xa5 + +True + += uper fixed size bit string refuses a mismatched length +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_BIT_STRING("b", "0" * 8, size_len=8)) + +assert raw(UPERFixedBitString(b="10110011")) == bytes.fromhex("b3") + +_raises(UPER_Encoding_Error, lambda: raw(UPERFixedBitString(b="101"))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERFixedBitString(b="1011001100"))) + +True + += uper octet string fragmentation +# X.691 11.9.3.8. Byte vectors checked against asn1tools: a fragment header, +# 16K octets, then the terminating determinant. +class UPERFreeOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_STRING("s", "")) + +data = bytes(i % 256 for i in range(16384)) + +built = raw(UPERFreeOctetString(s=data)) + +assert built == b"\xc1" + data + b"\x00" + +assert _roundtrip(UPERFreeOctetString, UPERFreeOctetString(s=data)).s.val == data + +data = bytes(i % 256 for i in range(40000)) + +built = raw(UPERFreeOctetString(s=data)) + +assert built == b"\xc2" + data[:32768] + b"\x9c\x40" + data[32768:] + +assert _roundtrip(UPERFreeOctetString, UPERFreeOctetString(s=data)).s.val == data + +True + += uper sequence of fragmentation +class UPERFreeSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER("x", 0, uper_min=0, uper_max=255)), + ) + +items = [i % 256 for i in range(16385)] + +built = raw(UPERFreeSeqOf(values=items)) + +assert built == b"\xc1" + bytes(items[:16384]) + b"\x01" + bytes(items[16384:]) + +decoded = _roundtrip(UPERFreeSeqOf, UPERFreeSeqOf(values=items)) + +assert [_val(x) for x in decoded.values] == items + +True + += uper integer with an empty length determinant +_raises( + UPER_Decoding_Error, + lambda: UPER_Decoder(b"\x00").read_unconstrained_whole_number(), +) + +True + += uper choice with tagged packet alternatives +class UPERAltA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("i", 0), + ) + +class UPERAltB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("b", True), + ) + +class UPERTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE( + "c", None, + ASN1F_PACKET("a1", None, UPERAltA, explicit_tag=0xA0), + ASN1F_PACKET("a2", None, UPERAltB, explicit_tag=0xA1), + ), + ) + +# Reference (asn1tools) for +# Ch ::= SEQUENCE { c CHOICE { a1 A, a2 B } } +# A ::= SEQUENCE { i INTEGER }, B ::= SEQUENCE { b BOOLEAN } +# The alternative is picked by the type of the value, tags are not encoded. +assert raw(UPERTaggedChoice(c=UPERAltA(i=4))) == b"\x00\x82\x00" + +assert raw(UPERTaggedChoice(c=UPERAltB(b=False))) == b"\x80" + +decoded = _roundtrip(UPERTaggedChoice, UPERTaggedChoice(c=UPERAltA(i=4))) + +assert isinstance(decoded.c, UPERAltA) and decoded.c.i.val == 4 + +decoded = _roundtrip(UPERTaggedChoice, UPERTaggedChoice(c=UPERAltB(b=False))) + +assert isinstance(decoded.c, UPERAltB) and decoded.c.b.val == 0 + +True + += uper choice with packet class alternatives +class UPERClassChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, UPERAltA, ASN1F_INTEGER) + +pkt = UPERClassChoice(c=UPERAltA(i=5)) + +# index bit 0, then the sequence: an unconstrained integer of one octet +assert raw(pkt) == b"\x00\x82\x80" + +decoded = _roundtrip(UPERClassChoice, pkt) + +assert isinstance(decoded.c, UPERAltA) and decoded.c.i.val == 5 + +decoded = _roundtrip(UPERClassChoice, UPERClassChoice(c=ASN1_INTEGER(3))) + +assert decoded.c.val == 3 + +# An unset CHOICE encodes to nothing, as the field is then absent +assert raw(UPERClassChoice(c=None)) == b"" + +True + += uper choice with constrained field alternatives +class UPERFieldChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", None, + ASN1F_INTEGER("a", 0, uper_min=0, uper_max=7), + ASN1F_INTEGER("b", 0, uper_min=0, uper_max=255), + ASN1F_NULL("n1", 0), + ASN1F_NULL("n2", 0), + ) + +# Reference (asn1tools) for +# Choice ::= CHOICE { a INTEGER (0..7), b INTEGER (0..255), n1 NULL, n2 NULL } +# Alternatives carry their own constraints and may share a tag, as PER tells +# them apart by their position alone. +assert UPERFieldChoice.ASN1_root.choice_order == [2, 2, 5, 5] + +assert raw(UPERFieldChoice(c=ASN1_INTEGER(5))) == b"\x28" + +assert raw(UPERFieldChoice(c=ASN1_NULL(0))) == b"\x80" + +assert UPERFieldChoice(b"\x28").c.val == 5 + +# The second alternative uses eight bits, the first three +assert UPERFieldChoice(b"\x72\x00").c.val == 200 + +assert isinstance(UPERFieldChoice(b"\xc0").c, ASN1_NULL) + +# A dissected value remembers the alternative it was read as, so it goes back +# out on the same one instead of on the first alternative of its type +for encoded in (b"\x28", b"\x72\x00", b"\x80", b"\xc0"): + assert raw(UPERFieldChoice(encoded)) == encoded + +# A value built by hand still takes the first alternative that carries it +assert raw(UPERFieldChoice(c=ASN1_NULL(0))) == b"\x80" + +True + += uper enumerated bounds +_raises(UPER_Encoding_Error, lambda: UPER_enumerated_enc(UPER_Encoder(), 0, [])) + +_raises(UPER_Decoding_Error, lambda: UPER_enumerated_dec(UPER_Decoder(b"\x00"), [])) + +# Three values are indexed on two bits, which can carry an index they do not +# define +_raises(UPER_Decoding_Error, lambda: UPER_enumerated_dec(UPER_Decoder(b"\xc0"), [0, 1, 2])) + +assert UPER_enumerated_dec(UPER_Decoder(b"\x40"), [0, 1, 2]) == 1 + +True + += uper untyped codec falls back on string and integer +enc = UPER_Encoder() + +UPERcodec_Object.encode_into(enc, b"hi") + +assert enc.as_bytes() == b"\x02hi" + +enc = UPER_Encoder() + +UPERcodec_Object.encode_into(enc, 5) + +assert enc.as_bytes() == b"\x01\x05" + +_raises(UPER_Encoding_Error, lambda: UPERcodec_Object.encode_into(UPER_Encoder(), object())) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_Object.dec_from_decoder(UPER_Decoder(b"\x01"))) + +True + += uper choice with a single alternative +class UPEROneAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, ASN1F_INTEGER) + +# X.691 23.5: one alternative leaves nothing to choose, so no index is encoded +assert raw(UPEROneAlt(c=ASN1_INTEGER(4))) == b"\x01\x04" + +assert _roundtrip(UPEROneAlt, UPEROneAlt(c=ASN1_INTEGER(4))).c.val == 4 + +class UPERThreeAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, ASN1F_INTEGER, ASN1F_STRING, ASN1F_BOOLEAN) + +# Three alternatives are indexed on two bits, which can carry a fourth index +_raises(ASN1_Error, lambda: UPERThreeAlt(b"\xc0")) + +True + += uper sequence of an unset field +class UPERUnsetSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER("x", 0, uper_min=0, uper_max=255)), + ) + +assert raw(UPERUnsetSeqOf(values=None)) == b"\x00" + +assert raw(UPERUnsetSeqOf(values=[])) == b"\x00" + +True + += uper field rejects a value of another type +class UPERIntOnly(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0), + ) + +_raises(ASN1_Error, lambda: raw(UPERIntOnly(n=ASN1_STRING(b"x")))) + +enc = UPER_Encoder() + +UPERcodec_OID.encode_into(enc, b"") + +assert enc.as_bytes() == b"\x00" + +True + += uper constrained values honour their range +class UPERSmallInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7), + ) + +assert raw(UPERSmallInt(n=5)) == b"\xa0" + +# The value is written on the width of the range, so one outside it would be +# read back as another value +_raises(UPER_Encoding_Error, lambda: raw(UPERSmallInt(n=100))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERSmallInt(n=-3))) + +class UPERSmallExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7, uper_extensible=True), + ) + +# An extensible range does accept it, as an extension addition +assert _roundtrip(UPERSmallExtInt, UPERSmallExtInt(n=100)).n.val == 100 + +True + += uper sequence of honours its size constraint +class UPERSizedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("x", 0, uper_min=0, uper_max=255), + uper_min=1, uper_max=3, + ), + ) + +assert raw(UPERSizedSeqOf(values=[ASN1_INTEGER(7)])) == b"\x01\xc0" + +# An unset field is an empty one, which the constraint rules out here +_raises(UPER_Encoding_Error, lambda: raw(UPERSizedSeqOf(values=[]))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERSizedSeqOf(values=None))) + +True diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index 9fa0bad0f44..a50e146b3b4 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -23,9 +23,12 @@ repr(ASN1_GENERALIZED_TIME("19991231235959")).startswith("1999-12-31 23:59:59 <" repr(ASN1_GENERALIZED_TIME("19991231235959.999")).startswith("1999-12-31 23:59:59.999 <") = with microseconds (invalid) assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99x")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.9999")) +True + ASN.1 Generalized Time (Zulu) = Z short HH @@ -52,8 +55,10 @@ repr(ASN1_GENERALIZED_TIME("19991231235959.999+0100")).startswith("1999-12-31 23 repr(ASN1_GENERALIZED_TIME("19991231235959-2359")).startswith("1999-12-31 23:59:59 -2359 <") = offset invalid (offset >= 24h) assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959-2400")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959+2400")) +True + ASN.1 UTC Time = UTC short HHMM @@ -83,10 +88,17 @@ ASN1_GENERALIZED_TIME("199912312359").datetime == datetime(1999, 12, 31, 23, 59) ASN1_GENERALIZED_TIME("19991231235959").datetime == datetime(1999, 12, 31, 23, 59, 59) = datetime assignment x = ASN1_GENERALIZED_TIME("19991231235959.999") + x.datetime = datetime(2020, 12, 31) + assert x.val == "20201231000000" + x.datetime = x.datetime.replace(tzinfo=timezone.utc) + x.val == "20201231000000Z" + +True + = datetime construction ASN1_GENERALIZED_TIME(datetime(2020, 12, 31)).val == "20201231000000" = datetime construction (UTC) @@ -101,3 +113,496 @@ ASN1_UTC_TIME(datetime(2020, 12, 31)).val == "201231000000" ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone.utc)).val == "201231000000Z" = UTC datetime construction (offset) ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=-23, minutes=-59)))).val == "201231000000-2359" + ++ ASN.1 cross-codec build and dissect += import contrib codecs +import scapy.contrib.oer +import scapy.contrib.uper +from scapy.contrib.oer import * +from scapy.contrib.uper import * +from scapy.packet import raw += prepare helpers and packet classes +class BERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class BERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1), + ASN1F_STRING("s", "", size_len=3), + ) + +class BEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class BERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class BERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class BERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class BEROptionalSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("hdr", 0), + ASN1F_optional(ASN1F_SEQUENCE( + ASN1F_INTEGER("id", None), + ASN1F_STRING("label", None), + explicit_tag=0xA0, + )), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +class OERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class OERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class OEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class OERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class OERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class OERNestedSequenceTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ASN1F_INTEGER("id", 0), + ) + +class OERSequenceOfWithTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ASN1F_INTEGER("id", 0), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +class UPERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class UPERIntegerField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0) + +class UPERBooleanField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BOOLEAN("b", False) + +class UPERStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("s", "") + +class UPERConstrainedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, + ) + +class UPEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class UPERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class UPERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class UPERChoiceStringFirst(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_STRING(b""), ASN1F_STRING, ASN1F_INTEGER, + ) + +class UPERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPEREnumeratedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +class UPERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING( + "bits", "0", uper_min=1, uper_max=20, + ) + +class UPERMessagePrefix(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("msgId", 0), + ASN1F_INTEGER("myflag", 0), + ASN1F_STRING("szDescription", "", size_len=10), + ASN1F_BOOLEAN("isReady", False), + ) + +class UPERSequenceWithChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING), + ) + +class UPERNullPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_NULL("n", None) + +class UPERVariableOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("data", "", uper_min=1, uper_max=20) + +class UPERConstrainedRangeInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=15) + +class UPERSequenceWithEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_ENUMERATED("state", 1, {1: "alpha", 200: "beta"}), + ) + +class UPERSequenceOfStrings(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("items", [], ASN1F_STRING) + +class UPERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERSequenceWithNull(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_NULL("n", None), + ) + +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING("b", "0", uper_min=16, uper_max=16) + +class UPERSequenceOfConstrainedInts(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("item", 0, uper_min=0, uper_max=255), + ) + +class UPERSignedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=-128, uper_max=127) + +class UPERMultiOptional(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("a", 0)), + ASN1F_optional(ASN1F_STRING("b", "")), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) += ber oer per choice build +class BERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class PERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +for cls in (BERChoice, OERChoice, PERChoice): + as_int = cls(c=ASN1_INTEGER(99)) + assert len(raw(as_int)) > 0 + decoded = _roundtrip(cls, as_int) + assert decoded.c.val == 99 + as_str = cls(c=ASN1_STRING(b"AB")) + assert len(raw(as_str)) > 0 + decoded = _roundtrip(cls, as_str) + assert decoded.c.val == b"AB" + +True + += ber oer per record dissect +for cls, data_hex in [ + ( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + ), + ( + OERRecord, + "80" + "012aff0268690107" + "0103010101020103", + ), + ( + UPERRecord, + "8095409a1a4041c0c04040408040c0", + ), +]: + _assert_record(_dissect(cls, data_hex)) + +True + += ber oer per constrained integer codec_opts +class BERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +class OERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +class PERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +for cls, expected in ( + (BERConstrained, b"\x02\x81\x02\x00\xc8"), + (OERConstrained, b"\xc8"), + (PERConstrained, b"\xc8"), +): + pkt = cls(n=200) + assert raw(pkt) == expected + assert _roundtrip(cls, pkt).n.val == 200 + assert cls.ASN1_root.codec_opts["oer_unsigned"] is True + assert cls.ASN1_root.codec_opts["uper_min"] == 0 + assert cls.ASN1_root.codec_opts["uper_max"] == 255 + +True + += ber oer per empty sequence of +class BEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class PEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, uper_min=0, uper_max=7), + uper_min=0, + uper_max=3, + ) + +for cls in (BEREmptySeqOf, OEREmptySeqOf, PEREmptySeqOf): + pkt = cls(values=[]) + decoded = _roundtrip(cls, pkt) + assert decoded.values == [] + assert len(raw(pkt)) > 0 + +True + += field hooks present after contrib load +assert ASN1_Codecs.hooks[ASN1_Codecs.OER] + +assert ASN1_Codecs.hooks[ASN1_Codecs.PER] + +assert ASN1_Codecs.OER.hook("sequence_m2i") is not None + +assert ASN1_Codecs.PER.hook("sequence_m2i") is not None + +True + += ber oer per default component +class _BerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +class _OerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +class _PerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +# A component holding its default value is not encoded, and comes back as the +# default when the encoding does not carry it. An unset component counts as +# holding it, and the default may be given as an ASN.1 object. +for cls in (_BerDefault, _OerDefault, _PerDefault): + assert len(raw(cls(a=1, b=7))) < len(raw(cls(a=1, b=9))) + absent = _roundtrip(cls, cls(a=1, b=7)).b + assert getattr(absent, "val", absent) == 7 + assert _roundtrip(cls, cls(a=1, b=9)).b.val == 9 + assert raw(cls(a=1, b=None)) == raw(cls(a=1, b=7)) + +class _AsnObjectDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_DEFAULT(ASN1F_INTEGER("a", 7), ASN1_INTEGER(7)), + ) + +assert raw(_AsnObjectDefault(a=ASN1_INTEGER(7))) == raw(_AsnObjectDefault(a=7)) + +assert raw(_AsnObjectDefault(a=7)) == b"\x00" + +True diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index 896f2ec746a..2e4704b9353 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -457,30 +457,30 @@ assert BERcodec_STRING.enc(b"x", uper_max=10) == BERcodec_STRING.enc(b"x") BERcodec_SEQUENCE.enc(BERcodec_INTEGER.enc(1), uper_min=0) == BERcodec_SEQUENCE.enc(BERcodec_INTEGER.enc(1)) + ASN.1 codec tagging contract -= BER tagging is exposed on the codec -assert ASN1_Codecs.BER.tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" -diff, payload = ASN1_Codecs.BER.tagging_dec( - b"\xa0\x01\x05", hidden_tag=2, implicit_tag=0xA0 -) += BER hooks the tagging of a field +tagging_enc = ASN1_Codecs.BER.hook("tagging_enc") + +assert tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" + +assert ASN1_Codecs.hooks[ASN1_Codecs.BER]["tagging_enc"] is tagging_enc + +tagging_dec = ASN1_Codecs.BER.hook("tagging_dec") + +diff, payload = tagging_dec(b"\xa0\x01\x05", hidden_tag=2, implicit_tag=0xA0) + diff is None and payload == b"\x02\x01\x05" -= identity tagging for PER-style codecs -def _id_tagging_enc(s, **kwargs): - return s += a codec that hooks no tagging leaves the encoding alone +class _NoHooks: + ASN1_codec = ASN1_Codecs.CER -def _id_tagging_dec(s, **kwargs): - return None, s +assert ASN1_Codecs.CER.hook("tagging_enc") is None -ASN1_Codecs.PER.register_tagging(_id_tagging_enc, _id_tagging_dec) -try: - assert ASN1_Codecs.PER.tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\x02\x01\x05" - diff, payload = ASN1_Codecs.PER.tagging_dec( - b"\x02\x01\x05", hidden_tag=2, explicit_tag=0xA1 - ) - assert diff is None and payload == b"\x02\x01\x05" -finally: - del ASN1_Codecs.PER._tagging_enc - del ASN1_Codecs.PER._tagging_dec +fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) + +assert fld._tagging_enc(_NoHooks(), b"\x02\x01\x05", explicit_tag=0xA0) == b"\x02\x01\x05" + +fld._tagging_dec(_NoHooks(), b"\x02\x01\x05", explicit_tag=0xA0) == (None, b"\x02\x01\x05") = field _codec_kwargs and object-enc hooks class P(ASN1_Packet): @@ -513,3 +513,191 @@ class ExtraPkt(ASN1_Packet): # BER enc swallows unknown kwargs; round-trip still works. assert raw(ExtraPkt(n=7)) == b"\x02\x01\x07" ExtraPkt(raw(ExtraPkt(n=7))).n.val == 7 + += field codec_opts storage +plain = ASN1F_INTEGER("n", 0) + +assert plain.codec_opts == {} + +assert plain._codec_kwargs(type("P", (), {"ASN1_codec": ASN1_Codecs.BER})()) == { + "size_len": None, +} + +constrained = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, +) + +assert constrained.codec_opts == { + "oer_unsigned": True, + "uper_min": 0, + "uper_max": 255, +} + +# Constraints live in codec_opts only: they must not become field attributes. +assert not hasattr(constrained, "oer_unsigned") + +assert not hasattr(constrained, "uper_min") + +kwargs = constrained._codec_kwargs( + type("P", (), {"ASN1_codec": ASN1_Codecs.BER})() +) + +assert kwargs["size_len"] == 1 + +assert kwargs["oer_unsigned"] is True + +assert kwargs["uper_min"] == 0 + +assert kwargs["uper_max"] == 255 + +# BER still encodes with constraints present in kwargs. +class ConstrainedBer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +assert raw(ConstrainedBer(n=5)) == b"\x02\x81\x01\x05" + +assert ConstrainedBer(raw(ConstrainedBer(n=5))).n.val == 5 + +True + += CHOICE order properties +choice = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, +) + +assert choice.choice_order == [2, 4] + +assert choice.choice_list[0] is ASN1F_INTEGER + +assert choice.choice_list[1] is ASN1F_STRING + +True + ++ ASN.1 BER build and dissect extras + += import helpers +from scapy.packet import raw + += prepare helpers +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + += ber record build roundtrip +pkt = BERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(BERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += ber field dissect +tagged = _dissect(BERTaggedInteger, "a103020105") + +assert tagged.n.val == 5 + +fixed = _dissect(BERFixedFields, "300d02810200c80483000003414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(BEROptionalField, "3008020101a003020107") + +assert present.id.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(BEROptionalField, "3003020101") + +assert absent.id.val == 1 + +assert absent.extra is None + +seqof = _dissect(BERSequenceOfIntegers, "3009020101020102020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +as_int = _dissect(BERChoiceField, "020163") + +assert as_int.c.val == 99 + +as_str = _dissect(BERChoiceField, "040178") + +assert as_str.c.val == b"x" + +True + += ber record dissect +decoded = _dissect( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + +) + +_assert_record(decoded) + +empty = _dissect(BERRecord, "300a02010101010004003000") + +_assert_record_empty(empty) + +True + diff --git a/tox.ini b/tox.ini index 495672a8e9d..05ad7014517 100644 --- a/tox.ini +++ b/tox.ini @@ -192,5 +192,7 @@ per-file-ignores = scapy/libs/winpcapy.py:F405,F403,E501 scapy/libs/manuf.py:E501 scapy/tools/UTscapy.py:E501 + scapy/tools/generate_its_asn1.py:E501 exclude = scapy/libs/ethertypes.py, - scapy/layers/msrpce/raw/* + scapy/layers/msrpce/raw/*, + scapy/contrib/automotive/v2x/packets.py