From 5ecb8a018b99eb373cec9b744491422854e351d5 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Sun, 19 Jul 2026 20:58:33 +0200 Subject: [PATCH 01/19] contrib: add ASN.1 OER and UPER codecs Move OER/UPER codec implementations to scapy.contrib and wire asn1fields for OER/PER using the pluggable tagging/kwargs hooks. AI-Assisted: yes (Cursor) --- .config/codespell_ignore.txt | 5 + scapy/asn1/ber.py | 3 + scapy/asn1fields.py | 677 +++++++++-- scapy/contrib/oer.py | 825 ++++++++++++++ scapy/contrib/uper.py | 1369 +++++++++++++++++++++++ test/scapy/layers/asn1.uts | 355 ++++++ test/scapy/layers/asn1_build_tests.py | 185 +++ test/scapy/layers/asn1_coverage.py | 890 +++++++++++++++ test/scapy/layers/asn1_dissect_tests.py | 280 +++++ test/scapy/layers/ber_codec.py | 275 +++++ test/scapy/layers/ber_packets.py | 184 +++ test/scapy/layers/oer_fuzz.py | 106 ++ test/scapy/layers/oer_iop.py | 160 +++ test/scapy/layers/oer_packets.py | 209 ++++ test/scapy/layers/uper_asn1scc_iop.py | 190 ++++ test/scapy/layers/uper_codec.py | 174 +++ test/scapy/layers/uper_fuzz.py | 133 +++ test/scapy/layers/uper_helpers.py | 122 ++ test/scapy/layers/uper_iop.py | 189 ++++ test/scapy/layers/uper_packets.py | 543 +++++++++ 20 files changed, 6789 insertions(+), 85 deletions(-) create mode 100644 scapy/contrib/oer.py create mode 100644 scapy/contrib/uper.py create mode 100644 test/scapy/layers/asn1_build_tests.py create mode 100644 test/scapy/layers/asn1_coverage.py create mode 100644 test/scapy/layers/asn1_dissect_tests.py create mode 100644 test/scapy/layers/ber_codec.py create mode 100644 test/scapy/layers/ber_packets.py create mode 100644 test/scapy/layers/oer_fuzz.py create mode 100644 test/scapy/layers/oer_iop.py create mode 100644 test/scapy/layers/oer_packets.py create mode 100644 test/scapy/layers/uper_asn1scc_iop.py create mode 100644 test/scapy/layers/uper_codec.py create mode 100644 test/scapy/layers/uper_fuzz.py create mode 100644 test/scapy/layers/uper_helpers.py create mode 100644 test/scapy/layers/uper_iop.py create mode 100644 test/scapy/layers/uper_packets.py 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/ber.py b/scapy/asn1/ber.py index c3da0f15b5d..59e58073b3f 100644 --- a/scapy/asn1/ber.py +++ b/scapy/asn1/ber.py @@ -297,6 +297,9 @@ def __new__(cls, class BERcodec_Object(Generic[_K], metaclass=BERcodec_metaclass): codec = ASN1_Codecs.BER tag = ASN1_Class_UNIVERSAL.ANY + skip_tagging = False + tagging_enc = staticmethod(BER_tagging_enc) + tagging_dec = staticmethod(BER_tagging_dec) @classmethod def asn1_object(cls, val): diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 0d92161153e..d5e4f932fb3 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -9,14 +9,30 @@ """ import copy - from functools import reduce +from typing import ( + Any, + AnyStr, + Callable, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, + TYPE_CHECKING, +) +from scapy import packet from scapy.asn1.asn1 import ( ASN1_BIT_STRING, ASN1_BOOLEAN, ASN1_Class, ASN1_Class_UNIVERSAL, + ASN1_Codecs, ASN1_Decoding_Error, ASN1_Error, ASN1_INTEGER, @@ -40,28 +56,22 @@ RandField, ) -from scapy import packet - -from typing import ( - Any, - AnyStr, - Callable, - Dict, - Generic, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, - cast, - TYPE_CHECKING, -) - if TYPE_CHECKING: from scapy.asn1packet import ASN1_Packet +def _oer(): + # type: () -> Any + from scapy.contrib import oer as _m + return _m + + +def _uper(): + # type: () -> Any + from scapy.contrib import uper as _m + return _m + + class ASN1F_badsequence(Exception): pass @@ -92,6 +102,10 @@ def __init__(self, explicit_tag=None, # type: Optional[int] flexible_tag=False, # type: Optional[bool] size_len=None, # type: Optional[int] + oer_unsigned=False, # type: Optional[bool] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + uper_enum_values=None, # type: Optional[List[int]] ): # type: (...) -> None if context is not None: @@ -104,6 +118,10 @@ def __init__(self, else: self.default = self.ASN1_tag.asn1_object(default) # type: ignore self.size_len = size_len + self.oer_unsigned = oer_unsigned + self.uper_min = uper_min + self.uper_max = uper_max + self.uper_enum_values = uper_enum_values 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" @@ -113,6 +131,7 @@ def __init__(self, # network_tag gets useful for ASN1F_CHOICE self.network_tag = int(implicit_tag or explicit_tag or self.ASN1_tag) self.owners = [] # type: List[Type[ASN1_Packet]] + self._uper_kwargs_cache = None # type: Optional[Dict[str, Any]] def register_owner(self, cls): # type: (Type[ASN1_Packet]) -> None @@ -156,14 +175,24 @@ 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} + # OER/UPER need extra constraints on every enc/dec call. + if pkt.ASN1_codec == ASN1_Codecs.PER: + return self._uper_codec_kwargs() + kwargs = {"size_len": self.size_len} # type: Dict[str, Any] + if pkt.ASN1_codec == ASN1_Codecs.OER: + kwargs["size_len"] = self.size_len or 0 + if self.oer_unsigned: + kwargs["oer_unsigned"] = self.oer_unsigned + 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). + # BER/LDAP: item.enc() when size_len is unset. PER/OER constraints + # must go through codec.enc(**kwargs). + if pkt.ASN1_codec == ASN1_Codecs.PER: + return False + if pkt.ASN1_codec == ASN1_Codecs.OER: + return self.size_len is None and not self.oer_unsigned return self.size_len is None def _encode_item(self, pkt, item): @@ -186,7 +215,7 @@ def _encode_item(self, pkt, item): item = item.val elif hasattr(item, "self_build"): # Packet values (e.g. ASN1F_STRING_PacketField) must still go through - # the BER type codec so the universal tag/length are applied. + # the type codec so the universal tag/length are applied. item = item.self_build() codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) return codec.enc(item, **self._codec_kwargs(pkt)) @@ -218,6 +247,20 @@ def m2i(self, pkt, s): dec = codec.safedec if self.flexible_tag else codec.dec return dec(s, context=self.context, **self._codec_kwargs(pkt)) # type: ignore + def m2i_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> _A + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + return cast( + _A, + codec.dec_from_decoder( # type: ignore[attr-defined] + dec, **self._codec_kwargs(pkt), + ), + ) + + def dissect_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + self.set_val(pkt, self.m2i_from_decoder(pkt, dec)) + def i2m(self, pkt, x): # type: (ASN1_Packet, Union[bytes, _I, _A]) -> bytes if x is None: @@ -297,6 +340,54 @@ def copy(self): # type: () -> ASN1F_field[_I, _A] return copy.copy(self) + def _uper_codec_kwargs(self, size_len=None): + # type: (Optional[int]) -> Dict[str, Any] + # These kwargs only depend on attributes set once at __init__ time, + # so the common (no override) case is cached to avoid rebuilding the + # dict on every field access during build/dissect. + if size_len is None and self._uper_kwargs_cache is not None: + return self._uper_kwargs_cache + kwargs = { + "size_len": (self.size_len if size_len is None else size_len) or 0, + "oer_unsigned": self.oer_unsigned, + "uper_min": self.uper_min, + "uper_max": self.uper_max, + } # type: Dict[str, Any] + if ( + getattr(self, "uper_extensible", False) and + self.ASN1_tag == ASN1_Class_UNIVERSAL.INTEGER + ): + kwargs["uper_extensible"] = True + if self.uper_enum_values is not None: + kwargs["uper_enum_values"] = self.uper_enum_values + if size_len is None: + self._uper_kwargs_cache = kwargs + return kwargs + + def _uper_encode_into(self, enc, pkt, value=None): + # type: (Any, ASN1_Packet, 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), + ) + ############################ # Simple ASN1 Fields # @@ -313,9 +404,32 @@ def randval(self): class ASN1F_INTEGER(ASN1F_field[int, ASN1_INTEGER]): ASN1_tag = ASN1_Class_UNIVERSAL.INTEGER + def __init__(self, + name, # type: str + default, # type: Optional[Union[int, ASN1_INTEGER]] + context=None, # type: Optional[Type[ASN1_Class]] + implicit_tag=None, # type: Optional[int] + explicit_tag=None, # type: Optional[int] + flexible_tag=False, # type: Optional[bool] + size_len=None, # type: Optional[int] + oer_unsigned=False, # type: Optional[bool] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + uper_extensible=False, # type: bool + ): + # type: (...) -> None + super(ASN1F_INTEGER, self).__init__( + name, cast(Optional[ASN1_INTEGER], default), context=context, + implicit_tag=implicit_tag, explicit_tag=explicit_tag, + flexible_tag=flexible_tag, size_len=size_len, + oer_unsigned=oer_unsigned, uper_min=uper_min, + uper_max=uper_max, + ) + self.uper_extensible = uper_extensible + def randval(self): # type: () -> RandNum - return RandNum(-2**64, 2**64 - 1) + return RandNum(-2 ** 64, 2 ** 64 - 1) class ASN1F_enum_INTEGER(ASN1F_INTEGER): @@ -344,6 +458,7 @@ def __init__(self, for k in keys: i2s[k] = enum[k] s2i[enum[k]] = k + self.uper_enum_values = list(keys) def i2m(self, pkt, # type: ASN1_Packet @@ -378,12 +493,16 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] ): # type: (...) -> None super(ASN1F_BIT_STRING, self).__init__( name, None, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + uper_min=uper_min, + uper_max=uper_max, ) if isinstance(default, (bytes, str)): self.default = ASN1_BIT_STRING(default, @@ -493,13 +612,18 @@ class ASN1F_SEQUENCE(ASN1F_field[List[Any], List[Any]]): def __init__(self, *seq, **kwargs): # type: (*Any, **Any) -> None + uper_extensible = kwargs.pop("uper_extensible", False) name = "dummy_seq_name" default = [field.default for field in seq] super(ASN1F_SEQUENCE, self).__init__( name, default, **kwargs ) + self.uper_extensible = uper_extensible self.seq = seq self.islist = len(seq) > 1 + self._optionals = tuple( + f for f in seq if isinstance(f, (ASN1F_optional, ASN1F_DEFAULT)) + ) def __repr__(self): # type: () -> str @@ -514,6 +638,46 @@ 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_oer(self, pkt, s): + # type: (Any, bytes) -> Tuple[Any, bytes] + s = self._apply_tagging_dec(s, pkt, _fname=pkt.name) + s = self._dissect_sequence_children(pkt, s) + return [], s + + def _m2i_per(self, pkt, s): + # type: (Any, bytes) -> Tuple[Any, bytes] + dec = _uper().UPER_Decoder(s) + self._uper_dissect_from_decoder(pkt, dec) + if _uper().UPER_has_unexpected_remainder(dec): + raise _uper().UPER_Decoding_Error( + "unexpected remainder", + remaining=dec.remaining(), + ) + return [], b"" + + def _m2i_ber(self, pkt, s): + # type: (Any, bytes) -> Tuple[Any, bytes] + 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) + s = self._dissect_sequence_children(pkt, s) + if len(s) > 0: + raise BER_Decoding_Error("unexpected remainder", remaining=s) + return [], remain + def m2i(self, pkt, s): # type: (Any, bytes) -> Tuple[Any, bytes] """ @@ -524,24 +688,36 @@ 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. """ - 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, + if pkt.ASN1_codec == ASN1_Codecs.OER: + return self._m2i_oer(pkt, s) + if pkt.ASN1_codec == ASN1_Codecs.PER: + return self._m2i_per(pkt, s) + return self._m2i_ber(pkt, s) + + def _uper_dissect_from_decoder(self, pkt, dec): + # type: (Any, Any) -> None + if self.uper_extensible: + if dec.read_bit(): + raise _uper().UPER_Decoding_Error( + "ASN1F_SEQUENCE: extension additions are not supported" ) - return [], remain + presence = [dec.read_bit() for _ in self._optionals] + opt_idx = 0 + for obj in self.seq: + if isinstance(obj, (ASN1F_optional, ASN1F_DEFAULT)): + 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 dissect_from_decoder(self, pkt, dec): + # type: (Any, Any) -> None + self._uper_dissect_from_decoder(pkt, dec) def dissect(self, pkt, s): # type: (Any, bytes) -> bytes @@ -550,10 +726,25 @@ def dissect(self, pkt, s): def build(self, pkt): # type: (ASN1_Packet) -> bytes + if pkt.ASN1_codec == ASN1_Codecs.PER: + enc = _uper().UPER_Encoder() + self._uper_encode_into(enc, pkt) + return super(ASN1F_SEQUENCE, self).i2m(pkt, enc.as_bytes()) s = reduce(lambda x, y: x + y.build(pkt), self.seq, b"") return super(ASN1F_SEQUENCE, self).i2m(pkt, s) + def _uper_encode_into(self, enc, pkt, value=None): + # type: (Any, ASN1_Packet, Optional[Any]) -> None + if self.uper_extensible: + enc.append_bit(0) + for opt in self._optionals: + enc.append_bit(0 if opt.is_empty(pkt) else 1) + for obj in self.seq: + if isinstance(obj, (ASN1F_optional, ASN1F_DEFAULT)) and obj.is_empty(pkt): + continue + obj._uper_encode_into(enc, pkt) + class ASN1F_SET(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_UNIVERSAL.SET @@ -568,7 +759,7 @@ class ASN1F_SET(ASN1F_SEQUENCE): class ASN1F_SEQUENCE_OF(ASN1F_field[List[_SEQ_T], - List[ASN1_Object[Any]]]): +List[ASN1_Object[Any]]]): """ Two types are allowed as cls: ASN1_Packet, ASN1F_field """ @@ -582,6 +773,9 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + uper_extensible=False, # type: bool ): # type: (...) -> None if isinstance(cls, type) and issubclass(cls, ASN1F_field) or \ @@ -604,6 +798,28 @@ def __init__(self, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) self.default = default + self.uper_min = uper_min + self.uper_max = uper_max + self.uper_extensible = uper_extensible + + def _uper_count_enc(self, enc, count): + # type: (Any, int) -> None + if self.uper_min is not None and self.uper_max is not None: + _uper().UPER_constrained_int_enc(count, self.uper_min, self.uper_max, enc=enc) + else: + enc.append_length_determinant(count) + + def _uper_count_dec(self, dec): + # type: (Any) -> int + if self.uper_min is not None and self.uper_max is not None: + size = self.uper_max - self.uper_min + return cast( + int, + dec.read_non_negative_binary_integer( + _uper().UPER_bits_for_range(size), + ) + self.uper_min, + ) + return cast(int, dec.read_length_determinant()) def is_empty(self, pkt, # type: ASN1_Packet @@ -611,11 +827,90 @@ def is_empty(self, # type: (...) -> bool return ASN1F_field.is_empty(self, pkt) + def _extract_packet_from_decoder(self, dec, pkt): + # type: (Any, ASN1_Packet) -> Tuple[Any, bytes] + if self.holds_packets: + p = self.cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p, b"" + return self.fld.m2i_from_decoder(pkt, dec), b"" + + def m2i_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> List[Any] + if self.uper_extensible and dec.read_bit(): + count = dec.read_length_determinant() + else: + count = self._uper_count_dec(dec) + lst = [] + for _ in range(count): + item, _ = self._extract_packet_from_decoder(dec, pkt) + lst.append(item) + return lst + + def _uper_encode_into(self, enc, pkt, value=None): + # type: (Any, ASN1_Packet, Any) -> None + if value is None: + value = getattr(pkt, self.name) + if value is None: + self._uper_count_enc(enc, 0) + return + count = len(value) + if self.uper_extensible: + if ( + self.uper_min is not None and self.uper_max is not None and + self.uper_min <= count <= self.uper_max + ): + enc.append_bit(0) + else: + enc.append_bit(1) + enc.append_length_determinant(count) + for item in value: + if self.holds_packets: + cast("ASN1_Packet", item).ASN1_root._uper_encode_into( + enc, item, + ) + else: + self.fld._uper_encode_into(enc, pkt, item) + return + self._uper_count_enc(enc, count) + for item in value: + if self.holds_packets: + cast("ASN1_Packet", item).ASN1_root._uper_encode_into( + enc, item, + ) + else: + self.fld._uper_encode_into(enc, pkt, item) + def m2i(self, pkt, # type: ASN1_Packet s, # type: bytes ): # type: (...) -> Tuple[List[Any], bytes] + if pkt.ASN1_codec == ASN1_Codecs.OER: + s = self._apply_tagging_dec(s, pkt) + count, s = _oer().OER_unsigned_integer_dec(s) + lst = [] + for _ in range(count): + c, s = self._extract_packet(s, pkt) # type: ignore + if c: + lst.append(c) + return lst, s + if pkt.ASN1_codec == ASN1_Codecs.PER: + dec = _uper().UPER_Decoder(s) + if self.uper_extensible and dec.read_bit(): + count = dec.read_length_determinant() + else: + count = self._uper_count_dec(dec) + lst = [] + for _ in range(count): + c, _ = self._extract_packet_from_decoder(dec, pkt) + if c: + lst.append(c) + if _uper().UPER_has_unexpected_remainder(dec): + raise _uper().UPER_Decoding_Error("unexpected remainder", + remaining=dec.remaining()) + return lst, b"" 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) @@ -639,12 +934,27 @@ def build(self, pkt): s = cast(Union[List[_SEQ_T], bytes], val) elif val is None: s = b"" - elif self.holds_packets: - s = b"".join(bytes(i) for i in val) + if pkt.ASN1_codec == ASN1_Codecs.OER: + s = _oer().OER_unsigned_integer_enc(0) + elif pkt.ASN1_codec == ASN1_Codecs.PER: + enc = _uper().UPER_Encoder() + enc.append_length_determinant(0) + s = enc.as_bytes() else: - # BER: element fields may carry implicit/explicit tags; i2m - # matches m2i()/fld.m2i(). (Packet elements use bytes() above.) - s = b"".join(self.fld.i2m(pkt, i) for i in val) + if pkt.ASN1_codec == ASN1_Codecs.PER: + enc = _uper().UPER_Encoder() + self._uper_encode_into(enc, pkt, val) + s = enc.as_bytes() + elif self.holds_packets: + s = b"".join(bytes(i) for i in val) + if pkt.ASN1_codec == ASN1_Codecs.OER: + s = _oer().OER_unsigned_integer_enc(len(val)) + s + else: + # BER/OER: element fields may carry implicit/explicit tags; + # i2m matches m2i()/fld.m2i(). + s = b"".join(self.fld.i2m(pkt, i) for i in val) + if pkt.ASN1_codec == ASN1_Codecs.OER: + s = _oer().OER_unsigned_integer_enc(len(val)) + s return self.i2m(pkt, s) def i2repr(self, pkt, x): @@ -690,6 +1000,7 @@ class ASN1F_optional(ASN1F_element): """ ASN.1 field that is optional. """ + def __init__(self, field): # type: (ASN1F_field[Any, Any]) -> None field.flexible_tag = False @@ -715,6 +1026,10 @@ def dissect(self, pkt, s): self._field.set_val(pkt, None) return s + def dissect_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + return self._field.dissect_from_decoder(pkt, dec) + def build(self, pkt): # type: (ASN1_Packet) -> bytes if self._field.is_empty(pkt): @@ -729,12 +1044,57 @@ def i2repr(self, pkt, x): # type: (ASN1_Packet, Any) -> str return self._field.i2repr(pkt, x) + def set_val(self, pkt, val): + # type: (ASN1_Packet, Any) -> None + self._field.set_val(pkt, val) + + def set_absent(self, pkt): + # type: (ASN1_Packet) -> None + self.set_val(pkt, None) + + def is_empty(self, pkt): + # type: (ASN1_Packet) -> bool + # Delegate to the wrapped field (e.g. SEQUENCE checks children). + return self._field.is_empty(pkt) + + def _uper_encode_into(self, enc, pkt, value=None): + # type: (Any, ASN1_Packet, Optional[Any]) -> None + self._field._uper_encode_into(enc, pkt, value) + + +class ASN1F_DEFAULT(ASN1F_optional): + """ + ASN.1 field with a DEFAULT value (PER presence bit). + """ + + 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.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. This is different from ASN1F_NULL which has a network representation. """ + def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[None, bytes] return None, s @@ -761,6 +1121,7 @@ def __init__(self, name, default, *args, **kwargs): if "implicit_tag" in kwargs: err_msg = "ASN1F_CHOICE has been called with an implicit_tag" raise ASN1_Error(err_msg) + uper_extensible = kwargs.pop("uper_extensible", False) self.implicit_tag = None for kwarg in ["context", "explicit_tag"]: setattr(self, kwarg, kwargs.get(kwarg)) @@ -768,9 +1129,12 @@ def __init__(self, name, default, *args, **kwargs): name, None, context=self.context, explicit_tag=self.explicit_tag ) + self.uper_extensible = uper_extensible self.default = default self.current_choice = None 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,21 +1142,75 @@ 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(): - # ASN1F_CHOICE recursion - self.choices[k] = v + for k in root.choice_order: + self._register_choice(k, root.choices[k]) 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._register_choice(p.network_tag, p) 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") + self._tag_to_index = { + tag: idx for idx, tag in enumerate(self.choice_order) + } + + 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) + + def _dissect_choice_payload(self, pkt, choice, payload): + # type: (ASN1_Packet, _CHOICE_T, bytes) -> Tuple[ASN1_Object[Any], bytes] + if hasattr(choice, "ASN1_root"): + return self.extract_packet(choice, payload, _underlayer=pkt) # type: ignore + if isinstance(choice, type): + return choice(self.name, b"").m2i(pkt, payload) + return choice.m2i(pkt, payload) + + def _m2i_oer(self, pkt, s): + # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] + s = self._apply_tagging_dec(s, pkt) + tag, payload = _oer().OER_id_dec(s) + return self._m2i_tagged(pkt, tag, payload) + + def _m2i_per(self, pkt, s): + # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] + dec = _uper().UPER_Decoder(s) + val = self.m2i_from_decoder(pkt, dec) + if _uper().UPER_has_unexpected_remainder(dec): + raise _uper().UPER_Decoding_Error( + "unexpected remainder", + remaining=dec.remaining(), + ) + return val, b"" + + def _m2i_ber(self, pkt, s): + # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] + s = self._apply_tagging_dec(s, pkt) + tag, _ = BER_id_dec(s) + return self._m2i_tagged(pkt, tag, s) + + def _m2i_tagged(self, pkt, tag, payload): + # type: (ASN1_Packet, int, bytes) -> Tuple[ASN1_Object[Any], bytes] + if tag in self.choices: + choice = self.choices[tag] + elif self.flexible_tag: + choice = ASN1F_field + else: + raise ASN1_Error( + "ASN1F_CHOICE: unexpected field in '%s' " + "(tag %s not in possible tags %s)" % ( + self.name, tag, list(self.choices.keys()) + ) + ) + return self._dissect_choice_payload(pkt, choice, payload) def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] @@ -802,33 +1220,92 @@ def m2i(self, pkt, s): """ if len(s) == 0: raise ASN1_Error("ASN1F_CHOICE: got empty string") - s = self._apply_tagging_dec(s, pkt) - tag, _ = BER_id_dec(s) - if tag in self.choices: - choice = self.choices[tag] - else: - if self.flexible_tag: - choice = ASN1F_field - else: - raise ASN1_Error( - "ASN1F_CHOICE: unexpected field in '%s' " - "(tag %s not in possible tags %s)" % ( - self.name, tag, list(self.choices.keys()) - ) + if pkt.ASN1_codec == ASN1_Codecs.OER: + return self._m2i_oer(pkt, s) + if pkt.ASN1_codec == ASN1_Codecs.PER: + return self._m2i_per(pkt, s) + return self._m2i_ber(pkt, s) + + def _choice_tag_for(self, x): + # type: (Any) -> Optional[int] + index = self._choice_index_for(x) + return None if index is None else self.choice_order[index] + + def _choice_index_for(self, x): + # type: (Any) -> Optional[int] + for index, choice in enumerate(self.choice_list): + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + if isinstance(x, choice): + return index + elif hasattr(choice, "ASN1_tag"): + if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + return index + return None + + def _choice_for_index(self, index): + # type: (int) -> _CHOICE_T + return self.choice_list[index] + + def m2i_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> ASN1_Object[Any] + if self.uper_extensible: + if dec.read_bit(): + raise _uper().UPER_Decoding_Error( + "ASN1F_CHOICE: extension additions are not supported" ) + if len(self.choice_order) > 1: + index, _ = _uper().UPER_choice_index_dec(b"", len(self.choice_order), dec=dec) + else: + index = 0 + if index >= len(self.choice_order): + raise ASN1_Error( + "ASN1F_CHOICE: unexpected index %s in '%s'" % + (index, self.name) + ) + choice = self._choice_for_index(index) + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + pkt_cls = cast("Type[ASN1_Packet]", choice) + p = pkt_cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return cast(ASN1_Object[Any], p) + if isinstance(choice, type): + return cast( + ASN1_Object[Any], + choice(self.name, b"").m2i_from_decoder(pkt, dec), + ) + return cast(ASN1_Object[Any], choice.m2i_from_decoder(pkt, dec)) + + def _uper_encode_into(self, enc, pkt, value=None): + # type: (Any, ASN1_Packet, Any) -> None + if value is None: + value = getattr(pkt, self.name) + index = self._choice_index_for(value) + if index is None: + raise ASN1_Error( + "ASN1F_CHOICE: cannot encode unknown alternative in '%s'" % + self.name + ) + if self.uper_extensible: + enc.append_bit(0) + if len(self.choice_order) > 1: + _uper().UPER_choice_index_enc(index, len(self.choice_order), enc=enc) + choice = self._choice_for_index(index) if hasattr(choice, "ASN1_root"): - # we don't want to import ASN1_Packet in this module... - return self.extract_packet(choice, s, _underlayer=pkt) # type: ignore + cast("ASN1_Packet", value).ASN1_root._uper_encode_into(enc, value) elif isinstance(choice, type): - return choice(self.name, b"").m2i(pkt, s) + choice(self.name, b"")._uper_encode_into(enc, pkt, value) else: - # XXX check properly if this is an ASN1F_PACKET - return choice.m2i(pkt, s) + choice._uper_encode_into(enc, pkt, value) def i2m(self, pkt, x): # type: (ASN1_Packet, Any) -> bytes if x is None: s = b"" + elif pkt.ASN1_codec == ASN1_Codecs.PER: + enc = _uper().UPER_Encoder() + self._uper_encode_into(enc, pkt, x) + s = enc.as_bytes() else: # Use the packet codec for ASN1_Object values; bytes(x) would # follow conf.ASN1_default_codec instead. @@ -836,7 +1313,11 @@ def i2m(self, pkt, x): s = x.enc(pkt.ASN1_codec) else: s = bytes(x) - if hash(type(x)) in self.pktchoices: + if pkt.ASN1_codec == ASN1_Codecs.OER: + alt_tag = self._choice_tag_for(x) + if alt_tag is not None: + s = _oer().OER_tag_enc(alt_tag & 0x3f, alt_tag & 0xc0) + s + elif hash(type(x)) in self.pktchoices: imp, exp = self.pktchoices[hash(type(x))] s = self._tagging_enc( pkt, s, @@ -886,18 +1367,39 @@ 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_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> Optional[ASN1_Packet] + cls = self._resolve_cls(pkt) + p = cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p + + def _uper_encode_into(self, enc, pkt, value=None): + # type: (Any, ASN1_Packet, Any) -> None + if value is None: + value = getattr(pkt, self.name) + if value is None: + return + if isinstance(value, ASN1_Object): + value = value.val + cast("ASN1_Packet", value).ASN1_root._uper_encode_into(enc, value) + 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) s = self._apply_tagging_dec( s, pkt, - hidden_tag=cls.ASN1_root.ASN1_tag, # noqa: E501 + hidden_tag=cls.ASN1_root.ASN1_tag, _fname=self.name, ) if not s: @@ -911,6 +1413,10 @@ def i2m(self, # type: (...) -> bytes if x is None: s = b"" + elif pkt.ASN1_codec == ASN1_Codecs.PER: + enc = _uper().UPER_Encoder() + self._uper_encode_into(enc, pkt, x) + s = enc.as_bytes() elif isinstance(x, bytes): s = x elif isinstance(x, ASN1_Object): @@ -979,10 +1485,7 @@ def m2i(self, pkt, s): # type: ignore else: return None, bit_string.val_readable if len(s) > 0: - raise BER_Decoding_Error( - "unexpected remainder in %s" % pkt.name, - remaining=s, - ) + raise BER_Decoding_Error("unexpected remainder", remaining=s) return p, remain def i2m(self, pkt, x): # type: ignore @@ -1003,6 +1506,8 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[Any] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] ): # type: (...) -> None self.mapping = mapping @@ -1011,7 +1516,9 @@ def __init__(self, default_readable=False, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + uper_min=uper_min, + uper_max=uper_max, ) def any2i(self, pkt, x): diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py new file mode 100644 index 00000000000..ab68b2b0e83 --- /dev/null +++ b/scapy/contrib/oer.py @@ -0,0 +1,825 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Octet Encoding Rules (OER) for ASN.1 + +Basic-OER as specified in ITU-T X.696 | ISO/IEC 8825-7. +""" + +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 ( + ASN1Tag, + ASN1_BADTAG, + ASN1_BadTag_Decoding_Error, + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) + +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 + + +class OER_BadTag_Decoding_Error(OER_Decoding_Error, + ASN1_BadTag_Decoding_Error): + pass + + +# 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_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 + if len(s) <= tmp_len: + raise OER_Decoding_Error( + "OER_len_dec: Got %i bytes while expecting %i" % + (len(s) - 1, tmp_len), + remaining=s + ) + 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 + if i < 0: + number_of_bits = i.bit_length() + number_of_bytes = (number_of_bits + 7) // 8 + value = (1 << (8 * number_of_bytes)) + i + if (value & (1 << (8 * number_of_bytes - 1))) == 0: + value |= (0xff << (8 * number_of_bytes)) + number_of_bytes += 1 + elif i > 0: + number_of_bits = i.bit_length() + number_of_bytes = (number_of_bits + 7) // 8 + if number_of_bits == (8 * number_of_bytes): + number_of_bytes += 1 + value = i + else: + number_of_bytes = 1 + value = 0 + 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) + if len(s) < number_of_bytes: + raise OER_Decoding_Error( + "OER_signed_integer_dec: Got %i bytes while expecting %i" % + (len(s), number_of_bytes), + 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 + 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) + if len(s) < number_of_bytes: + raise OER_Decoding_Error( + "OER_unsigned_integer_dec: Got %i bytes while expecting %i" % + (len(s), number_of_bytes), + remaining=s + ) + value = int.from_bytes(s[:number_of_bytes], "big") + return value, s[number_of_bytes:] + + +def OER_fixed_integer_enc(i, length, signed=True): + # type: (int, int, bool) -> bytes + fmt = {1: ">b", 2: ">h", 4: ">i", 8: ">q"} if signed else { + 1: ">B", 2: ">H", 4: ">I", 8: ">Q" + } + try: + return struct.pack(fmt[length], i) + except KeyError: + raise OER_Encoding_Error( + "OER_fixed_integer_enc: invalid length %i" % length + ) + + +def OER_fixed_integer_dec(s, length, signed=True): + # type: (bytes, int, bool) -> Tuple[int, bytes] + if len(s) < length: + raise OER_Decoding_Error( + "OER_fixed_integer_dec: Got %i bytes while expecting %i" % + (len(s), length), + remaining=s + ) + fmt = {1: ">b", 2: ">h", 4: ">i", 8: ">q"} if signed else { + 1: ">B", 2: ">H", 4: ">I", 8: ">Q" + } + 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 + if len(s) < length + 1: + raise OER_Decoding_Error( + "OER_enumerated_dec: Got %i bytes while expecting %i" % + (len(s) - 1, length), + remaining=s + ) + value = int.from_bytes(s[1:length + 1], "big", signed=True) + return value, s[length + 1:] + + +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_id_dec(s): + # type: (bytes) -> Tuple[int, bytes] + tag_class, tag_number, remainder = OER_tag_dec(s) + return tag_class | tag_number, remainder + + +def OER_tagging_dec(s, # type: bytes + hidden_tag=None, # type: Optional[int | ASN1Tag] + implicit_tag=None, # type: Optional[int] + explicit_tag=None, # type: Optional[int] + safe=False, # type: Optional[bool] + _fname="", # type: str + ): + # type: (...) -> Tuple[Optional[int], bytes] + # OER does not use implicit tagging. Explicit tags are encoded as choice + # alternatives (tag + value). + real_tag = None + if explicit_tag is not None and len(s) > 0: + err_msg = ( + "OER_tagging_dec: observed tag 0x%.02x does not " + "match expected tag 0x%.02x (%s)" + ) + tag_class, tag_number, remainder = OER_tag_dec(s) + observed = tag_class | tag_number + if observed != explicit_tag: + if not safe: + raise OER_Decoding_Error( + err_msg % (observed, explicit_tag, _fname), + remaining=s) + real_tag = observed + s = remainder + return real_tag, s + + +def OER_tagging_enc(s, implicit_tag=None, explicit_tag=None): + # type: (bytes, Optional[int], Optional[int]) -> bytes + if explicit_tag is not None: + return OER_tag_enc(explicit_tag & 0x3f, explicit_tag & 0xc0) + s + return s + + +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 check_type(cls, s): + # type: (bytes) -> bytes + cls.check_string(s) + return s + + @classmethod + def check_type_get_len(cls, s): + # type: (bytes) -> Tuple[int, bytes] + cls.check_string(s) + return len(s), s + + @classmethod + def check_type_check_len(cls, s): + # type: (bytes) -> Tuple[int, bytes, bytes] + cls.check_string(s) + return len(s), s, 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[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 + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + 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_BadTag_Decoding_Error as e: + o, remain = OERcodec_Object.dec( + e.remaining, context, safe, size_len, oer_unsigned + ) + return ASN1_BADTAG(o), remain + 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 + ): + # 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]) -> 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 !") + + +ASN1_Codecs.OER.register_stem(OERcodec_Object) +ASN1_Codecs.OER.register_tagging(OER_tagging_enc, OER_tagging_dec) + + +########################## +# OERcodec objects # +########################## + +class OERcodec_INTEGER(OERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.INTEGER + + @classmethod + def enc(cls, i, size_len=0, **_kwargs): + # type: (int, Optional[int]) -> bytes + if size_len in (1, 2, 4, 8): + if i >= 0: + if size_len == 1 and 0 <= i <= 255: + return OER_fixed_integer_enc(i, 1, signed=False) + if size_len == 2 and 0 <= i <= 65535: + return OER_fixed_integer_enc(i, 2, signed=False) + if size_len == 4 and 0 <= i <= 4294967295: + return OER_fixed_integer_enc(i, 4, signed=False) + if size_len == 8 and 0 <= i <= 18446744073709551615: + return OER_fixed_integer_enc(i, 8, signed=False) + return OER_fixed_integer_enc(i, size_len, signed=True) + 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, size_len=0, **_kwargs): + # type: (int, Optional[int]) -> 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:] + + +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] + length, s = OER_len_dec(s) + if length == 0: + return cls.tag.asn1_object(""), s + if len(s) < length: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % (cls.__name__, len(s), length), + remaining=s + ) + 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 = "".join(binrepr(orb(x)).zfill(8) for x in 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]) -> bytes + s = bytes_encode(_s) + if len(s) % 8 == 0: + unused_bits = 0 + else: + unused_bits = 8 - len(s) % 8 + s += b"0" * unused_bits + data = b"".join(chb(int(b"".join(chb(y) for y in x), 2)) + for x in zip(*[iter(s)] * 8)) + body = chb(unused_bits) + data + 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]) -> bytes + s = bytes_encode(_s) + if size_len and size_len == len(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 and size_len not in (1, 2, 4, 8): + if len(s) < size_len: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % + (cls.__name__, len(s), size_len), + remaining=s + ) + return cls.tag.asn1_object(s[:size_len]), s[size_len:] + length, s = OER_len_dec(s) + if len(s) < length: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % (cls.__name__, len(s), length), + remaining=s + ) + 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, size_len=0, **_kwargs): + # type: (Any, Optional[int]) -> 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, size_len=0, **_kwargs): + # type: (AnyStr, Optional[int]) -> 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) + if len(s) < length: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % (cls.__name__, len(s), length), + remaining=s + ) + 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, size_len=0, **_kwargs): + # type: (int, Optional[int]) -> 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, size_len=0, **_kwargs): + # type: (Union[bytes, List[OERcodec_Object[Any]]], Optional[int]) -> 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]) -> 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 diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py new file mode 100644 index 00000000000..1af22cd4e7a --- /dev/null +++ b/scapy/contrib/uper.py @@ -0,0 +1,1369 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +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. Not supported yet: +explicit/implicit tagging, SET, extension markers, +``ASN1F_CHOICE``/``ASN1F_SEQUENCE_OF`` with nested ``ASN1_Packet`` +alternatives, REAL, and PER-visible character string permuted alphabets. +""" + +import binascii + +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_BADTAG, + ASN1_BadTag_Decoding_Error, + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) + +from typing import ( + Any, + AnyStr, + 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 + + +class UPER_BadTag_Decoding_Error(UPER_Decoding_Error, + ASN1_BadTag_Decoding_Error): + pass + + +def UPER_bits_for_range(size): + # type: (int) -> int + if size <= 0: + return 0 + return size.bit_length() + + +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 number_of_bytes(self): + # type: () -> int + return (self.chunks_number_of_bits + self.number_of_bits + 7) // 8 + + def align_always(self): + # type: () -> None + width = 8 * self.number_of_bytes() + width -= self.chunks_number_of_bits + width -= self.number_of_bits + if width: + self.number_of_bits += width + self.value <<= width + + 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) -> int + if length < 128: + encoded = bytes([length]) + elif length < 16384: + encoded = bytes([(0x80 | (length >> 8)), (length & 0xff)]) + elif length < 32768: + encoded = b"\xc1" + length = 16384 + elif length < 49152: + encoded = b"\xc2" + length = 32768 + elif length < 65536: + encoded = b"\xc3" + length = 49152 + else: + encoded = b"\xc4" + length = 65536 + self.append_bytes(encoded) + return length + + def append_unconstrained_whole_number(self, value): + # type: (int) -> None + number_of_bits = 0 if value == 0 else value.bit_length() + if value < 0: + number_of_bytes = (number_of_bits + 7) // 8 + enc = (1 << (8 * number_of_bytes)) + value + if enc & (1 << (8 * number_of_bytes - 1)) == 0: + enc |= (0xff << (8 * number_of_bytes)) + number_of_bytes += 1 + elif value > 0: + number_of_bytes = (number_of_bits + 7) // 8 + if number_of_bits == 8 * number_of_bytes: + number_of_bytes += 1 + enc = value + else: + number_of_bytes = 1 + enc = 0 + self.append_length_determinant(number_of_bytes) + self.append_non_negative_binary_integer(enc, 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 + if number_of_bits == 0: + return b"" + number_of_alignment_bits = (8 - (number_of_bits % 8)) % 8 + value <<= number_of_alignment_bits + number_of_bits += number_of_alignment_bits + value |= (0x80 << number_of_bits) + hexval = hex(value)[4:].rstrip("L") + if len(hexval) % 2: + hexval = "0" + hexval + return binascii.unhexlify(hexval) + + +def _uper_significant_bit_count(data): + # type: (bytes) -> int + if not data: + return 0 + total = 8 * len(data) + bits = int.from_bytes(data, "big") + end = total + while end > 0 and ((bits >> (total - end)) & 1) == 0: + end -= 1 + trimmed = total - end + if trimmed > 0 and trimmed <= 8: + return end + return total + + +def _uper_per_bits_to_bytes(bit_value, number_of_bits): + # type: (int, int) -> bytes + if number_of_bits == 0: + return b"" + bitstr = format(bit_value, "0%db" % number_of_bits) + value = "10000000" + bitstr + number_of_alignment_bits = (8 - (number_of_bits % 8)) + if number_of_alignment_bits != 8: + value += "0" * number_of_alignment_bits + hexval = hex(int(value, 2))[4:].rstrip("L") + if len(hexval) % 2: + hexval = "0" + hexval + return binascii.unhexlify(hexval) + + +def UPER_append_encoded(enc, data): + # type: (UPER_Encoder, bytes) -> None + if not data: + return + nbits = _uper_significant_bit_count(data) + if nbits == 0: + return + total = 8 * len(data) + bits = int.from_bytes(data, "big") + shift = total - nbits + value = (bits >> shift) & ((1 << nbits) - 1) + enc.append_non_negative_binary_integer(value, nbits) + + +def UPER_join_encodings(*parts): + # type: (*bytes) -> bytes + enc = UPER_Encoder() + for part in parts: + UPER_append_encoded(enc, part) + return enc.as_bytes() + + +def UPER_optional_presence_enc(bits, enc=None): + # type: (List[int], Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + for bit in bits: + enc.append_bit(bit) + return enc.as_bytes() if standalone else b"" + + +def UPER_count_enc(count, enc=None): + # type: (int, Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + enc.append_length_determinant(count) + return enc.as_bytes() if standalone else b"" + + +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 + + +def UPER_count_dec(s, dec=None): + # type: (bytes, Optional[UPER_Decoder]) -> Tuple[int, bytes] + standalone = dec is None + if dec is None: + dec = UPER_Decoder(s) + count = dec.read_length_determinant() + if standalone: + return count, dec.remaining() + return count, b"" + + +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_per_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_per_bits_to_bytes(value, self.number_of_bits) + + 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 align_always(self): + # type: () -> None + consumed = self.total_number_of_bits - self.number_of_bits + width = (8 - (consumed % 8)) % 8 + if width: + if width > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + self.number_of_bits -= width + + def read_length_determinant(self): + # type: () -> int + value = self.read_non_negative_binary_integer(8) + if (value & 0x80) == 0x00: + return value + if (value & 0xc0) == 0x80: + return ((value & 0x7f) << 8) | self.read_non_negative_binary_integer(8) + mapping = {0xc1: 16384, 0xc2: 32768, 0xc3: 49152, 0xc4: 65536} + if value in mapping: + return mapping[value] + raise UPER_Decoding_Error( + "UPER_Decoder: bad length determinant 0x%02x" % value + ) + + def read_unconstrained_whole_number(self): + # type: () -> int + number_of_bytes = self.read_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 consume_input(self): + # type: () -> None + self.number_of_bits = 0 + + +def UPER_constrained_int_enc(value, minimum, maximum, enc=None): + # type: (int, int, int, Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + size = maximum - minimum + enc.append_non_negative_binary_integer( + value - minimum, UPER_bits_for_range(size) + ) + return enc.as_bytes() if standalone else b"" + + +def UPER_constrained_int_dec(s, minimum, maximum): + # type: (bytes, int, int) -> Tuple[int, bytes] + dec = UPER_Decoder(s) + size = maximum - minimum + value = dec.read_non_negative_binary_integer(UPER_bits_for_range(size)) + dec.consume_input() + return value + minimum, b"" + + +def UPER_constrained_int_dec_from_decoder(dec, minimum, maximum): + # type: (UPER_Decoder, int, int) -> int + size = maximum - minimum + value = dec.read_non_negative_binary_integer(UPER_bits_for_range(size)) + return value + minimum + + +def UPER_unconstrained_int_enc(value, enc=None): + # type: (int, Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + enc.append_unconstrained_whole_number(value) + return enc.as_bytes() if standalone else b"" + + +def UPER_unconstrained_int_dec(s): + # type: (bytes) -> Tuple[int, bytes] + dec = UPER_Decoder(s) + value = dec.read_unconstrained_whole_number() + remain = dec.remaining() + return value, remain + + +def UPER_boolean_enc(value, enc=None): + # type: (int, Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + enc.append_bit(1 if value else 0) + return enc.as_bytes() if standalone else b"" + + +def UPER_boolean_dec(s): + # type: (bytes) -> Tuple[int, bytes] + dec = UPER_Decoder(s) + value = dec.read_bit() + dec.consume_input() + return value, b"" + + +def UPER_octet_string_enc(data, minimum=None, maximum=None, enc=None): + # type: (bytes, Optional[int], Optional[int], Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + if minimum is not None and maximum is not None and minimum == maximum: + enc.append_bytes(data) + elif minimum is not None and maximum is not None: + enc.append_non_negative_binary_integer( + len(data) - minimum, + UPER_bits_for_range(maximum - minimum), + ) + enc.append_bytes(data) + else: + enc.append_length_determinant(len(data)) + enc.append_bytes(data) + return enc.as_bytes() if standalone else b"" + + +def UPER_octet_string_dec(s, minimum=None, maximum=None, dec=None): + # type: (bytes, Optional[int], Optional[int], Optional[UPER_Decoder]) -> Tuple[bytes, bytes] # noqa: E501 + standalone = dec is None + if dec is None: + dec = UPER_Decoder(s) + if minimum is not None and maximum is not None and minimum == maximum: + length = minimum + elif minimum is not None and maximum is not None: + length = minimum + dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + else: + length = dec.read_length_determinant() + data = dec.read_bytes(length) + if standalone: + return data, dec.remaining() + return data, b"" + + +def UPER_choice_index_enc(index, number_of_choices, enc=None): + # type: (int, int, Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + enc.append_non_negative_binary_integer( + index, UPER_bits_for_range(number_of_choices - 1) + ) + return enc.as_bytes() if standalone else b"" + + +def UPER_choice_index_dec(s, number_of_choices, dec=None): + # type: (bytes, int, Optional[UPER_Decoder]) -> Tuple[int, bytes] + standalone = dec is None + if dec is None: + dec = UPER_Decoder(s) + index = dec.read_non_negative_binary_integer( + UPER_bits_for_range(number_of_choices - 1) + ) + if standalone: + return index, dec.remaining() + return index, b"" + + +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) + + @classmethod + def check_string(cls, s): + # type: (bytes) -> None + if not s and cls.tag != ASN1_Class_UNIVERSAL.NULL: + raise UPER_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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_enum_values=None, # type: Optional[List[int]] + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + raise UPER_Decoding_Error( + "%s: Cannot decode unknown UPER 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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_enum_values=None, # type: Optional[List[int]] + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + dec_kwargs = {} # type: Dict[str, Any] + if uper_enum_values is not None: + dec_kwargs["uper_enum_values"] = uper_enum_values + if not safe: + return cls.do_dec( + s, context, safe, size_len, uper_min, uper_max, oer_unsigned, + **dec_kwargs + ) + try: + return cls.do_dec( + s, context, safe, size_len, uper_min, uper_max, oer_unsigned, + **dec_kwargs + ) + except UPER_BadTag_Decoding_Error as e: + o, remain = UPERcodec_Object.dec( + e.remaining, context, safe, size_len, uper_min, uper_max, + oer_unsigned, uper_enum_values=uper_enum_values, + ) + return ASN1_BADTAG(o), remain + except UPER_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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_enum_values=None, # type: Optional[List[int]] + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + return cls.dec( + s, context, safe=True, + size_len=size_len, uper_min=uper_min, uper_max=uper_max, + oer_unsigned=oer_unsigned, uper_enum_values=uper_enum_values, + ) + + @classmethod + def enc(cls, s, size_len=0, uper_min=None, uper_max=None, **_kwargs): + # type: (_K, Optional[int], Optional[int], Optional[int]) -> bytes + if isinstance(s, (str, bytes)): + return UPERcodec_STRING.enc(s, size_len=size_len, + uper_min=uper_min, uper_max=uper_max) + else: + try: + return UPERcodec_INTEGER.enc( + int(s), + size_len=size_len, + uper_min=uper_min, + uper_max=uper_max, + ) + except Exception: + raise UPER_Encoding_Error( + "Cannot encode value %r for %s" % (s, cls.__name__), + encoded=s + ) + + +def _uper_enc_via_encode_into(cls, *args, **kwargs): + # type: (Type[UPERcodec_Object[Any]], *Any, **Any) -> bytes + enc = UPER_Encoder() + cls.encode_into(enc, *args, **kwargs) + return enc.as_bytes() + + + +def UPER_tagging_enc(s, **kwargs): + # type: (bytes, **Any) -> bytes + # UPER has no BER-style TLV tagging. + return s + + +def UPER_tagging_dec(s, **kwargs): + # type: (bytes, **Any) -> Tuple[Optional[int], bytes] + return None, s + + +ASN1_Codecs.PER.register_stem(UPERcodec_Object) +ASN1_Codecs.PER.register_tagging(UPER_tagging_enc, UPER_tagging_dec) + + +######################### +# 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 + ): + # 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) + UPER_unconstrained_int_enc(i, enc=enc) + return + if minimum is not None and maximum is not None: + UPER_constrained_int_enc(i, minimum, maximum, enc=enc) + else: + UPER_unconstrained_int_enc(i, enc=enc) + + @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 + ): + # 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_from_decoder(dec, minimum, maximum) + else: + value = dec.read_unconstrained_whole_number() + return cls.asn1_object(value) + + @classmethod + def enc(cls, i, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): + # type: (int, Optional[int], Optional[int], Optional[int], bool) -> bytes + return _uper_enc_via_encode_into( + cls, i, size_len, uper_min, uper_max, oer_unsigned, + ) + + @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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + minimum, maximum = _uper_int_range(size_len, uper_min, uper_max, oer_unsigned) + if minimum is not None and maximum is not None: + x, t = UPER_constrained_int_dec(s, minimum, maximum) + else: + x, t = UPER_unconstrained_int_dec(s) + return cls.asn1_object(x), t + + +class UPERcodec_BOOLEAN(UPERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + @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 + ): + # type: (...) -> None + UPER_boolean_enc(i, enc=enc) + + @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 + ): + # type: (...) -> ASN1_Object[int] + return cls.asn1_object(dec.read_bit()) + + @classmethod + def enc(cls, i, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): + # type: (int, Optional[int], Optional[int], Optional[int], bool) -> bytes + return _uper_enc_via_encode_into( + cls, i, size_len, uper_min, uper_max, oer_unsigned, + ) + + @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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + x, t = UPER_boolean_dec(s) + return cls.asn1_object(x), t + + +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) + + +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] + oer_unsigned=False, # type: bool + ): + # type: (...) -> None + s, nbits = _uper_bit_string_parts(_s) + minimum = uper_min + maximum = uper_max + if size_len: + minimum = maximum = size_len + if minimum is not None and maximum is not None and minimum == maximum: + if nbits >= minimum: + value = int.from_bytes(s, "big") >> (8 * len(s) - minimum) + elif isinstance(_s, str) and _s and all(c in "01" for c in _s): + value = int(_s, 2) + elif nbits > 0: + value = int.from_bytes(s, "big") >> max(0, 8 * len(s) - nbits) + else: + value = 0 + enc.append_non_negative_binary_integer(value, minimum) + elif minimum is not None and maximum is not None: + enc.append_non_negative_binary_integer( + nbits - minimum, UPER_bits_for_range(maximum - minimum) + ) + enc.append_bits(s, nbits) + else: + enc.append_length_determinant((nbits + 7) // 8) + enc.append_bytes(s) + + @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 + ): + # type: (...) -> ASN1_Object[str] + minimum = uper_min + maximum = uper_max + if size_len: + minimum = maximum = size_len + if minimum is not None and maximum is not None and minimum == maximum: + nbits = minimum + elif minimum is not None and maximum is not None: + nbits = minimum + dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + else: + nbytes = dec.read_length_determinant() + raw = dec.read_bytes(nbytes) + nbits = 8 * nbytes + return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) + raw = dec.read_bits(nbits) + return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) + + @classmethod + def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): + # type: (Any, Optional[int], Optional[int], Optional[int], bool) -> bytes + return _uper_enc_via_encode_into( + cls, _s, size_len, uper_min, uper_max, oer_unsigned, + ) + + @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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[str], bytes] + dec = UPER_Decoder(s) + minimum = uper_min + maximum = uper_max + if minimum is not None and maximum is not None and minimum == maximum: + nbits = minimum + elif minimum is not None and maximum is not None: + nbits = minimum + dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + else: + nbytes = dec.read_length_determinant() + raw = dec.read_bytes(nbytes) + nbits = 8 * nbytes + return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)), dec.remaining() + raw = dec.read_bits(nbits) + return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)), dec.remaining() + + +def _uper_octet_string_bounds(size_len, uper_min, uper_max): + # type: (Optional[int], Optional[int], Optional[int]) -> Tuple[Optional[int], Optional[int]] # noqa: E501 + if size_len: + return size_len, size_len + return uper_min, uper_max + + +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] + oer_unsigned=False, # type: bool + ): + # type: (...) -> None + s = bytes_encode(_s) + minimum, maximum = _uper_octet_string_bounds( + size_len, uper_min, uper_max, + ) + UPER_octet_string_enc(s, minimum, maximum, enc=enc) + + @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 + ): + # type: (...) -> ASN1_Object[Any] + minimum, maximum = _uper_octet_string_bounds( + size_len, uper_min, uper_max, + ) + raw, _ = UPER_octet_string_dec(b"", minimum, maximum, dec=dec) + return cls.asn1_object(raw) + + @classmethod + def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): + # type: (Union[str, bytes], Optional[int], Optional[int], Optional[int], bool) -> bytes # noqa: E501 + return _uper_enc_via_encode_into( + cls, _s, size_len, uper_min, uper_max, oer_unsigned, + ) + + @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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + minimum, maximum = _uper_octet_string_bounds( + size_len, uper_min, uper_max, + ) + raw, remain = UPER_octet_string_dec(s, minimum, maximum) + return cls.asn1_object(raw), remain + + +class UPERcodec_NULL(UPERcodec_Object[None]): + tag = ASN1_Class_UNIVERSAL.NULL + + @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] + oer_unsigned=False, # type: bool + ): + # type: (...) -> None + return + + @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 + ): + # type: (...) -> ASN1_Object[None] + return cls.asn1_object(None) + + @classmethod + def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): + # type: (Any, Optional[int], Optional[int], Optional[int], bool) -> 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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[None], bytes] + return cls.asn1_object(None), s + + +class UPERcodec_OID(UPERcodec_Object[bytes]): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def enc(cls, _oid, size_len=0, uper_min=None, uper_max=None, **_kwargs): + # type: (AnyStr, Optional[int], Optional[int], Optional[int]) -> bytes + 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 = UPER_Encoder() + enc.append_length_determinant(len(body)) + enc.append_bytes(body) + return enc.as_bytes() + + @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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[bytes], bytes] + dec = UPER_Decoder(s) + length = dec.read_length_determinant() + content = dec.read_bytes(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)), + dec.remaining(), + ) + + +def UPER_enumerated_enc(value, + enum_values, # type: List[int] + enc=None # type: Optional[UPER_Encoder] + ): + # type: (int, List[int], Optional[UPER_Encoder]) -> bytes + standalone = enc is None + if enc is None: + enc = UPER_Encoder() + 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(index, len(enum_values), enc=enc) + return enc.as_bytes() if standalone else b"" + + +def UPER_enumerated_dec(s, + enum_values, # type: List[int] + dec=None # type: Optional[UPER_Decoder] + ): + # type: (bytes, List[int], Optional[UPER_Decoder]) -> Tuple[int, bytes] + standalone = dec is None + if dec is None: + dec = UPER_Decoder(s) + if not enum_values: + raise UPER_Decoding_Error("UPER_enumerated_dec: empty enumeration") + index, _ = UPER_choice_index_dec(b"", len(enum_values), dec=dec) + if index >= len(enum_values): + raise UPER_Decoding_Error( + "UPER_enumerated_dec: index %i out of range" % index + ) + if standalone: + dec.consume_input() + return enum_values[index], b"" + return enum_values[index], b"" + + +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] + oer_unsigned=False, # type: bool + uper_enum_values=None, # type: Optional[List[int]] + ): + # type: (...) -> None + if uper_enum_values is not None: + UPER_enumerated_enc(i, uper_enum_values, enc=enc) + return + minimum = uper_min if uper_min is not None else 0 + maximum = uper_max if uper_max is not None else size_len + if maximum is None: + maximum = max(i, 0) + UPER_constrained_int_enc(i, minimum, maximum, enc=enc) + + @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_enum_values=None, # type: Optional[List[int]] + ): + # type: (...) -> ASN1_Object[int] + if uper_enum_values is not None: + value, _ = UPER_enumerated_dec(b"", uper_enum_values, dec=dec) + return cls.asn1_object(value) + minimum = uper_min if uper_min is not None else 0 + maximum = uper_max if uper_max is not None else size_len + if maximum is None: + raise UPER_Decoding_Error("UPERcodec_ENUMERATED: missing range") + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + minimum + return cls.asn1_object(value) + + @classmethod + def enc(cls, + i, + size_len=0, + uper_min=None, + uper_max=None, + oer_unsigned=False, + uper_enum_values=None, + **_kwargs + ): + # type: (int, Optional[int], Optional[int], Optional[int], bool, Optional[List[int]], **Any) -> bytes # noqa: E501 + return _uper_enc_via_encode_into( + cls, i, size_len, uper_min, uper_max, oer_unsigned, + uper_enum_values=uper_enum_values, + ) + + @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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_enum_values=None, # type: Optional[List[int]] + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + if uper_enum_values is not None: + x, t = UPER_enumerated_dec(s, uper_enum_values) + return cls.asn1_object(x), t + minimum = uper_min if uper_min is not None else 0 + maximum = uper_max if uper_max is not None else size_len + if maximum is None: + raise UPER_Decoding_Error("UPERcodec_ENUMERATED: missing range") + x, t = UPER_constrained_int_dec(s, minimum, maximum) + return cls.asn1_object(x), t + + +class UPERcodec_SEQUENCE(UPERcodec_Object[Union[bytes, List[Any]]]): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + _ll, # type: Union[bytes, List[UPERcodec_Object[Any]]] + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> None + if isinstance(_ll, bytes): + UPER_append_encoded(enc, _ll) + + @classmethod + def enc(cls, _ll, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): + # type: (Union[bytes, List[UPERcodec_Object[Any]]], Optional[int], Optional[int], Optional[int], bool) -> bytes # noqa: E501 + if isinstance(_ll, bytes): + return _ll + raise UPER_Encoding_Error( + "UPERcodec_SEQUENCE: schema-defined field order required" + ) + + @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] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[Union[bytes, List[Any]]], bytes] + raise UPER_Decoding_Error( + "UPERcodec_SEQUENCE: decoding requires schema-defined field order", + remaining=s + ) + + +class UPERcodec_SET(UPERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class UPERcodec_IPADDRESS(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def enc(cls, ipaddr_ascii, size_len=0, uper_min=None, uper_max=None, **_kwargs): + # type: (str, Optional[int], Optional[int], Optional[int]) -> bytes + try: + s = inet_aton(ipaddr_ascii) + except Exception: + raise UPER_Encoding_Error("IPv4 address could not be encoded") + return UPER_octet_string_enc(s, 4, 4) + + @classmethod + def do_dec(cls, s, context=None, safe=False, + size_len=0, uper_min=None, uper_max=None, + oer_unsigned=False): + # type: (bytes, Optional[Any], bool, Optional[int], Optional[int], Optional[int], bool) -> Tuple[ASN1_Object[str], bytes] # noqa: E501 + raw, remain = UPER_octet_string_dec(s, 4, 4) + try: + ipaddr_ascii = inet_ntoa(raw) + except Exception: + raise UPER_Decoding_Error( + "IP address could not be decoded", + remaining=s, + ) + return cls.asn1_object(ipaddr_ascii), remain + + +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 diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index 9fa0bad0f44..c83f8388d8b 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -101,3 +101,358 @@ 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 OER/UPER contrib load += import contrib codecs +import scapy.contrib.oer +import scapy.contrib.uper +from scapy.contrib.oer import * +from scapy.contrib.uper import * + ++ 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) == 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 interoperability (reference vectors) += primitive encode interop +__import__('test.scapy.layers.oer_iop', fromlist=['check_primitive_interop']).check_primitive_interop() += scapy encode reference decode +__import__('test.scapy.layers.oer_iop', fromlist=['check_scapy_encode_reference_decode']).check_scapy_encode_reference_decode() + ++ ASN.1 OER review fixes += OER fixed integer decode roundtrip +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(128, size_len=1), 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 explicit null tagging +OER_tagging_enc(OERcodec_NULL.enc(None), explicit_tag=0x81) == b"\x81" += OER choice id decode +tag, r = OER_id_dec(b"\x81\x01") +tag == 0x81 and r == b"\x01" + ++ ASN.1 OER fuzzing += OER fuzz encode +__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_encode']).check_oer_fuzz_encode() += OER fuzz encode roundtrip +__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_roundtrip']).check_oer_fuzz_roundtrip() += OER fuzz codec decode +__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_codec_decode']).check_oer_fuzz_codec_decode() += OER fuzz packet decode +__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_packet_decode']).check_oer_fuzz_packet_decode() + ++ ASN.1 OER packets and fields += OER field explicit tag +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_explicit_tag']).check_oer_field_explicit_tag() += OER field fixed size +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_fixed_size']).check_oer_field_fixed_size() += OER field optional +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_optional']).check_oer_field_optional() += OER field sequence of +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_sequence_of']).check_oer_field_sequence_of() += OER field choice +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_choice']).check_oer_field_choice() += OER packet record +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_packet_record']).check_oer_packet_record() += OER nested sequence +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_nested_sequence']).check_oer_nested_sequence() += OER nested sequence trailing field +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_nested_sequence_trailing']).check_oer_nested_sequence_trailing() += OER sequence of with trailing field +__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_sequence_of_with_trailing']).check_oer_sequence_of_with_trailing() + + ++ ASN.1 packet build tests (BER, OER, PER) += BER record build roundtrip +__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_ber_record_build_roundtrip']).check_ber_record_build_roundtrip() += OER record build roundtrip +__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_oer_record_build_roundtrip']).check_oer_record_build_roundtrip() += PER record build roundtrip +__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_record_build_roundtrip']).check_per_record_build_roundtrip() += PER default field build +__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_default_field_build']).check_per_default_field_build() += PER extensible integer build +__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_extensible_integer_build']).check_per_extensible_integer_build() += PER constrained sequence of build +__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_constrained_sequence_of_build']).check_per_constrained_sequence_of_build() += BER OER PER choice build +__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_ber_oer_per_choice_build']).check_ber_oer_per_choice_build() + ++ ASN.1 packet dissection tests (BER, OER, PER) += BER field dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_ber_field_dissect']).check_ber_field_dissect() += BER record dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_ber_record_dissect']).check_ber_record_dissect() += OER field dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_oer_field_dissect']).check_oer_field_dissect() += OER record dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_oer_record_dissect']).check_oer_record_dissect() += PER field dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_field_dissect']).check_per_field_dissect() += PER record dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_record_dissect']).check_per_record_dissect() += PER default field dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_default_field_dissect']).check_per_default_field_dissect() += PER extensible integer dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_extensible_integer_dissect']).check_per_extensible_integer_dissect() += PER constrained sequence of dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_constrained_sequence_of_dissect']).check_per_constrained_sequence_of_dissect() += BER OER PER record dissect +__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_ber_oer_per_record_dissect']).check_ber_oer_per_record_dissect() + ++ 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 codec roundtrips += UPER codec primitive roundtrips +__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_roundtrips']).check_uper_codec_roundtrips() += UPER codec reference decode interop +__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_reference_decode']).check_uper_codec_reference_decode() += UPER codec scapy encode reference interop +__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_encode_reference']).check_uper_codec_encode_reference() += UPER codec OID encode interop +__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_oid_encode_interop']).check_uper_codec_oid_encode_interop() += UPER codec OID roundtrip +__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_oid_roundtrip']).check_uper_codec_oid_roundtrip() + ++ ASN.1 UPER helpers += UPER length determinant +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_length_determinant']).check_uper_length_determinant() += UPER count roundtrip +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_count_roundtrip']).check_uper_count_roundtrip() += UPER choice index roundtrip +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_choice_index_roundtrip']).check_uper_choice_index_roundtrip() += UPER optional presence +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_optional_presence']).check_uper_optional_presence() += UPER constrained integer helper +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_constrained_integer']).check_uper_constrained_integer() += UPER constrained signed integer helper +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_constrained_signed_integer']).check_uper_constrained_signed_integer() += UPER octet string helper roundtrip +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_octet_string_roundtrip']).check_uper_octet_string_roundtrip() += UPER unexpected remainder detection +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_has_unexpected_remainder']).check_uper_has_unexpected_remainder() += UPER join encodings +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_join_encodings']).check_uper_join_encodings() += UPER chained encode into +__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_chained_encode_into']).check_uper_chained_encode_into() + ++ ASN.1 UPER interoperability (reference vectors) += UPER primitive encode interop +__import__('test.scapy.layers.uper_iop', fromlist=['check_primitive_interop']).check_primitive_interop() += UPER composite encode interop +__import__('test.scapy.layers.uper_iop', fromlist=['check_composite_interop']).check_composite_interop() += UPER packet reference interop +__import__('test.scapy.layers.uper_iop', fromlist=['check_packet_reference_interop']).check_packet_reference_interop() += UPER packet decode vectors +__import__('test.scapy.layers.uper_iop', fromlist=['check_packet_decode_vectors']).check_packet_decode_vectors() + ++ ASN.1 UPER asn1scc interoperability += asn1scc vector encode interop +__import__('test.scapy.layers.uper_asn1scc_iop', fromlist=['check_asn1scc_vectors']).check_asn1scc_vectors() += asn1scc README Message uPER reference +__import__('test.scapy.layers.uper_asn1scc_iop', fromlist=['check_asn1scc_readme_message_reference']).check_asn1scc_readme_message_reference() += asn1scc README MessagePrefix Scapy interop +__import__('test.scapy.layers.uper_asn1scc_iop', fromlist=['check_asn1scc_readme_message_prefix']).check_asn1scc_readme_message_prefix() + ++ ASN.1 UPER packets and fields += UPER field fixed size +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_fixed_size']).check_uper_field_fixed_size() += UPER field integer +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_integer']).check_uper_field_integer() += UPER field boolean +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_boolean']).check_uper_field_boolean() += UPER field string +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_string']).check_uper_field_string() += UPER field constrained integer +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_constrained_integer']).check_uper_field_constrained_integer() += UPER field optional +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_optional']).check_uper_field_optional() += UPER field sequence of +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_sequence_of']).check_uper_field_sequence_of() += UPER field choice +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_choice']).check_uper_field_choice() += UPER field choice definition order +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_choice_definition_order']).check_uper_field_choice_definition_order() += UPER packet record +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_packet_record']).check_uper_packet_record() += UPER field enumerated +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_enumerated']).check_uper_field_enumerated() += UPER field bit string +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_bit_string']).check_uper_field_bit_string() += UPER message prefix +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_message_prefix']).check_uper_message_prefix() += UPER sequence with choice +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_with_choice']).check_uper_sequence_with_choice() += UPER null packet +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_null_packet']).check_uper_null_packet() += UPER variable octet string +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_variable_octet_string']).check_uper_variable_octet_string() += UPER constrained range integer +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_constrained_range_integer']).check_uper_constrained_range_integer() += UPER sequence with enumerated +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_with_enumerated']).check_uper_sequence_with_enumerated() += UPER sequence of strings +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_of_strings']).check_uper_sequence_of_strings() += UPER sequence choice hex +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_choice_hex']).check_uper_sequence_choice_hex() += UPER nested sequence +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_nested_sequence']).check_uper_nested_sequence() += UPER sequence with null +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_with_null']).check_uper_sequence_with_null() += UPER fixed bit string packet +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_fixed_bit_string']).check_uper_fixed_bit_string() += UPER multi optional +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_multi_optional']).check_uper_multi_optional() += UPER sequence of constrained integers +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_of_constrained_ints']).check_uper_sequence_of_constrained_ints() += UPER signed integer field +__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_signed_integer']).check_uper_signed_integer() + ++ ASN.1 UPER fuzzing += UPER fuzz encode +__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_encode']).check_uper_fuzz_encode() += UPER fuzz encode roundtrip +__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_roundtrip']).check_uper_fuzz_roundtrip() += UPER fuzz codec decode +__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_codec_decode']).check_uper_fuzz_codec_decode() += UPER fuzz packet decode +__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_packet_decode']).check_uper_fuzz_packet_decode() + diff --git a/test/scapy/layers/asn1_build_tests.py b/test/scapy/layers/asn1_build_tests.py new file mode 100644 index 00000000000..9fc9122a9e7 --- /dev/null +++ b/test/scapy/layers/asn1_build_tests.py @@ -0,0 +1,185 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Cross-codec ASN.1 packet build and round-trip tests (BER, OER, PER). +""" +import scapy.contrib.oer # noqa: F401 # register OER stem +import scapy.contrib.uper # noqa: F401 # register UPER stem + +from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING +from scapy.asn1fields import ( + ASN1F_BOOLEAN, + ASN1F_CHOICE, + ASN1F_DEFAULT, + ASN1F_INTEGER, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_STRING, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet +from scapy.packet import raw + +from typing import Any + +from test.scapy.layers.ber_packets import BERRecord +from test.scapy.layers.oer_packets import OERRecord +from test.scapy.layers.uper_packets import UPERRecord + + +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 check_ber_record_build_roundtrip(): + # type: () -> None + 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] + + +def check_oer_record_build_roundtrip(): + # type: () -> None + 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] + + +def check_per_record_build_roundtrip(): + # type: () -> None + 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] + + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + + +def check_per_default_field_build(): + # type: () -> None + 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 + + +def check_per_extensible_integer_build(): + # type: () -> None + 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 + + +def check_per_constrained_sequence_of_build(): + # type: () -> None + 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] + + +def check_ber_oer_per_choice_build(): + # type: () -> None + 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" diff --git a/test/scapy/layers/asn1_coverage.py b/test/scapy/layers/asn1_coverage.py new file mode 100644 index 00000000000..dea2d651e3f --- /dev/null +++ b/test/scapy/layers/asn1_coverage.py @@ -0,0 +1,890 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Additional coverage for UPER, OER, and asn1fields helpers. +""" + + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + + +from typing import Any +from unittest import mock + +from scapy.asn1.asn1 import ( + ASN1_BIT_STRING, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_Error, + ASN1_INTEGER, + ASN1_STRING, + ASN1_TIME_TICKS, +) +from scapy.asn1.ber import BER_Decoding_Error +from scapy.contrib.oer import ( + OER_Decoding_Error, + OER_Encoding_Error, + OERcodec_BIT_STRING, + OERcodec_IPADDRESS, + OERcodec_SEQUENCE, + OERcodec_SET, +) +from scapy.contrib.uper import ( + UPER_Decoding_Error, + UPER_Encoding_Error, + UPER_Decoder, + UPER_Encoder, + UPERcodec_BIT_STRING, + UPERcodec_ENUMERATED, + UPERcodec_IPADDRESS, + UPERcodec_SEQUENCE, + UPERcodec_SET, +) +from scapy.asn1fields import ( + ASN1F_BIT_STRING, + ASN1F_BIT_STRING_ENCAPS, + ASN1F_BOOLEAN, + ASN1F_CHOICE, + ASN1F_DEFAULT, + ASN1F_FLAGS, + ASN1F_IPADDRESS, + ASN1F_INTEGER, + ASN1F_OID, + ASN1F_PACKET, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_SET_OF, + ASN1F_STRING, + ASN1F_STRING_ENCAPS, + ASN1F_STRING_PacketField, + ASN1F_TIME_TICKS, + ASN1F_UTC_TIME, + ASN1F_badsequence, + ASN1F_enum_INTEGER, + ASN1F_omit, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet +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 check_uper_error_str(): + # type: () -> None + 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) + + +def check_uper_length_determinant_extended(): + # type: () -> None + enc = UPER_Encoder() + assert enc.append_length_determinant(32768) == 32768 + assert enc.as_bytes() == b"\xc2" + + enc = UPER_Encoder() + assert enc.append_length_determinant(49152) == 49152 + assert enc.as_bytes() == b"\xc3" + + enc = UPER_Encoder() + assert enc.append_length_determinant(65535) == 49152 + assert enc.as_bytes() == b"\xc3" + + +def check_uper_unconstrained_whole_number(): + # type: () -> None + 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 + + +def check_uper_bit_string_paths(): + # type: () -> None + 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" + + +def check_uper_enumerated_range(): + # type: () -> None + 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 + + +def check_uper_sequence_errors(): + # type: () -> None + _raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.enc([ASN1_INTEGER(1)])) + + _raises(UPER_Decoding_Error, lambda: UPERcodec_SEQUENCE.do_dec(b"\x00")) + + assert UPERcodec_SET.enc(b"raw") == b"raw" + + +def check_uper_ipaddress(): + # type: () -> None + 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")) + + +def check_oer_error_str(): + # type: () -> None + 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) + + +def check_oer_ipaddress_and_sequence(): + # type: () -> None + 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"" + + +def check_asn1fields_enum_and_flags(): + # type: () -> None + 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] + + +def check_asn1fields_encaps_and_packet(): + # type: () -> None + 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 + + +def check_asn1fields_choice_and_special(): + # type: () -> None + 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" + + +def check_asn1fields_optional_dissect(): + # type: () -> None + 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 + + +def check_asn1fields_default_and_omit(): + # type: () -> None + 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") + + +def check_asn1fields_extensible_per(): + # type: () -> None + 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 + + +def check_asn1fields_sequence_of_advanced(): + # type: () -> None + 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())) + + +def check_asn1fields_choice_advanced(): + # type: () -> None + 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._uper_encode_into( + UPER_Encoder(), _PerChoice(), 42, + ), + ) + + +def check_asn1fields_enum_bitstring_and_flags(): + # type: () -> None + 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", + ), + ) + + +def check_asn1fields_packet_and_sequence_errors(): + # type: () -> None + 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" + + +def check_asn1fields_more_coverage(): + # type: () -> None + _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) + diff --git a/test/scapy/layers/asn1_dissect_tests.py b/test/scapy/layers/asn1_dissect_tests.py new file mode 100644 index 00000000000..2383560c3cf --- /dev/null +++ b/test/scapy/layers/asn1_dissect_tests.py @@ -0,0 +1,280 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +ASN.1 packet dissection tests from fixed byte vectors (BER, OER, PER). +""" +import scapy.contrib.oer # noqa: F401 # register OER stem +import scapy.contrib.uper # noqa: F401 # register UPER stem + +from typing import Any, Type + +from scapy.asn1.asn1 import ASN1_Codecs +from scapy.asn1fields import ( + ASN1F_DEFAULT, + ASN1F_INTEGER, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, +) +from scapy.asn1packet import ASN1_Packet + +from test.scapy.layers.ber_packets import ( + BERChoiceField, + BERFixedFields, + BEROptionalField, + BERRecord, + BERSequenceOfIntegers, + BERTaggedInteger, +) +from test.scapy.layers.oer_packets import ( + OERChoiceField, + OERFixedFields, + OEROptionalField, + OERRecord, + OERSequenceOfIntegers, + OERTaggedInteger, +) +from test.scapy.layers.uper_packets import ( + UPERChoiceField, + UPERFixedFields, + UPEROptionalField, + UPERRecord, + UPERSequenceOfIntegers, +) + + +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 check_ber_field_dissect(): + # type: () -> None + 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" + + +def check_ber_record_dissect(): + # type: () -> None + decoded = _dissect( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + ) + _assert_record(decoded) + + empty = _dissect(BERRecord, "300a02010101010004003000") + _assert_record_empty(empty) + + +def check_oer_field_dissect(): + # type: () -> None + tagged = _dissect(OERTaggedInteger, "a10105") + assert tagged.n.val == 5 + + fixed = _dissect(OERFixedFields, "c8414243") + assert fixed.n.val == 200 + assert fixed.s.val == b"ABC" + + present = _dissect(OEROptionalField, "0101a00107") + assert present.id.val == 1 + assert present.extra.val == 7 + + absent = _dissect(OEROptionalField, "0101") + 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" + + +def check_oer_record_dissect(): + # type: () -> None + decoded = _dissect( + OERRecord, + "012aff026869a00107" + "0103010101020103", + ) + _assert_record(decoded) + + empty = _dissect(OERRecord, "010100000100") + _assert_record_empty(empty) + + +def check_per_field_dissect(): + # type: () -> None + 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" + + +def check_per_record_dissect(): + # type: () -> None + 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) + + +def check_per_default_field_dissect(): + # type: () -> None + 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 + + +def check_per_extensible_integer_dissect(): + # type: () -> None + 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 + + +def check_per_constrained_sequence_of_dissect(): + # type: () -> None + 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] + + +def check_ber_oer_per_record_dissect(): + # type: () -> None + for cls, data_hex in [ + ( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + ), + ( + OERRecord, + "012aff026869a00107" + "0103010101020103", + ), + ( + UPERRecord, + "8095409a1a4041c0c04040408040c0", + ), + ]: + _assert_record(_dissect(cls, data_hex)) diff --git a/test/scapy/layers/ber_codec.py b/test/scapy/layers/ber_codec.py new file mode 100644 index 00000000000..e6939f7a27e --- /dev/null +++ b/test/scapy/layers/ber_codec.py @@ -0,0 +1,275 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +BER codec and helper coverage tests. +""" + +from typing import Any + + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + + +from scapy.asn1.asn1 import ( + ASN1_Class_UNIVERSAL, + ASN1_DECODING_ERROR, + ASN1_INTEGER, + ASN1_Object, +) +from scapy.asn1.ber import ( + BER_BadTag_Decoding_Error, + BER_Decoding_Error, + BER_Encoding_Error, + BER_Exception, + BER_id_dec, + BER_id_enc, + BER_len_dec, + BER_len_enc, + BER_num_dec, + BER_num_enc, + BER_tagging_dec, + BER_tagging_enc, + BERcodec_BIT_STRING, + BERcodec_INTEGER, + BERcodec_IPADDRESS, + BERcodec_NULL, + BERcodec_Object, + BERcodec_OID, + BERcodec_SEQUENCE, + BERcodec_SET, + BERcodec_STRING, +) +from scapy.config import conf + + +def check_ber_error_str(): + # type: () -> None + obj = ASN1_INTEGER(1) + enc_err = BER_Encoding_Error("enc", encoded=obj, remaining=b"rest") + assert "Already encoded" in str(enc_err) + enc_err2 = BER_Encoding_Error("enc", encoded="raw", remaining=b"") + assert "raw" in str(enc_err2) + + dec_err = BER_Decoding_Error("dec", decoded=obj, remaining=b"tail") + assert "Already decoded" in str(dec_err) + dec_err2 = BER_Decoding_Error("dec", decoded=[1], remaining=b"") + assert "[1]" in str(dec_err2) + + +def check_ber_len_enc_dec(): + # type: () -> None + for value in [0, 1, 127, 128, 999]: + encoded = BER_len_enc(value) + length, remain = BER_len_dec(encoded) + assert length == value + assert remain == b"" + + assert BER_len_enc(45, size=None) == BER_len_enc(45, size=0) + assert BER_len_enc(45, size=4) == b"\x84\x00\x00\x00-" + + _raises(BER_Exception, lambda: BER_len_enc(0, size=128)) + + _raises(BER_Decoding_Error, lambda: BER_len_dec(b"\x82")) + + +def check_ber_num_enc_dec(): + # type: () -> None + for value in [0, 1, 127, 256, 16384]: + encoded = BER_num_enc(value) + decoded, remain = BER_num_dec(encoded) + assert decoded == value + assert remain == b"" + + _raises(BER_Decoding_Error, lambda: BER_num_dec(b"")) + + _raises(BER_Decoding_Error, lambda: BER_num_dec(b"\x80\x80")) + + +def check_ber_id_enc_dec(): + # type: () -> None + for tag in [0x02, 0x30, 0x81, 0xA0]: + encoded = BER_id_enc(tag) + decoded, remain = BER_id_dec(encoded) + assert decoded == tag + assert remain == b"" + + high_tag = (0x03 << 5) + 0x22 + encoded = BER_id_enc(high_tag) + decoded, remain = BER_id_dec(encoded) + assert decoded == high_tag + assert remain == b"" + + +def check_ber_tagging(): + # type: () -> None + inner = BERcodec_INTEGER.enc(7) + implicit = BER_tagging_enc(inner, implicit_tag=0xA0) + assert implicit.startswith(b"\xa0") + real_tag, payload = BER_tagging_dec( + implicit, + hidden_tag=ASN1_Class_UNIVERSAL.INTEGER, + implicit_tag=0xA0, + ) + assert real_tag is None + assert payload[0] == int(ASN1_Class_UNIVERSAL.INTEGER) + + conf.ASN1_default_long_size = 4 + try: + explicit = BER_tagging_enc(inner, explicit_tag=0xA1) + assert explicit.startswith(b"\xa1\x84") + real_tag, payload = BER_tagging_dec( + explicit, + explicit_tag=0xA1, + ) + assert real_tag is None + assert payload == inner + finally: + conf.ASN1_default_long_size = 0 + + _raises(BER_Decoding_Error, lambda: BER_tagging_dec( + implicit, + hidden_tag=ASN1_Class_UNIVERSAL.INTEGER, + implicit_tag=0xA1, + )) + + safe_tag, _ = BER_tagging_dec( + implicit, + hidden_tag=ASN1_Class_UNIVERSAL.INTEGER, + implicit_tag=0xA1, + safe=True, + ) + assert safe_tag == 0xA0 + + +def check_ber_integer(): + # type: () -> None + for value in [0, 1, 127, 128, 255, -1, -128, -129]: + encoded = BERcodec_INTEGER.enc(value) + obj, remain = BERcodec_INTEGER.do_dec(encoded) + assert obj.val == value + assert remain == b"" + + _raises(BER_BadTag_Decoding_Error, lambda: BERcodec_INTEGER.do_dec(BERcodec_STRING.enc(b"x"))) + + _raises(BER_Decoding_Error, lambda: BERcodec_INTEGER.check_type_get_len(b"\x02")) + + +def check_ber_bit_string(): + # type: () -> None + encoded = BERcodec_BIT_STRING.enc("1011") + obj, remain = BERcodec_BIT_STRING.do_dec(encoded) + assert obj.val == "1011" + assert remain == b"" + + padded = BERcodec_BIT_STRING.enc("10110000") + obj2, _ = BERcodec_BIT_STRING.do_dec(padded) + assert obj2.val == "10110000" + + _raises(BER_Decoding_Error, lambda: BERcodec_BIT_STRING.do_dec(b"\x03\x01\x08", safe=True)) + + _raises(BER_Decoding_Error, lambda: BERcodec_BIT_STRING.do_dec(b"\x03\x00")) + + +def check_ber_string_and_null(): + # type: () -> None + encoded = BERcodec_STRING.enc(b"hello") + obj, remain = BERcodec_STRING.do_dec(encoded) + assert obj.val == b"hello" + assert remain == b"" + + null = BERcodec_NULL.enc(0) + assert null == b"\x05\x00" + obj, remain = BERcodec_NULL.do_dec(null) + assert obj.val == 0 + + non_null = BERcodec_NULL.enc(42) + obj, remain = BERcodec_NULL.do_dec(non_null) + assert obj.val == 42 + + +def check_ber_oid(): + # type: () -> None + encoded = BERcodec_OID.enc("1.2.840.113556.1.4.529") + obj, remain = BERcodec_OID.do_dec(encoded) + assert obj.val == "1.2.840.113556.1.4.529" + assert remain == b"" + + empty, remain = BERcodec_OID.do_dec(BERcodec_OID.enc("")) + assert empty.val == "" + assert remain == b"" + + +def check_ber_sequence_and_set(): + # type: () -> None + payload = BERcodec_INTEGER.enc(1) + BERcodec_INTEGER.enc(2) + seq = BERcodec_SEQUENCE.enc(payload) + obj, remain = BERcodec_SEQUENCE.do_dec(seq) + assert len(obj.val) == 2 + assert obj.val[0].val == 1 + assert obj.val[1].val == 2 + assert remain == b"" + + as_list = BERcodec_SEQUENCE.enc([ASN1_INTEGER(3), ASN1_INTEGER(4)]) + obj2, remain2 = BERcodec_SEQUENCE.do_dec(as_list) + assert [x.val for x in obj2.val] == [3, 4] + assert remain2 == b"" + + st = BERcodec_SET.enc(payload) + obj3, remain3 = BERcodec_SET.do_dec(st) + assert len(obj3.val) == 2 + assert remain3 == b"" + + conf.ASN1_default_long_size = 4 + try: + long_seq = BERcodec_SEQUENCE.enc(payload) + assert long_seq.startswith(b"0\x84") + finally: + conf.ASN1_default_long_size = 0 + + _raises(BER_Decoding_Error, lambda: BERcodec_SEQUENCE.do_dec(b"\x30\x05" + BERcodec_INTEGER.enc(1))) + + +def check_ber_ipaddress(): + # type: () -> None + encoded = BERcodec_IPADDRESS.enc("192.168.0.1") + obj, remain = BERcodec_IPADDRESS.do_dec(encoded) + assert obj.val == "192.168.0.1" + assert remain == b"" + + _raises(BER_Encoding_Error, lambda: BERcodec_IPADDRESS.enc("not-an-ip")) + + _raises(BER_Decoding_Error, lambda: BERcodec_IPADDRESS.do_dec(BERcodec_STRING.enc(b"bad"))) + + +def check_ber_object_dispatch(): + # type: () -> None + encoded = BERcodec_INTEGER.enc(99) + obj, remain = BERcodec_Object.do_dec(encoded) + assert obj.val == 99 + assert remain == b"" + + _raises(BER_Decoding_Error, lambda: BERcodec_Object.check_string(b"")) + + _raises(BER_Decoding_Error, lambda: BERcodec_Object.do_dec(b"\xff\x00")) + + bad, remain = BERcodec_Object.safedec(b"\x02\x01\x01") + assert isinstance(bad, ASN1_INTEGER) + assert bad.val == 1 + + unknown, remain = BERcodec_Object.safedec(b"\xff\x00") + assert isinstance(unknown, ASN1_DECODING_ERROR) + + truncated, remain = BERcodec_Object.dec(b"\x02\x05\x01", safe=True) + assert isinstance(truncated, ASN1_DECODING_ERROR) + assert remain == b"" + + _raises(TypeError, lambda: BERcodec_Object.enc(object())) + assert BERcodec_Object.enc("42") == BERcodec_STRING.enc("42") diff --git a/test/scapy/layers/ber_packets.py b/test/scapy/layers/ber_packets.py new file mode 100644 index 00000000000..09cb02fe6f1 --- /dev/null +++ b/test/scapy/layers/ber_packets.py @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +BER ASN1_Packet and ASN1F_field build tests. +""" + +from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING +from scapy.asn1fields import ( + ASN1F_BOOLEAN, + ASN1F_CHOICE, + ASN1F_INTEGER, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_STRING, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet +from scapy.packet import raw + + +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)) + + +def check_ber_field_explicit_tag(): + # type: () -> None + pkt = BERTaggedInteger(n=5) + assert raw(pkt) == b"\xa1\x03\x02\x01\x05" + decoded = _roundtrip(BERTaggedInteger, pkt) + assert decoded.n.val == 5 + + +def check_ber_field_fixed_size(): + # type: () -> None + pkt = BERFixedFields(n=200, s=b"ABC") + assert raw(pkt) == bytes.fromhex("300d02810200c80483000003414243") + decoded = _roundtrip(BERFixedFields, pkt) + assert decoded.n.val == 200 + assert decoded.s.val == b"ABC" + + +def check_ber_field_optional(): + # type: () -> None + present = BEROptionalField(id=1, extra=7) + assert raw(present) == bytes.fromhex("3008020101a003020107") + decoded = _roundtrip(BEROptionalField, present) + assert decoded.id.val == 1 + assert decoded.extra.val == 7 + + absent = BEROptionalField(id=1, extra=None) + assert raw(absent) == bytes.fromhex("3003020101") + decoded = _roundtrip(BEROptionalField, absent) + assert decoded.id.val == 1 + assert decoded.extra is None + + +def check_ber_optional_sequence_is_empty(): + # type: () -> None + """Optional ASN1F_SEQUENCE must use the wrapped field's is_empty(). + + SEQUENCE stores children under their own names (not dummy_seq_name), so + inspecting pkt.dummy_seq_name incorrectly reports present children as empty + and makes the parent SEQUENCE look empty. + """ + opt = BEROptionalSequence.ASN1_root.seq[1] + + present = BEROptionalSequence(hdr=1, id=42, label=b"abc") + assert opt._field.is_empty(present) is False + assert opt.is_empty(present) is False + assert BEROptionalSequence.ASN1_root.is_empty(present) is False + assert raw(present) == bytes.fromhex("300f020101a00a300802012a0403616263") + + absent = BEROptionalSequence(hdr=1, id=None, label=None) + assert opt._field.is_empty(absent) is True + assert opt.is_empty(absent) is True + assert raw(absent) == bytes.fromhex("3003020101") + + +def check_ber_field_sequence_of(): + # type: () -> None + pkt = BERSequenceOfIntegers(values=[1, 2, 3]) + assert raw(pkt) == b"\x30\x09\x02\x01\x01\x02\x01\x02\x02\x01\x03" + decoded = _roundtrip(BERSequenceOfIntegers, pkt) + assert [x.val for x in decoded.values] == [1, 2, 3] + + +def check_ber_field_choice(): + # type: () -> None + as_int = BERChoiceField(c=ASN1_INTEGER(99)) + assert raw(as_int) == b"\x02\x01c" + decoded = _roundtrip(BERChoiceField, as_int) + assert decoded.c.val == 99 + + as_str = BERChoiceField(c=ASN1_STRING("x")) + assert raw(as_str) == b"\x04\x01x" + decoded = _roundtrip(BERChoiceField, as_str) + assert decoded.c.val == b"x" + + +def check_ber_packet_record(): + # type: () -> None + pkt = BERRecord( + id=42, flag=True, label="hi", extra=7, values=[1, 2, 3], + ) + expected = bytes.fromhex( + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103" + ) + assert raw(pkt) == expected + 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] + + empty = BERRecord(id=1, flag=False, label="", extra=None, values=[]) + assert raw(empty) == bytes.fromhex("300a02010101010004003000") + decoded = _roundtrip(BERRecord, 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] == [] diff --git a/test/scapy/layers/oer_fuzz.py b/test/scapy/layers/oer_fuzz.py new file mode 100644 index 00000000000..920e51e8abd --- /dev/null +++ b/test/scapy/layers/oer_fuzz.py @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +OER fuzzing helpers. + +Exercise OER encode/decode paths with packet.fuzz() and random payloads. +""" + +import os +import random +from typing import Iterable, Type + +from scapy.asn1.asn1 import ASN1_Codecs, ASN1_Decoding_Error, ASN1_Error +from scapy.contrib.oer import ( + OER_Decoding_Error, + OERcodec_BIT_STRING, + OERcodec_BOOLEAN, + OERcodec_ENUMERATED, + OERcodec_INTEGER, + OERcodec_NULL, + OERcodec_OID, + OERcodec_STRING, +) +from scapy.asn1fields import ( + ASN1F_BOOLEAN, + ASN1F_INTEGER, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_STRING, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet +from scapy.packet import fuzz, raw + +_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 check_oer_fuzz_encode(iterations=25): + # type: (int) -> None + for cls in _fuzz_packets(): + for _ in range(iterations): + data = raw(fuzz(cls())) + assert isinstance(data, bytes) + + +def check_oer_fuzz_roundtrip(iterations=25): + # type: (int) -> None + for cls in _fuzz_packets(): + for _ in range(iterations): + cls(raw(fuzz(cls()))) + + +def check_oer_fuzz_codec_decode(iterations=100): + # type: (int) -> None + 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 + + +def check_oer_fuzz_packet_decode(iterations=100): + # type: (int) -> None + for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass diff --git a/test/scapy/layers/oer_iop.py b/test/scapy/layers/oer_iop.py new file mode 100644 index 00000000000..baa68a5b534 --- /dev/null +++ b/test/scapy/layers/oer_iop.py @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +OER interoperability helpers. + +Cross-check Scapy's OER codec against reference encodings (from asn1tools). +Reference vectors are taken from asn1tools/tests/test_oer.py. +""" + +from scapy.contrib.oer import ( + OERcodec_BIT_STRING, + OERcodec_BOOLEAN, + OERcodec_ENUMERATED, + OERcodec_INTEGER, + OERcodec_NULL, + OERcodec_OID, + OERcodec_STRING, + OER_signed_integer_enc, + OER_unsigned_integer_enc, +) + +# (type name, value, scapy encoder callable, reference encoding) +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", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\x80"), + ("G", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x00\x80"), + ("G", 1000, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x03\xe8"), + ("H", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=4), b"\x00\x00\x00\x80"), + ( + "I", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=8), + 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"), +] + +# (type name, value, reference encoding) +SCAPY_DECODE_VECTORS = [ + ("A", 42, b"\x01*"), + ("F", 200, b"\xc8"), + ("B", -99, b"\x9d"), +] + + +def check_primitive_interop(): + # type: () -> bool + """Compare Scapy OER primitives against reference encodings.""" + 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 + + return True + + +def check_scapy_encode_reference_decode(): + # type: () -> bool + """Decode reference encodings with Scapy.""" + 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 + + return True diff --git a/test/scapy/layers/oer_packets.py b/test/scapy/layers/oer_packets.py new file mode 100644 index 00000000000..7260609a42a --- /dev/null +++ b/test/scapy/layers/oer_packets.py @@ -0,0 +1,209 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +OER ASN1_Packet and ASN1F_field tests. +""" +import scapy.contrib.oer # noqa: F401 # register OER stem + +from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING +from scapy.asn1fields import ( + ASN1F_BOOLEAN, + ASN1F_CHOICE, + ASN1F_INTEGER, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_STRING, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet +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)) + + +def check_oer_field_explicit_tag(): + # type: () -> None + pkt = OERTaggedInteger(n=5) + assert raw(pkt) == b"\xa1\x01\x05" + decoded = _roundtrip(OERTaggedInteger, pkt) + assert decoded.n.val == 5 + + +def check_oer_field_fixed_size(): + # type: () -> None + 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" + + +def check_oer_field_optional(): + # type: () -> None + present = OEROptionalField(id=1, extra=7) + assert raw(present) == b"\x01\x01\xa0\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"\x01\x01" + decoded = _roundtrip(OEROptionalField, absent) + assert decoded.id.val == 1 + assert decoded.extra is None + + +def check_oer_field_sequence_of(): + # type: () -> None + 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] + + +def check_oer_field_choice(): + # type: () -> None + 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" + + +def check_oer_packet_record(): + # type: () -> None + pkt = OERRecord( + id=42, flag=True, label="hi", extra=7, values=[1, 2, 3], + ) + expected = ( + b"\x01*\xff\x02hi\xa0\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"\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] == [] + + +def check_oer_nested_sequence(): + # type: () -> None + 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 + + +def check_oer_nested_sequence_trailing(): + # type: () -> None + 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 + + +def check_oer_sequence_of_with_trailing(): + # type: () -> None + 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 diff --git a/test/scapy/layers/uper_asn1scc_iop.py b/test/scapy/layers/uper_asn1scc_iop.py new file mode 100644 index 00000000000..411d23d9460 --- /dev/null +++ b/test/scapy/layers/uper_asn1scc_iop.py @@ -0,0 +1,190 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +UPER interoperability vectors from ESA asn1scc test cases. + +asn1scc (https://github.com/esa/asn1scc) primarily validates C/Ada code generation +with ACN custom encodings. Portable uPER vectors are taken from v4Tests where +``--TCLS MyPDU[]`` selects standard uPER (empty ACN = default PER). + +Cases that need REAL, explicit APPLICATION tags, or ACN overrides are not +compared against Scapy encoders here (or are reference-only). +""" + +from scapy.contrib.uper import ( + UPER_Encoder, + UPER_choice_index_enc, + UPERcodec_BIT_STRING, + UPERcodec_BOOLEAN, + UPERcodec_ENUMERATED, + UPERcodec_INTEGER, + UPERcodec_NULL, + UPERcodec_STRING, +) + +# asn1scc v4Tests/test-cases/acn/05-BOOLEAN/001.asn1 +BOOLEAN_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BOOLEAN " + "END" +) + +# asn1scc v4Tests/test-cases/acn/18-NULL/001.asn1 +NULL_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= NULL " + "END" +) + +# asn1scc v4Tests/test-cases/acn/06-OCTET-STRING/001.asn1 +OCTET_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= OCTET STRING (SIZE(1..20)) " + "END" +) + +# asn1scc v4Tests/test-cases/acn/09-CHOICE/001.asn1 (pdu1 = int1 : 10) +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" +) + +# asn1scc v4Tests/test-cases/acn/04-ENUMERATED/001.asn1 (pdu1 = beta) +ENUMERATED_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= ENUMERATED { alpha(1), beta(200) } " + "END" +) + +# asn1scc v4Tests/test-cases/acn/08-BIT-STRING/001.asn1 (pdu1 = 'ABCD'H) +BIT_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BIT STRING (SIZE(1..20)) " + "END" +) + +# asn1scc README.md sample.asn (REAL field; reference only) +README_MESSAGE_HEX = ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" +) + +README_MESSAGE_PREFIX_HEX = ( + "0101010248656c6c6f576f726c6480" +) + +# (name, pdu value, encoder callable, reference encoding) +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(0, 5, enc=enc) + UPERcodec_INTEGER.encode_into(enc, 10, uper_min=0, uper_max=15) + return enc.as_bytes() + + +def check_asn1scc_vectors(): + # type: () -> None + for name, _value, encoder, expected in ASN1SCC_VECTORS: + got = encoder(_value) + assert got == expected, ( + "%s: expected %s, got %s" % + (name, expected.hex(), got.hex()) + ) + + +def check_asn1scc_readme_message_prefix(): + # type: () -> None + """README sample without REAL; Scapy packet roundtrip vs reference.""" + from test.scapy.layers.uper_packets import UPERMessagePrefix + 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 + + +def check_asn1scc_readme_message_reference(): + # type: () -> None + """README C sample output; Scapy does not encode REAL in UPER yet.""" + assert README_MESSAGE_HEX == ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" + ) diff --git a/test/scapy/layers/uper_codec.py b/test/scapy/layers/uper_codec.py new file mode 100644 index 00000000000..180503fb06e --- /dev/null +++ b/test/scapy/layers/uper_codec.py @@ -0,0 +1,174 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +UPER primitive codec roundtrip and decode interoperability tests. +""" + +from typing import Any, Dict, Tuple, Type + +from scapy.contrib.uper import ( + UPERcodec_BIT_STRING, + UPERcodec_BOOLEAN, + UPERcodec_ENUMERATED, + UPERcodec_INTEGER, + UPERcodec_NULL, + UPERcodec_OID, + UPERcodec_STRING, +) + +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 + + +def check_uper_codec_roundtrips(): + # type: () -> None + for codec, value, kwargs, expected in CODEC_ROUNDTRIPS: + _assert_codec_roundtrip(codec, value, kwargs, expected) + + +def check_uper_codec_oid_roundtrip(): + # type: () -> None + 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 + + +def check_uper_codec_oid_encode_interop(): + # type: () -> None + 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()) + ) + + +def check_uper_codec_reference_decode(): + # type: () -> None + 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) + ) + + +def check_uper_codec_encode_reference(): + # type: () -> None + from test.scapy.layers.uper_iop import PRIMITIVE_VECTORS + + 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()) + ) diff --git a/test/scapy/layers/uper_fuzz.py b/test/scapy/layers/uper_fuzz.py new file mode 100644 index 00000000000..d0aa8571192 --- /dev/null +++ b/test/scapy/layers/uper_fuzz.py @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +UPER fuzzing helpers. + +Exercise UPER encode/decode paths with packet.fuzz() and random payloads. +""" + +import os +import random +from typing import Iterable, Type + +from scapy.asn1.asn1 import ASN1_Codecs, ASN1_Decoding_Error, ASN1_Error +from scapy.contrib.uper import ( + UPER_Decoding_Error, + UPER_Encoding_Error, + UPERcodec_BIT_STRING, + UPERcodec_BOOLEAN, + UPERcodec_ENUMERATED, + UPERcodec_INTEGER, + UPERcodec_NULL, + UPERcodec_OID, + UPERcodec_STRING, +) +from scapy.asn1fields import ( + ASN1F_BOOLEAN, + ASN1F_ENUMERATED, + ASN1F_INTEGER, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_STRING, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet +from scapy.packet import fuzz, raw + +_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 check_uper_fuzz_encode(iterations=25): + # type: (int) -> None + for cls in _fuzz_packets(): + for _ in range(iterations): + try: + data = raw(fuzz(cls())) + except _DECODE_ERRORS: + continue + assert isinstance(data, bytes) + + +def check_uper_fuzz_roundtrip(iterations=25): + # type: (int) -> None + for cls in _fuzz_packets(): + for _ in range(iterations): + try: + cls(raw(fuzz(cls()))) + except _DECODE_ERRORS: + pass + + +def check_uper_fuzz_codec_decode(iterations=100): + # type: (int) -> None + 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 + + +def check_uper_fuzz_packet_decode(iterations=100): + # type: (int) -> None + for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass diff --git a/test/scapy/layers/uper_helpers.py b/test/scapy/layers/uper_helpers.py new file mode 100644 index 00000000000..dc92ef18e68 --- /dev/null +++ b/test/scapy/layers/uper_helpers.py @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +UPER low-level helper and bitstream tests. +""" + +from scapy.contrib.uper import ( + UPER_Decoder, + UPER_Encoder, + UPER_choice_index_dec, + UPER_choice_index_enc, + UPER_constrained_int_dec, + UPER_constrained_int_enc, + UPER_count_dec, + UPER_count_enc, + UPER_has_unexpected_remainder, + UPER_join_encodings, + UPER_octet_string_dec, + UPER_octet_string_enc, + UPER_optional_presence_enc, + UPERcodec_INTEGER, +) + + +def check_uper_length_determinant(): + # type: () -> None + for length, expected in [ + (0, b"\x00"), + (1, b"\x01"), + (127, b"\x7f"), + (128, b"\x80\x80"), + (16383, b"\xbf\xff"), + (16384, b"\xc1"), + ]: + enc = UPER_Encoder() + enc.append_length_determinant(length) + assert enc.as_bytes() == expected + + +def check_uper_count_roundtrip(): + # type: () -> None + for count in [0, 1, 3, 127]: + enc = UPER_Encoder() + UPER_count_enc(count, enc=enc) + got, _ = UPER_count_dec(enc.as_bytes()) + assert got == count + + +def check_uper_choice_index_roundtrip(): + # type: () -> None + for index, choices in [(0, 2), (1, 5), (3, 5)]: + enc = UPER_Encoder() + UPER_choice_index_enc(index, choices, enc=enc) + got, _ = UPER_choice_index_dec(enc.as_bytes(), choices) + assert got == index + + +def check_uper_optional_presence(): + # type: () -> None + enc = UPER_Encoder() + UPER_optional_presence_enc([0, 1, 0], enc=enc) + assert enc.as_bytes() == b"\x40" + + +def check_uper_constrained_integer(): + # type: () -> None + data = UPER_constrained_int_enc(10, 0, 15) + value, remain = UPER_constrained_int_dec(data, 0, 15) + assert value == 10 + assert remain == b"" + + +def check_uper_constrained_signed_integer(): + # type: () -> None + for value, expected in [(0, b"\x80"), (-1, b"\x7f"), (127, b"\xff"), (-128, b"\x00")]: + data = UPER_constrained_int_enc(value, -128, 127) + assert data == expected + decoded, remain = UPER_constrained_int_dec(data, -128, 127) + assert decoded == value + assert remain == b"" + + +def check_uper_octet_string_roundtrip(): + # type: () -> None + for data, minimum, maximum in [ + (b"AB", None, None), + (b"\x12\x34\x56", 3, 3), + (bytes.fromhex("afbc4583"), 1, 20), + ]: + encoded = UPER_octet_string_enc(data, minimum, maximum) + dec = UPER_Decoder(encoded) + decoded, _ = UPER_octet_string_dec(encoded, minimum, maximum, dec=dec) + assert decoded == data + assert not UPER_has_unexpected_remainder(dec) + + +def check_uper_has_unexpected_remainder(): + # type: () -> None + assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x00")) is False + assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x80")) is True + + +def check_uper_join_encodings(): + # type: () -> None + a = UPERcodec_INTEGER.enc(1) + b = UPERcodec_INTEGER.enc(2) + joined = UPER_join_encodings(a, b) + dec = UPER_Decoder(joined) + assert dec.read_unconstrained_whole_number() == 1 + assert dec.read_unconstrained_whole_number() == 2 + + +def check_uper_chained_encode_into(): + # type: () -> None + 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 diff --git a/test/scapy/layers/uper_iop.py b/test/scapy/layers/uper_iop.py new file mode 100644 index 00000000000..af36a6dda7e --- /dev/null +++ b/test/scapy/layers/uper_iop.py @@ -0,0 +1,189 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +UPER interoperability helpers. + +Cross-check Scapy's UPER codec against reference encodings (from asn1tools). +""" + +from typing import Any + +from scapy.contrib.uper import ( + UPERcodec_BOOLEAN, + UPERcodec_ENUMERATED, + UPERcodec_INTEGER, + UPERcodec_NULL, + UPERcodec_STRING, + UPER_Encoder, + UPER_choice_index_enc, +) +from scapy.packet import raw + +from test.scapy.layers.uper_packets import ( + UPERMultiOptional, + UPERNestedSequence, +) + +# (type name, value, scapy encoder callable, reference encoding) +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", + ), +] + +# (type name, value, reference encoding) +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 check_primitive_interop(): + # type: () -> None + 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()) + ) + + +def check_composite_interop(): + # type: () -> None + 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()) + ) + + +def check_packet_reference_interop(): + # type: () -> None + 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 + + +def check_packet_decode_vectors(): + # type: () -> None + 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 + + +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(index, 2, enc=enc) + 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(index, 2, enc=enc) + 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) diff --git a/test/scapy/layers/uper_packets.py b/test/scapy/layers/uper_packets.py new file mode 100644 index 00000000000..0dec76a9002 --- /dev/null +++ b/test/scapy/layers/uper_packets.py @@ -0,0 +1,543 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +UPER ASN1_Packet and ASN1F_field tests. +""" +import scapy.contrib.uper # noqa: F401 # register UPER stem + +from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING +from scapy.asn1fields import ( + ASN1F_BIT_STRING, + ASN1F_BOOLEAN, + ASN1F_CHOICE, + ASN1F_ENUMERATED, + ASN1F_INTEGER, + ASN1F_NULL, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_STRING, + ASN1F_optional, +) +from scapy.asn1packet import ASN1_Packet +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)) + + +def check_uper_field_fixed_size(): + # type: () -> None + 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" + + +def check_uper_field_integer(): + # type: () -> None + pkt = UPERIntegerField(n=12345) + assert raw(pkt) == bytes.fromhex("023039") + decoded = _roundtrip(UPERIntegerField, pkt) + assert decoded.n.val == 12345 + + +def check_uper_field_boolean(): + # type: () -> None + 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 + + +def check_uper_field_string(): + # type: () -> None + pkt = UPERStringField(s=b"hi") + assert raw(pkt) == bytes.fromhex("026869") + decoded = _roundtrip(UPERStringField, pkt) + assert decoded.s.val == b"hi" + + +def check_uper_field_constrained_integer(): + # type: () -> None + pkt = UPERConstrainedInteger(n=200) + assert raw(pkt) == b"\xc8" + decoded = _roundtrip(UPERConstrainedInteger, pkt) + assert decoded.n.val == 200 + + +def check_uper_field_optional(): + # type: () -> None + 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 + + +def check_uper_field_sequence_of(): + # type: () -> None + 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] == [] + + +def check_uper_field_choice(): + # type: () -> None + 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" + + +def check_uper_field_choice_definition_order(): + # type: () -> None + 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 + + +def check_uper_packet_record(): + # type: () -> None + 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] == [] + + +def check_uper_field_enumerated(): + # type: () -> None + 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 + + +def check_uper_field_bit_string(): + # type: () -> None + 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" + + +def check_uper_message_prefix(): + # type: () -> None + 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 + + +def check_uper_sequence_with_choice(): + # type: () -> None + 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" + + +def check_uper_null_packet(): + # type: () -> None + pkt = UPERNullPacket() + assert raw(pkt) == b"" + decoded = _roundtrip(UPERNullPacket, pkt) + assert decoded.n is None + + +def check_uper_variable_octet_string(): + # type: () -> None + pkt = UPERVariableOctetString(data=bytes.fromhex("afbc4583")) + assert raw(pkt) == bytes.fromhex("1d7de22c18") + decoded = _roundtrip(UPERVariableOctetString, pkt) + assert decoded.data.val == bytes.fromhex("afbc4583") + + +def check_uper_constrained_range_integer(): + # type: () -> None + pkt = UPERConstrainedRangeInt(n=10) + assert raw(pkt) == b"\xa0" + decoded = _roundtrip(UPERConstrainedRangeInt, pkt) + assert decoded.n.val == 10 + + +def check_uper_sequence_with_enumerated(): + # type: () -> None + 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 + + +def check_uper_sequence_of_strings(): + # type: () -> None + 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] == [] + + +def check_uper_sequence_choice_hex(): + # type: () -> None + """Cross-check against reference composite encoding.""" + 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 + + +def check_uper_nested_sequence(): + # type: () -> None + 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 + + +def check_uper_sequence_with_null(): + # type: () -> None + 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 + + +def check_uper_fixed_bit_string(): + # type: () -> None + 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" + + +def check_uper_sequence_of_constrained_ints(): + # type: () -> None + 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] + + +def check_uper_signed_integer(): + # type: () -> None + 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 + + +def check_uper_multi_optional(): + # type: () -> None + 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 From 4cdc2de117bcd724bf722ce591ea0738f2c7a7a8 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Fri, 7 Aug 2026 20:44:45 +0200 Subject: [PATCH 02/19] Remove python based unit tests AI-Assisted: yes (Cursor) --- test/scapy/layers/asn1.uts | 729 +++--- test/scapy/layers/asn1_build_tests.py | 185 -- test/scapy/layers/asn1_coverage.py | 890 ------- test/scapy/layers/asn1_dissect_tests.py | 280 --- test/scapy/layers/ber.uts | 126 + test/scapy/layers/ber_codec.py | 275 --- test/scapy/layers/ber_packets.py | 184 -- test/scapy/layers/oer.uts | 862 +++++++ test/scapy/layers/oer_fuzz.py | 106 - test/scapy/layers/oer_iop.py | 160 -- test/scapy/layers/oer_packets.py | 209 -- test/scapy/layers/uper.uts | 2829 +++++++++++++++++++++++ test/scapy/layers/uper_asn1scc_iop.py | 190 -- test/scapy/layers/uper_codec.py | 174 -- test/scapy/layers/uper_fuzz.py | 133 -- test/scapy/layers/uper_helpers.py | 122 - test/scapy/layers/uper_iop.py | 189 -- test/scapy/layers/uper_packets.py | 543 ----- 18 files changed, 4199 insertions(+), 3987 deletions(-) delete mode 100644 test/scapy/layers/asn1_build_tests.py delete mode 100644 test/scapy/layers/asn1_coverage.py delete mode 100644 test/scapy/layers/asn1_dissect_tests.py delete mode 100644 test/scapy/layers/ber_codec.py delete mode 100644 test/scapy/layers/ber_packets.py create mode 100644 test/scapy/layers/oer.uts delete mode 100644 test/scapy/layers/oer_fuzz.py delete mode 100644 test/scapy/layers/oer_iop.py delete mode 100644 test/scapy/layers/oer_packets.py create mode 100644 test/scapy/layers/uper.uts delete mode 100644 test/scapy/layers/uper_asn1scc_iop.py delete mode 100644 test/scapy/layers/uper_codec.py delete mode 100644 test/scapy/layers/uper_fuzz.py delete mode 100644 test/scapy/layers/uper_helpers.py delete mode 100644 test/scapy/layers/uper_iop.py delete mode 100644 test/scapy/layers/uper_packets.py diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index c83f8388d8b..7f00cf6e17f 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) @@ -102,357 +114,380 @@ 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 OER/UPER contrib load ++ 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, + "012aff026869a00107" + "0103010101020103", + ), + ( + UPERRecord, + "8095409a1a4041c0c04040408040c0", + ), +]: + _assert_record(_dissect(cls, data_hex)) -+ 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) == 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 interoperability (reference vectors) -= primitive encode interop -__import__('test.scapy.layers.oer_iop', fromlist=['check_primitive_interop']).check_primitive_interop() -= scapy encode reference decode -__import__('test.scapy.layers.oer_iop', fromlist=['check_scapy_encode_reference_decode']).check_scapy_encode_reference_decode() - -+ ASN.1 OER review fixes -= OER fixed integer decode roundtrip -x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(128, size_len=1), 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 explicit null tagging -OER_tagging_enc(OERcodec_NULL.enc(None), explicit_tag=0x81) == b"\x81" -= OER choice id decode -tag, r = OER_id_dec(b"\x81\x01") -tag == 0x81 and r == b"\x01" - -+ ASN.1 OER fuzzing -= OER fuzz encode -__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_encode']).check_oer_fuzz_encode() -= OER fuzz encode roundtrip -__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_roundtrip']).check_oer_fuzz_roundtrip() -= OER fuzz codec decode -__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_codec_decode']).check_oer_fuzz_codec_decode() -= OER fuzz packet decode -__import__('test.scapy.layers.oer_fuzz', fromlist=['check_oer_fuzz_packet_decode']).check_oer_fuzz_packet_decode() - -+ ASN.1 OER packets and fields -= OER field explicit tag -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_explicit_tag']).check_oer_field_explicit_tag() -= OER field fixed size -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_fixed_size']).check_oer_field_fixed_size() -= OER field optional -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_optional']).check_oer_field_optional() -= OER field sequence of -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_sequence_of']).check_oer_field_sequence_of() -= OER field choice -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_field_choice']).check_oer_field_choice() -= OER packet record -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_packet_record']).check_oer_packet_record() -= OER nested sequence -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_nested_sequence']).check_oer_nested_sequence() -= OER nested sequence trailing field -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_nested_sequence_trailing']).check_oer_nested_sequence_trailing() -= OER sequence of with trailing field -__import__('test.scapy.layers.oer_packets', fromlist=['check_oer_sequence_of_with_trailing']).check_oer_sequence_of_with_trailing() - - -+ ASN.1 packet build tests (BER, OER, PER) -= BER record build roundtrip -__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_ber_record_build_roundtrip']).check_ber_record_build_roundtrip() -= OER record build roundtrip -__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_oer_record_build_roundtrip']).check_oer_record_build_roundtrip() -= PER record build roundtrip -__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_record_build_roundtrip']).check_per_record_build_roundtrip() -= PER default field build -__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_default_field_build']).check_per_default_field_build() -= PER extensible integer build -__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_extensible_integer_build']).check_per_extensible_integer_build() -= PER constrained sequence of build -__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_per_constrained_sequence_of_build']).check_per_constrained_sequence_of_build() -= BER OER PER choice build -__import__('test.scapy.layers.asn1_build_tests', fromlist=['check_ber_oer_per_choice_build']).check_ber_oer_per_choice_build() - -+ ASN.1 packet dissection tests (BER, OER, PER) -= BER field dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_ber_field_dissect']).check_ber_field_dissect() -= BER record dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_ber_record_dissect']).check_ber_record_dissect() -= OER field dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_oer_field_dissect']).check_oer_field_dissect() -= OER record dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_oer_record_dissect']).check_oer_record_dissect() -= PER field dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_field_dissect']).check_per_field_dissect() -= PER record dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_record_dissect']).check_per_record_dissect() -= PER default field dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_default_field_dissect']).check_per_default_field_dissect() -= PER extensible integer dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_extensible_integer_dissect']).check_per_extensible_integer_dissect() -= PER constrained sequence of dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_per_constrained_sequence_of_dissect']).check_per_constrained_sequence_of_dissect() -= BER OER PER record dissect -__import__('test.scapy.layers.asn1_dissect_tests', fromlist=['check_ber_oer_per_record_dissect']).check_ber_oer_per_record_dissect() - -+ 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 codec roundtrips -= UPER codec primitive roundtrips -__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_roundtrips']).check_uper_codec_roundtrips() -= UPER codec reference decode interop -__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_reference_decode']).check_uper_codec_reference_decode() -= UPER codec scapy encode reference interop -__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_encode_reference']).check_uper_codec_encode_reference() -= UPER codec OID encode interop -__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_oid_encode_interop']).check_uper_codec_oid_encode_interop() -= UPER codec OID roundtrip -__import__('test.scapy.layers.uper_codec', fromlist=['check_uper_codec_oid_roundtrip']).check_uper_codec_oid_roundtrip() - -+ ASN.1 UPER helpers -= UPER length determinant -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_length_determinant']).check_uper_length_determinant() -= UPER count roundtrip -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_count_roundtrip']).check_uper_count_roundtrip() -= UPER choice index roundtrip -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_choice_index_roundtrip']).check_uper_choice_index_roundtrip() -= UPER optional presence -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_optional_presence']).check_uper_optional_presence() -= UPER constrained integer helper -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_constrained_integer']).check_uper_constrained_integer() -= UPER constrained signed integer helper -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_constrained_signed_integer']).check_uper_constrained_signed_integer() -= UPER octet string helper roundtrip -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_octet_string_roundtrip']).check_uper_octet_string_roundtrip() -= UPER unexpected remainder detection -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_has_unexpected_remainder']).check_uper_has_unexpected_remainder() -= UPER join encodings -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_join_encodings']).check_uper_join_encodings() -= UPER chained encode into -__import__('test.scapy.layers.uper_helpers', fromlist=['check_uper_chained_encode_into']).check_uper_chained_encode_into() - -+ ASN.1 UPER interoperability (reference vectors) -= UPER primitive encode interop -__import__('test.scapy.layers.uper_iop', fromlist=['check_primitive_interop']).check_primitive_interop() -= UPER composite encode interop -__import__('test.scapy.layers.uper_iop', fromlist=['check_composite_interop']).check_composite_interop() -= UPER packet reference interop -__import__('test.scapy.layers.uper_iop', fromlist=['check_packet_reference_interop']).check_packet_reference_interop() -= UPER packet decode vectors -__import__('test.scapy.layers.uper_iop', fromlist=['check_packet_decode_vectors']).check_packet_decode_vectors() - -+ ASN.1 UPER asn1scc interoperability -= asn1scc vector encode interop -__import__('test.scapy.layers.uper_asn1scc_iop', fromlist=['check_asn1scc_vectors']).check_asn1scc_vectors() -= asn1scc README Message uPER reference -__import__('test.scapy.layers.uper_asn1scc_iop', fromlist=['check_asn1scc_readme_message_reference']).check_asn1scc_readme_message_reference() -= asn1scc README MessagePrefix Scapy interop -__import__('test.scapy.layers.uper_asn1scc_iop', fromlist=['check_asn1scc_readme_message_prefix']).check_asn1scc_readme_message_prefix() - -+ ASN.1 UPER packets and fields -= UPER field fixed size -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_fixed_size']).check_uper_field_fixed_size() -= UPER field integer -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_integer']).check_uper_field_integer() -= UPER field boolean -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_boolean']).check_uper_field_boolean() -= UPER field string -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_string']).check_uper_field_string() -= UPER field constrained integer -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_constrained_integer']).check_uper_field_constrained_integer() -= UPER field optional -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_optional']).check_uper_field_optional() -= UPER field sequence of -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_sequence_of']).check_uper_field_sequence_of() -= UPER field choice -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_choice']).check_uper_field_choice() -= UPER field choice definition order -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_choice_definition_order']).check_uper_field_choice_definition_order() -= UPER packet record -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_packet_record']).check_uper_packet_record() -= UPER field enumerated -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_enumerated']).check_uper_field_enumerated() -= UPER field bit string -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_field_bit_string']).check_uper_field_bit_string() -= UPER message prefix -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_message_prefix']).check_uper_message_prefix() -= UPER sequence with choice -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_with_choice']).check_uper_sequence_with_choice() -= UPER null packet -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_null_packet']).check_uper_null_packet() -= UPER variable octet string -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_variable_octet_string']).check_uper_variable_octet_string() -= UPER constrained range integer -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_constrained_range_integer']).check_uper_constrained_range_integer() -= UPER sequence with enumerated -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_with_enumerated']).check_uper_sequence_with_enumerated() -= UPER sequence of strings -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_of_strings']).check_uper_sequence_of_strings() -= UPER sequence choice hex -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_choice_hex']).check_uper_sequence_choice_hex() -= UPER nested sequence -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_nested_sequence']).check_uper_nested_sequence() -= UPER sequence with null -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_with_null']).check_uper_sequence_with_null() -= UPER fixed bit string packet -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_fixed_bit_string']).check_uper_fixed_bit_string() -= UPER multi optional -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_multi_optional']).check_uper_multi_optional() -= UPER sequence of constrained integers -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_sequence_of_constrained_ints']).check_uper_sequence_of_constrained_ints() -= UPER signed integer field -__import__('test.scapy.layers.uper_packets', fromlist=['check_uper_signed_integer']).check_uper_signed_integer() - -+ ASN.1 UPER fuzzing -= UPER fuzz encode -__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_encode']).check_uper_fuzz_encode() -= UPER fuzz encode roundtrip -__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_roundtrip']).check_uper_fuzz_roundtrip() -= UPER fuzz codec decode -__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_codec_decode']).check_uper_fuzz_codec_decode() -= UPER fuzz packet decode -__import__('test.scapy.layers.uper_fuzz', fromlist=['check_uper_fuzz_packet_decode']).check_uper_fuzz_packet_decode() +True diff --git a/test/scapy/layers/asn1_build_tests.py b/test/scapy/layers/asn1_build_tests.py deleted file mode 100644 index 9fc9122a9e7..00000000000 --- a/test/scapy/layers/asn1_build_tests.py +++ /dev/null @@ -1,185 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -Cross-codec ASN.1 packet build and round-trip tests (BER, OER, PER). -""" -import scapy.contrib.oer # noqa: F401 # register OER stem -import scapy.contrib.uper # noqa: F401 # register UPER stem - -from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING -from scapy.asn1fields import ( - ASN1F_BOOLEAN, - ASN1F_CHOICE, - ASN1F_DEFAULT, - ASN1F_INTEGER, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, - ASN1F_STRING, - ASN1F_optional, -) -from scapy.asn1packet import ASN1_Packet -from scapy.packet import raw - -from typing import Any - -from test.scapy.layers.ber_packets import BERRecord -from test.scapy.layers.oer_packets import OERRecord -from test.scapy.layers.uper_packets import UPERRecord - - -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 check_ber_record_build_roundtrip(): - # type: () -> None - 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] - - -def check_oer_record_build_roundtrip(): - # type: () -> None - 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] - - -def check_per_record_build_roundtrip(): - # type: () -> None - 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] - - -def _asn1_int(val): - # type: (Any) -> int - return val.val if hasattr(val, "val") else val - - -def check_per_default_field_build(): - # type: () -> None - 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 - - -def check_per_extensible_integer_build(): - # type: () -> None - 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 - - -def check_per_constrained_sequence_of_build(): - # type: () -> None - 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] - - -def check_ber_oer_per_choice_build(): - # type: () -> None - 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" diff --git a/test/scapy/layers/asn1_coverage.py b/test/scapy/layers/asn1_coverage.py deleted file mode 100644 index dea2d651e3f..00000000000 --- a/test/scapy/layers/asn1_coverage.py +++ /dev/null @@ -1,890 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -Additional coverage for UPER, OER, and asn1fields helpers. -""" - - -def _raises(exc, func): - # type: (type, Any) -> None - try: - func() - except exc: - return - raise AssertionError("Expected %s" % exc.__name__) - - -from typing import Any -from unittest import mock - -from scapy.asn1.asn1 import ( - ASN1_BIT_STRING, - ASN1_Class_UNIVERSAL, - ASN1_Codecs, - ASN1_Error, - ASN1_INTEGER, - ASN1_STRING, - ASN1_TIME_TICKS, -) -from scapy.asn1.ber import BER_Decoding_Error -from scapy.contrib.oer import ( - OER_Decoding_Error, - OER_Encoding_Error, - OERcodec_BIT_STRING, - OERcodec_IPADDRESS, - OERcodec_SEQUENCE, - OERcodec_SET, -) -from scapy.contrib.uper import ( - UPER_Decoding_Error, - UPER_Encoding_Error, - UPER_Decoder, - UPER_Encoder, - UPERcodec_BIT_STRING, - UPERcodec_ENUMERATED, - UPERcodec_IPADDRESS, - UPERcodec_SEQUENCE, - UPERcodec_SET, -) -from scapy.asn1fields import ( - ASN1F_BIT_STRING, - ASN1F_BIT_STRING_ENCAPS, - ASN1F_BOOLEAN, - ASN1F_CHOICE, - ASN1F_DEFAULT, - ASN1F_FLAGS, - ASN1F_IPADDRESS, - ASN1F_INTEGER, - ASN1F_OID, - ASN1F_PACKET, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, - ASN1F_SET_OF, - ASN1F_STRING, - ASN1F_STRING_ENCAPS, - ASN1F_STRING_PacketField, - ASN1F_TIME_TICKS, - ASN1F_UTC_TIME, - ASN1F_badsequence, - ASN1F_enum_INTEGER, - ASN1F_omit, - ASN1F_optional, -) -from scapy.asn1packet import ASN1_Packet -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 check_uper_error_str(): - # type: () -> None - 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) - - -def check_uper_length_determinant_extended(): - # type: () -> None - enc = UPER_Encoder() - assert enc.append_length_determinant(32768) == 32768 - assert enc.as_bytes() == b"\xc2" - - enc = UPER_Encoder() - assert enc.append_length_determinant(49152) == 49152 - assert enc.as_bytes() == b"\xc3" - - enc = UPER_Encoder() - assert enc.append_length_determinant(65535) == 49152 - assert enc.as_bytes() == b"\xc3" - - -def check_uper_unconstrained_whole_number(): - # type: () -> None - 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 - - -def check_uper_bit_string_paths(): - # type: () -> None - 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" - - -def check_uper_enumerated_range(): - # type: () -> None - 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 - - -def check_uper_sequence_errors(): - # type: () -> None - _raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.enc([ASN1_INTEGER(1)])) - - _raises(UPER_Decoding_Error, lambda: UPERcodec_SEQUENCE.do_dec(b"\x00")) - - assert UPERcodec_SET.enc(b"raw") == b"raw" - - -def check_uper_ipaddress(): - # type: () -> None - 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")) - - -def check_oer_error_str(): - # type: () -> None - 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) - - -def check_oer_ipaddress_and_sequence(): - # type: () -> None - 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"" - - -def check_asn1fields_enum_and_flags(): - # type: () -> None - 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] - - -def check_asn1fields_encaps_and_packet(): - # type: () -> None - 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 - - -def check_asn1fields_choice_and_special(): - # type: () -> None - 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" - - -def check_asn1fields_optional_dissect(): - # type: () -> None - 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 - - -def check_asn1fields_default_and_omit(): - # type: () -> None - 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") - - -def check_asn1fields_extensible_per(): - # type: () -> None - 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 - - -def check_asn1fields_sequence_of_advanced(): - # type: () -> None - 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())) - - -def check_asn1fields_choice_advanced(): - # type: () -> None - 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._uper_encode_into( - UPER_Encoder(), _PerChoice(), 42, - ), - ) - - -def check_asn1fields_enum_bitstring_and_flags(): - # type: () -> None - 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", - ), - ) - - -def check_asn1fields_packet_and_sequence_errors(): - # type: () -> None - 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" - - -def check_asn1fields_more_coverage(): - # type: () -> None - _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) - diff --git a/test/scapy/layers/asn1_dissect_tests.py b/test/scapy/layers/asn1_dissect_tests.py deleted file mode 100644 index 2383560c3cf..00000000000 --- a/test/scapy/layers/asn1_dissect_tests.py +++ /dev/null @@ -1,280 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -ASN.1 packet dissection tests from fixed byte vectors (BER, OER, PER). -""" -import scapy.contrib.oer # noqa: F401 # register OER stem -import scapy.contrib.uper # noqa: F401 # register UPER stem - -from typing import Any, Type - -from scapy.asn1.asn1 import ASN1_Codecs -from scapy.asn1fields import ( - ASN1F_DEFAULT, - ASN1F_INTEGER, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, -) -from scapy.asn1packet import ASN1_Packet - -from test.scapy.layers.ber_packets import ( - BERChoiceField, - BERFixedFields, - BEROptionalField, - BERRecord, - BERSequenceOfIntegers, - BERTaggedInteger, -) -from test.scapy.layers.oer_packets import ( - OERChoiceField, - OERFixedFields, - OEROptionalField, - OERRecord, - OERSequenceOfIntegers, - OERTaggedInteger, -) -from test.scapy.layers.uper_packets import ( - UPERChoiceField, - UPERFixedFields, - UPEROptionalField, - UPERRecord, - UPERSequenceOfIntegers, -) - - -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 check_ber_field_dissect(): - # type: () -> None - 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" - - -def check_ber_record_dissect(): - # type: () -> None - decoded = _dissect( - BERRecord, - "301a02012a01010104026869" - "a003020107" - "3009020101020102020103", - ) - _assert_record(decoded) - - empty = _dissect(BERRecord, "300a02010101010004003000") - _assert_record_empty(empty) - - -def check_oer_field_dissect(): - # type: () -> None - tagged = _dissect(OERTaggedInteger, "a10105") - assert tagged.n.val == 5 - - fixed = _dissect(OERFixedFields, "c8414243") - assert fixed.n.val == 200 - assert fixed.s.val == b"ABC" - - present = _dissect(OEROptionalField, "0101a00107") - assert present.id.val == 1 - assert present.extra.val == 7 - - absent = _dissect(OEROptionalField, "0101") - 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" - - -def check_oer_record_dissect(): - # type: () -> None - decoded = _dissect( - OERRecord, - "012aff026869a00107" - "0103010101020103", - ) - _assert_record(decoded) - - empty = _dissect(OERRecord, "010100000100") - _assert_record_empty(empty) - - -def check_per_field_dissect(): - # type: () -> None - 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" - - -def check_per_record_dissect(): - # type: () -> None - 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) - - -def check_per_default_field_dissect(): - # type: () -> None - 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 - - -def check_per_extensible_integer_dissect(): - # type: () -> None - 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 - - -def check_per_constrained_sequence_of_dissect(): - # type: () -> None - 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] - - -def check_ber_oer_per_record_dissect(): - # type: () -> None - for cls, data_hex in [ - ( - BERRecord, - "301a02012a01010104026869" - "a003020107" - "3009020101020102020103", - ), - ( - OERRecord, - "012aff026869a00107" - "0103010101020103", - ), - ( - UPERRecord, - "8095409a1a4041c0c04040408040c0", - ), - ]: - _assert_record(_dissect(cls, data_hex)) diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index 896f2ec746a..8087b383ec0 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -513,3 +513,129 @@ 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 + ++ 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/test/scapy/layers/ber_codec.py b/test/scapy/layers/ber_codec.py deleted file mode 100644 index e6939f7a27e..00000000000 --- a/test/scapy/layers/ber_codec.py +++ /dev/null @@ -1,275 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -BER codec and helper coverage tests. -""" - -from typing import Any - - -def _raises(exc, func): - # type: (type, Any) -> None - try: - func() - except exc: - return - raise AssertionError("Expected %s" % exc.__name__) - - -from scapy.asn1.asn1 import ( - ASN1_Class_UNIVERSAL, - ASN1_DECODING_ERROR, - ASN1_INTEGER, - ASN1_Object, -) -from scapy.asn1.ber import ( - BER_BadTag_Decoding_Error, - BER_Decoding_Error, - BER_Encoding_Error, - BER_Exception, - BER_id_dec, - BER_id_enc, - BER_len_dec, - BER_len_enc, - BER_num_dec, - BER_num_enc, - BER_tagging_dec, - BER_tagging_enc, - BERcodec_BIT_STRING, - BERcodec_INTEGER, - BERcodec_IPADDRESS, - BERcodec_NULL, - BERcodec_Object, - BERcodec_OID, - BERcodec_SEQUENCE, - BERcodec_SET, - BERcodec_STRING, -) -from scapy.config import conf - - -def check_ber_error_str(): - # type: () -> None - obj = ASN1_INTEGER(1) - enc_err = BER_Encoding_Error("enc", encoded=obj, remaining=b"rest") - assert "Already encoded" in str(enc_err) - enc_err2 = BER_Encoding_Error("enc", encoded="raw", remaining=b"") - assert "raw" in str(enc_err2) - - dec_err = BER_Decoding_Error("dec", decoded=obj, remaining=b"tail") - assert "Already decoded" in str(dec_err) - dec_err2 = BER_Decoding_Error("dec", decoded=[1], remaining=b"") - assert "[1]" in str(dec_err2) - - -def check_ber_len_enc_dec(): - # type: () -> None - for value in [0, 1, 127, 128, 999]: - encoded = BER_len_enc(value) - length, remain = BER_len_dec(encoded) - assert length == value - assert remain == b"" - - assert BER_len_enc(45, size=None) == BER_len_enc(45, size=0) - assert BER_len_enc(45, size=4) == b"\x84\x00\x00\x00-" - - _raises(BER_Exception, lambda: BER_len_enc(0, size=128)) - - _raises(BER_Decoding_Error, lambda: BER_len_dec(b"\x82")) - - -def check_ber_num_enc_dec(): - # type: () -> None - for value in [0, 1, 127, 256, 16384]: - encoded = BER_num_enc(value) - decoded, remain = BER_num_dec(encoded) - assert decoded == value - assert remain == b"" - - _raises(BER_Decoding_Error, lambda: BER_num_dec(b"")) - - _raises(BER_Decoding_Error, lambda: BER_num_dec(b"\x80\x80")) - - -def check_ber_id_enc_dec(): - # type: () -> None - for tag in [0x02, 0x30, 0x81, 0xA0]: - encoded = BER_id_enc(tag) - decoded, remain = BER_id_dec(encoded) - assert decoded == tag - assert remain == b"" - - high_tag = (0x03 << 5) + 0x22 - encoded = BER_id_enc(high_tag) - decoded, remain = BER_id_dec(encoded) - assert decoded == high_tag - assert remain == b"" - - -def check_ber_tagging(): - # type: () -> None - inner = BERcodec_INTEGER.enc(7) - implicit = BER_tagging_enc(inner, implicit_tag=0xA0) - assert implicit.startswith(b"\xa0") - real_tag, payload = BER_tagging_dec( - implicit, - hidden_tag=ASN1_Class_UNIVERSAL.INTEGER, - implicit_tag=0xA0, - ) - assert real_tag is None - assert payload[0] == int(ASN1_Class_UNIVERSAL.INTEGER) - - conf.ASN1_default_long_size = 4 - try: - explicit = BER_tagging_enc(inner, explicit_tag=0xA1) - assert explicit.startswith(b"\xa1\x84") - real_tag, payload = BER_tagging_dec( - explicit, - explicit_tag=0xA1, - ) - assert real_tag is None - assert payload == inner - finally: - conf.ASN1_default_long_size = 0 - - _raises(BER_Decoding_Error, lambda: BER_tagging_dec( - implicit, - hidden_tag=ASN1_Class_UNIVERSAL.INTEGER, - implicit_tag=0xA1, - )) - - safe_tag, _ = BER_tagging_dec( - implicit, - hidden_tag=ASN1_Class_UNIVERSAL.INTEGER, - implicit_tag=0xA1, - safe=True, - ) - assert safe_tag == 0xA0 - - -def check_ber_integer(): - # type: () -> None - for value in [0, 1, 127, 128, 255, -1, -128, -129]: - encoded = BERcodec_INTEGER.enc(value) - obj, remain = BERcodec_INTEGER.do_dec(encoded) - assert obj.val == value - assert remain == b"" - - _raises(BER_BadTag_Decoding_Error, lambda: BERcodec_INTEGER.do_dec(BERcodec_STRING.enc(b"x"))) - - _raises(BER_Decoding_Error, lambda: BERcodec_INTEGER.check_type_get_len(b"\x02")) - - -def check_ber_bit_string(): - # type: () -> None - encoded = BERcodec_BIT_STRING.enc("1011") - obj, remain = BERcodec_BIT_STRING.do_dec(encoded) - assert obj.val == "1011" - assert remain == b"" - - padded = BERcodec_BIT_STRING.enc("10110000") - obj2, _ = BERcodec_BIT_STRING.do_dec(padded) - assert obj2.val == "10110000" - - _raises(BER_Decoding_Error, lambda: BERcodec_BIT_STRING.do_dec(b"\x03\x01\x08", safe=True)) - - _raises(BER_Decoding_Error, lambda: BERcodec_BIT_STRING.do_dec(b"\x03\x00")) - - -def check_ber_string_and_null(): - # type: () -> None - encoded = BERcodec_STRING.enc(b"hello") - obj, remain = BERcodec_STRING.do_dec(encoded) - assert obj.val == b"hello" - assert remain == b"" - - null = BERcodec_NULL.enc(0) - assert null == b"\x05\x00" - obj, remain = BERcodec_NULL.do_dec(null) - assert obj.val == 0 - - non_null = BERcodec_NULL.enc(42) - obj, remain = BERcodec_NULL.do_dec(non_null) - assert obj.val == 42 - - -def check_ber_oid(): - # type: () -> None - encoded = BERcodec_OID.enc("1.2.840.113556.1.4.529") - obj, remain = BERcodec_OID.do_dec(encoded) - assert obj.val == "1.2.840.113556.1.4.529" - assert remain == b"" - - empty, remain = BERcodec_OID.do_dec(BERcodec_OID.enc("")) - assert empty.val == "" - assert remain == b"" - - -def check_ber_sequence_and_set(): - # type: () -> None - payload = BERcodec_INTEGER.enc(1) + BERcodec_INTEGER.enc(2) - seq = BERcodec_SEQUENCE.enc(payload) - obj, remain = BERcodec_SEQUENCE.do_dec(seq) - assert len(obj.val) == 2 - assert obj.val[0].val == 1 - assert obj.val[1].val == 2 - assert remain == b"" - - as_list = BERcodec_SEQUENCE.enc([ASN1_INTEGER(3), ASN1_INTEGER(4)]) - obj2, remain2 = BERcodec_SEQUENCE.do_dec(as_list) - assert [x.val for x in obj2.val] == [3, 4] - assert remain2 == b"" - - st = BERcodec_SET.enc(payload) - obj3, remain3 = BERcodec_SET.do_dec(st) - assert len(obj3.val) == 2 - assert remain3 == b"" - - conf.ASN1_default_long_size = 4 - try: - long_seq = BERcodec_SEQUENCE.enc(payload) - assert long_seq.startswith(b"0\x84") - finally: - conf.ASN1_default_long_size = 0 - - _raises(BER_Decoding_Error, lambda: BERcodec_SEQUENCE.do_dec(b"\x30\x05" + BERcodec_INTEGER.enc(1))) - - -def check_ber_ipaddress(): - # type: () -> None - encoded = BERcodec_IPADDRESS.enc("192.168.0.1") - obj, remain = BERcodec_IPADDRESS.do_dec(encoded) - assert obj.val == "192.168.0.1" - assert remain == b"" - - _raises(BER_Encoding_Error, lambda: BERcodec_IPADDRESS.enc("not-an-ip")) - - _raises(BER_Decoding_Error, lambda: BERcodec_IPADDRESS.do_dec(BERcodec_STRING.enc(b"bad"))) - - -def check_ber_object_dispatch(): - # type: () -> None - encoded = BERcodec_INTEGER.enc(99) - obj, remain = BERcodec_Object.do_dec(encoded) - assert obj.val == 99 - assert remain == b"" - - _raises(BER_Decoding_Error, lambda: BERcodec_Object.check_string(b"")) - - _raises(BER_Decoding_Error, lambda: BERcodec_Object.do_dec(b"\xff\x00")) - - bad, remain = BERcodec_Object.safedec(b"\x02\x01\x01") - assert isinstance(bad, ASN1_INTEGER) - assert bad.val == 1 - - unknown, remain = BERcodec_Object.safedec(b"\xff\x00") - assert isinstance(unknown, ASN1_DECODING_ERROR) - - truncated, remain = BERcodec_Object.dec(b"\x02\x05\x01", safe=True) - assert isinstance(truncated, ASN1_DECODING_ERROR) - assert remain == b"" - - _raises(TypeError, lambda: BERcodec_Object.enc(object())) - assert BERcodec_Object.enc("42") == BERcodec_STRING.enc("42") diff --git a/test/scapy/layers/ber_packets.py b/test/scapy/layers/ber_packets.py deleted file mode 100644 index 09cb02fe6f1..00000000000 --- a/test/scapy/layers/ber_packets.py +++ /dev/null @@ -1,184 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -BER ASN1_Packet and ASN1F_field build tests. -""" - -from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING -from scapy.asn1fields import ( - ASN1F_BOOLEAN, - ASN1F_CHOICE, - ASN1F_INTEGER, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, - ASN1F_STRING, - ASN1F_optional, -) -from scapy.asn1packet import ASN1_Packet -from scapy.packet import raw - - -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)) - - -def check_ber_field_explicit_tag(): - # type: () -> None - pkt = BERTaggedInteger(n=5) - assert raw(pkt) == b"\xa1\x03\x02\x01\x05" - decoded = _roundtrip(BERTaggedInteger, pkt) - assert decoded.n.val == 5 - - -def check_ber_field_fixed_size(): - # type: () -> None - pkt = BERFixedFields(n=200, s=b"ABC") - assert raw(pkt) == bytes.fromhex("300d02810200c80483000003414243") - decoded = _roundtrip(BERFixedFields, pkt) - assert decoded.n.val == 200 - assert decoded.s.val == b"ABC" - - -def check_ber_field_optional(): - # type: () -> None - present = BEROptionalField(id=1, extra=7) - assert raw(present) == bytes.fromhex("3008020101a003020107") - decoded = _roundtrip(BEROptionalField, present) - assert decoded.id.val == 1 - assert decoded.extra.val == 7 - - absent = BEROptionalField(id=1, extra=None) - assert raw(absent) == bytes.fromhex("3003020101") - decoded = _roundtrip(BEROptionalField, absent) - assert decoded.id.val == 1 - assert decoded.extra is None - - -def check_ber_optional_sequence_is_empty(): - # type: () -> None - """Optional ASN1F_SEQUENCE must use the wrapped field's is_empty(). - - SEQUENCE stores children under their own names (not dummy_seq_name), so - inspecting pkt.dummy_seq_name incorrectly reports present children as empty - and makes the parent SEQUENCE look empty. - """ - opt = BEROptionalSequence.ASN1_root.seq[1] - - present = BEROptionalSequence(hdr=1, id=42, label=b"abc") - assert opt._field.is_empty(present) is False - assert opt.is_empty(present) is False - assert BEROptionalSequence.ASN1_root.is_empty(present) is False - assert raw(present) == bytes.fromhex("300f020101a00a300802012a0403616263") - - absent = BEROptionalSequence(hdr=1, id=None, label=None) - assert opt._field.is_empty(absent) is True - assert opt.is_empty(absent) is True - assert raw(absent) == bytes.fromhex("3003020101") - - -def check_ber_field_sequence_of(): - # type: () -> None - pkt = BERSequenceOfIntegers(values=[1, 2, 3]) - assert raw(pkt) == b"\x30\x09\x02\x01\x01\x02\x01\x02\x02\x01\x03" - decoded = _roundtrip(BERSequenceOfIntegers, pkt) - assert [x.val for x in decoded.values] == [1, 2, 3] - - -def check_ber_field_choice(): - # type: () -> None - as_int = BERChoiceField(c=ASN1_INTEGER(99)) - assert raw(as_int) == b"\x02\x01c" - decoded = _roundtrip(BERChoiceField, as_int) - assert decoded.c.val == 99 - - as_str = BERChoiceField(c=ASN1_STRING("x")) - assert raw(as_str) == b"\x04\x01x" - decoded = _roundtrip(BERChoiceField, as_str) - assert decoded.c.val == b"x" - - -def check_ber_packet_record(): - # type: () -> None - pkt = BERRecord( - id=42, flag=True, label="hi", extra=7, values=[1, 2, 3], - ) - expected = bytes.fromhex( - "301a02012a01010104026869" - "a003020107" - "3009020101020102020103" - ) - assert raw(pkt) == expected - 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] - - empty = BERRecord(id=1, flag=False, label="", extra=None, values=[]) - assert raw(empty) == bytes.fromhex("300a02010101010004003000") - decoded = _roundtrip(BERRecord, 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] == [] diff --git a/test/scapy/layers/oer.uts b/test/scapy/layers/oer.uts new file mode 100644 index 00000000000..fe690a05ccd --- /dev/null +++ b/test/scapy/layers/oer.uts @@ -0,0 +1,862 @@ +% Tests for ASN.1 OER encoding + +# +# Try me with: +# bash test/run_tests -t test/scapy/layers/oer.uts -F + ++ ASN.1 OER load += import contrib codecs +import scapy.contrib.oer +from scapy.contrib.oer import * +from scapy.packet import raw + + ++ 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) == 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), 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 explicit null tagging +OER_tagging_enc(OERcodec_NULL.enc(None), explicit_tag=0x81) == b"\x81" += OER choice id decode +tag, r = OER_id_dec(b"\x81\x01") +tag == 0x81 and r == b"\x01" + ++ ASN.1 OER packets, interop and fuzz += import contrib codecs +import scapy.contrib.oer +from scapy.contrib.oer import * +from scapy.packet import raw += prepare helpers and packet classes +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", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\x80"), + ("G", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x00\x80"), + ("G", 1000, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x03\xe8"), + ("H", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=4), b"\x00\x00\x00\x80"), + ( + "I", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=8), + 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,) += oer field explicit tag +pkt = OERTaggedInteger(n=5) + +assert raw(pkt) == b"\xa1\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) + +assert raw(present) == b"\x01\x01\xa0\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"\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"\x01*\xff\x02hi\xa0\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"\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 += import contrib codecs +import scapy.contrib.oer +from scapy.contrib.oer import * +from scapy.packet import raw += prepare helpers and packet classes +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)) + +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)) += 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, "a10105") + +assert tagged.n.val == 5 + +fixed = _dissect(OERFixedFields, "c8414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(OEROptionalField, "0101a00107") + +assert present.id.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(OEROptionalField, "0101") + +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, + "012aff026869a00107" + "0103010101020103", +) +_assert_record(decoded) +empty = _dissect(OERRecord, "010100000100") +_assert_record_empty(empty) + +True + + ++ ASN.1 OER coverage += import contrib codecs +import scapy.contrib.oer +from scapy.contrib.oer import * +from scapy.packet import raw += prepare helpers and packet classes +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__) += 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 + diff --git a/test/scapy/layers/oer_fuzz.py b/test/scapy/layers/oer_fuzz.py deleted file mode 100644 index 920e51e8abd..00000000000 --- a/test/scapy/layers/oer_fuzz.py +++ /dev/null @@ -1,106 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -OER fuzzing helpers. - -Exercise OER encode/decode paths with packet.fuzz() and random payloads. -""" - -import os -import random -from typing import Iterable, Type - -from scapy.asn1.asn1 import ASN1_Codecs, ASN1_Decoding_Error, ASN1_Error -from scapy.contrib.oer import ( - OER_Decoding_Error, - OERcodec_BIT_STRING, - OERcodec_BOOLEAN, - OERcodec_ENUMERATED, - OERcodec_INTEGER, - OERcodec_NULL, - OERcodec_OID, - OERcodec_STRING, -) -from scapy.asn1fields import ( - ASN1F_BOOLEAN, - ASN1F_INTEGER, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, - ASN1F_STRING, - ASN1F_optional, -) -from scapy.asn1packet import ASN1_Packet -from scapy.packet import fuzz, raw - -_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 check_oer_fuzz_encode(iterations=25): - # type: (int) -> None - for cls in _fuzz_packets(): - for _ in range(iterations): - data = raw(fuzz(cls())) - assert isinstance(data, bytes) - - -def check_oer_fuzz_roundtrip(iterations=25): - # type: (int) -> None - for cls in _fuzz_packets(): - for _ in range(iterations): - cls(raw(fuzz(cls()))) - - -def check_oer_fuzz_codec_decode(iterations=100): - # type: (int) -> None - 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 - - -def check_oer_fuzz_packet_decode(iterations=100): - # type: (int) -> None - for cls in _fuzz_packets(): - for _ in range(iterations): - data = os.urandom(random.randint(0, 128)) - try: - cls(data) - except _DECODE_ERRORS: - pass diff --git a/test/scapy/layers/oer_iop.py b/test/scapy/layers/oer_iop.py deleted file mode 100644 index baa68a5b534..00000000000 --- a/test/scapy/layers/oer_iop.py +++ /dev/null @@ -1,160 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -OER interoperability helpers. - -Cross-check Scapy's OER codec against reference encodings (from asn1tools). -Reference vectors are taken from asn1tools/tests/test_oer.py. -""" - -from scapy.contrib.oer import ( - OERcodec_BIT_STRING, - OERcodec_BOOLEAN, - OERcodec_ENUMERATED, - OERcodec_INTEGER, - OERcodec_NULL, - OERcodec_OID, - OERcodec_STRING, - OER_signed_integer_enc, - OER_unsigned_integer_enc, -) - -# (type name, value, scapy encoder callable, reference encoding) -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", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\x80"), - ("G", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x00\x80"), - ("G", 1000, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x03\xe8"), - ("H", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=4), b"\x00\x00\x00\x80"), - ( - "I", - 128, - lambda v: OERcodec_INTEGER.enc(v, size_len=8), - 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"), -] - -# (type name, value, reference encoding) -SCAPY_DECODE_VECTORS = [ - ("A", 42, b"\x01*"), - ("F", 200, b"\xc8"), - ("B", -99, b"\x9d"), -] - - -def check_primitive_interop(): - # type: () -> bool - """Compare Scapy OER primitives against reference encodings.""" - 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 - - return True - - -def check_scapy_encode_reference_decode(): - # type: () -> bool - """Decode reference encodings with Scapy.""" - 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 - - return True diff --git a/test/scapy/layers/oer_packets.py b/test/scapy/layers/oer_packets.py deleted file mode 100644 index 7260609a42a..00000000000 --- a/test/scapy/layers/oer_packets.py +++ /dev/null @@ -1,209 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -OER ASN1_Packet and ASN1F_field tests. -""" -import scapy.contrib.oer # noqa: F401 # register OER stem - -from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING -from scapy.asn1fields import ( - ASN1F_BOOLEAN, - ASN1F_CHOICE, - ASN1F_INTEGER, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, - ASN1F_STRING, - ASN1F_optional, -) -from scapy.asn1packet import ASN1_Packet -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)) - - -def check_oer_field_explicit_tag(): - # type: () -> None - pkt = OERTaggedInteger(n=5) - assert raw(pkt) == b"\xa1\x01\x05" - decoded = _roundtrip(OERTaggedInteger, pkt) - assert decoded.n.val == 5 - - -def check_oer_field_fixed_size(): - # type: () -> None - 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" - - -def check_oer_field_optional(): - # type: () -> None - present = OEROptionalField(id=1, extra=7) - assert raw(present) == b"\x01\x01\xa0\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"\x01\x01" - decoded = _roundtrip(OEROptionalField, absent) - assert decoded.id.val == 1 - assert decoded.extra is None - - -def check_oer_field_sequence_of(): - # type: () -> None - 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] - - -def check_oer_field_choice(): - # type: () -> None - 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" - - -def check_oer_packet_record(): - # type: () -> None - pkt = OERRecord( - id=42, flag=True, label="hi", extra=7, values=[1, 2, 3], - ) - expected = ( - b"\x01*\xff\x02hi\xa0\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"\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] == [] - - -def check_oer_nested_sequence(): - # type: () -> None - 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 - - -def check_oer_nested_sequence_trailing(): - # type: () -> None - 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 - - -def check_oer_sequence_of_with_trailing(): - # type: () -> None - 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 diff --git a/test/scapy/layers/uper.uts b/test/scapy/layers/uper.uts new file mode 100644 index 00000000000..6b962e4dacb --- /dev/null +++ b/test/scapy/layers/uper.uts @@ -0,0 +1,2829 @@ +% Tests for ASN.1 UPER encoding + +# +# Try me with: +# bash test/run_tests -t test/scapy/layers/uper.uts -F + ++ ASN.1 UPER load += import contrib codecs +import scapy.contrib.uper +from scapy.contrib.uper import * +from scapy.packet import raw + + ++ 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 += import contrib codecs +import scapy.contrib.uper +from scapy.contrib.uper import * +from scapy.packet import raw += prepare helpers and packet classes +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(index, 2, enc=enc) + 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(index, 2, enc=enc) + 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(0, 5, enc=enc) + 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) += 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"), + (16384, b"\xc1"), +]: + enc = UPER_Encoder() + enc.append_length_determinant(length) + assert enc.as_bytes() == expected + +True + += uper count roundtrip +for count in [0, 1, 3, 127]: + enc = UPER_Encoder() + UPER_count_enc(count, enc=enc) + got, _ = UPER_count_dec(enc.as_bytes()) + assert got == count + +True + += uper choice index roundtrip +for index, choices in [(0, 2), (1, 5), (3, 5)]: + enc = UPER_Encoder() + UPER_choice_index_enc(index, choices, enc=enc) + got, _ = UPER_choice_index_dec(enc.as_bytes(), choices) + assert got == index + +True + += uper optional presence +enc = UPER_Encoder() + +UPER_optional_presence_enc([0, 1, 0], enc=enc) + +assert enc.as_bytes() == b"\x40" + +True + += uper constrained integer +data = UPER_constrained_int_enc(10, 0, 15) + +value, remain = UPER_constrained_int_dec(data, 0, 15) + +assert value == 10 + +assert remain == b"" + +True + += uper constrained signed integer +for value, expected in [(0, b"\x80"), (-1, b"\x7f"), (127, b"\xff"), (-128, b"\x00")]: + data = UPER_constrained_int_enc(value, -128, 127) + assert data == expected + decoded, remain = UPER_constrained_int_dec(data, -128, 127) + assert decoded == value + assert remain == b"" + +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), +]: + encoded = UPER_octet_string_enc(data, minimum, maximum) + dec = UPER_Decoder(encoded) + decoded, _ = UPER_octet_string_dec(encoded, minimum, maximum, dec=dec) + assert decoded == 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 join encodings +a = UPERcodec_INTEGER.enc(1) + +b = UPERcodec_INTEGER.enc(2) + +joined = UPER_join_encodings(a, b) + +dec = UPER_Decoder(joined) + +assert dec.read_unconstrained_whole_number() == 1 + +assert dec.read_unconstrained_whole_number() == 2 + +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 += import contrib codecs +import scapy.contrib.uper +from scapy.contrib.uper import * +from scapy.packet import raw += prepare helpers and packet classes +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)) += 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 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 += import contrib codecs +import scapy.contrib.uper +from scapy.contrib.uper import * +from scapy.packet import raw +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 += prepare helpers and packet classes +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__) += 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 +enc = UPER_Encoder() + +assert enc.append_length_determinant(32768) == 32768 + +assert enc.as_bytes() == b"\xc2" + +enc = UPER_Encoder() + +assert enc.append_length_determinant(49152) == 49152 + +assert enc.as_bytes() == b"\xc3" + +enc = UPER_Encoder() + +assert enc.append_length_determinant(65535) == 49152 + +assert enc.as_bytes() == b"\xc3" + +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 sequence errors +_raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.enc([ASN1_INTEGER(1)])) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_SEQUENCE.do_dec(b"\x00")) + +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 += 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 +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 += prepare helpers and packet classes +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__) += 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._uper_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 + diff --git a/test/scapy/layers/uper_asn1scc_iop.py b/test/scapy/layers/uper_asn1scc_iop.py deleted file mode 100644 index 411d23d9460..00000000000 --- a/test/scapy/layers/uper_asn1scc_iop.py +++ /dev/null @@ -1,190 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -UPER interoperability vectors from ESA asn1scc test cases. - -asn1scc (https://github.com/esa/asn1scc) primarily validates C/Ada code generation -with ACN custom encodings. Portable uPER vectors are taken from v4Tests where -``--TCLS MyPDU[]`` selects standard uPER (empty ACN = default PER). - -Cases that need REAL, explicit APPLICATION tags, or ACN overrides are not -compared against Scapy encoders here (or are reference-only). -""" - -from scapy.contrib.uper import ( - UPER_Encoder, - UPER_choice_index_enc, - UPERcodec_BIT_STRING, - UPERcodec_BOOLEAN, - UPERcodec_ENUMERATED, - UPERcodec_INTEGER, - UPERcodec_NULL, - UPERcodec_STRING, -) - -# asn1scc v4Tests/test-cases/acn/05-BOOLEAN/001.asn1 -BOOLEAN_SPEC = ( - "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " - "MyPDU ::= BOOLEAN " - "END" -) - -# asn1scc v4Tests/test-cases/acn/18-NULL/001.asn1 -NULL_SPEC = ( - "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " - "MyPDU ::= NULL " - "END" -) - -# asn1scc v4Tests/test-cases/acn/06-OCTET-STRING/001.asn1 -OCTET_STRING_VAR_SPEC = ( - "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " - "MyPDU ::= OCTET STRING (SIZE(1..20)) " - "END" -) - -# asn1scc v4Tests/test-cases/acn/09-CHOICE/001.asn1 (pdu1 = int1 : 10) -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" -) - -# asn1scc v4Tests/test-cases/acn/04-ENUMERATED/001.asn1 (pdu1 = beta) -ENUMERATED_SPEC = ( - "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " - "MyPDU ::= ENUMERATED { alpha(1), beta(200) } " - "END" -) - -# asn1scc v4Tests/test-cases/acn/08-BIT-STRING/001.asn1 (pdu1 = 'ABCD'H) -BIT_STRING_VAR_SPEC = ( - "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " - "MyPDU ::= BIT STRING (SIZE(1..20)) " - "END" -) - -# asn1scc README.md sample.asn (REAL field; reference only) -README_MESSAGE_HEX = ( - "010101020980cd191eb851eb851f48656c6c6f576f726c6480" -) - -README_MESSAGE_PREFIX_HEX = ( - "0101010248656c6c6f576f726c6480" -) - -# (name, pdu value, encoder callable, reference encoding) -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(0, 5, enc=enc) - UPERcodec_INTEGER.encode_into(enc, 10, uper_min=0, uper_max=15) - return enc.as_bytes() - - -def check_asn1scc_vectors(): - # type: () -> None - for name, _value, encoder, expected in ASN1SCC_VECTORS: - got = encoder(_value) - assert got == expected, ( - "%s: expected %s, got %s" % - (name, expected.hex(), got.hex()) - ) - - -def check_asn1scc_readme_message_prefix(): - # type: () -> None - """README sample without REAL; Scapy packet roundtrip vs reference.""" - from test.scapy.layers.uper_packets import UPERMessagePrefix - 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 - - -def check_asn1scc_readme_message_reference(): - # type: () -> None - """README C sample output; Scapy does not encode REAL in UPER yet.""" - assert README_MESSAGE_HEX == ( - "010101020980cd191eb851eb851f48656c6c6f576f726c6480" - ) diff --git a/test/scapy/layers/uper_codec.py b/test/scapy/layers/uper_codec.py deleted file mode 100644 index 180503fb06e..00000000000 --- a/test/scapy/layers/uper_codec.py +++ /dev/null @@ -1,174 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -UPER primitive codec roundtrip and decode interoperability tests. -""" - -from typing import Any, Dict, Tuple, Type - -from scapy.contrib.uper import ( - UPERcodec_BIT_STRING, - UPERcodec_BOOLEAN, - UPERcodec_ENUMERATED, - UPERcodec_INTEGER, - UPERcodec_NULL, - UPERcodec_OID, - UPERcodec_STRING, -) - -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 - - -def check_uper_codec_roundtrips(): - # type: () -> None - for codec, value, kwargs, expected in CODEC_ROUNDTRIPS: - _assert_codec_roundtrip(codec, value, kwargs, expected) - - -def check_uper_codec_oid_roundtrip(): - # type: () -> None - 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 - - -def check_uper_codec_oid_encode_interop(): - # type: () -> None - 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()) - ) - - -def check_uper_codec_reference_decode(): - # type: () -> None - 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) - ) - - -def check_uper_codec_encode_reference(): - # type: () -> None - from test.scapy.layers.uper_iop import PRIMITIVE_VECTORS - - 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()) - ) diff --git a/test/scapy/layers/uper_fuzz.py b/test/scapy/layers/uper_fuzz.py deleted file mode 100644 index d0aa8571192..00000000000 --- a/test/scapy/layers/uper_fuzz.py +++ /dev/null @@ -1,133 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -UPER fuzzing helpers. - -Exercise UPER encode/decode paths with packet.fuzz() and random payloads. -""" - -import os -import random -from typing import Iterable, Type - -from scapy.asn1.asn1 import ASN1_Codecs, ASN1_Decoding_Error, ASN1_Error -from scapy.contrib.uper import ( - UPER_Decoding_Error, - UPER_Encoding_Error, - UPERcodec_BIT_STRING, - UPERcodec_BOOLEAN, - UPERcodec_ENUMERATED, - UPERcodec_INTEGER, - UPERcodec_NULL, - UPERcodec_OID, - UPERcodec_STRING, -) -from scapy.asn1fields import ( - ASN1F_BOOLEAN, - ASN1F_ENUMERATED, - ASN1F_INTEGER, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, - ASN1F_STRING, - ASN1F_optional, -) -from scapy.asn1packet import ASN1_Packet -from scapy.packet import fuzz, raw - -_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 check_uper_fuzz_encode(iterations=25): - # type: (int) -> None - for cls in _fuzz_packets(): - for _ in range(iterations): - try: - data = raw(fuzz(cls())) - except _DECODE_ERRORS: - continue - assert isinstance(data, bytes) - - -def check_uper_fuzz_roundtrip(iterations=25): - # type: (int) -> None - for cls in _fuzz_packets(): - for _ in range(iterations): - try: - cls(raw(fuzz(cls()))) - except _DECODE_ERRORS: - pass - - -def check_uper_fuzz_codec_decode(iterations=100): - # type: (int) -> None - 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 - - -def check_uper_fuzz_packet_decode(iterations=100): - # type: (int) -> None - for cls in _fuzz_packets(): - for _ in range(iterations): - data = os.urandom(random.randint(0, 128)) - try: - cls(data) - except _DECODE_ERRORS: - pass diff --git a/test/scapy/layers/uper_helpers.py b/test/scapy/layers/uper_helpers.py deleted file mode 100644 index dc92ef18e68..00000000000 --- a/test/scapy/layers/uper_helpers.py +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -UPER low-level helper and bitstream tests. -""" - -from scapy.contrib.uper import ( - UPER_Decoder, - UPER_Encoder, - UPER_choice_index_dec, - UPER_choice_index_enc, - UPER_constrained_int_dec, - UPER_constrained_int_enc, - UPER_count_dec, - UPER_count_enc, - UPER_has_unexpected_remainder, - UPER_join_encodings, - UPER_octet_string_dec, - UPER_octet_string_enc, - UPER_optional_presence_enc, - UPERcodec_INTEGER, -) - - -def check_uper_length_determinant(): - # type: () -> None - for length, expected in [ - (0, b"\x00"), - (1, b"\x01"), - (127, b"\x7f"), - (128, b"\x80\x80"), - (16383, b"\xbf\xff"), - (16384, b"\xc1"), - ]: - enc = UPER_Encoder() - enc.append_length_determinant(length) - assert enc.as_bytes() == expected - - -def check_uper_count_roundtrip(): - # type: () -> None - for count in [0, 1, 3, 127]: - enc = UPER_Encoder() - UPER_count_enc(count, enc=enc) - got, _ = UPER_count_dec(enc.as_bytes()) - assert got == count - - -def check_uper_choice_index_roundtrip(): - # type: () -> None - for index, choices in [(0, 2), (1, 5), (3, 5)]: - enc = UPER_Encoder() - UPER_choice_index_enc(index, choices, enc=enc) - got, _ = UPER_choice_index_dec(enc.as_bytes(), choices) - assert got == index - - -def check_uper_optional_presence(): - # type: () -> None - enc = UPER_Encoder() - UPER_optional_presence_enc([0, 1, 0], enc=enc) - assert enc.as_bytes() == b"\x40" - - -def check_uper_constrained_integer(): - # type: () -> None - data = UPER_constrained_int_enc(10, 0, 15) - value, remain = UPER_constrained_int_dec(data, 0, 15) - assert value == 10 - assert remain == b"" - - -def check_uper_constrained_signed_integer(): - # type: () -> None - for value, expected in [(0, b"\x80"), (-1, b"\x7f"), (127, b"\xff"), (-128, b"\x00")]: - data = UPER_constrained_int_enc(value, -128, 127) - assert data == expected - decoded, remain = UPER_constrained_int_dec(data, -128, 127) - assert decoded == value - assert remain == b"" - - -def check_uper_octet_string_roundtrip(): - # type: () -> None - for data, minimum, maximum in [ - (b"AB", None, None), - (b"\x12\x34\x56", 3, 3), - (bytes.fromhex("afbc4583"), 1, 20), - ]: - encoded = UPER_octet_string_enc(data, minimum, maximum) - dec = UPER_Decoder(encoded) - decoded, _ = UPER_octet_string_dec(encoded, minimum, maximum, dec=dec) - assert decoded == data - assert not UPER_has_unexpected_remainder(dec) - - -def check_uper_has_unexpected_remainder(): - # type: () -> None - assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x00")) is False - assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x80")) is True - - -def check_uper_join_encodings(): - # type: () -> None - a = UPERcodec_INTEGER.enc(1) - b = UPERcodec_INTEGER.enc(2) - joined = UPER_join_encodings(a, b) - dec = UPER_Decoder(joined) - assert dec.read_unconstrained_whole_number() == 1 - assert dec.read_unconstrained_whole_number() == 2 - - -def check_uper_chained_encode_into(): - # type: () -> None - 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 diff --git a/test/scapy/layers/uper_iop.py b/test/scapy/layers/uper_iop.py deleted file mode 100644 index af36a6dda7e..00000000000 --- a/test/scapy/layers/uper_iop.py +++ /dev/null @@ -1,189 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -UPER interoperability helpers. - -Cross-check Scapy's UPER codec against reference encodings (from asn1tools). -""" - -from typing import Any - -from scapy.contrib.uper import ( - UPERcodec_BOOLEAN, - UPERcodec_ENUMERATED, - UPERcodec_INTEGER, - UPERcodec_NULL, - UPERcodec_STRING, - UPER_Encoder, - UPER_choice_index_enc, -) -from scapy.packet import raw - -from test.scapy.layers.uper_packets import ( - UPERMultiOptional, - UPERNestedSequence, -) - -# (type name, value, scapy encoder callable, reference encoding) -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", - ), -] - -# (type name, value, reference encoding) -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 check_primitive_interop(): - # type: () -> None - 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()) - ) - - -def check_composite_interop(): - # type: () -> None - 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()) - ) - - -def check_packet_reference_interop(): - # type: () -> None - 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 - - -def check_packet_decode_vectors(): - # type: () -> None - 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 - - -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(index, 2, enc=enc) - 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(index, 2, enc=enc) - 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) diff --git a/test/scapy/layers/uper_packets.py b/test/scapy/layers/uper_packets.py deleted file mode 100644 index 0dec76a9002..00000000000 --- a/test/scapy/layers/uper_packets.py +++ /dev/null @@ -1,543 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# This file is part of Scapy -# See https://scapy.net/ for more information - -""" -UPER ASN1_Packet and ASN1F_field tests. -""" -import scapy.contrib.uper # noqa: F401 # register UPER stem - -from scapy.asn1.asn1 import ASN1_Codecs, ASN1_INTEGER, ASN1_STRING -from scapy.asn1fields import ( - ASN1F_BIT_STRING, - ASN1F_BOOLEAN, - ASN1F_CHOICE, - ASN1F_ENUMERATED, - ASN1F_INTEGER, - ASN1F_NULL, - ASN1F_SEQUENCE, - ASN1F_SEQUENCE_OF, - ASN1F_STRING, - ASN1F_optional, -) -from scapy.asn1packet import ASN1_Packet -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)) - - -def check_uper_field_fixed_size(): - # type: () -> None - 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" - - -def check_uper_field_integer(): - # type: () -> None - pkt = UPERIntegerField(n=12345) - assert raw(pkt) == bytes.fromhex("023039") - decoded = _roundtrip(UPERIntegerField, pkt) - assert decoded.n.val == 12345 - - -def check_uper_field_boolean(): - # type: () -> None - 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 - - -def check_uper_field_string(): - # type: () -> None - pkt = UPERStringField(s=b"hi") - assert raw(pkt) == bytes.fromhex("026869") - decoded = _roundtrip(UPERStringField, pkt) - assert decoded.s.val == b"hi" - - -def check_uper_field_constrained_integer(): - # type: () -> None - pkt = UPERConstrainedInteger(n=200) - assert raw(pkt) == b"\xc8" - decoded = _roundtrip(UPERConstrainedInteger, pkt) - assert decoded.n.val == 200 - - -def check_uper_field_optional(): - # type: () -> None - 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 - - -def check_uper_field_sequence_of(): - # type: () -> None - 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] == [] - - -def check_uper_field_choice(): - # type: () -> None - 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" - - -def check_uper_field_choice_definition_order(): - # type: () -> None - 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 - - -def check_uper_packet_record(): - # type: () -> None - 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] == [] - - -def check_uper_field_enumerated(): - # type: () -> None - 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 - - -def check_uper_field_bit_string(): - # type: () -> None - 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" - - -def check_uper_message_prefix(): - # type: () -> None - 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 - - -def check_uper_sequence_with_choice(): - # type: () -> None - 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" - - -def check_uper_null_packet(): - # type: () -> None - pkt = UPERNullPacket() - assert raw(pkt) == b"" - decoded = _roundtrip(UPERNullPacket, pkt) - assert decoded.n is None - - -def check_uper_variable_octet_string(): - # type: () -> None - pkt = UPERVariableOctetString(data=bytes.fromhex("afbc4583")) - assert raw(pkt) == bytes.fromhex("1d7de22c18") - decoded = _roundtrip(UPERVariableOctetString, pkt) - assert decoded.data.val == bytes.fromhex("afbc4583") - - -def check_uper_constrained_range_integer(): - # type: () -> None - pkt = UPERConstrainedRangeInt(n=10) - assert raw(pkt) == b"\xa0" - decoded = _roundtrip(UPERConstrainedRangeInt, pkt) - assert decoded.n.val == 10 - - -def check_uper_sequence_with_enumerated(): - # type: () -> None - 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 - - -def check_uper_sequence_of_strings(): - # type: () -> None - 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] == [] - - -def check_uper_sequence_choice_hex(): - # type: () -> None - """Cross-check against reference composite encoding.""" - 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 - - -def check_uper_nested_sequence(): - # type: () -> None - 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 - - -def check_uper_sequence_with_null(): - # type: () -> None - 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 - - -def check_uper_fixed_bit_string(): - # type: () -> None - 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" - - -def check_uper_sequence_of_constrained_ints(): - # type: () -> None - 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] - - -def check_uper_signed_integer(): - # type: () -> None - 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 - - -def check_uper_multi_optional(): - # type: () -> None - 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 From be6d24acd9be7f1c9801108428bfe2ca54b35fe7 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Sat, 8 Aug 2026 07:11:14 +0200 Subject: [PATCH 03/19] Cleanup asn1fields AI-Assisted: yes (Cursor) --- scapy/asn1/asn1.py | 5 + scapy/asn1fields.py | 696 ++++++++---------------------------------- scapy/contrib/oer.py | 127 ++++++++ scapy/contrib/uper.py | 491 +++++++++++++++++++++++++++++ 4 files changed, 746 insertions(+), 573 deletions(-) diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 530ce185bd9..7e4fe145c82 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -132,6 +132,11 @@ def register_tagging(cls, enc, dec): cls._tagging_enc = enc cls._tagging_dec = dec + def register_field_hooks(cls, hooks): + # type: (Any) -> None + # Optional compound-field helpers (SEQUENCE/CHOICE/…) for contrib codecs. + cls._field_hooks = hooks + def tagging_enc(cls, s, **kwargs): # type: (bytes, **Any) -> bytes return cls._tagging_enc(s, **kwargs) # type: ignore diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index d5e4f932fb3..81fd9b007cb 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -9,30 +9,14 @@ """ import copy + from functools import reduce -from typing import ( - Any, - AnyStr, - Callable, - Dict, - Generic, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, - cast, - TYPE_CHECKING, -) -from scapy import packet from scapy.asn1.asn1 import ( ASN1_BIT_STRING, ASN1_BOOLEAN, ASN1_Class, ASN1_Class_UNIVERSAL, - ASN1_Codecs, ASN1_Decoding_Error, ASN1_Error, ASN1_INTEGER, @@ -56,20 +40,26 @@ RandField, ) -if TYPE_CHECKING: - from scapy.asn1packet import ASN1_Packet - - -def _oer(): - # type: () -> Any - from scapy.contrib import oer as _m - return _m +from scapy import packet +from typing import ( + Any, + AnyStr, + Callable, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, + TYPE_CHECKING, +) -def _uper(): - # type: () -> Any - from scapy.contrib import uper as _m - return _m +if TYPE_CHECKING: + from scapy.asn1packet import ASN1_Packet class ASN1F_badsequence(Exception): @@ -102,10 +92,7 @@ def __init__(self, explicit_tag=None, # type: Optional[int] flexible_tag=False, # type: Optional[bool] size_len=None, # type: Optional[int] - oer_unsigned=False, # type: Optional[bool] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - uper_enum_values=None, # type: Optional[List[int]] + **codec_opts # type: Any ): # type: (...) -> None if context is not None: @@ -118,10 +105,11 @@ def __init__(self, else: self.default = self.ASN1_tag.asn1_object(default) # type: ignore self.size_len = size_len - self.oer_unsigned = oer_unsigned - self.uper_min = uper_min - self.uper_max = uper_max - self.uper_enum_values = uper_enum_values + # 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] + for key, val in codec_opts.items(): + setattr(self, key, val) 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" @@ -131,7 +119,6 @@ def __init__(self, # network_tag gets useful for ASN1F_CHOICE self.network_tag = int(implicit_tag or explicit_tag or self.ASN1_tag) self.owners = [] # type: List[Type[ASN1_Packet]] - self._uper_kwargs_cache = None # type: Optional[Dict[str, Any]] def register_owner(self, cls): # type: (Type[ASN1_Packet]) -> None @@ -175,25 +162,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 on every enc/dec call. - if pkt.ASN1_codec == ASN1_Codecs.PER: - return self._uper_codec_kwargs() + # BER ignores unknown keys via **_kwargs. Contrib codecs read + # constraints from field.codec_opts. kwargs = {"size_len": self.size_len} # type: Dict[str, Any] - if pkt.ASN1_codec == ASN1_Codecs.OER: - kwargs["size_len"] = self.size_len or 0 - if self.oer_unsigned: - kwargs["oer_unsigned"] = self.oer_unsigned + 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. PER/OER constraints - # must go through codec.enc(**kwargs). - if pkt.ASN1_codec == ASN1_Codecs.PER: - return False - if pkt.ASN1_codec == ASN1_Codecs.OER: - return self.size_len is None and not self.oer_unsigned - return self.size_len is None + # Contrib codecs may force codec.enc(**kwargs) via field hooks. + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None and hasattr(hooks, "use_object_enc"): + return hooks.use_object_enc(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 @@ -215,7 +196,7 @@ def _encode_item(self, pkt, item): item = item.val elif hasattr(item, "self_build"): # Packet values (e.g. ASN1F_STRING_PacketField) must still go through - # the type codec so the universal tag/length are applied. + # the BER type codec so the universal tag/length are applied. item = item.self_build() codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) return codec.enc(item, **self._codec_kwargs(pkt)) @@ -247,20 +228,6 @@ def m2i(self, pkt, s): dec = codec.safedec if self.flexible_tag else codec.dec return dec(s, context=self.context, **self._codec_kwargs(pkt)) # type: ignore - def m2i_from_decoder(self, pkt, dec): - # type: (ASN1_Packet, Any) -> _A - codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) - return cast( - _A, - codec.dec_from_decoder( # type: ignore[attr-defined] - dec, **self._codec_kwargs(pkt), - ), - ) - - def dissect_from_decoder(self, pkt, dec): - # type: (ASN1_Packet, Any) -> None - self.set_val(pkt, self.m2i_from_decoder(pkt, dec)) - def i2m(self, pkt, x): # type: (ASN1_Packet, Union[bytes, _I, _A]) -> bytes if x is None: @@ -340,54 +307,6 @@ def copy(self): # type: () -> ASN1F_field[_I, _A] return copy.copy(self) - def _uper_codec_kwargs(self, size_len=None): - # type: (Optional[int]) -> Dict[str, Any] - # These kwargs only depend on attributes set once at __init__ time, - # so the common (no override) case is cached to avoid rebuilding the - # dict on every field access during build/dissect. - if size_len is None and self._uper_kwargs_cache is not None: - return self._uper_kwargs_cache - kwargs = { - "size_len": (self.size_len if size_len is None else size_len) or 0, - "oer_unsigned": self.oer_unsigned, - "uper_min": self.uper_min, - "uper_max": self.uper_max, - } # type: Dict[str, Any] - if ( - getattr(self, "uper_extensible", False) and - self.ASN1_tag == ASN1_Class_UNIVERSAL.INTEGER - ): - kwargs["uper_extensible"] = True - if self.uper_enum_values is not None: - kwargs["uper_enum_values"] = self.uper_enum_values - if size_len is None: - self._uper_kwargs_cache = kwargs - return kwargs - - def _uper_encode_into(self, enc, pkt, value=None): - # type: (Any, ASN1_Packet, 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), - ) - ############################ # Simple ASN1 Fields # @@ -404,32 +323,9 @@ def randval(self): class ASN1F_INTEGER(ASN1F_field[int, ASN1_INTEGER]): ASN1_tag = ASN1_Class_UNIVERSAL.INTEGER - def __init__(self, - name, # type: str - default, # type: Optional[Union[int, ASN1_INTEGER]] - context=None, # type: Optional[Type[ASN1_Class]] - implicit_tag=None, # type: Optional[int] - explicit_tag=None, # type: Optional[int] - flexible_tag=False, # type: Optional[bool] - size_len=None, # type: Optional[int] - oer_unsigned=False, # type: Optional[bool] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - uper_extensible=False, # type: bool - ): - # type: (...) -> None - super(ASN1F_INTEGER, self).__init__( - name, cast(Optional[ASN1_INTEGER], default), context=context, - implicit_tag=implicit_tag, explicit_tag=explicit_tag, - flexible_tag=flexible_tag, size_len=size_len, - oer_unsigned=oer_unsigned, uper_min=uper_min, - uper_max=uper_max, - ) - self.uper_extensible = uper_extensible - def randval(self): # type: () -> RandNum - return RandNum(-2 ** 64, 2 ** 64 - 1) + return RandNum(-2**64, 2**64 - 1) class ASN1F_enum_INTEGER(ASN1F_INTEGER): @@ -458,7 +354,6 @@ def __init__(self, for k in keys: i2s[k] = enum[k] s2i[enum[k]] = k - self.uper_enum_values = list(keys) def i2m(self, pkt, # type: ASN1_Packet @@ -493,16 +388,14 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] - uper_min=None, # type: Optional[int] - uper_max=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, - uper_min=uper_min, - uper_max=uper_max, + **codec_opts, ) if isinstance(default, (bytes, str)): self.default = ASN1_BIT_STRING(default, @@ -612,18 +505,13 @@ class ASN1F_SEQUENCE(ASN1F_field[List[Any], List[Any]]): def __init__(self, *seq, **kwargs): # type: (*Any, **Any) -> None - uper_extensible = kwargs.pop("uper_extensible", False) name = "dummy_seq_name" default = [field.default for field in seq] super(ASN1F_SEQUENCE, self).__init__( name, default, **kwargs ) - self.uper_extensible = uper_extensible self.seq = seq self.islist = len(seq) > 1 - self._optionals = tuple( - f for f in seq if isinstance(f, (ASN1F_optional, ASN1F_DEFAULT)) - ) def __repr__(self): # type: () -> str @@ -651,33 +539,6 @@ def _dissect_sequence_children(self, pkt, s): break return s - def _m2i_oer(self, pkt, s): - # type: (Any, bytes) -> Tuple[Any, bytes] - s = self._apply_tagging_dec(s, pkt, _fname=pkt.name) - s = self._dissect_sequence_children(pkt, s) - return [], s - - def _m2i_per(self, pkt, s): - # type: (Any, bytes) -> Tuple[Any, bytes] - dec = _uper().UPER_Decoder(s) - self._uper_dissect_from_decoder(pkt, dec) - if _uper().UPER_has_unexpected_remainder(dec): - raise _uper().UPER_Decoding_Error( - "unexpected remainder", - remaining=dec.remaining(), - ) - return [], b"" - - def _m2i_ber(self, pkt, s): - # type: (Any, bytes) -> Tuple[Any, bytes] - 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) - s = self._dissect_sequence_children(pkt, s) - if len(s) > 0: - raise BER_Decoding_Error("unexpected remainder", remaining=s) - return [], remain - def m2i(self, pkt, s): # type: (Any, bytes) -> Tuple[Any, bytes] """ @@ -688,36 +549,19 @@ 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. """ - if pkt.ASN1_codec == ASN1_Codecs.OER: - return self._m2i_oer(pkt, s) - if pkt.ASN1_codec == ASN1_Codecs.PER: - return self._m2i_per(pkt, s) - return self._m2i_ber(pkt, s) - - def _uper_dissect_from_decoder(self, pkt, dec): - # type: (Any, Any) -> None - if self.uper_extensible: - if dec.read_bit(): - raise _uper().UPER_Decoding_Error( - "ASN1F_SEQUENCE: extension additions are not supported" - ) - presence = [dec.read_bit() for _ in self._optionals] - opt_idx = 0 - for obj in self.seq: - if isinstance(obj, (ASN1F_optional, ASN1F_DEFAULT)): - 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 dissect_from_decoder(self, pkt, dec): - # type: (Any, Any) -> None - self._uper_dissect_from_decoder(pkt, dec) + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None: + return hooks.sequence_m2i(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) + 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): # type: (Any, bytes) -> bytes @@ -726,25 +570,13 @@ def dissect(self, pkt, s): def build(self, pkt): # type: (ASN1_Packet) -> bytes - if pkt.ASN1_codec == ASN1_Codecs.PER: - enc = _uper().UPER_Encoder() - self._uper_encode_into(enc, pkt) - return super(ASN1F_SEQUENCE, self).i2m(pkt, enc.as_bytes()) + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None: + return hooks.sequence_build(self, pkt) s = reduce(lambda x, y: x + y.build(pkt), self.seq, b"") return super(ASN1F_SEQUENCE, self).i2m(pkt, s) - def _uper_encode_into(self, enc, pkt, value=None): - # type: (Any, ASN1_Packet, Optional[Any]) -> None - if self.uper_extensible: - enc.append_bit(0) - for opt in self._optionals: - enc.append_bit(0 if opt.is_empty(pkt) else 1) - for obj in self.seq: - if isinstance(obj, (ASN1F_optional, ASN1F_DEFAULT)) and obj.is_empty(pkt): - continue - obj._uper_encode_into(enc, pkt) - class ASN1F_SET(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_UNIVERSAL.SET @@ -759,7 +591,7 @@ class ASN1F_SET(ASN1F_SEQUENCE): class ASN1F_SEQUENCE_OF(ASN1F_field[List[_SEQ_T], -List[ASN1_Object[Any]]]): + List[ASN1_Object[Any]]]): """ Two types are allowed as cls: ASN1_Packet, ASN1F_field """ @@ -773,9 +605,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - uper_extensible=False, # type: bool + **codec_opts # type: Any ): # type: (...) -> None if isinstance(cls, type) and issubclass(cls, ASN1F_field) or \ @@ -795,31 +625,10 @@ 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 - self.uper_min = uper_min - self.uper_max = uper_max - self.uper_extensible = uper_extensible - - def _uper_count_enc(self, enc, count): - # type: (Any, int) -> None - if self.uper_min is not None and self.uper_max is not None: - _uper().UPER_constrained_int_enc(count, self.uper_min, self.uper_max, enc=enc) - else: - enc.append_length_determinant(count) - - def _uper_count_dec(self, dec): - # type: (Any) -> int - if self.uper_min is not None and self.uper_max is not None: - size = self.uper_max - self.uper_min - return cast( - int, - dec.read_non_negative_binary_integer( - _uper().UPER_bits_for_range(size), - ) + self.uper_min, - ) - return cast(int, dec.read_length_determinant()) def is_empty(self, pkt, # type: ASN1_Packet @@ -827,90 +636,14 @@ def is_empty(self, # type: (...) -> bool return ASN1F_field.is_empty(self, pkt) - def _extract_packet_from_decoder(self, dec, pkt): - # type: (Any, ASN1_Packet) -> Tuple[Any, bytes] - if self.holds_packets: - p = self.cls() - p.add_underlayer(pkt) - p.ASN1_root.dissect_from_decoder(p, dec) - return p, b"" - return self.fld.m2i_from_decoder(pkt, dec), b"" - - def m2i_from_decoder(self, pkt, dec): - # type: (ASN1_Packet, Any) -> List[Any] - if self.uper_extensible and dec.read_bit(): - count = dec.read_length_determinant() - else: - count = self._uper_count_dec(dec) - lst = [] - for _ in range(count): - item, _ = self._extract_packet_from_decoder(dec, pkt) - lst.append(item) - return lst - - def _uper_encode_into(self, enc, pkt, value=None): - # type: (Any, ASN1_Packet, Any) -> None - if value is None: - value = getattr(pkt, self.name) - if value is None: - self._uper_count_enc(enc, 0) - return - count = len(value) - if self.uper_extensible: - if ( - self.uper_min is not None and self.uper_max is not None and - self.uper_min <= count <= self.uper_max - ): - enc.append_bit(0) - else: - enc.append_bit(1) - enc.append_length_determinant(count) - for item in value: - if self.holds_packets: - cast("ASN1_Packet", item).ASN1_root._uper_encode_into( - enc, item, - ) - else: - self.fld._uper_encode_into(enc, pkt, item) - return - self._uper_count_enc(enc, count) - for item in value: - if self.holds_packets: - cast("ASN1_Packet", item).ASN1_root._uper_encode_into( - enc, item, - ) - else: - self.fld._uper_encode_into(enc, pkt, item) - def m2i(self, pkt, # type: ASN1_Packet s, # type: bytes ): # type: (...) -> Tuple[List[Any], bytes] - if pkt.ASN1_codec == ASN1_Codecs.OER: - s = self._apply_tagging_dec(s, pkt) - count, s = _oer().OER_unsigned_integer_dec(s) - lst = [] - for _ in range(count): - c, s = self._extract_packet(s, pkt) # type: ignore - if c: - lst.append(c) - return lst, s - if pkt.ASN1_codec == ASN1_Codecs.PER: - dec = _uper().UPER_Decoder(s) - if self.uper_extensible and dec.read_bit(): - count = dec.read_length_determinant() - else: - count = self._uper_count_dec(dec) - lst = [] - for _ in range(count): - c, _ = self._extract_packet_from_decoder(dec, pkt) - if c: - lst.append(c) - if _uper().UPER_has_unexpected_remainder(dec): - raise _uper().UPER_Decoding_Error("unexpected remainder", - remaining=dec.remaining()) - return lst, b"" + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None: + return hooks.sequence_of_m2i(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) @@ -928,33 +661,21 @@ def m2i(self, def build(self, pkt): # type: (ASN1_Packet) -> bytes + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None: + return hooks.sequence_of_build(self, pkt) val = getattr(pkt, self.name) if isinstance(val, ASN1_Object) and \ val.tag == ASN1_Class_UNIVERSAL.RAW: s = cast(Union[List[_SEQ_T], bytes], val) elif val is None: s = b"" - if pkt.ASN1_codec == ASN1_Codecs.OER: - s = _oer().OER_unsigned_integer_enc(0) - elif pkt.ASN1_codec == ASN1_Codecs.PER: - enc = _uper().UPER_Encoder() - enc.append_length_determinant(0) - s = enc.as_bytes() + elif self.holds_packets: + s = b"".join(bytes(i) for i in val) else: - if pkt.ASN1_codec == ASN1_Codecs.PER: - enc = _uper().UPER_Encoder() - self._uper_encode_into(enc, pkt, val) - s = enc.as_bytes() - elif self.holds_packets: - s = b"".join(bytes(i) for i in val) - if pkt.ASN1_codec == ASN1_Codecs.OER: - s = _oer().OER_unsigned_integer_enc(len(val)) + s - else: - # BER/OER: element fields may carry implicit/explicit tags; - # i2m matches m2i()/fld.m2i(). - s = b"".join(self.fld.i2m(pkt, i) for i in val) - if pkt.ASN1_codec == ASN1_Codecs.OER: - s = _oer().OER_unsigned_integer_enc(len(val)) + s + # BER: element fields may carry implicit/explicit tags; i2m + # matches m2i()/fld.m2i(). (Packet elements use bytes() above.) + s = b"".join(self.fld.i2m(pkt, i) for i in val) return self.i2m(pkt, s) def i2repr(self, pkt, x): @@ -1000,7 +721,6 @@ class ASN1F_optional(ASN1F_element): """ ASN.1 field that is optional. """ - def __init__(self, field): # type: (ASN1F_field[Any, Any]) -> None field.flexible_tag = False @@ -1026,10 +746,6 @@ def dissect(self, pkt, s): self._field.set_val(pkt, None) return s - def dissect_from_decoder(self, pkt, dec): - # type: (ASN1_Packet, Any) -> None - return self._field.dissect_from_decoder(pkt, dec) - def build(self, pkt): # type: (ASN1_Packet) -> bytes if self._field.is_empty(pkt): @@ -1044,57 +760,12 @@ def i2repr(self, pkt, x): # type: (ASN1_Packet, Any) -> str return self._field.i2repr(pkt, x) - def set_val(self, pkt, val): - # type: (ASN1_Packet, Any) -> None - self._field.set_val(pkt, val) - - def set_absent(self, pkt): - # type: (ASN1_Packet) -> None - self.set_val(pkt, None) - - def is_empty(self, pkt): - # type: (ASN1_Packet) -> bool - # Delegate to the wrapped field (e.g. SEQUENCE checks children). - return self._field.is_empty(pkt) - - def _uper_encode_into(self, enc, pkt, value=None): - # type: (Any, ASN1_Packet, Optional[Any]) -> None - self._field._uper_encode_into(enc, pkt, value) - - -class ASN1F_DEFAULT(ASN1F_optional): - """ - ASN.1 field with a DEFAULT value (PER presence bit). - """ - - 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.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. This is different from ASN1F_NULL which has a network representation. """ - def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[None, bytes] return None, s @@ -1121,20 +792,18 @@ def __init__(self, name, default, *args, **kwargs): if "implicit_tag" in kwargs: err_msg = "ASN1F_CHOICE has been called with an implicit_tag" raise ASN1_Error(err_msg) - uper_extensible = kwargs.pop("uper_extensible", False) 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.uper_extensible = uper_extensible self.default = default self.current_choice = None 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"): @@ -1142,75 +811,31 @@ 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 in root.choice_order: - self._register_choice(k, root.choices[k]) + for k, v in root.choices.items(): + # ASN1F_CHOICE recursion + self.choices[k] = v else: - self._register_choice(p.ASN1_root.network_tag, p) + self.choices[p.ASN1_root.network_tag] = p elif hasattr(p, "ASN1_tag"): if isinstance(p, type): # should be ASN1F_field class - self._register_choice(int(p.ASN1_tag), p) + self.choices[int(p.ASN1_tag)] = p else: # should be ASN1F_PACKET instance - self._register_choice(p.network_tag, p) + self.choices[p.network_tag] = p 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") - self._tag_to_index = { - tag: idx for idx, tag in enumerate(self.choice_order) - } - - 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) - - def _dissect_choice_payload(self, pkt, choice, payload): - # type: (ASN1_Packet, _CHOICE_T, bytes) -> Tuple[ASN1_Object[Any], bytes] - if hasattr(choice, "ASN1_root"): - return self.extract_packet(choice, payload, _underlayer=pkt) # type: ignore - if isinstance(choice, type): - return choice(self.name, b"").m2i(pkt, payload) - return choice.m2i(pkt, payload) - def _m2i_oer(self, pkt, s): - # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] - s = self._apply_tagging_dec(s, pkt) - tag, payload = _oer().OER_id_dec(s) - return self._m2i_tagged(pkt, tag, payload) - - def _m2i_per(self, pkt, s): - # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] - dec = _uper().UPER_Decoder(s) - val = self.m2i_from_decoder(pkt, dec) - if _uper().UPER_has_unexpected_remainder(dec): - raise _uper().UPER_Decoding_Error( - "unexpected remainder", - remaining=dec.remaining(), - ) - return val, b"" + @property + def choice_order(self): + # type: () -> List[int] + return list(self.choices.keys()) - def _m2i_ber(self, pkt, s): - # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] - s = self._apply_tagging_dec(s, pkt) - tag, _ = BER_id_dec(s) - return self._m2i_tagged(pkt, tag, s) - - def _m2i_tagged(self, pkt, tag, payload): - # type: (ASN1_Packet, int, bytes) -> Tuple[ASN1_Object[Any], bytes] - if tag in self.choices: - choice = self.choices[tag] - elif self.flexible_tag: - choice = ASN1F_field - else: - raise ASN1_Error( - "ASN1F_CHOICE: unexpected field in '%s' " - "(tag %s not in possible tags %s)" % ( - self.name, tag, list(self.choices.keys()) - ) - ) - return self._dissect_choice_payload(pkt, choice, payload) + @property + def choice_list(self): + # type: () -> List[_CHOICE_T] + return list(self.choices.values()) def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] @@ -1220,92 +845,39 @@ def m2i(self, pkt, s): """ if len(s) == 0: raise ASN1_Error("ASN1F_CHOICE: got empty string") - if pkt.ASN1_codec == ASN1_Codecs.OER: - return self._m2i_oer(pkt, s) - if pkt.ASN1_codec == ASN1_Codecs.PER: - return self._m2i_per(pkt, s) - return self._m2i_ber(pkt, s) - - def _choice_tag_for(self, x): - # type: (Any) -> Optional[int] - index = self._choice_index_for(x) - return None if index is None else self.choice_order[index] - - def _choice_index_for(self, x): - # type: (Any) -> Optional[int] - for index, choice in enumerate(self.choice_list): - if isinstance(choice, type) and hasattr(choice, "ASN1_root"): - if isinstance(x, choice): - return index - elif hasattr(choice, "ASN1_tag"): - if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: - return index - return None - - def _choice_for_index(self, index): - # type: (int) -> _CHOICE_T - return self.choice_list[index] - - def m2i_from_decoder(self, pkt, dec): - # type: (ASN1_Packet, Any) -> ASN1_Object[Any] - if self.uper_extensible: - if dec.read_bit(): - raise _uper().UPER_Decoding_Error( - "ASN1F_CHOICE: extension additions are not supported" - ) - if len(self.choice_order) > 1: - index, _ = _uper().UPER_choice_index_dec(b"", len(self.choice_order), dec=dec) + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None: + return hooks.choice_m2i(self, pkt, s) + s = self._apply_tagging_dec(s, pkt) + tag, _ = BER_id_dec(s) + if tag in self.choices: + choice = self.choices[tag] else: - index = 0 - if index >= len(self.choice_order): - raise ASN1_Error( - "ASN1F_CHOICE: unexpected index %s in '%s'" % - (index, self.name) - ) - choice = self._choice_for_index(index) - if isinstance(choice, type) and hasattr(choice, "ASN1_root"): - pkt_cls = cast("Type[ASN1_Packet]", choice) - p = pkt_cls() - p.add_underlayer(pkt) - p.ASN1_root.dissect_from_decoder(p, dec) - return cast(ASN1_Object[Any], p) - if isinstance(choice, type): - return cast( - ASN1_Object[Any], - choice(self.name, b"").m2i_from_decoder(pkt, dec), - ) - return cast(ASN1_Object[Any], choice.m2i_from_decoder(pkt, dec)) - - def _uper_encode_into(self, enc, pkt, value=None): - # type: (Any, ASN1_Packet, Any) -> None - if value is None: - value = getattr(pkt, self.name) - index = self._choice_index_for(value) - if index is None: - raise ASN1_Error( - "ASN1F_CHOICE: cannot encode unknown alternative in '%s'" % - self.name - ) - if self.uper_extensible: - enc.append_bit(0) - if len(self.choice_order) > 1: - _uper().UPER_choice_index_enc(index, len(self.choice_order), enc=enc) - choice = self._choice_for_index(index) + if self.flexible_tag: + choice = ASN1F_field + else: + raise ASN1_Error( + "ASN1F_CHOICE: unexpected field in '%s' " + "(tag %s not in possible tags %s)" % ( + self.name, tag, list(self.choices.keys()) + ) + ) if hasattr(choice, "ASN1_root"): - cast("ASN1_Packet", value).ASN1_root._uper_encode_into(enc, value) + # we don't want to import ASN1_Packet in this module... + return self.extract_packet(choice, s, _underlayer=pkt) # type: ignore elif isinstance(choice, type): - choice(self.name, b"")._uper_encode_into(enc, pkt, value) + return choice(self.name, b"").m2i(pkt, s) else: - choice._uper_encode_into(enc, pkt, value) + # XXX check properly if this is an ASN1F_PACKET + return choice.m2i(pkt, s) def i2m(self, pkt, x): # type: (ASN1_Packet, Any) -> bytes + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None: + return hooks.choice_i2m(self, pkt, x) if x is None: s = b"" - elif pkt.ASN1_codec == ASN1_Codecs.PER: - enc = _uper().UPER_Encoder() - self._uper_encode_into(enc, pkt, x) - s = enc.as_bytes() else: # Use the packet codec for ASN1_Object values; bytes(x) would # follow conf.ASN1_default_codec instead. @@ -1313,11 +885,7 @@ def i2m(self, pkt, x): s = x.enc(pkt.ASN1_codec) else: s = bytes(x) - if pkt.ASN1_codec == ASN1_Codecs.OER: - alt_tag = self._choice_tag_for(x) - if alt_tag is not None: - s = _oer().OER_tag_enc(alt_tag & 0x3f, alt_tag & 0xc0) + s - elif hash(type(x)) in self.pktchoices: + if hash(type(x)) in self.pktchoices: imp, exp = self.pktchoices[hash(type(x))] s = self._tagging_enc( pkt, s, @@ -1373,24 +941,6 @@ def _resolve_cls(self, pkt): return self.next_cls_cb(pkt) or self.cls return self.cls - def m2i_from_decoder(self, pkt, dec): - # type: (ASN1_Packet, Any) -> Optional[ASN1_Packet] - cls = self._resolve_cls(pkt) - p = cls() - p.add_underlayer(pkt) - p.ASN1_root.dissect_from_decoder(p, dec) - return p - - def _uper_encode_into(self, enc, pkt, value=None): - # type: (Any, ASN1_Packet, Any) -> None - if value is None: - value = getattr(pkt, self.name) - if value is None: - return - if isinstance(value, ASN1_Object): - value = value.val - cast("ASN1_Packet", value).ASN1_root._uper_encode_into(enc, value) - def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[Any, bytes] cls = self._resolve_cls(pkt) @@ -1399,7 +949,7 @@ def m2i(self, pkt, s): return self.extract_packet(cls, s, _underlayer=pkt) s = self._apply_tagging_dec( s, pkt, - hidden_tag=cls.ASN1_root.ASN1_tag, + hidden_tag=cls.ASN1_root.ASN1_tag, # noqa: E501 _fname=self.name, ) if not s: @@ -1411,12 +961,11 @@ def i2m(self, x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> bytes + hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) + if hooks is not None and hasattr(hooks, "packet_i2m"): + return hooks.packet_i2m(self, pkt, x) if x is None: s = b"" - elif pkt.ASN1_codec == ASN1_Codecs.PER: - enc = _uper().UPER_Encoder() - self._uper_encode_into(enc, pkt, x) - s = enc.as_bytes() elif isinstance(x, bytes): s = x elif isinstance(x, ASN1_Object): @@ -1485,7 +1034,10 @@ def m2i(self, pkt, s): # type: ignore else: return None, bit_string.val_readable if len(s) > 0: - raise BER_Decoding_Error("unexpected remainder", remaining=s) + raise BER_Decoding_Error( + "unexpected remainder in %s" % pkt.name, + remaining=s, + ) return p, remain def i2m(self, pkt, x): # type: ignore @@ -1506,8 +1058,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[Any] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] + **codec_opts # type: Any ): # type: (...) -> None self.mapping = mapping @@ -1517,8 +1068,7 @@ def __init__(self, context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag, - uper_min=uper_min, - uper_max=uper_max, + **codec_opts, ) def any2i(self, pkt, x): diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index ab68b2b0e83..06df49c3ffe 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -823,3 +823,130 @@ class OERcodec_GAUGE32(OERcodec_INTEGER): class OERcodec_TIME_TICKS(OERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +########################## +# ASN1F field hooks # +########################## + +class _OER_FieldHooks(object): + """Compound ASN1F_* helpers for OER (kept out of asn1fields.py).""" + + @staticmethod + def use_object_enc(field, pkt, item): + # type: (Any, Any, Any) -> bool + # Constraints (e.g. oer_unsigned) must go through codec.enc(**kwargs). + return field.size_len is None and not field.codec_opts + + @staticmethod + def sequence_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + s = field._apply_tagging_dec(s, pkt, _fname=pkt.name) + s = field._dissect_sequence_children(pkt, s) + return [], s + + @staticmethod + def sequence_build(field, pkt): + # type: (Any, Any) -> bytes + from functools import reduce + s = reduce(lambda x, y: x + y.build(pkt), field.seq, b"") + return ASN1F_field_i2m(field, pkt, s) + + @staticmethod + def 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 + + @staticmethod + def 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 + elif val is None: + s = OER_unsigned_integer_enc(0) + elif field.holds_packets: + s = OER_unsigned_integer_enc(len(val)) + b"".join(bytes(i) for i in val) + else: + s = ( + OER_unsigned_integer_enc(len(val)) + + b"".join(field.fld.i2m(pkt, i) for i in val) + ) + return field.i2m(pkt, s) + + @staticmethod + def 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, payload = OER_id_dec(s) + if tag in field.choices: + choice = field.choices[tag] + elif field.flexible_tag: + choice = ASN1F_field + else: + raise ASN1_Error( + "ASN1F_CHOICE: unexpected field in '%s' " + "(tag %s not in possible tags %s)" % ( + field.name, tag, list(field.choices.keys()) + ) + ) + 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) + return choice.m2i(pkt, payload) + + @staticmethod + def 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) + alt_tag = _choice_tag_for(field, x) + if alt_tag is not None: + s = OER_tag_enc(alt_tag & 0x3f, alt_tag & 0xc0) + s + return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) + + +def _choice_index_for(field, x): + # type: (Any, Any) -> Optional[int] + from scapy.asn1.asn1 import ASN1_Object + for index, choice in enumerate(field.choice_list): + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + if isinstance(x, choice): + return index + elif hasattr(choice, "ASN1_tag"): + if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + return index + return None + + +def _choice_tag_for(field, x): + # type: (Any, Any) -> Optional[int] + index = _choice_index_for(field, x) + return None if index is None else field.choice_order[index] + + +def ASN1F_field_i2m(field, pkt, s): + # type: (Any, Any, bytes) -> bytes + # Call ASN1F_field.i2m without compound overrides. + from scapy.asn1fields import ASN1F_field + return ASN1F_field.i2m(field, pkt, s) + + +ASN1_Codecs.OER.register_field_hooks(_OER_FieldHooks) diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index 1af22cd4e7a..9d190b4b0ba 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -1367,3 +1367,494 @@ class UPERcodec_UNIVERSAL_STRING(UPERcodec_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, "uper_extensible", False)) + + +def _field_range(field): + # type: (Any) -> Tuple[Optional[int], Optional[int]] + return getattr(field, "uper_min", None), getattr(field, "uper_max", None) + + +class _UPER_FieldHooks(object): + """Compound ASN1F_* helpers for UPER/PER (kept out of asn1fields.py).""" + + @staticmethod + def use_object_enc(field, pkt, item): + # type: (Any, Any, Any) -> bool + # Always pass constraints through codec.enc(**kwargs). + return False + + @staticmethod + def sequence_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + dec = UPER_Decoder(s) + _UPER_FieldHooks.sequence_dissect_from_decoder(field, pkt, dec) + if UPER_has_unexpected_remainder(dec): + raise UPER_Decoding_Error( + "unexpected remainder", + remaining=dec.remaining(), + ) + return [], b"" + + @staticmethod + def sequence_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1fields import ASN1F_field + enc = UPER_Encoder() + _UPER_FieldHooks.sequence_encode_into(field, enc, pkt) + return ASN1F_field.i2m(field, pkt, enc.as_bytes()) + + @staticmethod + def _optionals(field): + # type: (Any) -> Tuple[Any, ...] + from scapy.asn1fields import ASN1F_optional + return tuple(f for f in field.seq if isinstance(f, ASN1F_optional)) + + @staticmethod + def 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 = _UPER_FieldHooks._optionals(field) + 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 + + @staticmethod + def 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 _UPER_FieldHooks._optionals(field): + 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) + + @staticmethod + def sequence_of_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[list, bytes] + dec = UPER_Decoder(s) + if _field_extensible(field) and dec.read_bit(): + count = dec.read_length_determinant() + else: + count = _uper_count_dec(field, dec) + lst = [] + for _ in range(count): + c, _ = _extract_packet_from_decoder(field, dec, pkt) + if c: + lst.append(c) + if UPER_has_unexpected_remainder(dec): + raise UPER_Decoding_Error( + "unexpected remainder", + remaining=dec.remaining(), + ) + return lst, b"" + + @staticmethod + def 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 + elif val is None: + enc = UPER_Encoder() + enc.append_length_determinant(0) + s = enc.as_bytes() + else: + enc = UPER_Encoder() + _UPER_FieldHooks.sequence_of_encode_into(field, enc, pkt, val) + s = enc.as_bytes() + return field.i2m(pkt, s) + + @staticmethod + def sequence_of_m2i_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> list + if _field_extensible(field) and dec.read_bit(): + count = dec.read_length_determinant() + else: + count = _uper_count_dec(field, dec) + lst = [] + for _ in range(count): + item, _ = _extract_packet_from_decoder(field, dec, pkt) + lst.append(item) + return lst + + @staticmethod + def 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) + return + count = len(value) + 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_length_determinant(count) + for item in value: + if field.holds_packets: + item.ASN1_root.encode_into(enc, item) + else: + field.fld.encode_into(enc, pkt, item) + return + _uper_count_enc(field, enc, count) + for item in value: + if field.holds_packets: + item.ASN1_root.encode_into(enc, item) + else: + field.fld.encode_into(enc, pkt, item) + + @staticmethod + def choice_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + dec = UPER_Decoder(s) + val = _UPER_FieldHooks.choice_m2i_from_decoder(field, pkt, dec) + if UPER_has_unexpected_remainder(dec): + raise UPER_Decoding_Error( + "unexpected remainder", + remaining=dec.remaining(), + ) + return val, b"" + + @staticmethod + def choice_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + if x is None: + s = b"" + else: + enc = UPER_Encoder() + _UPER_FieldHooks.choice_encode_into(field, enc, pkt, x) + s = enc.as_bytes() + return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) + + @staticmethod + def 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(b"", len(order), dec=dec) + 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"): + p = choice() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p + if isinstance(choice, type): + return choice(field.name, b"").m2i_from_decoder(pkt, dec) + return choice.m2i_from_decoder(pkt, dec) + + @staticmethod + def 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 = _choice_index_for(field, 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(index, len(order), enc=enc) + 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) + + @staticmethod + def 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 + + @staticmethod + def packet_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + if x is None: + s = b"" + else: + enc = UPER_Encoder() + _UPER_FieldHooks.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, + ) + + @staticmethod + def 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 _choice_index_for(field, x): + # type: (Any, Any) -> Optional[int] + from scapy.asn1.asn1 import ASN1_Object + for index, choice in enumerate(field.choice_list): + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + if isinstance(x, choice): + return index + elif hasattr(choice, "ASN1_tag"): + if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + return index + return None + + +def _uper_count_enc(field, enc, count): + # type: (Any, Any, int) -> None + uper_min, uper_max = _field_range(field) + if uper_min is not None and uper_max is not None: + UPER_constrained_int_enc(count, uper_min, uper_max, enc=enc) + else: + enc.append_length_determinant(count) + + +def _uper_count_dec(field, dec): + # type: (Any, Any) -> int + uper_min, uper_max = _field_range(field) + if uper_min is not None and uper_max is not None: + size = uper_max - uper_min + return ( + dec.read_non_negative_binary_integer(UPER_bits_for_range(size)) + + uper_min + ) + return dec.read_length_determinant() + + +def _extract_packet_from_decoder(field, dec, pkt): + # type: (Any, Any, Any) -> Tuple[Any, bytes] + if field.holds_packets: + p = field.cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p, b"" + return field.fld.m2i_from_decoder(pkt, dec), b"" + + +# Populated by _install_uper_asn1fields() (also published on scapy.asn1fields). +ASN1F_DEFAULT = None # type: Any + + +def _install_uper_asn1fields(): + # type: () -> None + """Attach UPER bitstream helpers and DEFAULT onto asn1fields classes.""" + from scapy import asn1fields as af + from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Error, ASN1_Object + + class _ASN1F_DEFAULT(af.ASN1F_optional): + """ASN.1 field with a DEFAULT value (PER presence bit).""" + + def __init__(self, field, default): + # type: (Any, Any) -> None + super(_ASN1F_DEFAULT, self).__init__(field) + self._default = default + + def is_empty(self, pkt): + # type: (Any) -> 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: (Any) -> None + self.set_val(pkt, self._default) + + global ASN1F_DEFAULT + ASN1F_DEFAULT = _ASN1F_DEFAULT # type: ignore[misc,assignment] + af.ASN1F_DEFAULT = _ASN1F_DEFAULT + + 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), + ) + + af.ASN1F_field.m2i_from_decoder = m2i_from_decoder # type: ignore[attr-defined] + af.ASN1F_field.dissect_from_decoder = dissect_from_decoder # type: ignore[attr-defined] + af.ASN1F_field.encode_into = encode_into # type: ignore[attr-defined] + af.ASN1F_field._uper_encode_into = encode_into # type: ignore[attr-defined] + + def seq_dissect_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> None + return _UPER_FieldHooks.sequence_dissect_from_decoder(self, pkt, dec) + + def seq_encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + return _UPER_FieldHooks.sequence_encode_into(self, enc, pkt, value) + + af.ASN1F_SEQUENCE.dissect_from_decoder = seq_dissect_from_decoder # type: ignore[attr-defined] + af.ASN1F_SEQUENCE.encode_into = seq_encode_into # type: ignore[attr-defined] + af.ASN1F_SEQUENCE._uper_encode_into = seq_encode_into # type: ignore[attr-defined] + + def seqof_m2i_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> Any + return _UPER_FieldHooks.sequence_of_m2i_from_decoder(self, pkt, dec) + + def seqof_encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + return _UPER_FieldHooks.sequence_of_encode_into(self, enc, pkt, value) + + af.ASN1F_SEQUENCE_OF.m2i_from_decoder = seqof_m2i_from_decoder # type: ignore[attr-defined] + af.ASN1F_SEQUENCE_OF.encode_into = seqof_encode_into # type: ignore[attr-defined] + af.ASN1F_SEQUENCE_OF._uper_encode_into = seqof_encode_into # type: ignore[attr-defined] + + def choice_m2i_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> Any + return _UPER_FieldHooks.choice_m2i_from_decoder(self, pkt, dec) + + def choice_encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + return _UPER_FieldHooks.choice_encode_into(self, enc, pkt, value) + + af.ASN1F_CHOICE.m2i_from_decoder = choice_m2i_from_decoder # type: ignore[attr-defined] + af.ASN1F_CHOICE.encode_into = choice_encode_into # type: ignore[attr-defined] + af.ASN1F_CHOICE._uper_encode_into = choice_encode_into # type: ignore[attr-defined] + + def packet_m2i_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> Any + return _UPER_FieldHooks.packet_m2i_from_decoder(self, pkt, dec) + + def packet_encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + return _UPER_FieldHooks.packet_encode_into(self, enc, pkt, value) + + af.ASN1F_PACKET.m2i_from_decoder = packet_m2i_from_decoder # type: ignore[attr-defined] + af.ASN1F_PACKET.encode_into = packet_encode_into # type: ignore[attr-defined] + af.ASN1F_PACKET._uper_encode_into = packet_encode_into # type: ignore[attr-defined] + + def opt_set_absent(self, pkt): + # type: (Any, Any) -> None + self.set_val(pkt, None) + + 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) + + af.ASN1F_optional.set_absent = opt_set_absent # type: ignore[attr-defined] + af.ASN1F_optional.dissect_from_decoder = opt_dissect_from_decoder # type: ignore[attr-defined] + af.ASN1F_optional.encode_into = opt_encode_into # type: ignore[attr-defined] + af.ASN1F_optional._uper_encode_into = opt_encode_into # type: ignore[attr-defined] + + _orig_enum_init = af.ASN1F_enum_INTEGER.__init__ + + def enum_init(self, name, default, enum, context=None, + implicit_tag=None, explicit_tag=None): + # type: (Any, str, Any, Any, Any, Any, Any) -> None + _orig_enum_init( + self, name, default, enum, context=context, + implicit_tag=implicit_tag, explicit_tag=explicit_tag, + ) + values = list(self.i2s) + self.uper_enum_values = values + opts = dict(getattr(self, "codec_opts", {})) + opts["uper_enum_values"] = values + self.codec_opts = opts + + af.ASN1F_enum_INTEGER.__init__ = enum_init # type: ignore[assignment] + + +_install_uper_asn1fields() +ASN1_Codecs.PER.register_field_hooks(_UPER_FieldHooks) From 754a251362c406f34f78f0da77c723e9361bc9a3 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Sat, 8 Aug 2026 07:19:08 +0200 Subject: [PATCH 04/19] More tests AI-Assisted: yes (Cursor) --- scapy/contrib/oer.py | 5 + test/scapy/layers/asn1.uts | 71 +++++++++++++ test/scapy/layers/ber.uts | 63 ++++++++++++ test/scapy/layers/oer.uts | 153 +++++++++++++++++++++++++++ test/scapy/layers/uper.uts | 205 +++++++++++++++++++++++++++++++++++++ 5 files changed, 497 insertions(+) diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 06df49c3ffe..4fdac91544b 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -404,6 +404,7 @@ def do_dec(cls, 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( @@ -418,8 +419,11 @@ def dec(cls, 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: @@ -440,6 +444,7 @@ def safedec(cls, 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( diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index 7f00cf6e17f..a00e8a79848 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -491,3 +491,74 @@ for cls, data_hex in [ 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 hasattr(ASN1_Codecs.OER, "_field_hooks") + +assert hasattr(ASN1_Codecs.PER, "_field_hooks") + +assert ASN1_Codecs.OER._field_hooks is not None + +assert ASN1_Codecs.PER._field_hooks is not None + +True + diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index 8087b383ec0..f33e5763a95 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -514,6 +514,69 @@ class ExtraPkt(ASN1_Packet): 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, +} + +assert constrained.oer_unsigned is True + +assert constrained.uper_min == 0 + +assert constrained.uper_max == 255 + +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 diff --git a/test/scapy/layers/oer.uts b/test/scapy/layers/oer.uts index fe690a05ccd..5ef6dfdaf39 100644 --- a/test/scapy/layers/oer.uts +++ b/test/scapy/layers/oer.uts @@ -860,3 +860,156 @@ assert remain == b"" True + ++ ASN.1 OER field hooks and packet extras += import contrib codecs +import scapy.contrib.oer +import scapy.contrib.uper +from scapy.contrib.oer import * +from scapy.packet import raw += prepare helpers and packet classes +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 OERUnsignedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) += oer field hooks registered +assert hasattr(ASN1_Codecs.OER, "_field_hooks") + +assert ASN1_Codecs.OER._field_hooks is not None + +assert hasattr(ASN1_Codecs.OER._field_hooks, "sequence_m2i") + +assert hasattr(ASN1_Codecs.OER._field_hooks, "use_object_enc") + +True + += oer use_object_enc via hooks +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)) + +assert raw(pkt) == b"\x30\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 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 + diff --git a/test/scapy/layers/uper.uts b/test/scapy/layers/uper.uts index 6b962e4dacb..4e5168c55a2 100644 --- a/test/scapy/layers/uper.uts +++ b/test/scapy/layers/uper.uts @@ -2827,3 +2827,208 @@ assert raw(oer_pkt_choice) True + ++ ASN.1 UPER field hooks and packet extras += import contrib codecs +import scapy.contrib.uper +from scapy.contrib.uper import * +from scapy.contrib.uper import ASN1F_DEFAULT +from scapy.packet import raw +import scapy.asn1fields as asn1fields += prepare helpers and packet classes +def _val(x): + # type: (Any) -> Any + return x.val if hasattr(x, "val") else x + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +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("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) += uper field hooks registered +assert hasattr(ASN1_Codecs.PER, "_field_hooks") + +assert ASN1_Codecs.PER._field_hooks is not None + +assert hasattr(ASN1_Codecs.PER._field_hooks, "sequence_m2i") + +assert hasattr(ASN1_Codecs.PER._field_hooks, "use_object_enc") + +assert ASN1_Codecs.PER._field_hooks.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"}) + +assert enum_fld.uper_enum_values == [1, 2] + +assert enum_fld.codec_opts["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 = UPERDefaultRecord(id=1, n=5) + +present = UPERDefaultRecord(id=1, n=7) + +assert raw(absent) == bytes.fromhex("0080") + +assert raw(present) == bytes.fromhex("80b8") + +assert raw(absent) != raw(present) + +decoded_absent = _roundtrip(UPERDefaultRecord, absent) + +decoded_present = _roundtrip(UPERDefaultRecord, 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 + From 5561b4ccbe914fc196adbe4d09adcc4e42894a42 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Sat, 8 Aug 2026 11:04:34 +0200 Subject: [PATCH 05/19] More tests AI-Assisted: yes (Cursor) --- scapy/contrib/oer.py | 3 +++ scapy/contrib/uper.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 4fdac91544b..9942c59709d 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -2,6 +2,9 @@ # 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 diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index 9d190b4b0ba..94958e7a686 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -2,6 +2,9 @@ # 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 From d0a7770553bcff48b243f18cf2399502a28d5b1f Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Mon, 10 Aug 2026 08:13:35 +0200 Subject: [PATCH 06/19] Cleanup asn1fields AI-Assisted: yes (Cursor) --- scapy/asn1fields.py | 18 +++++---- scapy/contrib/oer.py | 21 +++++----- scapy/contrib/uper.py | 81 +++++++++++++++++++++------------------ test/scapy/layers/ber.uts | 10 ++++- 4 files changed, 72 insertions(+), 58 deletions(-) diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 81fd9b007cb..ba43478f95f 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -173,7 +173,7 @@ def _use_object_enc(self, pkt, item): # Contrib codecs may force codec.enc(**kwargs) via field hooks. hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None and hasattr(hooks, "use_object_enc"): - return hooks.use_object_enc(self, pkt, item) + return cast(bool, hooks.use_object_enc(self, pkt, item)) return self.size_len is None and not self.codec_opts def _encode_item(self, pkt, item): @@ -551,7 +551,7 @@ def m2i(self, pkt, s): """ hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None: - return hooks.sequence_m2i(self, pkt, s) + return cast(Tuple[Any, bytes], hooks.sequence_m2i(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) @@ -572,7 +572,7 @@ def build(self, pkt): # type: (ASN1_Packet) -> bytes hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None: - return hooks.sequence_build(self, pkt) + return cast(bytes, hooks.sequence_build(self, pkt)) s = reduce(lambda x, y: x + y.build(pkt), self.seq, b"") return super(ASN1F_SEQUENCE, self).i2m(pkt, s) @@ -643,7 +643,8 @@ def m2i(self, # type: (...) -> Tuple[List[Any], bytes] hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None: - return hooks.sequence_of_m2i(self, pkt, s) + return cast(Tuple[List[Any], bytes], + hooks.sequence_of_m2i(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) @@ -663,7 +664,7 @@ def build(self, pkt): # type: (ASN1_Packet) -> bytes hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None: - return hooks.sequence_of_build(self, pkt) + return cast(bytes, hooks.sequence_of_build(self, pkt)) val = getattr(pkt, self.name) if isinstance(val, ASN1_Object) and \ val.tag == ASN1_Class_UNIVERSAL.RAW: @@ -847,7 +848,8 @@ def m2i(self, pkt, s): raise ASN1_Error("ASN1F_CHOICE: got empty string") hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None: - return hooks.choice_m2i(self, pkt, s) + return cast(Tuple[ASN1_Object[Any], bytes], + hooks.choice_m2i(self, pkt, s)) s = self._apply_tagging_dec(s, pkt) tag, _ = BER_id_dec(s) if tag in self.choices: @@ -875,7 +877,7 @@ def i2m(self, pkt, x): # type: (ASN1_Packet, Any) -> bytes hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None: - return hooks.choice_i2m(self, pkt, x) + return cast(bytes, hooks.choice_i2m(self, pkt, x)) if x is None: s = b"" else: @@ -963,7 +965,7 @@ def i2m(self, # type: (...) -> bytes hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) if hooks is not None and hasattr(hooks, "packet_i2m"): - return hooks.packet_i2m(self, pkt, x) + return cast(bytes, hooks.packet_i2m(self, pkt, x)) if x is None: s = b"" elif isinstance(x, bytes): diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 9942c59709d..45f5f22fc67 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -368,6 +368,7 @@ def __new__(cls, 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] @@ -457,7 +458,7 @@ def safedec(cls, @classmethod def enc(cls, s, size_len=0, **_kwargs): - # type: (_K, Optional[int]) -> bytes + # type: (_K, Optional[int], **Any) -> bytes if isinstance(s, (str, bytes)): return OERcodec_STRING.enc(s, size_len=size_len) else: @@ -480,7 +481,7 @@ class OERcodec_INTEGER(OERcodec_Object[int]): @classmethod def enc(cls, i, size_len=0, **_kwargs): - # type: (int, Optional[int]) -> bytes + # type: (int, Optional[int], **Any) -> bytes if size_len in (1, 2, 4, 8): if i >= 0: if size_len == 1 and 0 <= i <= 255: @@ -520,7 +521,7 @@ class OERcodec_BOOLEAN(OERcodec_Object[int]): @classmethod def enc(cls, i, size_len=0, **_kwargs): - # type: (int, Optional[int]) -> bytes + # type: (int, Optional[int], **Any) -> bytes return chb(0xff if i else 0x00) @classmethod @@ -569,7 +570,7 @@ def do_dec(cls, @classmethod def enc(cls, _s, size_len=0, **_kwargs): - # type: (AnyStr, Optional[int]) -> bytes + # type: (AnyStr, Optional[int], **Any) -> bytes s = bytes_encode(_s) if len(s) % 8 == 0: unused_bits = 0 @@ -587,7 +588,7 @@ class OERcodec_STRING(OERcodec_Object[str]): @classmethod def enc(cls, _s, size_len=0, **_kwargs): - # type: (Union[str, bytes], Optional[int]) -> bytes + # type: (Union[str, bytes], Optional[int], **Any) -> bytes s = bytes_encode(_s) if size_len and size_len == len(s): return s @@ -624,7 +625,7 @@ class OERcodec_NULL(OERcodec_Object[None]): @classmethod def enc(cls, i, size_len=0, **_kwargs): - # type: (Any, Optional[int]) -> bytes + # type: (Any, Optional[int], **Any) -> bytes return b"" @classmethod @@ -644,7 +645,7 @@ class OERcodec_OID(OERcodec_Object[bytes]): @classmethod def enc(cls, _oid, size_len=0, **_kwargs): - # type: (AnyStr, Optional[int]) -> bytes + # type: (AnyStr, Optional[int], **Any) -> bytes oid = bytes_encode(_oid) if oid: lst = [int(x) for x in oid.strip(b".").split(b".")] @@ -690,7 +691,7 @@ class OERcodec_ENUMERATED(OERcodec_INTEGER): @classmethod def enc(cls, i, size_len=0, **_kwargs): - # type: (int, Optional[int]) -> bytes + # type: (int, Optional[int], **Any) -> bytes return OER_enumerated_enc(i) @classmethod @@ -759,7 +760,7 @@ class OERcodec_SEQUENCE(OERcodec_Object[Union[bytes, List['OERcodec_Object[Any]' @classmethod def enc(cls, _ll, size_len=0, **_kwargs): - # type: (Union[bytes, List[OERcodec_Object[Any]]], Optional[int]) -> bytes + # type: (Union[bytes, List[OERcodec_Object[Any]]], Optional[int], **Any) -> bytes # noqa: E501 if isinstance(_ll, bytes): return _ll return b"".join(x.enc(cls.codec) for x in _ll) @@ -788,7 +789,7 @@ class OERcodec_IPADDRESS(OERcodec_STRING): @classmethod def enc(cls, ipaddr_ascii, size_len=0, **_kwargs): # type: ignore - # type: (str, Optional[int]) -> bytes + # type: (str, Optional[int], **Any) -> bytes try: s = inet_aton(ipaddr_ascii) except Exception: diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index 94958e7a686..d1637c3f529 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -652,7 +652,7 @@ def safedec(cls, @classmethod def enc(cls, s, size_len=0, uper_min=None, uper_max=None, **_kwargs): - # type: (_K, Optional[int], Optional[int], Optional[int]) -> bytes + # type: (_K, Optional[int], Optional[int], Optional[int], **Any) -> bytes if isinstance(s, (str, bytes)): return UPERcodec_STRING.enc(s, size_len=size_len, uper_min=uper_min, uper_max=uper_max) @@ -678,7 +678,6 @@ def _uper_enc_via_encode_into(cls, *args, **kwargs): return enc.as_bytes() - def UPER_tagging_enc(s, **kwargs): # type: (bytes, **Any) -> bytes # UPER has no BER-style TLV tagging. @@ -757,8 +756,9 @@ def dec_from_decoder(cls, return cls.asn1_object(value) @classmethod - def enc(cls, i, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): - # type: (int, Optional[int], Optional[int], Optional[int], bool) -> bytes + def enc(cls, i, size_len=0, uper_min=None, uper_max=None, + oer_unsigned=False, **_kwargs): + # type: (int, Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes return _uper_enc_via_encode_into( cls, i, size_len, uper_min, uper_max, oer_unsigned, ) @@ -809,8 +809,9 @@ def dec_from_decoder(cls, return cls.asn1_object(dec.read_bit()) @classmethod - def enc(cls, i, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): - # type: (int, Optional[int], Optional[int], Optional[int], bool) -> bytes + def enc(cls, i, size_len=0, uper_min=None, uper_max=None, + oer_unsigned=False, **_kwargs): + # type: (int, Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes return _uper_enc_via_encode_into( cls, i, size_len, uper_min, uper_max, oer_unsigned, ) @@ -917,8 +918,9 @@ def dec_from_decoder(cls, return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) @classmethod - def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): - # type: (Any, Optional[int], Optional[int], Optional[int], bool) -> bytes + def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, + oer_unsigned=False, **_kwargs): + # type: (Any, Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes return _uper_enc_via_encode_into( cls, _s, size_len, uper_min, uper_max, oer_unsigned, ) @@ -994,8 +996,9 @@ def dec_from_decoder(cls, return cls.asn1_object(raw) @classmethod - def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): - # type: (Union[str, bytes], Optional[int], Optional[int], Optional[int], bool) -> bytes # noqa: E501 + def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, + oer_unsigned=False, **_kwargs): + # type: (Union[str, bytes], Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes # noqa: E501 return _uper_enc_via_encode_into( cls, _s, size_len, uper_min, uper_max, oer_unsigned, ) @@ -1045,8 +1048,9 @@ def dec_from_decoder(cls, return cls.asn1_object(None) @classmethod - def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): - # type: (Any, Optional[int], Optional[int], Optional[int], bool) -> bytes + def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, + oer_unsigned=False, **_kwargs): + # type: (Any, Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes return b"" @classmethod @@ -1068,7 +1072,7 @@ class UPERcodec_OID(UPERcodec_Object[bytes]): @classmethod def enc(cls, _oid, size_len=0, uper_min=None, uper_max=None, **_kwargs): - # type: (AnyStr, Optional[int], Optional[int], Optional[int]) -> bytes + # type: (AnyStr, Optional[int], Optional[int], Optional[int], **Any) -> bytes oid = bytes_encode(_oid) if oid: lst = [int(x) for x in oid.split(b".")] @@ -1250,8 +1254,9 @@ def encode_into(cls, UPER_append_encoded(enc, _ll) @classmethod - def enc(cls, _ll, size_len=0, uper_min=None, uper_max=None, oer_unsigned=False, **_kwargs): - # type: (Union[bytes, List[UPERcodec_Object[Any]]], Optional[int], Optional[int], Optional[int], bool) -> bytes # noqa: E501 + def enc(cls, _ll, size_len=0, uper_min=None, uper_max=None, + oer_unsigned=False, **_kwargs): + # type: (Union[bytes, List[UPERcodec_Object[Any]]], Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes # noqa: E501 if isinstance(_ll, bytes): return _ll raise UPER_Encoding_Error( @@ -1284,7 +1289,7 @@ class UPERcodec_IPADDRESS(UPERcodec_STRING): @classmethod def enc(cls, ipaddr_ascii, size_len=0, uper_min=None, uper_max=None, **_kwargs): - # type: (str, Optional[int], Optional[int], Optional[int]) -> bytes + # type: (str, Optional[int], Optional[int], Optional[int], **Any) -> bytes try: s = inet_aton(ipaddr_ascii) except Exception: @@ -1739,7 +1744,7 @@ def set_absent(self, pkt): 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] + return codec.dec_from_decoder( # type: ignore[attr-defined] # noqa: E501 dec, **self._codec_kwargs(pkt), ) @@ -1767,14 +1772,14 @@ def encode_into(self, enc, pkt, value=None): ) else: raw = value - codec.encode_into( # type: ignore[attr-defined] + codec.encode_into( # type: ignore[attr-defined] # noqa: E501 enc, raw, **self._codec_kwargs(pkt), ) - af.ASN1F_field.m2i_from_decoder = m2i_from_decoder # type: ignore[attr-defined] - af.ASN1F_field.dissect_from_decoder = dissect_from_decoder # type: ignore[attr-defined] - af.ASN1F_field.encode_into = encode_into # type: ignore[attr-defined] - af.ASN1F_field._uper_encode_into = encode_into # type: ignore[attr-defined] + af.ASN1F_field.m2i_from_decoder = m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_field.dissect_from_decoder = dissect_from_decoder # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_field.encode_into = encode_into # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_field._uper_encode_into = encode_into # type: ignore[attr-defined] # noqa: E501 def seq_dissect_from_decoder(self, pkt, dec): # type: (Any, Any, Any) -> None @@ -1784,9 +1789,9 @@ def seq_encode_into(self, enc, pkt, value=None): # type: (Any, Any, Any, Any) -> None return _UPER_FieldHooks.sequence_encode_into(self, enc, pkt, value) - af.ASN1F_SEQUENCE.dissect_from_decoder = seq_dissect_from_decoder # type: ignore[attr-defined] - af.ASN1F_SEQUENCE.encode_into = seq_encode_into # type: ignore[attr-defined] - af.ASN1F_SEQUENCE._uper_encode_into = seq_encode_into # type: ignore[attr-defined] + af.ASN1F_SEQUENCE.dissect_from_decoder = seq_dissect_from_decoder # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_SEQUENCE.encode_into = seq_encode_into # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_SEQUENCE._uper_encode_into = seq_encode_into # type: ignore[attr-defined] # noqa: E501 def seqof_m2i_from_decoder(self, pkt, dec): # type: (Any, Any, Any) -> Any @@ -1796,9 +1801,9 @@ def seqof_encode_into(self, enc, pkt, value=None): # type: (Any, Any, Any, Any) -> None return _UPER_FieldHooks.sequence_of_encode_into(self, enc, pkt, value) - af.ASN1F_SEQUENCE_OF.m2i_from_decoder = seqof_m2i_from_decoder # type: ignore[attr-defined] - af.ASN1F_SEQUENCE_OF.encode_into = seqof_encode_into # type: ignore[attr-defined] - af.ASN1F_SEQUENCE_OF._uper_encode_into = seqof_encode_into # type: ignore[attr-defined] + af.ASN1F_SEQUENCE_OF.m2i_from_decoder = seqof_m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_SEQUENCE_OF.encode_into = seqof_encode_into # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_SEQUENCE_OF._uper_encode_into = seqof_encode_into # type: ignore[attr-defined] # noqa: E501 def choice_m2i_from_decoder(self, pkt, dec): # type: (Any, Any, Any) -> Any @@ -1808,9 +1813,9 @@ def choice_encode_into(self, enc, pkt, value=None): # type: (Any, Any, Any, Any) -> None return _UPER_FieldHooks.choice_encode_into(self, enc, pkt, value) - af.ASN1F_CHOICE.m2i_from_decoder = choice_m2i_from_decoder # type: ignore[attr-defined] - af.ASN1F_CHOICE.encode_into = choice_encode_into # type: ignore[attr-defined] - af.ASN1F_CHOICE._uper_encode_into = choice_encode_into # type: ignore[attr-defined] + af.ASN1F_CHOICE.m2i_from_decoder = choice_m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_CHOICE.encode_into = choice_encode_into # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_CHOICE._uper_encode_into = choice_encode_into # type: ignore[attr-defined] # noqa: E501 def packet_m2i_from_decoder(self, pkt, dec): # type: (Any, Any, Any) -> Any @@ -1820,9 +1825,9 @@ def packet_encode_into(self, enc, pkt, value=None): # type: (Any, Any, Any, Any) -> None return _UPER_FieldHooks.packet_encode_into(self, enc, pkt, value) - af.ASN1F_PACKET.m2i_from_decoder = packet_m2i_from_decoder # type: ignore[attr-defined] - af.ASN1F_PACKET.encode_into = packet_encode_into # type: ignore[attr-defined] - af.ASN1F_PACKET._uper_encode_into = packet_encode_into # type: ignore[attr-defined] + af.ASN1F_PACKET.m2i_from_decoder = packet_m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_PACKET.encode_into = packet_encode_into # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_PACKET._uper_encode_into = packet_encode_into # type: ignore[attr-defined] # noqa: E501 def opt_set_absent(self, pkt): # type: (Any, Any) -> None @@ -1836,10 +1841,10 @@ def opt_encode_into(self, enc, pkt, value=None): # type: (Any, Any, Any, Any) -> None self._field.encode_into(enc, pkt, value) - af.ASN1F_optional.set_absent = opt_set_absent # type: ignore[attr-defined] - af.ASN1F_optional.dissect_from_decoder = opt_dissect_from_decoder # type: ignore[attr-defined] - af.ASN1F_optional.encode_into = opt_encode_into # type: ignore[attr-defined] - af.ASN1F_optional._uper_encode_into = opt_encode_into # type: ignore[attr-defined] + af.ASN1F_optional.set_absent = opt_set_absent # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_optional.dissect_from_decoder = opt_dissect_from_decoder # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_optional.encode_into = opt_encode_into # type: ignore[attr-defined] # noqa: E501 + af.ASN1F_optional._uper_encode_into = opt_encode_into # type: ignore[attr-defined] # noqa: E501 _orig_enum_init = af.ASN1F_enum_INTEGER.__init__ diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index f33e5763a95..8c089ed1bff 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -471,6 +471,9 @@ def _id_tagging_enc(s, **kwargs): def _id_tagging_dec(s, **kwargs): return None, s +# Save/restore: asn1.uts may already have loaded contrib UPER tagging. +_prev_tagging_enc = getattr(ASN1_Codecs.PER, "_tagging_enc", None) +_prev_tagging_dec = getattr(ASN1_Codecs.PER, "_tagging_dec", 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" @@ -479,8 +482,11 @@ try: ) assert diff is None and payload == b"\x02\x01\x05" finally: - del ASN1_Codecs.PER._tagging_enc - del ASN1_Codecs.PER._tagging_dec + if _prev_tagging_enc is not None and _prev_tagging_dec is not None: + ASN1_Codecs.PER.register_tagging(_prev_tagging_enc, _prev_tagging_dec) + else: + del ASN1_Codecs.PER._tagging_enc + del ASN1_Codecs.PER._tagging_dec = field _codec_kwargs and object-enc hooks class P(ASN1_Packet): From 738b7c341f23435aebaddac5a6fe84961b820e17 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Mon, 10 Aug 2026 14:33:56 +0200 Subject: [PATCH 07/19] Cleanup asn1fields AI-Assisted: yes (Cursor) --- scapy/asn1/asn1.py | 16 + scapy/asn1/ber.py | 3 - scapy/asn1fields.py | 59 ++- scapy/contrib/uper.py | 31 +- test/{scapy/layers => contrib}/oer.uts | 613 ++++++++++------------ test/{scapy/layers => contrib}/uper.uts | 650 ++++++++---------------- test/scapy/layers/ber.uts | 7 +- 7 files changed, 539 insertions(+), 840 deletions(-) rename test/{scapy/layers => contrib}/oer.uts (88%) rename test/{scapy/layers => contrib}/uper.uts (89%) diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 7e4fe145c82..b7d057b4da6 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -137,6 +137,22 @@ def register_field_hooks(cls, hooks): # Optional compound-field helpers (SEQUENCE/CHOICE/…) for contrib codecs. cls._field_hooks = hooks + def unregister_field_hooks(cls): + # type: () -> Any + # Returns the previous hooks, so that callers can restore them. + hooks = getattr(cls, "_field_hooks", None) + try: + del cls._field_hooks + except AttributeError: + pass + return hooks + + def field_hook(cls, name): + # type: (str) -> Any + # Hooks are optional and may be partial: missing entries mean that + # asn1fields keeps its default (BER-style) implementation. + return getattr(getattr(cls, "_field_hooks", None), name, None) + def tagging_enc(cls, s, **kwargs): # type: (bytes, **Any) -> bytes return cls._tagging_enc(s, **kwargs) # type: ignore diff --git a/scapy/asn1/ber.py b/scapy/asn1/ber.py index 59e58073b3f..c3da0f15b5d 100644 --- a/scapy/asn1/ber.py +++ b/scapy/asn1/ber.py @@ -297,9 +297,6 @@ def __new__(cls, class BERcodec_Object(Generic[_K], metaclass=BERcodec_metaclass): codec = ASN1_Codecs.BER tag = ASN1_Class_UNIVERSAL.ANY - skip_tagging = False - tagging_enc = staticmethod(BER_tagging_enc) - tagging_dec = staticmethod(BER_tagging_dec) @classmethod def asn1_object(cls, val): diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index ba43478f95f..364bde87a90 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.field_hook(name) + + ########################## # Basic ASN1 Field # ########################## @@ -108,8 +115,6 @@ def __init__(self, # 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] - for key, val in codec_opts.items(): - setattr(self, key, val) 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" @@ -171,9 +176,9 @@ def _codec_kwargs(self, pkt): def _use_object_enc(self, pkt, item): # type: (ASN1_Packet, ASN1_Object[Any]) -> bool # Contrib codecs may force codec.enc(**kwargs) via field hooks. - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None and hasattr(hooks, "use_object_enc"): - return cast(bool, hooks.use_object_enc(self, pkt, item)) + 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): @@ -549,9 +554,9 @@ 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. """ - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None: - return cast(Tuple[Any, bytes], hooks.sequence_m2i(self, pkt, s)) + 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) @@ -570,9 +575,9 @@ def dissect(self, pkt, s): def build(self, pkt): # type: (ASN1_Packet) -> bytes - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None: - return cast(bytes, hooks.sequence_build(self, pkt)) + 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) @@ -641,10 +646,9 @@ def m2i(self, s, # type: bytes ): # type: (...) -> Tuple[List[Any], bytes] - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None: - return cast(Tuple[List[Any], bytes], - hooks.sequence_of_m2i(self, pkt, s)) + 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) @@ -662,9 +666,9 @@ def m2i(self, def build(self, pkt): # type: (ASN1_Packet) -> bytes - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None: - return cast(bytes, hooks.sequence_of_build(self, pkt)) + 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: @@ -846,10 +850,9 @@ def m2i(self, pkt, s): """ if len(s) == 0: raise ASN1_Error("ASN1F_CHOICE: got empty string") - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None: - return cast(Tuple[ASN1_Object[Any], bytes], - hooks.choice_m2i(self, pkt, s)) + 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: @@ -875,9 +878,9 @@ def m2i(self, pkt, s): def i2m(self, pkt, x): # type: (ASN1_Packet, Any) -> bytes - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None: - return cast(bytes, hooks.choice_i2m(self, pkt, x)) + 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: @@ -963,9 +966,9 @@ def i2m(self, x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> bytes - hooks = getattr(pkt.ASN1_codec, "_field_hooks", None) - if hooks is not None and hasattr(hooks, "packet_i2m"): - return cast(bytes, hooks.packet_i2m(self, pkt, x)) + 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): diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index d1637c3f529..41a2d09bf52 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -1383,12 +1383,13 @@ class UPERcodec_BMP_STRING(UPERcodec_STRING): def _field_extensible(field): # type: (Any) -> bool - return bool(getattr(field, "uper_extensible", False)) + return bool(getattr(field, "codec_opts", {}).get("uper_extensible", False)) def _field_range(field): # type: (Any) -> Tuple[Optional[int], Optional[int]] - return getattr(field, "uper_min", None), getattr(field, "uper_max", None) + opts = getattr(field, "codec_opts", {}) + return opts.get("uper_min"), opts.get("uper_max") class _UPER_FieldHooks(object): @@ -1846,22 +1847,20 @@ def opt_encode_into(self, enc, pkt, value=None): af.ASN1F_optional.encode_into = opt_encode_into # type: ignore[attr-defined] # noqa: E501 af.ASN1F_optional._uper_encode_into = opt_encode_into # type: ignore[attr-defined] # noqa: E501 - _orig_enum_init = af.ASN1F_enum_INTEGER.__init__ + _orig_enum_codec_kwargs = af.ASN1F_enum_INTEGER._codec_kwargs - def enum_init(self, name, default, enum, context=None, - implicit_tag=None, explicit_tag=None): - # type: (Any, str, Any, Any, Any, Any, Any) -> None - _orig_enum_init( - self, name, default, enum, context=context, - implicit_tag=implicit_tag, explicit_tag=explicit_tag, - ) - values = list(self.i2s) - self.uper_enum_values = values - opts = dict(getattr(self, "codec_opts", {})) - opts["uper_enum_values"] = values - self.codec_opts = opts + 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 getattr(codec, "_field_hooks", None) is _UPER_FieldHooks: + kwargs.setdefault("uper_enum_values", list(self.i2s)) + return kwargs - af.ASN1F_enum_INTEGER.__init__ = enum_init # type: ignore[assignment] + af.ASN1F_enum_INTEGER._codec_kwargs = enum_codec_kwargs # type: ignore[assignment] # noqa: E501 _install_uper_asn1fields() diff --git a/test/scapy/layers/oer.uts b/test/contrib/oer.uts similarity index 88% rename from test/scapy/layers/oer.uts rename to test/contrib/oer.uts index 5ef6dfdaf39..4d355f67d54 100644 --- a/test/scapy/layers/oer.uts +++ b/test/contrib/oer.uts @@ -5,139 +5,13 @@ # bash test/run_tests -t test/scapy/layers/oer.uts -F + ASN.1 OER load -= import contrib codecs += prepare helpers and packet classes import scapy.contrib.oer -from scapy.contrib.oer import * -from scapy.packet import raw - - -+ 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) == 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), 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 explicit null tagging -OER_tagging_enc(OERcodec_NULL.enc(None), explicit_tag=0x81) == b"\x81" -= OER choice id decode -tag, r = OER_id_dec(b"\x81\x01") -tag == 0x81 and r == b"\x01" -+ ASN.1 OER packets, interop and fuzz -= import contrib codecs -import scapy.contrib.oer from scapy.contrib.oer import * + from scapy.packet import raw -= prepare helpers and packet classes + class OERTaggedInteger(ASN1_Packet): ASN1_codec = ASN1_Codecs.OER ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) @@ -290,19 +164,263 @@ _DECODE_ERRORS = ( 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), - ) +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 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) == 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), 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 explicit null tagging +OER_tagging_enc(OERcodec_NULL.enc(None), explicit_tag=0x81) == b"\x81" += OER choice id decode +tag, r = OER_id_dec(b"\x81\x01") +tag == 0x81 and r == b"\x01" -def _fuzz_packets(): - # type: () -> Iterable[Type[ASN1_Packet]] - return (OERFuzzRecord,) ++ ASN.1 OER packets, interop and fuzz = oer field explicit tag pkt = OERTaggedInteger(n=5) @@ -577,121 +695,6 @@ for cls in _fuzz_packets(): True + ASN.1 OER build and dissect -= import contrib codecs -import scapy.contrib.oer -from scapy.contrib.oer import * -from scapy.packet import raw -= prepare helpers and packet classes -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)) - -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)) = oer record build roundtrip pkt = OERRecord(**_record_kwargs()) @@ -762,56 +765,6 @@ True + ASN.1 OER coverage -= import contrib codecs -import scapy.contrib.oer -from scapy.contrib.oer import * -from scapy.packet import raw -= prepare helpers and packet classes -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__) = oer error str obj = ASN1_INTEGER(1) @@ -862,54 +815,6 @@ True + ASN.1 OER field hooks and packet extras -= import contrib codecs -import scapy.contrib.oer -import scapy.contrib.uper -from scapy.contrib.oer import * -from scapy.packet import raw -= prepare helpers and packet classes -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 OERUnsignedField(ASN1_Packet): - ASN1_codec = ASN1_Codecs.OER - ASN1_root = ASN1F_INTEGER( - "n", 0, size_len=1, oer_unsigned=True, - ) - -def _roundtrip(cls, pkt): - # type: (type, ASN1_Packet) -> ASN1_Packet - return cls(raw(pkt)) = oer field hooks registered assert hasattr(ASN1_Codecs.OER, "_field_hooks") diff --git a/test/scapy/layers/uper.uts b/test/contrib/uper.uts similarity index 89% rename from test/scapy/layers/uper.uts rename to test/contrib/uper.uts index 4e5168c55a2..7e19761a8e9 100644 --- a/test/scapy/layers/uper.uts +++ b/test/contrib/uper.uts @@ -5,53 +5,13 @@ # bash test/run_tests -t test/scapy/layers/uper.uts -F + ASN.1 UPER load -= import contrib codecs += prepare helpers and packet classes import scapy.contrib.uper -from scapy.contrib.uper import * -from scapy.packet import raw - - -+ 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 -= import contrib codecs -import scapy.contrib.uper from scapy.contrib.uper import * + from scapy.packet import raw -= prepare helpers and packet classes + class UPERFixedFields(ASN1_Packet): ASN1_codec = ASN1_Codecs.PER ASN1_root = ASN1F_SEQUENCE( @@ -582,6 +542,201 @@ class UPERFuzzEnumerated(ASN1_Packet): 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") @@ -1377,209 +1532,10 @@ for cls in _fuzz_packets(): True + ASN.1 UPER build and dissect -= import contrib codecs -import scapy.contrib.uper -from scapy.contrib.uper import * -from scapy.packet import raw -= prepare helpers and packet classes -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), - ) += per record build roundtrip +pkt = UPERRecord(**_record_kwargs()) -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)) -= per record build roundtrip -pkt = UPERRecord(**_record_kwargs()) - -assert len(raw(pkt)) > 0 +assert len(raw(pkt)) > 0 decoded = _roundtrip(UPERRecord, pkt) @@ -1811,63 +1767,6 @@ assert [x.val for x in decoded.items] == [1, 2] True + ASN.1 UPER coverage -= import contrib codecs -import scapy.contrib.uper -from scapy.contrib.uper import * -from scapy.packet import raw -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 -= prepare helpers and packet classes -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__) = uper error str obj = ASN1_INTEGER(2) @@ -1990,65 +1889,6 @@ _raises(UPER_Encoding_Error, lambda: UPERcodec_IPADDRESS.enc("bad-ip")) True + ASN.1 fields coverage -= 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 -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 -= prepare helpers and packet classes -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__) = asn1fields enum and flags pkt = _InnerRecord(mode="on") @@ -2829,74 +2669,6 @@ True + ASN.1 UPER field hooks and packet extras -= import contrib codecs -import scapy.contrib.uper -from scapy.contrib.uper import * -from scapy.contrib.uper import ASN1F_DEFAULT -from scapy.packet import raw -import scapy.asn1fields as asn1fields -= prepare helpers and packet classes -def _val(x): - # type: (Any) -> Any - return x.val if hasattr(x, "val") else x - -def _roundtrip(cls, pkt): - # type: (type, ASN1_Packet) -> ASN1_Packet - return cls(raw(pkt)) - -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("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) = uper field hooks registered assert hasattr(ASN1_Codecs.PER, "_field_hooks") @@ -2919,9 +2691,17 @@ assert issubclass(ASN1F_DEFAULT, ASN1F_optional) enum_fld = ASN1F_ENUMERATED("e", 0, {1: "a", 2: "b"}) -assert enum_fld.uper_enum_values == [1, 2] +# 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_opts["uper_enum_values"] == [1, 2] +assert enum_fld._codec_kwargs( + type("P", (), {"ASN1_codec": ASN1_Codecs.PER})() +)["uper_enum_values"] == [1, 2] assert hasattr(ASN1F_field, "encode_into") @@ -2943,9 +2723,9 @@ assert _val(_roundtrip(UPERConstrainedInt, UPERConstrainedInt(n=5)).n) == 5 True = uper DEFAULT presence bit -absent = UPERDefaultRecord(id=1, n=5) +absent = UPERSmallDefaultRecord(id=1, n=5) -present = UPERDefaultRecord(id=1, n=7) +present = UPERSmallDefaultRecord(id=1, n=7) assert raw(absent) == bytes.fromhex("0080") @@ -2953,9 +2733,9 @@ assert raw(present) == bytes.fromhex("80b8") assert raw(absent) != raw(present) -decoded_absent = _roundtrip(UPERDefaultRecord, absent) +decoded_absent = _roundtrip(UPERSmallDefaultRecord, absent) -decoded_present = _roundtrip(UPERDefaultRecord, present) +decoded_present = _roundtrip(UPERSmallDefaultRecord, present) assert _val(decoded_absent.n) == 5 diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index 8c089ed1bff..bcfd5a28b22 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -539,11 +539,10 @@ assert constrained.codec_opts == { "uper_max": 255, } -assert constrained.oer_unsigned is True +# Constraints live in codec_opts only: they must not become field attributes. +assert not hasattr(constrained, "oer_unsigned") -assert constrained.uper_min == 0 - -assert constrained.uper_max == 255 +assert not hasattr(constrained, "uper_min") kwargs = constrained._codec_kwargs( type("P", (), {"ASN1_codec": ASN1_Codecs.BER})() From 932b780735927bb898dcc5657a2f718235258151 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Mon, 10 Aug 2026 15:17:00 +0200 Subject: [PATCH 08/19] Cleanup asn1fields AI-Assisted: yes (Cursor) --- scapy/asn1/asn1.py | 12 +- scapy/contrib/uper.py | 474 +++++++++++------------------------------- test/contrib/uper.uts | 27 ++- 3 files changed, 154 insertions(+), 359 deletions(-) diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index b7d057b4da6..46327f33388 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -122,6 +122,11 @@ class ASN1_BadTag_Decoding_Error(ASN1_Decoding_Error): class ASN1Codec(EnumElement): + # Class-level default: EnumElement.__getattr__ forwards unknown attributes + # to its int value, so a missing _field_hooks would raise (and swallow) an + # AttributeError on every field operation. + _field_hooks = None # type: Any + def register_stem(cls, stem): # type: (Type[BERcodec_Object[Any]]) -> None cls._stem = stem @@ -140,7 +145,7 @@ def register_field_hooks(cls, hooks): def unregister_field_hooks(cls): # type: () -> Any # Returns the previous hooks, so that callers can restore them. - hooks = getattr(cls, "_field_hooks", None) + hooks = cls._field_hooks try: del cls._field_hooks except AttributeError: @@ -151,7 +156,10 @@ def field_hook(cls, name): # type: (str) -> Any # Hooks are optional and may be partial: missing entries mean that # asn1fields keeps its default (BER-style) implementation. - return getattr(getattr(cls, "_field_hooks", None), name, None) + hooks = cls._field_hooks + if hooks is None: + return None + return getattr(hooks, name, None) def tagging_enc(cls, s, **kwargs): # type: (bytes, **Any) -> bytes diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index 41a2d09bf52..d4ed00c40cb 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -368,6 +368,15 @@ def remaining(self): value = self._read_bits_int(self.number_of_bits) return _uper_per_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) @@ -571,111 +580,65 @@ 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 check_string(cls, s): - # type: (bytes) -> None - if not s and cls.tag != ASN1_Class_UNIVERSAL.NULL: - raise UPER_Decoding_Error( - "%s: Got empty object while expecting %r" % - (cls.__name__, cls.tag), remaining=s + 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 do_dec(cls, - s, # type: bytes - context=None, # type: Optional[Type[ASN1_Class]] - safe=False, # type: bool - size_len=0, # type: Optional[int] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - uper_enum_values=None, # type: Optional[List[int]] - ): - # type: (...) -> Tuple[ASN1_Object[Any], bytes] + 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=s + cls.__name__, remaining=dec.remaining() ) @classmethod - def dec(cls, - s, # type: bytes - context=None, # type: Optional[Type[ASN1_Class]] - safe=False, # type: bool - size_len=0, # type: Optional[int] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - uper_enum_values=None, # type: Optional[List[int]] - ): - # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] - dec_kwargs = {} # type: Dict[str, Any] - if uper_enum_values is not None: - dec_kwargs["uper_enum_values"] = uper_enum_values + 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, size_len, uper_min, uper_max, oer_unsigned, - **dec_kwargs - ) + return cls.do_dec(s, context, safe, **kwargs) try: - return cls.do_dec( - s, context, safe, size_len, uper_min, uper_max, oer_unsigned, - **dec_kwargs - ) + return cls.do_dec(s, context, safe, **kwargs) except UPER_BadTag_Decoding_Error as e: o, remain = UPERcodec_Object.dec( - e.remaining, context, safe, size_len, uper_min, uper_max, - oer_unsigned, uper_enum_values=uper_enum_values, + e.remaining, context, safe, **kwargs ) return ASN1_BADTAG(o), remain - except UPER_Decoding_Error as e: - return ASN1_DECODING_ERROR(s, exc=e), b"" - except ASN1_Error as e: + except (UPER_Decoding_Error, 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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - uper_enum_values=None, # type: Optional[List[int]] - ): - # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] - return cls.dec( - s, context, safe=True, - size_len=size_len, uper_min=uper_min, uper_max=uper_max, - oer_unsigned=oer_unsigned, uper_enum_values=uper_enum_values, - ) - - @classmethod - def enc(cls, s, size_len=0, uper_min=None, uper_max=None, **_kwargs): - # type: (_K, Optional[int], Optional[int], Optional[int], **Any) -> bytes - if isinstance(s, (str, bytes)): - return UPERcodec_STRING.enc(s, size_len=size_len, - uper_min=uper_min, uper_max=uper_max) - else: - try: - return UPERcodec_INTEGER.enc( - int(s), - size_len=size_len, - uper_min=uper_min, - uper_max=uper_max, - ) - except Exception: - raise UPER_Encoding_Error( - "Cannot encode value %r for %s" % (s, cls.__name__), - encoded=s - ) - - -def _uper_enc_via_encode_into(cls, *args, **kwargs): - # type: (Type[UPERcodec_Object[Any]], *Any, **Any) -> bytes - enc = UPER_Encoder() - cls.encode_into(enc, *args, **kwargs) - return enc.as_bytes() + 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) def UPER_tagging_enc(s, **kwargs): @@ -719,6 +682,7 @@ def encode_into(cls, 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) @@ -742,6 +706,7 @@ def dec_from_decoder(cls, 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) @@ -755,32 +720,6 @@ def dec_from_decoder(cls, value = dec.read_unconstrained_whole_number() return cls.asn1_object(value) - @classmethod - def enc(cls, i, size_len=0, uper_min=None, uper_max=None, - oer_unsigned=False, **_kwargs): - # type: (int, Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes - return _uper_enc_via_encode_into( - cls, i, size_len, uper_min, uper_max, oer_unsigned, - ) - - @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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - ): - # type: (...) -> Tuple[ASN1_Object[int], bytes] - minimum, maximum = _uper_int_range(size_len, uper_min, uper_max, oer_unsigned) - if minimum is not None and maximum is not None: - x, t = UPER_constrained_int_dec(s, minimum, maximum) - else: - x, t = UPER_unconstrained_int_dec(s) - return cls.asn1_object(x), t - class UPERcodec_BOOLEAN(UPERcodec_Object[int]): tag = ASN1_Class_UNIVERSAL.BOOLEAN @@ -793,6 +732,7 @@ def encode_into(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> None UPER_boolean_enc(i, enc=enc) @@ -804,32 +744,11 @@ def dec_from_decoder(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> ASN1_Object[int] return cls.asn1_object(dec.read_bit()) - @classmethod - def enc(cls, i, size_len=0, uper_min=None, uper_max=None, - oer_unsigned=False, **_kwargs): - # type: (int, Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes - return _uper_enc_via_encode_into( - cls, i, size_len, uper_min, uper_max, oer_unsigned, - ) - - @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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - ): - # type: (...) -> Tuple[ASN1_Object[int], bytes] - x, t = UPER_boolean_dec(s) - return cls.asn1_object(x), t - def _uper_bytes_to_bitstr(data, nbits): # type: (bytes, int) -> str @@ -864,6 +783,7 @@ def encode_into(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> None s, nbits = _uper_bit_string_parts(_s) @@ -897,6 +817,7 @@ def dec_from_decoder(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> ASN1_Object[str] minimum = uper_min @@ -917,42 +838,6 @@ def dec_from_decoder(cls, raw = dec.read_bits(nbits) return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) - @classmethod - def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, - oer_unsigned=False, **_kwargs): - # type: (Any, Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes - return _uper_enc_via_encode_into( - cls, _s, size_len, uper_min, uper_max, oer_unsigned, - ) - - @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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - ): - # type: (...) -> Tuple[ASN1_Object[str], bytes] - dec = UPER_Decoder(s) - minimum = uper_min - maximum = uper_max - if minimum is not None and maximum is not None and minimum == maximum: - nbits = minimum - elif minimum is not None and maximum is not None: - nbits = minimum + dec.read_non_negative_binary_integer( - UPER_bits_for_range(maximum - minimum) - ) - else: - nbytes = dec.read_length_determinant() - raw = dec.read_bytes(nbytes) - nbits = 8 * nbytes - return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)), dec.remaining() - raw = dec.read_bits(nbits) - return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)), dec.remaining() - def _uper_octet_string_bounds(size_len, uper_min, uper_max): # type: (Optional[int], Optional[int], Optional[int]) -> Tuple[Optional[int], Optional[int]] # noqa: E501 @@ -972,6 +857,7 @@ def encode_into(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> None s = bytes_encode(_s) @@ -987,6 +873,7 @@ def dec_from_decoder(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> ASN1_Object[Any] minimum, maximum = _uper_octet_string_bounds( @@ -995,31 +882,6 @@ def dec_from_decoder(cls, raw, _ = UPER_octet_string_dec(b"", minimum, maximum, dec=dec) return cls.asn1_object(raw) - @classmethod - def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, - oer_unsigned=False, **_kwargs): - # type: (Union[str, bytes], Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes # noqa: E501 - return _uper_enc_via_encode_into( - cls, _s, size_len, uper_min, uper_max, oer_unsigned, - ) - - @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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - ): - # type: (...) -> Tuple[ASN1_Object[Any], bytes] - minimum, maximum = _uper_octet_string_bounds( - size_len, uper_min, uper_max, - ) - raw, remain = UPER_octet_string_dec(s, minimum, maximum) - return cls.asn1_object(raw), remain - class UPERcodec_NULL(UPERcodec_Object[None]): tag = ASN1_Class_UNIVERSAL.NULL @@ -1032,6 +894,7 @@ def encode_into(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> None return @@ -1043,27 +906,15 @@ def dec_from_decoder(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> ASN1_Object[None] return cls.asn1_object(None) @classmethod - def enc(cls, _s, size_len=0, uper_min=None, uper_max=None, - oer_unsigned=False, **_kwargs): - # type: (Any, Optional[int], Optional[int], Optional[int], bool, **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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - ): - # type: (...) -> Tuple[ASN1_Object[None], bytes] + 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 @@ -1071,8 +922,8 @@ class UPERcodec_OID(UPERcodec_Object[bytes]): tag = ASN1_Class_UNIVERSAL.OID @classmethod - def enc(cls, _oid, size_len=0, uper_min=None, uper_max=None, **_kwargs): - # type: (AnyStr, Optional[int], Optional[int], Optional[int], **Any) -> bytes + 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".")] @@ -1080,23 +931,12 @@ def enc(cls, _oid, size_len=0, uper_min=None, uper_max=None, **_kwargs): else: lst = [] body = b"".join(BER_num_enc(k) for k in lst) - enc = UPER_Encoder() enc.append_length_determinant(len(body)) enc.append_bytes(body) - return enc.as_bytes() @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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - ): - # type: (...) -> Tuple[ASN1_Object[bytes], bytes] - dec = UPER_Decoder(s) + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[bytes] length = dec.read_length_determinant() content = dec.read_bytes(length) lst = [] @@ -1106,10 +946,7 @@ def do_dec(cls, 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)), - dec.remaining(), - ) + return cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)) def UPER_enumerated_enc(value, @@ -1165,6 +1002,7 @@ def encode_into(cls, uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool uper_enum_values=None, # type: Optional[List[int]] + **_kwargs # type: Any ): # type: (...) -> None if uper_enum_values is not None: @@ -1184,6 +1022,7 @@ def dec_from_decoder(cls, uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool uper_enum_values=None, # type: Optional[List[int]] + **_kwargs # type: Any ): # type: (...) -> ASN1_Object[int] if uper_enum_values is not None: @@ -1198,44 +1037,6 @@ def dec_from_decoder(cls, ) + minimum return cls.asn1_object(value) - @classmethod - def enc(cls, - i, - size_len=0, - uper_min=None, - uper_max=None, - oer_unsigned=False, - uper_enum_values=None, - **_kwargs - ): - # type: (int, Optional[int], Optional[int], Optional[int], bool, Optional[List[int]], **Any) -> bytes # noqa: E501 - return _uper_enc_via_encode_into( - cls, i, size_len, uper_min, uper_max, oer_unsigned, - uper_enum_values=uper_enum_values, - ) - - @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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - uper_enum_values=None, # type: Optional[List[int]] - ): - # type: (...) -> Tuple[ASN1_Object[int], bytes] - if uper_enum_values is not None: - x, t = UPER_enumerated_dec(s, uper_enum_values) - return cls.asn1_object(x), t - minimum = uper_min if uper_min is not None else 0 - maximum = uper_max if uper_max is not None else size_len - if maximum is None: - raise UPER_Decoding_Error("UPERcodec_ENUMERATED: missing range") - x, t = UPER_constrained_int_dec(s, minimum, maximum) - return cls.asn1_object(x), t - class UPERcodec_SEQUENCE(UPERcodec_Object[Union[bytes, List[Any]]]): tag = ASN1_Class_UNIVERSAL.SEQUENCE @@ -1248,15 +1049,15 @@ def encode_into(cls, uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] oer_unsigned=False, # type: bool + **_kwargs # type: Any ): # type: (...) -> None if isinstance(_ll, bytes): UPER_append_encoded(enc, _ll) @classmethod - def enc(cls, _ll, size_len=0, uper_min=None, uper_max=None, - oer_unsigned=False, **_kwargs): - # type: (Union[bytes, List[UPERcodec_Object[Any]]], Optional[int], Optional[int], Optional[int], bool, **Any) -> bytes # noqa: E501 + def enc(cls, _ll, **_kwargs): + # type: (Union[bytes, List[UPERcodec_Object[Any]]], **Any) -> bytes if isinstance(_ll, bytes): return _ll raise UPER_Encoding_Error( @@ -1264,19 +1065,11 @@ def enc(cls, _ll, size_len=0, uper_min=None, uper_max=None, ) @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] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - ): - # type: (...) -> Tuple[ASN1_Object[Union[bytes, List[Any]]], bytes] + 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=s + remaining=dec.remaining() ) @@ -1288,28 +1081,23 @@ class UPERcodec_IPADDRESS(UPERcodec_STRING): tag = ASN1_Class_UNIVERSAL.IPADDRESS @classmethod - def enc(cls, ipaddr_ascii, size_len=0, uper_min=None, uper_max=None, **_kwargs): - # type: (str, Optional[int], Optional[int], Optional[int], **Any) -> bytes + 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") - return UPER_octet_string_enc(s, 4, 4) + UPER_octet_string_enc(s, 4, 4, enc=enc) @classmethod - def do_dec(cls, s, context=None, safe=False, - size_len=0, uper_min=None, uper_max=None, - oer_unsigned=False): - # type: (bytes, Optional[Any], bool, Optional[int], Optional[int], Optional[int], bool) -> Tuple[ASN1_Object[str], bytes] # noqa: E501 - raw, remain = UPER_octet_string_dec(s, 4, 4) + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[str] + raw, _ = UPER_octet_string_dec(b"", 4, 4, dec=dec) try: ipaddr_ascii = inet_ntoa(raw) except Exception: - raise UPER_Decoding_Error( - "IP address could not be decoded", - remaining=s, - ) - return cls.asn1_object(ipaddr_ascii), remain + raise UPER_Decoding_Error("IP address could not be decoded") + return cls.asn1_object(ipaddr_ascii) class UPERcodec_COUNTER32(UPERcodec_INTEGER): @@ -1745,7 +1533,7 @@ def set_absent(self, pkt): 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] # noqa: E501 + return codec.dec_from_decoder( # type: ignore[attr-defined] dec, **self._codec_kwargs(pkt), ) @@ -1773,63 +1561,10 @@ def encode_into(self, enc, pkt, value=None): ) else: raw = value - codec.encode_into( # type: ignore[attr-defined] # noqa: E501 + codec.encode_into( # type: ignore[attr-defined] enc, raw, **self._codec_kwargs(pkt), ) - af.ASN1F_field.m2i_from_decoder = m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_field.dissect_from_decoder = dissect_from_decoder # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_field.encode_into = encode_into # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_field._uper_encode_into = encode_into # type: ignore[attr-defined] # noqa: E501 - - def seq_dissect_from_decoder(self, pkt, dec): - # type: (Any, Any, Any) -> None - return _UPER_FieldHooks.sequence_dissect_from_decoder(self, pkt, dec) - - def seq_encode_into(self, enc, pkt, value=None): - # type: (Any, Any, Any, Any) -> None - return _UPER_FieldHooks.sequence_encode_into(self, enc, pkt, value) - - af.ASN1F_SEQUENCE.dissect_from_decoder = seq_dissect_from_decoder # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_SEQUENCE.encode_into = seq_encode_into # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_SEQUENCE._uper_encode_into = seq_encode_into # type: ignore[attr-defined] # noqa: E501 - - def seqof_m2i_from_decoder(self, pkt, dec): - # type: (Any, Any, Any) -> Any - return _UPER_FieldHooks.sequence_of_m2i_from_decoder(self, pkt, dec) - - def seqof_encode_into(self, enc, pkt, value=None): - # type: (Any, Any, Any, Any) -> None - return _UPER_FieldHooks.sequence_of_encode_into(self, enc, pkt, value) - - af.ASN1F_SEQUENCE_OF.m2i_from_decoder = seqof_m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_SEQUENCE_OF.encode_into = seqof_encode_into # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_SEQUENCE_OF._uper_encode_into = seqof_encode_into # type: ignore[attr-defined] # noqa: E501 - - def choice_m2i_from_decoder(self, pkt, dec): - # type: (Any, Any, Any) -> Any - return _UPER_FieldHooks.choice_m2i_from_decoder(self, pkt, dec) - - def choice_encode_into(self, enc, pkt, value=None): - # type: (Any, Any, Any, Any) -> None - return _UPER_FieldHooks.choice_encode_into(self, enc, pkt, value) - - af.ASN1F_CHOICE.m2i_from_decoder = choice_m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_CHOICE.encode_into = choice_encode_into # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_CHOICE._uper_encode_into = choice_encode_into # type: ignore[attr-defined] # noqa: E501 - - def packet_m2i_from_decoder(self, pkt, dec): - # type: (Any, Any, Any) -> Any - return _UPER_FieldHooks.packet_m2i_from_decoder(self, pkt, dec) - - def packet_encode_into(self, enc, pkt, value=None): - # type: (Any, Any, Any, Any) -> None - return _UPER_FieldHooks.packet_encode_into(self, enc, pkt, value) - - af.ASN1F_PACKET.m2i_from_decoder = packet_m2i_from_decoder # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_PACKET.encode_into = packet_encode_into # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_PACKET._uper_encode_into = packet_encode_into # type: ignore[attr-defined] # noqa: E501 - def opt_set_absent(self, pkt): # type: (Any, Any) -> None self.set_val(pkt, None) @@ -1842,10 +1577,37 @@ def opt_encode_into(self, enc, pkt, value=None): # type: (Any, Any, Any, Any) -> None self._field.encode_into(enc, pkt, value) - af.ASN1F_optional.set_absent = opt_set_absent # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_optional.dissect_from_decoder = opt_dissect_from_decoder # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_optional.encode_into = opt_encode_into # type: ignore[attr-defined] # noqa: E501 - af.ASN1F_optional._uper_encode_into = opt_encode_into # type: ignore[attr-defined] # noqa: E501 + hooks = _UPER_FieldHooks + 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": hooks.sequence_dissect_from_decoder, + "encode_into": hooks.sequence_encode_into, + }), + (af.ASN1F_SEQUENCE_OF, { + "m2i_from_decoder": hooks.sequence_of_m2i_from_decoder, + "encode_into": hooks.sequence_of_encode_into, + }), + (af.ASN1F_CHOICE, { + "m2i_from_decoder": hooks.choice_m2i_from_decoder, + "encode_into": hooks.choice_encode_into, + }), + (af.ASN1F_PACKET, { + "m2i_from_decoder": hooks.packet_m2i_from_decoder, + "encode_into": hooks.packet_encode_into, + }), + (af.ASN1F_optional, { + "set_absent": opt_set_absent, + "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 @@ -1860,7 +1622,7 @@ def enum_codec_kwargs(self, pkt): kwargs.setdefault("uper_enum_values", list(self.i2s)) return kwargs - af.ASN1F_enum_INTEGER._codec_kwargs = enum_codec_kwargs # type: ignore[assignment] # noqa: E501 + af.ASN1F_enum_INTEGER._codec_kwargs = enum_codec_kwargs # type: ignore[assignment] _install_uper_asn1fields() diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts index 7e19761a8e9..11edfb5de23 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -1616,6 +1616,31 @@ 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 @@ -2268,7 +2293,7 @@ _raises( _raises( ASN1_Error, - lambda: _PerChoice.ASN1_root._uper_encode_into( + lambda: _PerChoice.ASN1_root.encode_into( UPER_Encoder(), _PerChoice(), 42, ), ) From 08ca68421c03917bedb0e15f17713e3916207add Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 10:04:22 +0200 Subject: [PATCH 09/19] oer: fix X.696 conformance of SEQUENCE and fixed-size strings A SEQUENCE with OPTIONAL/DEFAULT components was encoded without the preamble required by X.696 16.2.2, so peers could not tell which components were present. Fixed-size BIT STRINGs kept their length determinant and unused-bit count, and fixed-size OCTET STRINGs of 1, 2, 4 or 8 bytes encoded without a length determinant but were decoded expecting one, so they could be built but never parsed. Encodings now match asn1tools byte for byte in both directions. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/contrib/oer.py | 169 +++++++++++++++++++++++++++++++--- test/contrib/oer.uts | 180 +++++++++++++++++++++++++++++++++++-- test/scapy/layers/asn1.uts | 1 + 3 files changed, 330 insertions(+), 20 deletions(-) diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 45f5f22fc67..0ef38f64f5e 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -9,6 +9,14 @@ 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). + +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 @@ -266,6 +274,50 @@ def OER_enumerated_dec(s): 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 + if len(s) < number_of_bytes: + raise OER_Decoding_Error( + "OER_preamble_dec: Got %i bytes while expecting %i" % + (len(s), number_of_bytes), + remaining=s + ) + 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: @@ -537,6 +589,17 @@ def do_dec(cls, 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 @@ -549,6 +612,20 @@ def do_dec(cls, oer_unsigned=False, # type: bool ): # type: (...) -> Tuple[ASN1_Object[str], bytes] + if size_len: + number_of_bytes = (size_len + 7) // 8 + if len(s) < number_of_bytes: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % + (cls.__name__, len(s), number_of_bytes), + remaining=s + ) + 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 @@ -563,7 +640,7 @@ def do_dec(cls, "OERcodec_BIT_STRING: too many unused_bits advertised", remaining=s ) - fs = "".join(binrepr(orb(x)).zfill(8) for x in s[1:length]) + fs = _oer_bytes_to_bitstr(s[1:length]) if unused_bits > 0: fs = fs[:-unused_bits] return cls.tag.asn1_object(fs), s[length:] @@ -572,14 +649,17 @@ def do_dec(cls, def enc(cls, _s, size_len=0, **_kwargs): # type: (AnyStr, Optional[int], **Any) -> bytes s = bytes_encode(_s) - if len(s) % 8 == 0: - unused_bits = 0 - else: - unused_bits = 8 - len(s) % 8 - s += b"0" * unused_bits - data = b"".join(chb(int(b"".join(chb(y) for y in x), 2)) - for x in zip(*[iter(s)] * 8)) - body = chb(unused_bits) + data + 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 @@ -590,7 +670,14 @@ class OERcodec_STRING(OERcodec_Object[str]): def enc(cls, _s, size_len=0, **_kwargs): # type: (Union[str, bytes], Optional[int], **Any) -> bytes s = bytes_encode(_s) - if size_len and size_len == len(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 @@ -603,7 +690,7 @@ def do_dec(cls, oer_unsigned=False, # type: bool ): # type: (...) -> Tuple[ASN1_Object[Any], bytes] - if size_len and size_len not in (1, 2, 4, 8): + if size_len: if len(s) < size_len: raise OER_Decoding_Error( "%s: Got %i bytes while expecting %i" % @@ -838,6 +925,22 @@ class OERcodec_TIME_TICKS(OERcodec_INTEGER): # ASN1F field hooks # ########################## +def _field_extensible(field): + # type: (Any) -> bool + return bool(getattr(field, "codec_opts", {}).get("oer_extensible", False)) + + +def _set_absent(field, pkt): + # type: (Any, Any) -> None + # ASN1F_DEFAULT restores its default value; a plain optional clears itself. + # set_absent() only exists once scapy.contrib.uper has been imported. + set_absent = getattr(field, "set_absent", None) + if set_absent is not None: + set_absent(pkt) + else: + field.set_val(pkt, None) + + class _OER_FieldHooks(object): """Compound ASN1F_* helpers for OER (kept out of asn1fields.py).""" @@ -847,18 +950,56 @@ def use_object_enc(field, pkt, item): # Constraints (e.g. oer_unsigned) must go through codec.enc(**kwargs). return field.size_len is None and not field.codec_opts + @staticmethod + def _optionals(field): + # type: (Any) -> Tuple[Any, ...] + from scapy.asn1fields import ASN1F_optional + return tuple(f for f in field.seq if isinstance(f, ASN1F_optional)) + @staticmethod def 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) - s = field._dissect_sequence_children(pkt, s) + 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(_OER_FieldHooks._optionals(field)), + ) + 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: + _set_absent(obj, 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 @staticmethod def sequence_build(field, pkt): # type: (Any, Any) -> bytes - from functools import reduce - s = reduce(lambda x, y: x + y.build(pkt), field.seq, b"") + from scapy.asn1fields import ASN1F_optional + optionals = _OER_FieldHooks._optionals(field) + 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) return ASN1F_field_i2m(field, pkt, s) @staticmethod diff --git a/test/contrib/oer.uts b/test/contrib/oer.uts index 4d355f67d54..748b08e6f9c 100644 --- a/test/contrib/oer.uts +++ b/test/contrib/oer.uts @@ -448,7 +448,8 @@ True = oer field optional present = OEROptionalField(id=1, extra=7) -assert raw(present) == b"\x01\x01\xa0\x01\x07" +# \x80: preamble with the presence bit set for the single OPTIONAL component +assert raw(present) == b"\x80\x01\x01\xa0\x01\x07" decoded = _roundtrip(OEROptionalField, present) @@ -458,7 +459,7 @@ assert decoded.extra.val == 7 absent = OEROptionalField(id=1, extra=None) -assert raw(absent) == b"\x01\x01" +assert raw(absent) == b"\x00\x01\x01" decoded = _roundtrip(OEROptionalField, absent) @@ -504,6 +505,7 @@ pkt = OERRecord( ) expected = ( + b"\x80" b"\x01*\xff\x02hi\xa0\x01\x07" b"\x01\x03\x01\x01\x01\x02\x01\x03" ) @@ -524,7 +526,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"\x01\x01\x00\x00\x01\x00" +assert raw(empty) == b"\x00\x01\x01\x00\x00\x01\x00" decoded = _roundtrip(OERRecord, empty) @@ -725,13 +727,13 @@ assert fixed.n.val == 200 assert fixed.s.val == b"ABC" -present = _dissect(OEROptionalField, "0101a00107") +present = _dissect(OEROptionalField, "800101a00107") assert present.id.val == 1 assert present.extra.val == 7 -absent = _dissect(OEROptionalField, "0101") +absent = _dissect(OEROptionalField, "000101") assert absent.id.val == 1 @@ -754,11 +756,12 @@ True = oer record dissect decoded = _dissect( OERRecord, + "80" "012aff026869a00107" "0103010101020103", ) _assert_record(decoded) -empty = _dissect(OERRecord, "010100000100") +empty = _dissect(OERRecord, "00010100000100") _assert_record_empty(empty) True @@ -918,3 +921,168 @@ 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.contrib.uper 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 diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index a00e8a79848..1cd942e6ab1 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -479,6 +479,7 @@ for cls, data_hex in [ ), ( OERRecord, + "80" "012aff026869a00107" "0103010101020103", ), From b78e5fe4b12c0f255baedf6024a6f0e4f763f22f Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 10:39:14 +0200 Subject: [PATCH 10/19] uper: fix bit string length, fragmentation and integer signedness An unconstrained BIT STRING counted its length determinant in octets instead of bits and padded the content to a whole octet, which also shifted every field encoded after it, so peers read back the wrong bits. Content of 16K units or more emitted a fragment header and then the whole content in one go, without the per-fragment determinants and the terminating one required by X.691 11.9.3.8: a conformant peer silently decoded a truncated value. Fragmentation is now implemented on both sides for OCTET STRING, BIT STRING, OBJECT IDENTIFIER and SEQUENCE OF, and append_length_determinant refuses lengths it cannot express rather than clamping them. A fixed or range constrained BIT STRING silently padded or truncated a value whose length violated the constraint, where OER already raised. On the OER side the integer encoder picked the width and the signedness from the value rather than from the declared type, so 200 in a field declared INTEGER (-128..127) encoded as 0xc8 and read back as -56, and an unbounded value with a zero lower bound gained a spurious leading zero octet. Out of range values now raise OER_Encoding_Error instead of struct.error quoting the bounds of the wrong format. Both decoders raised ValueError on an integer with an empty length determinant, which escaped the dissector as a non-ASN.1 exception. Encodings now match asn1tools byte for byte in both directions, over random schemas as well as the vectors added here. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/contrib/oer.py | 34 ++++-- scapy/contrib/uper.py | 276 ++++++++++++++++++++++++++++-------------- test/contrib/oer.uts | 92 ++++++++++++-- test/contrib/uper.uts | 159 ++++++++++++++++++++++-- 4 files changed, 435 insertions(+), 126 deletions(-) diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 0ef38f64f5e..44dbd22d105 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -186,6 +186,11 @@ def OER_signed_integer_dec(s): (len(s), number_of_bytes), remaining=s ) + 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)): @@ -196,6 +201,10 @@ def OER_signed_integer_dec(s): 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") @@ -225,6 +234,11 @@ def OER_fixed_integer_enc(i, length, signed=True): 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): @@ -532,19 +546,15 @@ class OERcodec_INTEGER(OERcodec_Object[int]): tag = ASN1_Class_UNIVERSAL.INTEGER @classmethod - def enc(cls, i, size_len=0, **_kwargs): - # type: (int, Optional[int], **Any) -> bytes + 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): - if i >= 0: - if size_len == 1 and 0 <= i <= 255: - return OER_fixed_integer_enc(i, 1, signed=False) - if size_len == 2 and 0 <= i <= 65535: - return OER_fixed_integer_enc(i, 2, signed=False) - if size_len == 4 and 0 <= i <= 4294967295: - return OER_fixed_integer_enc(i, 4, signed=False) - if size_len == 8 and 0 <= i <= 18446744073709551615: - return OER_fixed_integer_enc(i, 8, signed=False) - return OER_fixed_integer_enc(i, size_len, signed=True) + 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 diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index d4ed00c40cb..847e54de7f2 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -12,10 +12,15 @@ 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. Not supported yet: -explicit/implicit tagging, SET, extension markers, -``ASN1F_CHOICE``/``ASN1F_SEQUENCE_OF`` with nested ``ASN1_Packet`` -alternatives, REAL, and PER-visible character string permuted alphabets. +``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. """ import binascii @@ -41,6 +46,7 @@ from typing import ( Any, AnyStr, + Callable, Dict, Generic, List, @@ -114,6 +120,11 @@ def UPER_bits_for_range(size): 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 + + class UPER_Encoder(object): def __init__(self): # type: () -> None @@ -167,25 +178,38 @@ def append_bytes(self, data): self.append_bits(data, 8 * len(data)) def append_length_determinant(self, length): - # type: (int) -> int + # 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]) - elif length < 16384: - encoded = bytes([(0x80 | (length >> 8)), (length & 0xff)]) - elif length < 32768: - encoded = b"\xc1" - length = 16384 - elif length < 49152: - encoded = b"\xc2" - length = 32768 - elif length < 65536: - encoded = b"\xc3" - length = 49152 else: - encoded = b"\xc4" - length = 65536 + encoded = bytes([(0x80 | (length >> 8)), (length & 0xff)]) self.append_bytes(encoded) - return length + + 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 @@ -400,23 +424,50 @@ def align_always(self): raise UPER_Decoding_Error("UPER_Decoder: out of data") self.number_of_bits -= width - def read_length_determinant(self): - # type: () -> int + 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 + return value, False if (value & 0xc0) == 0x80: - return ((value & 0x7f) << 8) | self.read_non_negative_binary_integer(8) - mapping = {0xc1: 16384, 0xc2: 32768, 0xc3: 49152, 0xc4: 65536} - if value in mapping: - return mapping[value] + 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: @@ -493,38 +544,45 @@ def UPER_boolean_dec(s): def UPER_octet_string_enc(data, minimum=None, maximum=None, enc=None): # type: (bytes, Optional[int], Optional[int], Optional[UPER_Encoder]) -> bytes standalone = enc is None - if enc is None: - enc = UPER_Encoder() + encoder = UPER_Encoder() if enc is None else enc if minimum is not None and maximum is not None and minimum == maximum: - enc.append_bytes(data) + encoder.append_bytes(data) elif minimum is not None and maximum is not None: - enc.append_non_negative_binary_integer( + encoder.append_non_negative_binary_integer( len(data) - minimum, UPER_bits_for_range(maximum - minimum), ) - enc.append_bytes(data) + encoder.append_bytes(data) else: - enc.append_length_determinant(len(data)) - enc.append_bytes(data) - return enc.as_bytes() if standalone else b"" + encoder.append_fragmented( + len(data), + lambda offset, size: encoder.append_bytes( + data[offset:offset + size] + ), + ) + return encoder.as_bytes() if standalone else b"" def UPER_octet_string_dec(s, minimum=None, maximum=None, dec=None): # type: (bytes, Optional[int], Optional[int], Optional[UPER_Decoder]) -> Tuple[bytes, bytes] # noqa: E501 standalone = dec is None - if dec is None: - dec = UPER_Decoder(s) + decoder = UPER_Decoder(s) if dec is None else dec if minimum is not None and maximum is not None and minimum == maximum: - length = minimum + data = decoder.read_bytes(minimum) elif minimum is not None and maximum is not None: - length = minimum + dec.read_non_negative_binary_integer( - UPER_bits_for_range(maximum - minimum) + data = decoder.read_bytes( + minimum + decoder.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) ) else: - length = dec.read_length_determinant() - data = dec.read_bytes(length) + fragments = [] # type: List[bytes] + decoder.read_fragmented( + lambda size: fragments.append(decoder.read_bytes(size)) + ) + data = b"".join(fragments) if standalone: - return data, dec.remaining() + return data, decoder.remaining() return data, b"" @@ -791,24 +849,31 @@ def encode_into(cls, maximum = uper_max if size_len: minimum = maximum = size_len + if minimum is not None and maximum is not None: + if not minimum <= nbits <= maximum: + raise UPER_Encoding_Error( + "UPERcodec_BIT_STRING: got %i bits while expecting %s" % + (nbits, minimum if minimum == maximum + else "%i..%i" % (minimum, maximum)) + ) if minimum is not None and maximum is not None and minimum == maximum: - if nbits >= minimum: - value = int.from_bytes(s, "big") >> (8 * len(s) - minimum) - elif isinstance(_s, str) and _s and all(c in "01" for c in _s): - value = int(_s, 2) - elif nbits > 0: - value = int.from_bytes(s, "big") >> max(0, 8 * len(s) - nbits) - else: - value = 0 - enc.append_non_negative_binary_integer(value, minimum) + enc.append_bits(s, nbits) elif minimum is not None and maximum is not None: enc.append_non_negative_binary_integer( nbits - minimum, UPER_bits_for_range(maximum - minimum) ) enc.append_bits(s, nbits) else: - enc.append_length_determinant((nbits + 7) // 8) - enc.append_bytes(s) + # 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, @@ -831,10 +896,18 @@ def dec_from_decoder(cls, UPER_bits_for_range(maximum - minimum) ) else: - nbytes = dec.read_length_determinant() - raw = dec.read_bytes(nbytes) - nbits = 8 * nbytes - return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) + 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)) @@ -931,14 +1004,17 @@ def encode_into(cls, enc, _oid, **_kwargs): else: lst = [] body = b"".join(BER_num_enc(k) for k in lst) - enc.append_length_determinant(len(body)) - enc.append_bytes(body) + 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] - length = dec.read_length_determinant() - content = dec.read_bytes(length) + 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) @@ -1256,15 +1332,19 @@ def sequence_encode_into(field, enc, pkt, value=None): def sequence_of_m2i(field, pkt, s): # type: (Any, Any, bytes) -> Tuple[list, bytes] dec = UPER_Decoder(s) + lst = [] + + def read_items(count): + # type: (int) -> None + for _ in range(count): + c, _ = _extract_packet_from_decoder(field, dec, pkt) + if c: + lst.append(c) + if _field_extensible(field) and dec.read_bit(): - count = dec.read_length_determinant() + dec.read_fragmented(read_items) else: - count = _uper_count_dec(field, dec) - lst = [] - for _ in range(count): - c, _ = _extract_packet_from_decoder(field, dec, pkt) - if c: - lst.append(c) + _uper_count_dec(field, dec, read_items) if UPER_has_unexpected_remainder(dec): raise UPER_Decoding_Error( "unexpected remainder", @@ -1292,14 +1372,18 @@ def sequence_of_build(field, pkt): @staticmethod def 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(): - count = dec.read_length_determinant() + dec.read_fragmented(read_items) else: - count = _uper_count_dec(field, dec) - lst = [] - for _ in range(count): - item, _ = _extract_packet_from_decoder(field, dec, pkt) - lst.append(item) + _uper_count_dec(field, dec, read_items) return lst @staticmethod @@ -1308,9 +1392,18 @@ def sequence_of_encode_into(field, enc, pkt, value=None): if value is None: value = getattr(pkt, field.name) if value is None: - _uper_count_enc(field, enc, 0) + _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 ( @@ -1320,19 +1413,9 @@ def sequence_of_encode_into(field, enc, pkt, value=None): enc.append_bit(0) else: enc.append_bit(1) - enc.append_length_determinant(count) - for item in value: - if field.holds_packets: - item.ASN1_root.encode_into(enc, item) - else: - field.fld.encode_into(enc, pkt, item) + enc.append_fragmented(count, append_items) return - _uper_count_enc(field, enc, count) - for item in value: - if field.holds_packets: - item.ASN1_root.encode_into(enc, item) - else: - field.fld.encode_into(enc, pkt, item) + _uper_count_enc(field, enc, count, append_items) @staticmethod def choice_m2i(field, pkt, s): @@ -1461,25 +1544,30 @@ def _choice_index_for(field, x): return None -def _uper_count_enc(field, enc, count): - # type: (Any, Any, int) -> None +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(count, uper_min, uper_max, enc=enc) + append_items(0, count) else: - enc.append_length_determinant(count) + enc.append_fragmented(count, append_items) -def _uper_count_dec(field, dec): - # type: (Any, Any) -> int +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: size = uper_max - uper_min - return ( + read_items( dec.read_non_negative_binary_integer(UPER_bits_for_range(size)) + uper_min ) - return dec.read_length_determinant() + else: + dec.read_fragmented(read_items) def _extract_packet_from_decoder(field, dec, pkt): diff --git a/test/contrib/oer.uts b/test/contrib/oer.uts index 748b08e6f9c..5ec01bac174 100644 --- a/test/contrib/oer.uts +++ b/test/contrib/oer.uts @@ -96,14 +96,36 @@ INTEGER_VECTORS = [ lambda v: OERcodec_INTEGER.enc(v, size_len=8), b"\xff\xff\xff\xff\xff\xff\xff\xfe", ), - ("F", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\x80"), - ("G", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x00\x80"), - ("G", 1000, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\x03\xe8"), - ("H", 128, lambda v: OERcodec_INTEGER.enc(v, size_len=4), b"\x00\x00\x00\x80"), + # 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), + 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"), @@ -358,7 +380,7 @@ OER_unsigned_integer_enc(0) == b"\x01\x00" 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) == b"\xff" +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 @@ -406,7 +428,7 @@ 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), size_len=1, oer_unsigned=True) +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) @@ -1086,3 +1108,59 @@ 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 diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts index 11edfb5de23..df792f54162 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -1230,7 +1230,6 @@ for length, expected in [ (127, b"\x7f"), (128, b"\x80\x80"), (16383, b"\xbf\xff"), - (16384, b"\xc1"), ]: enc = UPER_Encoder() enc.append_length_determinant(length) @@ -1238,6 +1237,49 @@ for length, expected in [ 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() @@ -1806,23 +1848,23 @@ assert "Already decoded" in str(err2) True = uper length determinant extended -enc = UPER_Encoder() - -assert enc.append_length_determinant(32768) == 32768 - -assert enc.as_bytes() == b"\xc2" - -enc = UPER_Encoder() +# 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 enc.append_length_determinant(49152) == 49152 +assert _fragment_headers(32768) == (b"\xc2\x00", [32768, 0]) -assert enc.as_bytes() == b"\xc3" +assert _fragment_headers(49152) == (b"\xc3\x00", [49152, 0]) -enc = UPER_Encoder() +assert _fragment_headers(65535) == (b"\xc3\xbf\xff", [49152, 16383]) -assert enc.append_length_determinant(65535) == 49152 +assert _fragment_headers(65536) == (b"\xc4\x00", [65536, 0]) -assert enc.as_bytes() == b"\xc3" +assert _fragment_headers(81920) == (b"\xc4\xc1\x00", [65536, 16384, 0]) True @@ -2837,3 +2879,94 @@ 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 + From f90d7c1d412f5c8c4fc8d83600f930340056e692 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 12:45:06 +0200 Subject: [PATCH 11/19] oer, uper: remove dead helpers and deduplicate the codecs The UPER encoder and decoder each carried an align_always method, which is meaningless for an unaligned codec and had no caller. A family of standalone helpers (join_encodings, optional_presence_enc, count_enc, count_dec, constrained_int_dec, unconstrained_int_dec and boolean_dec) was likewise reachable only from its own tests, and OER kept copies of the BER check_type and check_type_get_len that only BER itself calls. UPERcodec_SEQUENCE spliced a raw byte string into the bitstream by guessing how many of its trailing zero bits were padding, which drops bits from a sequence that legitimately ends in zeroes. Nothing reaches it, as sequences are encoded through the ASN1F_SEQUENCE hooks, so it now refuses the input like its decoding counterpart already did rather than corrupting it silently. The surviving helpers took an optional encoder and returned either the finished bytes or b"", while every caller passed one and the decoding side passed b"" as a dummy first argument; they now take the encoder or the decoder directly, as encode_into already did. The minimal two's complement sizing, the bit to byte packing and the decode-and-check- remainder wrapper were each written out several times over, and codec methods declared options they never read, which hid the ones they do honour. No encoding changes: the ASN.1 suites, the asn1tools differential fuzzing and the malformed input fuzzing pass unchanged. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/contrib/oer.py | 81 +++---- scapy/contrib/uper.py | 552 +++++++++++------------------------------- test/contrib/uper.uts | 62 ++--- 3 files changed, 198 insertions(+), 497 deletions(-) diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 44dbd22d105..beb4079a058 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -158,22 +158,12 @@ def OER_len_dec(s): def OER_signed_integer_enc(i): # type: (int) -> bytes - if i < 0: - number_of_bits = i.bit_length() - number_of_bytes = (number_of_bits + 7) // 8 - value = (1 << (8 * number_of_bytes)) + i - if (value & (1 << (8 * number_of_bytes - 1))) == 0: - value |= (0xff << (8 * number_of_bytes)) - number_of_bytes += 1 - elif i > 0: - number_of_bits = i.bit_length() - number_of_bytes = (number_of_bits + 7) // 8 - if number_of_bits == (8 * number_of_bytes): - number_of_bytes += 1 - value = i - else: - number_of_bytes = 1 - value = 0 + # 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") @@ -223,11 +213,15 @@ def OER_unsigned_integer_dec(s): 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 = {1: ">b", 2: ">h", 4: ">i", 8: ">q"} if signed else { - 1: ">B", 2: ">H", 4: ">I", 8: ">Q" - } + fmt = _OER_FIXED_FORMATS[signed] try: return struct.pack(fmt[length], i) except KeyError: @@ -249,9 +243,7 @@ def OER_fixed_integer_dec(s, length, signed=True): (len(s), length), remaining=s ) - fmt = {1: ">b", 2: ">h", 4: ">i", 8: ">q"} if signed else { - 1: ">B", 2: ">H", 4: ">I", 8: ">Q" - } + fmt = _OER_FIXED_FORMATS[signed] try: return struct.unpack(fmt[length], s[:length])[0], s[length:] except KeyError: @@ -449,18 +441,6 @@ def check_string(cls, s): (cls.__name__, cls.tag), remaining=s ) - @classmethod - def check_type(cls, s): - # type: (bytes) -> bytes - cls.check_string(s) - return s - - @classmethod - def check_type_get_len(cls, s): - # type: (bytes) -> Tuple[int, bytes] - cls.check_string(s) - return len(s), s - @classmethod def check_type_check_len(cls, s): # type: (bytes) -> Tuple[int, bytes, bytes] @@ -582,8 +562,8 @@ class OERcodec_BOOLEAN(OERcodec_Object[int]): tag = ASN1_Class_UNIVERSAL.BOOLEAN @classmethod - def enc(cls, i, size_len=0, **_kwargs): - # type: (int, Optional[int], **Any) -> bytes + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes return chb(0xff if i else 0x00) @classmethod @@ -721,8 +701,8 @@ class OERcodec_NULL(OERcodec_Object[None]): tag = ASN1_Class_UNIVERSAL.NULL @classmethod - def enc(cls, i, size_len=0, **_kwargs): - # type: (Any, Optional[int], **Any) -> bytes + def enc(cls, i, **_kwargs): + # type: (Any, **Any) -> bytes return b"" @classmethod @@ -741,8 +721,8 @@ class OERcodec_OID(OERcodec_Object[bytes]): tag = ASN1_Class_UNIVERSAL.OID @classmethod - def enc(cls, _oid, size_len=0, **_kwargs): - # type: (AnyStr, Optional[int], **Any) -> bytes + 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".")] @@ -787,8 +767,8 @@ class OERcodec_ENUMERATED(OERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.ENUMERATED @classmethod - def enc(cls, i, size_len=0, **_kwargs): - # type: (int, Optional[int], **Any) -> bytes + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes return OER_enumerated_enc(i) @classmethod @@ -856,8 +836,8 @@ class OERcodec_SEQUENCE(OERcodec_Object[Union[bytes, List['OERcodec_Object[Any]' tag = ASN1_Class_UNIVERSAL.SEQUENCE @classmethod - def enc(cls, _ll, size_len=0, **_kwargs): - # type: (Union[bytes, List[OERcodec_Object[Any]]], Optional[int], **Any) -> bytes # noqa: E501 + 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) @@ -1031,15 +1011,12 @@ def sequence_of_build(field, pkt): val = getattr(pkt, field.name) if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: s = val # type: Any - elif val is None: - s = OER_unsigned_integer_enc(0) - elif field.holds_packets: - s = OER_unsigned_integer_enc(len(val)) + b"".join(bytes(i) for i in val) else: - s = ( - OER_unsigned_integer_enc(len(val)) + - b"".join(field.fld.i2m(pkt, i) for i in val) - ) + 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) @staticmethod diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index 847e54de7f2..fb15b9285f6 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -23,8 +23,6 @@ per character. """ -import binascii - from scapy.error import warning from scapy.compat import orb, bytes_encode from scapy.utils import binrepr, inet_aton, inet_ntoa @@ -125,6 +123,16 @@ def UPER_bits_for_range(size): 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 @@ -137,15 +145,6 @@ def number_of_bytes(self): # type: () -> int return (self.chunks_number_of_bits + self.number_of_bits + 7) // 8 - def align_always(self): - # type: () -> None - width = 8 * self.number_of_bytes() - width -= self.chunks_number_of_bits - width -= self.number_of_bits - if width: - self.number_of_bits += width - self.value <<= width - def append_bit(self, bit): # type: (int) -> None self.number_of_bits += 1 @@ -213,23 +212,15 @@ def append_fragmented(self, count, append_units): def append_unconstrained_whole_number(self, value): # type: (int) -> None - number_of_bits = 0 if value == 0 else value.bit_length() - if value < 0: - number_of_bytes = (number_of_bits + 7) // 8 - enc = (1 << (8 * number_of_bytes)) + value - if enc & (1 << (8 * number_of_bytes - 1)) == 0: - enc |= (0xff << (8 * number_of_bytes)) - number_of_bytes += 1 - elif value > 0: - number_of_bytes = (number_of_bits + 7) // 8 - if number_of_bits == 8 * number_of_bytes: - number_of_bytes += 1 - enc = value - else: - number_of_bytes = 1 - enc = 0 + # 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(enc, 8 * 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 @@ -242,87 +233,7 @@ def as_bytes(self): value <<= self.number_of_bits value |= self.value number_of_bits += self.number_of_bits - if number_of_bits == 0: - return b"" - number_of_alignment_bits = (8 - (number_of_bits % 8)) % 8 - value <<= number_of_alignment_bits - number_of_bits += number_of_alignment_bits - value |= (0x80 << number_of_bits) - hexval = hex(value)[4:].rstrip("L") - if len(hexval) % 2: - hexval = "0" + hexval - return binascii.unhexlify(hexval) - - -def _uper_significant_bit_count(data): - # type: (bytes) -> int - if not data: - return 0 - total = 8 * len(data) - bits = int.from_bytes(data, "big") - end = total - while end > 0 and ((bits >> (total - end)) & 1) == 0: - end -= 1 - trimmed = total - end - if trimmed > 0 and trimmed <= 8: - return end - return total - - -def _uper_per_bits_to_bytes(bit_value, number_of_bits): - # type: (int, int) -> bytes - if number_of_bits == 0: - return b"" - bitstr = format(bit_value, "0%db" % number_of_bits) - value = "10000000" + bitstr - number_of_alignment_bits = (8 - (number_of_bits % 8)) - if number_of_alignment_bits != 8: - value += "0" * number_of_alignment_bits - hexval = hex(int(value, 2))[4:].rstrip("L") - if len(hexval) % 2: - hexval = "0" + hexval - return binascii.unhexlify(hexval) - - -def UPER_append_encoded(enc, data): - # type: (UPER_Encoder, bytes) -> None - if not data: - return - nbits = _uper_significant_bit_count(data) - if nbits == 0: - return - total = 8 * len(data) - bits = int.from_bytes(data, "big") - shift = total - nbits - value = (bits >> shift) & ((1 << nbits) - 1) - enc.append_non_negative_binary_integer(value, nbits) - - -def UPER_join_encodings(*parts): - # type: (*bytes) -> bytes - enc = UPER_Encoder() - for part in parts: - UPER_append_encoded(enc, part) - return enc.as_bytes() - - -def UPER_optional_presence_enc(bits, enc=None): - # type: (List[int], Optional[UPER_Encoder]) -> bytes - standalone = enc is None - if enc is None: - enc = UPER_Encoder() - for bit in bits: - enc.append_bit(bit) - return enc.as_bytes() if standalone else b"" - - -def UPER_count_enc(count, enc=None): - # type: (int, Optional[UPER_Encoder]) -> bytes - standalone = enc is None - if enc is None: - enc = UPER_Encoder() - enc.append_length_determinant(count) - return enc.as_bytes() if standalone else b"" + return _uper_bits_to_bytes(value, number_of_bits) def UPER_has_unexpected_remainder(dec): @@ -333,17 +244,6 @@ def UPER_has_unexpected_remainder(dec): return (dec._bits & mask) != 0 -def UPER_count_dec(s, dec=None): - # type: (bytes, Optional[UPER_Decoder]) -> Tuple[int, bytes] - standalone = dec is None - if dec is None: - dec = UPER_Decoder(s) - count = dec.read_length_determinant() - if standalone: - return count, dec.remaining() - return count, b"" - - class UPER_Decoder(object): def __init__(self, encoded): # type: (bytes) -> None @@ -383,14 +283,14 @@ def read_bits(self, number_of_bits): return b"" value = self._read_bits_int(number_of_bits) self.number_of_bits -= number_of_bits - return _uper_per_bits_to_bytes(value, 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_per_bits_to_bytes(value, self.number_of_bits) + return _uper_bits_to_bytes(value, self.number_of_bits) def remaining_bytes(self): # type: () -> bytes @@ -415,15 +315,6 @@ def read_non_negative_binary_integer(self, number_of_bits): self.number_of_bits -= number_of_bits return value - def align_always(self): - # type: () -> None - consumed = self.total_number_of_bits - self.number_of_bits - width = (8 - (consumed % 8)) % 8 - if width: - if width > self.number_of_bits: - raise UPER_Decoding_Error("UPER_Decoder: out of data") - self.number_of_bits -= width - def _read_length_determinant(self): # type: () -> Tuple[int, bool] # Returns the number of units and whether more fragments follow. @@ -479,135 +370,63 @@ def consume_input(self): self.number_of_bits = 0 -def UPER_constrained_int_enc(value, minimum, maximum, enc=None): - # type: (int, int, int, Optional[UPER_Encoder]) -> bytes - standalone = enc is None - if enc is None: - enc = UPER_Encoder() - size = maximum - minimum +def UPER_constrained_int_enc(enc, value, minimum, maximum): + # type: (UPER_Encoder, int, int, int) -> None enc.append_non_negative_binary_integer( - value - minimum, UPER_bits_for_range(size) + value - minimum, UPER_bits_for_range(maximum - minimum) ) - return enc.as_bytes() if standalone else b"" -def UPER_constrained_int_dec(s, minimum, maximum): - # type: (bytes, int, int) -> Tuple[int, bytes] - dec = UPER_Decoder(s) - size = maximum - minimum - value = dec.read_non_negative_binary_integer(UPER_bits_for_range(size)) - dec.consume_input() - return value + minimum, b"" - - -def UPER_constrained_int_dec_from_decoder(dec, minimum, maximum): +def UPER_constrained_int_dec(dec, minimum, maximum): # type: (UPER_Decoder, int, int) -> int - size = maximum - minimum - value = dec.read_non_negative_binary_integer(UPER_bits_for_range(size)) + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) return value + minimum -def UPER_unconstrained_int_enc(value, enc=None): - # type: (int, Optional[UPER_Encoder]) -> bytes - standalone = enc is None - if enc is None: - enc = UPER_Encoder() - enc.append_unconstrained_whole_number(value) - return enc.as_bytes() if standalone else b"" - - -def UPER_unconstrained_int_dec(s): - # type: (bytes) -> Tuple[int, bytes] - dec = UPER_Decoder(s) - value = dec.read_unconstrained_whole_number() - remain = dec.remaining() - return value, remain - - -def UPER_boolean_enc(value, enc=None): - # type: (int, Optional[UPER_Encoder]) -> bytes - standalone = enc is None - if enc is None: - enc = UPER_Encoder() - enc.append_bit(1 if value else 0) - return enc.as_bytes() if standalone else b"" - - -def UPER_boolean_dec(s): - # type: (bytes) -> Tuple[int, bytes] - dec = UPER_Decoder(s) - value = dec.read_bit() - dec.consume_input() - return value, b"" - - -def UPER_octet_string_enc(data, minimum=None, maximum=None, enc=None): - # type: (bytes, Optional[int], Optional[int], Optional[UPER_Encoder]) -> bytes - standalone = enc is None - encoder = UPER_Encoder() if enc is None else enc - if minimum is not None and maximum is not None and minimum == maximum: - encoder.append_bytes(data) - elif minimum is not None and maximum is not None: - encoder.append_non_negative_binary_integer( - len(data) - minimum, - UPER_bits_for_range(maximum - minimum), - ) - encoder.append_bytes(data) +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: + if minimum != maximum: + enc.append_non_negative_binary_integer( + len(data) - minimum, + UPER_bits_for_range(maximum - minimum), + ) + enc.append_bytes(data) else: - encoder.append_fragmented( + enc.append_fragmented( len(data), - lambda offset, size: encoder.append_bytes( - data[offset:offset + size] - ), + lambda offset, size: enc.append_bytes(data[offset:offset + size]), ) - return encoder.as_bytes() if standalone else b"" - - -def UPER_octet_string_dec(s, minimum=None, maximum=None, dec=None): - # type: (bytes, Optional[int], Optional[int], Optional[UPER_Decoder]) -> Tuple[bytes, bytes] # noqa: E501 - standalone = dec is None - decoder = UPER_Decoder(s) if dec is None else dec - if minimum is not None and maximum is not None and minimum == maximum: - data = decoder.read_bytes(minimum) - elif minimum is not None and maximum is not None: - data = decoder.read_bytes( - minimum + decoder.read_non_negative_binary_integer( + + +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) ) - ) - else: - fragments = [] # type: List[bytes] - decoder.read_fragmented( - lambda size: fragments.append(decoder.read_bytes(size)) - ) - data = b"".join(fragments) - if standalone: - return data, decoder.remaining() - return data, b"" + 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(index, number_of_choices, enc=None): - # type: (int, int, Optional[UPER_Encoder]) -> bytes - standalone = enc is None - if enc is None: - enc = UPER_Encoder() +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) ) - return enc.as_bytes() if standalone else b"" -def UPER_choice_index_dec(s, number_of_choices, dec=None): - # type: (bytes, int, Optional[UPER_Decoder]) -> Tuple[int, bytes] - standalone = dec is None - if dec is None: - dec = UPER_Decoder(s) - index = dec.read_non_negative_binary_integer( +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) ) - if standalone: - return index, dec.remaining() - return index, b"" class UPERcodec_metaclass(type): @@ -749,12 +568,12 @@ def encode_into(cls, enc.append_bit(0) else: enc.append_bit(1) - UPER_unconstrained_int_enc(i, enc=enc) + enc.append_unconstrained_whole_number(i) return if minimum is not None and maximum is not None: - UPER_constrained_int_enc(i, minimum, maximum, enc=enc) + UPER_constrained_int_enc(enc, i, minimum, maximum) else: - UPER_unconstrained_int_enc(i, enc=enc) + enc.append_unconstrained_whole_number(i) @classmethod def dec_from_decoder(cls, @@ -773,7 +592,7 @@ def dec_from_decoder(cls, 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_from_decoder(dec, minimum, maximum) + value = UPER_constrained_int_dec(dec, minimum, maximum) else: value = dec.read_unconstrained_whole_number() return cls.asn1_object(value) @@ -783,28 +602,13 @@ class UPERcodec_BOOLEAN(UPERcodec_Object[int]): tag = ASN1_Class_UNIVERSAL.BOOLEAN @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 - **_kwargs # type: Any - ): - # type: (...) -> None - UPER_boolean_enc(i, enc=enc) + 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, # 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 - **_kwargs # type: Any - ): - # type: (...) -> ASN1_Object[int] + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[int] return cls.asn1_object(dec.read_bit()) @@ -830,6 +634,15 @@ def _uper_bit_string_parts(_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 @@ -840,15 +653,11 @@ def encode_into(cls, size_len=0, # type: Optional[int] uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool **_kwargs # type: Any ): # type: (...) -> None s, nbits = _uper_bit_string_parts(_s) - minimum = uper_min - maximum = uper_max - if size_len: - minimum = maximum = size_len + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) if minimum is not None and maximum is not None: if not minimum <= nbits <= maximum: raise UPER_Encoding_Error( @@ -856,12 +665,10 @@ def encode_into(cls, (nbits, minimum if minimum == maximum else "%i..%i" % (minimum, maximum)) ) - if minimum is not None and maximum is not None and minimum == maximum: - enc.append_bits(s, nbits) - elif minimum is not None and maximum is not None: - enc.append_non_negative_binary_integer( - nbits - minimum, UPER_bits_for_range(maximum - minimum) - ) + 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 @@ -881,20 +688,16 @@ def dec_from_decoder(cls, size_len=0, # type: Optional[int] uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool **_kwargs # type: Any ): # type: (...) -> ASN1_Object[str] - minimum = uper_min - maximum = uper_max - if size_len: - minimum = maximum = size_len - if minimum is not None and maximum is not None and minimum == maximum: + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + if minimum is not None and maximum is not None: nbits = minimum - elif minimum is not None and maximum is not None: - nbits = minimum + dec.read_non_negative_binary_integer( - UPER_bits_for_range(maximum - 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] @@ -912,13 +715,6 @@ def read_fragment(size): return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) -def _uper_octet_string_bounds(size_len, uper_min, uper_max): - # type: (Optional[int], Optional[int], Optional[int]) -> Tuple[Optional[int], Optional[int]] # noqa: E501 - if size_len: - return size_len, size_len - return uper_min, uper_max - - class UPERcodec_STRING(UPERcodec_Object[str]): tag = ASN1_Class_UNIVERSAL.STRING @@ -929,15 +725,12 @@ def encode_into(cls, size_len=0, # type: Optional[int] uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool **_kwargs # type: Any ): # type: (...) -> None s = bytes_encode(_s) - minimum, maximum = _uper_octet_string_bounds( - size_len, uper_min, uper_max, - ) - UPER_octet_string_enc(s, minimum, maximum, enc=enc) + 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, @@ -945,14 +738,11 @@ def dec_from_decoder(cls, size_len=0, # type: Optional[int] uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool **_kwargs # type: Any ): # type: (...) -> ASN1_Object[Any] - minimum, maximum = _uper_octet_string_bounds( - size_len, uper_min, uper_max, - ) - raw, _ = UPER_octet_string_dec(b"", minimum, maximum, dec=dec) + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + raw = UPER_octet_string_dec(dec, minimum, maximum) return cls.asn1_object(raw) @@ -960,28 +750,14 @@ class UPERcodec_NULL(UPERcodec_Object[None]): tag = ASN1_Class_UNIVERSAL.NULL @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] - oer_unsigned=False, # type: bool - **_kwargs # type: Any - ): - # type: (...) -> None + 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, # 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 - **_kwargs # type: Any - ): - # type: (...) -> ASN1_Object[None] + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[None] return cls.asn1_object(None) @classmethod @@ -1025,14 +801,8 @@ def dec_from_decoder(cls, dec, **_kwargs): return cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)) -def UPER_enumerated_enc(value, - enum_values, # type: List[int] - enc=None # type: Optional[UPER_Encoder] - ): - # type: (int, List[int], Optional[UPER_Encoder]) -> bytes - standalone = enc is None - if enc is None: - enc = UPER_Encoder() +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: @@ -1041,29 +811,19 @@ def UPER_enumerated_enc(value, raise UPER_Encoding_Error( "UPER_enumerated_enc: unknown enumeration value %r" % value ) - UPER_choice_index_enc(index, len(enum_values), enc=enc) - return enc.as_bytes() if standalone else b"" + UPER_choice_index_enc(enc, index, len(enum_values)) -def UPER_enumerated_dec(s, - enum_values, # type: List[int] - dec=None # type: Optional[UPER_Decoder] - ): - # type: (bytes, List[int], Optional[UPER_Decoder]) -> Tuple[int, bytes] - standalone = dec is None - if dec is None: - dec = UPER_Decoder(s) +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(b"", len(enum_values), dec=dec) + 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 ) - if standalone: - dec.consume_input() - return enum_values[index], b"" - return enum_values[index], b"" + return enum_values[index] class UPERcodec_ENUMERATED(UPERcodec_INTEGER): @@ -1076,19 +836,18 @@ def encode_into(cls, size_len=0, # type: Optional[int] uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool uper_enum_values=None, # type: Optional[List[int]] **_kwargs # type: Any ): # type: (...) -> None if uper_enum_values is not None: - UPER_enumerated_enc(i, uper_enum_values, enc=enc) + UPER_enumerated_enc(enc, i, uper_enum_values) return minimum = uper_min if uper_min is not None else 0 maximum = uper_max if uper_max is not None else size_len if maximum is None: maximum = max(i, 0) - UPER_constrained_int_enc(i, minimum, maximum, enc=enc) + UPER_constrained_int_enc(enc, i, minimum, maximum) @classmethod def dec_from_decoder(cls, @@ -1096,13 +855,12 @@ def dec_from_decoder(cls, size_len=0, # type: Optional[int] uper_min=None, # type: Optional[int] uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool uper_enum_values=None, # type: Optional[List[int]] **_kwargs # type: Any ): # type: (...) -> ASN1_Object[int] if uper_enum_values is not None: - value, _ = UPER_enumerated_dec(b"", uper_enum_values, dec=dec) + value = UPER_enumerated_dec(dec, uper_enum_values) return cls.asn1_object(value) minimum = uper_min if uper_min is not None else 0 maximum = uper_max if uper_max is not None else size_len @@ -1118,18 +876,14 @@ class UPERcodec_SEQUENCE(UPERcodec_Object[Union[bytes, List[Any]]]): tag = ASN1_Class_UNIVERSAL.SEQUENCE @classmethod - def encode_into(cls, - enc, # type: UPER_Encoder - _ll, # type: Union[bytes, List[UPERcodec_Object[Any]]] - size_len=0, # type: Optional[int] - uper_min=None, # type: Optional[int] - uper_max=None, # type: Optional[int] - oer_unsigned=False, # type: bool - **_kwargs # type: Any - ): - # type: (...) -> None - if isinstance(_ll, bytes): - UPER_append_encoded(enc, _ll) + 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): @@ -1163,12 +917,12 @@ def encode_into(cls, enc, ipaddr_ascii, **_kwargs): s = inet_aton(ipaddr_ascii) except Exception: raise UPER_Encoding_Error("IPv4 address could not be encoded") - UPER_octet_string_enc(s, 4, 4, enc=enc) + 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(b"", 4, 4, dec=dec) + raw = UPER_octet_string_dec(dec, 4, 4) try: ipaddr_ascii = inet_ntoa(raw) except Exception: @@ -1256,6 +1010,20 @@ def _field_range(field): 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 + + class _UPER_FieldHooks(object): """Compound ASN1F_* helpers for UPER/PER (kept out of asn1fields.py).""" @@ -1268,13 +1036,9 @@ def use_object_enc(field, pkt, item): @staticmethod def sequence_m2i(field, pkt, s): # type: (Any, Any, bytes) -> Tuple[Any, bytes] - dec = UPER_Decoder(s) - _UPER_FieldHooks.sequence_dissect_from_decoder(field, pkt, dec) - if UPER_has_unexpected_remainder(dec): - raise UPER_Decoding_Error( - "unexpected remainder", - remaining=dec.remaining(), - ) + _uper_decode_all(s, lambda dec: ( + _UPER_FieldHooks.sequence_dissect_from_decoder(field, pkt, dec) + )) return [], b"" @staticmethod @@ -1331,26 +1095,9 @@ def sequence_encode_into(field, enc, pkt, value=None): @staticmethod def sequence_of_m2i(field, pkt, s): # type: (Any, Any, bytes) -> Tuple[list, bytes] - dec = UPER_Decoder(s) - lst = [] - - def read_items(count): - # type: (int) -> None - for _ in range(count): - c, _ = _extract_packet_from_decoder(field, dec, pkt) - if c: - lst.append(c) - - if _field_extensible(field) and dec.read_bit(): - dec.read_fragmented(read_items) - else: - _uper_count_dec(field, dec, read_items) - if UPER_has_unexpected_remainder(dec): - raise UPER_Decoding_Error( - "unexpected remainder", - remaining=dec.remaining(), - ) - return lst, b"" + return _uper_decode_all(s, lambda dec: ( + _UPER_FieldHooks.sequence_of_m2i_from_decoder(field, pkt, dec) + )), b"" @staticmethod def sequence_of_build(field, pkt): @@ -1377,7 +1124,7 @@ def sequence_of_m2i_from_decoder(field, pkt, dec): def read_items(count): # type: (int) -> None for _ in range(count): - item, _ = _extract_packet_from_decoder(field, dec, pkt) + item = _extract_packet_from_decoder(field, dec, pkt) lst.append(item) if _field_extensible(field) and dec.read_bit(): @@ -1420,14 +1167,9 @@ def append_items(offset, size): @staticmethod def choice_m2i(field, pkt, s): # type: (Any, Any, bytes) -> Tuple[Any, bytes] - dec = UPER_Decoder(s) - val = _UPER_FieldHooks.choice_m2i_from_decoder(field, pkt, dec) - if UPER_has_unexpected_remainder(dec): - raise UPER_Decoding_Error( - "unexpected remainder", - remaining=dec.remaining(), - ) - return val, b"" + return _uper_decode_all(s, lambda dec: ( + _UPER_FieldHooks.choice_m2i_from_decoder(field, pkt, dec) + )), b"" @staticmethod def choice_i2m(field, pkt, x): @@ -1451,7 +1193,7 @@ def choice_m2i_from_decoder(field, pkt, dec): ) order = field.choice_order if len(order) > 1: - index, _ = UPER_choice_index_dec(b"", len(order), dec=dec) + index = UPER_choice_index_dec(dec, len(order)) else: index = 0 if index >= len(order): @@ -1485,7 +1227,7 @@ def choice_encode_into(field, enc, pkt, value=None): enc.append_bit(0) order = field.choice_order if len(order) > 1: - UPER_choice_index_enc(index, len(order), enc=enc) + 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) @@ -1551,7 +1293,7 @@ def _uper_count_enc(field, enc, count, append_items): # 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(count, uper_min, uper_max, enc=enc) + UPER_constrained_int_enc(enc, count, uper_min, uper_max) append_items(0, count) else: enc.append_fragmented(count, append_items) @@ -1561,23 +1303,19 @@ 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: - size = uper_max - uper_min - read_items( - dec.read_non_negative_binary_integer(UPER_bits_for_range(size)) + - uper_min - ) + 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) -> Tuple[Any, bytes] + # 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, b"" - return field.fld.m2i_from_decoder(pkt, dec), b"" + return p + return field.fld.m2i_from_decoder(pkt, dec) # Populated by _install_uper_asn1fields() (also published on scapy.asn1fields). diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts index df792f54162..3c043938841 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -360,7 +360,7 @@ def _encode_composite(typename, value): if typename == "Choice": alt, payload = value index = 0 if alt == "a" else 1 - UPER_choice_index_enc(index, 2, enc=enc) + UPER_choice_index_enc(enc, index, 2) if alt == "a": UPERcodec_INTEGER.encode_into(enc, payload) else: @@ -369,7 +369,7 @@ def _encode_composite(typename, value): if typename == "ChoiceC": alt, payload = value index = 0 if alt == "a" else 1 - UPER_choice_index_enc(index, 2, enc=enc) + UPER_choice_index_enc(enc, index, 2) if alt == "a": UPERcodec_INTEGER.encode_into( enc, payload, uper_min=0, uper_max=15, @@ -490,7 +490,7 @@ ASN1SCC_VECTORS = [ def _encode_choice_int1_10(): # type: () -> bytes enc = UPER_Encoder() - UPER_choice_index_enc(0, 5, enc=enc) + UPER_choice_index_enc(enc, 0, 5) UPERcodec_INTEGER.encode_into(enc, 10, uper_min=0, uper_max=15) return enc.as_bytes() @@ -1283,17 +1283,16 @@ True = uper count roundtrip for count in [0, 1, 3, 127]: enc = UPER_Encoder() - UPER_count_enc(count, enc=enc) - got, _ = UPER_count_dec(enc.as_bytes()) - assert got == count + 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(index, choices, enc=enc) - got, _ = UPER_choice_index_dec(enc.as_bytes(), choices) + UPER_choice_index_enc(enc, index, choices) + got = UPER_choice_index_dec(UPER_Decoder(enc.as_bytes()), choices) assert got == index True @@ -1301,30 +1300,29 @@ True = uper optional presence enc = UPER_Encoder() -UPER_optional_presence_enc([0, 1, 0], enc=enc) +for bit in [0, 1, 0]: + enc.append_bit(bit) assert enc.as_bytes() == b"\x40" True = uper constrained integer -data = UPER_constrained_int_enc(10, 0, 15) - -value, remain = UPER_constrained_int_dec(data, 0, 15) +enc = UPER_Encoder() -assert value == 10 +UPER_constrained_int_enc(enc, 10, 0, 15) -assert remain == b"" +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")]: - data = UPER_constrained_int_enc(value, -128, 127) - assert data == expected - decoded, remain = UPER_constrained_int_dec(data, -128, 127) - assert decoded == value - assert remain == b"" + 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 @@ -1334,10 +1332,10 @@ for data, minimum, maximum in [ (b"\x12\x34\x56", 3, 3), (bytes.fromhex("afbc4583"), 1, 20), ]: - encoded = UPER_octet_string_enc(data, minimum, maximum) - dec = UPER_Decoder(encoded) - decoded, _ = UPER_octet_string_dec(encoded, minimum, maximum, dec=dec) - assert decoded == data + 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 @@ -1349,21 +1347,6 @@ assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x80")) is True True -= uper join encodings -a = UPERcodec_INTEGER.enc(1) - -b = UPERcodec_INTEGER.enc(2) - -joined = UPER_join_encodings(a, b) - -dec = UPER_Decoder(joined) - -assert dec.read_unconstrained_whole_number() == 1 - -assert dec.read_unconstrained_whole_number() == 2 - -True - = uper chained encode into enc = UPER_Encoder() @@ -1938,6 +1921,9 @@ _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 From a563d12c73f3f2dfb638accaa911840ef1d8b327 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 15:02:53 +0200 Subject: [PATCH 12/19] uper: fix enumerated indexing and octet string size constraints The enumeration index followed the order the values were declared in rather than their ascending order, as X.691 14.1 requires, so an ENUMERATED { c(2), a(0), b(1) } encoded a as index 1 where a conformant peer reads b. Enumerations written in ascending order, which is the usual case, were already correct. An extensible enumerated dropped the one bit prefix of 14.3, shifting every field encoded after it. The option could not be reached anyway, as ASN1F_enum_INTEGER was the one field class that did not forward its codec options, so uper_extensible= raised a TypeError instead of constraining the field. An OCTET STRING ignored its SIZE constraint while encoding, where BIT STRING and OER already raised: a two octet value in a SIZE(4) field emitted two octets, and in a SIZE(2..4) field an eight octet value wrote a determinant that wrapped, so the peer read a different length and lost everything that followed. Without an enumeration list and without declared bounds, the enumerated encoder took the upper bound from the value at hand, making the width depend on the value while the decoder refused the same case; a size_len of zero was also read as an upper bound of zero, which encoded every value in no bits at all. Encodings match asn1tools byte for byte, over the vectors added here as well as random schemas. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/asn1fields.py | 4 ++- scapy/contrib/uper.py | 64 +++++++++++++++++++++++++++++++++++-------- test/contrib/uper.uts | 63 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 364bde87a90..7adea44fd88 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -341,12 +341,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] diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index fb15b9285f6..0d70e951b4f 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -21,6 +21,11 @@ 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 @@ -388,6 +393,15 @@ def UPER_constrained_int_dec(dec, 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: + if not minimum <= len(data) <= maximum: + # 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. + raise UPER_Encoding_Error( + "UPER_octet_string_enc: got %i octets while expecting %s" % + (len(data), minimum if minimum == maximum + else "%i..%i" % (minimum, maximum)) + ) if minimum != maximum: enc.append_non_negative_binary_integer( len(data) - minimum, @@ -837,16 +851,25 @@ def encode_into(cls, 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 = uper_min if uper_min is not None else 0 - maximum = uper_max if uper_max is not None else size_len - if maximum is None: - maximum = max(i, 0) + minimum, maximum = cls._range( + size_len, uper_min, uper_max, UPER_Encoding_Error + ) UPER_constrained_int_enc(enc, i, minimum, maximum) @classmethod @@ -856,21 +879,38 @@ def dec_from_decoder(cls, 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: - value = UPER_enumerated_dec(dec, uper_enum_values) - return cls.asn1_object(value) - minimum = uper_min if uper_min is not None else 0 - maximum = uper_max if uper_max is not None else size_len - if maximum is None: - raise UPER_Decoding_Error("UPERcodec_ENUMERATED: missing range") + 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 @@ -1445,7 +1485,9 @@ def enum_codec_kwargs(self, pkt): # keep an empty codec_opts and their item.enc() fast path. codec = getattr(pkt, "ASN1_codec", None) if getattr(codec, "_field_hooks", None) is _UPER_FieldHooks: - kwargs.setdefault("uper_enum_values", list(self.i2s)) + # 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] diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts index 3c043938841..9c8994bdcb8 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -1916,6 +1916,69 @@ 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)])) From cb35a5a642218839421d59506b16a687d734df13 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 19:10:59 +0200 Subject: [PATCH 13/19] oer, uper: fix choice alternatives, OER tagging and DEFAULT components A CHOICE whose alternatives are ASN1F_PACKET instances, which is how a choice between tagged sequences is written and what BER already supports, could not be encoded: the alternative lookup only recognised packet classes and basic field classes. UPER refused the value outright and OER dropped the alternative tag, emitting bytes it could not read back. OER also let the BER constructed bit into the tag number it emitted, so an alternative tagged [0] went out as tag number 32 and an untagged SEQUENCE alternative as universal 48 instead of 16. The encoding round tripped with itself and with nothing else. Tags of components were encoded at all, where X.696 encodes none whatever the tagging environment of the module: the only tag on the wire is the one of the chosen CHOICE alternative. OER_tagging_enc and OER_tagging_dec are now the identity, and the alternative tag is emitted by the choice hook alone. ASN1F_DEFAULT was defined by importing scapy.contrib.uper, although a DEFAULT component is not specific to a codec and the OER documentation refers to it, which left OER users with a name they could not import and OER with a getattr fallback for the absent set_absent. It now lives in asn1fields, next to ASN1F_optional, and both codecs re-export it. BER gains from it too: ASN1F_optional.build asked the wrapped field whether it was empty, so a DEFAULT component holding its default value was encoded where DER omits it. Alternative tags and the encodings of the sequences behind them match asn1tools byte for byte. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/asn1fields.py | 41 ++++++++++++++- scapy/contrib/oer.py | 103 +++++++++++++++++++------------------ scapy/contrib/uper.py | 56 +++++--------------- test/contrib/oer.uts | 63 +++++++++++++++++++---- test/contrib/uper.uts | 40 ++++++++++++++ test/scapy/layers/asn1.uts | 33 +++++++++++- 6 files changed, 230 insertions(+), 106 deletions(-) diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 7adea44fd88..ab9f2aa1f08 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -750,12 +750,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) @@ -768,6 +778,33 @@ 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. + """ + 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. diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index beb4079a058..346ae6e7fb1 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -15,6 +15,10 @@ 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). """ @@ -39,6 +43,8 @@ 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, @@ -369,6 +375,15 @@ def OER_id_dec(s): return tag_class | tag_number, remainder +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 + + def OER_tagging_dec(s, # type: bytes hidden_tag=None, # type: Optional[int | ASN1Tag] implicit_tag=None, # type: Optional[int] @@ -377,30 +392,14 @@ def OER_tagging_dec(s, # type: bytes _fname="", # type: str ): # type: (...) -> Tuple[Optional[int], bytes] - # OER does not use implicit tagging. Explicit tags are encoded as choice - # alternatives (tag + value). - real_tag = None - if explicit_tag is not None and len(s) > 0: - err_msg = ( - "OER_tagging_dec: observed tag 0x%.02x does not " - "match expected tag 0x%.02x (%s)" - ) - tag_class, tag_number, remainder = OER_tag_dec(s) - observed = tag_class | tag_number - if observed != explicit_tag: - if not safe: - raise OER_Decoding_Error( - err_msg % (observed, explicit_tag, _fname), - remaining=s) - real_tag = observed - s = remainder - return real_tag, s + # X.696 encodes no tag for a component, whatever the tagging environment + # of the module: the only tag on the wire is the one of a chosen CHOICE + # alternative, which _OER_FieldHooks handles. + return None, s def OER_tagging_enc(s, implicit_tag=None, explicit_tag=None): # type: (bytes, Optional[int], Optional[int]) -> bytes - if explicit_tag is not None: - return OER_tag_enc(explicit_tag & 0x3f, explicit_tag & 0xc0) + s return s @@ -920,17 +919,6 @@ def _field_extensible(field): return bool(getattr(field, "codec_opts", {}).get("oer_extensible", False)) -def _set_absent(field, pkt): - # type: (Any, Any) -> None - # ASN1F_DEFAULT restores its default value; a plain optional clears itself. - # set_absent() only exists once scapy.contrib.uper has been imported. - set_absent = getattr(field, "set_absent", None) - if set_absent is not None: - set_absent(pkt) - else: - field.set_val(pkt, None) - - class _OER_FieldHooks(object): """Compound ASN1F_* helpers for OER (kept out of asn1fields.py).""" @@ -966,7 +954,7 @@ def sequence_m2i(field, pkt, s): present = presence[opt_index] opt_index += 1 if not present: - _set_absent(obj, pkt) + 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. @@ -1025,23 +1013,32 @@ def choice_m2i(field, pkt, s): from scapy.asn1fields import ASN1F_field from scapy.asn1.asn1 import ASN1_Error s = field._apply_tagging_dec(s, pkt) - tag, payload = OER_id_dec(s) - if tag in field.choices: - choice = field.choices[tag] - elif field.flexible_tag: - choice = ASN1F_field - else: - raise ASN1_Error( - "ASN1F_CHOICE: unexpected field in '%s' " - "(tag %s not in possible tags %s)" % ( - field.name, tag, list(field.choices.keys()) + 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) - return choice.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, + ) @staticmethod def choice_i2m(field, pkt, x): @@ -1056,7 +1053,8 @@ def choice_i2m(field, pkt, x): s = bytes(x) alt_tag = _choice_tag_for(field, x) if alt_tag is not None: - s = OER_tag_enc(alt_tag & 0x3f, alt_tag & 0xc0) + s + tag_class, tag_number = _OER_tag_parts(alt_tag) + s = OER_tag_enc(tag_number, tag_class) + s return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) @@ -1064,11 +1062,16 @@ def _choice_index_for(field, x): # type: (Any, Any) -> Optional[int] from scapy.asn1.asn1 import ASN1_Object for index, choice in enumerate(field.choice_list): - if isinstance(choice, type) and hasattr(choice, "ASN1_root"): - if isinstance(x, choice): - return index - elif hasattr(choice, "ASN1_tag"): - if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + if isinstance(choice, type): + if hasattr(choice, "ASN1_root"): + if isinstance(x, choice): + return index + elif hasattr(choice, "ASN1_tag"): + if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + return index + elif getattr(choice, "cls", None) is not None: + # ASN1F_PACKET instance: the alternative is a tagged packet. + if isinstance(x, choice.cls): return index return None diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index 0d70e951b4f..de5605e386e 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -45,6 +45,8 @@ 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, @@ -1317,11 +1319,16 @@ def _choice_index_for(field, x): # type: (Any, Any) -> Optional[int] from scapy.asn1.asn1 import ASN1_Object for index, choice in enumerate(field.choice_list): - if isinstance(choice, type) and hasattr(choice, "ASN1_root"): - if isinstance(x, choice): - return index - elif hasattr(choice, "ASN1_tag"): - if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + if isinstance(choice, type): + if hasattr(choice, "ASN1_root"): + if isinstance(x, choice): + return index + elif hasattr(choice, "ASN1_tag"): + if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + return index + elif getattr(choice, "cls", None) is not None: + # ASN1F_PACKET instance: the alternative is a tagged packet. + if isinstance(x, choice.cls): return index return None @@ -1358,44 +1365,12 @@ def _extract_packet_from_decoder(field, dec, pkt): return field.fld.m2i_from_decoder(pkt, dec) -# Populated by _install_uper_asn1fields() (also published on scapy.asn1fields). -ASN1F_DEFAULT = None # type: Any - - def _install_uper_asn1fields(): # type: () -> None - """Attach UPER bitstream helpers and DEFAULT onto asn1fields classes.""" + """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 - class _ASN1F_DEFAULT(af.ASN1F_optional): - """ASN.1 field with a DEFAULT value (PER presence bit).""" - - def __init__(self, field, default): - # type: (Any, Any) -> None - super(_ASN1F_DEFAULT, self).__init__(field) - self._default = default - - def is_empty(self, pkt): - # type: (Any) -> 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: (Any) -> None - self.set_val(pkt, self._default) - - global ASN1F_DEFAULT - ASN1F_DEFAULT = _ASN1F_DEFAULT # type: ignore[misc,assignment] - af.ASN1F_DEFAULT = _ASN1F_DEFAULT - def m2i_from_decoder(self, pkt, dec): # type: (Any, Any, Any) -> Any codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) @@ -1431,10 +1406,6 @@ def encode_into(self, enc, pkt, value=None): enc, raw, **self._codec_kwargs(pkt), ) - def opt_set_absent(self, pkt): - # type: (Any, Any) -> None - self.set_val(pkt, None) - def opt_dissect_from_decoder(self, pkt, dec): # type: (Any, Any, Any) -> None return self._field.dissect_from_decoder(pkt, dec) @@ -1467,7 +1438,6 @@ def opt_encode_into(self, enc, pkt, value=None): "encode_into": hooks.packet_encode_into, }), (af.ASN1F_optional, { - "set_absent": opt_set_absent, "dissect_from_decoder": opt_dissect_from_decoder, "encode_into": opt_encode_into, }), diff --git a/test/contrib/oer.uts b/test/contrib/oer.uts index 5ec01bac174..778482884fb 100644 --- a/test/contrib/oer.uts +++ b/test/contrib/oer.uts @@ -315,6 +315,28 @@ 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( @@ -436,8 +458,8 @@ 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 explicit null tagging -OER_tagging_enc(OERcodec_NULL.enc(None), explicit_tag=0x81) == b"\x81" += OER does not encode the tag of a component +OER_tagging_enc(b"\x05", explicit_tag=0x81) == b"\x05" and OER_tagging_dec(b"\x05", explicit_tag=0x81) == (None, b"\x05") = OER choice id decode tag, r = OER_id_dec(b"\x81\x01") tag == 0x81 and r == b"\x01" @@ -446,7 +468,8 @@ tag == 0x81 and r == b"\x01" = oer field explicit tag pkt = OERTaggedInteger(n=5) -assert raw(pkt) == b"\xa1\x01\x05" +# X.696 encodes no tag for a component, whatever the tagging environment +assert raw(pkt) == b"\x01\x05" decoded = _roundtrip(OERTaggedInteger, pkt) @@ -471,7 +494,7 @@ True 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\xa0\x01\x07" +assert raw(present) == b"\x80\x01\x01\x01\x07" decoded = _roundtrip(OEROptionalField, present) @@ -528,7 +551,7 @@ pkt = OERRecord( expected = ( b"\x80" - b"\x01*\xff\x02hi\xa0\x01\x07" + b"\x01*\xff\x02hi\x01\x07" b"\x01\x03\x01\x01\x01\x02\x01\x03" ) @@ -739,7 +762,7 @@ assert [x.val for x in decoded.values] == [1, 2, 3] True = oer field dissect -tagged = _dissect(OERTaggedInteger, "a10105") +tagged = _dissect(OERTaggedInteger, "0105") assert tagged.n.val == 5 @@ -749,7 +772,7 @@ assert fixed.n.val == 200 assert fixed.s.val == b"ABC" -present = _dissect(OEROptionalField, "800101a00107") +present = _dissect(OEROptionalField, "8001010107") assert present.id.val == 1 @@ -779,7 +802,7 @@ True decoded = _dissect( OERRecord, "80" - "012aff026869a00107" + "012aff0268690107" "0103010101020103", ) _assert_record(decoded) @@ -915,7 +938,8 @@ True = oer choice with packet alternative pkt = OERPacketChoice(c=OERInnerSeq(x=3)) -assert raw(pkt) == b"\x30\x03" +# \x10: universal 16 (SEQUENCE), without the BER constructed bit +assert raw(pkt) == b"\x10\x03" decoded = _roundtrip(OERPacketChoice, pkt) @@ -931,6 +955,25 @@ 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( @@ -1001,7 +1044,7 @@ 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.contrib.uper import ASN1F_DEFAULT +from scapy.asn1fields import ASN1F_DEFAULT class OERDefault(ASN1_Packet): ASN1_codec = ASN1_Codecs.OER diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts index 9c8994bdcb8..28084e5da16 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -3019,3 +3019,43 @@ _raises( 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 diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index 1cd942e6ab1..b0e7ae1609c 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -480,7 +480,7 @@ for cls, data_hex in [ ( OERRecord, "80" - "012aff026869a00107" + "012aff0268690107" "0103010101020103", ), ( @@ -563,3 +563,34 @@ assert ASN1_Codecs.PER._field_hooks 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. +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 + +True From 55078c4e6187d862d632f25936ddaaaa1c5ebe5b Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 20:00:57 +0200 Subject: [PATCH 14/19] uper: reject out of range constrained values, drop dead code A constrained INTEGER is written on the width of its range, so a value outside it cannot be expressed: 100 in an INTEGER (0..7) went out as a byte that reads back as 4, and -3 as one that reads back as 5. The same hole let a SEQUENCE OF with a SIZE(1..3) constraint encode an empty list as index -1. UPER_constrained_int_enc now refuses such a value, as the string and bit string encoders already do; an extensible type still takes its extension path before coming here. Building a SEQUENCE OF also had a branch of its own for an unset field, which wrote a length determinant of zero past the size constraint, where an empty list went through the constrained count. The two codecs each defined a BadTag decoding error that nothing raises, along with the except branch catching it, and OER kept a check_type_check_len that only the fields it hooks would call and an OER_id_dec merging the tag class into the tag number, the lossy pattern just removed from the choice path. The UPER encoder and decoder also carried a number_of_bytes and a consume_input with no caller. Neither OER nor PER puts the tag of a field on the wire, so ASN1Codec now defaults to identity tagging and only BER registers its own. The alternative lookup of a CHOICE, copied verbatim in both codecs, becomes ASN1F_CHOICE.alternative_index, and the scan for optional components becomes an ASN1F_SEQUENCE.optionals tuple built once. Eleven copies of the OER length check and the two UPER size checks each collapse into one helper, with the same messages, and the OER use_object_enc hook returned exactly what asn1fields does without it. Coverage of the three modules over the ASN.1 suites goes from 89% to 97% for OER and from 95% to 99% for UPER, the added tests covering the long form of an OER tag, the untyped codec fallbacks, a dissect of an empty encoding, a pre-encoded value, a choice with an unknown tag, an unknown index, a single alternative or packet class alternatives, and the two constraint fixes above. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/asn1/asn1.py | 17 +++- scapy/asn1fields.py | 30 +++++- scapy/contrib/oer.py | 196 +++++++------------------------------ scapy/contrib/uper.py | 108 ++++++-------------- test/contrib/oer.uts | 60 ++++++++++-- test/contrib/uper.uts | 149 ++++++++++++++++++++++++++++ test/scapy/layers/asn1.uts | 14 ++- 7 files changed, 328 insertions(+), 246 deletions(-) diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 46327f33388..849e8f02d84 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -121,11 +121,25 @@ class ASN1_BadTag_Decoding_Error(ASN1_Decoding_Error): pass +def _identity_tagging_enc(s, **kwargs): + # type: (bytes, **Any) -> bytes + return s + + +def _identity_tagging_dec(s, **kwargs): + # type: (bytes, **Any) -> Tuple[Optional[int], bytes] + return None, s + + class ASN1Codec(EnumElement): # Class-level default: EnumElement.__getattr__ forwards unknown attributes # to its int value, so a missing _field_hooks would raise (and swallow) an # AttributeError on every field operation. _field_hooks = None # type: Any + # Only BER puts the tag of a field on the wire; the other codecs keep + # these identity defaults. + _tagging_enc = staticmethod(_identity_tagging_enc) # type: Any + _tagging_dec = staticmethod(_identity_tagging_dec) # type: Any def register_stem(cls, stem): # type: (Type[BERcodec_Object[Any]]) -> None @@ -133,7 +147,8 @@ def register_stem(cls, stem): def register_tagging(cls, enc, dec): # type: (Any, Any) -> None - # Codec-level implicit/explicit tagging (BER/OER) or identity (UPER/PER). + # Only for the codecs that put the tag of a field on the wire (BER): + # the others keep the identity defaults below. cls._tagging_enc = enc cls._tagging_dec = dec diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index ab9f2aa1f08..83aeea98a75 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -140,8 +140,8 @@ 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). + # Codec provides tagging_*; only BER puts the tag of a field on the + # wire, the others keep the identity default of ASN1Codec. return pkt.ASN1_codec.tagging_dec(s, **kwargs) # type: ignore def _tagging_enc(self, pkt, s, **kwargs): @@ -518,6 +518,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): @@ -782,6 +787,10 @@ 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 @@ -876,6 +885,23 @@ def choice_order(self): # type: () -> List[int] return list(self.choices.keys()) + def alternative_index(self, x): + # type: (Any) -> Optional[int] + """Position in choice_order of the alternative that carries x.""" + for index, choice in enumerate(self.choices.values()): + if isinstance(choice, type): + if hasattr(choice, "ASN1_root"): + # ASN1_Packet subclass + if isinstance(x, choice): + return index + elif isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + # ASN1F_field subclass + return index + elif isinstance(x, choice.cls): + # ASN1F_PACKET instance, holding a tagged packet + return index + return None + @property def choice_list(self): # type: () -> List[_CHOICE_T] diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 346ae6e7fb1..4a3cfdf3f8c 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -30,9 +30,6 @@ 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 ( - ASN1Tag, - ASN1_BADTAG, - ASN1_BadTag_Decoding_Error, ASN1_Class, ASN1_Class_UNIVERSAL, ASN1_Codecs, @@ -113,11 +110,6 @@ def __str__(self): return s -class OER_BadTag_Decoding_Error(OER_Decoding_Error, - ASN1_BadTag_Decoding_Error): - pass - - # OER tag classes (bits 8-7 of the first identifier octet) OER_CLASS_UNIVERSAL = 0x00 OER_CLASS_APPLICATION = 0x40 @@ -125,6 +117,18 @@ class OER_BadTag_Decoding_Error(OER_Decoding_Error, 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: @@ -149,12 +153,7 @@ def OER_len_dec(s): if not tmp_len & 0x80: return tmp_len, s[1:] tmp_len &= 0x7f - if len(s) <= tmp_len: - raise OER_Decoding_Error( - "OER_len_dec: Got %i bytes while expecting %i" % - (len(s) - 1, tmp_len), - remaining=s - ) + _OER_check_len("OER_len_dec", s, tmp_len, offset=1) ll = 0 for c in s[1:tmp_len + 1]: ll <<= 8 @@ -176,12 +175,7 @@ def OER_signed_integer_enc(i): def OER_signed_integer_dec(s): # type: (bytes) -> Tuple[int, bytes] number_of_bytes, s = OER_len_dec(s) - if len(s) < number_of_bytes: - raise OER_Decoding_Error( - "OER_signed_integer_dec: Got %i bytes while expecting %i" % - (len(s), number_of_bytes), - remaining=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", @@ -209,12 +203,7 @@ def OER_unsigned_integer_enc(i): def OER_unsigned_integer_dec(s): # type: (bytes) -> Tuple[int, bytes] number_of_bytes, s = OER_len_dec(s) - if len(s) < number_of_bytes: - raise OER_Decoding_Error( - "OER_unsigned_integer_dec: Got %i bytes while expecting %i" % - (len(s), number_of_bytes), - remaining=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:] @@ -243,12 +232,7 @@ def OER_fixed_integer_enc(i, length, signed=True): def OER_fixed_integer_dec(s, length, signed=True): # type: (bytes, int, bool) -> Tuple[int, bytes] - if len(s) < length: - raise OER_Decoding_Error( - "OER_fixed_integer_dec: Got %i bytes while expecting %i" % - (len(s), length), - remaining=s - ) + _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:] @@ -276,12 +260,7 @@ def OER_enumerated_dec(s): if not (first & 0x80): return first, s[1:] length = first & 0x7f - if len(s) < length + 1: - raise OER_Decoding_Error( - "OER_enumerated_dec: Got %i bytes while expecting %i" % - (len(s) - 1, length), - remaining=s - ) + _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:] @@ -309,12 +288,7 @@ def OER_preamble_dec(s, extensible, number_of_optionals): if number_of_bits == 0: return [], s number_of_bytes = (number_of_bits + 7) // 8 - if len(s) < number_of_bytes: - raise OER_Decoding_Error( - "OER_preamble_dec: Got %i bytes while expecting %i" % - (len(s), number_of_bytes), - remaining=s - ) + _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) @@ -369,12 +343,6 @@ def OER_tag_dec(s): return tag_class, tag_number, s[i:] -def OER_id_dec(s): - # type: (bytes) -> Tuple[int, bytes] - tag_class, tag_number, remainder = OER_tag_dec(s) - return tag_class | tag_number, remainder - - def _OER_tag_parts(identifier): # type: (int) -> Tuple[int, int] # ASN1F_* fields describe tags as BER identifier octets: class in the top @@ -384,25 +352,6 @@ def _OER_tag_parts(identifier): return identifier & 0xc0, identifier & 0x1f -def OER_tagging_dec(s, # type: bytes - hidden_tag=None, # type: Optional[int | ASN1Tag] - implicit_tag=None, # type: Optional[int] - explicit_tag=None, # type: Optional[int] - safe=False, # type: Optional[bool] - _fname="", # type: str - ): - # type: (...) -> Tuple[Optional[int], bytes] - # X.696 encodes no tag for a component, whatever the tagging environment - # of the module: the only tag on the wire is the one of a chosen CHOICE - # alternative, which _OER_FieldHooks handles. - return None, s - - -def OER_tagging_enc(s, implicit_tag=None, explicit_tag=None): - # type: (bytes, Optional[int], Optional[int]) -> bytes - return s - - class OERcodec_metaclass(type): def __new__(cls, name, # type: str @@ -440,12 +389,6 @@ def check_string(cls, s): (cls.__name__, cls.tag), remaining=s ) - @classmethod - def check_type_check_len(cls, s): - # type: (bytes) -> Tuple[int, bytes, bytes] - cls.check_string(s) - return len(s), s, b"" - @classmethod def do_dec(cls, s, # type: bytes @@ -477,11 +420,6 @@ def dec(cls, 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_BadTag_Decoding_Error as e: - o, remain = OERcodec_Object.dec( - e.remaining, context, safe, size_len, oer_unsigned - ) - return ASN1_BADTAG(o), remain except OER_Decoding_Error as e: return ASN1_DECODING_ERROR(s, exc=e), b"" except ASN1_Error as e: @@ -513,8 +451,11 @@ def enc(cls, s, size_len=0, **_kwargs): raise TypeError("Trying to encode an invalid value !") +# No register_tagging(): X.696 encodes no tag for a component, whatever the +# tagging environment of the module, so the identity default of ASN1Codec is +# what OER needs. The only tag on the wire is the one of a chosen CHOICE +# alternative, which _OER_FieldHooks writes itself. ASN1_Codecs.OER.register_stem(OERcodec_Object) -ASN1_Codecs.OER.register_tagging(OER_tagging_enc, OER_tagging_dec) ########################## @@ -603,12 +544,7 @@ def do_dec(cls, # type: (...) -> Tuple[ASN1_Object[str], bytes] if size_len: number_of_bytes = (size_len + 7) // 8 - if len(s) < number_of_bytes: - raise OER_Decoding_Error( - "%s: Got %i bytes while expecting %i" % - (cls.__name__, len(s), number_of_bytes), - remaining=s - ) + _OER_check_len(cls.__name__, s, number_of_bytes) return ( cls.tag.asn1_object( _oer_bytes_to_bitstr(s[:number_of_bytes])[:size_len] @@ -618,11 +554,7 @@ def do_dec(cls, length, s = OER_len_dec(s) if length == 0: return cls.tag.asn1_object(""), s - if len(s) < length: - raise OER_Decoding_Error( - "%s: Got %i bytes while expecting %i" % (cls.__name__, len(s), length), - remaining=s - ) + _OER_check_len(cls.__name__, s, length) unused_bits = orb(s[0]) if safe and unused_bits > 7: raise OER_Decoding_Error( @@ -680,19 +612,10 @@ def do_dec(cls, ): # type: (...) -> Tuple[ASN1_Object[Any], bytes] if size_len: - if len(s) < size_len: - raise OER_Decoding_Error( - "%s: Got %i bytes while expecting %i" % - (cls.__name__, len(s), size_len), - remaining=s - ) + _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) - if len(s) < length: - raise OER_Decoding_Error( - "%s: Got %i bytes while expecting %i" % (cls.__name__, len(s), length), - remaining=s - ) + _OER_check_len(cls.__name__, s, length) return cls.tag.asn1_object(s[:length]), s[length:] @@ -743,11 +666,7 @@ def do_dec(cls, ): # type: (...) -> Tuple[ASN1_Object[bytes], bytes] length, s = OER_len_dec(s) - if len(s) < length: - raise OER_Decoding_Error( - "%s: Got %i bytes while expecting %i" % (cls.__name__, len(s), length), - remaining=s - ) + _OER_check_len(cls.__name__, s, length) content, t = s[:length], s[length:] lst = [] while content: @@ -922,18 +841,6 @@ def _field_extensible(field): class _OER_FieldHooks(object): """Compound ASN1F_* helpers for OER (kept out of asn1fields.py).""" - @staticmethod - def use_object_enc(field, pkt, item): - # type: (Any, Any, Any) -> bool - # Constraints (e.g. oer_unsigned) must go through codec.enc(**kwargs). - return field.size_len is None and not field.codec_opts - - @staticmethod - def _optionals(field): - # type: (Any) -> Tuple[Any, ...] - from scapy.asn1fields import ASN1F_optional - return tuple(f for f in field.seq if isinstance(f, ASN1F_optional)) - @staticmethod def sequence_m2i(field, pkt, s): # type: (Any, Any, bytes) -> Tuple[Any, bytes] @@ -945,7 +852,7 @@ def sequence_m2i(field, pkt, s): return [], s presence, s = OER_preamble_dec( s, _field_extensible(field), - len(_OER_FieldHooks._optionals(field)), + len(field.optionals), ) opt_index = 0 for obj in field.seq: @@ -968,8 +875,8 @@ def sequence_m2i(field, pkt, s): @staticmethod def sequence_build(field, pkt): # type: (Any, Any) -> bytes - from scapy.asn1fields import ASN1F_optional - optionals = _OER_FieldHooks._optionals(field) + 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], @@ -978,7 +885,8 @@ def sequence_build(field, pkt): if isinstance(obj, ASN1F_optional) and obj.is_empty(pkt): continue s += obj.build(pkt) - return ASN1F_field_i2m(field, pkt, s) + # Through ASN1F_field, as ASN1F_SEQUENCE.i2m is the hook above + return ASN1F_field.i2m(field, pkt, s) @staticmethod def sequence_of_m2i(field, pkt, s): @@ -1051,42 +959,14 @@ def choice_i2m(field, pkt, x): s = x.enc(pkt.ASN1_codec) else: s = bytes(x) - alt_tag = _choice_tag_for(field, x) - if alt_tag is not None: - tag_class, tag_number = _OER_tag_parts(alt_tag) + 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) -def _choice_index_for(field, x): - # type: (Any, Any) -> Optional[int] - from scapy.asn1.asn1 import ASN1_Object - for index, choice in enumerate(field.choice_list): - if isinstance(choice, type): - if hasattr(choice, "ASN1_root"): - if isinstance(x, choice): - return index - elif hasattr(choice, "ASN1_tag"): - if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: - return index - elif getattr(choice, "cls", None) is not None: - # ASN1F_PACKET instance: the alternative is a tagged packet. - if isinstance(x, choice.cls): - return index - return None - - -def _choice_tag_for(field, x): - # type: (Any, Any) -> Optional[int] - index = _choice_index_for(field, x) - return None if index is None else field.choice_order[index] - - -def ASN1F_field_i2m(field, pkt, s): - # type: (Any, Any, bytes) -> bytes - # Call ASN1F_field.i2m without compound overrides. - from scapy.asn1fields import ASN1F_field - return ASN1F_field.i2m(field, pkt, s) - - ASN1_Codecs.OER.register_field_hooks(_OER_FieldHooks) diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index de5605e386e..3750e8fa22c 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -33,8 +33,6 @@ 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_BADTAG, - ASN1_BadTag_Decoding_Error, ASN1_Class, ASN1_Class_UNIVERSAL, ASN1_Codecs, @@ -113,11 +111,6 @@ def __str__(self): return s -class UPER_BadTag_Decoding_Error(UPER_Decoding_Error, - ASN1_BadTag_Decoding_Error): - pass - - def UPER_bits_for_range(size): # type: (int) -> int if size <= 0: @@ -148,10 +141,6 @@ def __init__(self): self.chunks_number_of_bits = 0 self.chunks = [] # type: List[List[int]] - def number_of_bytes(self): - # type: () -> int - return (self.chunks_number_of_bits + self.number_of_bits + 7) // 8 - def append_bit(self, bit): # type: (int) -> None self.number_of_bits += 1 @@ -372,13 +361,16 @@ def read_unconstrained_whole_number(self): return enc - (1 << (8 * number_of_bytes)) return enc - def consume_input(self): - # type: () -> None - self.number_of_bits = 0 - 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) ) @@ -392,18 +384,25 @@ def UPER_constrained_int_dec(dec, minimum, maximum): 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: - if not minimum <= len(data) <= maximum: - # 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. - raise UPER_Encoding_Error( - "UPER_octet_string_enc: got %i octets while expecting %s" % - (len(data), minimum if minimum == maximum - else "%i..%i" % (minimum, maximum)) - ) + _uper_check_size( + "UPER_octet_string_enc", "octets", len(data), minimum, maximum, + ) if minimum != maximum: enc.append_non_negative_binary_integer( len(data) - minimum, @@ -520,11 +519,6 @@ def dec(cls, s, context=None, safe=False, **kwargs): return cls.do_dec(s, context, safe, **kwargs) try: return cls.do_dec(s, context, safe, **kwargs) - except UPER_BadTag_Decoding_Error as e: - o, remain = UPERcodec_Object.dec( - e.remaining, context, safe, **kwargs - ) - return ASN1_BADTAG(o), remain except (UPER_Decoding_Error, ASN1_Error) as e: return ASN1_DECODING_ERROR(s, exc=e), b"" @@ -534,19 +528,9 @@ def safedec(cls, s, context=None, **kwargs): return cls.dec(s, context, safe=True, **kwargs) -def UPER_tagging_enc(s, **kwargs): - # type: (bytes, **Any) -> bytes - # UPER has no BER-style TLV tagging. - return s - - -def UPER_tagging_dec(s, **kwargs): - # type: (bytes, **Any) -> Tuple[Optional[int], bytes] - return None, s - - +# No register_tagging(): PER encodes no tag at all, so the identity default +# of ASN1Codec is what UPER needs. ASN1_Codecs.PER.register_stem(UPERcodec_Object) -ASN1_Codecs.PER.register_tagging(UPER_tagging_enc, UPER_tagging_dec) ######################### @@ -675,12 +659,7 @@ def encode_into(cls, 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: - if not minimum <= nbits <= maximum: - raise UPER_Encoding_Error( - "UPERcodec_BIT_STRING: got %i bits while expecting %s" % - (nbits, minimum if minimum == maximum - else "%i..%i" % (minimum, maximum)) - ) + _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) @@ -1091,12 +1070,6 @@ def sequence_build(field, pkt): _UPER_FieldHooks.sequence_encode_into(field, enc, pkt) return ASN1F_field.i2m(field, pkt, enc.as_bytes()) - @staticmethod - def _optionals(field): - # type: (Any) -> Tuple[Any, ...] - from scapy.asn1fields import ASN1F_optional - return tuple(f for f in field.seq if isinstance(f, ASN1F_optional)) - @staticmethod def sequence_dissect_from_decoder(field, pkt, dec): # type: (Any, Any, Any) -> None @@ -1106,7 +1079,7 @@ def sequence_dissect_from_decoder(field, pkt, dec): raise UPER_Decoding_Error( "ASN1F_SEQUENCE: extension additions are not supported" ) - optionals = _UPER_FieldHooks._optionals(field) + optionals = field.optionals presence = [dec.read_bit() for _ in optionals] opt_idx = 0 for obj in field.seq: @@ -1127,7 +1100,7 @@ def sequence_encode_into(field, enc, pkt, value=None): from scapy.asn1fields import ASN1F_optional if _field_extensible(field): enc.append_bit(0) - for opt in _UPER_FieldHooks._optionals(field): + 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): @@ -1148,11 +1121,8 @@ def sequence_of_build(field, pkt): val = getattr(pkt, field.name) if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: s = val # type: Any - elif val is None: - enc = UPER_Encoder() - enc.append_length_determinant(0) - s = enc.as_bytes() else: + # An unset field counts as an empty one, size constraint included enc = UPER_Encoder() _UPER_FieldHooks.sequence_of_encode_into(field, enc, pkt, val) s = enc.as_bytes() @@ -1259,7 +1229,7 @@ def choice_encode_into(field, enc, pkt, value=None): from scapy.asn1.asn1 import ASN1_Error if value is None: value = getattr(pkt, field.name) - index = _choice_index_for(field, value) + index = field.alternative_index(value) if index is None: raise ASN1_Error( "ASN1F_CHOICE: cannot encode unknown alternative in '%s'" % @@ -1315,24 +1285,6 @@ def packet_encode_into(field, enc, pkt, value=None): value.ASN1_root.encode_into(enc, value) -def _choice_index_for(field, x): - # type: (Any, Any) -> Optional[int] - from scapy.asn1.asn1 import ASN1_Object - for index, choice in enumerate(field.choice_list): - if isinstance(choice, type): - if hasattr(choice, "ASN1_root"): - if isinstance(x, choice): - return index - elif hasattr(choice, "ASN1_tag"): - if isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: - return index - elif getattr(choice, "cls", None) is not None: - # ASN1F_PACKET instance: the alternative is a tagged packet. - if isinstance(x, choice.cls): - return index - return None - - 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 diff --git a/test/contrib/oer.uts b/test/contrib/oer.uts index 778482884fb..2a449c79020 100644 --- a/test/contrib/oer.uts +++ b/test/contrib/oer.uts @@ -459,10 +459,20 @@ x.val == -2 and r == b"" 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 -OER_tagging_enc(b"\x05", explicit_tag=0x81) == b"\x05" and OER_tagging_dec(b"\x05", explicit_tag=0x81) == (None, b"\x05") -= OER choice id decode -tag, r = OER_id_dec(b"\x81\x01") -tag == 0x81 and r == b"\x01" +ASN1_Codecs.OER.tagging_enc(b"\x05", explicit_tag=0x81) == b"\x05" and ASN1_Codecs.OER.tagging_dec(b"\x05", explicit_tag=0x81) == (None, b"\x05") += 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 @@ -870,11 +880,11 @@ assert ASN1_Codecs.OER._field_hooks is not None assert hasattr(ASN1_Codecs.OER._field_hooks, "sequence_m2i") -assert hasattr(ASN1_Codecs.OER._field_hooks, "use_object_enc") +assert hasattr(ASN1_Codecs.OER._field_hooks, "choice_i2m") True -= oer use_object_enc via hooks += oer use_object_enc fld = OERUnsignedField.ASN1_root assert fld.codec_opts["oer_unsigned"] is True @@ -1207,3 +1217,41 @@ True _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 index 28084e5da16..d8ab2fb4b8d 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -3059,3 +3059,152 @@ 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 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 b0e7ae1609c..d40acd479dc 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -586,11 +586,23 @@ class _PerDefault(ASN1_Packet): ) # A component holding its default value is not encoded, and comes back as the -# default when the encoding does not carry it. +# 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 From 760fc6dce2cc6703d91a0fa3709c13489f532c39 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 20:12:19 +0200 Subject: [PATCH 15/19] asn1: let BER hook the tagging of a field, drop the codec-level one Tagging had a registration mechanism of its own, next to the field hooks, and every codec paid for it: ASN1Codec carried an identity tagging_enc and tagging_dec so that OER and PER, which encode no tag at all, would not have to register anything. Tagging is just another field operation a codec does its own way, so BER now registers it among its field hooks, where a missing entry already means the default behaviour, and asn1fields leaves the encoding alone when no codec hooks it. register_tagging, tagging_enc and tagging_dec go away with it, as does unregister_field_hooks, which nothing called. Dissecting and building a BER sequence of tagged fields takes the same time as before. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/asn1/asn1.py | 44 +++------------------------------------ scapy/asn1/ber.py | 9 +++++++- scapy/asn1fields.py | 14 +++++++++---- scapy/contrib/oer.py | 8 +++---- scapy/contrib/uper.py | 3 +-- test/contrib/oer.uts | 15 ++++++++++++- test/scapy/layers/ber.uts | 44 ++++++++++++++++----------------------- 7 files changed, 58 insertions(+), 79 deletions(-) diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 849e8f02d84..c6de239943e 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -121,69 +121,31 @@ class ASN1_BadTag_Decoding_Error(ASN1_Decoding_Error): pass -def _identity_tagging_enc(s, **kwargs): - # type: (bytes, **Any) -> bytes - return s - - -def _identity_tagging_dec(s, **kwargs): - # type: (bytes, **Any) -> Tuple[Optional[int], bytes] - return None, s - - class ASN1Codec(EnumElement): # Class-level default: EnumElement.__getattr__ forwards unknown attributes # to its int value, so a missing _field_hooks would raise (and swallow) an # AttributeError on every field operation. _field_hooks = None # type: Any - # Only BER puts the tag of a field on the wire; the other codecs keep - # these identity defaults. - _tagging_enc = staticmethod(_identity_tagging_enc) # type: Any - _tagging_dec = staticmethod(_identity_tagging_dec) # type: Any def register_stem(cls, stem): # type: (Type[BERcodec_Object[Any]]) -> None cls._stem = stem - def register_tagging(cls, enc, dec): - # type: (Any, Any) -> None - # Only for the codecs that put the tag of a field on the wire (BER): - # the others keep the identity defaults below. - cls._tagging_enc = enc - cls._tagging_dec = dec - def register_field_hooks(cls, hooks): # type: (Any) -> None - # Optional compound-field helpers (SEQUENCE/CHOICE/…) for contrib codecs. + # Field operations a codec does its own way: the tagging of a field + # (BER) and the compound fields (SEQUENCE/CHOICE/… in OER and PER). cls._field_hooks = hooks - def unregister_field_hooks(cls): - # type: () -> Any - # Returns the previous hooks, so that callers can restore them. - hooks = cls._field_hooks - try: - del cls._field_hooks - except AttributeError: - pass - return hooks - def field_hook(cls, name): # type: (str) -> Any # Hooks are optional and may be partial: missing entries mean that - # asn1fields keeps its default (BER-style) implementation. + # asn1fields keeps its default implementation. hooks = cls._field_hooks if hooks is None: return None return getattr(hooks, name, None) - 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 dec(cls, s, context=None, _depth=0): # type: (bytes, Optional[Type[ASN1_Class]], int) -> ASN1_Object[Any] return cls._stem.dec(s, context=context, _depth=_depth) # type: ignore diff --git a/scapy/asn1/ber.py b/scapy/asn1/ber.py index c3da0f15b5d..092a658c18a 100644 --- a/scapy/asn1/ber.py +++ b/scapy/asn1/ber.py @@ -423,8 +423,15 @@ def enc(cls, s, size_len=0, **_kwargs): raise TypeError("Trying to encode an invalid value !") +class _BER_FieldHooks(object): + """ASN1F_* helpers for BER, the one codec that tags a field on the wire.""" + + tagging_enc = staticmethod(BER_tagging_enc) + tagging_dec = staticmethod(BER_tagging_dec) + + ASN1_Codecs.BER.register_stem(BERcodec_Object) -ASN1_Codecs.BER.register_tagging(BER_tagging_enc, BER_tagging_dec) +ASN1_Codecs.BER.register_field_hooks(_BER_FieldHooks) ########################## diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 83aeea98a75..5b46641aeb7 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -140,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_*; only BER puts the tag of a field on the - # wire, the others keep the identity default of ASN1Codec. - 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 diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 4a3cfdf3f8c..148905b5772 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -451,10 +451,10 @@ def enc(cls, s, size_len=0, **_kwargs): raise TypeError("Trying to encode an invalid value !") -# No register_tagging(): X.696 encodes no tag for a component, whatever the -# tagging environment of the module, so the identity default of ASN1Codec is -# what OER needs. The only tag on the wire is the one of a chosen CHOICE -# alternative, which _OER_FieldHooks writes itself. +# 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 _OER_FieldHooks writes +# itself. ASN1_Codecs.OER.register_stem(OERcodec_Object) diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index 3750e8fa22c..2ffd92b1215 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -528,8 +528,7 @@ def safedec(cls, s, context=None, **kwargs): return cls.dec(s, context, safe=True, **kwargs) -# No register_tagging(): PER encodes no tag at all, so the identity default -# of ASN1Codec is what UPER needs. +# No tagging hook: PER encodes no tag at all, so a field is left alone. ASN1_Codecs.PER.register_stem(UPERcodec_Object) diff --git a/test/contrib/oer.uts b/test/contrib/oer.uts index 2a449c79020..b8182a50dae 100644 --- a/test/contrib/oer.uts +++ b/test/contrib/oer.uts @@ -459,7 +459,20 @@ x.val == -2 and r == b"" 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 -ASN1_Codecs.OER.tagging_enc(b"\x05", explicit_tag=0x81) == b"\x05" and ASN1_Codecs.OER.tagging_dec(b"\x05", explicit_tag=0x81) == (None, b"\x05") +# X.696 encodes none, so OER hooks no tagging and the field is left alone +assert ASN1_Codecs.OER.field_hook("tagging_enc") is None + +assert ASN1_Codecs.OER.field_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" diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index bcfd5a28b22..a8ceea06473 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -457,36 +457,28 @@ 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.field_hook("tagging_enc") + +assert tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" + +tagging_dec = ASN1_Codecs.BER.field_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.field_hook("tagging_enc") is None -# Save/restore: asn1.uts may already have loaded contrib UPER tagging. -_prev_tagging_enc = getattr(ASN1_Codecs.PER, "_tagging_enc", None) -_prev_tagging_dec = getattr(ASN1_Codecs.PER, "_tagging_dec", 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: - if _prev_tagging_enc is not None and _prev_tagging_dec is not None: - ASN1_Codecs.PER.register_tagging(_prev_tagging_enc, _prev_tagging_dec) - else: - 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): From 83cffbfd0c535c9b9f9d18962491941dc1104880 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Wed, 12 Aug 2026 23:00:16 +0200 Subject: [PATCH 16/19] asn1: register the field hooks of a codec by keyword, in one dictionary A codec that does a field operation its own way had to declare a class of static methods and hand it to register_field_hooks, where the attribute names of that class silently defined the hook points, and each codec kept its own class in a _field_hooks attribute. The functions are now named as keyword arguments of register_hooks, and they all land in ASN1_Codecs.hooks, a dictionary by codec then by field operation, so what a codec overrides reads at the call site and lives in one place. The three hook classes become plain module functions, which is what UPER already needed anyway to bolt its bitstream helpers onto the asn1fields classes, and the enumerated codec kwargs simply ask whether the packet is a PER one instead of comparing hook classes. Dissecting and building a BER sequence of tagged fields takes the same time as before. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/asn1/asn1.py | 21 +- scapy/asn1/ber.py | 13 +- scapy/asn1fields.py | 2 +- scapy/contrib/oer.py | 257 ++++++++++----------- scapy/contrib/uper.py | 456 +++++++++++++++++++------------------ test/contrib/oer.uts | 12 +- test/contrib/uper.uts | 10 +- test/scapy/layers/asn1.uts | 8 +- test/scapy/layers/ber.uts | 8 +- 9 files changed, 393 insertions(+), 394 deletions(-) diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index c6de239943e..37f7f683137 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -122,29 +122,21 @@ class ASN1_BadTag_Decoding_Error(ASN1_Decoding_Error): class ASN1Codec(EnumElement): - # Class-level default: EnumElement.__getattr__ forwards unknown attributes - # to its int value, so a missing _field_hooks would raise (and swallow) an - # AttributeError on every field operation. - _field_hooks = None # type: Any - def register_stem(cls, stem): # type: (Type[BERcodec_Object[Any]]) -> None cls._stem = stem - def register_field_hooks(cls, hooks): - # type: (Any) -> None + 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). - cls._field_hooks = hooks + ASN1_Codecs.hooks.setdefault(cls, {}).update(hooks) - def field_hook(cls, name): + def hook(cls, name): # type: (str) -> Any # Hooks are optional and may be partial: missing entries mean that # asn1fields keeps its default implementation. - hooks = cls._field_hooks - if hooks is None: - return None - return getattr(hooks, name, None) + 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] @@ -174,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, diff --git a/scapy/asn1/ber.py b/scapy/asn1/ber.py index 092a658c18a..bc0e4ba817a 100644 --- a/scapy/asn1/ber.py +++ b/scapy/asn1/ber.py @@ -423,15 +423,12 @@ def enc(cls, s, size_len=0, **_kwargs): raise TypeError("Trying to encode an invalid value !") -class _BER_FieldHooks(object): - """ASN1F_* helpers for BER, the one codec that tags a field on the wire.""" - - tagging_enc = staticmethod(BER_tagging_enc) - tagging_dec = staticmethod(BER_tagging_dec) - - ASN1_Codecs.BER.register_stem(BERcodec_Object) -ASN1_Codecs.BER.register_field_hooks(_BER_FieldHooks) +# 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 5b46641aeb7..6681d715ab5 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -74,7 +74,7 @@ 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.field_hook(name) + return pkt.ASN1_codec.hook(name) ########################## diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py index 148905b5772..6195ce2fbff 100644 --- a/scapy/contrib/oer.py +++ b/scapy/contrib/oer.py @@ -453,8 +453,8 @@ def enc(cls, s, size_len=0, **_kwargs): # 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 _OER_FieldHooks writes -# itself. +# wire is the one of a chosen CHOICE alternative, which the CHOICE hooks below +# write themselves. ASN1_Codecs.OER.register_stem(OERcodec_Object) @@ -838,135 +838,138 @@ def _field_extensible(field): return bool(getattr(field, "codec_opts", {}).get("oer_extensible", False)) -class _OER_FieldHooks(object): - """Compound ASN1F_* helpers for OER (kept out of asn1fields.py).""" - - @staticmethod - def 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 +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: - 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 + obj.set_val(pkt, None) return [], s - - @staticmethod - def 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): + 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 - s += obj.build(pkt) - # Through ASN1F_field, as ASN1F_SEQUENCE.i2m is the hook above - return ASN1F_field.i2m(field, pkt, s) - - @staticmethod - def 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 - - @staticmethod - def 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) - - @staticmethod - def 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()) - ) + # 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, - ) - - @staticmethod - def choice_i2m(field, pkt, x): - # type: (Any, Any, Any) -> bytes - from scapy.asn1.asn1 import ASN1_Object - if x is None: - s = b"" + ) + 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: - 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) + 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_field_hooks(_OER_FieldHooks) +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 index 2ffd92b1215..b64dc1d8369 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -1044,244 +1044,240 @@ def _uper_decode_all(s, read): return value -class _UPER_FieldHooks(object): - """Compound ASN1F_* helpers for UPER/PER (kept out of asn1fields.py).""" +def _uper_use_object_enc(field, pkt, item): + # type: (Any, Any, Any) -> bool + # Always pass constraints through codec.enc(**kwargs). + return False - @staticmethod - def use_object_enc(field, pkt, item): - # type: (Any, Any, Any) -> bool - # Always pass constraints through codec.enc(**kwargs). - return False - @staticmethod - def sequence_m2i(field, pkt, s): - # type: (Any, Any, bytes) -> Tuple[Any, bytes] - _uper_decode_all(s, lambda dec: ( - _UPER_FieldHooks.sequence_dissect_from_decoder(field, pkt, dec) - )) - return [], b"" +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"" - @staticmethod - def sequence_build(field, pkt): - # type: (Any, Any) -> bytes - from scapy.asn1fields import ASN1F_field - enc = UPER_Encoder() - _UPER_FieldHooks.sequence_encode_into(field, enc, pkt) - return ASN1F_field.i2m(field, pkt, enc.as_bytes()) - @staticmethod - def 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_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()) - @staticmethod - def 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): + +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 - obj.encode_into(enc, pkt) + 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) - @staticmethod - def sequence_of_m2i(field, pkt, s): - # type: (Any, Any, bytes) -> Tuple[list, bytes] - return _uper_decode_all(s, lambda dec: ( - _UPER_FieldHooks.sequence_of_m2i_from_decoder(field, pkt, dec) - )), b"" - @staticmethod - def 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_FieldHooks.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 = [] - @staticmethod - def 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) - 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 - if _field_extensible(field) and dec.read_bit(): - dec.read_fragmented(read_items) - else: - _uper_count_dec(field, dec, read_items) - return lst - @staticmethod - def 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_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) - @staticmethod - def choice_m2i(field, pkt, s): - # type: (Any, Any, bytes) -> Tuple[Any, bytes] - return _uper_decode_all(s, lambda dec: ( - _UPER_FieldHooks.choice_m2i_from_decoder(field, pkt, dec) - )), b"" + 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) - @staticmethod - def choice_i2m(field, pkt, x): - # type: (Any, Any, Any) -> bytes - if x is None: - s = b"" + 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 = UPER_Encoder() - _UPER_FieldHooks.choice_encode_into(field, enc, pkt, x) - s = enc.as_bytes() - return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) + enc.append_bit(1) + enc.append_fragmented(count, append_items) + return + _uper_count_enc(field, enc, count, append_items) - @staticmethod - def 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"): - p = choice() - p.add_underlayer(pkt) - p.ASN1_root.dissect_from_decoder(p, dec) - return p - if isinstance(choice, type): - return choice(field.name, b"").m2i_from_decoder(pkt, dec) - return choice.m2i_from_decoder(pkt, dec) - @staticmethod - def 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_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"" - @staticmethod - def packet_m2i_from_decoder(field, pkt, dec): - # type: (Any, Any, Any) -> Any - cls = field._resolve_cls(pkt) - p = cls() + +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"): + p = choice() p.add_underlayer(pkt) p.ASN1_root.dissect_from_decoder(p, dec) return p - - @staticmethod - def packet_i2m(field, pkt, x): - # type: (Any, Any, Any) -> bytes - if x is None: - s = b"" - else: - enc = UPER_Encoder() - _UPER_FieldHooks.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, + if isinstance(choice, type): + return choice(field.name, b"").m2i_from_decoder(pkt, dec) + return choice.m2i_from_decoder(pkt, dec) + + +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 ) - - @staticmethod - def 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 + 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): @@ -1365,7 +1361,6 @@ def opt_encode_into(self, enc, pkt, value=None): # type: (Any, Any, Any, Any) -> None self._field.encode_into(enc, pkt, value) - hooks = _UPER_FieldHooks for field_cls, methods in ( (af.ASN1F_field, { "m2i_from_decoder": m2i_from_decoder, @@ -1373,20 +1368,20 @@ def opt_encode_into(self, enc, pkt, value=None): "encode_into": encode_into, }), (af.ASN1F_SEQUENCE, { - "dissect_from_decoder": hooks.sequence_dissect_from_decoder, - "encode_into": hooks.sequence_encode_into, + "dissect_from_decoder": _uper_sequence_dissect_from_decoder, + "encode_into": _uper_sequence_encode_into, }), (af.ASN1F_SEQUENCE_OF, { - "m2i_from_decoder": hooks.sequence_of_m2i_from_decoder, - "encode_into": hooks.sequence_of_encode_into, + "m2i_from_decoder": _uper_sequence_of_m2i_from_decoder, + "encode_into": _uper_sequence_of_encode_into, }), (af.ASN1F_CHOICE, { - "m2i_from_decoder": hooks.choice_m2i_from_decoder, - "encode_into": hooks.choice_encode_into, + "m2i_from_decoder": _uper_choice_m2i_from_decoder, + "encode_into": _uper_choice_encode_into, }), (af.ASN1F_PACKET, { - "m2i_from_decoder": hooks.packet_m2i_from_decoder, - "encode_into": hooks.packet_encode_into, + "m2i_from_decoder": _uper_packet_m2i_from_decoder, + "encode_into": _uper_packet_encode_into, }), (af.ASN1F_optional, { "dissect_from_decoder": opt_dissect_from_decoder, @@ -1405,7 +1400,7 @@ def enum_codec_kwargs(self, pkt): # 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 getattr(codec, "_field_hooks", None) is _UPER_FieldHooks: + 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)) @@ -1415,4 +1410,13 @@ def enum_codec_kwargs(self, pkt): _install_uper_asn1fields() -ASN1_Codecs.PER.register_field_hooks(_UPER_FieldHooks) +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/test/contrib/oer.uts b/test/contrib/oer.uts index b8182a50dae..7a4a61b3a40 100644 --- a/test/contrib/oer.uts +++ b/test/contrib/oer.uts @@ -460,9 +460,9 @@ x, r = OERcodec_STRING.do_dec(OERcodec_STRING.enc(b"\x12\x34\x56", 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.field_hook("tagging_enc") is None +assert ASN1_Codecs.OER.hook("tagging_enc") is None -assert ASN1_Codecs.OER.field_hook("tagging_dec") is None +assert ASN1_Codecs.OER.hook("tagging_dec") is None fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) @@ -887,13 +887,13 @@ True + ASN.1 OER field hooks and packet extras = oer field hooks registered -assert hasattr(ASN1_Codecs.OER, "_field_hooks") +assert ASN1_Codecs.hooks[ASN1_Codecs.OER] -assert ASN1_Codecs.OER._field_hooks is not None +assert ASN1_Codecs.OER.hook("sequence_m2i") is not None -assert hasattr(ASN1_Codecs.OER._field_hooks, "sequence_m2i") +assert ASN1_Codecs.OER.hook("choice_i2m") is not None -assert hasattr(ASN1_Codecs.OER._field_hooks, "choice_i2m") +assert ASN1_Codecs.OER.hook("no_such_hook") is None True diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts index d8ab2fb4b8d..cf4e19e230b 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -2786,15 +2786,13 @@ True + ASN.1 UPER field hooks and packet extras = uper field hooks registered -assert hasattr(ASN1_Codecs.PER, "_field_hooks") +assert ASN1_Codecs.hooks[ASN1_Codecs.PER] -assert ASN1_Codecs.PER._field_hooks is not None +assert ASN1_Codecs.PER.hook("sequence_m2i") is not None -assert hasattr(ASN1_Codecs.PER._field_hooks, "sequence_m2i") +use_object_enc = ASN1_Codecs.PER.hook("use_object_enc") -assert hasattr(ASN1_Codecs.PER._field_hooks, "use_object_enc") - -assert ASN1_Codecs.PER._field_hooks.use_object_enc( +assert use_object_enc( UPERConstrainedInt.ASN1_root, UPERConstrainedInt(), ASN1_INTEGER(1), ) is False diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index d40acd479dc..a50e146b3b4 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -553,13 +553,13 @@ for cls in (BEREmptySeqOf, OEREmptySeqOf, PEREmptySeqOf): True = field hooks present after contrib load -assert hasattr(ASN1_Codecs.OER, "_field_hooks") +assert ASN1_Codecs.hooks[ASN1_Codecs.OER] -assert hasattr(ASN1_Codecs.PER, "_field_hooks") +assert ASN1_Codecs.hooks[ASN1_Codecs.PER] -assert ASN1_Codecs.OER._field_hooks is not None +assert ASN1_Codecs.OER.hook("sequence_m2i") is not None -assert ASN1_Codecs.PER._field_hooks is not None +assert ASN1_Codecs.PER.hook("sequence_m2i") is not None True diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index a8ceea06473..2e4704b9353 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -458,11 +458,13 @@ BERcodec_SEQUENCE.enc(BERcodec_INTEGER.enc(1), uper_min=0) == BERcodec_SEQUENCE. + ASN.1 codec tagging contract = BER hooks the tagging of a field -tagging_enc = ASN1_Codecs.BER.field_hook("tagging_enc") +tagging_enc = ASN1_Codecs.BER.hook("tagging_enc") assert tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" -tagging_dec = ASN1_Codecs.BER.field_hook("tagging_dec") +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) @@ -472,7 +474,7 @@ diff is None and payload == b"\x02\x01\x05" class _NoHooks: ASN1_codec = ASN1_Codecs.CER -assert ASN1_Codecs.CER.field_hook("tagging_enc") is None +assert ASN1_Codecs.CER.hook("tagging_enc") is None fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) From 69e861c37ecfddc4a8fc403fe344ebf8b54c19bf Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 23:50:52 +0200 Subject: [PATCH 17/19] asn1: let a CHOICE hold constrained alternatives of its own A CHOICE kept its alternatives in a dict keyed by tag, which suits BER, the codec that reads the tag off the wire, but not OER and PER, which encode the position of the alternative. Two alternatives of the same type, the four NULL of an ETSI ITS EuVehicleCategoryCode for one, then collapsed onto a single entry and shifted the index of every alternative behind them. Alternatives now also live in declaration order, next to the tag lookup. The constructor equally assumed that an alternative given as an instance was an ASN1F_PACKET, and reached for the class it wraps. An alternative of a basic type carries its constraints, a range or a size, on an instance too, and both OER and PER already encode and decode those; only the lookup of the alternative refused them. Which of those alternatives a value was read as is not something the value itself carries, so rebuilding a dissected packet took the first alternative of its type: an EuVehicleCategoryCode read from a0 went back out as 80. PER now records the alternative on the value it decodes and the lookup honours it, as long as that alternative still carries the value; a value built by hand keeps taking the first one that does. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/asn1/asn1.py | 3 ++ scapy/asn1fields.py | 80 ++++++++++++++++++++++++++++--------------- scapy/contrib/uper.py | 15 ++++---- test/contrib/uper.uts | 38 ++++++++++++++++++++ 4 files changed, 101 insertions(+), 35 deletions(-) diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 37f7f683137..418dde6e42f 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -316,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/asn1fields.py b/scapy/asn1fields.py index 6681d715ab5..a7fa2165d02 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -841,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 @@ -862,7 +863,12 @@ def __init__(self, name, default, *args, **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"): @@ -870,49 +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") - @property - def choice_order(self): - # type: () -> List[int] - return list(self.choices.keys()) + 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.""" - for index, choice in enumerate(self.choices.values()): - if isinstance(choice, type): - if hasattr(choice, "ASN1_root"): - # ASN1_Packet subclass - if isinstance(x, choice): - return index - elif isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: - # ASN1F_field subclass - return index - elif isinstance(x, choice.cls): - # ASN1F_PACKET instance, holding a tagged packet + 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 - @property - def choice_list(self): - # type: () -> List[_CHOICE_T] - return list(self.choices.values()) - def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] """ diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py index b64dc1d8369..6c0e53878d3 100644 --- a/scapy/contrib/uper.py +++ b/scapy/contrib/uper.py @@ -1210,13 +1210,14 @@ def _uper_choice_m2i_from_decoder(field, pkt, dec): ) choice = field.choice_list[index] if isinstance(choice, type) and hasattr(choice, "ASN1_root"): - p = choice() - p.add_underlayer(pkt) - p.ASN1_root.dissect_from_decoder(p, dec) - return p - if isinstance(choice, type): - return choice(field.name, b"").m2i_from_decoder(pkt, dec) - return choice.m2i_from_decoder(pkt, dec) + 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): diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts index cf4e19e230b..ce257a814b9 100644 --- a/test/contrib/uper.uts +++ b/test/contrib/uper.uts @@ -3081,6 +3081,44 @@ 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, [])) From f1f78f169a05de2302b4a7ea91657e9397615f3f Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 23:50:52 +0200 Subject: [PATCH 18/19] contrib: add the ETSI ITS V2X messages (CAM, DENM, IVIM, SPATEM, MAPEM) The five message types of ETSI TS 102 637 and TS 103 301, generated from the ASN.1 modules of the standard by scapy/tools/generate_its_asn1.py and encoded with the UPER codec. The ASN.1 sources are not redistributed here; the generator reads them from scapy/contrib/automotive/v2x/asn. A sized BIT STRING or string is written with exactly the number of bits or octets its constraint gives, so the generator now defaults such a component to that many zero bits rather than to an empty value, which the codec rejects. The module also imports the PER codec it names, as it lives in contrib and nothing else pulls it in. The DENM example published by LF Edge InstantX, taken off the wire, builds byte for byte and dissects back, and every packet class of the layer round trips its default encoding. asn1tools reports the extension marker of a type as None and the generator kept the members behind it, so an extension addition, or one of a version bracket, ended up in the extension root: an extra bit in the preamble of a SEQUENCE, a wider alternative index in a CHOICE. Only the root is generated now. The ETSI modules the CAM and DENM types come from carry no addition; regenerating tells for the ISO ones. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/contrib/automotive/v2x/__init__.py | 26 + scapy/contrib/automotive/v2x/packets.py | 1961 ++++++++++++++++++++++ scapy/tools/generate_its_asn1.py | 980 +++++++++++ test/contrib/automotive/v2x.uts | 272 +++ tox.ini | 4 +- 5 files changed, 3242 insertions(+), 1 deletion(-) create mode 100644 scapy/contrib/automotive/v2x/__init__.py create mode 100644 scapy/contrib/automotive/v2x/packets.py create mode 100644 scapy/tools/generate_its_asn1.py create mode 100644 test/contrib/automotive/v2x.uts 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/tools/generate_its_asn1.py b/scapy/tools/generate_its_asn1.py new file mode 100644 index 00000000000..0001bd26221 --- /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 " + f'"root", {default},\n ' + ",\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 ( + f"class {py_name}(ASN1_Packet):\n" + f" ASN1_codec = ASN1_Codecs.PER\n" + f" ASN1_root = {root}\n" + ) + + 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(f" {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(f' "{root}",') + for module, type_name in result_order: + lines.append(f' "{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/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 From 605dd8bf69280be80b7e79c880a506d5bf7eb7ff Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 11 Aug 2026 23:59:31 +0200 Subject: [PATCH 19/19] tools: keep the punctuation of the generated code out of f-strings pycodestyle looks inside an f-string on the Python versions that tokenize one, so the comma of a generated import list and the colon of a generated class header read as a missing whitespace (E231). They belong to the code the generator writes, where such a whitespace would be wrong, so those five spots interpolate their value instead. AI-Assisted: yes (Cursor) Co-authored-by: Cursor --- scapy/tools/generate_its_asn1.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scapy/tools/generate_its_asn1.py b/scapy/tools/generate_its_asn1.py index 0001bd26221..0e161923593 100644 --- a/scapy/tools/generate_its_asn1.py +++ b/scapy/tools/generate_its_asn1.py @@ -700,7 +700,7 @@ def _choice_root( 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 " + f'"root", {default},\n ' + ",\n ".join(alts) + ext_args + "\n )" + return "ASN1F_CHOICE(\n " + '"root", %s,\n ' % default + ",\n ".join(alts) + ext_args + "\n )" def _sequence_root( self, @@ -740,9 +740,9 @@ def _packet_class( else: root = self._sequence_root(module, type_name, desc, key, result_order) return ( - f"class {py_name}(ASN1_Packet):\n" - f" ASN1_codec = ASN1_Codecs.PER\n" - f" ASN1_root = {root}\n" + "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]]: @@ -854,7 +854,7 @@ def generate(self) -> str: if field_imports: lines.append("from scapy.asn1fields import (") for name in field_imports: - lines.append(f" {name},") + lines.append(" %s," % name) lines.append(")") lines.append("from scapy.asn1packet import ASN1_Packet") lines.append("") @@ -872,9 +872,9 @@ def generate(self) -> str: lines.append("") lines.append("__all__ = [") for root in ROOT_MESSAGES: - lines.append(f' "{root}",') + lines.append(' "%s",' % root) for module, type_name in result_order: - lines.append(f' "{self.class_names[(module, type_name)]}",') + lines.append(' "%s",' % self.class_names[(module, type_name)]) lines.append("]") lines.append("") return "\n".join(lines)