diff --git a/scapy/cbor/__init__.py b/scapy/cbor/__init__.py index de462e74528..dcec5d8ed5d 100644 --- a/scapy/cbor/__init__.py +++ b/scapy/cbor/__init__.py @@ -44,6 +44,26 @@ CBORcodec_SIMPLE_AND_FLOAT, ) +from scapy.cbor.cborfields import ( + CBORF_element, + CBORF_field, + CBORF_UNSIGNED_INTEGER, + CBORF_NEGATIVE_INTEGER, + CBORF_INTEGER, + CBORF_BYTE_STRING, + CBORF_TEXT_STRING, + CBORF_BOOLEAN, + CBORF_NULL, + CBORF_UNDEFINED, + CBORF_FLOAT, + CBORF_ARRAY, + CBORF_ARRAY_OF, + CBORF_MAP, + CBORF_SEMANTIC_TAG, + CBORF_optional, + CBORF_PACKET, +) + __all__ = [ # Exceptions "CBOR_Error", @@ -81,4 +101,25 @@ "CBORcodec_MAP", "CBORcodec_SEMANTIC_TAG", "CBORcodec_SIMPLE_AND_FLOAT", + # Field base classes + "CBORF_element", + "CBORF_field", + # Scalar fields + "CBORF_UNSIGNED_INTEGER", + "CBORF_NEGATIVE_INTEGER", + "CBORF_INTEGER", + "CBORF_BYTE_STRING", + "CBORF_TEXT_STRING", + "CBORF_BOOLEAN", + "CBORF_NULL", + "CBORF_UNDEFINED", + "CBORF_FLOAT", + # Structured fields + "CBORF_ARRAY", + "CBORF_ARRAY_OF", + "CBORF_MAP", + "CBORF_SEMANTIC_TAG", + # Complex fields + "CBORF_optional", + "CBORF_PACKET", ] diff --git a/scapy/cbor/cborfields.py b/scapy/cbor/cborfields.py new file mode 100644 index 00000000000..536424728ec --- /dev/null +++ b/scapy/cbor/cborfields.py @@ -0,0 +1,904 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Classes that implement CBOR (Concise Binary Object Representation) data +structures as packet fields. Modelled after scapy/asn1fields.py. +""" + +import copy + +from functools import reduce + +from scapy.cbor.cbor import ( + CBOR_Decoding_Error, + CBOR_Error, + CBOR_MajorTypes, + CBOR_Object, + CBOR_UNSIGNED_INTEGER, + CBOR_NEGATIVE_INTEGER, + CBOR_BYTE_STRING, + CBOR_TEXT_STRING, + CBOR_SEMANTIC_TAG, + CBOR_FALSE, + CBOR_TRUE, + CBOR_NULL, + CBOR_UNDEFINED, + CBOR_FLOAT, +) +from scapy.cbor.cborcodec import ( + CBOR_Codec_Decoding_Error, + CBOR_decode_head, + CBOR_encode_head, + CBORcodec_Object, + CBORcodec_UNSIGNED_INTEGER, + CBORcodec_NEGATIVE_INTEGER, + CBORcodec_BYTE_STRING, + CBORcodec_TEXT_STRING, + CBORcodec_SIMPLE_AND_FLOAT, +) +from scapy.base_classes import BasePacket +from scapy.volatile import ( + RandChoice, + RandFloat, + RandNum, + RandString, + RandField, +) + +from scapy import packet + +from typing import ( + Any, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, + TYPE_CHECKING, +) + +if TYPE_CHECKING: + from scapy.cborpacket import CBOR_Packet # noqa: F401 + + +class CBORF_badsequence(Exception): + pass + + +class CBORF_element(object): + pass + + +########################## +# Basic CBOR Field # +########################## + +_I = TypeVar('_I') # Internal storage +_A = TypeVar('_A') # CBOR object + + +class CBORF_field(CBORF_element, Generic[_I, _A]): + holds_packets = 0 + islist = 0 + CBOR_tag = None # type: Optional[Any] + + def __init__(self, + name, # type: str + default, # type: Optional[_A] + ): + # type: (...) -> None + self.name = name + if default is None: + self.default = default # type: Optional[_A] + else: + self.default = self._wrap(default) + self.owners = [] # type: List[Type[CBOR_Packet]] + + def _wrap(self, val): + # type: (Any) -> _A + """Return a CBOR object wrapping *val*. + + The base implementation is a pass-through cast; subclasses override + this to convert a raw Python value to the appropriate CBOR object + type (e.g. :class:`~scapy.cbor.cbor.CBOR_UNSIGNED_INTEGER`). + """ + return cast(_A, val) + + def register_owner(self, cls): + # type: (Type[CBOR_Packet]) -> None + self.owners.append(cls) + + def i2repr(self, pkt, x): + # type: (CBOR_Packet, _I) -> str + return repr(x) + + def i2h(self, pkt, x): + # type: (CBOR_Packet, _I) -> Any + return x + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[_A, bytes] + raise NotImplementedError("Subclasses must implement m2i") + + def i2m(self, pkt, x): + # type: (CBOR_Packet, Union[bytes, _I, _A]) -> bytes + if x is None: + return b"" + if isinstance(x, CBOR_Object): + return x.enc() + return self._encode(x) + + def _encode(self, x): + # type: (Any) -> bytes + """Encode a raw Python value to CBOR bytes.""" + raise NotImplementedError("Subclasses must implement _encode") + + def any2i(self, pkt, x): + # type: (CBOR_Packet, Any) -> _I + return cast(_I, x) + + def extract_packet(self, + cls, # type: Type[CBOR_Packet] + s, # type: bytes + _underlayer=None, # type: Optional[CBOR_Packet] + ): + # type: (...) -> Tuple[CBOR_Packet, bytes] + try: + c = cls(s, _underlayer=_underlayer) + except CBORF_badsequence: + c = packet.Raw(s, _underlayer=_underlayer) # type: ignore + cpad = c.getlayer(packet.Raw) + s = b"" + if cpad is not None: + s = cpad.load + if cpad.underlayer: + del cpad.underlayer.payload + return c, s + + def build(self, pkt): + # type: (CBOR_Packet) -> bytes + return self.i2m(pkt, getattr(pkt, self.name)) + + def dissect(self, pkt, s): + # type: (CBOR_Packet, bytes) -> bytes + v, s = self.m2i(pkt, s) + self.set_val(pkt, v) + return s + + def do_copy(self, x): + # type: (Any) -> Any + if isinstance(x, list): + x = x[:] + for i in range(len(x)): + if isinstance(x[i], BasePacket): + x[i] = x[i].copy() + return x + if hasattr(x, "copy"): + return x.copy() + return x + + def set_val(self, pkt, val): + # type: (CBOR_Packet, Any) -> None + setattr(pkt, self.name, val) + + def is_empty(self, pkt): + # type: (CBOR_Packet) -> bool + return getattr(pkt, self.name) is None + + def get_fields_list(self): + # type: () -> List[CBORF_field[Any, Any]] + return [self] + + def __str__(self): + # type: () -> str + return repr(self) + + def randval(self): + # type: () -> RandField[_I] + return cast(RandField[_I], RandNum(0, 2 ** 32)) + + def copy(self): + # type: () -> CBORF_field[_I, _A] + return copy.copy(self) + + +############################# +# Simple CBOR Fields # +############################# + +class CBORF_UNSIGNED_INTEGER(CBORF_field[int, CBOR_UNSIGNED_INTEGER]): + """CBOR unsigned integer field (major type 0).""" + CBOR_tag = CBOR_MajorTypes.UNSIGNED_INTEGER + + def _wrap(self, val): + # type: (Any) -> CBOR_UNSIGNED_INTEGER + if isinstance(val, CBOR_UNSIGNED_INTEGER): + return val + return CBOR_UNSIGNED_INTEGER(int(val)) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_UNSIGNED_INTEGER, bytes] + return CBORcodec_UNSIGNED_INTEGER.dec(s) # type: ignore + + def _encode(self, x): + # type: (Any) -> bytes + return CBORcodec_UNSIGNED_INTEGER.enc( + x if isinstance(x, CBOR_Object) else CBOR_UNSIGNED_INTEGER(int(x)) + ) + + def randval(self): + # type: () -> RandNum + return RandNum(0, 2 ** 64 - 1) + + +class CBORF_NEGATIVE_INTEGER(CBORF_field[int, CBOR_NEGATIVE_INTEGER]): + """CBOR negative integer field (major type 1).""" + CBOR_tag = CBOR_MajorTypes.NEGATIVE_INTEGER + + def _wrap(self, val): + # type: (Any) -> CBOR_NEGATIVE_INTEGER + if isinstance(val, CBOR_NEGATIVE_INTEGER): + return val + return CBOR_NEGATIVE_INTEGER(int(val)) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_NEGATIVE_INTEGER, bytes] + return CBORcodec_NEGATIVE_INTEGER.dec(s) # type: ignore + + def _encode(self, x): + # type: (Any) -> bytes + return CBORcodec_NEGATIVE_INTEGER.enc( + x if isinstance(x, CBOR_Object) else CBOR_NEGATIVE_INTEGER(int(x)) + ) + + def randval(self): + # type: () -> RandNum + return RandNum(-2 ** 64, -1) + + +class CBORF_INTEGER(CBORF_field[int, + Union[CBOR_UNSIGNED_INTEGER, + CBOR_NEGATIVE_INTEGER]]): + """CBOR integer field handling both positive and negative values.""" + + def _wrap(self, val): + # type: (Any) -> Union[CBOR_UNSIGNED_INTEGER, CBOR_NEGATIVE_INTEGER] + if isinstance(val, (CBOR_UNSIGNED_INTEGER, CBOR_NEGATIVE_INTEGER)): + return val + i = int(val) + if i >= 0: + return CBOR_UNSIGNED_INTEGER(i) + return CBOR_NEGATIVE_INTEGER(i) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[Union[CBOR_UNSIGNED_INTEGER, CBOR_NEGATIVE_INTEGER], bytes] # noqa: E501 + if not s: + raise CBOR_Decoding_Error("Empty CBOR data") + major_type = (s[0] >> 5) & 0x7 + if major_type == 0: + return CBORcodec_UNSIGNED_INTEGER.dec(s) # type: ignore + elif major_type == 1: + return CBORcodec_NEGATIVE_INTEGER.dec(s) # type: ignore + raise CBOR_Decoding_Error( + "Expected integer (major type 0 or 1), got %d" % major_type) + + def i2m(self, pkt, x): + # type: (CBOR_Packet, Any) -> bytes + if x is None: + return b"" + if isinstance(x, CBOR_Object): + return x.enc() + i = int(x) + if i >= 0: + return CBORcodec_UNSIGNED_INTEGER.enc(CBOR_UNSIGNED_INTEGER(i)) + return CBORcodec_NEGATIVE_INTEGER.enc(CBOR_NEGATIVE_INTEGER(i)) + + def randval(self): + # type: () -> RandNum + return RandNum(-2 ** 64, 2 ** 64 - 1) + + +class CBORF_BYTE_STRING(CBORF_field[bytes, CBOR_BYTE_STRING]): + """CBOR byte string field (major type 2).""" + CBOR_tag = CBOR_MajorTypes.BYTE_STRING + + def _wrap(self, val): + # type: (Any) -> CBOR_BYTE_STRING + if isinstance(val, CBOR_BYTE_STRING): + return val + return CBOR_BYTE_STRING(bytes(val)) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_BYTE_STRING, bytes] + return CBORcodec_BYTE_STRING.dec(s) # type: ignore + + def _encode(self, x): + # type: (Any) -> bytes + return CBORcodec_BYTE_STRING.enc( + x if isinstance(x, CBOR_Object) else CBOR_BYTE_STRING(bytes(x)) + ) + + def randval(self): + # type: () -> RandString + return RandString(RandNum(0, 1000)) + + +class CBORF_TEXT_STRING(CBORF_field[str, CBOR_TEXT_STRING]): + """CBOR text string field (major type 3).""" + CBOR_tag = CBOR_MajorTypes.TEXT_STRING + + def _wrap(self, val): + # type: (Any) -> CBOR_TEXT_STRING + if isinstance(val, CBOR_TEXT_STRING): + return val + return CBOR_TEXT_STRING(str(val)) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_TEXT_STRING, bytes] + return CBORcodec_TEXT_STRING.dec(s) # type: ignore + + def _encode(self, x): + # type: (Any) -> bytes + return CBORcodec_TEXT_STRING.enc( + x if isinstance(x, CBOR_Object) else CBOR_TEXT_STRING(str(x)) + ) + + def randval(self): + # type: () -> RandString + return RandString(RandNum(0, 1000)) + + +class CBORF_BOOLEAN(CBORF_field[bool, Union[CBOR_FALSE, CBOR_TRUE]]): + """CBOR boolean field (major type 7, simple values 20/21).""" + CBOR_tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def _wrap(self, val): + # type: (Any) -> Union[CBOR_FALSE, CBOR_TRUE] + if isinstance(val, (CBOR_FALSE, CBOR_TRUE)): + return val + return CBOR_TRUE() if val else CBOR_FALSE() + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[Union[CBOR_FALSE, CBOR_TRUE], bytes] + obj, remain = CBORcodec_SIMPLE_AND_FLOAT.dec(s) + if not isinstance(obj, (CBOR_FALSE, CBOR_TRUE)): + raise CBOR_Decoding_Error( + "Expected boolean (CBOR_FALSE or CBOR_TRUE), got %r" % obj) + return obj, remain # type: ignore + + def i2m(self, pkt, x): + # type: (CBOR_Packet, Any) -> bytes + if x is None: + return b"" + if isinstance(x, (CBOR_FALSE, CBOR_TRUE)): + return x.enc() + return CBORcodec_SIMPLE_AND_FLOAT.enc( + CBOR_TRUE() if x else CBOR_FALSE() + ) + + def randval(self): + # type: () -> RandChoice + return RandChoice(True, False) + + +class CBORF_NULL(CBORF_field[None, CBOR_NULL]): + """CBOR null field (major type 7, simple value 22).""" + CBOR_tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def __init__(self, + name, # type: str + default=None, # type: None + ): + # type: (...) -> None + super(CBORF_NULL, self).__init__(name, None) + + def _wrap(self, val): + # type: (Any) -> CBOR_NULL + return CBOR_NULL() + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_NULL, bytes] + obj, remain = CBORcodec_SIMPLE_AND_FLOAT.dec(s) + if not isinstance(obj, CBOR_NULL): + raise CBOR_Decoding_Error( + "Expected null, got %r" % obj) + return obj, remain # type: ignore + + def i2m(self, pkt, x): + # type: (CBOR_Packet, Any) -> bytes + return CBOR_NULL().enc() + + def is_empty(self, pkt): + # type: (CBOR_Packet) -> bool + return False + + +class CBORF_UNDEFINED(CBORF_field[None, CBOR_UNDEFINED]): + """CBOR undefined field (major type 7, simple value 23).""" + CBOR_tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def __init__(self, + name, # type: str + default=None, # type: None + ): + # type: (...) -> None + super(CBORF_UNDEFINED, self).__init__(name, None) + + def _wrap(self, val): + # type: (Any) -> CBOR_UNDEFINED + return CBOR_UNDEFINED() + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_UNDEFINED, bytes] + obj, remain = CBORcodec_SIMPLE_AND_FLOAT.dec(s) + if not isinstance(obj, CBOR_UNDEFINED): + raise CBOR_Decoding_Error( + "Expected undefined, got %r" % obj) + return obj, remain # type: ignore + + def i2m(self, pkt, x): + # type: (CBOR_Packet, Any) -> bytes + return CBOR_UNDEFINED().enc() + + def is_empty(self, pkt): + # type: (CBOR_Packet) -> bool + return False + + +class CBORF_FLOAT(CBORF_field[float, CBOR_FLOAT]): + """CBOR float field (major type 7, double precision).""" + CBOR_tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def _wrap(self, val): + # type: (Any) -> CBOR_FLOAT + if isinstance(val, CBOR_FLOAT): + return val + return CBOR_FLOAT(float(val)) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_FLOAT, bytes] + obj, remain = CBORcodec_SIMPLE_AND_FLOAT.dec(s) + if not isinstance(obj, CBOR_FLOAT): + raise CBOR_Decoding_Error( + "Expected float, got %r" % obj) + return obj, remain # type: ignore + + def i2m(self, pkt, x): + # type: (CBOR_Packet, Any) -> bytes + if x is None: + return b"" + if isinstance(x, CBOR_FLOAT): + return x.enc() + return CBORcodec_SIMPLE_AND_FLOAT.enc(CBOR_FLOAT(float(x))) + + def randval(self): + # type: () -> RandFloat + return RandFloat(0, 2 ** 32) + + +############################## +# Structured CBOR Fields # +############################## + +class CBORF_ARRAY(CBORF_field[List[Any], List[Any]]): + """ + CBOR array with a fixed sequence of named, typed fields (major type 4). + Analogous to ASN1F_SEQUENCE: each positional element corresponds to a + specific CBORF_field. The CBOR array count must match the number of + declared fields. + + Example:: + + class MyCBOR(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER("version", 1), + CBORF_TEXT_STRING("name", ""), + ) + """ + CBOR_tag = CBOR_MajorTypes.ARRAY + holds_packets = 1 + + def __init__(self, *seq, **kwargs): + # type: (*Any, **Any) -> None + # The array itself is a structural field without its own named slot on + # the packet; a placeholder name is used so the base class __init__ + # stays happy. Individual element fields are the ones that carry names. + name = "_cbor_array" + default = [field.default for field in seq] + super(CBORF_ARRAY, self).__init__(name, None) + self.default = default + self.seq = seq + self.islist = len(seq) > 1 + + def __repr__(self): + # type: () -> str + return "<%s%r>" % (self.__class__.__name__, self.seq) + + def is_empty(self, pkt): + # type: (CBOR_Packet) -> bool + return all(f.is_empty(pkt) for f in self.seq) + + def get_fields_list(self): + # type: () -> List[CBORF_field[Any, Any]] + return reduce(lambda x, y: x + y.get_fields_list(), + self.seq, []) + + def m2i(self, pkt, s): + # type: (Any, bytes) -> Tuple[Any, bytes] + """ + Decode a CBOR array. Each element is decoded by its corresponding + field in ``self.seq``. The decoded values are set directly on the + packet by each field's ``dissect`` call, so this method returns an + empty list (which is discarded by ``dissect``). + """ + try: + major_type, count, s = CBOR_decode_head(s) + except CBOR_Codec_Decoding_Error as e: + raise CBOR_Decoding_Error(str(e)) + if major_type != 4: + raise CBOR_Decoding_Error( + "Expected major type 4 (array), got %d" % major_type) + if count != len(self.seq): + raise CBOR_Decoding_Error( + "Array length mismatch: expected %d, got %d" % + (len(self.seq), count)) + for obj in self.seq: + try: + s = obj.dissect(pkt, s) + except CBORF_badsequence: + break + return [], s + + def dissect(self, pkt, s): + # type: (Any, bytes) -> bytes + _, x = self.m2i(pkt, s) + return x + + def build(self, pkt): + # type: (CBOR_Packet) -> bytes + items = b"".join(obj.build(pkt) for obj in self.seq) + return CBOR_encode_head(4, len(self.seq)) + items + + +_ARRAY_T = Union[ + 'CBOR_Packet', + Type[CBORF_field[Any, Any]], + 'CBORF_PACKET', + CBORF_field[Any, Any], +] + + +class CBORF_ARRAY_OF(CBORF_field[List[_ARRAY_T], List[CBOR_Object[Any]]]): + """ + CBOR array of homogeneous elements (major type 4). + Analogous to ASN1F_SEQUENCE_OF: variable-length array where every + element shares the same type, specified by ``cls``. + + ``cls`` may be a :class:`CBORF_field` class/instance (leaf type) or a + :class:`CBOR_Packet` subclass (structured type). + """ + CBOR_tag = CBOR_MajorTypes.ARRAY + islist = 1 + + def __init__(self, + name, # type: str + default, # type: Any + cls, # type: _ARRAY_T + ): + # type: (...) -> None + if isinstance(cls, type) and issubclass(cls, CBORF_field) or \ + isinstance(cls, CBORF_field): + if isinstance(cls, type): + self.fld = cls("_item", None) # type: ignore + else: + self.fld = cls + self._extract_item = lambda s, pkt: self.fld.m2i(pkt, s) + self.holds_packets = 0 + elif hasattr(cls, "CBOR_root") or callable(cls): + self.cls = cast("Type[CBOR_Packet]", cls) + self._extract_item = lambda s, pkt: self.extract_packet( + self.cls, s, _underlayer=pkt) + self.holds_packets = 1 + else: + raise ValueError("cls must be a CBORF_field or CBOR_Packet") + super(CBORF_ARRAY_OF, self).__init__(name, None) + self.default = default + + def is_empty(self, pkt): + # type: (CBOR_Packet) -> bool + return CBORF_field.is_empty(self, pkt) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[List[Any], bytes] + try: + major_type, count, s = CBOR_decode_head(s) + except CBOR_Codec_Decoding_Error as e: + raise CBOR_Decoding_Error(str(e)) + if major_type != 4: + raise CBOR_Decoding_Error( + "Expected major type 4 (array), got %d" % major_type) + lst = [] + for _ in range(count): + c, s = self._extract_item(s, pkt) # type: ignore + if c is not None: + lst.append(c) + return lst, s + + def build(self, pkt): + # type: (CBOR_Packet) -> bytes + val = getattr(pkt, self.name) + if val is None: + val = [] + items = b"".join(bytes(item) for item in val) + return CBOR_encode_head(4, len(val)) + items + + def i2repr(self, pkt, x): + # type: (CBOR_Packet, Any) -> str + if self.holds_packets: + return repr(x) + elif x is None: + return "[]" + else: + return "[%s]" % ", ".join( + self.fld.i2repr(pkt, item) for item in x # type: ignore + ) + + def __repr__(self): + # type: () -> str + return "<%s %s>" % (self.__class__.__name__, self.name) + + +class CBORF_MAP(CBORF_field[Dict[str, Any], Dict[str, Any]]): + """ + CBOR map with a fixed set of named, typed fields (major type 5). + + Each field in ``seq`` represents one key-value pair. The key is the + field's ``name`` encoded as a CBOR text string. The value is encoded + and decoded by the corresponding :class:`CBORF_field`. + + Example:: + + class MyCBOR(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER("version", 1), + CBORF_TEXT_STRING("name", ""), + ) + """ + CBOR_tag = CBOR_MajorTypes.MAP + holds_packets = 1 + + def __init__(self, *seq, **kwargs): + # type: (*Any, **Any) -> None + # The map itself is a structural field without its own named slot on + # the packet; a placeholder name is used so the base class __init__ + # stays happy. Individual value fields are the ones that carry names + # (which also serve as the CBOR text-string keys in the wire encoding). + name = "_cbor_map" + default = {field.name: field.default for field in seq} + super(CBORF_MAP, self).__init__(name, None) + self.default = default + self.seq = seq + self.islist = 1 + + def __repr__(self): + # type: () -> str + return "<%s%r>" % (self.__class__.__name__, self.seq) + + def is_empty(self, pkt): + # type: (CBOR_Packet) -> bool + return all(f.is_empty(pkt) for f in self.seq) + + def get_fields_list(self): + # type: () -> List[CBORF_field[Any, Any]] + return reduce(lambda x, y: x + y.get_fields_list(), + self.seq, []) + + def m2i(self, pkt, s): + # type: (Any, bytes) -> Tuple[Any, bytes] + """ + Decode a CBOR map. Keys are decoded as CBOR items and matched to + fields by name. Values are decoded by the matching field. Unknown + keys are silently skipped. + """ + try: + major_type, count, s = CBOR_decode_head(s) + except CBOR_Codec_Decoding_Error as e: + raise CBOR_Decoding_Error(str(e)) + if major_type != 5: + raise CBOR_Decoding_Error( + "Expected major type 5 (map), got %d" % major_type) + # Build a lookup from field name to field object. + field_map = {f.name: f for f in self.seq} + for _ in range(count): + # Decode the key (any CBOR type; convert to str for lookup). + key_obj, s = CBORcodec_Object.decode_cbor_item(s) + if isinstance(key_obj, CBOR_Object): + key = str(key_obj.val) + else: + key = str(key_obj) + fld = field_map.get(key) + if fld is not None: + s = fld.dissect(pkt, s) + else: + # Skip unknown value. + _unknown, s = CBORcodec_Object.decode_cbor_item(s) + return [], s + + def dissect(self, pkt, s): + # type: (Any, bytes) -> bytes + _, x = self.m2i(pkt, s) + return x + + def build(self, pkt): + # type: (CBOR_Packet) -> bytes + result = CBOR_encode_head(5, len(self.seq)) + for fld in self.seq: + # Encode key as a CBOR text string. + result += CBORcodec_TEXT_STRING.enc(CBOR_TEXT_STRING(fld.name)) + result += fld.build(pkt) + return result + + +class CBORF_SEMANTIC_TAG(CBORF_field[Tuple[int, Any], + CBOR_SEMANTIC_TAG]): + """ + CBOR semantic tag field (major type 6). + + Wraps an ``inner_field`` with the given numeric ``tag_num``. The inner + field handles encoding and decoding of the tagged value. The outer field + (named ``name``) stores the :class:`~scapy.cbor.cbor.CBOR_SEMANTIC_TAG` + wrapper (tag number + ``None`` placeholder), while the inner field stores + its value under its own name on the packet. + + Example:: + + class TimestampPkt(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG( + "tag_info", None, 1, CBORF_INTEGER("ts", 0) + ) + """ + CBOR_tag = CBOR_MajorTypes.TAG + + def __init__(self, + name, # type: str + default, # type: Any + tag_num, # type: int + inner_field, # type: CBORF_field[Any, Any] + ): + # type: (...) -> None + self.tag_num = tag_num + self.inner_field = inner_field + super(CBORF_SEMANTIC_TAG, self).__init__(name, default) + + def _wrap(self, val): + # type: (Any) -> CBOR_SEMANTIC_TAG + if isinstance(val, CBOR_SEMANTIC_TAG): + return val + return CBOR_SEMANTIC_TAG((self.tag_num, val)) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[CBOR_SEMANTIC_TAG, bytes] + try: + major_type, tag_num, s = CBOR_decode_head(s) + except CBOR_Codec_Decoding_Error as e: + raise CBOR_Decoding_Error(str(e)) + if major_type != 6: + raise CBOR_Decoding_Error( + "Expected major type 6 (semantic tag), got %d" % major_type) + return CBOR_SEMANTIC_TAG((tag_num, None)), s # type: ignore + + def dissect(self, pkt, s): + # type: (CBOR_Packet, bytes) -> bytes + tag_obj, s = self.m2i(pkt, s) + self.set_val(pkt, tag_obj) + # Dissect the tagged content using the inner field. + return self.inner_field.dissect(pkt, s) + + def build(self, pkt): + # type: (CBOR_Packet) -> bytes + inner_bytes = self.inner_field.build(pkt) + return CBOR_encode_head(6, self.tag_num) + inner_bytes + + def get_fields_list(self): + # type: () -> List[CBORF_field[Any, Any]] + return [self] + self.inner_field.get_fields_list() + + +############################## +# Complex CBOR Fields # +############################## + +class CBORF_optional(CBORF_element): + """ + Wrapper making a :class:`CBORF_field` optional. + + During decoding, if the next CBOR item does not match the expected major + type, the field value is set to ``None`` and the stream is left unchanged. + """ + + def __init__(self, field): + # type: (CBORF_field[Any, Any]) -> None + self._field = field + + def __getattr__(self, attr): + # type: (str) -> Optional[Any] + return getattr(self._field, attr) + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[Any, bytes] + try: + return self._field.m2i(pkt, s) + except (CBOR_Error, CBORF_badsequence, + CBOR_Codec_Decoding_Error): + return None, s + + def dissect(self, pkt, s): + # type: (CBOR_Packet, bytes) -> bytes + try: + return self._field.dissect(pkt, s) + except (CBOR_Error, CBORF_badsequence, + CBOR_Codec_Decoding_Error): + self._field.set_val(pkt, None) + return s + + def build(self, pkt): + # type: (CBOR_Packet) -> bytes + if self._field.is_empty(pkt): + return b"" + return self._field.build(pkt) + + def any2i(self, pkt, x): + # type: (CBOR_Packet, Any) -> Any + return self._field.any2i(pkt, x) + + def i2repr(self, pkt, x): + # type: (CBOR_Packet, Any) -> str + return self._field.i2repr(pkt, x) + + +class CBORF_PACKET(CBORF_field['CBOR_Packet', Optional['CBOR_Packet']]): + """ + CBOR field that encapsulates a nested :class:`CBOR_Packet`. + + The nested packet is encoded as-is (its ``CBOR_root.build()`` output) + and decoded by instantiating ``cls`` from the current byte stream. + """ + holds_packets = 1 + + def __init__(self, + name, # type: str + default, # type: Optional[CBOR_Packet] + cls, # type: Type[CBOR_Packet] + ): + # type: (...) -> None + self.cls = cls + super(CBORF_PACKET, self).__init__(name, None) + self.default = default + + def m2i(self, pkt, s): + # type: (CBOR_Packet, bytes) -> Tuple[Any, bytes] + return self.extract_packet(self.cls, s, _underlayer=pkt) + + def i2m(self, pkt, x): + # type: (CBOR_Packet, Any) -> bytes + if x is None: + return b"" + if isinstance(x, bytes): + return x + return bytes(x) + + def any2i(self, pkt, x): + # type: (CBOR_Packet, Any) -> CBOR_Packet + if hasattr(x, "add_underlayer"): + x.add_underlayer(pkt) + return super(CBORF_PACKET, self).any2i(pkt, x) # type: ignore + + def randval(self): # type: ignore + # type: () -> CBOR_Packet + return packet.fuzz(self.cls()) diff --git a/scapy/cborpacket.py b/scapy/cborpacket.py new file mode 100644 index 00000000000..eb12bedaea9 --- /dev/null +++ b/scapy/cborpacket.py @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +CBOR Packet + +Packet holding data encoded in Concise Binary Object Representation (CBOR). +Modelled after scapy/asn1packet.py. +""" + +from scapy.base_classes import Packet_metaclass +from scapy.packet import Packet + +from typing import ( + Any, + Dict, + Tuple, + Type, + cast, + TYPE_CHECKING, +) + +if TYPE_CHECKING: + from scapy.cbor.cborfields import CBORF_field # noqa: F401 + + +class CBORPacket_metaclass(Packet_metaclass): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type[CBOR_Packet] + if dct.get("CBOR_root") is not None: + dct["fields_desc"] = dct["CBOR_root"].get_fields_list() + return cast( + 'Type[CBOR_Packet]', + super(CBORPacket_metaclass, cls).__new__(cls, name, bases, dct), + ) + + +class CBOR_Packet(Packet, metaclass=CBORPacket_metaclass): + CBOR_root = cast('CBORF_field[Any, Any]', None) + + def self_build(self): + # type: () -> bytes + """Build this CBOR packet to wire bytes using CBOR_root. + + Returns the raw packet cache when already built, otherwise delegates + to CBOR_root.build() which encodes all fields according to the CBOR + schema defined for this packet. + """ + if self.raw_packet_cache is not None: + return self.raw_packet_cache + return self.CBOR_root.build(self) + + def do_dissect(self, x): + # type: (bytes) -> bytes + """Dissect CBOR-encoded bytes into packet fields. + + Delegates to CBOR_root.dissect() which reads CBOR items from *x*, + populates each field on the packet, and returns any unconsumed bytes. + """ + return self.CBOR_root.dissect(self, x) diff --git a/test/scapy/layers/cbor.uts b/test/scapy/layers/cbor.uts index f56d116a505..f65c75d89ce 100644 --- a/test/scapy/layers/cbor.uts +++ b/test/scapy/layers/cbor.uts @@ -992,3 +992,2960 @@ for _ in range(50): pass roundtrip_count >= 45 + +########### CBOR Fields ########################################### + ++ CBORF scalar fields - CBORF_UNSIGNED_INTEGER + += CBORF_UNSIGNED_INTEGER basic encode/decode +from scapy.cbor.cborfields import CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PktUInt(CBOR_Packet): + CBOR_root = CBORF_UNSIGNED_INTEGER("value", 42) + +pkt = PktUInt() +assert pkt.value.val == 42 +raw_data = bytes(pkt) +pkt2 = PktUInt(raw_data) +assert pkt2.value.val == 42 + += CBORF_UNSIGNED_INTEGER zero value +from scapy.cbor.cborfields import CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PktUIntZero(CBOR_Packet): + CBOR_root = CBORF_UNSIGNED_INTEGER("value", 0) + +pkt = PktUIntZero() +raw_data = bytes(pkt) +assert raw_data == b'\x00' +pkt2 = PktUIntZero(raw_data) +assert pkt2.value.val == 0 + += CBORF_UNSIGNED_INTEGER large value roundtrip +from scapy.cbor.cborfields import CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PktUIntLarge(CBOR_Packet): + CBOR_root = CBORF_UNSIGNED_INTEGER("value", 1000000) + +pkt = PktUIntLarge() +raw_data = bytes(pkt) +pkt2 = PktUIntLarge(raw_data) +assert pkt2.value.val == 1000000 + ++ CBORF scalar fields - CBORF_NEGATIVE_INTEGER + += CBORF_NEGATIVE_INTEGER basic encode/decode +from scapy.cbor.cborfields import CBORF_NEGATIVE_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PktNInt(CBOR_Packet): + CBOR_root = CBORF_NEGATIVE_INTEGER("value", -1) + +pkt = PktNInt() +assert pkt.value.val == -1 +raw_data = bytes(pkt) +pkt2 = PktNInt(raw_data) +assert pkt2.value.val == -1 + += CBORF_NEGATIVE_INTEGER -100 roundtrip +from scapy.cbor.cborfields import CBORF_NEGATIVE_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PktNInt100(CBOR_Packet): + CBOR_root = CBORF_NEGATIVE_INTEGER("value", -100) + +pkt = PktNInt100() +raw_data = bytes(pkt) +pkt2 = PktNInt100(raw_data) +assert pkt2.value.val == -100 + ++ CBORF scalar fields - CBORF_INTEGER + += CBORF_INTEGER positive value +from scapy.cbor.cborfields import CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PktInt(CBOR_Packet): + CBOR_root = CBORF_INTEGER("value", 7) + +pkt = PktInt() +raw_data = bytes(pkt) +pkt2 = PktInt(raw_data) +assert pkt2.value.val == 7 + += CBORF_INTEGER negative value +from scapy.cbor.cborfields import CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PktIntNeg(CBOR_Packet): + CBOR_root = CBORF_INTEGER("value", -5) + +pkt = PktIntNeg() +raw_data = bytes(pkt) +pkt2 = PktIntNeg(raw_data) +assert pkt2.value.val == -5 + ++ CBORF scalar fields - CBORF_BYTE_STRING + += CBORF_BYTE_STRING basic encode/decode +from scapy.cbor.cborfields import CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class PktBStr(CBOR_Packet): + CBOR_root = CBORF_BYTE_STRING("data", b"hello") + +pkt = PktBStr() +assert pkt.data.val == b"hello" +raw_data = bytes(pkt) +pkt2 = PktBStr(raw_data) +assert pkt2.data.val == b"hello" + += CBORF_BYTE_STRING empty bytes +from scapy.cbor.cborfields import CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class PktBStrEmpty(CBOR_Packet): + CBOR_root = CBORF_BYTE_STRING("data", b"") + +pkt = PktBStrEmpty() +raw_data = bytes(pkt) +assert raw_data == b'\x40' +pkt2 = PktBStrEmpty(raw_data) +assert pkt2.data.val == b"" + ++ CBORF scalar fields - CBORF_TEXT_STRING + += CBORF_TEXT_STRING basic encode/decode +from scapy.cbor.cborfields import CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class PktTStr(CBOR_Packet): + CBOR_root = CBORF_TEXT_STRING("title", "hello") + +pkt = PktTStr() +assert pkt.title.val == "hello" +raw_data = bytes(pkt) +pkt2 = PktTStr(raw_data) +assert pkt2.title.val == "hello" + += CBORF_TEXT_STRING empty string +from scapy.cbor.cborfields import CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class PktTStrEmpty(CBOR_Packet): + CBOR_root = CBORF_TEXT_STRING("title", "") + +pkt = PktTStrEmpty() +raw_data = bytes(pkt) +assert raw_data == b'\x60' +pkt2 = PktTStrEmpty(raw_data) +assert pkt2.title.val == "" + ++ CBORF scalar fields - CBORF_BOOLEAN + += CBORF_BOOLEAN true value +from scapy.cbor.cborfields import CBORF_BOOLEAN +from scapy.cbor.cbor import CBOR_TRUE +from scapy.cborpacket import CBOR_Packet + +class PktBool(CBOR_Packet): + CBOR_root = CBORF_BOOLEAN("flag", True) + +pkt = PktBool() +assert isinstance(pkt.flag, CBOR_TRUE) +raw_data = bytes(pkt) +assert raw_data == b'\xf5' +pkt2 = PktBool(raw_data) +assert isinstance(pkt2.flag, CBOR_TRUE) + += CBORF_BOOLEAN false value +from scapy.cbor.cborfields import CBORF_BOOLEAN +from scapy.cbor.cbor import CBOR_FALSE +from scapy.cborpacket import CBOR_Packet + +class PktBoolFalse(CBOR_Packet): + CBOR_root = CBORF_BOOLEAN("flag", False) + +pkt = PktBoolFalse() +raw_data = bytes(pkt) +assert raw_data == b'\xf4' +pkt2 = PktBoolFalse(raw_data) +assert isinstance(pkt2.flag, CBOR_FALSE) + ++ CBORF scalar fields - CBORF_FLOAT + += CBORF_FLOAT encode/decode +from scapy.cbor.cborfields import CBORF_FLOAT +from scapy.cborpacket import CBOR_Packet + +class PktFloat(CBOR_Packet): + CBOR_root = CBORF_FLOAT("value", 1.5) + +pkt = PktFloat() +raw_data = bytes(pkt) +pkt2 = PktFloat(raw_data) +assert abs(pkt2.value.val - 1.5) < 1e-9 + += CBORF_NULL encode/decode +from scapy.cbor.cborfields import CBORF_NULL +from scapy.cbor.cbor import CBOR_NULL +from scapy.cborpacket import CBOR_Packet + +class PktNull(CBOR_Packet): + CBOR_root = CBORF_NULL("nothing") + +pkt = PktNull() +raw_data = bytes(pkt) +assert raw_data == b'\xf6' +pkt2 = PktNull(raw_data) +assert isinstance(pkt2.nothing, CBOR_NULL) + ++ CBORF scalar fields - CBORF_UNDEFINED + += CBORF_UNDEFINED encode/decode +from scapy.cbor.cborfields import CBORF_UNDEFINED +from scapy.cbor.cbor import CBOR_UNDEFINED +from scapy.cborpacket import CBOR_Packet + +class PktUndef(CBOR_Packet): + CBOR_root = CBORF_UNDEFINED("undef") + +pkt = PktUndef() +raw_data = bytes(pkt) +assert raw_data == b'\xf7' +pkt2 = PktUndef(raw_data) +assert isinstance(pkt2.undef, CBOR_UNDEFINED) + ++ CBORF structured fields - CBORF_ARRAY + += CBORF_ARRAY two-field encode/decode +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class MyCBOR(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER("version", 1), + CBORF_TEXT_STRING("title", "test"), + ) + +pkt = MyCBOR() +assert pkt.version.val == 1 +assert pkt.title.val == "test" +raw_data = bytes(pkt) +pkt2 = MyCBOR(raw_data) +assert pkt2.version.val == 1 +assert pkt2.title.val == "test" + += CBORF_ARRAY three-field encode/decode +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class Multi(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER("id", 99), + CBORF_TEXT_STRING("label", "x"), + CBORF_BOOLEAN("active", True), + ) + +pkt = Multi() +raw_data = bytes(pkt) +pkt2 = Multi(raw_data) +assert pkt2.id.val == 99 +assert pkt2.label.val == "x" + += CBORF_ARRAY single integer roundtrip +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class Single(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_UNSIGNED_INTEGER("count", 5), + ) + +pkt = Single() +raw_data = bytes(pkt) +pkt2 = Single(raw_data) +assert pkt2.count.val == 5 + ++ CBORF structured fields - CBORF_ARRAY_OF + += CBORF_ARRAY_OF with CBORF_INTEGER elements +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_INTEGER +from scapy.cbor.cbor import CBOR_UNSIGNED_INTEGER, CBOR_NEGATIVE_INTEGER +from scapy.cborpacket import CBOR_Packet + +class ArrOfInt(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF("items", [], CBORF_INTEGER) + +pkt = ArrOfInt() +pkt.items = [CBOR_UNSIGNED_INTEGER(1), CBOR_UNSIGNED_INTEGER(2), CBOR_UNSIGNED_INTEGER(3)] +raw_data = bytes(pkt) +pkt2 = ArrOfInt(raw_data) +assert len(pkt2.items) == 3 +assert pkt2.items[0].val == 1 +assert pkt2.items[2].val == 3 + ++ CBORF structured fields - CBORF_MAP + += CBORF_MAP basic encode/decode +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class MyMap(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER("version", 2), + CBORF_TEXT_STRING("title", "cbor"), + ) + +pkt = MyMap() +assert pkt.version.val == 2 +assert pkt.title.val == "cbor" +raw_data = bytes(pkt) +pkt2 = MyMap(raw_data) +assert pkt2.version.val == 2 +assert pkt2.title.val == "cbor" + += CBORF_MAP byte string value +from scapy.cbor.cborfields import CBORF_MAP, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class BinMap(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_BYTE_STRING("data", b"\xde\xad\xbe\xef"), + ) + +pkt = BinMap() +raw_data = bytes(pkt) +pkt2 = BinMap(raw_data) +assert pkt2.data.val == b"\xde\xad\xbe\xef" + ++ CBORF complex fields - CBORF_optional + += CBORF_optional present field +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_optional +from scapy.cborpacket import CBOR_Packet + +class OptPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER("version", 1), + CBORF_optional(CBORF_TEXT_STRING("title", "")), + ) + +pkt = OptPkt() +raw_data = bytes(pkt) +pkt2 = OptPkt(raw_data) +assert pkt2.version.val == 1 +assert pkt2.title.val == "" + ++ CBORF_PACKET nested packet + += CBORF_PACKET basic nesting +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class Inner(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER("x", 10), + ) + +class Outer(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_TEXT_STRING("label", "outer"), + CBORF_PACKET("inner", None, Inner), + ) + +inner = Inner() +outer = Outer() +outer.label = outer.label # keep default +outer.inner = inner +raw_data = bytes(outer) +outer2 = Outer(raw_data) +assert outer2.label.val == "outer" + ++ CBORF_SEMANTIC_TAG + += CBORF_SEMANTIC_TAG encode with inner integer +from scapy.cbor.cborfields import CBORF_SEMANTIC_TAG, CBORF_INTEGER +from scapy.cbor.cbor import CBOR_SEMANTIC_TAG as CBOR_SEM +from scapy.cborpacket import CBOR_Packet + +class TaggedPkt(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG("tag_info", None, 1, CBORF_INTEGER("ts", 0)) + +pkt = TaggedPkt() +# Build encodes tag 1 + inner field default +raw_data = bytes(pkt) +# Major type 6 (tag), tag number 1 => 0xc1 +assert raw_data[0:1] == b'\xc1' + ++ CBOR_Packet / CBORF field integration + += CBOR_Packet fields_desc built from CBORF_ARRAY +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_UNSIGNED_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class Demo(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_UNSIGNED_INTEGER("id", 1), + CBORF_TEXT_STRING("desc", "demo"), + ) + +# fields_desc should contain both fields +field_names = [f.name for f in Demo.fields_desc] +assert "id" in field_names +assert "desc" in field_names + += CBOR_Packet roundtrip preserves raw bytes +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class Simple(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER("a", 3), + CBORF_INTEGER("b", 7), + ) + +pkt = Simple() +raw_data = bytes(pkt) +pkt2 = Simple(raw_data) +assert bytes(pkt2) == raw_data + +########### Additional Unit Tests #################################### + ++ CBOR Simple Values + += Decode CBOR simple value 0 +data = bytes.fromhex('e0') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +from scapy.cbor.cbor import CBOR_SIMPLE_VALUE +isinstance(obj, CBOR_SIMPLE_VALUE) and obj.val == 0 and remainder == b'' + += Decode CBOR simple value 16 +data = bytes.fromhex('f0') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_SIMPLE_VALUE) and obj.val == 16 and remainder == b'' + += Decode CBOR simple value 255 (1-byte extended) +data = bytes.fromhex('f8ff') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_SIMPLE_VALUE) and obj.val == 255 and remainder == b'' + ++ CBOR Float Encodings - RFC 8949 Test Vectors + += Half-precision: positive zero (0xf90000) +import math +data = bytes.fromhex('f90000') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 0.0 and remainder == b'' + += Half-precision: negative zero (0xf98000) +data = bytes.fromhex('f98000') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == -0.0 and math.copysign(1, obj.val) == -1.0 and remainder == b'' + += Half-precision: 1.0 (0xf93c00) +data = bytes.fromhex('f93c00') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 1.0 and remainder == b'' + += Half-precision: 1.5 (0xf93e00) +data = bytes.fromhex('f93e00') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 1.5 and remainder == b'' + += Half-precision: max (65504.0) (0xf97bff) +data = bytes.fromhex('f97bff') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 65504.0 and remainder == b'' + += Half-precision: smallest subnormal (0xf90001) +data = bytes.fromhex('f90001') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and abs(obj.val - 5.960464477539063e-8) < 1e-15 and remainder == b'' + += Half-precision: smallest normal (0xf90400) +data = bytes.fromhex('f90400') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and abs(obj.val - 6.103515625e-5) < 1e-12 and remainder == b'' + += Half-precision: positive infinity (0xf97c00) +data = bytes.fromhex('f97c00') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isinf(obj.val) and obj.val > 0 and remainder == b'' + += Half-precision: negative infinity (0xf9fc00) +data = bytes.fromhex('f9fc00') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isinf(obj.val) and obj.val < 0 and remainder == b'' + += Half-precision: NaN (0xf97e00) +data = bytes.fromhex('f97e00') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isnan(obj.val) and remainder == b'' + += Single-precision: 100000.0 (0xfa47c35000) +data = bytes.fromhex('fa47c35000') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 100000.0 and remainder == b'' + += Single-precision: max float32 (0xfa7f7fffff) +data = bytes.fromhex('fa7f7fffff') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and abs(obj.val - 3.4028234663852886e+38) < 1e30 and remainder == b'' + += Single-precision: positive infinity (0xfa7f800000) +data = bytes.fromhex('fa7f800000') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isinf(obj.val) and obj.val > 0 and remainder == b'' + += Single-precision: NaN (0xfa7fc00000) +data = bytes.fromhex('fa7fc00000') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isnan(obj.val) and remainder == b'' + += Double-precision: 1.1 (0xfb3ff199999999999a) +data = bytes.fromhex('fb3ff199999999999a') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and abs(obj.val - 1.1) < 1e-10 and remainder == b'' + += Double-precision: 1.0e+300 (0xfb7e37e43c8800759c) +data = bytes.fromhex('fb7e37e43c8800759c') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and abs(obj.val - 1.0e+300) / 1.0e+300 < 1e-10 and remainder == b'' + += Double-precision: NaN (0xfb7ff8000000000000) +data = bytes.fromhex('fb7ff8000000000000') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isnan(obj.val) and remainder == b'' + ++ CBOR Integer Encoding - RFC 8949 Test Vectors + += RFC 8949: encode 0 +obj = CBOR_UNSIGNED_INTEGER(0) +bytes(obj) == bytes.fromhex('00') + += RFC 8949: encode 1 +obj = CBOR_UNSIGNED_INTEGER(1) +bytes(obj) == bytes.fromhex('01') + += RFC 8949: encode 10 +obj = CBOR_UNSIGNED_INTEGER(10) +bytes(obj) == bytes.fromhex('0a') + += RFC 8949: encode 23 +obj = CBOR_UNSIGNED_INTEGER(23) +bytes(obj) == bytes.fromhex('17') + += RFC 8949: encode 24 +obj = CBOR_UNSIGNED_INTEGER(24) +bytes(obj) == bytes.fromhex('1818') + += RFC 8949: encode 25 +obj = CBOR_UNSIGNED_INTEGER(25) +bytes(obj) == bytes.fromhex('1819') + += RFC 8949: encode 100 +obj = CBOR_UNSIGNED_INTEGER(100) +bytes(obj) == bytes.fromhex('1864') + += RFC 8949: encode 1000 +obj = CBOR_UNSIGNED_INTEGER(1000) +bytes(obj) == bytes.fromhex('1903e8') + += RFC 8949: encode 1000000 +obj = CBOR_UNSIGNED_INTEGER(1000000) +bytes(obj) == bytes.fromhex('1a000f4240') + += RFC 8949: encode 1000000000000 +obj = CBOR_UNSIGNED_INTEGER(1000000000000) +bytes(obj) == bytes.fromhex('1b000000e8d4a51000') + += RFC 8949: encode 18446744073709551615 (2^64-1) +obj = CBOR_UNSIGNED_INTEGER(18446744073709551615) +bytes(obj) == bytes.fromhex('1bffffffffffffffff') + += RFC 8949: encode -1 +obj = CBOR_NEGATIVE_INTEGER(-1) +bytes(obj) == bytes.fromhex('20') + += RFC 8949: encode -10 +obj = CBOR_NEGATIVE_INTEGER(-10) +bytes(obj) == bytes.fromhex('29') + += RFC 8949: encode -100 +obj = CBOR_NEGATIVE_INTEGER(-100) +bytes(obj) == bytes.fromhex('3863') + += RFC 8949: encode -1000 +obj = CBOR_NEGATIVE_INTEGER(-1000) +bytes(obj) == bytes.fromhex('3903e7') + += RFC 8949: decode 0 +obj, remainder = CBOR_Codecs.CBOR.dec(bytes.fromhex('00')) +obj.val == 0 and remainder == b'' + += RFC 8949: decode 23 +obj, remainder = CBOR_Codecs.CBOR.dec(bytes.fromhex('17')) +obj.val == 23 and remainder == b'' + += RFC 8949: decode 24 +obj, remainder = CBOR_Codecs.CBOR.dec(bytes.fromhex('1818')) +obj.val == 24 and remainder == b'' + += RFC 8949: decode 1000000000000 +obj, remainder = CBOR_Codecs.CBOR.dec(bytes.fromhex('1b000000e8d4a51000')) +obj.val == 1000000000000 and remainder == b'' + += RFC 8949: decode -1000 +obj, remainder = CBOR_Codecs.CBOR.dec(bytes.fromhex('3903e7')) +obj.val == -1000 and remainder == b'' + ++ CBOR Byte String with All Byte Values + += CBOR_BYTE_STRING: encode/decode all 256 byte values +all_bytes = bytes(range(256)) +obj = CBOR_BYTE_STRING(all_bytes) +enc = bytes(obj) +dec, remainder = CBOR_Codecs.CBOR.dec(enc) +dec.val == all_bytes and remainder == b'' + += CBOR_BYTE_STRING: cbor2 interop with all 256 byte values +import cbor2 +all_bytes = bytes(range(256)) +obj = CBOR_BYTE_STRING(all_bytes) +enc = bytes(obj) +dec = cbor2.loads(enc) +dec == all_bytes + ++ CBOR Map with Integer Keys + += Decode map with integer keys (cbor2 encode, Scapy decode) +import cbor2 +enc = cbor2.dumps({1: 'one', 2: 'two', -1: 'minus_one'}) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_MAP) and obj.val.get(1) is not None and obj.val[1].val == 'one' and remainder == b'' + += Encode map with integer keys (Scapy encode, cbor2 decode) +from scapy.cbor.cborcodec import CBORcodec_MAP +enc = CBORcodec_MAP.enc({1: 'one', 2: 'two'}) +dec = cbor2.loads(enc) +dec == {1: 'one', 2: 'two'} + += Map with mixed key types roundtrip +enc = cbor2.dumps({'str_key': 42, 1: 'int_key'}) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_MAP) and len(obj.val) == 2 and remainder == b'' + ++ CBOR Multiple Items in Stream + += Decode three integers from a single byte stream +data = bytes.fromhex('01') + bytes.fromhex('0a') + bytes.fromhex('17') +obj1, rest1 = CBOR_Codecs.CBOR.dec(data) +obj2, rest2 = CBOR_Codecs.CBOR.dec(rest1) +obj3, rest3 = CBOR_Codecs.CBOR.dec(rest2) +obj1.val == 1 and obj2.val == 10 and obj3.val == 23 and rest3 == b'' + += Decode integer followed by string +data = bytes.fromhex('1864') + bytes.fromhex('626869') +obj1, rest1 = CBOR_Codecs.CBOR.dec(data) +obj2, rest2 = CBOR_Codecs.CBOR.dec(rest1) +obj1.val == 100 and obj2.val == 'hi' and rest2 == b'' + ++ CBOR Nested Structures Unit Tests + += Encode and decode doubly nested array +from scapy.cbor.cborcodec import CBORcodec_ARRAY +enc = CBORcodec_ARRAY.enc([[1, 2], [3, 4], [5, 6]]) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 3 and len(obj.val[0].val) == 2 and remainder == b'' + += Encode and decode map containing arrays +from scapy.cbor.cborcodec import CBORcodec_MAP, CBORcodec_ARRAY +enc = CBORcodec_MAP.enc({'nums': [1, 2, 3], 'strs': ['a', 'b']}) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_MAP) and 'nums' in obj.val and isinstance(obj.val['nums'], CBOR_ARRAY) and remainder == b'' + += Encode and decode array containing maps +from scapy.cbor.cborcodec import CBORcodec_ARRAY +enc = CBORcodec_ARRAY.enc([{'id': 1}, {'id': 2}]) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 2 and isinstance(obj.val[0], CBOR_MAP) and remainder == b'' + +########### Extended Interoperability Tests with cbor2 ################ + ++ CBOR Interoperability - RFC 8949 Appendix B (Scapy encode, cbor2 decode) + += RFC 8949 Appendix B: 0 +import cbor2 +obj = CBOR_UNSIGNED_INTEGER(0) +cbor2.loads(bytes(obj)) == 0 + += RFC 8949 Appendix B: 1 +obj = CBOR_UNSIGNED_INTEGER(1) +cbor2.loads(bytes(obj)) == 1 + += RFC 8949 Appendix B: 10 +obj = CBOR_UNSIGNED_INTEGER(10) +cbor2.loads(bytes(obj)) == 10 + += RFC 8949 Appendix B: 23 +obj = CBOR_UNSIGNED_INTEGER(23) +cbor2.loads(bytes(obj)) == 23 + += RFC 8949 Appendix B: 24 +obj = CBOR_UNSIGNED_INTEGER(24) +cbor2.loads(bytes(obj)) == 24 + += RFC 8949 Appendix B: 1000 +obj = CBOR_UNSIGNED_INTEGER(1000) +cbor2.loads(bytes(obj)) == 1000 + += RFC 8949 Appendix B: 1000000000000 +obj = CBOR_UNSIGNED_INTEGER(1000000000000) +cbor2.loads(bytes(obj)) == 1000000000000 + += RFC 8949 Appendix B: 18446744073709551615 (max u64) +obj = CBOR_UNSIGNED_INTEGER(18446744073709551615) +cbor2.loads(bytes(obj)) == 18446744073709551615 + += RFC 8949 Appendix B: -1 +obj = CBOR_NEGATIVE_INTEGER(-1) +cbor2.loads(bytes(obj)) == -1 + += RFC 8949 Appendix B: -1000 +obj = CBOR_NEGATIVE_INTEGER(-1000) +cbor2.loads(bytes(obj)) == -1000 + += RFC 8949 Appendix B: false +obj = CBOR_FALSE() +cbor2.loads(bytes(obj)) is False + += RFC 8949 Appendix B: true +obj = CBOR_TRUE() +cbor2.loads(bytes(obj)) is True + += RFC 8949 Appendix B: null +obj = CBOR_NULL() +cbor2.loads(bytes(obj)) is None + += RFC 8949 Appendix B: undefined +obj = CBOR_UNDEFINED() +decoded = cbor2.loads(bytes(obj)) +from cbor2 import undefined +decoded is undefined + += RFC 8949 Appendix B: empty byte string +obj = CBOR_BYTE_STRING(b'') +cbor2.loads(bytes(obj)) == b'' + += RFC 8949 Appendix B: byte string b'\x01\x02\x03\x04' +obj = CBOR_BYTE_STRING(b'\x01\x02\x03\x04') +cbor2.loads(bytes(obj)) == b'\x01\x02\x03\x04' + += RFC 8949 Appendix B: empty text string +obj = CBOR_TEXT_STRING('') +cbor2.loads(bytes(obj)) == '' + += RFC 8949 Appendix B: 'a' +obj = CBOR_TEXT_STRING('a') +cbor2.loads(bytes(obj)) == 'a' + += RFC 8949 Appendix B: 'IETF' +obj = CBOR_TEXT_STRING('IETF') +cbor2.loads(bytes(obj)) == 'IETF' + += RFC 8949 Appendix B: u00fc (ü) +obj = CBOR_TEXT_STRING('\u00fc') +cbor2.loads(bytes(obj)) == '\u00fc' + += RFC 8949 Appendix B: u6c34 (water in Chinese) +obj = CBOR_TEXT_STRING('\u6c34') +cbor2.loads(bytes(obj)) == '\u6c34' + += RFC 8949 Appendix B: empty array +from scapy.cbor.cborcodec import CBORcodec_ARRAY +enc = CBORcodec_ARRAY.enc([]) +cbor2.loads(enc) == [] + += RFC 8949 Appendix B: [1, 2, 3] +enc = CBORcodec_ARRAY.enc([1, 2, 3]) +cbor2.loads(enc) == [1, 2, 3] + += RFC 8949 Appendix B: [1, [2, 3], [4, 5]] +enc = CBORcodec_ARRAY.enc([1, [2, 3], [4, 5]]) +cbor2.loads(enc) == [1, [2, 3], [4, 5]] + += RFC 8949 Appendix B: empty map +from scapy.cbor.cborcodec import CBORcodec_MAP +enc = CBORcodec_MAP.enc({}) +cbor2.loads(enc) == {} + += RFC 8949 Appendix B: {1: 2, 3: 4} +enc = CBORcodec_MAP.enc({1: 2, 3: 4}) +cbor2.loads(enc) == {1: 2, 3: 4} + += RFC 8949 Appendix B: {"a": 1, "b": [2, 3]} +enc = CBORcodec_MAP.enc({"a": 1, "b": [2, 3]}) +cbor2.loads(enc) == {"a": 1, "b": [2, 3]} + ++ CBOR Interoperability - RFC 8949 Appendix B (cbor2 encode, Scapy decode) + += RFC 8949 Appendix B decode: 0 +import cbor2 +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(0)) +obj.val == 0 and isinstance(obj, CBOR_UNSIGNED_INTEGER) + += RFC 8949 Appendix B decode: 23 +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(23)) +obj.val == 23 and isinstance(obj, CBOR_UNSIGNED_INTEGER) + += RFC 8949 Appendix B decode: 24 +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(24)) +obj.val == 24 and isinstance(obj, CBOR_UNSIGNED_INTEGER) + += RFC 8949 Appendix B decode: -1 +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(-1)) +obj.val == -1 and isinstance(obj, CBOR_NEGATIVE_INTEGER) + += RFC 8949 Appendix B decode: -1000 +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(-1000)) +obj.val == -1000 and isinstance(obj, CBOR_NEGATIVE_INTEGER) + += RFC 8949 Appendix B decode: false +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(False)) +isinstance(obj, CBOR_FALSE) and obj.val is False + += RFC 8949 Appendix B decode: true +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(True)) +isinstance(obj, CBOR_TRUE) and obj.val is True + += RFC 8949 Appendix B decode: null +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(None)) +isinstance(obj, CBOR_NULL) and obj.val is None + += RFC 8949 Appendix B decode: empty string +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps('')) +isinstance(obj, CBOR_TEXT_STRING) and obj.val == '' + += RFC 8949 Appendix B decode: 'IETF' +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps('IETF')) +isinstance(obj, CBOR_TEXT_STRING) and obj.val == 'IETF' + += RFC 8949 Appendix B decode: u00fc +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps('\u00fc')) +isinstance(obj, CBOR_TEXT_STRING) and obj.val == '\u00fc' + += RFC 8949 Appendix B decode: b'\x01\x02\x03\x04' +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps(b'\x01\x02\x03\x04')) +isinstance(obj, CBOR_BYTE_STRING) and obj.val == b'\x01\x02\x03\x04' + += RFC 8949 Appendix B decode: [1, 2, 3] +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps([1, 2, 3])) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 3 and obj.val[0].val == 1 + += RFC 8949 Appendix B decode: [1, [2, 3], [4, 5]] +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps([1, [2, 3], [4, 5]])) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 3 and isinstance(obj.val[1], CBOR_ARRAY) + += RFC 8949 Appendix B decode: {"a": 1, "b": [2, 3]} +obj, _ = CBOR_Codecs.CBOR.dec(cbor2.dumps({"a": 1, "b": [2, 3]})) +isinstance(obj, CBOR_MAP) and obj.val['a'].val == 1 and isinstance(obj.val['b'], CBOR_ARRAY) + ++ CBOR Interoperability - Byte-exact Comparison + += Scapy and cbor2 produce identical bytes for integer 0 +import cbor2 +bytes(CBOR_UNSIGNED_INTEGER(0)) == cbor2.dumps(0) + += Scapy and cbor2 produce identical bytes for integer 255 +bytes(CBOR_UNSIGNED_INTEGER(255)) == cbor2.dumps(255) + += Scapy and cbor2 produce identical bytes for -1 +bytes(CBOR_NEGATIVE_INTEGER(-1)) == cbor2.dumps(-1) + += Scapy and cbor2 produce identical bytes for -1000 +bytes(CBOR_NEGATIVE_INTEGER(-1000)) == cbor2.dumps(-1000) + += Scapy and cbor2 produce identical bytes for empty byte string +bytes(CBOR_BYTE_STRING(b'')) == cbor2.dumps(b'') + += Scapy and cbor2 produce identical bytes for 'hello' +bytes(CBOR_TEXT_STRING('hello')) == cbor2.dumps('hello') + += Scapy and cbor2 produce identical bytes for true +bytes(CBOR_TRUE()) == cbor2.dumps(True) + += Scapy and cbor2 produce identical bytes for false +bytes(CBOR_FALSE()) == cbor2.dumps(False) + += Scapy and cbor2 produce identical bytes for null +bytes(CBOR_NULL()) == cbor2.dumps(None) + += Scapy and cbor2 produce identical bytes for undefined +from cbor2 import undefined +bytes(CBOR_UNDEFINED()) == cbor2.dumps(undefined) + += Scapy and cbor2 produce identical bytes for empty array +from scapy.cbor.cborcodec import CBORcodec_ARRAY +CBORcodec_ARRAY.enc([]) == cbor2.dumps([]) + += Scapy and cbor2 produce identical bytes for empty map +from scapy.cbor.cborcodec import CBORcodec_MAP +CBORcodec_MAP.enc({}) == cbor2.dumps({}) + += Scapy and cbor2 produce identical bytes for [1, 2, 3] +CBORcodec_ARRAY.enc([1, 2, 3]) == cbor2.dumps([1, 2, 3]) + += Scapy and cbor2 produce identical bytes for {'a': 1} +CBORcodec_MAP.enc({'a': 1}) == cbor2.dumps({'a': 1}) + ++ CBOR Interoperability - Semantic Tags + += Scapy encode semantic tag (tag 42), cbor2 decode +import cbor2 +obj = CBOR_SEMANTIC_TAG((42, CBOR_TEXT_STRING('test-content'))) +enc = bytes(obj) +dec = cbor2.loads(enc) +isinstance(dec, cbor2.CBORTag) and dec.tag == 42 and dec.value == 'test-content' + += cbor2 encode semantic tag (tag 42), Scapy decode +enc = cbor2.dumps(cbor2.CBORTag(42, 'test-content')) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_SEMANTIC_TAG) and obj.val[0] == 42 and obj.val[1].val == 'test-content' and remainder == b'' + += Scapy and cbor2 produce identical bytes for semantic tag 42 +import cbor2 +scapy_enc = bytes(CBOR_SEMANTIC_TAG((42, CBOR_TEXT_STRING('test-content')))) +cbor2_enc = cbor2.dumps(cbor2.CBORTag(42, 'test-content')) +scapy_enc == cbor2_enc + += cbor2 encode epoch-based datetime tag (tag 1), Scapy decode +enc = cbor2.dumps(cbor2.CBORTag(1, 1363896240)) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_SEMANTIC_TAG) and obj.val[0] == 1 and obj.val[1].val == 1363896240 and remainder == b'' + += cbor2 encode integer-tagged byte string, Scapy decode +enc = cbor2.dumps(cbor2.CBORTag(100, b'\xde\xad\xbe\xef')) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_SEMANTIC_TAG) and obj.val[0] == 100 and obj.val[1].val == b'\xde\xad\xbe\xef' and remainder == b'' + ++ CBOR Interoperability - Half-Precision Floats (RFC 8949 vectors) + += Half-precision from RFC 8949: 0.0 +import cbor2 +data = bytes.fromhex('f90000') +obj, _ = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 0.0 + += Half-precision from RFC 8949: 1.0 +data = bytes.fromhex('f93c00') +obj, _ = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 1.0 + += Half-precision from RFC 8949: 1.5 +data = bytes.fromhex('f93e00') +obj, _ = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 1.5 + += Half-precision from RFC 8949: positive infinity +import math +data = bytes.fromhex('f97c00') +obj, _ = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isinf(obj.val) and obj.val > 0 + += Half-precision from RFC 8949: NaN +data = bytes.fromhex('f97e00') +obj, _ = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and math.isnan(obj.val) + += Scapy decode half-precision 1.5 agrees with cbor2 decode of double 1.5 +import cbor2 +half_data = bytes.fromhex('f93e00') +scapy_obj, _ = CBOR_Codecs.CBOR.dec(half_data) +double_data = bytes.fromhex('fb3ff8000000000000') +cbor2_val = cbor2.loads(double_data) +scapy_obj.val == cbor2_val + ++ CBOR Interoperability - Large Integers + += Large uint 18446744073709551615 bytes match cbor2 +import cbor2 +max_u64 = 18446744073709551615 +bytes(CBOR_UNSIGNED_INTEGER(max_u64)) == cbor2.dumps(max_u64) + += Large uint roundtrip Scapy to cbor2 to Scapy +max_u64 = 18446744073709551615 +scapy_enc = bytes(CBOR_UNSIGNED_INTEGER(max_u64)) +cbor2_val = cbor2.loads(scapy_enc) +cbor2_enc = cbor2.dumps(cbor2_val) +scapy_dec, _ = CBOR_Codecs.CBOR.dec(cbor2_enc) +scapy_dec.val == max_u64 + += Large negative int -18446744073709551616 roundtrip via cbor2 +neg_max = -18446744073709551616 +cbor2_enc = cbor2.dumps(neg_max) +scapy_dec, _ = CBOR_Codecs.CBOR.dec(cbor2_enc) +scapy_dec.val == neg_max + ++ CBOR Interoperability - Complex Nested Structures + += cbor2 deeply nested map: 3 levels, Scapy decode +import cbor2 +deep = {"level1": {"level2": {"level3": [1, 2, 3]}}} +enc = cbor2.dumps(deep) +obj, _ = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_MAP) and 'level1' in obj.val + += Scapy deeply nested array, cbor2 decode +from scapy.cbor.cborcodec import CBORcodec_ARRAY +enc = CBORcodec_ARRAY.enc([[1, [2, [3, [4]]]], 5]) +dec = cbor2.loads(enc) +dec == [[1, [2, [3, [4]]]], 5] + += cbor2 complex mixed structure: Scapy decodes it +import cbor2 +data = { + "name": "Alice", + "scores": [100, 95, 87], + "active": True, + "meta": {"created": 12345, "tag": "user"}, +} +enc = cbor2.dumps(data) +obj, _ = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_MAP) and 'name' in obj.val and 'scores' in obj.val + += Scapy encode complex structure, cbor2 decode, values match +from scapy.cbor.cborcodec import CBORcodec_MAP, CBORcodec_ARRAY +enc = CBORcodec_MAP.enc({ + "items": [1, 2, 3], + "count": 3, + "valid": True, +}) +dec = cbor2.loads(enc) +dec["items"] == [1, 2, 3] and dec["count"] == 3 and dec["valid"] is True + +########### CBORF Fields Interoperability Tests with cbor2 ############ + ++ CBORF Fields - Interop: CBORF_ARRAY packet to cbor2 + += CBORF_ARRAY packet to cbor2 list (version info) +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_UNSIGNED_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class VersionInfo(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_UNSIGNED_INTEGER('major', 1), + CBORF_UNSIGNED_INTEGER('minor', 2), + CBORF_UNSIGNED_INTEGER('patch', 3), + ) + +pkt = VersionInfo() +raw = bytes(pkt) +dec = cbor2.loads(raw) +isinstance(dec, list) and dec == [1, 2, 3] + += cbor2 list to CBORF_ARRAY packet +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class VersionInfo2(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_UNSIGNED_INTEGER('major', 0), + CBORF_UNSIGNED_INTEGER('minor', 0), + CBORF_UNSIGNED_INTEGER('patch', 0), + ) + +cbor2_data = cbor2.dumps([4, 5, 6]) +pkt = VersionInfo2(cbor2_data) +pkt.major.val == 4 and pkt.minor.val == 5 and pkt.patch.val == 6 + += CBORF_ARRAY packet roundtrip through cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class MsgPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('code', 200), + CBORF_TEXT_STRING('status', 'ok'), + ) + +pkt = MsgPkt() +raw = bytes(pkt) +cbor2_dec = cbor2.loads(raw) +cbor2_re_enc = cbor2.dumps(cbor2_dec) +pkt2 = MsgPkt(cbor2_re_enc) +pkt2.code.val == 200 and pkt2.status.val == 'ok' + += CBORF_ARRAY with boolean and null fields to cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_BOOLEAN, CBORF_NULL, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class FlagPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('id', 7), + CBORF_BOOLEAN('active', True), + CBORF_NULL('reserved'), + ) + +pkt = FlagPkt() +raw = bytes(pkt) +dec = cbor2.loads(raw) +dec[0] == 7 and dec[1] is True and dec[2] is None + += cbor2 list with mixed types to CBORF_ARRAY packet +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_BOOLEAN, CBORF_NULL +from scapy.cborpacket import CBOR_Packet + +class Mixed(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('num', 0), + CBORF_BOOLEAN('flag', False), + CBORF_NULL('nval'), + ) + +cbor2_data = cbor2.dumps([42, False, None]) +pkt = Mixed(cbor2_data) +pkt.num.val == 42 + ++ CBORF Fields - Interop: CBORF_MAP packet to cbor2 + += CBORF_MAP packet to cbor2 dict +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class ClaimSet(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('iss', 'scapy'), + CBORF_INTEGER('exp', 9999999), + ) + +pkt = ClaimSet() +raw = bytes(pkt) +dec = cbor2.loads(raw) +isinstance(dec, dict) and dec.get('iss') == 'scapy' and dec.get('exp') == 9999999 + += cbor2 dict to CBORF_MAP packet +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class Claims(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('iss', ''), + CBORF_INTEGER('exp', 0), + ) + +cbor2_data = cbor2.dumps({'iss': 'myapp', 'exp': 12345}) +pkt = Claims(cbor2_data) +pkt.iss.val == 'myapp' and pkt.exp.val == 12345 + += CBORF_MAP packet roundtrip through cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class BinHeader(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('alg', 'ES256'), + CBORF_BYTE_STRING('kid', b'\x01\x02\x03\x04'), + ) + +pkt = BinHeader() +raw = bytes(pkt) +cbor2_dec = cbor2.loads(raw) +cbor2_re_enc = cbor2.dumps(cbor2_dec) +pkt2 = BinHeader(cbor2_re_enc) +pkt2.alg.val == 'ES256' and pkt2.kid.val == b'\x01\x02\x03\x04' + += CBORF_MAP with boolean values to cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_BOOLEAN, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class Flags(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_BOOLEAN('enabled', True), + CBORF_INTEGER('count', 5), + ) + +pkt = Flags() +raw = bytes(pkt) +dec = cbor2.loads(raw) +dec.get('enabled') is True and dec.get('count') == 5 + += cbor2 dict with unknown keys: CBORF_MAP skips them +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class SimpleMap(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('known', 'default'), + ) + +cbor2_data = cbor2.dumps({'known': 'value', 'unknown': 'extra'}) +pkt = SimpleMap(cbor2_data) +pkt.known.val == 'value' + ++ CBORF Fields - Interop: CBORF_ARRAY_OF packet to cbor2 + += CBORF_ARRAY_OF with integer elements to cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_INTEGER +from scapy.cbor.cbor import CBOR_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class IntList(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_INTEGER) + +pkt = IntList() +pkt.items = [CBOR_UNSIGNED_INTEGER(10), CBOR_UNSIGNED_INTEGER(20), CBOR_UNSIGNED_INTEGER(30)] +raw = bytes(pkt) +dec = cbor2.loads(raw) +isinstance(dec, list) and dec == [10, 20, 30] + += cbor2 list to CBORF_ARRAY_OF +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class IntList2(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_INTEGER) + +cbor2_data = cbor2.dumps([100, 200, 300]) +pkt = IntList2(cbor2_data) +len(pkt.items) == 3 and pkt.items[0].val == 100 and pkt.items[2].val == 300 + ++ CBORF Fields - Interop: CBORF_SEMANTIC_TAG to cbor2 + += CBORF_SEMANTIC_TAG packet to cbor2 CBORTag +import cbor2 +from scapy.cbor.cborfields import CBORF_SEMANTIC_TAG, CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class TimestampPkt(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG('tag_info', None, 1, CBORF_UNSIGNED_INTEGER('ts', 1363896240)) + +pkt = TimestampPkt() +raw = bytes(pkt) +import datetime +dec = cbor2.loads(raw) +isinstance(dec, (cbor2.CBORTag, datetime.datetime, datetime.date)) + += cbor2 CBORTag (tag 42) decoded by Scapy CBOR_SEMANTIC_TAG +import cbor2 +enc = cbor2.dumps(cbor2.CBORTag(42, 'tagged-value')) +obj, remainder = CBOR_Codecs.CBOR.dec(enc) +isinstance(obj, CBOR_SEMANTIC_TAG) and obj.val[0] == 42 and obj.val[1].val == 'tagged-value' and remainder == b'' + += CBORF_SEMANTIC_TAG bytes identical to cbor2 CBORTag bytes +import cbor2 +scapy_enc = bytes(CBOR_SEMANTIC_TAG((42, CBOR_TEXT_STRING('tagged-value')))) +cbor2_enc = cbor2.dumps(cbor2.CBORTag(42, 'tagged-value')) +scapy_enc == cbor2_enc + ++ CBORF Fields - Interop: CBORF_UNSIGNED_INTEGER with cbor2 + += CBORF_UNSIGNED_INTEGER boundary values - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class UIntPkt(CBOR_Packet): + CBOR_root = CBORF_UNSIGNED_INTEGER('n', 0) + +results = [] +for val in [0, 23, 24, 255, 256, 65535, 65536, 4294967295, 4294967296, 18446744073709551615]: + pkt = UIntPkt() + pkt.n.val = val + dec = cbor2.loads(bytes(pkt)) + results.append(dec == val) + +all(results) + += CBORF_UNSIGNED_INTEGER boundary values - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class UIntPkt2(CBOR_Packet): + CBOR_root = CBORF_UNSIGNED_INTEGER('n', 0) + +results = [] +for val in [0, 23, 24, 255, 256, 65535, 65536, 4294967295, 4294967296]: + pkt = UIntPkt2(cbor2.dumps(val)) + results.append(pkt.n.val == val) + +all(results) + += CBORF_UNSIGNED_INTEGER byte-exact comparison with cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_UNSIGNED_INTEGER +from scapy.cborpacket import CBOR_Packet + +class UIntExact(CBOR_Packet): + CBOR_root = CBORF_UNSIGNED_INTEGER('n', 0) + +results = [] +for val in [0, 1, 10, 23, 24, 255, 256, 65535, 65536, 4294967295]: + pkt = UIntExact() + pkt.n.val = val + results.append(bytes(pkt) == cbor2.dumps(val)) + +all(results) + ++ CBORF Fields - Interop: CBORF_NEGATIVE_INTEGER with cbor2 + += CBORF_NEGATIVE_INTEGER boundary values - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_NEGATIVE_INTEGER +from scapy.cborpacket import CBOR_Packet + +class NIntPkt(CBOR_Packet): + CBOR_root = CBORF_NEGATIVE_INTEGER('n', -1) + +results = [] +for val in [-1, -24, -25, -256, -257, -65536, -65537, -4294967296, -4294967297]: + pkt = NIntPkt() + pkt.n.val = val + dec = cbor2.loads(bytes(pkt)) + results.append(dec == val) + +all(results) + += CBORF_NEGATIVE_INTEGER boundary values - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_NEGATIVE_INTEGER +from scapy.cborpacket import CBOR_Packet + +class NIntPkt2(CBOR_Packet): + CBOR_root = CBORF_NEGATIVE_INTEGER('n', -1) + +results = [] +for val in [-1, -24, -25, -256, -257, -65536, -4294967296]: + pkt = NIntPkt2(cbor2.dumps(val)) + results.append(pkt.n.val == val) + +all(results) + += CBORF_NEGATIVE_INTEGER byte-exact comparison with cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_NEGATIVE_INTEGER +from scapy.cborpacket import CBOR_Packet + +class NIntExact(CBOR_Packet): + CBOR_root = CBORF_NEGATIVE_INTEGER('n', -1) + +results = [] +for val in [-1, -10, -24, -25, -256, -257, -65536, -65537]: + pkt = NIntExact() + pkt.n.val = val + results.append(bytes(pkt) == cbor2.dumps(val)) + +all(results) + ++ CBORF Fields - Interop: CBORF_INTEGER with cbor2 + += CBORF_INTEGER positive values - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class IntPkt(CBOR_Packet): + CBOR_root = CBORF_INTEGER('n', 0) + +results = [] +for val in [0, 1, 42, 100, 1000, 1000000]: + pkt = IntPkt() + pkt.n.val = val + dec = cbor2.loads(bytes(pkt)) + results.append(dec == val) + +all(results) + += CBORF_INTEGER negative values - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class IntNegPkt(CBOR_Packet): + CBOR_root = CBORF_INTEGER('n', -1) + +results = [] +for val in [-1, -10, -100, -1000, -1000000]: + pkt = IntNegPkt() + pkt.n.val = val + dec = cbor2.loads(bytes(pkt)) + results.append(dec == val) + +all(results) + += CBORF_INTEGER - cbor2 encode positive and negative, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class IntPkt2(CBOR_Packet): + CBOR_root = CBORF_INTEGER('n', 0) + +results = [] +for val in [0, 42, -1, -42, 255, -256, 65536, -65537]: + pkt = IntPkt2(cbor2.dumps(val)) + results.append(pkt.n.val == val) + +all(results) + ++ CBORF Fields - Interop: CBORF_BYTE_STRING with cbor2 + += CBORF_BYTE_STRING empty bytes - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class BytePkt(CBOR_Packet): + CBOR_root = CBORF_BYTE_STRING('data', b'') + +pkt = BytePkt() +dec = cbor2.loads(bytes(pkt)) +dec == b'' + += CBORF_BYTE_STRING all 256 byte values - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class ByteAllPkt(CBOR_Packet): + CBOR_root = CBORF_BYTE_STRING('data', b'') + +pkt = ByteAllPkt() +pkt.data.val = bytes(range(256)) +dec = cbor2.loads(bytes(pkt)) +dec == bytes(range(256)) + += CBORF_BYTE_STRING - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class BytePkt3(CBOR_Packet): + CBOR_root = CBORF_BYTE_STRING('data', b'') + +for raw_val in [b'', b'\xde\xad\xbe\xef', bytes(range(256))]: + pkt = BytePkt3(cbor2.dumps(raw_val)) + assert pkt.data.val == raw_val + +True + += CBORF_BYTE_STRING byte-exact comparison with cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class ByteExact(CBOR_Packet): + CBOR_root = CBORF_BYTE_STRING('data', b'') + +results = [] +for raw_val in [b'', b'\x00', b'\xff', b'\xde\xad\xbe\xef', b'hello']: + pkt = ByteExact() + pkt.data.val = raw_val + results.append(bytes(pkt) == cbor2.dumps(raw_val)) + +all(results) + ++ CBORF Fields - Interop: CBORF_TEXT_STRING with cbor2 + += CBORF_TEXT_STRING empty string - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextPkt(CBOR_Packet): + CBOR_root = CBORF_TEXT_STRING('txt', '') + +pkt = TextPkt() +dec = cbor2.loads(bytes(pkt)) +dec == '' + += CBORF_TEXT_STRING ASCII string - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextPkt2(CBOR_Packet): + CBOR_root = CBORF_TEXT_STRING('txt', '') + +pkt = TextPkt2() +pkt.txt.val = 'Hello, World!' +dec = cbor2.loads(bytes(pkt)) +dec == 'Hello, World!' + += CBORF_TEXT_STRING unicode string - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextUniPkt(CBOR_Packet): + CBOR_root = CBORF_TEXT_STRING('txt', '') + +pkt = TextUniPkt() +pkt.txt.val = u'Hello, \u4e16\u754c' +dec = cbor2.loads(bytes(pkt)) +dec == u'Hello, \u4e16\u754c' + += CBORF_TEXT_STRING - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextPkt3(CBOR_Packet): + CBOR_root = CBORF_TEXT_STRING('txt', '') + +for s in ['', 'hello', 'Hello, World!', u'caf\u00e9', u'\u4e16\u754c']: + pkt = TextPkt3(cbor2.dumps(s)) + assert pkt.txt.val == s + +True + += CBORF_TEXT_STRING byte-exact comparison with cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextExact(CBOR_Packet): + CBOR_root = CBORF_TEXT_STRING('txt', '') + +results = [] +for s in ['', 'a', 'hello', 'IETF', u'\u6c34']: + pkt = TextExact() + pkt.txt.val = s + results.append(bytes(pkt) == cbor2.dumps(s)) + +all(results) + ++ CBORF Fields - Interop: CBORF_BOOLEAN with cbor2 + += CBORF_BOOLEAN true - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class BoolPkt(CBOR_Packet): + CBOR_root = CBORF_BOOLEAN('flag', True) + +pkt = BoolPkt() +dec = cbor2.loads(bytes(pkt)) +dec is True + += CBORF_BOOLEAN false - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class BoolFalsePkt(CBOR_Packet): + CBOR_root = CBORF_BOOLEAN('flag', False) + +pkt = BoolFalsePkt() +dec = cbor2.loads(bytes(pkt)) +dec is False + += CBORF_BOOLEAN - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class BoolPkt2(CBOR_Packet): + CBOR_root = CBORF_BOOLEAN('flag', False) + +pkt_true = BoolPkt2(cbor2.dumps(True)) +pkt_false = BoolPkt2(cbor2.dumps(False)) +pkt_true.flag.val is True and pkt_false.flag.val is False + += CBORF_BOOLEAN byte-exact comparison with cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class BoolExactTrue(CBOR_Packet): + CBOR_root = CBORF_BOOLEAN('flag', True) + +class BoolExactFalse(CBOR_Packet): + CBOR_root = CBORF_BOOLEAN('flag', False) + +pkt_t = BoolExactTrue() +pkt_f = BoolExactFalse() +bytes(pkt_t) == cbor2.dumps(True) and bytes(pkt_f) == cbor2.dumps(False) + ++ CBORF Fields - Interop: CBORF_NULL with cbor2 + += CBORF_NULL - Scapy encode, cbor2 decode gives None +import cbor2 +from scapy.cbor.cborfields import CBORF_NULL +from scapy.cborpacket import CBOR_Packet + +class NullPkt(CBOR_Packet): + CBOR_root = CBORF_NULL('n') + +pkt = NullPkt() +dec = cbor2.loads(bytes(pkt)) +dec is None + += CBORF_NULL byte-exact comparison with cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_NULL +from scapy.cborpacket import CBOR_Packet + +class NullExact(CBOR_Packet): + CBOR_root = CBORF_NULL('n') + +pkt = NullExact() +bytes(pkt) == cbor2.dumps(None) + += CBORF_NULL - cbor2 None encode, Scapy decode gives CBOR_NULL +import cbor2 +from scapy.cbor.cbor import CBOR_NULL +from scapy.cbor.cborfields import CBORF_NULL +from scapy.cborpacket import CBOR_Packet + +class NullPkt2(CBOR_Packet): + CBOR_root = CBORF_NULL('n') + +pkt = NullPkt2(cbor2.dumps(None)) +isinstance(pkt.n, CBOR_NULL) + ++ CBORF Fields - Interop: CBORF_FLOAT with cbor2 + += CBORF_FLOAT basic values - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_FLOAT +from scapy.cborpacket import CBOR_Packet + +class FloatPkt(CBOR_Packet): + CBOR_root = CBORF_FLOAT('f', 0.0) + +results = [] +for val in [0.0, 1.0, -1.0, 3.14159, 1e10, -2.5]: + pkt = FloatPkt() + pkt.f.val = val + dec = cbor2.loads(bytes(pkt)) + results.append(dec == val) + +all(results) + += CBORF_FLOAT special values (NaN, Inf, -Inf) - Scapy encode, cbor2 decode +import cbor2, math +from scapy.cbor.cborfields import CBORF_FLOAT +from scapy.cborpacket import CBOR_Packet + +class FloatSpecialPkt(CBOR_Packet): + CBOR_root = CBORF_FLOAT('f', 0.0) + +pkt_nan = FloatSpecialPkt() +pkt_nan.f.val = float('nan') +raw_nan = bytes(pkt_nan) +pkt_inf = FloatSpecialPkt() +pkt_inf.f.val = float('inf') +raw_inf = bytes(pkt_inf) +pkt_ninf = FloatSpecialPkt() +pkt_ninf.f.val = float('-inf') +raw_ninf = bytes(pkt_ninf) +math.isnan(cbor2.loads(raw_nan)) and math.isinf(cbor2.loads(raw_inf)) and cbor2.loads(raw_ninf) == float('-inf') + += CBORF_FLOAT special values - cbor2 encode, Scapy decode +import cbor2, math +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_FLOAT +from scapy.cborpacket import CBOR_Packet + +class FloatArrPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_FLOAT('nan_val', 0.0), + CBORF_FLOAT('inf_val', 0.0), + CBORF_FLOAT('ninf_val', 0.0), + ) + +pkt = FloatArrPkt(cbor2.dumps([float('nan'), float('inf'), float('-inf')])) +math.isnan(pkt.nan_val.val) and math.isinf(pkt.inf_val.val) and pkt.ninf_val.val == float('-inf') + += CBORF_FLOAT - cbor2 encode, Scapy decode roundtrip +import cbor2 +from scapy.cbor.cborfields import CBORF_FLOAT +from scapy.cborpacket import CBOR_Packet + +class FloatPkt2(CBOR_Packet): + CBOR_root = CBORF_FLOAT('f', 0.0) + +results = [] +for val in [0.0, 1.0, -1.0, 2.5, 100.0]: + pkt = FloatPkt2(cbor2.dumps(val)) + results.append(pkt.f.val == val) + +all(results) + ++ CBORF Fields - Interop: CBORF_ARRAY with cbor2 + += CBORF_ARRAY with integer fields - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class PointPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('x', 10), + CBORF_INTEGER('y', 20), + CBORF_INTEGER('z', 30), + ) + +pkt = PointPkt() +dec = cbor2.loads(bytes(pkt)) +dec == [10, 20, 30] + += CBORF_ARRAY with mixed types - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class MixedPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('id', 99), + CBORF_TEXT_STRING('label', 'test'), + CBORF_BOOLEAN('active', True), + ) + +pkt = MixedPkt() +dec = cbor2.loads(bytes(pkt)) +dec[0] == 99 and dec[1] == 'test' and dec[2] is True + += CBORF_ARRAY - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class RecordPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('code', 0), + CBORF_TEXT_STRING('msg', ''), + ) + +pkt = RecordPkt(cbor2.dumps([200, 'OK'])) +pkt.code.val == 200 and pkt.msg.val == 'OK' + += CBORF_ARRAY roundtrip through cbor2 - multiple encode/decode cycles +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class RTPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('seq', 1), + CBORF_TEXT_STRING('data', 'payload'), + ) + +pkt = RTPkt() +raw = bytes(pkt) +cbor2_dec = cbor2.loads(raw) +re_enc = cbor2.dumps(cbor2_dec) +pkt2 = RTPkt(re_enc) +pkt2.seq.val == 1 and pkt2.data.val == 'payload' + += CBORF_ARRAY with null elements - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_NULL +from scapy.cborpacket import CBOR_Packet + +class NullArrPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('id', 5), + CBORF_NULL('opt'), + ) + +pkt = NullArrPkt() +dec = cbor2.loads(bytes(pkt)) +dec[0] == 5 and dec[1] is None + ++ CBORF Fields - Interop: CBORF_ARRAY_OF with cbor2 + += CBORF_ARRAY_OF with text strings - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cbor import CBOR_TEXT_STRING +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextListPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_TEXT_STRING) + +pkt = TextListPkt(cbor2.dumps(['hello', 'world', 'foo'])) +len(pkt.items) == 3 and pkt.items[0].val == 'hello' and pkt.items[2].val == 'foo' + += CBORF_ARRAY_OF with text strings - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cbor import CBOR_TEXT_STRING +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextListPkt2(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_TEXT_STRING) + +pkt = TextListPkt2() +pkt.items = [CBOR_TEXT_STRING('abc'), CBOR_TEXT_STRING('def'), CBOR_TEXT_STRING('ghi')] +dec = cbor2.loads(bytes(pkt)) +dec == ['abc', 'def', 'ghi'] + += CBORF_ARRAY_OF with text strings roundtrip through cbor2 +import cbor2 +from scapy.cbor.cbor import CBOR_TEXT_STRING +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class TextListRT(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_TEXT_STRING) + +pkt = TextListRT() +pkt.items = [CBOR_TEXT_STRING('x'), CBOR_TEXT_STRING('y'), CBOR_TEXT_STRING('z')] +raw = bytes(pkt) +re_enc = cbor2.dumps(cbor2.loads(raw)) +pkt2 = TextListRT(re_enc) +len(pkt2.items) == 3 and pkt2.items[1].val == 'y' + += CBORF_ARRAY_OF with byte strings - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cbor import CBOR_BYTE_STRING +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class ByteListPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_BYTE_STRING) + +pkt = ByteListPkt(cbor2.dumps([b'\x01\x02', b'\x03\x04', b'\x05\x06'])) +len(pkt.items) == 3 and pkt.items[0].val == b'\x01\x02' and pkt.items[2].val == b'\x05\x06' + += CBORF_ARRAY_OF with byte strings - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cbor import CBOR_BYTE_STRING +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class ByteListPkt2(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_BYTE_STRING) + +pkt = ByteListPkt2() +pkt.items = [CBOR_BYTE_STRING(b'\xaa\xbb'), CBOR_BYTE_STRING(b'\xcc\xdd')] +dec = cbor2.loads(bytes(pkt)) +dec == [b'\xaa\xbb', b'\xcc\xdd'] + += CBORF_ARRAY_OF integers - large list cbor2 roundtrip +import cbor2 +from scapy.cbor.cbor import CBOR_UNSIGNED_INTEGER +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class BigIntList(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_INTEGER) + +cbor2_data = cbor2.dumps(list(range(50))) +pkt = BigIntList(cbor2_data) +len(pkt.items) == 50 and pkt.items[0].val == 0 and pkt.items[49].val == 49 + += CBORF_ARRAY_OF integers - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cbor import CBOR_UNSIGNED_INTEGER +from scapy.cbor.cborfields import CBORF_ARRAY_OF, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class IntListPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], CBORF_INTEGER) + +pkt = IntListPkt() +pkt.items = [CBOR_UNSIGNED_INTEGER(i) for i in [10, 20, 30, 40, 50]] +dec = cbor2.loads(bytes(pkt)) +dec == [10, 20, 30, 40, 50] + ++ CBORF Fields - Interop: CBORF_MAP with cbor2 + += CBORF_MAP with text string values - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class HeaderPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('alg', 'ES256'), + CBORF_TEXT_STRING('typ', 'JWT'), + CBORF_INTEGER('ver', 1), + ) + +pkt = HeaderPkt() +dec = cbor2.loads(bytes(pkt)) +isinstance(dec, dict) and dec.get('alg') == 'ES256' and dec.get('typ') == 'JWT' and dec.get('ver') == 1 + += CBORF_MAP - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_INTEGER, CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class CredPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('sub', ''), + CBORF_INTEGER('iat', 0), + CBORF_BOOLEAN('admin', False), + ) + +pkt = CredPkt(cbor2.dumps({'sub': 'user42', 'iat': 1700000000, 'admin': True})) +pkt.sub.val == 'user42' and pkt.iat.val == 1700000000 and pkt.admin.val is True + += CBORF_MAP roundtrip through cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_BYTE_STRING, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class CoseHeaderPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('alg', 'ES256'), + CBORF_BYTE_STRING('kid', b'\x01\x02\x03\x04'), + CBORF_INTEGER('crit', 1), + ) + +pkt = CoseHeaderPkt() +raw = bytes(pkt) +re_enc = cbor2.dumps(cbor2.loads(raw)) +pkt2 = CoseHeaderPkt(re_enc) +pkt2.alg.val == 'ES256' and pkt2.kid.val == b'\x01\x02\x03\x04' and pkt2.crit.val == 1 + += CBORF_MAP with null value - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_NULL +from scapy.cborpacket import CBOR_Packet + +class OptionalPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('id', 7), + CBORF_NULL('optional_data'), + ) + +pkt = OptionalPkt() +dec = cbor2.loads(bytes(pkt)) +dec.get('id') == 7 and dec.get('optional_data') is None + += CBORF_MAP with boolean values roundtrip with cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_BOOLEAN, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class FlagsPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_BOOLEAN('active', True), + CBORF_BOOLEAN('verified', False), + CBORF_INTEGER('level', 3), + CBORF_TEXT_STRING('role', 'admin'), + ) + +pkt = FlagsPkt() +raw = bytes(pkt) +dec = cbor2.loads(raw) +re_enc = cbor2.dumps(dec) +pkt2 = FlagsPkt(re_enc) +pkt2.active.val is True and pkt2.verified.val is False and pkt2.level.val == 3 and pkt2.role.val == 'admin' + += CBORF_MAP skip unknown keys from cbor2 +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_INTEGER +from scapy.cborpacket import CBOR_Packet + +class KnownKeysPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('known', 'default'), + CBORF_INTEGER('count', 0), + ) + +pkt = KnownKeysPkt(cbor2.dumps({'known': 'found', 'count': 42, 'extra': 'ignored'})) +pkt.known.val == 'found' and pkt.count.val == 42 + ++ CBORF Fields - Interop: CBOR_Packet complex structures with cbor2 + += CBOR_Packet CBORF_ARRAY with multiple field types - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class SensorReading(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('sensor_id', 42), + CBORF_TEXT_STRING('unit', 'fahrenheit'), + CBORF_INTEGER('value', 98), + CBORF_BOOLEAN('alarm', True), + ) + +pkt = SensorReading() +dec = cbor2.loads(bytes(pkt)) +dec[0] == 42 and dec[1] == 'fahrenheit' and dec[2] == 98 and dec[3] is True + += CBOR_Packet with CBORF_MAP multiple field types - Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BOOLEAN, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class DeviceInfo(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('id', 0), + CBORF_TEXT_STRING('label', ''), + CBORF_BOOLEAN('online', False), + CBORF_BYTE_STRING('hwaddr', b''), + ) + +pkt = DeviceInfo(cbor2.dumps({'id': 1001, 'label': 'device-01', 'online': True, 'hwaddr': b'\x00\x11\x22\x33\x44\x55'})) +dec = cbor2.loads(bytes(pkt)) +dec.get('id') == 1001 and dec.get('label') == 'device-01' and dec.get('online') is True and dec.get('hwaddr') == b'\x00\x11\x22\x33\x44\x55' + += CBOR_Packet CBORF_MAP full cbor2 roundtrip +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BOOLEAN +from scapy.cborpacket import CBOR_Packet + +class ClaimsPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('iss', ''), + CBORF_TEXT_STRING('sub', ''), + CBORF_INTEGER('exp', 0), + CBORF_BOOLEAN('admin', False), + ) + +pkt = ClaimsPkt(cbor2.dumps({'iss': 'auth.example.com', 'sub': 'user99', 'exp': 9999999, 'admin': False})) +raw = bytes(pkt) +dec = cbor2.loads(raw) +dec.get('iss') == 'auth.example.com' and dec.get('sub') == 'user99' and dec.get('exp') == 9999999 and dec.get('admin') is False + += CBOR_Packet CBORF_MAP with negative integer - cbor2 encode, Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class OffsetPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('name', ''), + CBORF_INTEGER('offset', 0), + CBORF_INTEGER('count', 0), + ) + +pkt = OffsetPkt(cbor2.dumps({'name': 'delta', 'offset': -1024, 'count': 512})) +pkt.name.val == 'delta' and pkt.offset.val == -1024 and pkt.count.val == 512 + ++ CBOR_Packet - nested CBORF_PACKET structures + += CBORF_PACKET three levels deep: Outer(ARRAY) -> Middle(ARRAY) -> Inner(ARRAY) +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class NestInner(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('x', 0), + CBORF_INTEGER('y', 0), + ) + +class NestMiddle(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_TEXT_STRING('zone', ''), + CBORF_PACKET('point', None, NestInner), + ) + +class NestOuter(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('version', 0), + CBORF_PACKET('region', None, NestMiddle), + ) + +inner = NestInner(cbor2.dumps([30, 40])) +mid = NestMiddle() +mid.zone.val = 'north' +mid.point = inner +outer = NestOuter() +outer.version.val = 2 +outer.region = mid +raw = bytes(outer) +outer2 = NestOuter(raw) +outer2.version.val == 2 and outer2.region.zone.val == 'north' and outer2.region.point.x.val == 30 and outer2.region.point.y.val == 40 + += CBORF_PACKET three-level nesting cbor2 interop +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class NestInner2(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('x', 0), + CBORF_INTEGER('y', 0), + ) + +class NestMiddle2(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_TEXT_STRING('zone', ''), + CBORF_PACKET('point', None, NestInner2), + ) + +class NestOuter2(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('version', 0), + CBORF_PACKET('region', None, NestMiddle2), + ) + +inner = NestInner2(cbor2.dumps([10, 20])) +mid = NestMiddle2() +mid.zone.val = 'south' +mid.point = inner +outer = NestOuter2() +outer.version.val = 1 +outer.region = mid +dec = cbor2.loads(bytes(outer)) +dec == [1, ['south', [10, 20]]] + += CBORF_PACKET inside CBORF_MAP: cbor2 decode matches field values +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_MAP, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class MapInner(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('px', 0), + CBORF_INTEGER('py', 0), + ) + +class MapWithNestedPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('label', ''), + CBORF_PACKET('coords', None, MapInner), + ) + +inner = MapInner(cbor2.dumps([5, 7])) +pkt = MapWithNestedPkt() +pkt.label.val = 'origin' +pkt.coords = inner +dec = cbor2.loads(bytes(pkt)) +dec.get('label') == 'origin' and dec.get('coords') == [5, 7] + += CBORF_PACKET inside CBORF_MAP: Scapy decode roundtrip +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_MAP, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class CoordsInner(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('px', 0), + CBORF_INTEGER('py', 0), + ) + +class CoordsOuter(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('label', ''), + CBORF_PACKET('coords', None, CoordsInner), + ) + +inner = CoordsInner(cbor2.dumps([5, 7])) +pkt = CoordsOuter() +pkt.label.val = 'origin' +pkt.coords = inner +pkt2 = CoordsOuter(bytes(pkt)) +pkt2.label.val == 'origin' and pkt2.coords.px.val == 5 and pkt2.coords.py.val == 7 + += CBORF_PACKET: nested MAP-in-MAP via CBORF_PACKET (Document/Metadata) +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_BYTE_STRING, CBORF_INTEGER, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class DocMeta(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('creator', ''), + CBORF_INTEGER('version', 0), + ) + +class DocPacket(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('title', ''), + CBORF_BYTE_STRING('body', b''), + CBORF_PACKET('metadata', None, DocMeta), + ) + +meta = DocMeta() +meta.creator.val = 'alice' +meta.version.val = 3 +doc = DocPacket() +doc.title.val = 'My Document' +doc.body.val = b'hello world' +doc.metadata = meta +raw = bytes(doc) +dec = cbor2.loads(raw) +dec.get('title') == 'My Document' and dec.get('body') == b'hello world' and dec.get('metadata') == {'creator': 'alice', 'version': 3} + += CBORF_PACKET: nested MAP-in-MAP Scapy roundtrip +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_BYTE_STRING, CBORF_INTEGER, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class DocMeta2(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('creator', ''), + CBORF_INTEGER('version', 0), + ) + +class DocPacket2(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('title', ''), + CBORF_BYTE_STRING('body', b''), + CBORF_PACKET('metadata', None, DocMeta2), + ) + +meta = DocMeta2() +meta.creator.val = 'bob' +meta.version.val = 7 +doc = DocPacket2() +doc.title.val = 'Report' +doc.body.val = b'\x01\x02\x03' +doc.metadata = meta +raw = bytes(doc) +doc2 = DocPacket2(raw) +doc2.title.val == 'Report' and doc2.body.val == b'\x01\x02\x03' and doc2.metadata.creator.val == 'bob' and doc2.metadata.version.val == 7 + ++ CBOR_Packet - CBORF_ARRAY_OF with CBOR_Packet elements + += CBORF_ARRAY_OF with CBOR_Packet class: cbor2 list of lists → Scapy decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_ARRAY_OF +from scapy.cborpacket import CBOR_Packet + +class StatusItem(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('code', 0), + CBORF_TEXT_STRING('msg', ''), + ) + +class StatusList(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('statuses', [], StatusItem) + +raw = cbor2.dumps([[200, 'OK'], [201, 'Created'], [204, 'No Content']]) +pkt = StatusList(raw) +len(pkt.statuses) == 3 and pkt.statuses[0].code.val == 200 and pkt.statuses[1].msg.val == 'Created' and pkt.statuses[2].code.val == 204 + += CBORF_ARRAY_OF with CBOR_Packet class: Scapy encode → cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_ARRAY_OF +from scapy.cborpacket import CBOR_Packet + +class ErrItem(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('code', 0), + CBORF_TEXT_STRING('msg', ''), + ) + +class ErrList(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('errors', [], ErrItem) + +pkt = ErrList() +pkt.errors = [ErrItem(cbor2.dumps([404, 'Not Found'])), ErrItem(cbor2.dumps([500, 'Server Error']))] +dec = cbor2.loads(bytes(pkt)) +dec == [[404, 'Not Found'], [500, 'Server Error']] + += CBORF_ARRAY_OF with CBOR_Packet class: roundtrip +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_ARRAY_OF +from scapy.cborpacket import CBOR_Packet + +class MsgItem(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('id', 0), + CBORF_TEXT_STRING('txt', ''), + ) + +class MsgList(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('messages', [], MsgItem) + +raw = cbor2.dumps([[1, 'hello'], [2, 'world'], [3, 'foo']]) +pkt = MsgList(raw) +raw2 = bytes(pkt) +pkt2 = MsgList(raw2) +len(pkt2.messages) == 3 and pkt2.messages[2].id.val == 3 and pkt2.messages[2].txt.val == 'foo' + += CBORF_ARRAY_OF with CBOR_Packet class: empty list +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_ARRAY_OF +from scapy.cborpacket import CBOR_Packet + +class EmptyItem(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('val', 0), + ) + +class EmptyItemList(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('items', [], EmptyItem) + +pkt = EmptyItemList() +raw = bytes(pkt) +dec = cbor2.loads(raw) +dec == [] and len(EmptyItemList(raw).items) == 0 + += CBORF_ARRAY_OF with CBOR_Packet class inside CBORF_MAP +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_ARRAY_OF, CBORF_MAP, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class EventItem(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_TEXT_STRING('evt', ''), + CBORF_INTEGER('ts', 0), + ) + +class EventLog(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('events', [], EventItem) + +class Report(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('source', ''), + CBORF_INTEGER('count', 0), + CBORF_PACKET('log', None, EventLog), + ) + +log = EventLog() +log.events = [EventItem(cbor2.dumps(['boot', 1000])), EventItem(cbor2.dumps(['login', 2000]))] +rpt = Report() +rpt.source.val = 'sensor-1' +rpt.count.val = 2 +rpt.log = log +raw = bytes(rpt) +dec = cbor2.loads(raw) +dec.get('source') == 'sensor-1' and dec.get('count') == 2 and dec.get('log') == [['boot', 1000], ['login', 2000]] + ++ CBOR_Packet - CBORF_optional extended tests + += CBORF_optional: type mismatch in CBORF_ARRAY sets field to None +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_optional +from scapy.cborpacket import CBOR_Packet + +class TwoFieldPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('version', 0), + CBORF_optional(CBORF_TEXT_STRING('description', 'none')), + ) + +raw = cbor2.dumps([7, 99]) +pkt = TwoFieldPkt(raw) +pkt.version.val == 7 and pkt.description is None + += CBORF_optional: correct type present is decoded normally +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_optional +from scapy.cborpacket import CBOR_Packet + +class OptPresentPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('version', 0), + CBORF_optional(CBORF_TEXT_STRING('description', '')), + ) + +raw = cbor2.dumps([3, 'hello world']) +pkt = OptPresentPkt(raw) +pkt.version.val == 3 and pkt.description.val == 'hello world' + += CBORF_optional: encode and decode roundtrip with present field +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_optional +from scapy.cborpacket import CBOR_Packet + +class OptRTPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('version', 1), + CBORF_optional(CBORF_TEXT_STRING('title', '')), + ) + +pkt = OptRTPkt() +pkt.title.val = 'test title' +raw = bytes(pkt) +pkt2 = OptRTPkt(raw) +pkt2.version.val == 1 and pkt2.title.val == 'test title' + += CBORF_optional: cbor2 interop - optional present +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_optional +from scapy.cborpacket import CBOR_Packet + +class OptInteropPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('seq', 0), + CBORF_optional(CBORF_TEXT_STRING('note', '')), + ) + +pkt = OptInteropPkt() +pkt.note.val = 'cbor2 interop' +dec = cbor2.loads(bytes(pkt)) +dec == [0, 'cbor2 interop'] + += CBORF_optional inside CBORF_MAP: key present in cbor2 dict is decoded +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_optional +from scapy.cborpacket import CBOR_Packet + +class ConfigWithOpt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('timeout', 30), + CBORF_optional(CBORF_TEXT_STRING('endpoint', '')), + CBORF_INTEGER('retries', 3), + ) + +pkt = ConfigWithOpt(cbor2.dumps({'timeout': 60, 'endpoint': 'https://example.com', 'retries': 5})) +pkt.timeout.val == 60 and pkt.endpoint.val == 'https://example.com' and pkt.retries.val == 5 + += CBORF_optional inside CBORF_MAP: missing key stays at default +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_optional +from scapy.cborpacket import CBOR_Packet + +class ConfigNoOpt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('timeout', 30), + CBORF_optional(CBORF_TEXT_STRING('endpoint', '')), + CBORF_INTEGER('retries', 3), + ) + +pkt = ConfigNoOpt(cbor2.dumps({'timeout': 15, 'retries': 2})) +pkt.timeout.val == 15 and pkt.retries.val == 2 + ++ CBOR_Packet - CBORF_SEMANTIC_TAG extended tests + += CBORF_SEMANTIC_TAG with TEXT_STRING inner: Scapy encode, cbor2 decode as datetime +import cbor2, datetime +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_TEXT_STRING, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class DatetimePkt(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG('tag', None, 0, CBORF_TEXT_STRING('dt', '')) + +pkt = DatetimePkt() +pkt.dt.val = '2023-01-15T12:00:00Z' +dec = cbor2.loads(bytes(pkt)) +isinstance(dec, datetime.datetime) + += CBORF_SEMANTIC_TAG with INTEGER inner: Scapy encode, cbor2 decode as datetime +import cbor2, datetime +from scapy.cbor.cborfields import CBORF_INTEGER, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class UnixTimePkt(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG('tag', None, 1, CBORF_INTEGER('ts', 0)) + +pkt = UnixTimePkt() +pkt.ts.val = 1700000000 +dec = cbor2.loads(bytes(pkt)) +isinstance(dec, datetime.datetime) + += CBORF_SEMANTIC_TAG roundtrip: Scapy encode → Scapy decode preserves inner value +import cbor2 +from scapy.cbor.cborfields import CBORF_INTEGER, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class TagRTPkt(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG('tag', None, 1, CBORF_INTEGER('ts', 0)) + +pkt = TagRTPkt() +pkt.ts.val = 1700000000 +raw = bytes(pkt) +pkt2 = TagRTPkt(raw) +pkt2.ts.val == 1700000000 + += CBORF_SEMANTIC_TAG: tag byte matches CBOR major type 6 encoding +import cbor2 +from scapy.cbor.cborfields import CBORF_BYTE_STRING, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class TagBigNum(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG('tag', None, 2, CBORF_BYTE_STRING('n', b'')) + +pkt = TagBigNum() +pkt.n.val = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00' +raw = bytes(pkt) +raw[0:1] == b'\xc2' + += CBORF_SEMANTIC_TAG: byte-exact comparison with cbor2 CBORTag +import cbor2 +from scapy.cbor.cborfields import CBORF_INTEGER, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class TagCmpPkt(CBOR_Packet): + CBOR_root = CBORF_SEMANTIC_TAG('tag', None, 1, CBORF_INTEGER('ts', 0)) + +pkt = TagCmpPkt() +pkt.ts.val = 9999999 +bytes(pkt) == cbor2.dumps(cbor2.CBORTag(1, 9999999)) + += CBORF_SEMANTIC_TAG inside CBORF_MAP: Scapy encode, cbor2 decode +import cbor2, datetime +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_INTEGER, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class EventPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('event_type', ''), + CBORF_SEMANTIC_TAG('tag', None, 1, CBORF_INTEGER('ts', 0)), + ) + +pkt = EventPkt() +pkt.event_type.val = 'login' +pkt.ts.val = 9999999 +dec = cbor2.loads(bytes(pkt)) +isinstance(dec, dict) and dec.get('event_type') == 'login' and isinstance(dec.get('tag'), datetime.datetime) + += CBORF_SEMANTIC_TAG inside CBORF_MAP: Scapy roundtrip preserves inner value +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_TEXT_STRING, CBORF_INTEGER, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class EventRTPkt(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_TEXT_STRING('event_type', ''), + CBORF_SEMANTIC_TAG('tag', None, 1, CBORF_INTEGER('ts', 0)), + ) + +pkt = EventRTPkt() +pkt.event_type.val = 'logout' +pkt.ts.val = 1234567890 +raw = bytes(pkt) +pkt2 = EventRTPkt(raw) +pkt2.event_type.val == 'logout' and pkt2.ts.val == 1234567890 + += CBORF_SEMANTIC_TAG inside CBORF_ARRAY: Scapy encode, cbor2 decode +import cbor2, datetime +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_TEXT_STRING, CBORF_INTEGER, CBORF_SEMANTIC_TAG +from scapy.cborpacket import CBOR_Packet + +class TimedEventArr(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_TEXT_STRING('evt', ''), + CBORF_SEMANTIC_TAG('tag', None, 1, CBORF_INTEGER('ts', 0)), + ) + +pkt = TimedEventArr() +pkt.evt.val = 'start' +pkt.ts.val = 1700000000 +dec = cbor2.loads(bytes(pkt)) +dec[0] == 'start' and isinstance(dec[1], datetime.datetime) + ++ CBOR_Packet - realistic models + += Realistic model: EAT-like attestation token +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class EATToken(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('nonce', 0), + CBORF_TEXT_STRING('ueid', ''), + CBORF_BYTE_STRING('boot_seed', b''), + CBORF_INTEGER('hwver', 0), + ) + +raw = cbor2.dumps({'nonce': 12345, 'ueid': 'device-abc', 'boot_seed': b'\x00' * 16, 'hwver': 3}) +pkt = EATToken(raw) +pkt.nonce.val == 12345 and pkt.ueid.val == 'device-abc' and pkt.boot_seed.val == b'\x00' * 16 and pkt.hwver.val == 3 + += Realistic model: EAT-like token Scapy encode, cbor2 decode +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class EATToken2(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('nonce', 0), + CBORF_TEXT_STRING('ueid', ''), + CBORF_BYTE_STRING('boot_seed', b''), + CBORF_INTEGER('hwver', 0), + ) + +pkt = EATToken2() +pkt.nonce.val = 99999 +pkt.ueid.val = 'iot-sensor-01' +pkt.boot_seed.val = b'\xde\xad\xbe\xef' * 4 +pkt.hwver.val = 5 +dec = cbor2.loads(bytes(pkt)) +dec.get('nonce') == 99999 and dec.get('ueid') == 'iot-sensor-01' and dec.get('boot_seed') == b'\xde\xad\xbe\xef' * 4 and dec.get('hwver') == 5 + += Realistic model: SensorReport with CBORF_PACKET inner reading +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_FLOAT, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class SensorData(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('sensor_id', 0), + CBORF_FLOAT('temperature', 0.0), + ) + +class SensorReport(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('station', 0), + CBORF_TEXT_STRING('unit', ''), + CBORF_PACKET('reading', None, SensorData), + ) + +reading = SensorData() +reading.sensor_id.val = 3 +reading.temperature.val = 98.6 +rpt = SensorReport() +rpt.station.val = 5 +rpt.unit.val = 'fahrenheit' +rpt.reading = reading +dec = cbor2.loads(bytes(rpt)) +dec.get('station') == 5 and dec.get('unit') == 'fahrenheit' and dec.get('reading') == [3, 98.6] + += Realistic model: SensorReport Scapy roundtrip +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_FLOAT, CBORF_PACKET +from scapy.cborpacket import CBOR_Packet + +class SensorData2(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('sensor_id', 0), + CBORF_FLOAT('temperature', 0.0), + ) + +class SensorReport2(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('station', 0), + CBORF_TEXT_STRING('unit', ''), + CBORF_PACKET('reading', None, SensorData2), + ) + +raw = cbor2.dumps({'station': 9, 'unit': 'celsius', 'reading': [7, 36.5]}) +pkt = SensorReport2(raw) +pkt2 = SensorReport2(bytes(pkt)) +pkt2.station.val == 9 and pkt2.unit.val == 'celsius' and pkt2.reading.sensor_id.val == 7 + += Realistic model: StatusList (CBORF_ARRAY_OF of CBOR_Packets) encode and decode +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_ARRAY_OF +from scapy.cborpacket import CBOR_Packet + +class HttpStatus(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('code', 0), + CBORF_TEXT_STRING('phrase', ''), + ) + +class HttpStatusList(CBOR_Packet): + CBOR_root = CBORF_ARRAY_OF('statuses', [], HttpStatus) + +raw = cbor2.dumps([[200, 'OK'], [201, 'Created'], [404, 'Not Found']]) +pkt = HttpStatusList(raw) +raw2 = bytes(pkt) +dec = cbor2.loads(raw2) +dec == [[200, 'OK'], [201, 'Created'], [404, 'Not Found']] + += Realistic model: HTTP response header map +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class HttpResponse(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('status', 0), + CBORF_TEXT_STRING('content_type', ''), + CBORF_INTEGER('content_length', 0), + CBORF_BYTE_STRING('body', b''), + ) + +pkt = HttpResponse() +pkt.status.val = 200 +pkt.content_type.val = 'application/cbor' +pkt.content_length.val = 4 +pkt.body.val = b'\x01\x02\x03\x04' +dec = cbor2.loads(bytes(pkt)) +dec.get('status') == 200 and dec.get('content_type') == 'application/cbor' and dec.get('body') == b'\x01\x02\x03\x04' + += Realistic model: HTTP response header cbor2 → Scapy roundtrip +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BYTE_STRING +from scapy.cborpacket import CBOR_Packet + +class HttpResponse2(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('status', 0), + CBORF_TEXT_STRING('content_type', ''), + CBORF_INTEGER('content_length', 0), + CBORF_BYTE_STRING('body', b''), + ) + +raw = cbor2.dumps({'status': 404, 'content_type': 'text/plain', 'content_length': 9, 'body': b'Not Found'}) +pkt = HttpResponse2(raw) +pkt2 = HttpResponse2(bytes(pkt)) +pkt2.status.val == 404 and pkt2.content_type.val == 'text/plain' and pkt2.body.val == b'Not Found' + += Realistic model: COSE-like header map with integer algorithm +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_BYTE_STRING, CBORF_TEXT_STRING +from scapy.cborpacket import CBOR_Packet + +class CoseHeader(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('alg', 0), + CBORF_TEXT_STRING('kid', ''), + CBORF_BYTE_STRING('x5t', b''), + ) + +pkt = CoseHeader(cbor2.dumps({'alg': -7, 'kid': 'key-42', 'x5t': b'\xaa\xbb\xcc\xdd'})) +dec = cbor2.loads(bytes(pkt)) +dec.get('alg') == -7 and dec.get('kid') == 'key-42' and dec.get('x5t') == b'\xaa\xbb\xcc\xdd' + += Realistic model: CBOR_Packet fields_desc populated for complex structures +import cbor2 +from scapy.cbor.cborfields import CBORF_MAP, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BYTE_STRING, CBORF_FLOAT +from scapy.cborpacket import CBOR_Packet + +class FullRecord(CBOR_Packet): + CBOR_root = CBORF_MAP( + CBORF_INTEGER('seq', 0), + CBORF_TEXT_STRING('source', ''), + CBORF_FLOAT('score', 0.0), + CBORF_BYTE_STRING('checksum', b''), + ) + +field_names = [f.name for f in FullRecord.fields_desc] +'seq' in field_names and 'source' in field_names and 'score' in field_names and 'checksum' in field_names + += Realistic model: multi-field packet encoding is byte-for-byte reproducible +import cbor2 +from scapy.cbor.cborfields import CBORF_ARRAY, CBORF_INTEGER, CBORF_TEXT_STRING, CBORF_BYTE_STRING, CBORF_FLOAT +from scapy.cborpacket import CBOR_Packet + +class MeasurementPkt(CBOR_Packet): + CBOR_root = CBORF_ARRAY( + CBORF_INTEGER('seq', 0), + CBORF_TEXT_STRING('sensor', ''), + CBORF_FLOAT('value', 0.0), + CBORF_BYTE_STRING('raw', b''), + ) + +pkt = MeasurementPkt() +pkt.seq.val = 42 +pkt.sensor.val = 'temp-01' +pkt.value.val = 23.5 +pkt.raw.val = b'\x01\x02' +raw1 = bytes(pkt) +raw2 = bytes(MeasurementPkt(raw1)) +raw1 == raw2 + +########### CBOR Fuzzing / Random Object Tests #################### + ++ CBOR Random Object Generation + += Create RandCBORObject +from scapy.cbor import RandCBORObject +rand = RandCBORObject() +isinstance(rand, RandCBORObject) + += Generate random CBOR unsigned integer +from scapy.cbor import RandCBORObject, CBOR_UNSIGNED_INTEGER +rand = RandCBORObject(objlist=[CBOR_UNSIGNED_INTEGER]) +obj = rand._fix() +isinstance(obj, CBOR_UNSIGNED_INTEGER) and isinstance(obj.val, int) and obj.val >= 0 + += Generate random CBOR negative integer +from scapy.cbor import RandCBORObject, CBOR_NEGATIVE_INTEGER +rand = RandCBORObject(objlist=[CBOR_NEGATIVE_INTEGER]) +obj = rand._fix() +isinstance(obj, CBOR_NEGATIVE_INTEGER) and isinstance(obj.val, int) and obj.val < 0 + += Generate random CBOR byte string +from scapy.cbor import RandCBORObject, CBOR_BYTE_STRING +rand = RandCBORObject(objlist=[CBOR_BYTE_STRING]) +obj = rand._fix() +isinstance(obj, CBOR_BYTE_STRING) and isinstance(obj.val, bytes) + += Generate random CBOR text string +from scapy.cbor import RandCBORObject, CBOR_TEXT_STRING +rand = RandCBORObject(objlist=[CBOR_TEXT_STRING]) +obj = rand._fix() +isinstance(obj, CBOR_TEXT_STRING) and isinstance(obj.val, str) and len(obj.val) > 0 + += Generate random CBOR array +from scapy.cbor import RandCBORObject, CBOR_ARRAY +rand = RandCBORObject(objlist=[CBOR_ARRAY]) +obj = rand._fix() +isinstance(obj, CBOR_ARRAY) and isinstance(obj.val, list) + += Generate random CBOR map +from scapy.cbor import RandCBORObject, CBOR_MAP +rand = RandCBORObject(objlist=[CBOR_MAP]) +obj = rand._fix() +isinstance(obj, CBOR_MAP) and isinstance(obj.val, dict) + += Generate random CBOR boolean (false) +from scapy.cbor import RandCBORObject, CBOR_FALSE +rand = RandCBORObject(objlist=[CBOR_FALSE]) +obj = rand._fix() +isinstance(obj, CBOR_FALSE) and obj.val == False + += Generate random CBOR boolean (true) +from scapy.cbor import RandCBORObject, CBOR_TRUE +rand = RandCBORObject(objlist=[CBOR_TRUE]) +obj = rand._fix() +isinstance(obj, CBOR_TRUE) and obj.val == True + += Generate random CBOR null +from scapy.cbor import RandCBORObject, CBOR_NULL +rand = RandCBORObject(objlist=[CBOR_NULL]) +obj = rand._fix() +isinstance(obj, CBOR_NULL) and obj.val is None + += Generate random CBOR undefined +from scapy.cbor import RandCBORObject, CBOR_UNDEFINED +rand = RandCBORObject(objlist=[CBOR_UNDEFINED]) +obj = rand._fix() +isinstance(obj, CBOR_UNDEFINED) and obj.val is None + += Generate random CBOR float +from scapy.cbor import RandCBORObject, CBOR_FLOAT +rand = RandCBORObject(objlist=[CBOR_FLOAT]) +obj = rand._fix() +isinstance(obj, CBOR_FLOAT) and isinstance(obj.val, float) + ++ CBOR Random Object Encoding/Decoding + += Encode and decode random unsigned integer +from scapy.cbor import RandCBORObject, CBOR_UNSIGNED_INTEGER, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_UNSIGNED_INTEGER]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_UNSIGNED_INTEGER) and remainder == b'' and decoded.val == obj.val + += Encode and decode random text string +from scapy.cbor import RandCBORObject, CBOR_TEXT_STRING, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_TEXT_STRING]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_TEXT_STRING) and remainder == b'' and decoded.val == obj.val + += Encode and decode random byte string +from scapy.cbor import RandCBORObject, CBOR_BYTE_STRING, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_BYTE_STRING]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_BYTE_STRING) and remainder == b'' and decoded.val == obj.val + += Encode and decode random array +from scapy.cbor import RandCBORObject, CBOR_ARRAY, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_ARRAY]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_ARRAY) and remainder == b'' and len(decoded.val) == len(obj.val) + += Encode and decode random map +from scapy.cbor import RandCBORObject, CBOR_MAP, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_MAP]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_MAP) and remainder == b'' and len(decoded.val) == len(obj.val) + += Encode and decode random float +from scapy.cbor import RandCBORObject, CBOR_FLOAT, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_FLOAT]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_FLOAT) and remainder == b'' + ++ CBOR Random Mixed Types + += Generate multiple random objects of different types +from scapy.cbor import RandCBORObject +rand = RandCBORObject() +objects = [rand._fix() for _ in range(10)] +len(objects) == 10 and all(hasattr(obj, 'val') for obj in objects) + += Encode and decode multiple random objects +from scapy.cbor import RandCBORObject, CBOR_Codecs +rand = RandCBORObject() +success_count = 0 +for _ in range(20): + obj = rand._fix() + try: + encoded = bytes(obj) + decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) + if remainder == b'': + success_count += 1 + except: + pass + +success_count >= 18 + += Random nested arrays encode/decode correctly +from scapy.cbor import RandCBORObject, CBOR_ARRAY, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_ARRAY]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_ARRAY) and remainder == b'' + += Random nested maps encode/decode correctly +from scapy.cbor import RandCBORObject, CBOR_MAP, CBOR_Codecs +rand = RandCBORObject(objlist=[CBOR_MAP]) +obj = rand._fix() +encoded = bytes(obj) +decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_MAP) and remainder == b'' + ++ CBOR Fuzzing Stress Tests + += Generate 100 random objects without errors +from scapy.cbor import RandCBORObject +rand = RandCBORObject() +objects = [] +for _ in range(100): + obj = None + try: + obj = rand._fix() + except: + pass + if obj is not None: + objects.append(obj) + +len(objects) >= 95 + += Encode 50 random objects without errors +from scapy.cbor import RandCBORObject +rand = RandCBORObject() +encoded_count = 0 +for _ in range(50): + obj = rand._fix() + try: + encoded = bytes(obj) + if len(encoded) > 0: + encoded_count += 1 + except: + pass + +encoded_count >= 45 + += Roundtrip 50 random objects +from scapy.cbor import RandCBORObject, CBOR_Codecs +rand = RandCBORObject() +roundtrip_count = 0 +for _ in range(50): + obj = rand._fix() + try: + encoded = bytes(obj) + decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) + if remainder == b'': + roundtrip_count += 1 + except: + pass + +roundtrip_count >= 45