diff --git a/.config/codespell_ignore.txt b/.config/codespell_ignore.txt index e5d65af4708..98e33583f15 100644 --- a/.config/codespell_ignore.txt +++ b/.config/codespell_ignore.txt @@ -54,3 +54,8 @@ wan wanna webp widgits +uper +UPER +uPER +acn +ACN diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 530ce185bd9..37f7f683137 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -126,19 +126,17 @@ def register_stem(cls, stem): # type: (Type[BERcodec_Object[Any]]) -> None cls._stem = stem - def register_tagging(cls, enc, dec): - # type: (Any, Any) -> None - # Codec-level implicit/explicit tagging (BER/OER) or identity (UPER/PER). - cls._tagging_enc = enc - cls._tagging_dec = dec - - def tagging_enc(cls, s, **kwargs): - # type: (bytes, **Any) -> bytes - return cls._tagging_enc(s, **kwargs) # type: ignore - - def tagging_dec(cls, s, **kwargs): - # type: (bytes, **Any) -> Tuple[Optional[int], bytes] - return cls._tagging_dec(s, **kwargs) # type: ignore + def register_hooks(cls, **hooks): + # type: (**Any) -> None + # Field operations a codec does its own way: the tagging of a field + # (BER) and the compound fields (SEQUENCE/CHOICE/… in OER and PER). + ASN1_Codecs.hooks.setdefault(cls, {}).update(hooks) + + def hook(cls, name): + # type: (str) -> Any + # Hooks are optional and may be partial: missing entries mean that + # asn1fields keeps its default implementation. + return ASN1_Codecs.hooks.get(cls, {}).get(name) def dec(cls, s, context=None, _depth=0): # type: (bytes, Optional[Type[ASN1_Class]], int) -> ASN1_Object[Any] @@ -168,6 +166,9 @@ class ASN1_Codecs(metaclass=ASN1_Codecs_metaclass): SER = cast(ASN1Codec, 8) XER = cast(ASN1Codec, 9) + # The field hooks of every codec, by codec then by field operation. + hooks = {} # type: Dict[ASN1Codec, Dict[str, Any]] + class ASN1Tag(EnumElement): def __init__(self, diff --git a/scapy/asn1/ber.py b/scapy/asn1/ber.py index c3da0f15b5d..bc0e4ba817a 100644 --- a/scapy/asn1/ber.py +++ b/scapy/asn1/ber.py @@ -424,7 +424,11 @@ def enc(cls, s, size_len=0, **_kwargs): ASN1_Codecs.BER.register_stem(BERcodec_Object) -ASN1_Codecs.BER.register_tagging(BER_tagging_enc, BER_tagging_dec) +# BER is the one codec that puts the tag of a field on the wire. +ASN1_Codecs.BER.register_hooks( + tagging_enc=BER_tagging_enc, + tagging_dec=BER_tagging_dec, +) ########################## diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 0d92161153e..6681d715ab5 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -70,6 +70,13 @@ class ASN1F_element(object): pass +def _field_hook(pkt, name): + # type: (Any, str) -> Any + # Contrib codecs (OER/UPER/…) may override compound field operations. + # Returns None when the codec keeps the default BER behaviour. + return pkt.ASN1_codec.hook(name) + + ########################## # Basic ASN1 Field # ########################## @@ -92,6 +99,7 @@ def __init__(self, explicit_tag=None, # type: Optional[int] flexible_tag=False, # type: Optional[bool] size_len=None, # type: Optional[int] + **codec_opts # type: Any ): # type: (...) -> None if context is not None: @@ -104,6 +112,9 @@ def __init__(self, else: self.default = self.ASN1_tag.asn1_object(default) # type: ignore self.size_len = size_len + # Contrib codecs (OER/UPER/…) pass constraints here, e.g. + # oer_unsigned=, uper_min=/uper_max=, uper_extensible=. + self.codec_opts = codec_opts # type: Dict[str, Any] self.flexible_tag = flexible_tag if (implicit_tag is not None) and (explicit_tag is not None): err_msg = "field cannot be both implicitly and explicitly tagged" @@ -129,13 +140,19 @@ def _apply_diff_tag(self, diff_tag): def _tagging_dec(self, pkt, s, **kwargs): # type: (ASN1_Packet, bytes, **Any) -> Tuple[Optional[int], bytes] - # Codec provides tagging_*; OER implements real tags, UPER/PER use - # identity helpers (no BER-style tagging). - return pkt.ASN1_codec.tagging_dec(s, **kwargs) # type: ignore + # Only BER puts the tag of a field on the wire: a codec that does not + # hook the tagging leaves the encoding alone. + hook = _field_hook(pkt, "tagging_dec") + if hook is None: + return None, s + return cast(Tuple[Optional[int], bytes], hook(s, **kwargs)) def _tagging_enc(self, pkt, s, **kwargs): # type: (ASN1_Packet, bytes, **Any) -> bytes - return pkt.ASN1_codec.tagging_enc(s, **kwargs) # type: ignore + hook = _field_hook(pkt, "tagging_enc") + if hook is None: + return s + return cast(bytes, hook(s, **kwargs)) def _apply_tagging_dec(self, s, pkt, hidden_tag=None, **kwargs): # type: (bytes, ASN1_Packet, Optional[Any], **Any) -> bytes @@ -156,15 +173,19 @@ def _apply_tagging_dec(self, s, pkt, hidden_tag=None, **kwargs): def _codec_kwargs(self, pkt): # type: (ASN1_Packet) -> Dict[str, Any] - # OER/UPER need extra constraints (oer_unsigned, uper_min/max, …) on - # every enc/dec call; override this instead of hardcoding BER size_len. - return {"size_len": self.size_len} + # BER ignores unknown keys via **_kwargs. Contrib codecs read + # constraints from field.codec_opts. + kwargs = {"size_len": self.size_len} # type: Dict[str, Any] + kwargs.update(self.codec_opts) + return kwargs def _use_object_enc(self, pkt, item): # type: (ASN1_Packet, ASN1_Object[Any]) -> bool - # BER/LDAP: item.enc() when size_len is unset. UPER must override to - # False so constrained integers go through codec.enc(**kwargs). - return self.size_len is None + # Contrib codecs may force codec.enc(**kwargs) via field hooks. + hook = _field_hook(pkt, "use_object_enc") + if hook is not None: + return cast(bool, hook(self, pkt, item)) + return self.size_len is None and not self.codec_opts def _encode_item(self, pkt, item): # type: (ASN1_Packet, Any) -> bytes @@ -326,12 +347,14 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None super(ASN1F_enum_INTEGER, self).__init__( name, default, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts ) i2s = self.i2s = {} # type: Dict[int, str] s2i = self.s2i = {} # type: Dict[str, int] @@ -378,12 +401,14 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] + **codec_opts # type: Any ): # type: (...) -> None super(ASN1F_BIT_STRING, self).__init__( name, None, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts, ) if isinstance(default, (bytes, str)): self.default = ASN1_BIT_STRING(default, @@ -499,6 +524,11 @@ def __init__(self, *seq, **kwargs): name, default, **kwargs ) self.seq = seq + # Codecs that describe presence out of band (OER/PER preambles) need + # the optional components in declaration order. + self.optionals = tuple( + f for f in seq if isinstance(f, ASN1F_optional) + ) # type: Tuple[ASN1F_optional, ...] self.islist = len(seq) > 1 def __repr__(self): @@ -514,6 +544,19 @@ def get_fields_list(self): return reduce(lambda x, y: x + y.get_fields_list(), self.seq, []) + def _dissect_sequence_children(self, pkt, s): + # type: (Any, bytes) -> bytes + if len(s) == 0: + for obj in self.seq: + obj.set_val(pkt, None) + return s + for obj in self.seq: + try: + s = obj.dissect(pkt, s) + except ASN1F_badsequence: + break + return s + def m2i(self, pkt, s): # type: (Any, bytes) -> Tuple[Any, bytes] """ @@ -524,23 +567,18 @@ def m2i(self, pkt, s): Thus m2i returns an empty list (along with the proper remainder). It is discarded by dissect() and should not be missed elsewhere. """ + hook = _field_hook(pkt, "sequence_m2i") + if hook is not None: + return cast(Tuple[Any, bytes], hook(self, pkt, s)) s = self._apply_tagging_dec(s, pkt, _fname=pkt.name) codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) i, s, remain = codec.check_type_check_len(s) - if len(s) == 0: - for obj in self.seq: - obj.set_val(pkt, None) - else: - for obj in self.seq: - try: - s = obj.dissect(pkt, s) - except ASN1F_badsequence: - break - if len(s) > 0: - raise BER_Decoding_Error( - "unexpected remainder in %s" % pkt.name, - remaining=s, - ) + s = self._dissect_sequence_children(pkt, s) + if len(s) > 0: + raise BER_Decoding_Error( + "unexpected remainder in %s" % pkt.name, + remaining=s, + ) return [], remain def dissect(self, pkt, s): @@ -550,6 +588,9 @@ def dissect(self, pkt, s): def build(self, pkt): # type: (ASN1_Packet) -> bytes + hook = _field_hook(pkt, "sequence_build") + if hook is not None: + return cast(bytes, hook(self, pkt)) s = reduce(lambda x, y: x + y.build(pkt), self.seq, b"") return super(ASN1F_SEQUENCE, self).i2m(pkt, s) @@ -582,6 +623,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None if isinstance(cls, type) and issubclass(cls, ASN1F_field) or \ @@ -601,7 +643,8 @@ def __init__(self, raise ValueError("cls should be an ASN1_Packet or ASN1_field") super(ASN1F_SEQUENCE_OF, self).__init__( name, None, context=context, - implicit_tag=implicit_tag, explicit_tag=explicit_tag + implicit_tag=implicit_tag, explicit_tag=explicit_tag, + **codec_opts, ) self.default = default @@ -616,6 +659,9 @@ def m2i(self, s, # type: bytes ): # type: (...) -> Tuple[List[Any], bytes] + hook = _field_hook(pkt, "sequence_of_m2i") + if hook is not None: + return cast(Tuple[List[Any], bytes], hook(self, pkt, s)) s = self._apply_tagging_dec(s, pkt) codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) i, s, remain = codec.check_type_check_len(s) @@ -633,6 +679,9 @@ def m2i(self, def build(self, pkt): # type: (ASN1_Packet) -> bytes + hook = _field_hook(pkt, "sequence_of_build") + if hook is not None: + return cast(bytes, hook(self, pkt)) val = getattr(pkt, self.name) if isinstance(val, ASN1_Object) and \ val.tag == ASN1_Class_UNIVERSAL.RAW: @@ -712,12 +761,22 @@ def dissect(self, pkt, s): try: return self._field.dissect(pkt, s) except (ASN1_Error, ASN1F_badsequence, ASN1_Decoding_Error): - self._field.set_val(pkt, None) + self.set_absent(pkt) return s + def set_absent(self, pkt): + # type: (ASN1_Packet) -> None + """Called when the encoding does not carry the component.""" + self._field.set_val(pkt, None) + + def is_empty(self, pkt): + # type: (ASN1_Packet) -> bool + return self._field.is_empty(pkt) + def build(self, pkt): # type: (ASN1_Packet) -> bytes - if self._field.is_empty(pkt): + # Through self, so that a DEFAULT component omits its default value. + if self.is_empty(pkt): return b"" return self._field.build(pkt) @@ -730,6 +789,37 @@ def i2repr(self, pkt, x): return self._field.i2repr(pkt, x) +class ASN1F_DEFAULT(ASN1F_optional): + """ + ASN.1 field holding a DEFAULT value: it is omitted from the encoding while + it holds that value, and restored when the encoding does not carry it. + + As with OPTIONAL components, a BER encoding only tells the component apart + from the one that follows it by its tag, so the schema must give it a + distinct one. OER and PER describe presence in the preamble instead. + """ + def __init__(self, field, default): + # type: (ASN1F_field[Any, Any], Any) -> None + super(ASN1F_DEFAULT, self).__init__(field) + self._default = default + + def is_empty(self, pkt): + # type: (ASN1_Packet) -> bool + val = getattr(pkt, self._field.name, None) + if val is None: + return True + if isinstance(val, ASN1_Object): + val = val.val + default = self._default + if isinstance(default, ASN1_Object): + default = default.val + return bool(val == default) + + def set_absent(self, pkt): + # type: (ASN1_Packet) -> None + self._field.set_val(pkt, self._default) + + class ASN1F_omit(ASN1F_field[None, None]): """ ASN.1 field that is not specified. This is simply omitted on the network. @@ -762,11 +852,13 @@ def __init__(self, name, default, *args, **kwargs): err_msg = "ASN1F_CHOICE has been called with an implicit_tag" raise ASN1_Error(err_msg) self.implicit_tag = None - for kwarg in ["context", "explicit_tag"]: - setattr(self, kwarg, kwargs.get(kwarg)) + context = kwargs.pop("context", None) + explicit_tag = kwargs.pop("explicit_tag", None) + # Remaining kwargs are codec constraints (e.g. uper_extensible=). super(ASN1F_CHOICE, self).__init__( - name, None, context=self.context, - explicit_tag=self.explicit_tag + name, None, context=context, + explicit_tag=explicit_tag, + **kwargs ) self.default = default self.current_choice = None @@ -794,6 +886,33 @@ def __init__(self, name, default, *args, **kwargs): 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 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] + return list(self.choices.values()) + def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] """ @@ -802,6 +921,9 @@ def m2i(self, pkt, s): """ if len(s) == 0: raise ASN1_Error("ASN1F_CHOICE: got empty string") + hook = _field_hook(pkt, "choice_m2i") + if hook is not None: + return cast(Tuple[ASN1_Object[Any], bytes], hook(self, pkt, s)) s = self._apply_tagging_dec(s, pkt) tag, _ = BER_id_dec(s) if tag in self.choices: @@ -827,6 +949,9 @@ def m2i(self, pkt, s): def i2m(self, pkt, x): # type: (ASN1_Packet, Any) -> bytes + hook = _field_hook(pkt, "choice_i2m") + if hook is not None: + return cast(bytes, hook(self, pkt, x)) if x is None: s = b"" else: @@ -886,12 +1011,15 @@ def __init__(self, self.network_tag = 16 | 0x20 # 16 + CONSTRUCTED self.default = default + def _resolve_cls(self, pkt): + # type: (ASN1_Packet) -> Type[ASN1_Packet] + if self.next_cls_cb: + return self.next_cls_cb(pkt) or self.cls + return self.cls + def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[Any, bytes] - if self.next_cls_cb: - cls = self.next_cls_cb(pkt) or self.cls - else: - cls = self.cls + cls = self._resolve_cls(pkt) if not hasattr(cls, "ASN1_root"): # A normal Packet (!= ASN1) return self.extract_packet(cls, s, _underlayer=pkt) @@ -909,6 +1037,9 @@ def i2m(self, x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> bytes + hook = _field_hook(pkt, "packet_i2m") + if hook is not None: + return cast(bytes, hook(self, pkt, x)) if x is None: s = b"" elif isinstance(x, bytes): @@ -1003,6 +1134,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None self.mapping = mapping @@ -1011,7 +1143,8 @@ def __init__(self, default_readable=False, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts, ) def any2i(self, pkt, x): diff --git a/scapy/contrib/oer.py b/scapy/contrib/oer.py new file mode 100644 index 00000000000..6195ce2fbff --- /dev/null +++ b/scapy/contrib/oer.py @@ -0,0 +1,975 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +# scapy.contrib.description = ASN.1 Octet Encoding Rules (OER) +# scapy.contrib.status = loads + +""" +Octet Encoding Rules (OER) for ASN.1 + +Basic-OER as specified in ITU-T X.696 | ISO/IEC 8825-7. + +``ASN1F_SEQUENCE`` emits the preamble required by 16.2.2: a presence bit per +``ASN1F_optional``/``ASN1F_DEFAULT`` component, preceded by an extension bit +for sequences declared with ``oer_extensible=True``. Fixed size constraints +are expressed with ``size_len=`` (octets for strings, bits for BIT STRING). + +Tags declared on a field are not encoded: OER only puts a tag on the wire for +the chosen alternative of an ``ASN1F_CHOICE`` (20.2), so the ``implicit_tag=`` +and ``explicit_tag=`` of the alternatives are what selects it. + +Not supported yet: extension additions (an encoding that carries them is +refused rather than misparsed), SET, REAL, and the canonical variant (C-OER). +""" + +import struct + +from scapy.error import warning +from scapy.compat import chb, orb, bytes_encode +from scapy.utils import binrepr, inet_aton, inet_ntoa +from scapy.asn1.ber import BER_num_dec, BER_num_enc +from scapy.asn1.asn1 import ( + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) +# Re-exported: DEFAULT components are what the preamble bits describe. +from scapy.asn1fields import ASN1F_DEFAULT # noqa: F401 + +from typing import ( + Any, + AnyStr, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +################## +# OER encoding # +################## + + +class OER_Exception(Exception): + pass + + +class OER_Encoding_Error(ASN1_Encoding_Error): + def __init__(self, + msg, # type: str + encoded=None, # type: Optional[Union['OERcodec_Object[Any]', str]] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.encoded = encoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.encoded, ASN1_Object): + s += "\n### Already encoded ###\n%s" % self.encoded.strshow() + else: + s += "\n### Already encoded ###\n%r" % self.encoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +class OER_Decoding_Error(ASN1_Decoding_Error): + def __init__(self, + msg, # type: str + decoded=None, # type: Optional[Any] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.decoded = decoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.decoded, ASN1_Object): + s += "\n### Already decoded ###\n%s" % self.decoded.strshow() + else: + s += "\n### Already decoded ###\n%r" % self.decoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +# OER tag classes (bits 8-7 of the first identifier octet) +OER_CLASS_UNIVERSAL = 0x00 +OER_CLASS_APPLICATION = 0x40 +OER_CLASS_CONTEXT = 0x80 +OER_CLASS_PRIVATE = 0xc0 + + +def _OER_check_len(name, s, number_of_bytes, offset=0): + # type: (str, bytes, int, int) -> None + """Raise unless s carries number_of_bytes octets past its first offset.""" + available = len(s) - offset + if available < number_of_bytes: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % + (name, available, number_of_bytes), + remaining=s + ) + + +def OER_len_enc(ll): + # type: (int) -> bytes + if ll < 128: + return chb(ll) + encoded = [] + value = ll + while value > 0: + encoded.insert(0, value & 0xff) + value >>= 8 + if len(encoded) > 127: + raise OER_Exception( + "OER_len_enc: Length too long (%i) to be encoded" % len(encoded) + ) + return chb(0x80 | len(encoded)) + bytes(encoded) + + +def OER_len_dec(s): + # type: (bytes) -> Tuple[int, bytes] + if not s: + raise OER_Decoding_Error("OER_len_dec: got empty string", remaining=s) + tmp_len = orb(s[0]) + if not tmp_len & 0x80: + return tmp_len, s[1:] + tmp_len &= 0x7f + _OER_check_len("OER_len_dec", s, tmp_len, offset=1) + ll = 0 + for c in s[1:tmp_len + 1]: + ll <<= 8 + ll |= orb(c) + return ll, s[tmp_len + 1:] + + +def OER_signed_integer_enc(i): + # type: (int) -> bytes + # X.696 10.4: the shortest two's complement encoding. A negative value + # needs one bit less than its magnitude suggests, as -2**(8n-1) still + # fits in n octets, hence the increment before measuring. + magnitude = i + 1 if i < 0 else i + number_of_bytes = (magnitude.bit_length() + 8) // 8 + value = i & ((1 << (8 * number_of_bytes)) - 1) + return OER_len_enc(number_of_bytes) + value.to_bytes(number_of_bytes, "big") + + +def OER_signed_integer_dec(s): + # type: (bytes) -> Tuple[int, bytes] + number_of_bytes, s = OER_len_dec(s) + _OER_check_len("OER_signed_integer_dec", s, number_of_bytes) + if number_of_bytes == 0: + raise OER_Decoding_Error( + "OER_signed_integer_dec: got an empty length determinant", + remaining=s + ) + value = int.from_bytes(s[:number_of_bytes], "big") + number_of_bits = 8 * number_of_bytes + if value & (1 << (number_of_bits - 1)): + value -= (1 << number_of_bits) - 1 + value -= 1 + return value, s[number_of_bytes:] + + +def OER_unsigned_integer_enc(i): + # type: (int) -> bytes + if i < 0: + raise OER_Encoding_Error( + "OER_unsigned_integer_enc: %i is negative" % i + ) + number_of_bits = max(i.bit_length(), 1) + number_of_bytes = (number_of_bits + 7) // 8 + return OER_len_enc(number_of_bytes) + i.to_bytes(number_of_bytes, "big") + + +def OER_unsigned_integer_dec(s): + # type: (bytes) -> Tuple[int, bytes] + number_of_bytes, s = OER_len_dec(s) + _OER_check_len("OER_unsigned_integer_dec", s, number_of_bytes) + value = int.from_bytes(s[:number_of_bytes], "big") + return value, s[number_of_bytes:] + + +_OER_FIXED_FORMATS = { + True: {1: ">b", 2: ">h", 4: ">i", 8: ">q"}, + False: {1: ">B", 2: ">H", 4: ">I", 8: ">Q"}, +} + + +def OER_fixed_integer_enc(i, length, signed=True): + # type: (int, int, bool) -> bytes + fmt = _OER_FIXED_FORMATS[signed] + try: + return struct.pack(fmt[length], i) + except KeyError: + raise OER_Encoding_Error( + "OER_fixed_integer_enc: invalid length %i" % length + ) + except struct.error: + raise OER_Encoding_Error( + "OER_fixed_integer_enc: %i does not fit in %i %s octet(s)" % + (i, length, "signed" if signed else "unsigned") + ) + + +def OER_fixed_integer_dec(s, length, signed=True): + # type: (bytes, int, bool) -> Tuple[int, bytes] + _OER_check_len("OER_fixed_integer_dec", s, length) + fmt = _OER_FIXED_FORMATS[signed] + try: + return struct.unpack(fmt[length], s[:length])[0], s[length:] + except KeyError: + raise OER_Decoding_Error( + "OER_fixed_integer_dec: invalid length %i" % length, + remaining=s + ) + + +def OER_enumerated_enc(i): + # type: (int) -> bytes + if 0 <= i <= 127: + return chb(i) + body = OER_signed_integer_enc(i)[1:] + return chb(0x80 | len(body)) + body + + +def OER_enumerated_dec(s): + # type: (bytes) -> Tuple[int, bytes] + if not s: + raise OER_Decoding_Error("OER_enumerated_dec: got empty string", + remaining=s) + first = orb(s[0]) + if not (first & 0x80): + return first, s[1:] + length = first & 0x7f + _OER_check_len("OER_enumerated_dec", s, length, offset=1) + value = int.from_bytes(s[1:length + 1], "big", signed=True) + return value, s[length + 1:] + + +def OER_preamble_enc(extensible, presence): + # type: (bool, List[bool]) -> bytes + # X.696 16.2.2: an extension bit (extensible types only) followed by one + # presence bit per OPTIONAL/DEFAULT component, zero-padded to a whole + # number of octets. A type with neither has no preamble at all. + bits = [0] if extensible else [] + bits += [1 if present else 0 for present in presence] + if not bits: + return b"" + number_of_bytes = (len(bits) + 7) // 8 + value = 0 + for bit in bits: + value = (value << 1) | bit + value <<= 8 * number_of_bytes - len(bits) + return value.to_bytes(number_of_bytes, "big") + + +def OER_preamble_dec(s, extensible, number_of_optionals): + # type: (bytes, bool, int) -> Tuple[List[bool], bytes] + number_of_bits = (1 if extensible else 0) + number_of_optionals + if number_of_bits == 0: + return [], s + number_of_bytes = (number_of_bits + 7) // 8 + _OER_check_len("OER_preamble_dec", s, number_of_bytes) + value = int.from_bytes(s[:number_of_bytes], "big") + bits = [ + bool((value >> (8 * number_of_bytes - 1 - i)) & 1) + for i in range(number_of_bits) + ] + if extensible: + if bits[0]: + raise OER_Decoding_Error( + "OER_preamble_dec: extension additions are not supported", + remaining=s + ) + bits = bits[1:] + return bits, s[number_of_bytes:] + + +def OER_tag_enc(n, tag_class=OER_CLASS_CONTEXT): + # type: (int, int) -> bytes + if n < 63: + return chb(tag_class | n) + tag = bytearray([tag_class | 0x3f]) + encoded = [] + value = n + while value > 0: + encoded.append(0x80 | (value & 0x7f)) + value >>= 7 + encoded[0] &= 0x7f + encoded.reverse() + tag.extend(encoded) + return bytes(tag) + + +def OER_tag_dec(s): + # type: (bytes) -> Tuple[int, int, bytes] + if not s: + raise OER_Decoding_Error("OER_tag_dec: got empty string", remaining=s) + first = orb(s[0]) + tag_class = first & 0xc0 + tag_number = first & 0x3f + if tag_number != 0x3f: + return tag_class, tag_number, s[1:] + tag_number = 0 + i = 1 + while i < len(s): + c = orb(s[i]) + tag_number <<= 7 + tag_number |= c & 0x7f + i += 1 + if not (c & 0x80): + break + else: + raise OER_Decoding_Error("OER_tag_dec: unfinished tag", remaining=s) + return tag_class, tag_number, s[i:] + + +def _OER_tag_parts(identifier): + # type: (int) -> Tuple[int, int] + # ASN1F_* fields describe tags as BER identifier octets: class in the top + # two bits, constructed flag in 0x20 and tag number in the low five bits. + # X.696 8.7 only keeps the class and the number, so the constructed flag + # must not leak into the encoded tag number. + return identifier & 0xc0, identifier & 0x1f + + +class OERcodec_metaclass(type): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type['OERcodec_Object[Any]'] + c = cast('Type[OERcodec_Object[Any]]', + super(OERcodec_metaclass, cls).__new__(cls, name, bases, dct)) + try: + c.tag.register(c.codec, c) + except Exception: + warning("Error registering %r for %r" % (c.tag, c.codec)) + return c + + +_K = TypeVar('_K') + + +class OERcodec_Object(Generic[_K], metaclass=OERcodec_metaclass): + codec = ASN1_Codecs.OER + tag = ASN1_Class_UNIVERSAL.ANY + + @classmethod + def asn1_object(cls, val): + # type: (_K) -> ASN1_Object[_K] + return cls.tag.asn1_object(val) + + @classmethod + def check_string(cls, s): + # type: (bytes) -> None + if not s: + raise OER_Decoding_Error( + "%s: Got empty object while expecting %r" % + (cls.__name__, cls.tag), remaining=s + ) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + raise OER_Decoding_Error( + "%s: Cannot decode unknown OER type without context" % + cls.__name__, remaining=s + ) + + @classmethod + def dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + # Ignore unknown kwargs so shared field._codec_kwargs() dicts (UPER + # keys) do not TypeError on OER packets. + if not safe: + return cls.do_dec(s, context, safe, size_len, oer_unsigned) + try: + return cls.do_dec(s, context, safe, size_len, oer_unsigned) + except OER_Decoding_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + except ASN1_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + + @classmethod + def safedec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + return cls.dec( + s, context, safe=True, + size_len=size_len, oer_unsigned=oer_unsigned, + ) + + @classmethod + def enc(cls, s, size_len=0, **_kwargs): + # type: (_K, Optional[int], **Any) -> bytes + if isinstance(s, (str, bytes)): + return OERcodec_STRING.enc(s, size_len=size_len) + else: + try: + return OERcodec_INTEGER.enc(int(s), size_len=size_len) # type: ignore + except TypeError: + raise TypeError("Trying to encode an invalid value !") + + +# No tagging hook: X.696 encodes no tag for a component, whatever the tagging +# environment of the module, so a field is left alone. The only tag on the +# wire is the one of a chosen CHOICE alternative, which the CHOICE hooks below +# write themselves. +ASN1_Codecs.OER.register_stem(OERcodec_Object) + + +########################## +# OERcodec objects # +########################## + +class OERcodec_INTEGER(OERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.INTEGER + + @classmethod + def enc(cls, i, size_len=0, oer_unsigned=False, **_kwargs): + # type: (int, Optional[int], bool, **Any) -> bytes + # X.696 10: the width and the signedness follow the declared bounds of + # the type, never the value at hand, otherwise the decoder (which only + # knows the type) reads something else back. + if size_len in (1, 2, 4, 8): + return OER_fixed_integer_enc(i, size_len, signed=not oer_unsigned) + if oer_unsigned: + return OER_unsigned_integer_enc(i) + return OER_signed_integer_enc(i) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + if size_len in (1, 2, 4, 8): + x, t = OER_fixed_integer_dec( + s, size_len, signed=not oer_unsigned + ) + return cls.asn1_object(x), t + if oer_unsigned: + x, t = OER_unsigned_integer_dec(s) + else: + x, t = OER_signed_integer_dec(s) + return cls.asn1_object(x), t + + +class OERcodec_BOOLEAN(OERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + @classmethod + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes + return chb(0xff if i else 0x00) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + cls.check_string(s) + return cls.asn1_object(0 if orb(s[0]) == 0 else 1), s[1:] + + +def _oer_bitstr_to_bytes(bitstr): + # type: (bytes) -> bytes + padded = bitstr + b"0" * (-len(bitstr) % 8) + return bytes([int(padded[i:i + 8], 2) for i in range(0, len(padded), 8)]) + + +def _oer_bytes_to_bitstr(data): + # type: (bytes) -> str + return "".join(binrepr(orb(x)).zfill(8) for x in data) + + +class OERcodec_BIT_STRING(OERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[str], bytes] + if size_len: + number_of_bytes = (size_len + 7) // 8 + _OER_check_len(cls.__name__, s, number_of_bytes) + return ( + cls.tag.asn1_object( + _oer_bytes_to_bitstr(s[:number_of_bytes])[:size_len] + ), + s[number_of_bytes:], + ) + length, s = OER_len_dec(s) + if length == 0: + return cls.tag.asn1_object(""), s + _OER_check_len(cls.__name__, s, length) + unused_bits = orb(s[0]) + if safe and unused_bits > 7: + raise OER_Decoding_Error( + "OERcodec_BIT_STRING: too many unused_bits advertised", + remaining=s + ) + fs = _oer_bytes_to_bitstr(s[1:length]) + if unused_bits > 0: + fs = fs[:-unused_bits] + return cls.tag.asn1_object(fs), s[length:] + + @classmethod + def enc(cls, _s, size_len=0, **_kwargs): + # type: (AnyStr, Optional[int], **Any) -> bytes + s = bytes_encode(_s) + if size_len: + # X.696 13.3: a fixed size means the bits are written padded to a + # whole number of octets, without length or unused-bit count. + if len(s) != size_len: + raise OER_Encoding_Error( + "%s: got %i bits while expecting %i" % + (cls.__name__, len(s), size_len), + encoded=_s + ) + return _oer_bitstr_to_bytes(s) + body = chb(-len(s) % 8) + _oer_bitstr_to_bytes(s) + return OER_len_enc(len(body)) + body + + +class OERcodec_STRING(OERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.STRING + + @classmethod + def enc(cls, _s, size_len=0, **_kwargs): + # type: (Union[str, bytes], Optional[int], **Any) -> bytes + s = bytes_encode(_s) + if size_len: + # X.696 16.1: a fixed size means no length determinant. + if len(s) != size_len: + raise OER_Encoding_Error( + "%s: got %i bytes while expecting %i" % + (cls.__name__, len(s), size_len), + encoded=_s + ) + return s + return OER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + if size_len: + _OER_check_len(cls.__name__, s, size_len) + return cls.tag.asn1_object(s[:size_len]), s[size_len:] + length, s = OER_len_dec(s) + _OER_check_len(cls.__name__, s, length) + return cls.tag.asn1_object(s[:length]), s[length:] + + +class OERcodec_NULL(OERcodec_Object[None]): + tag = ASN1_Class_UNIVERSAL.NULL + + @classmethod + def enc(cls, i, **_kwargs): + # type: (Any, **Any) -> bytes + return b"" + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[None], bytes] + return cls.asn1_object(None), s + + +class OERcodec_OID(OERcodec_Object[bytes]): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def enc(cls, _oid, **_kwargs): + # type: (AnyStr, **Any) -> bytes + oid = bytes_encode(_oid) + if oid: + lst = [int(x) for x in oid.strip(b".").split(b".")] + else: + lst = list() + if len(lst) >= 2: + lst[1] += 40 * lst[0] + del lst[0] + body = b"".join(BER_num_enc(k) for k in lst) + return OER_len_enc(len(body)) + body + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[bytes], bytes] + length, s = OER_len_dec(s) + _OER_check_len(cls.__name__, s, length) + content, t = s[:length], s[length:] + lst = [] + while content: + val, content = BER_num_dec(content) + lst.append(val) + if len(lst) > 0: + lst.insert(0, lst[0] // 40) + lst[1] %= 40 + return ( + cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)), + t, + ) + + +class OERcodec_ENUMERATED(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + @classmethod + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes + return OER_enumerated_enc(i) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + x, t = OER_enumerated_dec(s) + return cls.asn1_object(x), t + + +class OERcodec_UTF8_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class OERcodec_NUMERIC_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class OERcodec_PRINTABLE_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class OERcodec_T61_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class OERcodec_VIDEOTEX_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class OERcodec_IA5_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class OERcodec_GENERAL_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERAL_STRING + + +class OERcodec_UTC_TIME(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + +class OERcodec_GENERALIZED_TIME(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class OERcodec_ISO646_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class OERcodec_UNIVERSAL_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class OERcodec_BMP_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +class OERcodec_SEQUENCE(OERcodec_Object[Union[bytes, List['OERcodec_Object[Any]']]]): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def enc(cls, _ll, **_kwargs): + # type: (Union[bytes, List[OERcodec_Object[Any]]], **Any) -> bytes + if isinstance(_ll, bytes): + return _ll + return b"".join(x.enc(cls.codec) for x in _ll) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + size_len=0, # type: Optional[int] + oer_unsigned=False, # type: bool + ): + # type: (...) -> Tuple[ASN1_Object[Union[bytes, List[Any]]], bytes] + raise OER_Decoding_Error( + "OERcodec_SEQUENCE: decoding requires schema-defined field order", + remaining=s + ) + + +class OERcodec_SET(OERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class OERcodec_IPADDRESS(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def enc(cls, ipaddr_ascii, size_len=0, **_kwargs): # type: ignore + # type: (str, Optional[int], **Any) -> bytes + try: + s = inet_aton(ipaddr_ascii) + except Exception: + raise OER_Encoding_Error("IPv4 address could not be encoded") + if size_len == len(s): + return s + return OER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, s, context=None, safe=False, + size_len=0, oer_unsigned=False): + # type: (bytes, Optional[Any], bool, Optional[int], bool) -> Tuple[ASN1_Object[str], bytes] # noqa: E501 + if size_len == 4: + raw, remain = s[:4], s[4:] + else: + length, remain = OER_len_dec(s) + if len(remain) < length: + raise OER_Decoding_Error("IP address could not be decoded", + remaining=s) + raw, remain = remain[:length], remain[length:] + try: + ipaddr_ascii = inet_ntoa(raw) + except Exception: + raise OER_Decoding_Error("IP address could not be decoded", + remaining=s) + return cls.asn1_object(ipaddr_ascii), remain + + +class OERcodec_COUNTER32(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class OERcodec_COUNTER64(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER64 + + +class OERcodec_GAUGE32(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class OERcodec_TIME_TICKS(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +########################## +# ASN1F field hooks # +########################## + +def _field_extensible(field): + # type: (Any) -> bool + return bool(getattr(field, "codec_opts", {}).get("oer_extensible", False)) + + +def _oer_sequence_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + from scapy.asn1fields import ASN1F_badsequence, ASN1F_optional + s = field._apply_tagging_dec(s, pkt, _fname=pkt.name) + if not s: + for obj in field.seq: + obj.set_val(pkt, None) + return [], s + presence, s = OER_preamble_dec( + s, _field_extensible(field), + len(field.optionals), + ) + opt_index = 0 + for obj in field.seq: + target = obj + if isinstance(obj, ASN1F_optional): + present = presence[opt_index] + opt_index += 1 + if not present: + obj.set_absent(pkt) + continue + # The preamble already said the component is there, so dissect + # it directly: a failure is an error, not an absence. + target = obj._field + try: + s = target.dissect(pkt, s) + except ASN1F_badsequence: + break + return [], s + + +def _oer_sequence_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1fields import ASN1F_field, ASN1F_optional + optionals = field.optionals + s = OER_preamble_enc( + _field_extensible(field), + [not opt.is_empty(pkt) for opt in optionals], + ) + for obj in field.seq: + if isinstance(obj, ASN1F_optional) and obj.is_empty(pkt): + continue + s += obj.build(pkt) + # Through ASN1F_field, as ASN1F_SEQUENCE.i2m is the hook above + return ASN1F_field.i2m(field, pkt, s) + + +def _oer_sequence_of_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[list, bytes] + s = field._apply_tagging_dec(s, pkt) + count, s = OER_unsigned_integer_dec(s) + lst = [] + for _ in range(count): + c, s = field._extract_packet(s, pkt) + if c: + lst.append(c) + return lst, s + + +def _oer_sequence_of_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Object + val = getattr(pkt, field.name) + if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: + s = val # type: Any + else: + items = [ + bytes(item) if field.holds_packets else field.fld.i2m(pkt, item) + for item in val or [] + ] + s = OER_unsigned_integer_enc(len(items)) + b"".join(items) + return field.i2m(pkt, s) + + +def _oer_choice_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + from scapy.asn1fields import ASN1F_field + from scapy.asn1.asn1 import ASN1_Error + s = field._apply_tagging_dec(s, pkt) + tag_class, tag_number, payload = OER_tag_dec(s) + choice = None + for key, alternative in field.choices.items(): + if _OER_tag_parts(key) == (tag_class, tag_number): + choice = alternative + break + if choice is None: + if not field.flexible_tag: + raise ASN1_Error( + "ASN1F_CHOICE: unexpected field in '%s' " + "(tag %s not in possible tags %s)" % ( + field.name, tag_class | tag_number, + list(field.choices.keys()) + ) + ) + choice = ASN1F_field + if hasattr(choice, "ASN1_root"): + return field.extract_packet(choice, payload, _underlayer=pkt) + if isinstance(choice, type): + return choice(field.name, b"").m2i(pkt, payload) + # ASN1F_PACKET instance: X.696 20.2 puts the alternative tag in front + # of the value, so it was consumed above and must not be looked for + # again by the field itself. + return field.extract_packet( + choice._resolve_cls(pkt), payload, _underlayer=pkt, + ) + + +def _oer_choice_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + from scapy.asn1.asn1 import ASN1_Object + if x is None: + s = b"" + else: + if isinstance(x, ASN1_Object): + s = x.enc(pkt.ASN1_codec) + else: + s = bytes(x) + index = field.alternative_index(x) + if index is not None: + # X.696 20.2: the chosen alternative is prefixed with its tag + tag_class, tag_number = _OER_tag_parts( + field.choice_order[index] + ) + s = OER_tag_enc(tag_number, tag_class) + s + return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) + + +ASN1_Codecs.OER.register_hooks( + sequence_m2i=_oer_sequence_m2i, + sequence_build=_oer_sequence_build, + sequence_of_m2i=_oer_sequence_of_m2i, + sequence_of_build=_oer_sequence_of_build, + choice_m2i=_oer_choice_m2i, + choice_i2m=_oer_choice_i2m, +) diff --git a/scapy/contrib/uper.py b/scapy/contrib/uper.py new file mode 100644 index 00000000000..b64dc1d8369 --- /dev/null +++ b/scapy/contrib/uper.py @@ -0,0 +1,1422 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +# scapy.contrib.description = ASN.1 Unaligned Packed Encoding Rules (UPER) +# scapy.contrib.status = loads + +""" +Unaligned Packed Encoding Rules (UPER) for ASN.1 + +As specified in ITU-T X.691 | ISO/IEC 8825-2. + +UPER is registered on ``ASN1_Codecs.PER``. Schema-driven encoding and decoding +(``ASN1F_SEQUENCE``, ``ASN1F_CHOICE``, ``ASN1F_SEQUENCE_OF``, +``ASN1F_ENUMERATED``) is supported for common field types. Value ranges are +declared with ``uper_min=``/``uper_max=``, fixed sizes with ``size_len=``, and +an extension marker with ``uper_extensible=True``. Content of 16K units or +more is fragmented as required by 11.9.3.8. + +Not supported yet: extension additions (an encoding that carries them is +refused rather than misparsed), SET, REAL, and the known-multiplier character +string encodings, which are emitted as plain octets rather than 7 or 4 bits +per character. + +``ASN1F_CHOICE`` alternatives are indexed in declaration order, where 10.2 +asks for the canonical order of their tags. The two coincide for a schema +compiled with AUTOMATIC TAGS, which assigns the tags in declaration order; +declare the alternatives in ascending tag order otherwise. +""" + +from scapy.error import warning +from scapy.compat import orb, bytes_encode +from scapy.utils import binrepr, inet_aton, inet_ntoa +from scapy.asn1.ber import BER_num_dec, BER_num_enc +from scapy.asn1.asn1 import ( + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) +# Re-exported: DEFAULT components are what the preamble bits describe. +from scapy.asn1fields import ASN1F_DEFAULT # noqa: F401 + +from typing import ( + Any, + AnyStr, + Callable, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + + +################### +# UPER encoding # +################### + + +class UPER_Encoding_Error(ASN1_Encoding_Error): + def __init__(self, + msg, # type: str + encoded=None, # type: Optional[Union['UPERcodec_Object[Any]', str]] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.encoded = encoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.encoded, ASN1_Object): + s += "\n### Already encoded ###\n%s" % self.encoded.strshow() + else: + s += "\n### Already encoded ###\n%r" % self.encoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +class UPER_Decoding_Error(ASN1_Decoding_Error): + def __init__(self, + msg, # type: str + decoded=None, # type: Optional[Any] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.decoded = decoded + + def __str__(self): + # type: () -> str + s = Exception.__str__(self) + if isinstance(self.decoded, ASN1_Object): + s += "\n### Already decoded ###\n%s" % self.decoded.strshow() + else: + s += "\n### Already decoded ###\n%r" % self.decoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +def UPER_bits_for_range(size): + # type: (int) -> int + if size <= 0: + return 0 + return size.bit_length() + + +# X.691 11.9.3.8: content of 16K units or more is split into fragments, each +# one holding a multiple of this many units. +UPER_FRAGMENT_SIZE = 16384 + + +def _uper_bits_to_bytes(value, number_of_bits): + # type: (int, int) -> bytes + # X.691 11.1: an encoding is padded with zero bits up to an octet + # boundary. + if number_of_bits == 0: + return b"" + padding = -number_of_bits % 8 + return (value << padding).to_bytes((number_of_bits + padding) // 8, "big") + + +class UPER_Encoder(object): + def __init__(self): + # type: () -> None + self.number_of_bits = 0 + self.value = 0 + self.chunks_number_of_bits = 0 + self.chunks = [] # type: List[List[int]] + + def append_bit(self, bit): + # type: (int) -> None + self.number_of_bits += 1 + self.value <<= 1 + self.value |= 1 if bit else 0 + + def append_bits(self, data, number_of_bits): + # type: (bytes, int) -> None + if number_of_bits == 0: + return + value = int.from_bytes(data, "big") + value >>= (8 * len(data) - number_of_bits) + self.append_non_negative_binary_integer(value, number_of_bits) + + def append_non_negative_binary_integer(self, value, number_of_bits): + # type: (int, int) -> None + if number_of_bits == 0: + return + if self.number_of_bits > 4096: + self.chunks.append([self.value, self.number_of_bits]) + self.chunks_number_of_bits += self.number_of_bits + self.number_of_bits = 0 + self.value = 0 + self.number_of_bits += number_of_bits + self.value <<= number_of_bits + self.value |= value & ((1 << number_of_bits) - 1) + + def append_bytes(self, data): + # type: (bytes) -> None + self.append_bits(data, 8 * len(data)) + + def append_length_determinant(self, length): + # type: (int) -> None + # X.691 11.9.3.6/11.9.3.7 only define the one and two octet forms up + # to 16K. Longer content has to be fragmented, which requires slicing + # the content itself, so leave that to append_fragmented rather than + # silently emitting a determinant that does not match what follows. + if length >= UPER_FRAGMENT_SIZE: + raise UPER_Encoding_Error( + "UPER_Encoder: length %i requires fragmentation" % length + ) + if length < 128: + encoded = bytes([length]) + else: + encoded = bytes([(0x80 | (length >> 8)), (length & 0xff)]) + self.append_bytes(encoded) + + def append_fragmented(self, count, append_units): + # type: (int, Callable[[int, int], None]) -> None + # X.691 11.9.3.8: emit the content as fragments of at most 4 * 16K + # units, each preceded by its own determinant, and always terminate + # with a determinant below 16K (possibly zero). append_units(offset, + # size) appends the units of one fragment. + offset = 0 + remaining = count + while remaining >= UPER_FRAGMENT_SIZE: + number_of_fragments = min(remaining // UPER_FRAGMENT_SIZE, 4) + size = number_of_fragments * UPER_FRAGMENT_SIZE + self.append_bytes(bytes([0xc0 | number_of_fragments])) + append_units(offset, size) + offset += size + remaining -= size + self.append_length_determinant(remaining) + append_units(offset, remaining) + + def append_unconstrained_whole_number(self, value): + # type: (int) -> None + # X.691 11.4: the shortest two's complement encoding. A negative value + # needs one bit less than its magnitude suggests, as -2**(8n-1) still + # fits in n octets, hence the increment before measuring. + magnitude = value + 1 if value < 0 else value + number_of_bytes = (magnitude.bit_length() + 8) // 8 + self.append_length_determinant(number_of_bytes) + self.append_non_negative_binary_integer( + value & ((1 << (8 * number_of_bytes)) - 1), 8 * number_of_bytes + ) + + def as_bytes(self): + # type: () -> bytes + value = 0 + number_of_bits = 0 + for chunk_value, chunk_number_of_bits in self.chunks: + value <<= chunk_number_of_bits + value |= chunk_value + number_of_bits += chunk_number_of_bits + value <<= self.number_of_bits + value |= self.value + number_of_bits += self.number_of_bits + return _uper_bits_to_bytes(value, number_of_bits) + + +def UPER_has_unexpected_remainder(dec): + # type: (UPER_Decoder) -> bool + if dec.number_of_bits == 0: + return False + mask = (1 << dec.number_of_bits) - 1 + return (dec._bits & mask) != 0 + + +class UPER_Decoder(object): + def __init__(self, encoded): + # type: (bytes) -> None + self.total_number_of_bits = 8 * len(encoded) + self.number_of_bits = self.total_number_of_bits + if encoded: + self._bits = int.from_bytes(encoded, "big") + else: + self._bits = 0 + + def _read_offset(self): + # type: () -> int + return self.total_number_of_bits - self.number_of_bits + + def _read_bits_int(self, number_of_bits): + # type: (int) -> int + if number_of_bits == 0: + return 0 + consumed = self._read_offset() + shift = self.total_number_of_bits - consumed - number_of_bits + mask = (1 << number_of_bits) - 1 + return (self._bits >> shift) & mask + + def read_bit(self): + # type: () -> int + if self.number_of_bits == 0: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + bit = self._read_bits_int(1) + self.number_of_bits -= 1 + return bit + + def read_bits(self, number_of_bits): + # type: (int) -> bytes + if number_of_bits > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + if number_of_bits == 0: + return b"" + value = self._read_bits_int(number_of_bits) + self.number_of_bits -= number_of_bits + return _uper_bits_to_bytes(value, number_of_bits) + + def remaining(self): + # type: () -> bytes + if self.number_of_bits == 0: + return b"" + value = self._read_bits_int(self.number_of_bits) + return _uper_bits_to_bytes(value, self.number_of_bits) + + def remaining_bytes(self): + # type: () -> bytes + # A standalone UPER encoding is padded to an octet boundary, so the + # bits left over inside the current octet are padding; only whole + # octets after it are actual remaining input. + pad = -self._read_offset() % 8 + self.number_of_bits = max(0, self.number_of_bits - pad) + return self.remaining() + + def read_bytes(self, number_of_bytes): + # type: (int) -> bytes + return self.read_bits(8 * number_of_bytes) + + def read_non_negative_binary_integer(self, number_of_bits): + # type: (int) -> int + if number_of_bits > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + if number_of_bits == 0: + return 0 + value = self._read_bits_int(number_of_bits) + self.number_of_bits -= number_of_bits + return value + + def _read_length_determinant(self): + # type: () -> Tuple[int, bool] + # Returns the number of units and whether more fragments follow. + value = self.read_non_negative_binary_integer(8) + if (value & 0x80) == 0x00: + return value, False + if (value & 0xc0) == 0x80: + return ( + ((value & 0x7f) << 8) | + self.read_non_negative_binary_integer(8) + ), False + if 0xc1 <= value <= 0xc4: + return (value & 0x0f) * UPER_FRAGMENT_SIZE, True + raise UPER_Decoding_Error( + "UPER_Decoder: bad length determinant 0x%02x" % value + ) + + def read_length_determinant(self): + # type: () -> int + length, fragmented = self._read_length_determinant() + if fragmented: + raise UPER_Decoding_Error( + "UPER_Decoder: unexpected fragmented length determinant" + ) + return length + + def read_fragmented(self, read_units): + # type: (Callable[[int], None]) -> None + # Counterpart of UPER_Encoder.append_fragmented: read_units(size) is + # called once per fragment, the last one being the (possibly empty) + # fragment introduced by a determinant below 16K. + while True: + size, fragmented = self._read_length_determinant() + read_units(size) + if not fragmented: + return + + def read_unconstrained_whole_number(self): + # type: () -> int + number_of_bytes = self.read_length_determinant() + if number_of_bytes == 0: + raise UPER_Decoding_Error( + "UPER_Decoder: integer with an empty length determinant" + ) + enc = self.read_non_negative_binary_integer(8 * number_of_bytes) + sign_bit = 1 << (8 * number_of_bytes - 1) + if enc & sign_bit: + return enc - (1 << (8 * number_of_bytes)) + return enc + + +def UPER_constrained_int_enc(enc, value, minimum, maximum): + # type: (UPER_Encoder, int, int, int) -> None + # X.691 13.2.2: the field is sized after the range, so a value outside it + # cannot be expressed. Callers handle extensibility before coming here. + if not minimum <= value <= maximum: + raise UPER_Encoding_Error( + "UPER_constrained_int_enc: got %i while expecting %i..%i" % + (value, minimum, maximum) + ) + enc.append_non_negative_binary_integer( + value - minimum, UPER_bits_for_range(maximum - minimum) + ) + + +def UPER_constrained_int_dec(dec, minimum, maximum): + # type: (UPER_Decoder, int, int) -> int + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + return value + minimum + + +def _uper_check_size(name, unit, count, minimum, maximum): + # type: (str, str, int, int, int) -> None + # The determinant is sized after the constraint, so a value that violates + # it cannot be expressed: refuse rather than emit something the peer reads + # as a different length. + if not minimum <= count <= maximum: + raise UPER_Encoding_Error( + "%s: got %i %s while expecting %s" % + (name, count, unit, minimum if minimum == maximum + else "%i..%i" % (minimum, maximum)) + ) + + +def UPER_octet_string_enc(enc, data, minimum=None, maximum=None): + # type: (UPER_Encoder, bytes, Optional[int], Optional[int]) -> None + if minimum is not None and maximum is not None: + _uper_check_size( + "UPER_octet_string_enc", "octets", len(data), minimum, maximum, + ) + if minimum != maximum: + enc.append_non_negative_binary_integer( + len(data) - minimum, + UPER_bits_for_range(maximum - minimum), + ) + enc.append_bytes(data) + else: + enc.append_fragmented( + len(data), + lambda offset, size: enc.append_bytes(data[offset:offset + size]), + ) + + +def UPER_octet_string_dec(dec, minimum=None, maximum=None): + # type: (UPER_Decoder, Optional[int], Optional[int]) -> bytes + if minimum is not None and maximum is not None: + length = minimum + if minimum != maximum: + length += dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + return dec.read_bytes(length) + fragments = [] # type: List[bytes] + dec.read_fragmented(lambda size: fragments.append(dec.read_bytes(size))) + return b"".join(fragments) + + +def UPER_choice_index_enc(enc, index, number_of_choices): + # type: (UPER_Encoder, int, int) -> None + enc.append_non_negative_binary_integer( + index, UPER_bits_for_range(number_of_choices - 1) + ) + + +def UPER_choice_index_dec(dec, number_of_choices): + # type: (UPER_Decoder, int) -> int + return dec.read_non_negative_binary_integer( + UPER_bits_for_range(number_of_choices - 1) + ) + + +class UPERcodec_metaclass(type): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type['UPERcodec_Object[Any]'] + c = cast('Type[UPERcodec_Object[Any]]', + super(UPERcodec_metaclass, cls).__new__(cls, name, bases, dct)) + try: + c.tag.register(c.codec, c) + except Exception: + warning("Error registering %r for %r" % (c.tag, c.codec)) + return c + + +_K = TypeVar('_K') + + +class UPERcodec_Object(Generic[_K], metaclass=UPERcodec_metaclass): + codec = ASN1_Codecs.PER + tag = ASN1_Class_UNIVERSAL.ANY + + @classmethod + def asn1_object(cls, val): + # type: (_K) -> ASN1_Object[_K] + return cls.tag.asn1_object(val) + + # The bit-oriented encode_into()/dec_from_decoder() pair is the primitive + # every codec implements; enc()/do_dec() below are the standalone (byte + # buffer) entry points, and pass every codec option straight through. + + @classmethod + def encode_into(cls, enc, s, **kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # No schema information here (ANY): guess from the Python type. + if isinstance(s, (str, bytes)): + UPERcodec_STRING.encode_into(enc, s, **kwargs) + return + try: + UPERcodec_INTEGER.encode_into(enc, int(s), **kwargs) + except Exception: + raise UPER_Encoding_Error( + "Cannot encode value %r for %s" % (s, cls.__name__), + encoded=s + ) + + @classmethod + def dec_from_decoder(cls, dec, **kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Any] + raise UPER_Decoding_Error( + "%s: Cannot decode unknown UPER type without context" % + cls.__name__, remaining=dec.remaining() + ) + + @classmethod + def enc(cls, s, **kwargs): + # type: (Any, **Any) -> bytes + enc = UPER_Encoder() + cls.encode_into(enc, s, **kwargs) + return enc.as_bytes() + + @classmethod + def do_dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[ASN1_Object[Any], bytes] # noqa: E501 + dec = UPER_Decoder(s) + return cls.dec_from_decoder(dec, **kwargs), dec.remaining_bytes() + + @classmethod + def dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] # noqa: E501 + if not safe: + return cls.do_dec(s, context, safe, **kwargs) + try: + return cls.do_dec(s, context, safe, **kwargs) + except (UPER_Decoding_Error, ASN1_Error) as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + + @classmethod + def safedec(cls, s, context=None, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], **Any) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] # noqa: E501 + return cls.dec(s, context, safe=True, **kwargs) + + +# No tagging hook: PER encodes no tag at all, so a field is left alone. +ASN1_Codecs.PER.register_stem(UPERcodec_Object) + + +######################### +# UPERcodec objects # +######################### + + +def _uper_int_range(size_len, uper_min, uper_max, oer_unsigned=False): + # type: (Optional[int], Optional[int], Optional[int], bool) -> Tuple[Optional[int], Optional[int]] # noqa: E501 + if uper_min is not None or uper_max is not None: + return uper_min, uper_max + if size_len in (1, 2, 4, 8) and oer_unsigned: + return 0, (256 ** size_len) - 1 + return None, None + + +class UPERcodec_INTEGER(UPERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.INTEGER + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + i, # type: int + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> None + minimum, maximum = _uper_int_range(size_len, uper_min, uper_max, oer_unsigned) + if uper_extensible and minimum is not None and maximum is not None: + if minimum <= i <= maximum: + enc.append_bit(0) + else: + enc.append_bit(1) + enc.append_unconstrained_whole_number(i) + return + if minimum is not None and maximum is not None: + UPER_constrained_int_enc(enc, i, minimum, maximum) + else: + enc.append_unconstrained_whole_number(i) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + oer_unsigned=False, # type: bool + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[int] + minimum, maximum = _uper_int_range(size_len, uper_min, uper_max, oer_unsigned) + if uper_extensible and minimum is not None and maximum is not None: + if dec.read_bit(): + value = dec.read_unconstrained_whole_number() + return cls.asn1_object(value) + if minimum is not None and maximum is not None: + value = UPER_constrained_int_dec(dec, minimum, maximum) + else: + value = dec.read_unconstrained_whole_number() + return cls.asn1_object(value) + + +class UPERcodec_BOOLEAN(UPERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + @classmethod + def encode_into(cls, enc, i, **_kwargs): + # type: (UPER_Encoder, int, **Any) -> None + enc.append_bit(1 if i else 0) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[int] + return cls.asn1_object(dec.read_bit()) + + +def _uper_bytes_to_bitstr(data, nbits): + # type: (bytes, int) -> str + bitstr = "".join(binrepr(orb(x)).zfill(8) for x in data) + return bitstr[:nbits] + + +def _uper_bit_string_parts(_s): + # type: (Any) -> Tuple[bytes, int] + if isinstance(_s, tuple) and len(_s) == 2: + data, nbits = _s + return bytes_encode(data), nbits + if isinstance(_s, str) and _s and all(c in "01" for c in _s): + nbits = len(_s) + padded = _s + "0" * ((8 - nbits % 8) % 8) + data = int(padded or "0", 2).to_bytes( + max(1, len(padded) // 8), "big" + ) + return data, nbits + s = bytes_encode(_s) + return s, 8 * len(s) + + +def _uper_size_bounds(size_len, uper_min, uper_max): + # type: (Optional[int], Optional[int], Optional[int]) -> Tuple[Optional[int], Optional[int]] # noqa: E501 + # A SIZE constraint given as size_len is a fixed size, i.e. a range whose + # bounds coincide. + if size_len: + return size_len, size_len + return uper_min, uper_max + + +class UPERcodec_BIT_STRING(UPERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + _s, # type: Any + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> None + s, nbits = _uper_bit_string_parts(_s) + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + if minimum is not None and maximum is not None: + _uper_check_size(cls.__name__, "bits", nbits, minimum, maximum) + if minimum != maximum: + enc.append_non_negative_binary_integer( + nbits - minimum, UPER_bits_for_range(maximum - minimum) + ) + enc.append_bits(s, nbits) + else: + # X.691 16.11: the determinant counts bits, not octets, and no + # padding is inserted before whatever follows the bit string. + enc.append_fragmented( + nbits, + # Fragments hold whole multiples of 16K bits, so every chunk + # but the last starts and ends on an octet boundary. + lambda offset, size: enc.append_bits( + s[offset // 8:(offset + size + 7) // 8], size + ), + ) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[str] + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + if minimum is not None and maximum is not None: + nbits = minimum + if minimum != maximum: + nbits += dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + else: + fragments = [] # type: List[bytes] + sizes = [] # type: List[int] + + def read_fragment(size): + # type: (int) -> None + fragments.append(dec.read_bits(size)) + sizes.append(size) + + dec.read_fragmented(read_fragment) + return cls.asn1_object( + _uper_bytes_to_bitstr(b"".join(fragments), sum(sizes)) + ) + raw = dec.read_bits(nbits) + return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) + + +class UPERcodec_STRING(UPERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.STRING + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + _s, # type: Union[str, bytes] + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> None + s = bytes_encode(_s) + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + UPER_octet_string_enc(enc, s, minimum, maximum) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[Any] + minimum, maximum = _uper_size_bounds(size_len, uper_min, uper_max) + raw = UPER_octet_string_dec(dec, minimum, maximum) + return cls.asn1_object(raw) + + +class UPERcodec_NULL(UPERcodec_Object[None]): + tag = ASN1_Class_UNIVERSAL.NULL + + @classmethod + def encode_into(cls, enc, _s, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # NULL has an empty encoding. + return + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[None] + return cls.asn1_object(None) + + @classmethod + def do_dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[ASN1_Object[None], bytes] # noqa: E501 + # NULL occupies no bits at all, so the input is left untouched. + return cls.asn1_object(None), s + + +class UPERcodec_OID(UPERcodec_Object[bytes]): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def encode_into(cls, enc, _oid, **_kwargs): + # type: (UPER_Encoder, AnyStr, **Any) -> None + oid = bytes_encode(_oid) + if oid: + lst = [int(x) for x in oid.split(b".")] + lst = [40 * lst[0] + lst[1]] + lst[2:] + else: + lst = [] + body = b"".join(BER_num_enc(k) for k in lst) + enc.append_fragmented( + len(body), + lambda offset, size: enc.append_bytes(body[offset:offset + size]), + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[bytes] + fragments = [] # type: List[bytes] + dec.read_fragmented(lambda size: fragments.append(dec.read_bytes(size))) + content = b"".join(fragments) + lst = [] + while content: + val, content = BER_num_dec(content) + lst.append(val) + if len(lst) > 0: + lst.insert(0, lst[0] // 40) + lst[1] %= 40 + return cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)) + + +def UPER_enumerated_enc(enc, value, enum_values): + # type: (UPER_Encoder, int, List[int]) -> None + if not enum_values: + raise UPER_Encoding_Error("UPER_enumerated_enc: empty enumeration") + try: + index = enum_values.index(value) + except ValueError: + raise UPER_Encoding_Error( + "UPER_enumerated_enc: unknown enumeration value %r" % value + ) + UPER_choice_index_enc(enc, index, len(enum_values)) + + +def UPER_enumerated_dec(dec, enum_values): + # type: (UPER_Decoder, List[int]) -> int + if not enum_values: + raise UPER_Decoding_Error("UPER_enumerated_dec: empty enumeration") + index = UPER_choice_index_dec(dec, len(enum_values)) + if index >= len(enum_values): + raise UPER_Decoding_Error( + "UPER_enumerated_dec: index %i out of range" % index + ) + return enum_values[index] + + +class UPERcodec_ENUMERATED(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + i, # type: int + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + uper_enum_values=None, # type: Optional[List[int]] + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> None + if uper_enum_values is not None: + if uper_extensible: + # X.691 14.3: a one bit prefix says whether the value is an + # extension addition. Only root values can be encoded. + if i not in uper_enum_values: + raise UPER_Encoding_Error( + "UPERcodec_ENUMERATED: extension additions are not " + "supported" + ) + enc.append_bit(0) + UPER_enumerated_enc(enc, i, uper_enum_values) + return + minimum, maximum = cls._range( + size_len, uper_min, uper_max, UPER_Encoding_Error + ) + UPER_constrained_int_enc(enc, i, minimum, maximum) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + size_len=0, # type: Optional[int] + uper_min=None, # type: Optional[int] + uper_max=None, # type: Optional[int] + uper_enum_values=None, # type: Optional[List[int]] + uper_extensible=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[int] + if uper_enum_values is not None: + if uper_extensible and dec.read_bit(): + raise UPER_Decoding_Error( + "UPERcodec_ENUMERATED: extension additions are not " + "supported" + ) + return cls.asn1_object(UPER_enumerated_dec(dec, uper_enum_values)) + minimum, maximum = cls._range( + size_len, uper_min, uper_max, UPER_Decoding_Error + ) + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + minimum + return cls.asn1_object(value) + + @staticmethod + def _range(size_len, uper_min, uper_max, error): + # type: (Optional[int], Optional[int], Optional[int], Any) -> Tuple[int, int] # noqa: E501 + # Without the enumeration itself the index range has to come from + # the declared bounds; deriving it from the value at hand would + # make the width depend on the value, which the decoder cannot + # reproduce. + minimum = uper_min if uper_min is not None else 0 + maximum = uper_max if uper_max is not None else (size_len or None) + if maximum is None: + raise error("UPERcodec_ENUMERATED: missing range") + return minimum, maximum + + +class UPERcodec_SEQUENCE(UPERcodec_Object[Union[bytes, List[Any]]]): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def encode_into(cls, enc, _ll, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # A finished encoding is padded to an octet boundary, so its real bit + # length is lost and it cannot be spliced into a bitstream. Sequences + # are encoded through the ASN1F_SEQUENCE hooks instead. + raise UPER_Encoding_Error( + "UPERcodec_SEQUENCE: schema-defined field order required" + ) + + @classmethod + def enc(cls, _ll, **_kwargs): + # type: (Union[bytes, List[UPERcodec_Object[Any]]], **Any) -> bytes + if isinstance(_ll, bytes): + return _ll + raise UPER_Encoding_Error( + "UPERcodec_SEQUENCE: schema-defined field order required" + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Union[bytes, List[Any]]] + raise UPER_Decoding_Error( + "UPERcodec_SEQUENCE: decoding requires schema-defined field order", + remaining=dec.remaining() + ) + + +class UPERcodec_SET(UPERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class UPERcodec_IPADDRESS(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def encode_into(cls, enc, ipaddr_ascii, **_kwargs): + # type: (UPER_Encoder, str, **Any) -> None + try: + s = inet_aton(ipaddr_ascii) + except Exception: + raise UPER_Encoding_Error("IPv4 address could not be encoded") + UPER_octet_string_enc(enc, s, 4, 4) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[str] + raw = UPER_octet_string_dec(dec, 4, 4) + try: + ipaddr_ascii = inet_ntoa(raw) + except Exception: + raise UPER_Decoding_Error("IP address could not be decoded") + return cls.asn1_object(ipaddr_ascii) + + +class UPERcodec_COUNTER32(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class UPERcodec_COUNTER64(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER64 + + +class UPERcodec_GAUGE32(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class UPERcodec_TIME_TICKS(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +# string aliases +class UPERcodec_UTF8_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class UPERcodec_NUMERIC_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class UPERcodec_PRINTABLE_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class UPERcodec_T61_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class UPERcodec_VIDEOTEX_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class UPERcodec_IA5_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class UPERcodec_GENERAL_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERAL_STRING + + +class UPERcodec_UTC_TIME(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + +class UPERcodec_GENERALIZED_TIME(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class UPERcodec_ISO646_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class UPERcodec_UNIVERSAL_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class UPERcodec_BMP_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +########################## +# ASN1F field hooks # +########################## + +def _field_extensible(field): + # type: (Any) -> bool + return bool(getattr(field, "codec_opts", {}).get("uper_extensible", False)) + + +def _field_range(field): + # type: (Any) -> Tuple[Optional[int], Optional[int]] + opts = getattr(field, "codec_opts", {}) + return opts.get("uper_min"), opts.get("uper_max") + + +def _uper_decode_all(s, read): + # type: (bytes, Callable[[UPER_Decoder], Any]) -> Any + # The field owns the whole substring it was handed, so any bit left set + # beyond the octet padding means the encoding did not match the schema. + dec = UPER_Decoder(s) + value = read(dec) + if UPER_has_unexpected_remainder(dec): + raise UPER_Decoding_Error( + "unexpected remainder", + remaining=dec.remaining(), + ) + return value + + +def _uper_use_object_enc(field, pkt, item): + # type: (Any, Any, Any) -> bool + # Always pass constraints through codec.enc(**kwargs). + return False + + +def _uper_sequence_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + _uper_decode_all(s, lambda dec: ( + _uper_sequence_dissect_from_decoder(field, pkt, dec) + )) + return [], b"" + + +def _uper_sequence_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1fields import ASN1F_field + enc = UPER_Encoder() + _uper_sequence_encode_into(field, enc, pkt) + return ASN1F_field.i2m(field, pkt, enc.as_bytes()) + + +def _uper_sequence_dissect_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> None + from scapy.asn1fields import ASN1F_badsequence, ASN1F_optional + if _field_extensible(field): + if dec.read_bit(): + raise UPER_Decoding_Error( + "ASN1F_SEQUENCE: extension additions are not supported" + ) + optionals = field.optionals + presence = [dec.read_bit() for _ in optionals] + opt_idx = 0 + for obj in field.seq: + if isinstance(obj, ASN1F_optional): + if not presence[opt_idx]: + obj.set_absent(pkt) + opt_idx += 1 + continue + opt_idx += 1 + try: + obj.dissect_from_decoder(pkt, dec) + except ASN1F_badsequence: + break + + +def _uper_sequence_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1fields import ASN1F_optional + if _field_extensible(field): + enc.append_bit(0) + for opt in field.optionals: + enc.append_bit(0 if opt.is_empty(pkt) else 1) + for obj in field.seq: + if isinstance(obj, ASN1F_optional) and obj.is_empty(pkt): + continue + obj.encode_into(enc, pkt) + + +def _uper_sequence_of_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[list, bytes] + return _uper_decode_all(s, lambda dec: ( + _uper_sequence_of_m2i_from_decoder(field, pkt, dec) + )), b"" + + +def _uper_sequence_of_build(field, pkt): + # type: (Any, Any) -> bytes + from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Object + val = getattr(pkt, field.name) + if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: + s = val # type: Any + else: + # An unset field counts as an empty one, size constraint included + enc = UPER_Encoder() + _uper_sequence_of_encode_into(field, enc, pkt, val) + s = enc.as_bytes() + return field.i2m(pkt, s) + + +def _uper_sequence_of_m2i_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> list + lst = [] + + def read_items(count): + # type: (int) -> None + for _ in range(count): + item = _extract_packet_from_decoder(field, dec, pkt) + lst.append(item) + + if _field_extensible(field) and dec.read_bit(): + dec.read_fragmented(read_items) + else: + _uper_count_dec(field, dec, read_items) + return lst + + +def _uper_sequence_of_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + if value is None: + value = getattr(pkt, field.name) + if value is None: + _uper_count_enc(field, enc, 0, lambda offset, size: None) + return + count = len(value) + + def append_items(offset, size): + # type: (int, int) -> None + for item in value[offset:offset + size]: + if field.holds_packets: + item.ASN1_root.encode_into(enc, item) + else: + field.fld.encode_into(enc, pkt, item) + + uper_min, uper_max = _field_range(field) + if _field_extensible(field): + if ( + uper_min is not None and uper_max is not None and + uper_min <= count <= uper_max + ): + enc.append_bit(0) + else: + enc.append_bit(1) + enc.append_fragmented(count, append_items) + return + _uper_count_enc(field, enc, count, append_items) + + +def _uper_choice_m2i(field, pkt, s): + # type: (Any, Any, bytes) -> Tuple[Any, bytes] + return _uper_decode_all(s, lambda dec: ( + _uper_choice_m2i_from_decoder(field, pkt, dec) + )), b"" + + +def _uper_choice_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + if x is None: + s = b"" + else: + enc = UPER_Encoder() + _uper_choice_encode_into(field, enc, pkt, x) + s = enc.as_bytes() + return field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag) + + +def _uper_choice_m2i_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> Any + from scapy.asn1.asn1 import ASN1_Error + if _field_extensible(field): + if dec.read_bit(): + raise UPER_Decoding_Error( + "ASN1F_CHOICE: extension additions are not supported" + ) + order = field.choice_order + if len(order) > 1: + index = UPER_choice_index_dec(dec, len(order)) + else: + index = 0 + if index >= len(order): + raise ASN1_Error( + "ASN1F_CHOICE: unexpected index %s in '%s'" % + (index, field.name) + ) + choice = field.choice_list[index] + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + 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) + + +def _uper_choice_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1.asn1 import ASN1_Error + if value is None: + value = getattr(pkt, field.name) + index = field.alternative_index(value) + if index is None: + raise ASN1_Error( + "ASN1F_CHOICE: cannot encode unknown alternative in '%s'" % + field.name + ) + if _field_extensible(field): + enc.append_bit(0) + order = field.choice_order + if len(order) > 1: + UPER_choice_index_enc(enc, index, len(order)) + choice = field.choice_list[index] + if hasattr(choice, "ASN1_root"): + value.ASN1_root.encode_into(enc, value) + elif isinstance(choice, type): + choice(field.name, b"").encode_into(enc, pkt, value) + else: + choice.encode_into(enc, pkt, value) + + +def _uper_packet_m2i_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> Any + cls = field._resolve_cls(pkt) + p = cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p + + +def _uper_packet_i2m(field, pkt, x): + # type: (Any, Any, Any) -> bytes + if x is None: + s = b"" + else: + enc = UPER_Encoder() + _uper_packet_encode_into(field, enc, pkt, x) + s = enc.as_bytes() + return field._tagging_enc( + pkt, s, + implicit_tag=field.implicit_tag, + explicit_tag=field.explicit_tag, + ) + + +def _uper_packet_encode_into(field, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1.asn1 import ASN1_Object + if value is None: + value = getattr(pkt, field.name) + if value is None: + return + if isinstance(value, ASN1_Object): + value = value.val + value.ASN1_root.encode_into(enc, value) + + +def _uper_count_enc(field, enc, count, append_items): + # type: (Any, Any, int, Callable[[int, int], None]) -> None + # The count of a SEQUENCE OF is a constrained whole number when the field + # carries a size constraint; otherwise it is a length determinant, and the + # items themselves are what gets fragmented, hence the callback. + uper_min, uper_max = _field_range(field) + if uper_min is not None and uper_max is not None: + UPER_constrained_int_enc(enc, count, uper_min, uper_max) + append_items(0, count) + else: + enc.append_fragmented(count, append_items) + + +def _uper_count_dec(field, dec, read_items): + # type: (Any, Any, Callable[[int], None]) -> None + uper_min, uper_max = _field_range(field) + if uper_min is not None and uper_max is not None: + read_items(UPER_constrained_int_dec(dec, uper_min, uper_max)) + else: + dec.read_fragmented(read_items) + + +def _extract_packet_from_decoder(field, dec, pkt): + # type: (Any, Any, Any) -> Any + if field.holds_packets: + p = field.cls() + p.add_underlayer(pkt) + p.ASN1_root.dissect_from_decoder(p, dec) + return p + return field.fld.m2i_from_decoder(pkt, dec) + + +def _install_uper_asn1fields(): + # type: () -> None + """Attach the UPER bitstream helpers onto the asn1fields classes.""" + from scapy import asn1fields as af + from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Error, ASN1_Object + + def m2i_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> Any + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + return codec.dec_from_decoder( # type: ignore[attr-defined] + dec, **self._codec_kwargs(pkt), + ) + + def dissect_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> None + self.set_val(pkt, self.m2i_from_decoder(pkt, dec)) + + def encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + if value is None: + value = getattr(pkt, self.name) + if value is None: + return + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + if isinstance(value, ASN1_Object): + if (self.ASN1_tag == ASN1_Class_UNIVERSAL.ANY or + value.tag == ASN1_Class_UNIVERSAL.RAW or + value.tag == ASN1_Class_UNIVERSAL.ERROR or + self.ASN1_tag == value.tag): + raw = value.val + else: + raise ASN1_Error( + "Encoding Error: got %r instead of an %r for field [%s]" % + (value, self.ASN1_tag, self.name) + ) + else: + raw = value + codec.encode_into( # type: ignore[attr-defined] + enc, raw, **self._codec_kwargs(pkt), + ) + + def opt_dissect_from_decoder(self, pkt, dec): + # type: (Any, Any, Any) -> None + return self._field.dissect_from_decoder(pkt, dec) + + def opt_encode_into(self, enc, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + self._field.encode_into(enc, pkt, value) + + for field_cls, methods in ( + (af.ASN1F_field, { + "m2i_from_decoder": m2i_from_decoder, + "dissect_from_decoder": dissect_from_decoder, + "encode_into": encode_into, + }), + (af.ASN1F_SEQUENCE, { + "dissect_from_decoder": _uper_sequence_dissect_from_decoder, + "encode_into": _uper_sequence_encode_into, + }), + (af.ASN1F_SEQUENCE_OF, { + "m2i_from_decoder": _uper_sequence_of_m2i_from_decoder, + "encode_into": _uper_sequence_of_encode_into, + }), + (af.ASN1F_CHOICE, { + "m2i_from_decoder": _uper_choice_m2i_from_decoder, + "encode_into": _uper_choice_encode_into, + }), + (af.ASN1F_PACKET, { + "m2i_from_decoder": _uper_packet_m2i_from_decoder, + "encode_into": _uper_packet_encode_into, + }), + (af.ASN1F_optional, { + "dissect_from_decoder": opt_dissect_from_decoder, + "encode_into": opt_encode_into, + }), + ): + for method_name, func in methods.items(): + setattr(field_cls, method_name, func) + + _orig_enum_codec_kwargs = af.ASN1F_enum_INTEGER._codec_kwargs + + def enum_codec_kwargs(self, pkt): + # type: (Any, Any) -> Any + kwargs = _orig_enum_codec_kwargs(self, pkt) + # The permitted values belong to the UPER encoding, not to the field + # definition, so they are only added for PER packets. Other codecs + # keep an empty codec_opts and their item.enc() fast path. + codec = getattr(pkt, "ASN1_codec", None) + if codec is ASN1_Codecs.PER: + # X.691 14.1: the index follows the enumeration values in + # ascending order, whatever order they were declared in. + kwargs.setdefault("uper_enum_values", sorted(self.i2s)) + return kwargs + + af.ASN1F_enum_INTEGER._codec_kwargs = enum_codec_kwargs # type: ignore[assignment] + + +_install_uper_asn1fields() +ASN1_Codecs.PER.register_hooks( + use_object_enc=_uper_use_object_enc, + sequence_m2i=_uper_sequence_m2i, + sequence_build=_uper_sequence_build, + sequence_of_m2i=_uper_sequence_of_m2i, + sequence_of_build=_uper_sequence_of_build, + choice_m2i=_uper_choice_m2i, + choice_i2m=_uper_choice_i2m, + packet_i2m=_uper_packet_i2m, +) diff --git a/test/contrib/oer.uts b/test/contrib/oer.uts new file mode 100644 index 00000000000..7a4a61b3a40 --- /dev/null +++ b/test/contrib/oer.uts @@ -0,0 +1,1270 @@ +% Tests for ASN.1 OER encoding + +# +# Try me with: +# bash test/run_tests -t test/scapy/layers/oer.uts -F + ++ ASN.1 OER load += prepare helpers and packet classes +import scapy.contrib.oer + +from scapy.contrib.oer import * + +from scapy.packet import raw + +class OERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class OERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class OEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class OERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class OERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class OERNestedSequenceTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ASN1F_INTEGER("id", 0), + ) + +class OERSequenceOfWithTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ASN1F_INTEGER("id", 0), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +INTEGER_VECTORS = [ + ("A", 0, lambda v: OERcodec_INTEGER.enc(v), b"\x01\x00"), + ("A", 128, lambda v: OERcodec_INTEGER.enc(v), b"\x02\x00\x80"), + ("A", 100000, lambda v: OERcodec_INTEGER.enc(v), b"\x03\x01\x86\xa0"), + ("A", -255, lambda v: OERcodec_INTEGER.enc(v), b"\x02\xff\x01"), + ("A", -1234567, lambda v: OERcodec_INTEGER.enc(v), b"\x03\xed)y"), + ("B", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\xfe"), + ("C", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\xff\xfe"), + ("D", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=4), b"\xff\xff\xff\xfe"), + ( + "E", + -2, + lambda v: OERcodec_INTEGER.enc(v, size_len=8), + b"\xff\xff\xff\xff\xff\xff\xff\xfe", + ), + # F to I have a lower bound of zero, so they are unsigned: the width and + # the signedness come from the declared type, not from the value. + ( + "F", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=1, oer_unsigned=True), + b"\x80", + ), + ( + "G", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=2, oer_unsigned=True), + b"\x00\x80", + ), + ( + "G", + 1000, + lambda v: OERcodec_INTEGER.enc(v, size_len=2, oer_unsigned=True), + b"\x03\xe8", + ), + ( + "H", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=4, oer_unsigned=True), + b"\x00\x00\x00\x80", + ), + ( + "I", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=8, oer_unsigned=True), + b"\x00\x00\x00\x00\x00\x00\x00\x80", + ), + ("B", 1, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\x01"), + ("K", 1, lambda v: OER_unsigned_integer_enc(v), b"\x01\x01"), + ("K", 128, lambda v: OER_unsigned_integer_enc(v), b"\x01\x80"), + ("L", -128, lambda v: OER_signed_integer_enc(v), b"\x01\x80"), +] + +BOOLEAN_VECTORS = [ + (True, lambda v: OERcodec_BOOLEAN.enc(1 if v else 0), b"\xff"), + (False, lambda v: OERcodec_BOOLEAN.enc(1 if v else 0), b"\x00"), +] + +ENUMERATED_VECTORS = [ + ("A", "a", 1, b"\x01"), + ("B", "a", 128, b"\x82\x00\x80"), + ("C", "a", 0, b"\x00"), + ("C", "b", 127, b"\x7f"), + ("E", "a", -1, b"\x81\xff"), +] + +OID_VECTORS = [ + ("1.2", lambda v: OERcodec_OID.enc(v), b"\x01*"), + ("1.2.3321", lambda v: OERcodec_OID.enc(v), b"\x03*\x99y"), +] + +OCTET_STRING_VECTORS = [ + (b"\x12\x34", 0, b"\x02\x124"), + (b"\x12\x34\x56", 3, b"\x124V"), +] + +BIT_STRING_VECTORS = [ + ("0100", b"\x02\x04@"), + ("01000001", b"\x02\x00A"), +] + +SCAPY_DECODE_VECTORS = [ + ("A", 42, b"\x01*"), + ("F", 200, b"\xc8"), + ("B", -99, b"\x9d"), +] + +_OER_CODEC_CLASSES = ( + OERcodec_INTEGER, + OERcodec_BOOLEAN, + OERcodec_NULL, + OERcodec_STRING, + OERcodec_OID, + OERcodec_ENUMERATED, + OERcodec_BIT_STRING, +) + +_DECODE_ERRORS = ( + OER_Decoding_Error, + ASN1_Decoding_Error, + ASN1_Error, + ValueError, + IndexError, +) + +class OERFuzzRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +def _fuzz_packets(): + # type: () -> Iterable[Type[ASN1_Packet]] + return (OERFuzzRecord,) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +class _InnerRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("mode", ASN1_INTEGER(0), ["off", "on"]), + ) + +class _EncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_ENCAPS("payload", None, _InnerRecord), + ) + +class _FlagsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("f", "000", ["read", "write", "exec"]), + ) + +class _SetOfRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SET_OF("items", [], ASN1F_INTEGER) + +class _PacketFieldRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_PacketField("data", b""), + ) + +class _ExplicitPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2) + +class _BitEncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord), + ) + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + +import scapy.contrib.uper + +class OEREmptySequenceOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OEREnumField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_ENUMERATED("e", 0, {0: "a", 1: "b"}) + +class OERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_BIT_STRING("b", "0101") + +class OERNullRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_NULL("z", 0), + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ) + +class OEROidField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_OID("oid", "1.2.3") + +class OERInnerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, size_len=1, oer_unsigned=True), + ) + +class OERPacketChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE("c", None, OERInnerSeq, ASN1F_INTEGER) + +class OERAltA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("i", 0), + ) + +class OERAltB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("b", True), + ) + +class OERTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE( + "c", None, + ASN1F_PACKET("a1", None, OERAltA, explicit_tag=0xA0), + ASN1F_PACKET("a2", None, OERAltB, explicit_tag=0xA1), + ), + ) + +class OERUnsignedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, + ) + ++ ASN.1 OER codec += OER length determinant short form +OER_len_enc(3) == b"\x03" += OER length determinant long form +OER_len_enc(200) == b"\x81\xc8" += OER boolean false +OERcodec_BOOLEAN.enc(0) == b"\x00" += OER boolean true +OERcodec_BOOLEAN.enc(1) == b"\xff" += OER null +OERcodec_NULL.enc(None) == b"" += OER unconstrained integer +OERcodec_INTEGER.enc(4) == b"\x01\x04" += OER constrained unsigned integer +OERcodec_INTEGER.enc(4, size_len=1) == b"\x04" += OER constrained signed integer +OERcodec_INTEGER.enc(4, size_len=2) == b"\x00\x04" += OER enumerated short form +OERcodec_ENUMERATED.enc(6) == b"\x06" += OER octet string +OERcodec_STRING.enc(b"ABC") == b"\x03ABC" += OER OID +OERcodec_OID.enc("1.2.3") == b"\x02\x2a\x03" += OER integer roundtrip +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(12345)) +x.val == 12345 and r == b"" += OER boolean roundtrip +x, r = OERcodec_BOOLEAN.do_dec(OERcodec_BOOLEAN.enc(1)) +x.val == 1 and r == b"" += OER ASN1 object encoding +ASN1_INTEGER(42).enc(ASN1_Codecs.OER) == b"\x01*" += OER codec registration +ASN1_Class_UNIVERSAL.INTEGER.get_codec(ASN1_Codecs.OER) is OERcodec_INTEGER + ++ ASN.1 OER codec (extended) += OER length zero +OER_len_enc(0) == b"\x00" += OER length boundary short form +OER_len_enc(127) == b"\x7f" += OER length boundary long form +OER_len_enc(128) == b"\x81\x80" += OER length roundtrip +l, r = OER_len_dec(OER_len_enc(999)) +l == 999 and r == b"" += OER signed integer zero +OER_signed_integer_enc(0) == b"\x01\x00" += OER signed integer negative +OER_signed_integer_enc(-255) == b"\x02\xff\x01" += OER signed integer large +OER_signed_integer_enc(100000) == b"\x03\x01\x86\xa0" += OER signed integer roundtrip +v, r = OER_signed_integer_dec(OER_signed_integer_enc(-1234567)) +v == -1234567 and r == b"" += OER unsigned integer zero +OER_unsigned_integer_enc(0) == b"\x01\x00" += OER unsigned integer roundtrip +v, r = OER_unsigned_integer_dec(OER_unsigned_integer_enc(65535)) +v == 65535 and r == b"" += OER fixed unsigned 1 byte +OERcodec_INTEGER.enc(255, size_len=1, oer_unsigned=True) == b"\xff" += OER fixed signed 2 bytes negative +OERcodec_INTEGER.enc(-2, size_len=2) == b"\xff\xfe" += OER fixed signed 4 bytes +OERcodec_INTEGER.enc(-2, size_len=4) == b"\xff\xff\xff\xfe" += OER enumerated long form +OERcodec_ENUMERATED.enc(128) == b"\x82\x00\x80" += OER enumerated negative +OERcodec_ENUMERATED.enc(-1) == b"\x81\xff" += OER enumerated roundtrip +x, r = OERcodec_ENUMERATED.do_dec(OERcodec_ENUMERATED.enc(128)) +x.val == 128 and r == b"" += OER null roundtrip +x, r = OERcodec_NULL.do_dec(OERcodec_NULL.enc(None)) +x.val is None and r == b"" += OER octet string empty +OERcodec_STRING.enc(b"") == b"\x00" += OER octet string fixed size +OERcodec_STRING.enc(b"\x12\x34\x56", size_len=3) == b"\x12\x34\x56" += OER octet string roundtrip +x, r = OERcodec_STRING.do_dec(OERcodec_STRING.enc(b"\x12\x34")) +x.val == b"\x12\x34" and r == b"" += OER OID 1.2 +OERcodec_OID.enc("1.2") == b"\x01\x2a" += OER OID roundtrip +x, r = OERcodec_OID.do_dec(OERcodec_OID.enc("1.2.3321")) +x.val == "1.2.3321" and r == b"" += OER bit string variable size +OERcodec_BIT_STRING.enc("0100") == b"\x02\x04\x40" += OER bit string roundtrip +x, r = OERcodec_BIT_STRING.do_dec(OERcodec_BIT_STRING.enc("01000001")) +x.val == "01000001" and r == b"" += OER IA5 string +OERcodec_IA5_STRING.enc(b"ABC") == b"\x03ABC" += OER tag short form +OER_tag_enc(1, OER_CLASS_CONTEXT) == b"\x81" += OER tag roundtrip +cls, num, r = OER_tag_dec(OER_tag_enc(1, OER_CLASS_CONTEXT)) +cls == OER_CLASS_CONTEXT and num == 1 and r == b"" += OER sequence concat +OERcodec_SEQUENCE.enc([ASN1_INTEGER(4), ASN1_INTEGER(5)]) == b"\x01\x04\x01\x05" += OER ASN1 boolean object +ASN1_BOOLEAN(1).enc(ASN1_Codecs.OER) == b"\xff" += OER ASN1 null object +ASN1_NULL(None).enc(ASN1_Codecs.OER) == b"" + ++ ASN.1 OER review fixes += OER fixed integer decode roundtrip +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(128, size_len=1, oer_unsigned=True), size_len=1, oer_unsigned=True) +x.val == 128 and r == b"" += OER fixed integer signed decode +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(-2, size_len=2), size_len=2) +x.val == -2 and r == b"" += OER fixed octet string decode +x, r = OERcodec_STRING.do_dec(OERcodec_STRING.enc(b"\x12\x34\x56", size_len=3), size_len=3) +x.val == b"\x12\x34\x56" and r == b"" += OER does not encode the tag of a component +# X.696 encodes none, so OER hooks no tagging and the field is left alone +assert ASN1_Codecs.OER.hook("tagging_enc") is None + +assert ASN1_Codecs.OER.hook("tagging_dec") is None + +fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) + +pkt = OERTaggedInteger() + +assert fld._tagging_enc(pkt, b"\x05", explicit_tag=0xA0) == b"\x05" + +assert fld._tagging_dec(pkt, b"\x05", explicit_tag=0xA0) == (None, b"\x05") + +True += OER tag long form +# X.696 8.7.2.2: a tag number of 63 or more spills into continuation octets +assert OER_tag_enc(100, OER_CLASS_CONTEXT) == b"\xbf\x64" + +assert OER_tag_dec(b"\xbf\x64\x01") == (OER_CLASS_CONTEXT, 100, b"\x01") + +assert OER_tag_dec(OER_tag_enc(16384, OER_CLASS_PRIVATE)) == (OER_CLASS_PRIVATE, 16384, b"") + +_raises(OER_Decoding_Error, lambda: OER_tag_dec(b"\xbf\x81")) + +_raises(OER_Decoding_Error, lambda: OER_tag_dec(b"")) + +True + ++ ASN.1 OER packets, interop and fuzz += oer field explicit tag +pkt = OERTaggedInteger(n=5) + +# X.696 encodes no tag for a component, whatever the tagging environment +assert raw(pkt) == b"\x01\x05" + +decoded = _roundtrip(OERTaggedInteger, pkt) + +assert decoded.n.val == 5 + +True + += oer field fixed size +pkt = OERFixedFields(n=200, s=b"ABC") + +assert raw(pkt) == b"\xc8ABC" + +decoded = _roundtrip(OERFixedFields, pkt) + +assert decoded.n.val == 200 + +assert decoded.s.val == b"ABC" + +True + += oer field optional +present = OEROptionalField(id=1, extra=7) + +# \x80: preamble with the presence bit set for the single OPTIONAL component +assert raw(present) == b"\x80\x01\x01\x01\x07" + +decoded = _roundtrip(OEROptionalField, present) + +assert decoded.id.val == 1 + +assert decoded.extra.val == 7 + +absent = OEROptionalField(id=1, extra=None) + +assert raw(absent) == b"\x00\x01\x01" + +decoded = _roundtrip(OEROptionalField, absent) + +assert decoded.id.val == 1 + +assert decoded.extra is None + +True + += oer field sequence of +pkt = OERSequenceOfIntegers(values=[1, 2, 3]) + +assert raw(pkt) == b"\x01\x03\x01\x01\x01\x02\x01\x03" + +decoded = _roundtrip(OERSequenceOfIntegers, pkt) + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += oer field choice +as_int = OERChoiceField(c=ASN1_INTEGER(99)) + +assert raw(as_int) == b"\x02\x01c" + +decoded = _roundtrip(OERChoiceField, as_int) + +assert decoded.c.val == 99 + +as_str = OERChoiceField(c=ASN1_STRING("x")) + +assert raw(as_str) == b"\x04\x01x" + +decoded = _roundtrip(OERChoiceField, as_str) + +assert decoded.c.val == b"x" + +True + += oer packet record +pkt = OERRecord( + id=42, flag=True, label="hi", extra=7, values=[1, 2, 3], +) + +expected = ( + b"\x80" + b"\x01*\xff\x02hi\x01\x07" + b"\x01\x03\x01\x01\x01\x02\x01\x03" +) + +assert raw(pkt) == expected + +decoded = _roundtrip(OERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +empty = OERRecord(id=1, flag=False, label="", extra=None, values=[]) + +assert raw(empty) == b"\x00\x01\x01\x00\x00\x01\x00" + +decoded = _roundtrip(OERRecord, empty) + +assert decoded.id.val == 1 + +assert decoded.flag.val == 0 + +assert decoded.label.val == b"" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [] + +True + += oer nested sequence +pkt = OERNestedSequence(id=5, x=3, y=True) + +assert raw(pkt) == b"\x01\x05\x01\x03\xff" + +decoded = _roundtrip(OERNestedSequence, pkt) + +assert decoded.id.val == 5 + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +True + += oer nested sequence trailing +pkt = OERNestedSequenceTrailing(x=3, y=True, id=5) + +assert raw(pkt) == b"\x01\x03\xff\x01\x05" + +decoded = _roundtrip(OERNestedSequenceTrailing, pkt) + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +assert decoded.id.val == 5 + +True + += oer sequence of with trailing +pkt = OERSequenceOfWithTrailing(values=[1, 2], id=7) + +assert raw(pkt) == b"\x01\x02\x01\x01\x01\x02\x01\x07" + +decoded = _roundtrip(OERSequenceOfWithTrailing, pkt) + +assert [x.val for x in decoded.values] == [1, 2] + +assert decoded.id.val == 7 + +True + += primitive interop +for type_name, value, enc, expected in INTEGER_VECTORS: + got = enc(value) + assert got == expected, ( + "integer %s=%r: reference=%r scapy=%r" % + (type_name, value, expected, got) + ) + if type_name == "A": + dec, remain = OERcodec_INTEGER.do_dec(got) + assert remain == b"" and dec.val == value + +for value, enc, expected in BOOLEAN_VECTORS: + got = enc(value) + assert got == expected + dec, remain = OERcodec_BOOLEAN.do_dec(got) + assert remain == b"" and dec.val == (1 if value else 0) + +got = OERcodec_NULL.enc(None) + +assert got == b"" + +for type_name, _enum_name, enum_val, expected in ENUMERATED_VECTORS: + got = OERcodec_ENUMERATED.enc(enum_val) + assert got == expected + dec, remain = OERcodec_ENUMERATED.do_dec(got) + assert remain == b"" and dec.val == enum_val + +for oid, enc, expected in OID_VECTORS: + got = enc(oid) + assert got == expected + dec, remain = OERcodec_OID.do_dec(got) + assert remain == b"" and dec.val == oid + +for data, fixed_size, expected in OCTET_STRING_VECTORS: + got = OERcodec_STRING.enc(data, size_len=fixed_size or 0) + assert got == expected + dec, remain = OERcodec_STRING.do_dec(got, size_len=fixed_size or 0) + assert remain == b"" and dec.val == data + +for bitstr, expected in BIT_STRING_VECTORS: + got = OERcodec_BIT_STRING.enc(bitstr) + assert got == expected + dec, remain = OERcodec_BIT_STRING.do_dec(got) + assert remain == b"" and dec.val == bitstr + +True + += scapy encode reference decode +for type_name, value, encoded in SCAPY_DECODE_VECTORS: + if type_name == "A": + dec, remain = OERcodec_INTEGER.do_dec(encoded) + elif type_name == "F": + dec, remain = OERcodec_INTEGER.do_dec( + encoded, size_len=1, oer_unsigned=True, + ) + else: + dec, remain = OERcodec_INTEGER.do_dec(encoded, size_len=1) + assert remain == b"" and dec.val == value + +for val in [0, 1]: + encoded = OERcodec_BOOLEAN.enc(val) + dec, remain = OERcodec_BOOLEAN.do_dec(encoded) + assert remain == b"" and dec.val == val + +True + += oer fuzz encode +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = raw(fuzz(cls())) + assert isinstance(data, bytes) + +True + += oer fuzz roundtrip +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + cls(raw(fuzz(cls()))) + +True + += oer fuzz codec decode +iterations = 100 + +for codec in _OER_CODEC_CLASSES: + for _ in range(iterations): + data = os.urandom(random.randint(0, 64)) + try: + codec.safedec(data) + except _DECODE_ERRORS: + pass + +True + += oer fuzz packet decode +iterations = 100 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass + +True + ++ ASN.1 OER build and dissect += oer record build roundtrip +pkt = OERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(OERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += oer field dissect +tagged = _dissect(OERTaggedInteger, "0105") + +assert tagged.n.val == 5 + +fixed = _dissect(OERFixedFields, "c8414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(OEROptionalField, "8001010107") + +assert present.id.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(OEROptionalField, "000101") + +assert absent.id.val == 1 + +assert absent.extra is None + +seqof = _dissect(OERSequenceOfIntegers, "0103010101020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +as_int = _dissect(OERChoiceField, "020163") + +assert as_int.c.val == 99 + +as_str = _dissect(OERChoiceField, "040178") + +assert as_str.c.val == b"x" + +True + += oer record dissect +decoded = _dissect( + OERRecord, + "80" + "012aff0268690107" + "0103010101020103", +) +_assert_record(decoded) +empty = _dissect(OERRecord, "00010100000100") +_assert_record_empty(empty) + +True + + ++ ASN.1 OER coverage += oer error str +obj = ASN1_INTEGER(1) + +err = OER_Encoding_Error("enc", encoded=obj, remaining=b"z") + +assert "Already encoded" in str(err) + +err2 = OER_Decoding_Error("dec", decoded=obj, remaining=b"w") + +assert "Already decoded" in str(err2) + +True + += oer ipaddress and sequence +encoded = OERcodec_IPADDRESS.enc("127.0.0.1") + +obj, remain = OERcodec_IPADDRESS.do_dec(encoded) + +assert obj.val == "127.0.0.1" + +assert remain == b"" + +fixed = OERcodec_IPADDRESS.enc("127.0.0.1", size_len=4) + +obj2, remain2 = OERcodec_IPADDRESS.do_dec(fixed, size_len=4) + +assert obj2.val == "127.0.0.1" + +assert remain2 == b"" + +_raises(OER_Encoding_Error, lambda: OERcodec_IPADDRESS.enc("bad-ip")) + +_raises(OER_Decoding_Error, lambda: OERcodec_IPADDRESS.do_dec(b"\x01")) + +assert OERcodec_SEQUENCE.enc(b"payload") == b"payload" + +assert OERcodec_SET.enc(b"payload") == b"payload" + +_raises(OER_Decoding_Error, lambda: OERcodec_SEQUENCE.do_dec(b"\x00")) + +empty, remain = OERcodec_BIT_STRING.do_dec(OERcodec_BIT_STRING.enc("")) + +assert empty.val == "" + +assert remain == b"" + +True + + ++ ASN.1 OER field hooks and packet extras += oer field hooks registered +assert ASN1_Codecs.hooks[ASN1_Codecs.OER] + +assert ASN1_Codecs.OER.hook("sequence_m2i") is not None + +assert ASN1_Codecs.OER.hook("choice_i2m") is not None + +assert ASN1_Codecs.OER.hook("no_such_hook") is None + +True + += oer use_object_enc +fld = OERUnsignedField.ASN1_root + +assert fld.codec_opts["oer_unsigned"] is True + +assert fld._use_object_enc(OERUnsignedField(), ASN1_INTEGER(5)) is False + +assert raw(OERUnsignedField(n=5)) == b"\x05" + +assert _roundtrip(OERUnsignedField, OERUnsignedField(n=5)).n.val == 5 + +True + += oer empty sequence of +pkt = OEREmptySequenceOf(values=[]) + +assert raw(pkt) == b"\x01\x00" + +decoded = _roundtrip(OEREmptySequenceOf, pkt) + +assert decoded.values == [] + +True + += oer enumerated field +pkt = OEREnumField(e=1) + +assert raw(pkt) == b"\x01" + +decoded = _roundtrip(OEREnumField, pkt) + +assert decoded.e.val == 1 + +True + += oer bit string field +pkt = OERBitStringField(b="0101") + +assert raw(pkt) == b"\x02\x04\x50" + +decoded = _roundtrip(OERBitStringField, pkt) + +assert decoded.b.val == "0101" + +True + += oer null and oid fields +null_pkt = OERNullRecord(z=0, n=2) + +assert raw(null_pkt) == b"\x02" + +assert _roundtrip(OERNullRecord, null_pkt).n.val == 2 + +oid_pkt = OEROidField(oid="1.2.3") + +assert raw(oid_pkt) == b"\x02\x2a\x03" + +assert _roundtrip(OEROidField, oid_pkt).oid.val == "1.2.3" + +True + += oer choice with packet alternative +pkt = OERPacketChoice(c=OERInnerSeq(x=3)) + +# \x10: universal 16 (SEQUENCE), without the BER constructed bit +assert raw(pkt) == b"\x10\x03" + +decoded = _roundtrip(OERPacketChoice, pkt) + +assert isinstance(decoded.c, OERInnerSeq) + +assert decoded.c.x.val == 3 + +as_int = OERPacketChoice(c=ASN1_INTEGER(9)) + +decoded_int = _roundtrip(OERPacketChoice, as_int) + +assert decoded_int.c.val == 9 + +True + += oer choice with tagged packet alternatives +# Reference (asn1tools) for +# Ch ::= SEQUENCE { c CHOICE { a1 A, a2 B } } +# A ::= SEQUENCE { i INTEGER }, B ::= SEQUENCE { b BOOLEAN } +# in an AUTOMATIC TAGS module: the alternative tag is the only one encoded. +assert raw(OERTaggedChoice(c=OERAltA(i=4))) == b"\x80\x01\x04" + +assert raw(OERTaggedChoice(c=OERAltB(b=False))) == b"\x81\x00" + +decoded = _roundtrip(OERTaggedChoice, OERTaggedChoice(c=OERAltA(i=4))) + +assert isinstance(decoded.c, OERAltA) and decoded.c.i.val == 4 + +decoded = _roundtrip(OERTaggedChoice, OERTaggedChoice(c=OERAltB(b=False))) + +assert isinstance(decoded.c, OERAltB) and decoded.c.b.val == 0 + +True + += oer dec ignores foreign codec kwargs +# Shared field.codec_opts may include UPER keys after contrib.uper is loaded. +x, remain = OERcodec_ENUMERATED.dec( + b"\x01", uper_enum_values=[0, 1], uper_min=0, +) + +assert x.val == 1 + +assert remain == b"" + +True + + ++ ASN.1 OER X.696 conformance + += oer sequence preamble presence bits +# X.696 16.2.2: one presence bit per OPTIONAL/DEFAULT component, zero padded +# to a whole number of octets. Byte vectors checked against asn1tools. +class OERPreambleOne(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + ASN1F_optional(ASN1F_INTEGER("b", 0, size_len=1, oer_unsigned=True)), + ) + +assert raw(OERPreambleOne(a=1, b=2)) == bytes.fromhex("800102") + +assert raw(OERPreambleOne(a=1, b=None)) == bytes.fromhex("0001") + +assert _dissect(OERPreambleOne, "800102").b.val == 2 + +assert _dissect(OERPreambleOne, "0001").b is None + +class OERPreambleTwo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True)), + ASN1F_optional(ASN1F_BOOLEAN("b", False)), + ) + +assert raw(OERPreambleTwo(a=1, b=None)) == bytes.fromhex("8001") + +assert raw(OERPreambleTwo(a=None, b=True)) == bytes.fromhex("40ff") + +# Nine optionals need a two-octet preamble. +class OERPreambleNine(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(*[ + ASN1F_optional(ASN1F_INTEGER(c, 0, size_len=1, oer_unsigned=True)) + for c in "abcdefghi" + ]) + +nine = OERPreambleNine(a=1, b=None, c=None, d=None, e=None, f=None, g=None, + h=None, i=9) + +assert raw(nine) == bytes.fromhex("80800109") + +assert _roundtrip(OERPreambleNine, nine).i.val == 9 + +# A sequence without OPTIONAL/DEFAULT components has no preamble at all. +class OERNoPreamble(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + ) + +assert raw(OERNoPreamble(a=1)) == bytes.fromhex("01") + +# A DEFAULT component takes a presence bit too, and is omitted when it holds +# the default value. +from scapy.asn1fields import ASN1F_DEFAULT + +class OERDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_DEFAULT( + ASN1F_INTEGER("a", 7, size_len=1, oer_unsigned=True), 7, + ), + ) + +assert raw(OERDefault(a=7)) == bytes.fromhex("00") + +assert raw(OERDefault(a=9)) == bytes.fromhex("8009") + +# An absent DEFAULT is restored as the raw default value handed to +# ASN1F_DEFAULT, while a present one is decoded into an ASN1_INTEGER. +assert _dissect(OERDefault, "00").a == 7 + +assert _dissect(OERDefault, "8009").a.val == 9 + +True + += oer extensible sequence +class OERExtSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + oer_extensible=True, + ) + +assert raw(OERExtSeq(a=1)) == bytes.fromhex("0001") + +assert _dissect(OERExtSeq, "0001").a.val == 1 + +class OERExtSeqOpt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True), + ASN1F_optional(ASN1F_BOOLEAN("b", False)), + oer_extensible=True, + ) + +assert raw(OERExtSeqOpt(a=1, b=True)) == bytes.fromhex("4001ff") + +# An encoding that actually carries extension additions is refused rather +# than silently misparsed. +_raises(OER_Decoding_Error, lambda: _dissect(OERExtSeq, "8001")) + +True + += oer fixed size bit string +# X.696 13.3: a fixed size drops both the length determinant and the +# unused-bit count. +class OERBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_BIT_STRING("b", "", size_len=4)) + +assert raw(OERBits(b="1010")) == bytes.fromhex("a0") + +assert _dissect(OERBits, "a0").b.val == "1010" + +for bits, size, expected in [ + ("1010", 4, "a0"), + ("10100101", 8, "a5"), + ("101001010101", 12, "a550"), + ("1010010101011010", 16, "a55a"), +]: + assert OERcodec_BIT_STRING.enc(bits, size_len=size) == bytes.fromhex(expected) + obj, remain = OERcodec_BIT_STRING.do_dec(bytes.fromhex(expected), size_len=size) + assert obj.val == bits + assert remain == b"" + +# Unconstrained bit strings keep the length and unused-bit count. +assert OERcodec_BIT_STRING.enc("101") == bytes.fromhex("0205a0") + +# A value that does not match the declared size is refused. +_raises(OER_Encoding_Error, lambda: OERcodec_BIT_STRING.enc("101", size_len=4)) + +True + += oer fixed size octet string +# X.696 16.1: a fixed size means no length determinant. Sizes 1, 2, 4 and 8 +# used to encode without one but decode expecting one. +for size in (1, 2, 3, 4, 8): + value = b"x" * size + encoded = OERcodec_STRING.enc(value, size_len=size) + assert encoded == value + obj, remain = OERcodec_STRING.do_dec(encoded, size_len=size) + assert obj.val == value + assert remain == b"" + +class OERFixedOctets(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_STRING("s", "", size_len=4)) + +pkt = OERFixedOctets(s="abcd") + +assert raw(pkt) == b"abcd" + +assert _roundtrip(OERFixedOctets, pkt).s.val == b"abcd" + +# Unconstrained octet strings keep their length determinant. +assert OERcodec_STRING.enc(b"abc") == bytes.fromhex("03616263") + +_raises(OER_Encoding_Error, lambda: OERcodec_STRING.enc(b"abc", size_len=4)) + +True + += oer integer signedness follows the declared type +# X.696 10: the encoder must not pick the width or the signedness from the +# value, or the decoder (which only knows the type) reads something else back. +class OERSignedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, size_len=1)) + +class OERUnsignedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, size_len=1, oer_unsigned=True)) + +for cls, value, expected in [ + (OERSignedByte, 127, "7f"), + (OERSignedByte, -128, "80"), + (OERUnsignedByte, 255, "ff"), + (OERUnsignedByte, 0, "00"), +]: + pkt = cls(a=value) + assert raw(pkt) == bytes.fromhex(expected), (value, raw(pkt).hex()) + assert _roundtrip(cls, pkt).a.val == value + +# 200 used to encode as an unsigned 0xc8 and read back as -56. +_raises(OER_Encoding_Error, lambda: raw(OERSignedByte(a=200))) + +_raises(OER_Encoding_Error, lambda: raw(OERUnsignedByte(a=256))) + +_raises(OER_Encoding_Error, lambda: raw(OERUnsignedByte(a=-1))) + +True + += oer unbounded unsigned integer +# X.696 10.2: a lower bound of zero means the value is encoded unsigned, with +# no leading zero octet. Byte vectors checked against asn1tools. +class OERUnboundedUnsigned(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, oer_unsigned=True)) + +for value, expected in [ + (0, "0100"), + (127, "017f"), + (128, "0180"), + (200, "01c8"), + (65535, "02ffff"), + (100000, "030186a0"), +]: + pkt = OERUnboundedUnsigned(a=value) + assert raw(pkt) == bytes.fromhex(expected), (value, raw(pkt).hex()) + assert _roundtrip(OERUnboundedUnsigned, pkt).a.val == value + +True + += oer integer with an empty length determinant +_raises(OER_Decoding_Error, lambda: OER_signed_integer_dec(b"\x00")) + +True + += oer choice rejects an unknown alternative tag +_raises(ASN1_Error, lambda: OERPacketChoice(b"\x40\x00")) + +# An unset CHOICE encodes to nothing, as the field is then absent +assert raw(OERPacketChoice(c=None)) == b"" + +True + += oer untyped codec falls back on string and integer +assert OERcodec_Object.enc(b"hi") == b"\x02hi" + +assert OERcodec_Object.enc(5) == b"\x01\x05" + +_raises(TypeError, lambda: OERcodec_Object.enc(object())) + +# Without a schema there is nothing to tell one type from another +_raises(OER_Decoding_Error, lambda: OERcodec_Object.dec(b"\x01")) + +assert OERcodec_OID.enc(b"") == b"\x00" + +True + += oer sequence dissect of an empty encoding +# Nothing to read leaves every component unset, hence holding its default +decoded = _dissect(OERRecord, "") + +assert decoded.id.val == 0 and decoded.label.val == "" and decoded.values == [] + +True + += oer sequence of a pre-encoded value +# A RAW object is written as-is, without the quantity determinant +raw_items = ASN1_Class_UNIVERSAL.RAW.asn1_object(b"\x01\x01\x07") + +assert raw(OERSequenceOfIntegers(values=raw_items)) == b"\x01\x01\x07" + +True diff --git a/test/contrib/uper.uts b/test/contrib/uper.uts new file mode 100644 index 00000000000..cf4e19e230b --- /dev/null +++ b/test/contrib/uper.uts @@ -0,0 +1,3208 @@ +% Tests for ASN.1 UPER encoding + +# +# Try me with: +# bash test/run_tests -t test/scapy/layers/uper.uts -F + ++ ASN.1 UPER load += prepare helpers and packet classes +import scapy.contrib.uper + +from scapy.contrib.uper import * + +from scapy.packet import raw + +class UPERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class UPERIntegerField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0) + +class UPERBooleanField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BOOLEAN("b", False) + +class UPERStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("s", "") + +class UPERConstrainedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, + ) + +class UPEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class UPERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class UPERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class UPERChoiceStringFirst(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_STRING(b""), ASN1F_STRING, ASN1F_INTEGER, + ) + +class UPERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPEREnumeratedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +class UPERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING( + "bits", "0", uper_min=1, uper_max=20, + ) + +class UPERMessagePrefix(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("msgId", 0), + ASN1F_INTEGER("myflag", 0), + ASN1F_STRING("szDescription", "", size_len=10), + ASN1F_BOOLEAN("isReady", False), + ) + +class UPERSequenceWithChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING), + ) + +class UPERNullPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_NULL("n", None) + +class UPERVariableOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("data", "", uper_min=1, uper_max=20) + +class UPERConstrainedRangeInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=15) + +class UPERSequenceWithEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_ENUMERATED("state", 1, {1: "alpha", 200: "beta"}), + ) + +class UPERSequenceOfStrings(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("items", [], ASN1F_STRING) + +class UPERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERSequenceWithNull(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_NULL("n", None), + ) + +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING("b", "0", uper_min=16, uper_max=16) + +class UPERSequenceOfConstrainedInts(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("item", 0, uper_min=0, uper_max=255), + ) + +class UPERSignedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=-128, uper_max=127) + +class UPERMultiOptional(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("a", 0)), + ASN1F_optional(ASN1F_STRING("b", "")), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +CodecRoundtrip = Tuple[ + Type[Any], + Any, + Dict[str, Any], + Any, +] + +CODEC_ROUNDTRIPS = [ + (UPERcodec_NULL, None, {}, None), + (UPERcodec_BOOLEAN, 1, {}, 1), + (UPERcodec_BOOLEAN, 0, {}, 0), + (UPERcodec_INTEGER, 42, {}, 42), + (UPERcodec_INTEGER, -1, {}, -1), + (UPERcodec_INTEGER, 68719476736, {}, 68719476736), + (UPERcodec_INTEGER, 200, {"uper_min": 0, "uper_max": 255}, 200), + (UPERcodec_INTEGER, -1, {"uper_min": -128, "uper_max": 127}, -1), + (UPERcodec_INTEGER, 127, {"uper_min": -128, "uper_max": 127}, 127), + (UPERcodec_INTEGER, -128, {"uper_min": -128, "uper_max": 127}, -128), + (UPERcodec_STRING, b"AB", {}, b"AB"), + (UPERcodec_STRING, b"\x12\x34\x56", {"size_len": 3}, b"\x12\x34\x56"), + ( + UPERcodec_STRING, + bytes.fromhex("afbc4583"), + {"uper_min": 1, "uper_max": 20}, + bytes.fromhex("afbc4583"), + ), + (UPERcodec_ENUMERATED, 1, {"uper_enum_values": [1, 200]}, 1), + (UPERcodec_ENUMERATED, 200, {"uper_enum_values": [1, 200]}, 200), + ( + UPERcodec_BIT_STRING, + (bytes.fromhex("abcd"), 16), + {"uper_min": 1, "uper_max": 20}, + "1010101111001101", + ), + ( + UPERcodec_BIT_STRING, + (bytes.fromhex("abcd"), 16), + {"uper_min": 16, "uper_max": 16}, + "1010101111001101", + ), + (UPERcodec_ENUMERATED, 1, {"uper_enum_values": [1]}, 1), +] + +DecodeVector = Tuple[ + str, + Any, + Type[Any], + Dict[str, Any], + Any, + bytes, +] + +DECODE_VECTORS = [ + ("A", True, UPERcodec_BOOLEAN, {}, 1, b"\x80"), + ("A", False, UPERcodec_BOOLEAN, {}, 0, b"\x00"), + ("B", 42, UPERcodec_INTEGER, {}, 42, b"\x01*"), + ("B", -1, UPERcodec_INTEGER, {}, -1, b"\x01\xff"), + ( + "C", + 200, + UPERcodec_INTEGER, + {"uper_min": 0, "uper_max": 255}, + 200, + b"\xc8", + ), + ( + "Signed", + -1, + UPERcodec_INTEGER, + {"uper_min": -128, "uper_max": 127}, + -1, + b"\x7f", + ), + ( + "Signed", + 127, + UPERcodec_INTEGER, + {"uper_min": -128, "uper_max": 127}, + 127, + b"\xff", + ), + ("D", b"AB", UPERcodec_STRING, {}, b"AB", b"\x02AB"), + ( + "E", + b"\x12\x34\x56", + UPERcodec_STRING, + {"size_len": 3}, + b"\x12\x34\x56", + b"\x12\x34\x56", + ), + ("G", None, UPERcodec_NULL, {}, None, b""), + ("H", "alpha", UPERcodec_ENUMERATED, {"uper_enum_values": [1, 200]}, 1, b"\x00"), + ("H", "beta", UPERcodec_ENUMERATED, {"uper_enum_values": [1, 200]}, 200, b"\x80"), +] + +OID_ENCODE_VECTORS = [ + ("1.2.3", b"\x02*\x03"), + ("2.999.3", b"\x03\x887\x03"), +] + +def _assert_codec_roundtrip(codec, value, kwargs, expected): + # type: (Type[Any], Any, Dict[str, Any], Any) -> None + data = codec.enc(value, **kwargs) + decoded, _remain = codec.do_dec(data, **kwargs) + assert decoded.val == expected + +PRIMITIVE_VECTORS = [ + ("A", True, lambda v: UPERcodec_BOOLEAN.enc(1 if v else 0), b"\x80"), + ("A", False, lambda v: UPERcodec_BOOLEAN.enc(1 if v else 0), b"\x00"), + ("B", 42, lambda v: UPERcodec_INTEGER.enc(v), b"\x01*"), + ("B", -1, lambda v: UPERcodec_INTEGER.enc(v), b"\x01\xff"), + ( + "C", + 200, + lambda v: UPERcodec_INTEGER.enc(v, uper_min=0, uper_max=255), + b"\xc8", + ), + ("D", b"AB", lambda v: UPERcodec_STRING.enc(v), b"\x02AB"), + ( + "E", + b"\x12\x34\x56", + lambda v: UPERcodec_STRING.enc(v, size_len=3), + b"\x12\x34\x56", + ), + ("G", None, lambda v: UPERcodec_NULL.enc(None), b""), + ( + "H", + "beta", + lambda v: UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), + b"\x80", + ), +] + +COMPOSITE_VECTORS = [ + ("Seq", {"id": 42, "flag": True}, b"\x00\x95@"), + ("Seq", {"id": 42, "flag": True, "extra": 7}, b"\x80\x95@A\xc0"), + ("SeqOf", [1, 2, 3], b"\x03\x01\x01\x01\x02\x01\x03"), + ("SeqOfC", [1, 200, 0], b"\x03\x01\xc8\x00"), + ("Choice", ("a", 99), b"\x00\xb1\x80"), + ("Choice", ("b", b"AB"), b"\x81 \xa1\x00"), + ("ChoiceC", ("a", 10), b"P"), + ("ChoiceC", ("b", b"AB"), b"\x81 \xa1\x00"), +] + +DECODE_PACKET_VECTORS = [ + ( + UPERNestedSequence, + {"id": 5, "x": 3, "y": True}, + bytes.fromhex("0105010380"), + ), + ( + UPERMultiOptional, + {"id": 1, "a": 2, "b": b"hi"}, + bytes.fromhex("c0404040809a1a40"), + ), +] + +PACKET_REFERENCE_VECTORS = [ + ( + UPERNestedSequence, + {"id": 5, "x": 3, "y": True}, + bytes.fromhex("0105010380"), + ), + ( + UPERMultiOptional, + {"id": 1, "a": 2, "b": b"hi"}, + bytes.fromhex("c0404040809a1a40"), + ), +] + +def _encode_composite(typename, value): + # type: (str, Any) -> bytes + enc = UPER_Encoder() + if typename == "Seq": + enc.append_bit(1 if value.get("extra") is not None else 0) + UPERcodec_INTEGER.encode_into(enc, value["id"]) + UPERcodec_BOOLEAN.encode_into(enc, 1 if value["flag"] else 0) + if value.get("extra") is not None: + UPERcodec_INTEGER.encode_into(enc, value["extra"]) + return enc.as_bytes() + if typename == "SeqOf": + enc.append_length_determinant(len(value)) + for item in value: + UPERcodec_INTEGER.encode_into(enc, item) + return enc.as_bytes() + if typename == "SeqOfC": + enc.append_length_determinant(len(value)) + for item in value: + UPERcodec_INTEGER.encode_into( + enc, item, uper_min=0, uper_max=255, + ) + return enc.as_bytes() + if typename == "Choice": + alt, payload = value + index = 0 if alt == "a" else 1 + UPER_choice_index_enc(enc, index, 2) + if alt == "a": + UPERcodec_INTEGER.encode_into(enc, payload) + else: + UPERcodec_STRING.encode_into(enc, payload) + return enc.as_bytes() + if typename == "ChoiceC": + alt, payload = value + index = 0 if alt == "a" else 1 + UPER_choice_index_enc(enc, index, 2) + if alt == "a": + UPERcodec_INTEGER.encode_into( + enc, payload, uper_min=0, uper_max=15, + ) + else: + UPERcodec_STRING.encode_into(enc, payload) + return enc.as_bytes() + raise ValueError("unknown composite type %s" % typename) + +BOOLEAN_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BOOLEAN " + "END" +) + +NULL_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= NULL " + "END" +) + +OCTET_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= OCTET STRING (SIZE(1..20)) " + "END" +) + +CHOICE_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= CHOICE { " + "int1 INTEGER(0..15), " + "int2 INTEGER(0..65535), " + "enm ENUMERATED { one(1), two(2), three(3), four(4), thousand(1000) }, " + "buf OCTET STRING (SIZE(10)), " + "gg SEQUENCE { " + "int1 INTEGER(0..15), " + "int2 INTEGER(0..65535), " + "enm ENUMERATED { pone(1), ptwo(2), pthree(3), pfour(4), pthousand(1000) }, " + "buf [APPLICATION 104] OCTET STRING (SIZE(10)) " + "} " + "} " + "END" +) + +ENUMERATED_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= ENUMERATED { alpha(1), beta(200) } " + "END" +) + +BIT_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BIT STRING (SIZE(1..20)) " + "END" +) + +README_MESSAGE_HEX = ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" +) + +README_MESSAGE_PREFIX_HEX = ( + "0101010248656c6c6f576f726c6480" +) + +ASN1SCC_VECTORS = [ + ( + "05-BOOLEAN/001 pdu1", + True, + lambda _v: UPERcodec_BOOLEAN.enc(1), + b"\x80", + ), + ( + "18-NULL/001 pdu1", + None, + lambda _v: UPERcodec_NULL.enc(None), + b"", + ), + ( + "06-OCTET-STRING/001 pdu1", + bytes.fromhex("afbc4583"), + lambda v: UPERcodec_STRING.enc(v, uper_min=1, uper_max=20), + bytes.fromhex("1d7de22c18"), + ), + ( + "05-BOOLEAN/001 pdu1 false", + False, + lambda _v: UPERcodec_BOOLEAN.enc(0), + b"\x00", + ), + ( + "04-ENUMERATED/001 pdu1 alpha", + "alpha", + lambda _v: UPERcodec_ENUMERATED.enc(1, uper_enum_values=[1, 200]), + b"\x00", + ), + ( + "04-ENUMERATED/001 pdu1 beta", + "beta", + lambda _v: UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), + b"\x80", + ), + ( + "09-CHOICE/001 pdu1 int1:10", + ("int1", 10), + lambda _v: _encode_choice_int1_10(), + b"\x14", + ), + ( + "08-BIT-STRING/001 pdu1 ABCD", + (bytes.fromhex("abcd"), 16), + lambda _v: UPERcodec_BIT_STRING.enc( + (bytes.fromhex("abcd"), 16), uper_min=1, uper_max=20, + ), + bytes.fromhex("7d5e68"), + ), +] + +def _encode_choice_int1_10(): + # type: () -> bytes + enc = UPER_Encoder() + UPER_choice_index_enc(enc, 0, 5) + UPERcodec_INTEGER.encode_into(enc, 10, uper_min=0, uper_max=15) + return enc.as_bytes() + +_UPER_CODEC_CLASSES = ( + UPERcodec_INTEGER, + UPERcodec_BOOLEAN, + UPERcodec_NULL, + UPERcodec_STRING, + UPERcodec_OID, + UPERcodec_ENUMERATED, + UPERcodec_BIT_STRING, +) + +_DECODE_ERRORS = ( + UPER_Decoding_Error, + UPER_Encoding_Error, + ASN1_Decoding_Error, + ASN1_Error, + ValueError, + IndexError, +) + +class UPERFuzzRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPERFuzzNested(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERFuzzEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +def _fuzz_packets(): + # type: () -> Iterable[Type[ASN1_Packet]] + return (UPERFuzzRecord, UPERFuzzNested, UPERFuzzEnumerated) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +from unittest import mock + +from scapy.asn1.ber import BER_Decoding_Error + +from scapy.contrib.oer import OER_Decoding_Error, OER_Encoding_Error + +from scapy.contrib.uper import ( + UPER_Decoding_Error, UPER_Encoding_Error, UPER_Decoder, UPER_Encoder, +) + +from scapy.packet import Raw, raw + +class _InnerRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("mode", ASN1_INTEGER(0), ["off", "on"]), + ) + +class _EncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_ENCAPS("payload", None, _InnerRecord), + ) + +class _FlagsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("f", "000", ["read", "write", "exec"]), + ) + +class _SetOfRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SET_OF("items", [], ASN1F_INTEGER) + +class _PacketFieldRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_PacketField("data", b""), + ) + +class _ExplicitPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2) + +class _BitEncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord), + ) + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + +import scapy.contrib.oer + +from scapy.contrib.oer import * + +from scapy.contrib.uper import ASN1F_DEFAULT + +import scapy.asn1fields as asn1fields + +def _val(x): + # type: (Any) -> Any + return x.val if hasattr(x, "val") else x + +class UPERSmallDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER("n", 5, uper_min=0, uper_max=10), + 5, + ), + ) + +class UPEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, uper_min=0, uper_max=7), + uper_min=0, + uper_max=3, + ) + +class UPERExtSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, uper_min=0, uper_max=7), + uper_min=1, + uper_max=2, + uper_extensible=True, + ) + +class UPERFlagsField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_FLAGS( + "f", "101", ["a", "b", "c"], uper_min=3, uper_max=3, + ) + +class UPERInnerPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, uper_min=0, uper_max=15), + ) + +class UPERWrappedPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_PACKET("inner", None, UPERInnerPacket), + ) + +class UPERConstrainedInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=255) + ++ ASN.1 UPER codec += UPER boolean true +UPERcodec_BOOLEAN.enc(1) == b"\x80" += UPER boolean false +UPERcodec_BOOLEAN.enc(0) == b"\x00" += UPER unconstrained integer +UPERcodec_INTEGER.enc(42) == b"\x01*" += UPER constrained integer +UPERcodec_INTEGER.enc(200, uper_min=0, uper_max=255) == b"\xc8" += UPER signed constrained integer +UPERcodec_INTEGER.enc(-1, uper_min=-128, uper_max=127) == b"\x7f" += UPER octet string +UPERcodec_STRING.enc(b"AB") == b"\x02AB" += UPER fixed octet string +UPERcodec_STRING.enc(b"\x12\x34\x56", size_len=3) == b"\x12\x34\x56" += UPER null +UPERcodec_NULL.enc(None) == b"" += UPER enumerated index +UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]) == b"\x80" += UPER bit string variable size +UPERcodec_BIT_STRING.enc((b"\xab\xcd", 16), uper_min=1, uper_max=20) == bytes.fromhex("7d5e68") += UPER enumerated roundtrip +x, r = UPERcodec_ENUMERATED.do_dec(UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), uper_enum_values=[1, 200]) +x.val == 200 and r == b"" += UPER integer roundtrip +x, r = UPERcodec_INTEGER.do_dec(UPERcodec_INTEGER.enc(-1)) +x.val == -1 and r == b"" += UPER boolean roundtrip +x, r = UPERcodec_BOOLEAN.do_dec(UPERcodec_BOOLEAN.enc(1)) +x.val == 1 and r == b"" += UPER ASN1 object encoding +ASN1_INTEGER(42).enc(ASN1_Codecs.PER) == b"\x01*" += UPER codec registration +ASN1_Class_UNIVERSAL.INTEGER.get_codec(ASN1_Codecs.PER) is UPERcodec_INTEGER + ++ ASN.1 UPER packets, helpers, interop and fuzz += uper field fixed size +pkt = UPERFixedFields(n=200, s=b"ABC") + +assert raw(pkt) == b"\xc8ABC" + +decoded = _roundtrip(UPERFixedFields, pkt) + +assert decoded.n.val == 200 + +assert decoded.s.val == b"ABC" + +True + += uper field integer +pkt = UPERIntegerField(n=12345) + +assert raw(pkt) == bytes.fromhex("023039") + +decoded = _roundtrip(UPERIntegerField, pkt) + +assert decoded.n.val == 12345 + +True + += uper field boolean +true_pkt = UPERBooleanField(b=True) + +assert raw(true_pkt) == b"\x80" + +decoded = _roundtrip(UPERBooleanField, true_pkt) + +assert decoded.b.val == 1 + +false_pkt = UPERBooleanField(b=False) + +assert raw(false_pkt) == b"\x00" + +decoded = _roundtrip(UPERBooleanField, false_pkt) + +assert decoded.b.val == 0 + +True + += uper field string +pkt = UPERStringField(s=b"hi") + +assert raw(pkt) == bytes.fromhex("026869") + +decoded = _roundtrip(UPERStringField, pkt) + +assert decoded.s.val == b"hi" + +True + += uper field constrained integer +pkt = UPERConstrainedInteger(n=200) + +assert raw(pkt) == b"\xc8" + +decoded = _roundtrip(UPERConstrainedInteger, pkt) + +assert decoded.n.val == 200 + +True + += uper field optional +present = UPEROptionalField(id=42, flag=True, extra=7) + +assert raw(present) == bytes.fromhex("80954041c0") + +decoded = _roundtrip(UPEROptionalField, present) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.extra.val == 7 + +absent = UPEROptionalField(id=42, flag=True, extra=None) + +assert raw(absent) == bytes.fromhex("009540") + +decoded = _roundtrip(UPEROptionalField, absent) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.extra is None + +True + += uper field sequence of +pkt = UPERSequenceOfIntegers(values=[1, 2, 3]) + +assert raw(pkt) == bytes.fromhex("03010101020103") + +decoded = _roundtrip(UPERSequenceOfIntegers, pkt) + +assert [x.val for x in decoded.values] == [1, 2, 3] + +empty = UPERSequenceOfIntegers(values=[]) + +assert raw(empty) == b"\x00" + +decoded = _roundtrip(UPERSequenceOfIntegers, empty) + +assert [x.val for x in decoded.values] == [] + +True + += uper field choice +as_int = UPERChoiceField(c=ASN1_INTEGER(99)) + +assert raw(as_int) == bytes.fromhex("00b180") + +decoded = _roundtrip(UPERChoiceField, as_int) + +assert decoded.c.val == 99 + +as_str = UPERChoiceField(c=ASN1_STRING(b"AB")) + +assert raw(as_str) == bytes.fromhex("8120a100") + +decoded = _roundtrip(UPERChoiceField, as_str) + +assert decoded.c.val == b"AB" + +True + += uper field choice definition order +as_str = UPERChoiceStringFirst(c=ASN1_STRING(b"AB")) + +assert raw(as_str) == bytes.fromhex("0120a100") + +decoded = _roundtrip(UPERChoiceStringFirst, as_str) + +assert decoded.c.val == b"AB" + +as_int = UPERChoiceStringFirst(c=ASN1_INTEGER(99)) + +assert raw(as_int) == bytes.fromhex("80b180") + +decoded = _roundtrip(UPERChoiceStringFirst, as_int) + +assert decoded.c.val == 99 + +True + += uper packet record +full = UPERRecord( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], +) + +assert raw(full) == bytes.fromhex("8095409a1a4041c0c04040408040c0") + +decoded = _roundtrip(UPERRecord, full) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +pkt = UPERRecord( + id=42, + flag=True, + label=b"AB", + extra=None, + values=[1, 2], +) + +body = bytes.fromhex("0095409050808040404080") + +assert raw(pkt) == body + +decoded = _roundtrip(UPERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"AB" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [1, 2] + +empty = UPERRecord( + id=1, + flag=False, + label=b"", + extra=None, + values=[], +) + +assert raw(empty) == bytes.fromhex("0080800000") + +decoded = _roundtrip(UPERRecord, empty) + +assert decoded.id.val == 1 + +assert decoded.flag.val == 0 + +assert decoded.label.val == b"" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [] + +True + += uper field enumerated +alpha = UPEREnumeratedField(state=1) + +assert raw(alpha) == b"\x00" + +decoded = _roundtrip(UPEREnumeratedField, alpha) + +assert decoded.state.val == 1 + +beta = UPEREnumeratedField(state=200) + +assert raw(beta) == b"\x80" + +decoded = _roundtrip(UPEREnumeratedField, beta) + +assert decoded.state.val == 200 + +True + += uper field bit string +from scapy.asn1.asn1 import ASN1_BIT_STRING + +pkt = UPERBitStringField(bits=ASN1_BIT_STRING("1010101111001101")) + +assert raw(pkt) == bytes.fromhex("7d5e68") + +decoded = _roundtrip(UPERBitStringField, pkt) + +assert decoded.bits.val == "1010101111001101" + +True + += uper message prefix +pkt = UPERMessagePrefix( + msgId=1, + myflag=2, + szDescription=b"HelloWorld", + isReady=True, +) + +assert raw(pkt) == bytes.fromhex("0101010248656c6c6f576f726c6480") + +decoded = _roundtrip(UPERMessagePrefix, pkt) + +assert decoded.msgId.val == 1 + +assert decoded.myflag.val == 2 + +assert decoded.szDescription.val == b"HelloWorld" + +assert decoded.isReady.val == 1 + +True + += uper sequence with choice +pkt = UPERSequenceWithChoice(id=42, c=ASN1_INTEGER(99)) + +body = raw(pkt) + +decoded = UPERSequenceWithChoice(body) + +assert decoded.id.val == 42 + +assert decoded.c.val == 99 + +as_str = UPERSequenceWithChoice(id=1, c=ASN1_STRING(b"AB")) + +decoded = UPERSequenceWithChoice(raw(as_str)) + +assert decoded.id.val == 1 + +assert decoded.c.val == b"AB" + +True + += uper null packet +pkt = UPERNullPacket() + +assert raw(pkt) == b"" + +decoded = _roundtrip(UPERNullPacket, pkt) + +assert decoded.n is None + +True + += uper variable octet string +pkt = UPERVariableOctetString(data=bytes.fromhex("afbc4583")) + +assert raw(pkt) == bytes.fromhex("1d7de22c18") + +decoded = _roundtrip(UPERVariableOctetString, pkt) + +assert decoded.data.val == bytes.fromhex("afbc4583") + +True + += uper constrained range integer +pkt = UPERConstrainedRangeInt(n=10) + +assert raw(pkt) == b"\xa0" + +decoded = _roundtrip(UPERConstrainedRangeInt, pkt) + +assert decoded.n.val == 10 + +True + += uper sequence with enumerated +pkt = UPERSequenceWithEnumerated(id=1, state=200) + +assert raw(pkt) == bytes.fromhex("010180") + +decoded = _roundtrip(UPERSequenceWithEnumerated, pkt) + +assert decoded.id.val == 1 + +assert decoded.state.val == 200 + +alpha = UPERSequenceWithEnumerated(id=7, state=1) + +assert raw(alpha) == bytes.fromhex("010700") + +decoded = _roundtrip(UPERSequenceWithEnumerated, alpha) + +assert decoded.state.val == 1 + +True + += uper sequence of strings +pkt = UPERSequenceOfStrings(items=[b"A", b"BC"]) + +assert raw(pkt) == bytes.fromhex("020141024243") + +decoded = _roundtrip(UPERSequenceOfStrings, pkt) + +assert [x.val for x in decoded.items] == [b"A", b"BC"] + +empty = UPERSequenceOfStrings(items=[]) + +assert raw(empty) == b"\x00" + +decoded = _roundtrip(UPERSequenceOfStrings, empty) + +assert [x.val for x in decoded.items] == [] + +True + += uper sequence choice hex +pkt = UPERSequenceWithChoice(id=1, c=ASN1_INTEGER(99)) + +assert raw(pkt) == bytes.fromhex("010100b180") + +decoded = UPERSequenceWithChoice(raw(pkt)) + +assert decoded.id.val == 1 + +assert decoded.c.val == 99 + +True + += uper nested sequence +pkt = UPERNestedSequence(id=5, x=3, y=True) + +assert raw(pkt) == bytes.fromhex("0105010380") + +decoded = _roundtrip(UPERNestedSequence, pkt) + +assert decoded.id.val == 5 + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +True + += uper sequence with null +pkt = UPERSequenceWithNull(id=1) + +assert raw(pkt) == bytes.fromhex("0101") + +decoded = _roundtrip(UPERSequenceWithNull, pkt) + +assert decoded.id.val == 1 + +assert getattr(decoded.n, "val", decoded.n) is None + +True + += uper fixed bit string +from scapy.asn1.asn1 import ASN1_BIT_STRING + +pkt = UPERFixedBitString(b=ASN1_BIT_STRING("1010101111001101")) + +assert raw(pkt) == bytes.fromhex("abcd") + +decoded = _roundtrip(UPERFixedBitString, pkt) + +assert decoded.b.val == "1010101111001101" + +True + += uper sequence of constrained ints +pkt = UPERSequenceOfConstrainedInts(values=[1, 200, 0]) + +assert raw(pkt) == bytes.fromhex("0301c800") + +decoded = _roundtrip(UPERSequenceOfConstrainedInts, pkt) + +assert [x.val for x in decoded.values] == [1, 200, 0] + +True + += uper signed integer +for value, expected in [ + (0, b"\x80"), + (-1, b"\x7f"), + (127, b"\xff"), + (-128, b"\x00"), +]: + pkt = UPERSignedInteger(n=value) + assert raw(pkt) == expected + decoded = _roundtrip(UPERSignedInteger, pkt) + assert decoded.n.val == value + +True + += uper multi optional +both = UPERMultiOptional(id=1, a=2, b=b"hi") + +assert raw(both) == bytes.fromhex("c0404040809a1a40") + +decoded = _roundtrip(UPERMultiOptional, both) + +assert decoded.id.val == 1 + +assert decoded.a.val == 2 + +assert decoded.b.val == b"hi" + +none = UPERMultiOptional(id=1, a=None, b=None) + +assert raw(none) == bytes.fromhex("004040") + +decoded = _roundtrip(UPERMultiOptional, none) + +assert decoded.id.val == 1 + +assert decoded.a is None + +assert decoded.b is None + +only_a = UPERMultiOptional(id=3, a=9, b=None) + +assert raw(only_a) == bytes.fromhex("8040c04240") + +decoded = _roundtrip(UPERMultiOptional, only_a) + +assert decoded.id.val == 3 + +assert decoded.a.val == 9 + +assert decoded.b is None + +True + += uper length determinant +for length, expected in [ + (0, b"\x00"), + (1, b"\x01"), + (127, b"\x7f"), + (128, b"\x80\x80"), + (16383, b"\xbf\xff"), +]: + enc = UPER_Encoder() + enc.append_length_determinant(length) + assert enc.as_bytes() == expected + +True + += uper length determinant refuses lengths that need fragmentation +# X.691 11.9.3.8: the caller has to split the content, so a bare determinant +# of 16K or more would not match what follows it. +enc = UPER_Encoder() +try: + enc.append_length_determinant(16384) + assert False +except UPER_Encoding_Error: + pass + +True + += uper fragmented length determinant +# X.691 11.9.3.8: fragments of 16K units, always closed by a determinant +# below 16K. Byte vectors checked against asn1tools. +for count, expected_header in [ + (16384, b"\xc1"), + (32768, b"\xc2"), + (49152, b"\xc3"), + (65536, b"\xc4"), + (81920, b"\xc4"), +]: + enc = UPER_Encoder() + seen = [] + enc.append_fragmented(count, lambda offset, size: seen.append((offset, size))) + got = enc.as_bytes() + assert got.startswith(expected_header), (count, got[:1]) + assert sum(size for _, size in seen) == count, (count, seen) + assert got.endswith(b"\x00"), (count, got[-1:]) + +True + += uper fragmented length determinant roundtrip +for count in [0, 127, 16383, 16384, 40000, 70000]: + enc = UPER_Encoder() + enc.append_fragmented(count, lambda offset, size: None) + dec = UPER_Decoder(enc.as_bytes()) + seen = [] + dec.read_fragmented(lambda size: seen.append(size)) + assert sum(seen) == count, (count, seen) + +True + += uper count roundtrip +for count in [0, 1, 3, 127]: + enc = UPER_Encoder() + enc.append_length_determinant(count) + assert UPER_Decoder(enc.as_bytes()).read_length_determinant() == count + +True + += uper choice index roundtrip +for index, choices in [(0, 2), (1, 5), (3, 5)]: + enc = UPER_Encoder() + UPER_choice_index_enc(enc, index, choices) + got = UPER_choice_index_dec(UPER_Decoder(enc.as_bytes()), choices) + assert got == index + +True + += uper optional presence +enc = UPER_Encoder() + +for bit in [0, 1, 0]: + enc.append_bit(bit) + +assert enc.as_bytes() == b"\x40" + +True + += uper constrained integer +enc = UPER_Encoder() + +UPER_constrained_int_enc(enc, 10, 0, 15) + +assert UPER_constrained_int_dec(UPER_Decoder(enc.as_bytes()), 0, 15) == 10 + +True + += uper constrained signed integer +for value, expected in [(0, b"\x80"), (-1, b"\x7f"), (127, b"\xff"), (-128, b"\x00")]: + enc = UPER_Encoder() + UPER_constrained_int_enc(enc, value, -128, 127) + assert enc.as_bytes() == expected + dec = UPER_Decoder(enc.as_bytes()) + assert UPER_constrained_int_dec(dec, -128, 127) == value + +True + += uper octet string roundtrip +for data, minimum, maximum in [ + (b"AB", None, None), + (b"\x12\x34\x56", 3, 3), + (bytes.fromhex("afbc4583"), 1, 20), +]: + enc = UPER_Encoder() + UPER_octet_string_enc(enc, data, minimum, maximum) + dec = UPER_Decoder(enc.as_bytes()) + assert UPER_octet_string_dec(dec, minimum, maximum) == data + assert not UPER_has_unexpected_remainder(dec) + +True + += uper has unexpected remainder +assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x00")) is False + +assert UPER_has_unexpected_remainder(UPER_Decoder(b"\x80")) is True + +True + += uper chained encode into +enc = UPER_Encoder() + +UPERcodec_INTEGER.encode_into(enc, 42) + +UPERcodec_INTEGER.encode_into(enc, -7) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == 42 + +assert dec.read_unconstrained_whole_number() == -7 + +True + += uper codec roundtrips +for codec, value, kwargs, expected in CODEC_ROUNDTRIPS: + _assert_codec_roundtrip(codec, value, kwargs, expected) + +True + += uper codec oid roundtrip +import scapy.all # noqa: F401 # loads conf.mib for ASN1_OID + +for oid in ("1.2.3", "1.2.840.113549"): + data = UPERcodec_OID.enc(oid) + decoded, remain = UPERcodec_OID.do_dec(data) + assert remain == b"" + assert decoded.val == oid + +True + += uper codec oid encode interop +for oid, expected in OID_ENCODE_VECTORS: + got = UPERcodec_OID.enc(oid) + assert got == expected, ( + "OID %r: expected %s, got %s" % + (oid, expected.hex(), got.hex()) + ) + +True + += uper codec reference decode +for _typename, _value, codec, kwargs, expected, encoded in DECODE_VECTORS: + decoded, _remain = codec.do_dec(encoded, **kwargs) + assert decoded.val == expected, ( + "%s %r: expected %r, got %r" % + (_typename, _value, expected, decoded.val) + ) + +True + += uper codec encode reference +for typename, value, encoder, expected in PRIMITIVE_VECTORS: + encoded = encoder(value) + assert encoded == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), encoded.hex()) + ) + +True + += primitive interop +for typename, value, encoder, expected in PRIMITIVE_VECTORS: + got = encoder(value) + assert got == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), got.hex()) + ) + +True + += composite interop +for typename, value, expected in COMPOSITE_VECTORS: + got = _encode_composite(typename, value) + assert got == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), got.hex()) + ) + +True + += packet reference interop +for cls, pkt_kwargs, expected in PACKET_REFERENCE_VECTORS: + got = raw(cls(**pkt_kwargs)) + assert got == expected, ( + "%s: expected %s, got %s" % + (cls.__name__, expected.hex(), got.hex()) + ) + decoded = cls(got) + for key, value in pkt_kwargs.items(): + field = getattr(decoded, key) + if value is None: + assert field is None + elif isinstance(value, bool): + assert field.val == (1 if value else 0) + else: + assert field.val == value + +True + += packet decode vectors +for cls, pkt_kwargs, data in DECODE_PACKET_VECTORS: + decoded = cls(data) + for key, value in pkt_kwargs.items(): + field = getattr(decoded, key) + if isinstance(value, bool): + assert field.val == (1 if value else 0) + else: + assert field.val == value + +True + += asn1scc vectors +for name, _value, encoder, expected in ASN1SCC_VECTORS: + got = encoder(_value) + assert got == expected, ( + "%s: expected %s, got %s" % + (name, expected.hex(), got.hex()) + ) + +True + += asn1scc readme message prefix +from scapy.packet import raw + +expected = bytes.fromhex(README_MESSAGE_PREFIX_HEX) + +pkt = UPERMessagePrefix( + msgId=1, + myflag=2, + szDescription=b"HelloWorld", + isReady=True, +) + +got = raw(pkt) + +assert got == expected + +decoded = UPERMessagePrefix(got) + +assert decoded.msgId.val == 1 + +assert decoded.myflag.val == 2 + +assert decoded.szDescription.val == b"HelloWorld" + +assert decoded.isReady.val == 1 + +True + += asn1scc readme message reference +assert README_MESSAGE_HEX == ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" +) + +True + += uper fuzz encode +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + try: + data = raw(fuzz(cls())) + except _DECODE_ERRORS: + continue + assert isinstance(data, bytes) + +True + += uper fuzz roundtrip +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + try: + cls(raw(fuzz(cls()))) + except _DECODE_ERRORS: + pass + +True + += uper fuzz codec decode +iterations = 100 + +for codec in _UPER_CODEC_CLASSES: + for _ in range(iterations): + data = os.urandom(random.randint(0, 64)) + try: + codec.safedec(data) + except _DECODE_ERRORS: + pass + +True + += uper fuzz packet decode +iterations = 100 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass + +True + ++ ASN.1 UPER build and dissect += per record build roundtrip +pkt = UPERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(UPERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += per default field build +class UPERDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + uper_min=0, uper_max=86401, oer_unsigned=True, + ), + 600, + ), + ) + +absent = UPERDefaultRecord(id=1) + +assert raw(absent) == b"\x00\x80" + +decoded = _roundtrip(UPERDefaultRecord, absent) + +assert decoded.id.val == 1 + +assert _asn1_int(decoded.count) == 600 + +present = UPERDefaultRecord(id=1, count=86400) + +assert raw(present) == bytes.fromhex("80d46000") + +decoded = _roundtrip(UPERDefaultRecord, present) + +assert decoded.id.val == 1 + +assert _asn1_int(decoded.count) == 86400 + +True + += per extensible integer build +class UPERExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER( + "n", 0, + uper_min=1, uper_max=65535, + uper_extensible=True, oer_unsigned=True, + ), + ) + +in_range = UPERExtInt(n=42) + +assert raw(in_range) == bytes.fromhex("001480") + +decoded = _roundtrip(UPERExtInt, in_range) + +assert decoded.n.val == 42 + +out_of_range = UPERExtInt(n=1706733817) + +assert raw(out_of_range) == bytes.fromhex("8232dd587c80") + +decoded = _roundtrip(UPERExtInt, out_of_range) + +assert decoded.n.val == 1706733817 + +True + += per extensible integer as a bare root +~ per +class UPERBareExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, uper_min=0, uper_max=15, uper_extensible=True, + ) + +class UPERWrappedExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=15, uper_extensible=True), + ) + +# A bare root must encode the extension bit just like the nested field does. +assert raw(UPERBareExtInt(n=5)) == bytes.fromhex("28") + +assert raw(UPERBareExtInt(n=5)) == raw(UPERWrappedExtInt(n=5)) + +assert _dissect(UPERBareExtInt, "28").n.val == 5 + +assert _roundtrip(UPERBareExtInt, UPERBareExtInt(n=99)).n.val == 99 + +True + += per constrained sequence of build +class UPERConstrainedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7), + uper_min=1, uper_max=3, + ) + +pkt = UPERConstrainedSeqOf(items=[1, 2]) + +assert raw(pkt) == bytes.fromhex("4a") + +decoded = _roundtrip(UPERConstrainedSeqOf, pkt) + +assert [x.val for x in decoded.items] == [1, 2] + +True + += per field dissect +fixed = _dissect(UPERFixedFields, "c8414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(UPEROptionalField, "80954041c0") + +assert present.id.val == 42 + +assert present.flag.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(UPEROptionalField, "009540") + +assert absent.id.val == 42 + +assert absent.flag.val == 1 + +assert absent.extra is None + +seqof = _dissect(UPERSequenceOfIntegers, "03010101020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +empty_seqof = _dissect(UPERSequenceOfIntegers, "00") + +assert [x.val for x in empty_seqof.values] == [] + +as_int = _dissect(UPERChoiceField, "00b180") + +assert as_int.c.val == 99 + +as_str = _dissect(UPERChoiceField, "8120a100") + +assert as_str.c.val == b"AB" + +True + += per record dissect +decoded = _dissect( + UPERRecord, + "8095409a1a4041c0c04040408040c0", +) + +_assert_record(decoded) + +partial = _dissect(UPERRecord, "0095409050808040404080") + +assert partial.id.val == 42 + +assert partial.flag.val == 1 + +assert partial.label.val == b"AB" + +assert partial.extra is None + +assert [x.val for x in partial.values] == [1, 2] + +empty = _dissect(UPERRecord, "0080800000") + +_assert_record_empty(empty) + +True + += per default field dissect +class UPERDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + uper_min=0, uper_max=86401, oer_unsigned=True, + ), + 600, + ), + ) + +absent = _dissect(UPERDefaultRecord, "0080") + +assert absent.id.val == 1 + +assert _asn1_int(absent.count) == 600 + +present = _dissect(UPERDefaultRecord, "80d46000") + +assert present.id.val == 1 + +assert _asn1_int(present.count) == 86400 + +True + += per extensible integer dissect +class UPERExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER( + "n", 0, + uper_min=1, uper_max=65535, + uper_extensible=True, oer_unsigned=True, + ), + ) + +in_range = _dissect(UPERExtInt, "001480") + +assert in_range.n.val == 42 + +out_of_range = _dissect(UPERExtInt, "8232dd587c80") + +assert out_of_range.n.val == 1706733817 + +True + += per constrained sequence of dissect +class UPERConstrainedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7), + uper_min=1, uper_max=3, + ) + +decoded = _dissect(UPERConstrainedSeqOf, "4a") + +assert [x.val for x in decoded.items] == [1, 2] + +True + ++ ASN.1 UPER coverage += uper error str +obj = ASN1_INTEGER(2) + +err = UPER_Encoding_Error("enc", encoded=obj, remaining=b"x") + +assert "Already encoded" in str(err) + +err2 = UPER_Decoding_Error("dec", decoded=obj, remaining=b"y") + +assert "Already decoded" in str(err2) + +True + += uper length determinant extended +# X.691 11.9.3.8: multiples of 16K units are emitted as 0xc1..0xc4 fragments +# and the sequence is closed by a determinant below 16K. +def _fragment_headers(count): + enc = UPER_Encoder() + sizes = [] + enc.append_fragmented(count, lambda offset, size: sizes.append(size)) + return enc.as_bytes(), sizes + +assert _fragment_headers(32768) == (b"\xc2\x00", [32768, 0]) + +assert _fragment_headers(49152) == (b"\xc3\x00", [49152, 0]) + +assert _fragment_headers(65535) == (b"\xc3\xbf\xff", [49152, 16383]) + +assert _fragment_headers(65536) == (b"\xc4\x00", [65536, 0]) + +assert _fragment_headers(81920) == (b"\xc4\xc1\x00", [65536, 16384, 0]) + +True + += uper unconstrained whole number +enc = UPER_Encoder() + +enc.append_unconstrained_whole_number(-256) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == -256 + +enc = UPER_Encoder() + +enc.append_unconstrained_whole_number(0) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == 0 + +True + += uper bit string paths +encoded = UPERcodec_BIT_STRING.enc("1010", uper_min=1, uper_max=20) + +obj, remain = UPERcodec_BIT_STRING.do_dec( + encoded, uper_min=1, uper_max=20, +) + +assert obj.val == "1010" + +encoded2 = UPERcodec_BIT_STRING.enc(b"\xab", uper_min=4, uper_max=8) + +obj2, _ = UPERcodec_BIT_STRING.do_dec(encoded2, uper_min=4, uper_max=8) + +assert len(obj2.val) == 8 + +fixed = UPERcodec_BIT_STRING.enc("1010101111001101", uper_min=16, uper_max=16) + +obj3, _ = UPERcodec_BIT_STRING.do_dec(fixed, uper_min=16, uper_max=16) + +assert obj3.val == "1010101111001101" + +True + += uper enumerated range +encoded = UPERcodec_ENUMERATED.enc(3, uper_min=0, uper_max=7) + +obj, remain = UPERcodec_ENUMERATED.do_dec(encoded, uper_min=0, uper_max=7) + +assert obj.val == 3 + +assert remain == b"" + +enc = UPER_Encoder() + +UPERcodec_ENUMERATED.encode_into(enc, 2, uper_min=0, uper_max=3) + +obj2 = UPERcodec_ENUMERATED.dec_from_decoder( + UPER_Decoder(enc.as_bytes()), + uper_min=0, + uper_max=3, +) + +assert obj2.val == 2 + +True + += uper enumerated without a range +# The width would otherwise follow the value, which the decoder cannot redo +_raises(UPER_Encoding_Error, lambda: UPERcodec_ENUMERATED.enc(3)) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_ENUMERATED.do_dec(b"\x60")) + +True + += uper enumerated index follows the value order +# X.691 14.1: sort the enumeration by value, whatever order it was declared in +class UPERUnsortedEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("e", 0, {2: "c", 0: "a", 1: "b"}), + ) + +# byte vectors from asn1tools for ENUMERATED { c(2), a(0), b(1) } +for value, expected in [(0, "00"), (1, "40"), (2, "80")]: + assert raw(UPERUnsortedEnum(e=value)) == bytes.fromhex(expected), value + assert _dissect(UPERUnsortedEnum, expected).e.val == value + +True + += uper extensible enumerated +# X.691 14.3: a one bit prefix, zero for a value of the extension root +class UPERExtEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("e", 0, {0: "a", 1: "b", 2: "c"}, + uper_extensible=True), + ) + +# byte vectors from asn1tools for ENUMERATED { a(0), b(1), c(2), ... } +for value, expected in [(0, "00"), (1, "20"), (2, "40")]: + assert raw(UPERExtEnum(e=value)) == bytes.fromhex(expected), value + assert _dissect(UPERExtEnum, expected).e.val == value + +enc = UPER_Encoder() + +_raises(UPER_Encoding_Error, lambda: UPERcodec_ENUMERATED.encode_into( + enc, 7, uper_enum_values=[0, 1, 2], uper_extensible=True)) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_ENUMERATED.dec_from_decoder( + UPER_Decoder(b"\x80"), uper_enum_values=[0, 1, 2], uper_extensible=True)) + +True + += uper octet string honours its size constraint +# A determinant sized after the constraint cannot express a violating length +_raises(UPER_Encoding_Error, lambda: UPERcodec_STRING.enc(b"AB", size_len=4)) + +_raises(UPER_Encoding_Error, lambda: UPERcodec_STRING.enc(b"ABCDEF", size_len=4)) + +assert UPERcodec_STRING.enc(b"ABCD", size_len=4) == b"ABCD" + +_raises(UPER_Encoding_Error, + lambda: UPERcodec_STRING.enc(b"A", uper_min=2, uper_max=4)) + +_raises(UPER_Encoding_Error, + lambda: UPERcodec_STRING.enc(b"ABCDEFGH", uper_min=2, uper_max=4)) + +True + += uper sequence errors +_raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.enc([ASN1_INTEGER(1)])) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_SEQUENCE.do_dec(b"\x00")) + +# A finished encoding is octet padded, so it cannot be spliced into a bitstream +_raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.encode_into(UPER_Encoder(), b"raw")) + +assert UPERcodec_SET.enc(b"raw") == b"raw" + +True + += uper ipaddress +encoded = UPERcodec_IPADDRESS.enc("10.0.0.1") + +obj, remain = UPERcodec_IPADDRESS.do_dec(encoded) + +assert obj.val == "10.0.0.1" + +assert remain == b"" + +_raises(UPER_Encoding_Error, lambda: UPERcodec_IPADDRESS.enc("bad-ip")) + +True + ++ ASN.1 fields coverage += asn1fields enum and flags +pkt = _InnerRecord(mode="on") + +built = raw(pkt) + +decoded = _InnerRecord(built) + +assert decoded.mode.val == 1 + +flags = _FlagsRecord(f="read+exec") + +assert flags.f.val == "101" + +assert "read, exec" in _FlagsRecord.ASN1_root.seq[0].i2repr(flags, flags.f) + +set_pkt = _SetOfRecord(items=[ASN1_INTEGER(0), ASN1_INTEGER(1)]) + +set_raw = raw(set_pkt) + +set_dec = _SetOfRecord(set_raw) + +assert [x.val for x in set_dec.items] == [0, 1] + +True + += asn1fields encaps and packet +inner = _InnerRecord(mode=1) + +enc = _EncapsRecord() + +enc.payload = inner + +enc_raw = raw(enc) + +enc_dec = _EncapsRecord(enc_raw) + +assert enc_dec.payload.mode.val == 1 + +pkt_field = _PacketFieldRecord() + +pkt_field.data = _InnerRecord(mode=0) + +pf_raw = raw(pkt_field) + +pf_dec = _PacketFieldRecord(pf_raw) + +assert isinstance(pf_dec.data.val, bytes) + +explicit = _ExplicitPacket() + +explicit.inner = _InnerRecord(mode=1) + +ex_raw = raw(explicit) + +ex_dec = _ExplicitPacket(ex_raw) + +assert ex_dec.inner.mode.val == 1 + +True + += asn1fields choice and special +class _OerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class _BerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +oer = _OerChoiceRecord(c=ASN1_INTEGER(1)) + +oer_dec = _OerChoiceRecord(raw(oer)) + +assert oer_dec.c.val == 1 + +ber = _BerChoiceRecord(c=ASN1_INTEGER(0)) + +ber_dec = _BerChoiceRecord(raw(ber)) + +assert ber_dec.c.val == 0 + +inner_bytes = raw(_InnerRecord(mode=0)) + +bit_payload = ASN1_BIT_STRING( + inner_bytes, + readable=True, +) + +bit_pkt = _BitEncapsRecord(b=bit_payload) + +bit_dec = _BitEncapsRecord(raw(bit_pkt)) + +assert bit_dec.b.mode.val == 0 + +class _TicksRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_TIME_TICKS("t", ASN1_TIME_TICKS(0)) + +class _IpRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_IPADDRESS("addr", ASN1_STRING(b"")) + +ticks = _TicksRecord(t=ASN1_TIME_TICKS(1234)) + +assert raw(ticks).endswith(b"\x04\xd2") + +ip = _IpRecord() + +ip.addr = "192.168.1.1" + +assert raw(ip) == b"\x40\x04\xc0\xa8\x01\x01" + +True + += asn1fields optional dissect +class _OptRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class _BerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +pkt = _OptRecord(id=0, extra=None) + +assert raw(pkt) + +decoded = _OptRecord(raw(pkt)) + +assert decoded.extra is None + +choice_rand = _BerChoiceRecord.ASN1_root.randval() + +assert choice_rand is not None + +True + += asn1fields default and omit +class _DefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + uper_min=0, uper_max=86401, oer_unsigned=True, + ), + 600, + ), + ) + +absent = _DefaultRecord(id=1) + +assert raw(absent) == b"\x00\x80" + +decoded = _DefaultRecord(raw(absent)) + +assert decoded.id.val == 1 + +assert decoded.count == 600 or decoded.count.val == 600 + +present = _DefaultRecord(id=1, count=86400) + +decoded = _DefaultRecord(raw(present)) + +assert decoded.count.val == 86400 + +class _OmitRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_omit("ignored", None), + ) + +omit_pkt = _OmitRecord(id=7) + +assert raw(omit_pkt) == bytes.fromhex("3003020107") + +True + += asn1fields extensible per +class _ExtSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_optional(ASN1F_INTEGER("extra", 0, uper_min=0, uper_max=7)), + uper_extensible=True, + ) + +pkt = _ExtSeq(id=2, extra=3) + +data = raw(pkt) + +decoded = _ExtSeq(data) + +assert decoded.id.val == 2 + +assert decoded.extra.val == 3 + +dec = UPER_Decoder(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtSeq.ASN1_root.dissect_from_decoder(_ExtSeq(), dec), +) + +class _ExtChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + uper_extensible=True, + ) + +choice = _ExtChoice(c=ASN1_INTEGER(4)) + +assert raw(choice) + +dec = UPER_Decoder(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtChoice.ASN1_root.m2i_from_decoder(_ExtChoice(), dec), +) + +class _InnerItem(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7) + +class _ExtSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], _InnerItem, + uper_min=1, uper_max=2, uper_extensible=True, + ) + +in_range = _ExtSeqOf(items=[_InnerItem(n=1)]) + +assert raw(in_range) + +decoded = _ExtSeqOf(raw(in_range)) + +assert decoded.items[0].n.val == 1 + +out_of_range = _ExtSeqOf( + items=[_InnerItem(n=i) for i in range(4)], +) + +assert raw(out_of_range) + +decoded = _ExtSeqOf(raw(out_of_range)) + +assert len(decoded.items) == 4 + +True + += asn1fields sequence of advanced +class _Inner(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7) + +class _SeqOfPackets(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], _Inner, uper_min=1, uper_max=3, + ) + +pkt = _SeqOfPackets(items=[_Inner(n=1), _Inner(n=2)]) + +decoded = _SeqOfPackets(raw(pkt)) + +assert [x.n.val for x in decoded.items] == [1, 2] + +class _OerSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +oer_pkt = _OerSeqOf(values=[1, 2]) + +oer_dec = _OerSeqOf(raw(oer_pkt)) + +assert [x.val for x in oer_dec.values] == [1, 2] + +class _EmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +empty = _EmptySeqOf(values=None) + +assert raw(empty) == b"\x00" + +assert _EmptySeqOf.ASN1_root.i2repr(empty, None) == "[]" + +assert _EmptySeqOf.ASN1_root.i2repr( + _EmptySeqOf(values=[ASN1_INTEGER(1)]), + [ASN1_INTEGER(1)], +).startswith("[") + +_raises(ValueError, lambda: ASN1F_SEQUENCE_OF("bad", [], object())) + +True + += asn1fields choice advanced +class _InnerChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class _NestedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), _InnerChoice, ASN1F_INTEGER, + ) + +nested = _NestedChoice(c=_InnerChoice(c=ASN1_STRING(b"xy"))) + +assert len(raw(nested)) > 0 + +nested_dec = _NestedChoice(raw(nested)) + +assert isinstance(nested_dec.c, (_InnerChoice, ASN1_STRING)) + +class _OerTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + explicit_tag=0xA1, + ) + +oer_choice = _OerTaggedChoice(c=ASN1_INTEGER(9)) + +assert raw(oer_choice) + +class _PacketChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", + ASN1_INTEGER(0), + ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2), + ASN1F_INTEGER, + ) + +packet_choice = _PacketChoice( + c=_InnerRecord(mode=ASN1_INTEGER(1)), +) + +packet_dec = _PacketChoice(raw(packet_choice)) + +assert packet_dec.c.mode.val == 1 + +class _PerChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +_raises( + ASN1_Error, + lambda: ASN1F_CHOICE( + "c", 0, ASN1F_INTEGER, implicit_tag=0xA0, + ), +) + +_raises( + ASN1_Error, + lambda: _PerChoice.ASN1_root.m2i(_PerChoice(), b""), +) + +_raises( + ASN1_Error, + lambda: _PerChoice.ASN1_root.encode_into( + UPER_Encoder(), _PerChoice(), 42, + ), +) + +True + += asn1fields enum bitstring and flags +class _NamedEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_enum_INTEGER( + "state", 0, ["off", "on", "auto"], + ) + +named = _NamedEnum(state="on") + +built = raw(named) + +decoded = _NamedEnum(built) + +assert decoded.state.val == 1 + +assert "'on'" in _NamedEnum.ASN1_root.i2repr(decoded, decoded.state) + +class _BitRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING("bits", b"\xaa") + +assert raw(_BitRecord()) + +flags = _FlagsRecord() + +flags.f = ASN1_BIT_STRING("101") + +assert "read, exec" in _FlagsRecord.ASN1_root.seq[0].i2repr(flags, flags.f) + +class _BadBitEncaps(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord) + +_raises( + BER_Decoding_Error, + lambda: _BadBitEncaps.ASN1_root.m2i( + _BadBitEncaps(), + b"\x03\x02\x01\x00", + ), +) + +True + += asn1fields packet and sequence errors +class _PerInner(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("mode", 0, uper_min=0, uper_max=1) + +class _PacketWrap(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_PACKET("inner", None, _PerInner) + +inner = _PerInner(mode=1) + +wrap = _PacketWrap(inner=inner) + +decoded = _PacketWrap(raw(wrap)) + +assert decoded.inner.mode.val == 1 + +class _DynamicPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_PACKET( + "inner", None, _PerInner, + next_cls_cb=lambda pkt: _PerInner, + ) + +dyn = _DynamicPacket(inner=_PerInner(mode=0)) + +assert _DynamicPacket.ASN1_root._resolve_cls(dyn) is _PerInner + +empty_packet = _PacketWrap(inner=None) + +assert raw(empty_packet) == b"" + +class _BerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_INTEGER("extra", 0), + ) + +_raises( + BER_Decoding_Error, + lambda: _BerSeq.ASN1_root.m2i( + _BerSeq(), + bytes.fromhex("300702010102010200ff"), + ), +) + +class _OerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, size_len=1, oer_unsigned=True), + ) + +_, remain = _OerSeq.ASN1_root.m2i(_OerSeq(), b"\x01\xff") + +assert remain == b"\xff" + +class _PerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ) + +_raises( + UPER_Decoding_Error, + lambda: _PerSeq.ASN1_root.m2i(_PerSeq(), b"\x80\xff"), +) + +empty_seq = _BerSeq() + +_BerSeq.ASN1_root._dissect_sequence_children(empty_seq, b"") + +assert empty_seq.id is None + +assert empty_seq.extra is None + +class _OptListRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, uper_min=0, uper_max=255), + ASN1F_optional( + ASN1F_SEQUENCE_OF("items", [], ASN1F_INTEGER), + ), + ) + +opt_list = _OptListRecord(id=1, items=None) + +assert raw(opt_list) + +field = ASN1F_INTEGER("n", 0) + +with mock.patch.object( + _InnerRecord, "__init__", side_effect=ASN1F_badsequence, +): + pkt_obj, remain = field.extract_packet( + _InnerRecord, b"\xab\xcd", _underlayer=None, + ) + +assert isinstance(pkt_obj, Raw) + +assert pkt_obj.load == b"\xab\xcd" + +assert remain == b"\xab\xcd" + +True + += asn1fields more coverage +_raises( + ASN1_Error, + lambda: ASN1F_INTEGER("x", 0, implicit_tag=1, explicit_tag=2), +) + +class _IntRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0) + +field = _IntRecord.ASN1_root + +_raises( + ASN1_Error, + lambda: field.i2m(_IntRecord(), ASN1_STRING(b"bad")), +) + +flex_field = ASN1F_INTEGER("n", 0, flexible_tag=True, explicit_tag=0xA0) + +obj, remain = flex_field.m2i(_IntRecord(), bytes.fromhex("a1020101")) + +assert obj.tag != ASN1_Class_UNIVERSAL.INTEGER or remain == b"" + +class _FlexSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + explicit_tag=0xA1, + flexible_tag=True, + ) + +flex_seq = _FlexSeq(id=1) + +assert raw(flex_seq) + +decoded = _FlexSeq(raw(flex_seq)) + +assert decoded.id.val == 1 + +assert ASN1F_BOOLEAN("b", False).randval() is not None + +assert ASN1F_BIT_STRING("b", b"").randval() is not None + +assert ASN1F_OID("o", None).randval() is not None + +assert ASN1F_UTC_TIME("t", "").randval() is not None + +assert " 0 + +empty_inner, remain = packet_field.m2i(_FlexPacket(), b"") + +assert empty_inner is None and remain == b"" + +obj_val = packet_field.i2m(_FlexPacket(), _InnerRecord(mode=0)) + +assert len(obj_val) > 0 + +flags_field = _FlagsRecord.ASN1_root.seq[0] + +assert flags_field.i2repr(_FlagsRecord(), None) == "None" + +class _OerFlexSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER, + explicit_tag=0xA1, + ) + +_OerFlexSeqOf.ASN1_root.flexible_tag = True + +oer_seq = _OerFlexSeqOf(values=[1]) + +data = raw(oer_seq) + +decoded = _OerFlexSeqOf(data) + +assert decoded.values[0].val == 1 + +class _BerFlexSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER, + explicit_tag=0xA1, + ) + +_BerFlexSeqOf.ASN1_root.flexible_tag = True + +ber_seq = _BerFlexSeqOf(values=[2]) + +assert raw(ber_seq) + +class _ExtChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + uper_extensible=True, + ) + +dec = UPER_Decoder(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtChoice.ASN1_root.m2i_from_decoder(_ExtChoice(), dec), +) + +class _SingleChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER) + +single = _SingleChoice(c=ASN1_INTEGER(3)) + +assert raw(single) + +class _FlexChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + flexible_tag=True, + ) + +flex_choice = _FlexChoice(c=ASN1_INTEGER(4)) + +assert raw(flex_choice) + +class _OerPktChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +oer_pkt_choice = _OerPktChoice(c=ASN1_STRING(b"hi")) + +assert raw(oer_pkt_choice) + +True + + ++ ASN.1 UPER field hooks and packet extras += uper field hooks registered +assert ASN1_Codecs.hooks[ASN1_Codecs.PER] + +assert ASN1_Codecs.PER.hook("sequence_m2i") is not None + +use_object_enc = ASN1_Codecs.PER.hook("use_object_enc") + +assert use_object_enc( + UPERConstrainedInt.ASN1_root, UPERConstrainedInt(), ASN1_INTEGER(1), +) is False + +True + += uper DEFAULT published on asn1fields +assert ASN1F_DEFAULT is asn1fields.ASN1F_DEFAULT + +assert issubclass(ASN1F_DEFAULT, ASN1F_optional) + +enum_fld = ASN1F_ENUMERATED("e", 0, {1: "a", 2: "b"}) + +# ENUMERATED values are added per-codec, so BER packets keep an empty +# codec_opts while PER packets get the permitted values. +assert enum_fld.codec_opts == {} + +assert "uper_enum_values" not in enum_fld._codec_kwargs( + type("P", (), {"ASN1_codec": ASN1_Codecs.BER})() +) + +assert enum_fld._codec_kwargs( + type("P", (), {"ASN1_codec": ASN1_Codecs.PER})() +)["uper_enum_values"] == [1, 2] + +assert hasattr(ASN1F_field, "encode_into") + +assert hasattr(ASN1F_SEQUENCE, "dissect_from_decoder") + +True + += uper use_object_enc and codec_opts +fld = UPERConstrainedInt.ASN1_root + +assert fld.codec_opts == {"uper_min": 0, "uper_max": 255} + +assert fld._use_object_enc(UPERConstrainedInt(), ASN1_INTEGER(5)) is False + +assert raw(UPERConstrainedInt(n=5)) == b"\x05" + +assert _val(_roundtrip(UPERConstrainedInt, UPERConstrainedInt(n=5)).n) == 5 + +True + += uper DEFAULT presence bit +absent = UPERSmallDefaultRecord(id=1, n=5) + +present = UPERSmallDefaultRecord(id=1, n=7) + +assert raw(absent) == bytes.fromhex("0080") + +assert raw(present) == bytes.fromhex("80b8") + +assert raw(absent) != raw(present) + +decoded_absent = _roundtrip(UPERSmallDefaultRecord, absent) + +decoded_present = _roundtrip(UPERSmallDefaultRecord, present) + +assert _val(decoded_absent.n) == 5 + +assert _val(decoded_present.n) == 7 + +True + += uper empty constrained sequence of +pkt = UPEREmptySeqOf(values=[]) + +assert raw(pkt) == b"\x00" + +decoded = _roundtrip(UPEREmptySeqOf, pkt) + +assert decoded.values == [] + +True + += uper extensible sequence of outside range +pkt = UPERExtSeqOf(values=[1, 2, 3]) + +assert raw(pkt) == bytes.fromhex("8194c0") + +decoded = _roundtrip(UPERExtSeqOf, pkt) + +assert [_val(x) for x in decoded.values] == [1, 2, 3] + +True + += uper FLAGS field +pkt = UPERFlagsField(f="101") + +assert raw(pkt) == bytes.fromhex("a0") + +decoded = _roundtrip(UPERFlagsField, pkt) + +assert decoded.f.val == "101" + +assert UPERFlagsField.ASN1_root.get_flags(decoded) == ["a", "c"] + +True + += uper nested ASN1F_PACKET +pkt = UPERWrappedPacket(id=1, inner=UPERInnerPacket(x=7)) + +assert raw(pkt) == bytes.fromhex("0170") + +decoded = _roundtrip(UPERWrappedPacket, pkt) + +assert _val(decoded.id) == 1 + +assert _val(decoded.inner.x) == 7 + +True + += uper field encode_into nesting +built = UPERWrappedPacket(id=1, inner=UPERInnerPacket(x=7)) + +enc = UPER_Encoder() + +UPERWrappedPacket.ASN1_root.encode_into(enc, built) + +assert enc.as_bytes() == raw(built) + +empty = UPERWrappedPacket() + +UPERWrappedPacket.ASN1_root.dissect_from_decoder( + empty, UPER_Decoder(raw(built)), +) + +assert _val(empty.id) == 1 + +assert _val(empty.inner.x) == 7 + +True + += uper unconstrained bit string counts bits +# X.691 16.11: the length determinant of an unconstrained BIT STRING counts +# bits, not octets, and nothing is padded before the next field. Byte vectors +# checked against asn1tools. +class UPERFreeBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING("b", ""), + ASN1F_INTEGER("tail", 0, uper_min=0, uper_max=255), + ) + +for bits, expected in [ + ("", "00a5"), + ("1", "01d280"), + ("10110", "05b528"), + ("10110011", "08b3a5"), + ("1" * 20, "14fffffa50"), +]: + pkt = UPERFreeBitString(b=bits, tail=0xa5) + assert raw(pkt) == bytes.fromhex(expected), (bits, raw(pkt).hex()) + decoded = _roundtrip(UPERFreeBitString, pkt) + assert decoded.b.val == bits, (bits, decoded.b.val) + assert _val(decoded.tail) == 0xa5 + +True + += uper fixed size bit string refuses a mismatched length +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_BIT_STRING("b", "0" * 8, size_len=8)) + +assert raw(UPERFixedBitString(b="10110011")) == bytes.fromhex("b3") + +_raises(UPER_Encoding_Error, lambda: raw(UPERFixedBitString(b="101"))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERFixedBitString(b="1011001100"))) + +True + += uper octet string fragmentation +# X.691 11.9.3.8. Byte vectors checked against asn1tools: a fragment header, +# 16K octets, then the terminating determinant. +class UPERFreeOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_STRING("s", "")) + +data = bytes(i % 256 for i in range(16384)) + +built = raw(UPERFreeOctetString(s=data)) + +assert built == b"\xc1" + data + b"\x00" + +assert _roundtrip(UPERFreeOctetString, UPERFreeOctetString(s=data)).s.val == data + +data = bytes(i % 256 for i in range(40000)) + +built = raw(UPERFreeOctetString(s=data)) + +assert built == b"\xc2" + data[:32768] + b"\x9c\x40" + data[32768:] + +assert _roundtrip(UPERFreeOctetString, UPERFreeOctetString(s=data)).s.val == data + +True + += uper sequence of fragmentation +class UPERFreeSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER("x", 0, uper_min=0, uper_max=255)), + ) + +items = [i % 256 for i in range(16385)] + +built = raw(UPERFreeSeqOf(values=items)) + +assert built == b"\xc1" + bytes(items[:16384]) + b"\x01" + bytes(items[16384:]) + +decoded = _roundtrip(UPERFreeSeqOf, UPERFreeSeqOf(values=items)) + +assert [_val(x) for x in decoded.values] == items + +True + += uper integer with an empty length determinant +_raises( + UPER_Decoding_Error, + lambda: UPER_Decoder(b"\x00").read_unconstrained_whole_number(), +) + +True + += uper choice with tagged packet alternatives +class UPERAltA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("i", 0), + ) + +class UPERAltB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("b", True), + ) + +class UPERTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE( + "c", None, + ASN1F_PACKET("a1", None, UPERAltA, explicit_tag=0xA0), + ASN1F_PACKET("a2", None, UPERAltB, explicit_tag=0xA1), + ), + ) + +# Reference (asn1tools) for +# Ch ::= SEQUENCE { c CHOICE { a1 A, a2 B } } +# A ::= SEQUENCE { i INTEGER }, B ::= SEQUENCE { b BOOLEAN } +# The alternative is picked by the type of the value, tags are not encoded. +assert raw(UPERTaggedChoice(c=UPERAltA(i=4))) == b"\x00\x82\x00" + +assert raw(UPERTaggedChoice(c=UPERAltB(b=False))) == b"\x80" + +decoded = _roundtrip(UPERTaggedChoice, UPERTaggedChoice(c=UPERAltA(i=4))) + +assert isinstance(decoded.c, UPERAltA) and decoded.c.i.val == 4 + +decoded = _roundtrip(UPERTaggedChoice, UPERTaggedChoice(c=UPERAltB(b=False))) + +assert isinstance(decoded.c, UPERAltB) and decoded.c.b.val == 0 + +True + += uper choice with packet class alternatives +class UPERClassChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, UPERAltA, ASN1F_INTEGER) + +pkt = UPERClassChoice(c=UPERAltA(i=5)) + +# index bit 0, then the sequence: an unconstrained integer of one octet +assert raw(pkt) == b"\x00\x82\x80" + +decoded = _roundtrip(UPERClassChoice, pkt) + +assert isinstance(decoded.c, UPERAltA) and decoded.c.i.val == 5 + +decoded = _roundtrip(UPERClassChoice, UPERClassChoice(c=ASN1_INTEGER(3))) + +assert decoded.c.val == 3 + +# An unset CHOICE encodes to nothing, as the field is then absent +assert raw(UPERClassChoice(c=None)) == b"" + +True + += uper enumerated bounds +_raises(UPER_Encoding_Error, lambda: UPER_enumerated_enc(UPER_Encoder(), 0, [])) + +_raises(UPER_Decoding_Error, lambda: UPER_enumerated_dec(UPER_Decoder(b"\x00"), [])) + +# Three values are indexed on two bits, which can carry an index they do not +# define +_raises(UPER_Decoding_Error, lambda: UPER_enumerated_dec(UPER_Decoder(b"\xc0"), [0, 1, 2])) + +assert UPER_enumerated_dec(UPER_Decoder(b"\x40"), [0, 1, 2]) == 1 + +True + += uper untyped codec falls back on string and integer +enc = UPER_Encoder() + +UPERcodec_Object.encode_into(enc, b"hi") + +assert enc.as_bytes() == b"\x02hi" + +enc = UPER_Encoder() + +UPERcodec_Object.encode_into(enc, 5) + +assert enc.as_bytes() == b"\x01\x05" + +_raises(UPER_Encoding_Error, lambda: UPERcodec_Object.encode_into(UPER_Encoder(), object())) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_Object.dec_from_decoder(UPER_Decoder(b"\x01"))) + +True + += uper choice with a single alternative +class UPEROneAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, ASN1F_INTEGER) + +# X.691 23.5: one alternative leaves nothing to choose, so no index is encoded +assert raw(UPEROneAlt(c=ASN1_INTEGER(4))) == b"\x01\x04" + +assert _roundtrip(UPEROneAlt, UPEROneAlt(c=ASN1_INTEGER(4))).c.val == 4 + +class UPERThreeAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, ASN1F_INTEGER, ASN1F_STRING, ASN1F_BOOLEAN) + +# Three alternatives are indexed on two bits, which can carry a fourth index +_raises(ASN1_Error, lambda: UPERThreeAlt(b"\xc0")) + +True + += uper sequence of an unset field +class UPERUnsetSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER("x", 0, uper_min=0, uper_max=255)), + ) + +assert raw(UPERUnsetSeqOf(values=None)) == b"\x00" + +assert raw(UPERUnsetSeqOf(values=[])) == b"\x00" + +True + += uper field rejects a value of another type +class UPERIntOnly(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0), + ) + +_raises(ASN1_Error, lambda: raw(UPERIntOnly(n=ASN1_STRING(b"x")))) + +enc = UPER_Encoder() + +UPERcodec_OID.encode_into(enc, b"") + +assert enc.as_bytes() == b"\x00" + +True + += uper constrained values honour their range +class UPERSmallInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7), + ) + +assert raw(UPERSmallInt(n=5)) == b"\xa0" + +# The value is written on the width of the range, so one outside it would be +# read back as another value +_raises(UPER_Encoding_Error, lambda: raw(UPERSmallInt(n=100))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERSmallInt(n=-3))) + +class UPERSmallExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, uper_min=0, uper_max=7, uper_extensible=True), + ) + +# An extensible range does accept it, as an extension addition +assert _roundtrip(UPERSmallExtInt, UPERSmallExtInt(n=100)).n.val == 100 + +True + += uper sequence of honours its size constraint +class UPERSizedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("x", 0, uper_min=0, uper_max=255), + uper_min=1, uper_max=3, + ), + ) + +assert raw(UPERSizedSeqOf(values=[ASN1_INTEGER(7)])) == b"\x01\xc0" + +# An unset field is an empty one, which the constraint rules out here +_raises(UPER_Encoding_Error, lambda: raw(UPERSizedSeqOf(values=[]))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERSizedSeqOf(values=None))) + +True diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index 9fa0bad0f44..a50e146b3b4 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -23,9 +23,12 @@ repr(ASN1_GENERALIZED_TIME("19991231235959")).startswith("1999-12-31 23:59:59 <" repr(ASN1_GENERALIZED_TIME("19991231235959.999")).startswith("1999-12-31 23:59:59.999 <") = with microseconds (invalid) assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99x")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.9999")) +True + ASN.1 Generalized Time (Zulu) = Z short HH @@ -52,8 +55,10 @@ repr(ASN1_GENERALIZED_TIME("19991231235959.999+0100")).startswith("1999-12-31 23 repr(ASN1_GENERALIZED_TIME("19991231235959-2359")).startswith("1999-12-31 23:59:59 -2359 <") = offset invalid (offset >= 24h) assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959-2400")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959+2400")) +True + ASN.1 UTC Time = UTC short HHMM @@ -83,10 +88,17 @@ ASN1_GENERALIZED_TIME("199912312359").datetime == datetime(1999, 12, 31, 23, 59) ASN1_GENERALIZED_TIME("19991231235959").datetime == datetime(1999, 12, 31, 23, 59, 59) = datetime assignment x = ASN1_GENERALIZED_TIME("19991231235959.999") + x.datetime = datetime(2020, 12, 31) + assert x.val == "20201231000000" + x.datetime = x.datetime.replace(tzinfo=timezone.utc) + x.val == "20201231000000Z" + +True + = datetime construction ASN1_GENERALIZED_TIME(datetime(2020, 12, 31)).val == "20201231000000" = datetime construction (UTC) @@ -101,3 +113,496 @@ ASN1_UTC_TIME(datetime(2020, 12, 31)).val == "201231000000" ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone.utc)).val == "201231000000Z" = UTC datetime construction (offset) ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=-23, minutes=-59)))).val == "201231000000-2359" + ++ ASN.1 cross-codec build and dissect += import contrib codecs +import scapy.contrib.oer +import scapy.contrib.uper +from scapy.contrib.oer import * +from scapy.contrib.uper import * +from scapy.packet import raw += prepare helpers and packet classes +class BERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class BERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1), + ASN1F_STRING("s", "", size_len=3), + ) + +class BEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class BERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class BERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class BERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class BEROptionalSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("hdr", 0), + ASN1F_optional(ASN1F_SEQUENCE( + ASN1F_INTEGER("id", None), + ASN1F_STRING("label", None), + explicit_tag=0xA0, + )), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +class OERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class OERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class OEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class OERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class OERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class OERNestedSequenceTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ASN1F_INTEGER("id", 0), + ) + +class OERSequenceOfWithTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ASN1F_INTEGER("id", 0), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +class UPERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, oer_unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class UPERIntegerField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0) + +class UPERBooleanField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BOOLEAN("b", False) + +class UPERStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("s", "") + +class UPERConstrainedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, + ) + +class UPEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class UPERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class UPERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class UPERChoiceStringFirst(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_STRING(b""), ASN1F_STRING, ASN1F_INTEGER, + ) + +class UPERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPEREnumeratedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +class UPERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING( + "bits", "0", uper_min=1, uper_max=20, + ) + +class UPERMessagePrefix(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("msgId", 0), + ASN1F_INTEGER("myflag", 0), + ASN1F_STRING("szDescription", "", size_len=10), + ASN1F_BOOLEAN("isReady", False), + ) + +class UPERSequenceWithChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING), + ) + +class UPERNullPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_NULL("n", None) + +class UPERVariableOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("data", "", uper_min=1, uper_max=20) + +class UPERConstrainedRangeInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=0, uper_max=15) + +class UPERSequenceWithEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_ENUMERATED("state", 1, {1: "alpha", 200: "beta"}), + ) + +class UPERSequenceOfStrings(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("items", [], ASN1F_STRING) + +class UPERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERSequenceWithNull(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_NULL("n", None), + ) + +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING("b", "0", uper_min=16, uper_max=16) + +class UPERSequenceOfConstrainedInts(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("item", 0, uper_min=0, uper_max=255), + ) + +class UPERSignedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, uper_min=-128, uper_max=127) + +class UPERMultiOptional(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("a", 0)), + ASN1F_optional(ASN1F_STRING("b", "")), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) += ber oer per choice build +class BERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class PERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +for cls in (BERChoice, OERChoice, PERChoice): + as_int = cls(c=ASN1_INTEGER(99)) + assert len(raw(as_int)) > 0 + decoded = _roundtrip(cls, as_int) + assert decoded.c.val == 99 + as_str = cls(c=ASN1_STRING(b"AB")) + assert len(raw(as_str)) > 0 + decoded = _roundtrip(cls, as_str) + assert decoded.c.val == b"AB" + +True + += ber oer per record dissect +for cls, data_hex in [ + ( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + ), + ( + OERRecord, + "80" + "012aff0268690107" + "0103010101020103", + ), + ( + UPERRecord, + "8095409a1a4041c0c04040408040c0", + ), +]: + _assert_record(_dissect(cls, data_hex)) + +True + += ber oer per constrained integer codec_opts +class BERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +class OERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +class PERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +for cls, expected in ( + (BERConstrained, b"\x02\x81\x02\x00\xc8"), + (OERConstrained, b"\xc8"), + (PERConstrained, b"\xc8"), +): + pkt = cls(n=200) + assert raw(pkt) == expected + assert _roundtrip(cls, pkt).n.val == 200 + assert cls.ASN1_root.codec_opts["oer_unsigned"] is True + assert cls.ASN1_root.codec_opts["uper_min"] == 0 + assert cls.ASN1_root.codec_opts["uper_max"] == 255 + +True + += ber oer per empty sequence of +class BEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class PEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, uper_min=0, uper_max=7), + uper_min=0, + uper_max=3, + ) + +for cls in (BEREmptySeqOf, OEREmptySeqOf, PEREmptySeqOf): + pkt = cls(values=[]) + decoded = _roundtrip(cls, pkt) + assert decoded.values == [] + assert len(raw(pkt)) > 0 + +True + += field hooks present after contrib load +assert ASN1_Codecs.hooks[ASN1_Codecs.OER] + +assert ASN1_Codecs.hooks[ASN1_Codecs.PER] + +assert ASN1_Codecs.OER.hook("sequence_m2i") is not None + +assert ASN1_Codecs.PER.hook("sequence_m2i") is not None + +True + += ber oer per default component +class _BerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +class _OerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +class _PerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +# A component holding its default value is not encoded, and comes back as the +# default when the encoding does not carry it. An unset component counts as +# holding it, and the default may be given as an ASN.1 object. +for cls in (_BerDefault, _OerDefault, _PerDefault): + assert len(raw(cls(a=1, b=7))) < len(raw(cls(a=1, b=9))) + absent = _roundtrip(cls, cls(a=1, b=7)).b + assert getattr(absent, "val", absent) == 7 + assert _roundtrip(cls, cls(a=1, b=9)).b.val == 9 + assert raw(cls(a=1, b=None)) == raw(cls(a=1, b=7)) + +class _AsnObjectDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_DEFAULT(ASN1F_INTEGER("a", 7), ASN1_INTEGER(7)), + ) + +assert raw(_AsnObjectDefault(a=ASN1_INTEGER(7))) == raw(_AsnObjectDefault(a=7)) + +assert raw(_AsnObjectDefault(a=7)) == b"\x00" + +True diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index 896f2ec746a..2e4704b9353 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -457,30 +457,30 @@ assert BERcodec_STRING.enc(b"x", uper_max=10) == BERcodec_STRING.enc(b"x") BERcodec_SEQUENCE.enc(BERcodec_INTEGER.enc(1), uper_min=0) == BERcodec_SEQUENCE.enc(BERcodec_INTEGER.enc(1)) + ASN.1 codec tagging contract -= BER tagging is exposed on the codec -assert ASN1_Codecs.BER.tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" -diff, payload = ASN1_Codecs.BER.tagging_dec( - b"\xa0\x01\x05", hidden_tag=2, implicit_tag=0xA0 -) += BER hooks the tagging of a field +tagging_enc = ASN1_Codecs.BER.hook("tagging_enc") + +assert tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" + +assert ASN1_Codecs.hooks[ASN1_Codecs.BER]["tagging_enc"] is tagging_enc + +tagging_dec = ASN1_Codecs.BER.hook("tagging_dec") + +diff, payload = tagging_dec(b"\xa0\x01\x05", hidden_tag=2, implicit_tag=0xA0) + diff is None and payload == b"\x02\x01\x05" -= identity tagging for PER-style codecs -def _id_tagging_enc(s, **kwargs): - return s += a codec that hooks no tagging leaves the encoding alone +class _NoHooks: + ASN1_codec = ASN1_Codecs.CER -def _id_tagging_dec(s, **kwargs): - return None, s +assert ASN1_Codecs.CER.hook("tagging_enc") is None -ASN1_Codecs.PER.register_tagging(_id_tagging_enc, _id_tagging_dec) -try: - assert ASN1_Codecs.PER.tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\x02\x01\x05" - diff, payload = ASN1_Codecs.PER.tagging_dec( - b"\x02\x01\x05", hidden_tag=2, explicit_tag=0xA1 - ) - assert diff is None and payload == b"\x02\x01\x05" -finally: - del ASN1_Codecs.PER._tagging_enc - del ASN1_Codecs.PER._tagging_dec +fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) + +assert fld._tagging_enc(_NoHooks(), b"\x02\x01\x05", explicit_tag=0xA0) == b"\x02\x01\x05" + +fld._tagging_dec(_NoHooks(), b"\x02\x01\x05", explicit_tag=0xA0) == (None, b"\x02\x01\x05") = field _codec_kwargs and object-enc hooks class P(ASN1_Packet): @@ -513,3 +513,191 @@ class ExtraPkt(ASN1_Packet): # BER enc swallows unknown kwargs; round-trip still works. assert raw(ExtraPkt(n=7)) == b"\x02\x01\x07" ExtraPkt(raw(ExtraPkt(n=7))).n.val == 7 + += field codec_opts storage +plain = ASN1F_INTEGER("n", 0) + +assert plain.codec_opts == {} + +assert plain._codec_kwargs(type("P", (), {"ASN1_codec": ASN1_Codecs.BER})()) == { + "size_len": None, +} + +constrained = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, +) + +assert constrained.codec_opts == { + "oer_unsigned": True, + "uper_min": 0, + "uper_max": 255, +} + +# Constraints live in codec_opts only: they must not become field attributes. +assert not hasattr(constrained, "oer_unsigned") + +assert not hasattr(constrained, "uper_min") + +kwargs = constrained._codec_kwargs( + type("P", (), {"ASN1_codec": ASN1_Codecs.BER})() +) + +assert kwargs["size_len"] == 1 + +assert kwargs["oer_unsigned"] is True + +assert kwargs["uper_min"] == 0 + +assert kwargs["uper_max"] == 255 + +# BER still encodes with constraints present in kwargs. +class ConstrainedBer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, oer_unsigned=True, uper_min=0, uper_max=255, + ) + +assert raw(ConstrainedBer(n=5)) == b"\x02\x81\x01\x05" + +assert ConstrainedBer(raw(ConstrainedBer(n=5))).n.val == 5 + +True + += CHOICE order properties +choice = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, +) + +assert choice.choice_order == [2, 4] + +assert choice.choice_list[0] is ASN1F_INTEGER + +assert choice.choice_list[1] is ASN1F_STRING + +True + ++ ASN.1 BER build and dissect extras + += import helpers +from scapy.packet import raw + += prepare helpers +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + += ber record build roundtrip +pkt = BERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(BERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += ber field dissect +tagged = _dissect(BERTaggedInteger, "a103020105") + +assert tagged.n.val == 5 + +fixed = _dissect(BERFixedFields, "300d02810200c80483000003414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(BEROptionalField, "3008020101a003020107") + +assert present.id.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(BEROptionalField, "3003020101") + +assert absent.id.val == 1 + +assert absent.extra is None + +seqof = _dissect(BERSequenceOfIntegers, "3009020101020102020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +as_int = _dissect(BERChoiceField, "020163") + +assert as_int.c.val == 99 + +as_str = _dissect(BERChoiceField, "040178") + +assert as_str.c.val == b"x" + +True + += ber record dissect +decoded = _dissect( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + +) + +_assert_record(decoded) + +empty = _dissect(BERRecord, "300a02010101010004003000") + +_assert_record_empty(empty) + +True +