diff --git a/doc/scapy/advanced_usage/cbor.rst b/doc/scapy/advanced_usage/cbor.rst new file mode 100644 index 00000000000..2558ad62f6e --- /dev/null +++ b/doc/scapy/advanced_usage/cbor.rst @@ -0,0 +1,218 @@ +CBOR +==== + +What is CBOR? +------------- + +.. note:: + + This section provides a practical introduction to CBOR from Scapy's perspective. For the complete specification, see RFC 8949. + +CBOR (Concise Binary Object Representation) is a data format whose goal is to provide a compact, self-describing binary data interchange format based on the JSON data model. It is defined in RFC 8949 and is designed to be small in code size, reasonably small in message size, and extensible without the need for version negotiation. + +CBOR provides basic data types including: + +* **Unsigned integers** (major type 0): Non-negative integers +* **Negative integers** (major type 1): Negative integers +* **Byte strings** (major type 2): Raw binary data +* **Text strings** (major type 3): UTF-8 encoded strings +* **Arrays** (major type 4): Ordered sequences of values +* **Maps** (major type 5): Unordered key-value pairs +* **Semantic tags** (major type 6): Tagged values with additional semantics +* **Simple values and floats** (major type 7): Booleans, null, undefined, and floating-point numbers + +Each CBOR data item begins with an initial byte that encodes the major type (in the top 3 bits) and additional information (in the low 5 bits). This design allows for compact encoding while maintaining self-describing properties. + +Scapy and CBOR +-------------- + +Scapy provides a complete CBOR encoder and decoder following the same architectural pattern as the ASN.1 implementation. The CBOR engine can encode Python objects to CBOR binary format and decode CBOR data back to Python objects. It has been designed to be RFC 8949 compliant and interoperable with other CBOR implementations. + +CBOR engine +^^^^^^^^^^^ + +Scapy's CBOR engine provides classes to represent CBOR data items. The main components are: + +* ``CBOR_MajorTypes``: Defines the 8 major types (0-7) used in CBOR encoding +* ``CBOR_Object``: Base class for all CBOR value objects +* ``CBOR_Codecs``: Registry for encoding/decoding rules + +The ``CBOR_MajorTypes`` class defines tags for all major types:: + + class CBOR_MajorTypes: + name = "CBOR_MAJOR_TYPES" + UNSIGNED_INTEGER = 0 + NEGATIVE_INTEGER = 1 + BYTE_STRING = 2 + TEXT_STRING = 3 + ARRAY = 4 + MAP = 5 + TAG = 6 + SIMPLE_AND_FLOAT = 7 + +All CBOR objects are represented by Python instances that wrap raw values. They inherit from ``CBOR_Object``:: + + class CBOR_UNSIGNED_INTEGER(CBOR_Object): + tag = CBOR_MajorTypes.UNSIGNED_INTEGER + + class CBOR_TEXT_STRING(CBOR_Object): + tag = CBOR_MajorTypes.TEXT_STRING + + class CBOR_ARRAY(CBOR_Object): + tag = CBOR_MajorTypes.ARRAY + +Creating CBOR objects +^^^^^^^^^^^^^^^^^^^^^ + +CBOR objects can be easily created and composed:: + + >>> from scapy.cbor import * + >>> # Create basic types + >>> num = CBOR_UNSIGNED_INTEGER(42) + >>> text = CBOR_TEXT_STRING("Hello, CBOR!") + >>> data = CBOR_BYTE_STRING(b'\x01\x02\x03') + >>> + >>> # Create collections + >>> arr = CBOR_ARRAY([CBOR_UNSIGNED_INTEGER(1), + ... CBOR_UNSIGNED_INTEGER(2), + ... CBOR_TEXT_STRING("three")]) + >>> arr + , , ]]> + >>> + >>> # Create maps + >>> from scapy.cbor.cborcodec import CBORcodec_MAP + >>> mapping = {"name": "Alice", "age": 30, "active": True} + +Encoding and decoding +^^^^^^^^^^^^^^^^^^^^^ + +CBOR objects are encoded using their ``.enc()`` method. All codecs are referenced in the ``CBOR_Codecs`` object. The default codec is ``CBOR_Codecs.CBOR``:: + + >>> num = CBOR_UNSIGNED_INTEGER(42) + >>> encoded = bytes(num) + >>> encoded.hex() + '182a' + >>> + >>> # Decode back + >>> decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) + >>> decoded.val + 42 + >>> isinstance(decoded, CBOR_UNSIGNED_INTEGER) + True + +Encoding collections:: + + >>> from scapy.cbor.cborcodec import CBORcodec_ARRAY, CBORcodec_MAP + >>> # Encode an array + >>> encoded = CBORcodec_ARRAY.enc([1, 2, 3, 4, 5]) + >>> encoded.hex() + '850102030405' + >>> + >>> # Decode the array + >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) + >>> [item.val for item in decoded.val] + [1, 2, 3, 4, 5] + >>> + >>> # Encode a map + >>> encoded = CBORcodec_MAP.enc({"x": 100, "y": 200}) + >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) + >>> isinstance(decoded, CBOR_MAP) + True + +Working with different types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CBOR supports various data types:: + + >>> # Booleans + >>> true_val = CBOR_TRUE() + >>> false_val = CBOR_FALSE() + >>> bytes(true_val).hex() + 'f5' + >>> bytes(false_val).hex() + 'f4' + >>> + >>> # Null and undefined + >>> null_val = CBOR_NULL() + >>> undef_val = CBOR_UNDEFINED() + >>> bytes(null_val).hex() + 'f6' + >>> bytes(undef_val).hex() + 'f7' + >>> + >>> # Floating point + >>> float_val = CBOR_FLOAT(3.14159) + >>> bytes(float_val).hex() + 'fb400921f9f01b866e' + >>> + >>> # Negative integers + >>> neg = CBOR_NEGATIVE_INTEGER(-100) + >>> bytes(neg).hex() + '3863' + +Complex structures +^^^^^^^^^^^^^^^^^^ + +CBOR supports nested structures:: + + >>> # Nested arrays + >>> nested = CBORcodec_ARRAY.enc([1, [2, 3], [4, [5, 6]]]) + >>> decoded, _ = CBOR_Codecs.CBOR.dec(nested) + >>> isinstance(decoded, CBOR_ARRAY) + True + >>> + >>> # Complex maps with mixed types + >>> data = { + ... "name": "Bob", + ... "age": 25, + ... "active": True, + ... "tags": ["user", "admin"] + ... } + >>> encoded = CBORcodec_MAP.enc(data) + >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) + >>> len(decoded.val) + 4 + +Semantic tags +^^^^^^^^^^^^^ + +CBOR supports semantic tags (major type 6) for providing additional meaning to data items:: + + >>> # Tag 1 is for Unix epoch timestamps + >>> import time + >>> timestamp = int(time.time()) + >>> tagged = CBOR_SEMANTIC_TAG((1, CBOR_UNSIGNED_INTEGER(timestamp))) + >>> encoded = bytes(tagged) + >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) + >>> decoded.val[0] # Tag number + 1 + +Interoperability +^^^^^^^^^^^^^^^^ + +Scapy's CBOR implementation is fully interoperable with other CBOR libraries. The implementation has been tested with the ``cbor2`` Python library to ensure RFC 8949 compliance:: + + >>> import cbor2 + >>> # Encode with Scapy, decode with cbor2 + >>> scapy_obj = CBOR_UNSIGNED_INTEGER(12345) + >>> scapy_encoded = bytes(scapy_obj) + >>> cbor2.loads(scapy_encoded) + 12345 + >>> + >>> # Encode with cbor2, decode with Scapy + >>> cbor2_encoded = cbor2.dumps([1, "test", True]) + >>> scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) + >>> isinstance(scapy_decoded, CBOR_ARRAY) + True + +Error handling +^^^^^^^^^^^^^^ + +Scapy provides safe decoding with error handling:: + + >>> # Safe decoding returns error objects for invalid data + >>> invalid_data = b'\xff\xff\xff' + >>> obj, remainder = CBOR_Codecs.CBOR.safedec(invalid_data) + >>> isinstance(obj, CBOR_DECODING_ERROR) + True + diff --git a/scapy/cbor/__init__.py b/scapy/cbor/__init__.py new file mode 100644 index 00000000000..0572c57f9c7 --- /dev/null +++ b/scapy/cbor/__init__.py @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Package holding CBOR (Concise Binary Object Representation) related modules. +Follows the same paradigm as ASN.1 implementation. +""" + +from scapy.cbor.cbor import ( + CBOR_Error, + CBOR_Encoding_Error, + CBOR_Decoding_Error, + CBOR_BadTag_Decoding_Error, + CBOR_Codecs, + CBOR_MajorTypes, + CBOR_Object, + CBOR_UNSIGNED_INTEGER, + CBOR_NEGATIVE_INTEGER, + CBOR_BYTE_STRING, + CBOR_TEXT_STRING, + CBOR_ARRAY, + CBOR_MAP, + CBOR_SEMANTIC_TAG, + CBOR_SIMPLE_VALUE, + CBOR_FALSE, + CBOR_TRUE, + CBOR_NULL, + CBOR_UNDEFINED, + CBOR_FLOAT, + CBOR_DECODING_ERROR, +) + +from scapy.cbor.cborcodec import ( + CBORcodec_Object, + CBORcodec_UNSIGNED_INTEGER, + CBORcodec_NEGATIVE_INTEGER, + CBORcodec_BYTE_STRING, + CBORcodec_TEXT_STRING, + CBORcodec_ARRAY, + CBORcodec_MAP, + CBORcodec_SEMANTIC_TAG, + CBORcodec_SIMPLE_AND_FLOAT, +) + +__all__ = [ + # Exceptions + "CBOR_Error", + "CBOR_Encoding_Error", + "CBOR_Decoding_Error", + "CBOR_BadTag_Decoding_Error", + # Codecs + "CBOR_Codecs", + "CBOR_MajorTypes", + # Objects + "CBOR_Object", + "CBOR_UNSIGNED_INTEGER", + "CBOR_NEGATIVE_INTEGER", + "CBOR_BYTE_STRING", + "CBOR_TEXT_STRING", + "CBOR_ARRAY", + "CBOR_MAP", + "CBOR_SEMANTIC_TAG", + "CBOR_SIMPLE_VALUE", + "CBOR_FALSE", + "CBOR_TRUE", + "CBOR_NULL", + "CBOR_UNDEFINED", + "CBOR_FLOAT", + "CBOR_DECODING_ERROR", + # Codec classes + "CBORcodec_Object", + "CBORcodec_UNSIGNED_INTEGER", + "CBORcodec_NEGATIVE_INTEGER", + "CBORcodec_BYTE_STRING", + "CBORcodec_TEXT_STRING", + "CBORcodec_ARRAY", + "CBORcodec_MAP", + "CBORcodec_SEMANTIC_TAG", + "CBORcodec_SIMPLE_AND_FLOAT", +] diff --git a/scapy/cbor/cbor.py b/scapy/cbor/cbor.py new file mode 100644 index 00000000000..b2f2025079c --- /dev/null +++ b/scapy/cbor/cbor.py @@ -0,0 +1,359 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +CBOR (Concise Binary Object Representation) - RFC 8949 +Following the ASN.1 paradigm +""" + +from typing import ( + Any, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, + TYPE_CHECKING, +) + +from build.lib.scapy.error import log_runtime +from scapy.compat import plain_str +from scapy.error import Scapy_Exception +from scapy.utils import Enum_metaclass, EnumElement + +if TYPE_CHECKING: + from scapy.cbor.cborcodec import CBORcodec_Object + + +############## +# CBOR # +############## + + +class CBOR_Error(Scapy_Exception): + pass + + +class CBOR_Encoding_Error(CBOR_Error): + pass + + +class CBOR_Decoding_Error(CBOR_Error): + pass + + +class CBOR_BadTag_Decoding_Error(CBOR_Decoding_Error): + pass + + +class CBORCodec(EnumElement): + def register_stem(cls, stem): + # type: (Type[CBORcodec_Object[Any]]) -> None + cls._stem = stem + + def dec(cls, s, context=None): + # type: (bytes, Optional[Any]) -> CBOR_Object[Any] + return cls._stem.dec(s, context=context) # type: ignore + + def safedec(cls, s, context=None): + # type: (bytes, Optional[Any]) -> CBOR_Object[Any] + return cls._stem.safedec(s, context=context) # type: ignore + + def get_stem(cls): + # type: () -> type + return cls._stem + + +class CBOR_Codecs_metaclass(Enum_metaclass): + element_class = CBORCodec + + +class CBOR_Codecs(metaclass=CBOR_Codecs_metaclass): + CBOR = cast(CBORCodec, 1) + + +class CBORTag(EnumElement): + """Represents a CBOR major type""" + + def __init__(self, + key, # type: str + value, # type: int + codec=None # type: Optional[Dict[CBORCodec, Type[CBORcodec_Object[Any]]]] # noqa: E501 + ): + # type: (...) -> None + EnumElement.__init__(self, key, value) + if codec is None: + codec = {} + self._codec = codec + + def clone(self): + # type: () -> CBORTag + return self.__class__(self._key, self._value, self._codec) + + def register_cbor_object(self, cborobj): + # type: (Type[CBOR_Object[Any]]) -> None + self._cbor_obj = cborobj + + def cbor_object(self, val): + # type: (Any) -> CBOR_Object[Any] + if hasattr(self, "_cbor_obj"): + return self._cbor_obj(val) + raise CBOR_Error("%r does not have any assigned CBOR object" % self) + + def register(self, codecnum, codec): + # type: (CBORCodec, Type[CBORcodec_Object[Any]]) -> None + self._codec[codecnum] = codec + + def get_codec(self, codec): + # type: (Any) -> Type[CBORcodec_Object[Any]] + try: + c = self._codec[codec] + except KeyError: + raise CBOR_Error("Codec %r not found for tag %r" % (codec, self)) + return c + + +class CBOR_MajorTypes_metaclass(Enum_metaclass): + element_class = CBORTag + + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type[CBOR_MajorTypes] + rdict = {} + for k, v in dct.items(): + if isinstance(v, int): + v = CBORTag(k, v) + dct[k] = v + rdict[v] = v + elif isinstance(v, CBORTag): + rdict[v] = v + dct["__rdict__"] = rdict + + ncls = cast('Type[CBOR_MajorTypes]', + type.__new__(cls, name, bases, dct)) + return ncls + + +class CBOR_MajorTypes(metaclass=CBOR_MajorTypes_metaclass): + """CBOR Major Types (RFC 8949)""" + name = "CBOR_MAJOR_TYPES" + # CBOR major types (3-bit value in the high-order 3 bits) + UNSIGNED_INTEGER = cast(CBORTag, 0) + NEGATIVE_INTEGER = cast(CBORTag, 1) + BYTE_STRING = cast(CBORTag, 2) + TEXT_STRING = cast(CBORTag, 3) + ARRAY = cast(CBORTag, 4) + MAP = cast(CBORTag, 5) + TAG = cast(CBORTag, 6) + SIMPLE_AND_FLOAT = cast(CBORTag, 7) + + +class CBOR_Object_metaclass(type): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type[CBOR_Object[Any]] + c = cast( + 'Type[CBOR_Object[Any]]', + super(CBOR_Object_metaclass, cls).__new__(cls, name, bases, dct) + ) + try: + c.tag.register_cbor_object(c) + except Exception: + log_runtime.error("Failed to register codec for tag") + return c + + +_K = TypeVar('_K') + + +class CBOR_Object(Generic[_K], metaclass=CBOR_Object_metaclass): + """Base class for CBOR value objects""" + tag = None # type: ignore # Subclasses must define their own tag + + def __init__(self, val): + # type: (_K) -> None + self.val = val + + def enc(self, codec=None): + # type: (Any) -> bytes + if codec is None: + codec = CBOR_Codecs.CBOR + if self.tag is None: + raise CBOR_Error("Cannot encode object without a tag") + # Pass self instead of self.val for special handling + return self.tag.get_codec(codec).enc(self) + + def __repr__(self): + # type: () -> str + return "<%s[%r]>" % (self.__class__.__name__, self.val) + + def __str__(self): + # type: () -> str + return plain_str(self.enc()) + + def __bytes__(self): + # type: () -> bytes + return self.enc() + + def strshow(self, lvl=0): + # type: (int) -> str + return (" " * lvl) + repr(self) + "\n" + + def show(self, lvl=0): + # type: (int) -> None + print(self.strshow(lvl)) + + def __eq__(self, other): + # type: (Any) -> bool + return bool(self.val == other) + + +####################### +# CBOR objects # +####################### + + +class CBOR_UNSIGNED_INTEGER(CBOR_Object[int]): + """CBOR unsigned integer (major type 0)""" + tag = CBOR_MajorTypes.UNSIGNED_INTEGER + + +class CBOR_NEGATIVE_INTEGER(CBOR_Object[int]): + """CBOR negative integer (major type 1)""" + tag = CBOR_MajorTypes.NEGATIVE_INTEGER + + +class CBOR_BYTE_STRING(CBOR_Object[bytes]): + """CBOR byte string (major type 2)""" + tag = CBOR_MajorTypes.BYTE_STRING + + +class CBOR_TEXT_STRING(CBOR_Object[str]): + """CBOR text string (major type 3)""" + tag = CBOR_MajorTypes.TEXT_STRING + + +class CBOR_ARRAY(CBOR_Object[List[Any]]): + """CBOR array (major type 4)""" + tag = CBOR_MajorTypes.ARRAY + + def strshow(self, lvl=0): + # type: (int) -> str + s = (" " * lvl) + ("# CBOR_ARRAY:") + "\n" + for o in self.val: + if hasattr(o, 'strshow'): + s += o.strshow(lvl=lvl + 1) + else: + s += (" " * (lvl + 1)) + repr(o) + "\n" + return s + + +class CBOR_MAP(CBOR_Object[Dict[Any, Any]]): + """CBOR map (major type 5)""" + tag = CBOR_MajorTypes.MAP + + def strshow(self, lvl=0): + # type: (int) -> str + s = (" " * lvl) + ("# CBOR_MAP:") + "\n" + for k, v in self.val.items(): + s += (" " * (lvl + 1)) + "Key: " + if hasattr(k, 'strshow'): + s += k.strshow(0).strip() + "\n" + else: + s += repr(k) + "\n" + s += (" " * (lvl + 1)) + "Value: " + if hasattr(v, 'strshow'): + s += v.strshow(0).strip() + "\n" + else: + s += repr(v) + "\n" + return s + + +class CBOR_SEMANTIC_TAG(CBOR_Object[Tuple[int, Any]]): + """CBOR semantic tag (major type 6)""" + tag = CBOR_MajorTypes.TAG + + +class CBOR_SIMPLE_VALUE(CBOR_Object[int]): + """CBOR simple value (major type 7)""" + tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + +class CBOR_FALSE(CBOR_Object[bool]): + """CBOR false value""" + tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def __init__(self): + # type: () -> None + super(CBOR_FALSE, self).__init__(False) + + +class CBOR_TRUE(CBOR_Object[bool]): + """CBOR true value""" + tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def __init__(self): + # type: () -> None + super(CBOR_TRUE, self).__init__(True) + + +class CBOR_NULL(CBOR_Object[None]): + """CBOR null value""" + tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def __init__(self): + # type: () -> None + super(CBOR_NULL, self).__init__(None) + + +class CBOR_UNDEFINED(CBOR_Object[None]): + """CBOR undefined value""" + tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + def __init__(self): + # type: () -> None + super(CBOR_UNDEFINED, self).__init__(None) + + +class CBOR_FLOAT(CBOR_Object[float]): + """CBOR floating-point number (major type 7)""" + tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + +class _CBOR_ERROR(CBOR_Object[Union[bytes, CBOR_Object[Any]]]): + """CBOR decoding error wrapper""" + tag = None # type: ignore # Error objects don't have a CBOR tag + + +class CBOR_DECODING_ERROR(_CBOR_ERROR): + """CBOR decoding error object""" + + def __init__(self, val, exc=None): + # type: (Union[bytes, CBOR_Object[Any]], Optional[Exception]) -> None + CBOR_Object.__init__(self, val) + self.exc = exc + + def __repr__(self): + # type: () -> str + return "<%s[%r]{{%r}}>" % ( + self.__class__.__name__, + self.val, + self.exc and self.exc.args[0] or "" + ) + + def enc(self, codec=None): + # type: (Any) -> bytes + if isinstance(self.val, CBOR_Object): + return self.val.enc(codec) + return self.val # type: ignore diff --git a/scapy/cbor/cborcodec.py b/scapy/cbor/cborcodec.py new file mode 100644 index 00000000000..b49b9d38b30 --- /dev/null +++ b/scapy/cbor/cborcodec.py @@ -0,0 +1,706 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +CBOR Codec Implementation - RFC 8949 +Following the BER paradigm for ASN.1 +""" + +import struct +from typing import ( + Any, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +from scapy.cbor.cbor import ( + CBOR_Codecs, + CBOR_DECODING_ERROR, + CBOR_Decoding_Error, + CBOR_Encoding_Error, + CBOR_Error, + CBOR_MajorTypes, + CBOR_Object, + _CBOR_ERROR, +) +from scapy.compat import chb, orb +from scapy.error import log_runtime + + +################## +# CBOR encoding # +################## + + +class CBOR_Exception(Exception): + pass + + +class CBOR_Codec_Encoding_Error(CBOR_Encoding_Error): + def __init__(self, + msg, # type: str + encoded=None, # type: Optional[Any] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.encoded = encoded + + +class CBOR_Codec_Decoding_Error(CBOR_Decoding_Error): + def __init__(self, + msg, # type: str + decoded=None, # type: Optional[Any] + remaining=b"" # type: bytes + ): + # type: (...) -> None + Exception.__init__(self, msg) + self.remaining = remaining + self.decoded = decoded + + +def CBOR_encode_head(major_type, value): + # type: (int, int) -> bytes + """ + Encode CBOR initial byte and additional info. + Format: 3 bits major type + 5 bits additional info + """ + if value < 24: + # Value fits in 5 bits + return chb((major_type << 5) | value) + elif value < 256: + # 1-byte value follows + return chb((major_type << 5) | 24) + chb(value) + elif value < 65536: + # 2-byte value follows + return chb((major_type << 5) | 25) + struct.pack(">H", value) + elif value < 4294967296: + # 4-byte value follows + return chb((major_type << 5) | 26) + struct.pack(">I", value) + else: + # 8-byte value follows + return chb((major_type << 5) | 27) + struct.pack(">Q", value) + + +def CBOR_decode_head(s): + # type: (bytes) -> Tuple[int, int, bytes] + """ + Decode CBOR initial byte and additional info. + Returns: (major_type, value, remaining_bytes) + """ + if not s: + raise CBOR_Codec_Decoding_Error("Empty CBOR data", remaining=s) + + initial_byte = orb(s[0]) + major_type = initial_byte >> 5 + additional_info = initial_byte & 0x1f + + if additional_info < 24: + # Value is in the additional info + return major_type, additional_info, s[1:] + elif additional_info == 24: + # 1-byte value follows + if len(s) < 2: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for 1-byte value", remaining=s) + return major_type, orb(s[1]), s[2:] + elif additional_info == 25: + # 2-byte value follows + if len(s) < 3: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for 2-byte value", remaining=s) + value = struct.unpack(">H", s[1:3])[0] + return major_type, value, s[3:] + elif additional_info == 26: + # 4-byte value follows + if len(s) < 5: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for 4-byte value", remaining=s) + value = struct.unpack(">I", s[1:5])[0] + return major_type, value, s[5:] + elif additional_info == 27: + # 8-byte value follows + if len(s) < 9: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for 8-byte value", remaining=s) + value = struct.unpack(">Q", s[1:9])[0] + return major_type, value, s[9:] + else: + raise CBOR_Codec_Decoding_Error( + "Invalid additional info: %d" % additional_info, remaining=s) + + +# [ CBOR codec classes ] # + + +class CBORcodec_metaclass(type): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> Type[CBORcodec_Object[Any]] + c = cast('Type[CBORcodec_Object[Any]]', + super(CBORcodec_metaclass, cls).__new__(cls, name, bases, dct)) + try: + c.tag.register(c.codec, c) + except Exception: + log_runtime.error("Failed to register codec for tag") + return c + + +_K = TypeVar('_K') + + +class CBORcodec_Object(Generic[_K], metaclass=CBORcodec_metaclass): + """Base CBOR codec class""" + codec = CBOR_Codecs.CBOR + tag = CBOR_MajorTypes.UNSIGNED_INTEGER + + @classmethod + def cbor_object(cls, val): + # type: (_K) -> CBOR_Object[_K] + return cls.tag.cbor_object(val) + + @classmethod + def check_string(cls, s): + # type: (bytes) -> None + if not s: + raise CBOR_Codec_Decoding_Error( + "%s: Got empty object while expecting tag %r" % + (cls.__name__, cls.tag), remaining=s + ) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[Any], bytes] + """Decode CBOR data using automatic dispatch based on major type.""" + return _decode_cbor_item(s, safe=safe) + + @classmethod + def dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[Union[_CBOR_ERROR, CBOR_Object[_K]], bytes] + if not safe: + return cls.do_dec(s, context, safe) + try: + return cls.do_dec(s, context, safe) + except CBOR_Codec_Decoding_Error as e: + return CBOR_DECODING_ERROR(s, exc=e), b"" + except CBOR_Error as e: + return CBOR_DECODING_ERROR(s, exc=e), b"" + + @classmethod + def safedec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + ): + # type: (...) -> Tuple[Union[_CBOR_ERROR, CBOR_Object[_K]], bytes] + return cls.dec(s, context, safe=True) + + @classmethod + def enc(cls, s): + # type: (_K) -> bytes + raise NotImplementedError("Subclasses must implement enc") + + +CBOR_Codecs.CBOR.register_stem(CBORcodec_Object) + + +########################## +# CBORcodec objects # +########################## + + +class CBORcodec_UNSIGNED_INTEGER(CBORcodec_Object[int]): + """CBOR unsigned integer codec (major type 0)""" + tag = CBOR_MajorTypes.UNSIGNED_INTEGER + + @classmethod + def enc(cls, obj): + # type: (Union[int, CBOR_Object[int]]) -> bytes + from scapy.cbor.cbor import CBOR_Object + i = obj.val if isinstance(obj, CBOR_Object) else obj + if i < 0: + raise CBOR_Codec_Encoding_Error( + "Cannot encode negative value as unsigned integer. " + "Use CBOR_NEGATIVE_INTEGER for negative values.") + return CBOR_encode_head(0, i) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[int], bytes] + cls.check_string(s) + major_type, value, remainder = CBOR_decode_head(s) + if major_type != 0: + raise CBOR_Codec_Decoding_Error( + "Expected major type 0 (unsigned integer), got %d" % major_type, + remaining=s) + return cls.cbor_object(value), remainder + + +class CBORcodec_NEGATIVE_INTEGER(CBORcodec_Object[int]): + """CBOR negative integer codec (major type 1)""" + tag = CBOR_MajorTypes.NEGATIVE_INTEGER + + @classmethod + def enc(cls, obj): + # type: (Union[int, CBOR_Object[int]]) -> bytes + from scapy.cbor.cbor import CBOR_Object + i = obj.val if isinstance(obj, CBOR_Object) else obj + if i >= 0: + raise CBOR_Codec_Encoding_Error( + "Cannot encode non-negative value as negative integer. " + "Use CBOR_UNSIGNED_INTEGER for non-negative values.") + # CBOR negative integer: -1 - n + return CBOR_encode_head(1, -1 - i) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[int], bytes] + cls.check_string(s) + major_type, value, remainder = CBOR_decode_head(s) + if major_type != 1: + raise CBOR_Codec_Decoding_Error( + "Expected major type 1 (negative integer), got %d" % major_type, + remaining=s) + # Decode: -1 - n + return cls.cbor_object(-1 - value), remainder + + +class CBORcodec_BYTE_STRING(CBORcodec_Object[bytes]): + """CBOR byte string codec (major type 2)""" + tag = CBOR_MajorTypes.BYTE_STRING + + @classmethod + def enc(cls, obj): + # type: (Union[bytes, CBOR_Object[bytes]]) -> bytes + from scapy.cbor.cbor import CBOR_Object + data = obj.val if isinstance(obj, CBOR_Object) else obj + if not isinstance(data, bytes): + data = bytes(data) + return CBOR_encode_head(2, len(data)) + data + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[bytes], bytes] + cls.check_string(s) + major_type, length, remainder = CBOR_decode_head(s) + if major_type != 2: + raise CBOR_Codec_Decoding_Error( + "Expected major type 2 (byte string), got %d" % major_type, + remaining=s) + if len(remainder) < length: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for byte string: expected %d, got %d" % + (length, len(remainder)), remaining=s) + return cls.cbor_object(remainder[:length]), remainder[length:] + + +class CBORcodec_TEXT_STRING(CBORcodec_Object[str]): + """CBOR text string codec (major type 3)""" + tag = CBOR_MajorTypes.TEXT_STRING + + @classmethod + def enc(cls, obj): + # type: (Union[str, CBOR_Object[str]]) -> bytes + from scapy.cbor.cbor import CBOR_Object + text = obj.val if isinstance(obj, CBOR_Object) else obj + if isinstance(text, str): + text_bytes = text.encode('utf-8') + else: + text_bytes = bytes(text) + return CBOR_encode_head(3, len(text_bytes)) + text_bytes + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[str], bytes] + cls.check_string(s) + major_type, length, remainder = CBOR_decode_head(s) + if major_type != 3: + raise CBOR_Codec_Decoding_Error( + "Expected major type 3 (text string), got %d" % major_type, + remaining=s) + if len(remainder) < length: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for text string: expected %d, got %d" % + (length, len(remainder)), remaining=s) + try: + text = remainder[:length].decode('utf-8') + except UnicodeDecodeError as e: + raise CBOR_Codec_Decoding_Error( + "Invalid UTF-8 in text string: %s" % str(e), remaining=s) + return cls.cbor_object(text), remainder[length:] + + +class CBORcodec_ARRAY(CBORcodec_Object[List[Any]]): + """CBOR array codec (major type 4)""" + tag = CBOR_MajorTypes.ARRAY + + @classmethod + def enc(cls, obj): + # type: (Union[List[Any], CBOR_Object[List[Any]]]) -> bytes + from scapy.cbor.cbor import CBOR_Object + array = obj.val if isinstance(obj, CBOR_Object) else obj + result = CBOR_encode_head(4, len(array)) + for item in array: + result += CBORcodec_Object.encode_cbor_item(item) + return result + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[List[Any]], bytes] + cls.check_string(s) + major_type, length, remainder = CBOR_decode_head(s) + if major_type != 4: + raise CBOR_Codec_Decoding_Error( + "Expected major type 4 (array), got %d" % major_type, + remaining=s) + + items = [] + for _ in range(length): + if not remainder: + raise CBOR_Codec_Decoding_Error( + "Not enough items in array", remaining=s) + item, remainder = CBORcodec_Object.decode_cbor_item( + remainder, safe=safe) + items.append(item) + + return cls.cbor_object(items), remainder + + +class CBORcodec_MAP(CBORcodec_Object[Dict[Any, Any]]): + """CBOR map codec (major type 5)""" + tag = CBOR_MajorTypes.MAP + + @classmethod + def enc(cls, obj): + # type: (Union[Dict[Any, Any], CBOR_Object[Dict[Any, Any]]]) -> bytes + from scapy.cbor.cbor import CBOR_Object + mapping = obj.val if isinstance(obj, CBOR_Object) else obj + result = CBOR_encode_head(5, len(mapping)) + for key, value in mapping.items(): + result += CBORcodec_Object.encode_cbor_item(key) + result += CBORcodec_Object.encode_cbor_item(value) + return result + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[Dict[Any, Any]], bytes] + cls.check_string(s) + major_type, length, remainder = CBOR_decode_head(s) + if major_type != 5: + raise CBOR_Codec_Decoding_Error( + "Expected major type 5 (map), got %d" % major_type, + remaining=s) + + mapping = {} + for _ in range(length): + if not remainder: + raise CBOR_Codec_Decoding_Error( + "Not enough key-value pairs in map", remaining=s) + key, remainder = CBORcodec_Object.decode_cbor_item( + remainder, safe=safe) + if not remainder: + raise CBOR_Codec_Decoding_Error( + "Map key without value", remaining=s) + value, remainder = CBORcodec_Object.decode_cbor_item( + remainder, safe=safe) + # Convert key to hashable type if it's a CBOR object + if isinstance(key, CBOR_Object): + key_val = key.val + else: + key_val = key + mapping[key_val] = value + + return cls.cbor_object(mapping), remainder + + +class CBORcodec_SEMANTIC_TAG(CBORcodec_Object[Tuple[int, Any]]): + """CBOR semantic tag codec (major type 6)""" + tag = CBOR_MajorTypes.TAG + + @classmethod + def enc(cls, obj): + # type: (Union[Tuple[int, Any], CBOR_Object[Tuple[int, Any]]]) -> bytes + from scapy.cbor.cbor import CBOR_Object + tagged_item = obj.val if isinstance(obj, CBOR_Object) else obj + tag_num, item = tagged_item + result = CBOR_encode_head(6, tag_num) + result += CBORcodec_Object.encode_cbor_item(item) + return result + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[Tuple[int, Any]], bytes] + cls.check_string(s) + major_type, tag_num, remainder = CBOR_decode_head(s) + if major_type != 6: + raise CBOR_Codec_Decoding_Error( + "Expected major type 6 (tag), got %d" % major_type, + remaining=s) + + if not remainder: + raise CBOR_Codec_Decoding_Error( + "Tag without following item", remaining=s) + + item, remainder = CBORcodec_Object.decode_cbor_item( + remainder, safe=safe) + return cls.cbor_object((tag_num, item)), remainder + + +class CBORcodec_SIMPLE_AND_FLOAT(CBORcodec_Object[Union[int, float, bool, None]]): + """CBOR simple values and floats codec (major type 7)""" + tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT + + @classmethod + def enc(cls, obj): + # type: (Union[int, float, bool, None, CBOR_Object[Any]]) -> bytes + from scapy.cbor.cbor import ( + CBOR_FALSE, CBOR_TRUE, CBOR_NULL, CBOR_UNDEFINED, CBOR_Object + ) + + # Check if obj is a CBOR object instance (for special cases like UNDEFINED) + if isinstance(obj, CBOR_UNDEFINED): + return chb(0xf7) # undefined + elif isinstance(obj, CBOR_NULL): + return chb(0xf6) # null + elif isinstance(obj, CBOR_TRUE): + return chb(0xf5) # true + elif isinstance(obj, CBOR_FALSE): + return chb(0xf4) # false + elif isinstance(obj, CBOR_Object): + # For other CBOR objects, use their val attribute + val = obj.val + else: + val = obj + + if val is False: + return chb(0xf4) # false + elif val is True: + return chb(0xf5) # true + elif val is None: + return chb(0xf6) # null + elif isinstance(val, float): + # Encode as double precision (8 bytes) + return chb(0xfb) + struct.pack(">d", val) + elif isinstance(val, int) and 0 <= val <= 23: + # Simple value 0-23 + return CBOR_encode_head(7, val) + else: + raise CBOR_Codec_Encoding_Error( + "Cannot encode value as simple/float: %r" % val) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Any] + safe=False, # type: bool + ): + # type: (...) -> Tuple[CBOR_Object[Any], bytes] + from scapy.cbor.cbor import ( + CBOR_FALSE, CBOR_TRUE, CBOR_NULL, CBOR_UNDEFINED, + CBOR_FLOAT, CBOR_SIMPLE_VALUE + ) + + cls.check_string(s) + + # For major type 7, we need special handling because additional_info + # encodes different things (simple values vs float sizes) + initial_byte = orb(s[0]) + major_type = initial_byte >> 5 + additional_info = initial_byte & 0x1f + + if major_type != 7: + raise CBOR_Codec_Decoding_Error( + "Expected major type 7 (simple/float), got %d" % major_type, + remaining=s) + + # Check for special simple values (encoded directly in additional_info) + if additional_info == 20: + return CBOR_FALSE(), s[1:] + elif additional_info == 21: + return CBOR_TRUE(), s[1:] + elif additional_info == 22: + return CBOR_NULL(), s[1:] + elif additional_info == 23: + return CBOR_UNDEFINED(), s[1:] + elif additional_info == 25: + # Half precision float (2 bytes) - IEEE 754 binary16 + if len(s) < 3: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for half float", remaining=s) + half_bytes = s[1:3] + remainder = s[3:] + # Convert IEEE 754 binary16 to binary64 (double) + half_int = struct.unpack(">H", half_bytes)[0] + sign = (half_int >> 15) & 0x1 + exponent = (half_int >> 10) & 0x1f + fraction = half_int & 0x3ff + + # Handle special cases + if exponent == 0: + if fraction == 0: + # Zero + float_val = -0.0 if sign else 0.0 + else: + # Subnormal number + float_val = ((-1) ** sign) * (fraction / 1024.0) * (2 ** -14) + elif exponent == 31: + if fraction == 0: + # Infinity + float_val = float('-inf') if sign else float('inf') + else: + # NaN + float_val = float('nan') + else: + # Normalized number + float_val = ( + ((-1) ** sign) * + (1 + fraction / 1024.0) * + (2 ** (exponent - 15))) + + return CBOR_FLOAT(float_val), remainder + elif additional_info == 26: + # Single precision float (4 bytes) + if len(s) < 5: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for single float", remaining=s) + float_val = struct.unpack(">f", s[1:5])[0] + return CBOR_FLOAT(float_val), s[5:] + elif additional_info == 27: + # Double precision float (8 bytes) + if len(s) < 9: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for double float", remaining=s) + float_val = struct.unpack(">d", s[1:9])[0] + return CBOR_FLOAT(float_val), s[9:] + elif additional_info < 24: + # Simple value 0-23 + return CBOR_SIMPLE_VALUE(additional_info), s[1:] + else: + # additional_info 24 means 1-byte simple value follows + if additional_info == 24: + if len(s) < 2: + raise CBOR_Codec_Decoding_Error( + "Not enough bytes for simple value", remaining=s) + return CBOR_SIMPLE_VALUE(orb(s[1])), s[2:] + else: + raise CBOR_Codec_Decoding_Error( + "Invalid additional info for major type 7: %d" % additional_info, + remaining=s) + + +# Helper methods for encoding/decoding arbitrary CBOR items + + +def _encode_cbor_item(item): + # type: (Any) -> bytes + """Encode a Python value to CBOR bytes""" + from scapy.cbor.cbor import CBOR_Object + + if isinstance(item, CBOR_Object): + return item.enc() + elif isinstance(item, bool): + # Must check bool before int (bool is subclass of int) + return CBORcodec_SIMPLE_AND_FLOAT.enc(item) + elif isinstance(item, int): + if item >= 0: + return CBORcodec_UNSIGNED_INTEGER.enc(item) + else: + return CBORcodec_NEGATIVE_INTEGER.enc(item) + elif isinstance(item, bytes): + return CBORcodec_BYTE_STRING.enc(item) + elif isinstance(item, str): + return CBORcodec_TEXT_STRING.enc(item) + elif isinstance(item, list): + return CBORcodec_ARRAY.enc(item) + elif isinstance(item, dict): + return CBORcodec_MAP.enc(item) + elif isinstance(item, float): + return CBORcodec_SIMPLE_AND_FLOAT.enc(item) + elif item is None: + return CBORcodec_SIMPLE_AND_FLOAT.enc(None) + else: + raise CBOR_Codec_Encoding_Error( + "Cannot encode type: %s" % type(item)) + + +def _decode_cbor_item(s, safe=False): + # type: (bytes, bool) -> Tuple[CBOR_Object[Any], bytes] + """Decode CBOR bytes to a CBOR_Object""" + if not s: + raise CBOR_Codec_Decoding_Error("Empty CBOR data", remaining=s) + + initial_byte = orb(s[0]) + major_type = initial_byte >> 5 + + # Dispatch to appropriate codec based on major type + if major_type == 0: + return CBORcodec_UNSIGNED_INTEGER.dec(s, safe=safe) + elif major_type == 1: + return CBORcodec_NEGATIVE_INTEGER.dec(s, safe=safe) + elif major_type == 2: + return CBORcodec_BYTE_STRING.dec(s, safe=safe) + elif major_type == 3: + return CBORcodec_TEXT_STRING.dec(s, safe=safe) + elif major_type == 4: + return CBORcodec_ARRAY.dec(s, safe=safe) + elif major_type == 5: + return CBORcodec_MAP.dec(s, safe=safe) + elif major_type == 6: + return CBORcodec_SEMANTIC_TAG.dec(s, safe=safe) + elif major_type == 7: + return CBORcodec_SIMPLE_AND_FLOAT.dec(s, safe=safe) + else: + raise CBOR_Codec_Decoding_Error( + "Invalid major type: %d" % major_type, remaining=s) + + +# Add helper methods to CBORcodec_Object +CBORcodec_Object.encode_cbor_item = staticmethod(_encode_cbor_item) +CBORcodec_Object.decode_cbor_item = staticmethod(_decode_cbor_item) diff --git a/scapy/tools/UTscapy.py b/scapy/tools/UTscapy.py index b4761c5b8fc..e50c8346f13 100644 --- a/scapy/tools/UTscapy.py +++ b/scapy/tools/UTscapy.py @@ -330,7 +330,7 @@ def parse_config_file(config_path, verb=3): } """ - with open(config_path) as config_file: + with open(config_path, encoding='utf-8') as config_file: data = json.load(config_file) if verb > 2: print(" %s Loaded config file" % arrow, config_path) @@ -473,7 +473,7 @@ def compute_campaign_digests(test_campaign): ts.crc = crc32(dts) dc += "\0\x01" + dts test_campaign.crc = crc32(dc) - with open(test_campaign.filename) as fdesc: + with open(test_campaign.filename, encoding='utf-8') as fdesc: test_campaign.sha = sha1(fdesc.read()) @@ -1185,7 +1185,7 @@ def main(): if VERB > 2: print(theme.green(dash + " Loading: %s" % TESTFILE)) PREEXEC = PREEXEC_DICT[TESTFILE] if TESTFILE in PREEXEC_DICT else GLOB_PREEXEC - with open(TESTFILE) as testfile: + with open(TESTFILE, encoding='utf-8') as testfile: output, result, campaign = execute_campaign( testfile, OUTPUTFILE, PREEXEC, NUM, KW_OK, KW_KO, DUMP, DOCS, FORMAT, VERB, ONLYFAILED, CRC, INTERPRETER, diff --git a/test/scapy/layers/cbor.uts b/test/scapy/layers/cbor.uts new file mode 100644 index 00000000000..97322e388c3 --- /dev/null +++ b/test/scapy/layers/cbor.uts @@ -0,0 +1,781 @@ +% Tests for CBOR encoding/decoding +# Following the ASN.1 test paradigm +# +# Try me with: +# bash test/run_tests -t test/scapy/layers/cbor.uts -F +# +# NOTE: Interoperability tests require cbor2 (test-only dependency): +# pip install cbor2 +# cbor2 is used ONLY in tests, NOT in the scapy CBOR implementation + +########### CBOR Basic Types ####################################### + ++ CBOR Unsigned Integer + += Encode small unsigned integer (0-23) +from scapy.cbor import * +obj = CBOR_UNSIGNED_INTEGER(0) +bytes(obj) == b'\x00' + += Encode unsigned integer with 1-byte value +obj = CBOR_UNSIGNED_INTEGER(24) +bytes(obj) == b'\x18\x18' + += Encode unsigned integer with 2-byte value +obj = CBOR_UNSIGNED_INTEGER(1000) +bytes(obj) == b'\x19\x03\xe8' + += Encode unsigned integer with 4-byte value +obj = CBOR_UNSIGNED_INTEGER(1000000) +bytes(obj) == b'\x1a\x00\x0f\x42\x40' + += Decode small unsigned integer +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x00') +obj.val == 0 and remainder == b'' + += Decode unsigned integer with 1-byte value +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x18\x18') +obj.val == 24 and remainder == b'' + += Decode unsigned integer with 2-byte value +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x19\x03\xe8') +obj.val == 1000 and remainder == b'' + ++ CBOR Negative Integer + += Encode negative integer -1 +obj = CBOR_NEGATIVE_INTEGER(-1) +bytes(obj) == b'\x20' + += Encode negative integer -10 +obj = CBOR_NEGATIVE_INTEGER(-10) +bytes(obj) == b'\x29' + += Encode negative integer -100 +obj = CBOR_NEGATIVE_INTEGER(-100) +bytes(obj) == b'\x38\x63' + += Decode negative integer -1 +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x20') +obj.val == -1 and remainder == b'' + += Decode negative integer -100 +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x38\x63') +obj.val == -100 and remainder == b'' + ++ CBOR Byte String + += Encode empty byte string +obj = CBOR_BYTE_STRING(b'') +bytes(obj) == b'\x40' + += Encode byte string +obj = CBOR_BYTE_STRING(b'hello') +bytes(obj) == b'\x45hello' + += Decode empty byte string +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x40') +obj.val == b'' and remainder == b'' + += Decode byte string +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x45hello') +obj.val == b'hello' and remainder == b'' + ++ CBOR Text String + += Encode empty text string +obj = CBOR_TEXT_STRING('') +bytes(obj) == b'\x60' + += Encode text string +obj = CBOR_TEXT_STRING('hello') +bytes(obj) == b'\x65hello' + += Encode UTF-8 text string +obj = CBOR_TEXT_STRING('café') +bytes(obj) == b'\x65caf\xc3\xa9' + += Decode empty text string +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x60') +obj.val == '' and remainder == b'' + += Decode text string +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x65hello') +obj.val == 'hello' and remainder == b'' + += Decode UTF-8 text string +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x65caf\xc3\xa9') +obj.val == 'café' and remainder == b'' + ++ CBOR Simple Values + += Encode false +obj = CBOR_FALSE() +bytes(obj) == b'\xf4' + += Encode true +obj = CBOR_TRUE() +bytes(obj) == b'\xf5' + += Encode null +obj = CBOR_NULL() +bytes(obj) == b'\xf6' + += Encode undefined +obj = CBOR_UNDEFINED() +bytes(obj) == b'\xf7' + += Decode false +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf4') +isinstance(obj, CBOR_FALSE) and obj.val is False and remainder == b'' + += Decode true +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf5') +isinstance(obj, CBOR_TRUE) and obj.val is True and remainder == b'' + += Decode null +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf6') +isinstance(obj, CBOR_NULL) and obj.val is None and remainder == b'' + += Decode undefined +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf7') +isinstance(obj, CBOR_UNDEFINED) and remainder == b'' + ++ CBOR Float + += Encode double precision float +obj = CBOR_FLOAT(1.5) +bytes(obj) == b'\xfb\x3f\xf8\x00\x00\x00\x00\x00\x00' + += Decode double precision float +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xfb\x3f\xf8\x00\x00\x00\x00\x00\x00') +abs(obj.val - 1.5) < 0.0001 and remainder == b'' + ++ CBOR Array + += Encode empty array +obj = CBOR_ARRAY([]) +bytes(obj) == b'\x80' + += Encode array with integers +from scapy.cbor.cborcodec import CBORcodec_ARRAY +obj = CBOR_ARRAY([CBOR_UNSIGNED_INTEGER(1), CBOR_UNSIGNED_INTEGER(2), CBOR_UNSIGNED_INTEGER(3)]) +bytes(obj) == b'\x83\x01\x02\x03' + += Encode array with Python integers +result = CBORcodec_ARRAY.enc([1, 2, 3]) +result == b'\x83\x01\x02\x03' + += Decode empty array +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x80') +isinstance(obj, CBOR_ARRAY) and obj.val == [] and remainder == b'' + += Decode array with integers +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x83\x01\x02\x03') +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 3 + += Decode nested array +obj, remainder = CBOR_Codecs.CBOR.dec(b'\x82\x01\x82\x02\x03') +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 2 + ++ CBOR Map + += Encode empty map +obj = CBOR_MAP({}) +bytes(obj) == b'\xa0' + += Encode map with string keys +from scapy.cbor.cborcodec import CBORcodec_MAP +result = CBORcodec_MAP.enc({"a": 1, "b": 2}) +result == b'\xa2\x61a\x01\x61b\x02' or result == b'\xa2\x61b\x02\x61a\x01' + += Decode empty map +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xa0') +isinstance(obj, CBOR_MAP) and obj.val == {} and remainder == b'' + += Decode map with integer values +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xa2\x61a\x01\x61b\x02') +isinstance(obj, CBOR_MAP) and len(obj.val) == 2 + ++ CBOR Semantic Tag + += Encode semantic tag (datetime) +obj = CBOR_SEMANTIC_TAG((0, CBOR_TEXT_STRING("2013-03-21T20:04:00Z"))) +bytes(obj) == b'\xc0\x74' + b'2013-03-21T20:04:00Z' + += Decode semantic tag +obj, remainder = CBOR_Codecs.CBOR.dec(b'\xc0\x74' + b'2013-03-21T20:04:00Z') +isinstance(obj, CBOR_SEMANTIC_TAG) and obj.val[0] == 0 and remainder == b'' + ++ CBOR Roundtrip Tests + += Roundtrip unsigned integer +original = CBOR_UNSIGNED_INTEGER(42) +encoded = bytes(original) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +decoded.val == original.val + += Roundtrip negative integer +original = CBOR_NEGATIVE_INTEGER(-42) +encoded = bytes(original) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +decoded.val == original.val + += Roundtrip byte string +original = CBOR_BYTE_STRING(b'test data') +encoded = bytes(original) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +decoded.val == original.val + += Roundtrip text string +original = CBOR_TEXT_STRING('test string') +encoded = bytes(original) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +decoded.val == original.val + += Roundtrip array +from scapy.cbor.cborcodec import CBORcodec_ARRAY +encoded = CBORcodec_ARRAY.enc([1, 2, 3, 4, 5]) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_ARRAY) and len(decoded.val) == 5 + += Roundtrip map +from scapy.cbor.cborcodec import CBORcodec_MAP +encoded = CBORcodec_MAP.enc({"x": 100, "y": 200}) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_MAP) and len(decoded.val) == 2 + ++ CBOR Complex Structures + += Encode nested structure +from scapy.cbor.cborcodec import CBORcodec_MAP +input_dict = { + "name": "John", + "age": 30, + "active": True +} +encoded = CBORcodec_MAP.enc(input_dict) +len(encoded) > 0 + += Decode nested structure +encoded_data = b'\xa3\x64name\x64John\x63age\x18\x1e\x66active\xf5' +obj, remainder = CBOR_Codecs.CBOR.dec(encoded_data) +isinstance(obj, CBOR_MAP) and remainder == b'' + ++ CBOR Error Handling + += Safe decode with invalid data +obj, remainder = CBOR_Codecs.CBOR.safedec(b'\xff\xff\xff') +isinstance(obj, CBOR_DECODING_ERROR) + += Decode with insufficient bytes for length +try: + obj, remainder = CBOR_Codecs.CBOR.dec(b'\x18') + False +except: + True + += Decode byte string with insufficient data +try: + obj, remainder = CBOR_Codecs.CBOR.dec(b'\x45hel') + False +except: + True + +########### CBOR Interoperability Tests with cbor2 ################# +# These tests verify interoperability between scapy's CBOR implementation +# and the standard cbor2 library. cbor2 is ONLY used in tests, not in +# the scapy implementation. +# +# NOTE: These tests require cbor2 to be installed: pip install cbor2 + ++ CBOR Interoperability - Basic Types (Scapy encode, cbor2 decode) + += Check cbor2 availability +try: + import cbor2 + cbor2_available = True +except ImportError: + cbor2_available = False + +cbor2_available + += Interop: Scapy encode unsigned integer, cbor2 decode +import cbor2 +obj = CBOR_UNSIGNED_INTEGER(42) +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded == 42 + += Interop: Scapy encode negative integer, cbor2 decode +obj = CBOR_NEGATIVE_INTEGER(-100) +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded == -100 + += Interop: Scapy encode text string, cbor2 decode +obj = CBOR_TEXT_STRING("Hello, World!") +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded == "Hello, World!" + += Interop: Scapy encode UTF-8 text string, cbor2 decode +obj = CBOR_TEXT_STRING("Café ☕") +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded == "Café ☕" + += Interop: Scapy encode byte string, cbor2 decode +obj = CBOR_BYTE_STRING(b'\x01\x02\x03\x04\x05') +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded == b'\x01\x02\x03\x04\x05' + += Interop: Scapy encode true, cbor2 decode +obj = CBOR_TRUE() +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded is True + += Interop: Scapy encode false, cbor2 decode +obj = CBOR_FALSE() +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded is False + += Interop: Scapy encode null, cbor2 decode +obj = CBOR_NULL() +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +decoded is None + += Interop: Scapy encode undefined, cbor2 decode +obj = CBOR_UNDEFINED() +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +from cbor2 import undefined +decoded is undefined + += Interop: Scapy encode float, cbor2 decode +obj = CBOR_FLOAT(3.14159) +encoded = bytes(obj) +decoded = cbor2.loads(encoded) +abs(decoded - 3.14159) < 0.0001 + ++ CBOR Interoperability - Collections (Scapy encode, cbor2 decode) + += Interop: Scapy encode array, cbor2 decode +from scapy.cbor.cborcodec import CBORcodec_ARRAY +encoded = CBORcodec_ARRAY.enc([1, 2, 3, 4, 5]) +decoded = cbor2.loads(encoded) +decoded == [1, 2, 3, 4, 5] + += Interop: Scapy encode nested array, cbor2 decode +encoded = CBORcodec_ARRAY.enc([1, [2, 3], [4, [5, 6]]]) +decoded = cbor2.loads(encoded) +decoded == [1, [2, 3], [4, [5, 6]]] + += Interop: Scapy encode map, cbor2 decode +from scapy.cbor.cborcodec import CBORcodec_MAP +encoded = CBORcodec_MAP.enc({"a": 1, "b": 2, "c": 3}) +decoded = cbor2.loads(encoded) +decoded == {"a": 1, "b": 2, "c": 3} + += Interop: Scapy encode complex map, cbor2 decode +data = {"name": "Alice", "age": 30, "active": True, "tags": ["user", "admin"]} +encoded = CBORcodec_MAP.enc(data) +decoded = cbor2.loads(encoded) +decoded == data + += Interop: Scapy encode mixed array, cbor2 decode +encoded = CBORcodec_ARRAY.enc([42, "hello", True, None, 3.14, [1, 2]]) +decoded = cbor2.loads(encoded) +len(decoded) == 6 and decoded[0] == 42 and decoded[1] == "hello" + ++ CBOR Interoperability - Basic Types (cbor2 encode, Scapy decode) + += Interop: cbor2 encode unsigned integer, Scapy decode +encoded = cbor2.dumps(42) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val == 42 and isinstance(obj, CBOR_UNSIGNED_INTEGER) + += Interop: cbor2 encode negative integer, Scapy decode +encoded = cbor2.dumps(-100) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val == -100 and isinstance(obj, CBOR_NEGATIVE_INTEGER) + += Interop: cbor2 encode text string, Scapy decode +encoded = cbor2.dumps("Hello, World!") +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val == "Hello, World!" and isinstance(obj, CBOR_TEXT_STRING) + += Interop: cbor2 encode UTF-8 text string, Scapy decode +encoded = cbor2.dumps("Café ☕") +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val == "Café ☕" and isinstance(obj, CBOR_TEXT_STRING) + += Interop: cbor2 encode byte string, Scapy decode +encoded = cbor2.dumps(b'\x01\x02\x03\x04\x05') +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val == b'\x01\x02\x03\x04\x05' and isinstance(obj, CBOR_BYTE_STRING) + += Interop: cbor2 encode true, Scapy decode +encoded = cbor2.dumps(True) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val is True and isinstance(obj, CBOR_TRUE) + += Interop: cbor2 encode false, Scapy decode +encoded = cbor2.dumps(False) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val is False and isinstance(obj, CBOR_FALSE) + += Interop: cbor2 encode null, Scapy decode +encoded = cbor2.dumps(None) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +obj.val is None and isinstance(obj, CBOR_NULL) + += Interop: cbor2 encode undefined, Scapy decode +from cbor2 import CBORSimpleValue, undefined +encoded = cbor2.dumps(undefined) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(obj, CBOR_UNDEFINED) + += Interop: cbor2 encode float, Scapy decode +encoded = cbor2.dumps(3.14159) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +abs(obj.val - 3.14159) < 0.0001 and isinstance(obj, CBOR_FLOAT) + ++ CBOR Interoperability - Collections (cbor2 encode, Scapy decode) + += Interop: cbor2 encode array, Scapy decode +encoded = cbor2.dumps([1, 2, 3, 4, 5]) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 5 + += Interop: cbor2 encode nested array, Scapy decode +encoded = cbor2.dumps([1, [2, 3], [4, [5, 6]]]) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 3 + += Interop: cbor2 encode map, Scapy decode +encoded = cbor2.dumps({"a": 1, "b": 2, "c": 3}) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(obj, CBOR_MAP) and len(obj.val) == 3 + += Interop: cbor2 encode complex map, Scapy decode +data = {"name": "Alice", "age": 30, "active": True} +encoded = cbor2.dumps(data) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(obj, CBOR_MAP) and "name" in obj.val + += Interop: cbor2 encode mixed array, Scapy decode +encoded = cbor2.dumps([42, "hello", True, None, 3.14]) +obj, remainder = CBOR_Codecs.CBOR.dec(encoded) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 5 + ++ CBOR Interoperability - Roundtrip Tests + += Interop roundtrip: integer through cbor2 +original_val = 12345 +scapy_obj = CBOR_UNSIGNED_INTEGER(original_val) +scapy_encoded = bytes(scapy_obj) +cbor2_decoded = cbor2.loads(scapy_encoded) +cbor2_encoded = cbor2.dumps(cbor2_decoded) +scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) +scapy_decoded.val == original_val + += Interop roundtrip: string through cbor2 +original_val = "Test String 测试" +scapy_obj = CBOR_TEXT_STRING(original_val) +scapy_encoded = bytes(scapy_obj) +cbor2_decoded = cbor2.loads(scapy_encoded) +cbor2_encoded = cbor2.dumps(cbor2_decoded) +scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) +scapy_decoded.val == original_val + += Interop roundtrip: array through cbor2 +original_val = [1, "two", 3.0, True, None] +scapy_encoded = CBORcodec_ARRAY.enc(original_val) +cbor2_decoded = cbor2.loads(scapy_encoded) +cbor2_encoded = cbor2.dumps(cbor2_decoded) +scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) +isinstance(scapy_decoded, CBOR_ARRAY) and len(scapy_decoded.val) == 5 + += Interop roundtrip: map through cbor2 +original_val = {"int": 42, "str": "value", "bool": True, "null": None} +scapy_encoded = CBORcodec_MAP.enc(original_val) +cbor2_decoded = cbor2.loads(scapy_encoded) +cbor2_encoded = cbor2.dumps(cbor2_decoded) +scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) +isinstance(scapy_decoded, CBOR_MAP) and len(scapy_decoded.val) == 4 + ++ CBOR Interoperability - Edge Cases + += Interop: Large unsigned integer +large_int = 18446744073709551615 # 2^64 - 1 +encoded = cbor2.dumps(large_int) +obj, _ = CBOR_Codecs.CBOR.dec(encoded) +obj.val == large_int + += Interop: Very negative integer +neg_int = -18446744073709551616 # -(2^64) +encoded = cbor2.dumps(neg_int) +obj, _ = CBOR_Codecs.CBOR.dec(encoded) +obj.val == neg_int + += Interop: Empty collections +empty_array = cbor2.dumps([]) +obj1, _ = CBOR_Codecs.CBOR.dec(empty_array) +empty_map = cbor2.dumps({}) +obj2, _ = CBOR_Codecs.CBOR.dec(empty_map) +isinstance(obj1, CBOR_ARRAY) and len(obj1.val) == 0 and isinstance(obj2, CBOR_MAP) and len(obj2.val) == 0 + += Interop: Deeply nested structure +deep = {"level1": {"level2": {"level3": {"level4": [1, 2, 3]}}}} +encoded = cbor2.dumps(deep) +obj, _ = CBOR_Codecs.CBOR.dec(encoded) +isinstance(obj, CBOR_MAP) + += Interop: Special float values (infinity) +import math +pos_inf_encoded = cbor2.dumps(math.inf) +pos_inf_obj, _ = CBOR_Codecs.CBOR.dec(pos_inf_encoded) +neg_inf_encoded = cbor2.dumps(-math.inf) +neg_inf_obj, _ = CBOR_Codecs.CBOR.dec(neg_inf_encoded) +math.isinf(pos_inf_obj.val) and math.isinf(neg_inf_obj.val) + += Interop: Special float value (NaN) +nan_encoded = cbor2.dumps(math.nan) +nan_obj, _ = CBOR_Codecs.CBOR.dec(nan_encoded) +math.isnan(nan_obj.val) + += Interop: Zero values +zero_int = cbor2.dumps(0) +zero_float = cbor2.dumps(0.0) +obj1, _ = CBOR_Codecs.CBOR.dec(zero_int) +obj2, _ = CBOR_Codecs.CBOR.dec(zero_float) +obj1.val == 0 and obj2.val == 0.0 + +########### Additional Tests Adapted from PR #4875 ################### +# These tests verify specific encoding sizes and edge cases + ++ CBOR Encoding Sizes - Unsigned Integers + += uint encoding size 0 (argument in initial byte) +obj = CBOR_UNSIGNED_INTEGER(0x12) +data = bytes(obj) +data == bytes.fromhex('12') + += uint encoding size 1 (1-byte argument follows) +obj = CBOR_UNSIGNED_INTEGER(0x34) +data = bytes(obj) +data == bytes.fromhex('1834') + += uint encoding size 2 (2-byte argument follows) +obj = CBOR_UNSIGNED_INTEGER(0x1234) +data = bytes(obj) +data == bytes.fromhex('191234') + += uint encoding size 4 (4-byte argument follows) +obj = CBOR_UNSIGNED_INTEGER(0x12345678) +data = bytes(obj) +data == bytes.fromhex('1a12345678') + += uint encoding size 8 (8-byte argument follows) +obj = CBOR_UNSIGNED_INTEGER(0x1234567812345678) +data = bytes(obj) +data == bytes.fromhex('1b1234567812345678') + += uint decoding size 0 +data = bytes.fromhex('12') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == 18 and remainder == b'' + += uint decoding size 1 +data = bytes.fromhex('1834') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == 0x34 and remainder == b'' + += uint decoding size 2 +data = bytes.fromhex('191234') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == 0x1234 and remainder == b'' + += uint decoding size 4 +data = bytes.fromhex('1a12345678') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == 0x12345678 and remainder == b'' + += uint decoding size 8 +data = bytes.fromhex('1b1234567812345678') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == 0x1234567812345678 and remainder == b'' + ++ CBOR Encoding Sizes - Negative Integers + += nint encoding size 0 +obj = CBOR_NEGATIVE_INTEGER(-0x13) +data = bytes(obj) +data == bytes.fromhex('32') + += nint decoding size 0 +data = bytes.fromhex('32') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == -0x13 and isinstance(obj, CBOR_NEGATIVE_INTEGER) and remainder == b'' + += nint decoding size 2 +data = bytes.fromhex('391234') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == (-0x1234 - 1) and isinstance(obj, CBOR_NEGATIVE_INTEGER) and remainder == b'' + ++ CBOR Byte String Edge Cases + += bstr encoding with specific content +obj = CBOR_BYTE_STRING(b'hi') +data = bytes(obj) +data == bytes.fromhex('426869') + += bstr decoding with specific content +data = bytes.fromhex('426869') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == b'hi' and isinstance(obj, CBOR_BYTE_STRING) and remainder == b'' + += bstr longer content (24 bytes) +content = b'longlonglonglonglonglong' +obj = CBOR_BYTE_STRING(content) +data = bytes(obj) +# Should use 1-byte length encoding (0x58 = major type 2, additional info 24) +data[:2] == bytes.fromhex('5818') and data[2:] == content + += bstr decoding longer content +data = bytes.fromhex('58186c6f6e676c6f6e676c6f6e676c6f6e676c6f6e676c6f6e67') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == b'longlonglonglonglonglong' and remainder == b'' + ++ CBOR Text String Edge Cases + += tstr encoding with specific content +obj = CBOR_TEXT_STRING('hi') +data = bytes(obj) +data == bytes.fromhex('626869') + += tstr decoding with specific content +data = bytes.fromhex('626869') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == 'hi' and isinstance(obj, CBOR_TEXT_STRING) and remainder == b'' + += tstr longer content (24 chars) +content = 'longlonglonglonglonglong' +obj = CBOR_TEXT_STRING(content) +data = bytes(obj) +# Should use 1-byte length encoding (0x78 = major type 3, additional info 24) +data[:2] == bytes.fromhex('7818') and data[2:] == content.encode('utf8') + += tstr decoding longer content +data = bytes.fromhex('78186c6f6e676c6f6e676c6f6e676c6f6e676c6f6e676c6f6e67') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +obj.val == 'longlonglonglonglonglong' and remainder == b'' + ++ CBOR Array Specific Encodings + += array encoding with mixed integer types +from scapy.cbor.cborcodec import CBORcodec_ARRAY +# Array with positive 10 and negative 20 +encoded = CBORcodec_ARRAY.enc([10, -20]) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_ARRAY) and len(decoded.val) == 2 + += array decoding specific encoding +data = bytes.fromhex('820A33') # array(2): [10, -20] +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 2 and remainder == b'' + ++ CBOR Map Specific Encodings + += map encoding with integer keys +from scapy.cbor.cborcodec import CBORcodec_MAP +encoded = CBORcodec_MAP.enc({10: -20}) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_MAP) and len(decoded.val) == 1 + += map decoding specific encoding +data = bytes.fromhex('A10A33') # map(1): {10: -20} +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_MAP) and len(obj.val) == 1 and remainder == b'' + ++ CBOR Float Specific Encodings + += float64 encoding specific value +obj = CBOR_FLOAT(1.5e20) +data = bytes(obj) +data == bytes.fromhex('FB442043561A882930') + += float64 decoding specific value +data = bytes.fromhex('FB442043561A882930') +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_FLOAT) and obj.val == 1.5e20 and remainder == b'' + ++ CBOR Multiple Item Decoding + += decode multiple items in sequence +data = bytes.fromhex('010203') # Three unsigned integers: 1, 2, 3 +obj1, remainder1 = CBOR_Codecs.CBOR.dec(data) +obj2, remainder2 = CBOR_Codecs.CBOR.dec(remainder1) +obj3, remainder3 = CBOR_Codecs.CBOR.dec(remainder2) +obj1.val == 1 and obj2.val == 2 and obj3.val == 3 and remainder3 == b'' + += decode nested array with specific encoding +data = bytes.fromhex('8201820203') # array(2): [1, array(2): [2, 3]] +obj, remainder = CBOR_Codecs.CBOR.dec(data) +isinstance(obj, CBOR_ARRAY) and len(obj.val) == 2 and remainder == b'' and isinstance(obj.val[1], CBOR_ARRAY) + ++ CBOR Boundary Value Tests + += encode maximum value that fits in each size +# Maximum for size 0 (0-23) +obj = CBOR_UNSIGNED_INTEGER(23) +bytes(obj) == bytes.fromhex('17') + += encode minimum value needing size 1 +obj = CBOR_UNSIGNED_INTEGER(24) +bytes(obj) == bytes.fromhex('1818') + += encode maximum value for size 1 +obj = CBOR_UNSIGNED_INTEGER(255) +bytes(obj) == bytes.fromhex('18ff') + += encode minimum value needing size 2 +obj = CBOR_UNSIGNED_INTEGER(256) +bytes(obj) == bytes.fromhex('190100') + += negative integer boundary at -24 +obj = CBOR_NEGATIVE_INTEGER(-24) +bytes(obj) == bytes.fromhex('37') + += negative integer boundary at -25 +obj = CBOR_NEGATIVE_INTEGER(-25) +bytes(obj) == bytes.fromhex('3818') + ++ CBOR Empty Container Tests + += encode empty array +from scapy.cbor.cborcodec import CBORcodec_ARRAY +encoded = CBORcodec_ARRAY.enc([]) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_ARRAY) and len(decoded.val) == 0 + += encode empty map +from scapy.cbor.cborcodec import CBORcodec_MAP +encoded = CBORcodec_MAP.enc({}) +decoded, _ = CBOR_Codecs.CBOR.dec(encoded) +isinstance(decoded, CBOR_MAP) and len(decoded.val) == 0 + += encode empty byte string +obj = CBOR_BYTE_STRING(b'') +data = bytes(obj) +data == bytes.fromhex('40') + += encode empty text string +obj = CBOR_TEXT_STRING('') +data = bytes(obj) +data == bytes.fromhex('60') diff --git a/tox.ini b/tox.ini index a013a8b9807..442ba9fdab1 100644 --- a/tox.ini +++ b/tox.ini @@ -33,6 +33,7 @@ deps = cryptography coverage[toml] python-can + cbor2 scapy-rpc # disabled on windows because they require c++ dependencies # brotli 1.1.0 broken https://github.com/google/brotli/issues/1072