Internet Engineering Task Force A. Luis Bustamante Internet-Draft Internet of Thinger SL Intended status: Standards Track 30 March 2026 Expires: 1 October 2026 Packed Sensor Object Notation (PSON) draft-bustamante-pson-00 Abstract PSON (Packed Sensor Object Notation) is a compact binary data encoding format designed for IoT environments where bandwidth, memory, and processing power are constrained. PSON efficiently represents the data types commonly found in sensor telemetry and device interaction: integers, floating-point numbers, booleans, strings, binary blobs, arrays, and key-value maps. It achieves significant size reductions over JSON (40-75% for typical IoT payloads) through inline small value encoding and variable-length integers. PSON is self-describing (no external schema required for decoding) and designed for minimal implementation complexity on microcontrollers. Status of This Memo This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet- Drafts is at https://datatracker.ietf.org/drafts/current/. Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." This Internet-Draft will expire on 1 October 2026. Copyright Notice Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/ license-info) in effect on the date of publication of this document. Luis Bustamante Expires 1 October 2026 [Page 1] Internet-Draft PSON March 2026 Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 3 1.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2. Background . . . . . . . . . . . . . . . . . . . . . . . 3 1.3. Relationship to CBOR . . . . . . . . . . . . . . . . . . 4 1.4. Relationship to MessagePack . . . . . . . . . . . . . . . 5 2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 5 3. Design Goals . . . . . . . . . . . . . . . . . . . . . . . . 5 4. Tag Byte Structure . . . . . . . . . . . . . . . . . . . . . 6 5. Wire Types . . . . . . . . . . . . . . . . . . . . . . . . . 6 6. Encoding Rules . . . . . . . . . . . . . . . . . . . . . . . 7 6.1. Unsigned Integers (Wire Type 0) . . . . . . . . . . . . . 7 6.2. Signed Integers (Wire Type 1) . . . . . . . . . . . . . . 8 6.3. Floating-Point Numbers (Wire Type 2) . . . . . . . . . . 8 6.4. Discrete Values (Wire Type 3) . . . . . . . . . . . . . . 9 6.5. Strings (Wire Type 4) . . . . . . . . . . . . . . . . . . 10 6.6. Binary Data (Wire Type 5) . . . . . . . . . . . . . . . . 10 6.7. Maps / Objects (Wire Type 6) . . . . . . . . . . . . . . 11 6.8. Arrays (Wire Type 7) . . . . . . . . . . . . . . . . . . 11 7. Byte Ordering . . . . . . . . . . . . . . . . . . . . . . . . 12 8. Varint Encoding . . . . . . . . . . . . . . . . . . . . . . . 12 8.1. Encoding Algorithm . . . . . . . . . . . . . . . . . . . 12 8.2. Examples . . . . . . . . . . . . . . . . . . . . . . . . 13 8.3. Maximum Varint Size . . . . . . . . . . . . . . . . . . . 13 9. Size Limits . . . . . . . . . . . . . . . . . . . . . . . . . 13 10. Encoding Optimizations . . . . . . . . . . . . . . . . . . . 14 10.1. Float-to-Integer Promotion . . . . . . . . . . . . . . . 14 10.2. Precision Selection . . . . . . . . . . . . . . . . . . 15 11. PSON vs JSON Size Comparison . . . . . . . . . . . . . . . . 15 12. Complete Encoding Examples . . . . . . . . . . . . . . . . . 16 12.1. Simple Sensor Reading . . . . . . . . . . . . . . . . . 16 12.2. Device Credentials . . . . . . . . . . . . . . . . . . . 16 12.3. Boolean Configuration . . . . . . . . . . . . . . . . . 16 12.4. Nested Structure . . . . . . . . . . . . . . . . . . . . 16 13. Security Considerations . . . . . . . . . . . . . . . . . . . 17 13.1. Input Validation . . . . . . . . . . . . . . . . . . . . 17 13.2. Denial of Service . . . . . . . . . . . . . . . . . . . 17 13.3. Confidentiality . . . . . . . . . . . . . . . . . . . . 18 14. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 18 14.1. Media Type Registration . . . . . . . . . . . . . . . . 18 15. References . . . . . . . . . . . . . . . . . . . . . . . . . 18 Luis Bustamante Expires 1 October 2026 [Page 2] Internet-Draft PSON March 2026 15.1. Normative References . . . . . . . . . . . . . . . . . . 18 15.2. Informative References . . . . . . . . . . . . . . . . . 19 Appendix A. Conformance Test Vectors . . . . . . . . . . . . . . 19 A.1. Integer Test Vectors . . . . . . . . . . . . . . . . . . 19 A.2. Floating-Point Test Vectors . . . . . . . . . . . . . . . 20 A.3. Discrete Test Vectors . . . . . . . . . . . . . . . . . . 20 A.4. String Test Vectors . . . . . . . . . . . . . . . . . . . 20 A.5. Container Test Vectors . . . . . . . . . . . . . . . . . 21 A.6. Composite Test Vector . . . . . . . . . . . . . . . . . . 21 Appendix B. Implementation Complexity Comparison . . . . . . . . 21 B.1. Source Code Size . . . . . . . . . . . . . . . . . . . . 21 B.2. Compiled Binary Size (ESP32, ESP-IDF v5.4) . . . . . . . 22 B.3. Encoded Wire Size . . . . . . . . . . . . . . . . . . . . 22 B.4. Encoding and Decoding Performance (ESP32, 240 MHz) . . . 23 B.5. Summary . . . . . . . . . . . . . . . . . . . . . . . . . 24 Appendix C. Revision History . . . . . . . . . . . . . . . . . . 24 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 25 1. Introduction 1.1. Purpose PSON is a binary serialization format that provides a compact, self- describing encoding for structured data. It is designed as a drop-in binary replacement for JSON in environments where bandwidth and processing resources are limited -- particularly IoT devices, embedded systems, and constrained networks. PSON has been deployed in production IoT systems since 2015 as the native encoding format of the Thinger.io platform, processing sensor data across thousands of connected devices. This specification formalizes the encoding that has been validated through a decade of production use. 1.2. Background IoT devices frequently exchange structured data: sensor readings, configuration parameters, device metadata, and command payloads. JSON [RFC8259] is the de facto standard for structured data interchange but imposes significant overhead on constrained devices: * *Verbose encoding:* Field names and values are repeated as text in every message. * *Parsing cost:* Text parsing requires string-to-number conversion and delimiter scanning. * *Size inefficiency:* Common IoT values like small integers, booleans, and short strings are disproportionately expensive in JSON. Luis Bustamante Expires 1 October 2026 [Page 3] Internet-Draft PSON March 2026 PSON addresses these issues through a binary encoding that preserves JSON's self-describing nature while dramatically reducing wire size and parsing complexity. 1.3. Relationship to CBOR PSON shares structural similarities with CBOR [RFC8949]. Both use a tag byte with 3 bits for type identification and 5 bits for an inline value. However, PSON and CBOR make different design trade-offs reflecting different goals: CBOR is a general-purpose binary encoding designed for broad applicability; PSON is a minimal encoding designed specifically for integration with the IOTMP protocol on constrained IoT devices. *Protocol-Encoding Integration.* PSON's primary design motivation is architectural. PSON and the IOTMP protocol share identical encoding primitives: the same tag-byte structure (3-bit type + 5-bit inline value) and the same varint encoding for variable-length integers. A single encoder/decoder implementation on a constrained device serves both the protocol framing layer and the application data layer. Adopting CBOR would require maintaining two independent type systems -- CBOR's major types alongside IOTMP's own field encoding -- increasing code size and complexity for no functional benefit on these devices. *Reduced Scope.* CBOR is designed to cover a wide range of use cases through extensibility mechanisms that add implementation complexity: * *Semantic tags* (CBOR major type 6): Over 350 IANA-registered tags for dates, bignums, decimal fractions, URIs, regular expressions, and more. Each tag number can be 1-9 bytes. Decoders must handle unknown tags gracefully. PSON has no semantic tag system -- the 8 wire types cover all data types needed for IoT applications. * *Indefinite-length encoding*: CBOR allows arrays, maps, and strings of unknown length at encode time, terminated by a break stop code (0xFF). This roughly doubles the decoder's state machine complexity. PSON requires definite lengths, which is sufficient for IoT data that is typically fully known in memory before encoding. * *Half-precision floats* (IEEE 754 binary16): CBOR supports 16-bit, 32-bit, and 64-bit floats, with a preferred serialization rule that requires testing each value against all three sizes. Most platforms lack native float16 support, requiring dedicated conversion routines. PSON supports only 32-bit and 64-bit floats -- the two sizes natively available on all target platforms. Luis Bustamante Expires 1 October 2026 [Page 4] Internet-Draft PSON March 2026 * *Simple values*: CBOR defines a 256-value extensible space for simple values (currently: false, true, null, undefined, plus unassigned slots). PSON defines exactly three discrete values: false, true, and null. * *Arbitrary map key types*: CBOR allows any data type as a map key (integers, arrays, nested maps). PSON restricts map keys to strings, matching JSON's object model and simplifying decoder implementation. *Wire Size.* In terms of encoded size, PSON and CBOR produce comparable results for typical IoT data. PSON encodes unsigned integers 0-30 in a single byte (vs. CBOR's 0-23), providing a modest advantage for values 24-30. For most payloads, the size difference between PSON and CBOR is negligible. The justification for PSON is not superior compression -- it is the reduced implementation complexity and the shared encoding primitives with IOTMP. *Byte Ordering.* CBOR encodes all multi-byte values in big-endian (network byte order). PSON uses little-endian for floating-point values, matching the native byte order of virtually all IoT microcontrollers (ARM Cortex-M, ESP32, RISC-V). See Section 7. 1.4. Relationship to MessagePack MessagePack [MessagePack] is another binary encoding with similar goals. Like CBOR, it uses big-endian byte ordering and a more complex type system (with multiple fixed-width integer sizes and format families). PSON's simpler tag-byte design and varint-based overflow mechanism result in a smaller and more uniform implementation. 2. Terminology The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. 3. Design Goals * *Compactness:* Minimize encoded size for typical IoT data (sensor readings, configuration, commands). * *Self-describing:* No external schema required for decoding. The wire type and structure are embedded in the data. * *Simplicity:* Minimal implementation complexity. Encodable/ decodable in a single pass with no lookahead. Luis Bustamante Expires 1 October 2026 [Page 5] Internet-Draft PSON March 2026 * *JSON superset:* PSON represents all JSON data types (objects, arrays, strings, numbers, booleans, null) plus native binary data (wire type 5), enabling lossless round-trip conversion from JSON to PSON. Binary data has no JSON equivalent and requires application-level encoding (e.g., Base64) when converting to JSON. * *Protocol integration:* Share encoding primitives (tag-byte structure, varint encoding) with IOTMP, enabling a single encoder/ decoder to serve both protocol framing and application data. 4. Tag Byte Structure Every PSON value begins with a single tag byte that encodes both the data type and, for small values, the value itself: +-------------+--------------+ | Wire Type | Inline Value | | (bits 7-5) | (bits 4-0) | +-------------+--------------+ * *Wire Type* (3 most significant bits): Identifies the data type (0-7). * *Inline Value* (5 least significant bits): For values 0-30, the value is stored directly in the tag byte, requiring no additional bytes. If the inline value is 31 (0x1F mask), the actual value follows the tag byte as a varint. The tag byte formula is: tag = (wire_type << 5) | inline_value. This design means that common small values (integers 0-30, short strings, small maps and arrays) are encoded in the minimum possible space. 5. Wire Types +=======+============+===========+===========================+ | Value | Name | Tag Range | Description | +=======+============+===========+===========================+ | 0 | unsigned_t | 0x00-0x1F | Unsigned integer. Inline | | | | | 0-30 or varint. | +-------+------------+-----------+---------------------------+ | 1 | signed_t | 0x20-0x3F | Negative integer. Stored | | | | | as absolute value. | +-------+------------+-----------+---------------------------+ | 2 | floating_t | 0x40-0x5F | IEEE 754 float (inline=0) | | | | | or double (inline=1). | +-------+------------+-----------+---------------------------+ | 3 | discrete_t | 0x60-0x7F | Boolean or null. 0=false, | | | | | 1=true, 2=null. | Luis Bustamante Expires 1 October 2026 [Page 6] Internet-Draft PSON March 2026 +-------+------------+-----------+---------------------------+ | 4 | string_t | 0x80-0x9F | UTF-8 string. Inline or | | | | | varint = byte length. | +-------+------------+-----------+---------------------------+ | 5 | bytes_t | 0xA0-0xBF | Raw binary data. Inline | | | | | or varint = byte length. | +-------+------------+-----------+---------------------------+ | 6 | map_t | 0xC0-0xDF | Key-value map. Inline or | | | | | varint = entry count. | +-------+------------+-----------+---------------------------+ | 7 | array_t | 0xE0-0xFF | Ordered array. Inline or | | | | | varint = element count. | +-------+------------+-----------+---------------------------+ Table 1 Wire types 0-7 are defined by this specification. No additional wire types can be defined (the 3-bit field is fully allocated). 6. Encoding Rules 6.1. Unsigned Integers (Wire Type 0) Unsigned integers represent non-negative whole numbers. * *Values 0-30:* Encoded in a single byte: (0 << 5) | value. * *Values >= 31:* Tag byte is 0x1F, followed by the value encoded as a varint Section 8. Examples: +=======+===============+=========+ | Value | Encoded (hex) | Size | +=======+===============+=========+ | 0 | 00 | 1 byte | +-------+---------------+---------+ | 5 | 05 | 1 byte | +-------+---------------+---------+ | 30 | 1E | 1 byte | +-------+---------------+---------+ | 31 | 1F 1F | 2 bytes | +-------+---------------+---------+ | 127 | 1F 7F | 2 bytes | +-------+---------------+---------+ | 300 | 1F AC 02 | 3 bytes | +-------+---------------+---------+ Table 2 Luis Bustamante Expires 1 October 2026 [Page 7] Internet-Draft PSON March 2026 6.2. Signed Integers (Wire Type 1) Negative integers are encoded using wire type 1 with the absolute value. Decoding: negate the stored value. * *Values -1 to -30:* Single byte: (1 << 5) | abs(value). * *Values <= -31:* Tag byte 0x3F, followed by the absolute value as a varint. Zero and positive integers MUST use wire type 0 (unsigned_t). Wire type 1 is exclusively for negative values. Encoding zero as signed_t (tag byte 0x20) is invalid and a decoder MUST treat it as an error. Examples: +=======+===============+=========+ | Value | Encoded (hex) | Size | +=======+===============+=========+ | -1 | 21 | 1 byte | +-------+---------------+---------+ | -15 | 2F | 1 byte | +-------+---------------+---------+ | -30 | 3E | 1 byte | +-------+---------------+---------+ | -31 | 3F 1F | 2 bytes | +-------+---------------+---------+ | -300 | 3F AC 02 | 3 bytes | +-------+---------------+---------+ Table 3 6.3. Floating-Point Numbers (Wire Type 2) IEEE 754 [IEEE754] floating-point values. The inline value selects the precision: +==============+=====================+=====================+ | Inline Value | Precision | Bytes Following Tag | +==============+=====================+=====================+ | 0 | 32-bit float (IEEE | 4 bytes, little- | | | 754 binary32) | endian | +--------------+---------------------+---------------------+ | 1 | 64-bit double (IEEE | 8 bytes, little- | | | 754 binary64) | endian | +--------------+---------------------+---------------------+ Table 4 Luis Bustamante Expires 1 October 2026 [Page 8] Internet-Draft PSON March 2026 Inline values 2-30 are reserved for future use. Inline value 31 (varint extension) is not used for this wire type. A decoder that encounters inline value 31 or any reserved inline value (2-30) for this wire type MUST treat it as a decode error. *Negative zero:* IEEE 754 defines negative zero (-0.0) as distinct from positive zero (+0.0). Encoders MUST encode -0.0 as a floating- point value (wire type 2), not as unsigned integer zero (wire type 0). Decoders MUST preserve the sign of zero when converting from PSON to IEEE 754. *NaN and Infinity:* Encoders MUST encode NaN and Infinity values as 32-bit or 64-bit floats (wire type 2). These values MUST NOT be promoted to integers. Encoders SHOULD use a canonical NaN representation (quiet NaN with all-zero payload) when the specific NaN payload is not significant. Examples: +=============+============================+===============+ | Value | Encoded (hex) | Notes | +=============+============================+===============+ | 3.14 | 40 C3 F5 48 40 | 32-bit float | +-------------+----------------------------+---------------+ | 3.141592653 | 41 38 E9 2F 54 FB 21 09 40 | 64-bit double | +-------------+----------------------------+---------------+ Table 5 6.4. Discrete Values (Wire Type 3) Boolean and null values. +==============+=========+===============+ | Inline Value | Meaning | Encoded (hex) | +==============+=========+===============+ | 0 | false | 60 | +--------------+---------+---------------+ | 1 | true | 61 | +--------------+---------+---------------+ | 2 | null | 62 | +--------------+---------+---------------+ Table 6 Luis Bustamante Expires 1 October 2026 [Page 9] Internet-Draft PSON March 2026 Inline values 3-30 are reserved for future use. Inline value 31 (varint extension) is not used for this wire type. A decoder that encounters inline value 31 or any reserved inline value (3-30) for this wire type MUST treat it as a decode error. 6.5. Strings (Wire Type 4) UTF-8 encoded text strings. The inline value (or subsequent varint if inline = 31) indicates the byte length of the string. The string bytes follow immediately, with no null terminator. * *Strings of 0-30 bytes:* Single tag byte with inline length, followed by the string bytes. * *Strings of >= 31 bytes:* Tag byte 0x9F, followed by a varint length, followed by the string bytes. Implementations MUST encode strings as valid UTF-8. A decoder that encounters invalid UTF-8 sequences SHOULD treat it as a decode error. Examples: +===============+=====================================+==========+ | Value | Encoded (hex) | Size | +===============+=====================================+==========+ | "" (empty) | 80 | 1 byte | +---------------+-------------------------------------+----------+ | "hi" | 82 68 69 | 3 bytes | +---------------+-------------------------------------+----------+ | "temperature" | 8B 74 65 6D 70 65 72 61 74 75 72 65 | 12 bytes | +---------------+-------------------------------------+----------+ Table 7 6.6. Binary Data (Wire Type 5) Raw binary (opaque byte sequences). Encoding is identical to strings: the inline value (or varint) indicates byte length, followed by the raw bytes. * *0-30 bytes:* Single tag byte with inline length, followed by data. * *>= 31 bytes:* Tag byte 0xBF, followed by a varint length, followed by data. Unlike strings, binary data has no encoding requirement (no UTF-8 constraint). Luis Bustamante Expires 1 October 2026 [Page 10] Internet-Draft PSON March 2026 6.7. Maps / Objects (Wire Type 6) Key-value maps (equivalent to JSON objects). The inline value (or varint) indicates the number of key-value pairs. Each entry consists of: 1. A PSON string (the key). Map keys MUST be strings. 2. A PSON value (any wire type, including nested maps and arrays). * *0-30 entries:* Single tag byte with inline count. * *>= 31 entries:* Tag byte 0xDF, followed by a varint count. *Key ordering:* PSON does not define a canonical key ordering. However, implementations SHOULD preserve insertion order to support use cases where iteration order is significant (e.g., compact streaming mode in IOTMP). *Duplicate keys:* A PSON map SHOULD NOT contain duplicate keys. A decoder that encounters duplicate keys SHOULD reject the map or use the last value for the duplicated key. The behavior for duplicate keys is undefined and implementations MUST NOT rely on it. Example -- {"temp": 25, "hum": 60}: C2 # map_t, 2 entries 84 74 65 6D 70 # string "temp" (key, 4 bytes) 19 # unsigned 25 (value) 83 68 75 6D # string "hum" (key, 3 bytes) 1F 3C # unsigned 60 (varint) Total: 13 bytes. Equivalent JSON {"temp":25,"hum":60} = 20 bytes (35% savings). 6.8. Arrays (Wire Type 7) Ordered sequences of values (equivalent to JSON arrays). The inline value (or varint) indicates the number of elements. Each element is a PSON-encoded value (any wire type). * *0-30 elements:* Single tag byte with inline count. * *>= 31 elements:* Tag byte 0xFF, followed by a varint count. Example -- [1, 2, 3]: Luis Bustamante Expires 1 October 2026 [Page 11] Internet-Draft PSON March 2026 E3 # array_t, 3 elements 01 # unsigned 1 02 # unsigned 2 03 # unsigned 3 Total: 4 bytes. Equivalent JSON [1,2,3] = 7 bytes (43% savings). 7. Byte Ordering All multi-byte floating-point values (float32, float64) MUST be encoded in little-endian byte order (least significant byte first). *Rationale:* Traditional Internet protocols use big-endian ("network byte order"), a convention established when dominant networking hardware was big-endian. Today, virtually all microcontrollers and processors used in IoT are little-endian: ARM Cortex-M, ESP32 (Xtensa), RISC-V, and x86. Little-endian encoding allows constrained devices to write float and double values directly from memory to the wire with no conversion. On the most constrained ARM cores (Cortex- M0/M0+), which lack a hardware byte-reversal instruction (REV), byte swapping a 32-bit float requires 4 instructions per value. Integer values use varint encoding Section 8, which is byte-order independent by design. This is the same design choice made by Protocol Buffers [ProtocolBuffers], which encodes fixed-width floats and doubles in little-endian regardless of platform. 8. Varint Encoding PSON uses Protocol Buffers-style variable-length integer encoding for values that exceed the inline capacity (>= 31). 8.1. Encoding Algorithm * Each byte uses bits [6:0] for data (7 bits per byte). * Bit [7] is a continuation flag: set (1) if more bytes follow, clear (0) for the last byte. * Least significant group comes first (little-endian byte order). Luis Bustamante Expires 1 October 2026 [Page 12] Internet-Draft PSON March 2026 8.2. Examples +=========+========+====================+=========+ | Decimal | Hex | Varint Bytes (hex) | Size | +=========+========+====================+=========+ | 0 | 0x00 | 00 | 1 byte | +---------+--------+--------------------+---------+ | 1 | 0x01 | 01 | 1 byte | +---------+--------+--------------------+---------+ | 127 | 0x7F | 7F | 1 byte | +---------+--------+--------------------+---------+ | 128 | 0x80 | 80 01 | 2 bytes | +---------+--------+--------------------+---------+ | 300 | 0x012C | AC 02 | 2 bytes | +---------+--------+--------------------+---------+ | 16384 | 0x4000 | 80 80 01 | 3 bytes | +---------+--------+--------------------+---------+ Table 8 8.3. Maximum Varint Size Implementations MUST support varints up to 10 bytes, representing values up to 2^64 - 1 (the full uint64 range). A 64-bit unsigned integer requires at most 10 varint bytes (ceil(64/7) = 10). A receiver that encounters a varint that does not terminate within 10 bytes MUST treat it as a decode error and discard the data. 9. Size Limits * *Strings and binary data:* The maximum byte length is constrained by the varint encoding (up to 2^64 - 1). In practice, implementations SHOULD impose limits appropriate to available memory. A RECOMMENDED limit for constrained devices is 65,535 bytes (16-bit addressable). * *Maps and arrays:* Element counts have no protocol-enforced limit beyond the varint range, but implementations SHOULD impose reasonable limits based on available memory. * *Nesting depth:* Implementations SHOULD limit nesting depth to prevent stack overflow. A limit of 16 levels is RECOMMENDED for constrained devices. Luis Bustamante Expires 1 October 2026 [Page 13] Internet-Draft PSON March 2026 10. Encoding Optimizations The following optimizations are RECOMMENDED for encoders but are not required for protocol conformance. A conformant decoder MUST be able to decode data produced by any conformant encoder, whether or not these optimizations are applied. 10.1. Float-to-Integer Promotion When encoding a floating-point number that has no fractional part and whose absolute value fits within the uint64 range (0 to 2^64-1), implementations SHOULD encode it as an unsigned integer (wire type 0) or signed integer (wire type 1) instead of a float (wire type 2). This saves 3-7 bytes per value and exploits the fact that many IoT sensor readings are whole numbers (e.g., humidity percentages, relay states, counters). This promotion MUST NOT be applied to negative zero (-0.0), NaN, or Infinity values. A decoder that receives an integer where a float was expected MUST convert it to the appropriate floating-point type. This promotion is transparent to the application. Size savings: +=======+===================+=======================+=========+ | Value | As float (type 2) | As integer (type 0/1) | Savings | +=======+===================+=======================+=========+ | 0.0 | 5 bytes | 1 byte | 4 bytes | +-------+-------------------+-----------------------+---------+ | 25.0 | 5 bytes | 1 byte | 4 bytes | +-------+-------------------+-----------------------+---------+ | -3.0 | 5 bytes | 1 byte | 4 bytes | +-------+-------------------+-----------------------+---------+ | 100.0 | 5 bytes | 3 bytes | 2 bytes | +-------+-------------------+-----------------------+---------+ Table 9 *Note:* CBOR's core specification ([RFC8949], Section 4.2.2) treats float-to-integer promotion as optional and application-dependent. PSON follows the same approach: this optimization is RECOMMENDED but not required for conformance. Luis Bustamante Expires 1 October 2026 [Page 14] Internet-Draft PSON March 2026 10.2. Precision Selection When encoding a floating-point value that requires fractional precision, implementations SHOULD choose 32-bit float (inline value 0) when the float32 representation of the value is identical to the original float64 value. Otherwise, 64-bit double (inline value 1) MUST be used. This saves 4 bytes per value. More precisely: an encoder SHOULD encode a float64 value v as float32 if and only if (float64)(float32)v == v (the round-trip through float32 preserves the exact value). 11. PSON vs JSON Size Comparison The following table compares encoded sizes for common IoT data patterns: +====================================+=========+=========+=========+ | Data | JSON | PSON | Savings | | | (bytes) | (bytes) | | +====================================+=========+=========+=========+ | 25 | 2 | 1 | 50% | +------------------------------------+---------+---------+---------+ | true | 4 | 1 | 75% | +------------------------------------+---------+---------+---------+ | null | 4 | 1 | 75% | +------------------------------------+---------+---------+---------+ | "hello" | 7 | 6 | 14% | +------------------------------------+---------+---------+---------+ | {"temp":25,"hum":60} | 20 | 13 | 35% | +------------------------------------+---------+---------+---------+ | {"temp":25.3,"hum":60.1,"co2":412} | 34 | 27 | 21% | +------------------------------------+---------+---------+---------+ | [1,2,3,4,5] | 11 | 6 | 45% | +------------------------------------+---------+---------+---------+ Table 10 Note: PSON sizes assume float32 precision for floating-point values, which is typical for IoT sensor data. JSON sizes use compact encoding with no whitespace. For typical IoT payloads (maps with numeric sensor values), PSON achieves 21-75% size reduction compared to JSON. Luis Bustamante Expires 1 October 2026 [Page 15] Internet-Draft PSON March 2026 12. Complete Encoding Examples 12.1. Simple Sensor Reading JSON: {"temperature": 23.5, "humidity": 60} C2 # map_t, 2 entries 8B 74 65 6D 70 65 72 61 # string "temperature" 74 75 72 65 # (key, 11 bytes) 40 00 00 BC 41 # float 23.5 (32-bit, LE) 88 68 75 6D 69 64 69 # string "humidity" 74 79 # (key, 8 bytes) 1F 3C # unsigned 60 (varint) JSON size: 34 bytes. PSON size: 29 bytes. Savings: 15%. 12.2. Device Credentials JSON: ["user", "device1", "secretkey"] E3 # array_t, 3 elements 84 75 73 65 72 # string "user" (4 bytes) 87 64 65 76 69 63 65 31 # string "device1" (7 bytes) 89 73 65 63 72 65 74 # string "secretkey" 6B 65 79 # (9 bytes) JSON size: 30 bytes. PSON size: 24 bytes. Savings: 20%. 12.3. Boolean Configuration JSON: {"enabled": true, "debug": false} C2 # map_t, 2 entries 87 65 6E 61 62 6C 65 64 # string "enabled" (7 bytes) 61 # true 85 64 65 62 75 67 # string "debug" (5 bytes) 60 # false JSON size: 30 bytes. PSON size: 17 bytes. Savings: 43%. 12.4. Nested Structure JSON: {"gps": {"lat": 40.4168, "lon": -3.7038}, "alt": 650} Luis Bustamante Expires 1 October 2026 [Page 16] Internet-Draft PSON March 2026 C2 # map_t, 2 entries 83 67 70 73 # string "gps" (3 bytes) C2 # map_t, 2 entries (nested) 83 6C 61 74 # string "lat" (3 bytes) 41 85 7C D0 B3 # double 40.4168 59 35 44 40 # (64-bit, LE) 83 6C 6F 6E # string "lon" (3 bytes) 41 FE 65 F7 E4 # double -3.7038 61 A1 0D C0 # (64-bit, LE) 83 61 6C 74 # string "alt" (3 bytes) 1F 8A 05 # unsigned 650 (varint) JSON size: 47 bytes. PSON size: 39 bytes. Savings: 17%. (Note: GPS coordinates require double precision, limiting compaction. Integer values like altitude benefit most from PSON encoding.) 13. Security Considerations 13.1. Input Validation Implementations MUST validate PSON data during decoding: * *Length bounds:* String and binary lengths MUST NOT exceed the implementation's maximum Section 9. A decoder MUST NOT allocate memory based on an untrusted length field without bounds checking. * *Nesting depth:* Deeply nested maps and arrays can cause stack overflow on constrained devices. Implementations SHOULD enforce a maximum nesting depth. * *Map key uniqueness:* Implementations SHOULD reject maps with duplicate keys, as they may indicate an injection attempt. * *Varint termination:* A varint that does not terminate (every byte has the continuation bit set) constitutes a denial-of-service vector. Implementations MUST enforce the maximum varint length Section 8.3. * *Wire type validation:* A decoder MUST reject values with reserved inline values (e.g., floating_t with inline value > 1, discrete_t with inline value > 2). 13.2. Denial of Service PSON data from untrusted sources can be crafted to consume excessive resources: Luis Bustamante Expires 1 October 2026 [Page 17] Internet-Draft PSON March 2026 * *Large allocations:* A malicious length prefix can trigger large memory allocations. Implementations MUST impose application- appropriate limits and MUST NOT allocate memory based solely on untrusted length fields. * *Processing time:* Deeply nested structures can cause excessive processing time. Depth limits Section 9 mitigate this. 13.3. Confidentiality PSON does not provide encryption. When transmitting sensitive data, PSON MUST be used within an encrypted transport (e.g., TLS). 14. IANA Considerations 14.1. Media Type Registration This specification requests registration of the following media type in the "Media Types" registry: * Type name: application * Subtype name: pson * Required parameters: none * Optional parameters: none * Encoding considerations: binary * Security considerations: See Section 13 of this document * Interoperability considerations: PSON uses little-endian byte ordering for floating-point values, which differs from CBOR and most Internet protocols. Implementations on big-endian platforms MUST perform byte swapping when encoding and decoding floats and doubles. * Published specification: This document * Applications that use this media type: IoT device communication, sensor data encoding, IOTMP protocol payloads * Fragment identifier considerations: N/A * Contact: Alvaro Luis Bustamante, alvaro@thinger.io * Change controller: IETF 15. References 15.1. Normative References [IEEE754] IEEE, "IEEE Standard for Floating-Point Arithmetic", IEEE 754-2019, 2019. Luis Bustamante Expires 1 October 2026 [Page 18] Internet-Draft PSON March 2026 [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . 15.2. Informative References [MessagePack] Furuhashi, S., "MessagePack specification", . [ProtocolBuffers] Google, "Protocol Buffers Encoding", . [RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017, . [RFC8949] Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, December 2020, . Appendix A. Conformance Test Vectors The following test vectors allow implementations to verify correct encoding and decoding. Each entry shows a logical value and its expected PSON encoding in hexadecimal. A.1. Integer Test Vectors +==============+===============+=========+ | Value | Encoded (hex) | Size | +==============+===============+=========+ | Unsigned 0 | 00 | 1 byte | +--------------+---------------+---------+ | Unsigned 25 | 19 | 1 byte | +--------------+---------------+---------+ | Unsigned 30 | 1E | 1 byte | +--------------+---------------+---------+ | Unsigned 31 | 1F 1F | 2 bytes | +--------------+---------------+---------+ Luis Bustamante Expires 1 October 2026 [Page 19] Internet-Draft PSON March 2026 | Unsigned 300 | 1F AC 02 | 3 bytes | +--------------+---------------+---------+ | Signed -1 | 21 | 1 byte | +--------------+---------------+---------+ | Signed -30 | 3E | 1 byte | +--------------+---------------+---------+ | Signed -300 | 3F AC 02 | 3 bytes | +--------------+---------------+---------+ Table 11 A.2. Floating-Point Test Vectors +====================+============================+=========+ | Value | Encoded (hex) | Size | +====================+============================+=========+ | Float 23.5 | 40 00 00 BC 41 | 5 bytes | +--------------------+----------------------------+---------+ | Float 3.14 | 40 C3 F5 48 40 | 5 bytes | +--------------------+----------------------------+---------+ | Double 3.141592653 | 41 38 E9 2F 54 FB 21 09 40 | 9 bytes | +--------------------+----------------------------+---------+ Table 12 A.3. Discrete Test Vectors +=======+===============+========+ | Value | Encoded (hex) | Size | +=======+===============+========+ | False | 60 | 1 byte | +-------+---------------+--------+ | True | 61 | 1 byte | +-------+---------------+--------+ | Null | 62 | 1 byte | +-------+---------------+--------+ Table 13 A.4. String Test Vectors +===============+=====================================+==========+ | Value | Encoded (hex) | Size | +===============+=====================================+==========+ | "" (empty) | 80 | 1 byte | +---------------+-------------------------------------+----------+ | "hi" | 82 68 69 | 3 bytes | +---------------+-------------------------------------+----------+ Luis Bustamante Expires 1 October 2026 [Page 20] Internet-Draft PSON March 2026 | "temperature" | 8B 74 65 6D 70 65 72 61 74 75 72 65 | 12 bytes | +---------------+-------------------------------------+----------+ Table 14 A.5. Container Test Vectors +================+===============+=========+ | Value | Encoded (hex) | Size | +================+===============+=========+ | Empty map {} | C0 | 1 byte | +----------------+---------------+---------+ | Empty array [] | E0 | 1 byte | +----------------+---------------+---------+ | Array [1,2,3] | E3 01 02 03 | 4 bytes | +----------------+---------------+---------+ Table 15 A.6. Composite Test Vector Map {"temp": 25, "hum": 60} -- 13 bytes total: C2 # map_t, 2 entries 84 74 65 6D 70 # string "temp" (key, 4 bytes) 19 # unsigned 25 (value) 83 68 75 6D # string "hum" (key, 3 bytes) 1F 3C # unsigned 60 (varint) Hex: C2 84 74 65 6D 70 19 83 68 75 6D 1F 3C Appendix B. Implementation Complexity Comparison This appendix provides a quantitative comparison of implementation complexity between PSON and two widely-used CBOR libraries for constrained devices. All measurements use the same test payload: {"temperature": 23.5, "humidity": 60, "pressure": 1013, "label": "outdoor"}. B.1. Source Code Size +============================+==================================+ | Implementation | Source Lines (encoder + decoder) | +============================+==================================+ | PSON (standalone C) | 344 | +----------------------------+----------------------------------+ | NanoCBOR (C, minimal CBOR) | 2,223 | +----------------------------+----------------------------------+ Luis Bustamante Expires 1 October 2026 [Page 21] Internet-Draft PSON March 2026 | TinyCBOR (C, Intel) | 5,619 | +----------------------------+----------------------------------+ Table 16 PSON's reduced source size results from its narrower scope: 8 wire types with a single varint overflow mechanism, no semantic tags, no indefinite-length encoding, no half-precision floats, and string-only map keys. B.2. Compiled Binary Size (ESP32, ESP-IDF v5.4) All projects compiled for ESP32 using espressif/idf:v5.4 Docker image with default optimization (-Og). No networking, no TLS -- pure encoding benchmark. +==========+==============+==========+=========+============+ | Impl. | Library | App Code | Total | Full Image | +==========+==============+==========+=========+============+ | PSON | 0 B (inline) | 2,589 B | 2,589 B | 195,044 B | +----------+--------------+----------+---------+------------+ | NanoCBOR | 2,306 B | 1,510 B | 3,816 B | 196,076 B | +----------+--------------+----------+---------+------------+ | TinyCBOR | 4,445 B | 2,159 B | 6,604 B | 200,888 B | +----------+--------------+----------+---------+------------+ Table 17 PSON's compiled encoding code is 32% smaller than NanoCBOR and 61% smaller than TinyCBOR for identical functionality. Full image sizes differ by less than 3% because the ESP-IDF base (FreeRTOS, HAL, libc) dominates at ~190 KB. NanoCBOR is the most minimal CBOR implementation available for constrained devices. The difference with TinyCBOR illustrates how CBOR's broader feature set (validation, pretty-printing, JSON conversion) increases footprint even when those features are not used by the application. B.3. Encoded Wire Size All three encodings produce comparable wire sizes for the same payload: Luis Bustamante Expires 1 October 2026 [Page 22] Internet-Draft PSON March 2026 +================+==============+===================================+ | Implementation | Encoded Size | Notes | +================+==============+===================================+ | PSON | 55 bytes | float32 for 23.5 | +----------------+--------------+-----------------------------------+ | NanoCBOR | 53 bytes | float16 for 23.5 | | | | (half-precision) | +----------------+--------------+-----------------------------------+ | TinyCBOR | 55 bytes | float32 for 23.5 | +----------------+--------------+-----------------------------------+ Table 18 The 2-byte difference with NanoCBOR is due to CBOR's half-precision float support (IEEE 754 binary16), which PSON deliberately omits to avoid the implementation complexity of float16 conversion routines. For typical IoT payloads, wire size differences between PSON and CBOR are negligible. B.4. Encoding and Decoding Performance (ESP32, 240 MHz) All benchmarks executed on the same ESP32 hardware (Xtensa LX6, 240 MHz, single core used). Each measurement averages 1,000,000 iterations of encoding or decoding the test payload. +================+==================+==================+ | Implementation | Encode (us/iter) | Decode (us/iter) | +================+==================+==================+ | PSON | 10.00 | 5.93 | +----------------+------------------+------------------+ | NanoCBOR | 19.65 | 16.86 | +----------------+------------------+------------------+ | TinyCBOR | 20.04 | 52.78 | +----------------+------------------+------------------+ Table 19 *Encoding:* PSON encodes approximately 2x faster than both CBOR implementations. PSON's single-pass encoder with varint overflow requires fewer branches than CBOR's fixed-width (1/2/4/8 byte) argument encoding. Luis Bustamante Expires 1 October 2026 [Page 23] Internet-Draft PSON March 2026 *Decoding:* PSON decodes 2.8x faster than NanoCBOR and 8.9x faster than TinyCBOR. The decoder benefits from the simpler tag structure (no indefinite-length containers to check, no semantic tags to skip, no half-precision float conversion, string-only map keys). TinyCBOR's significantly slower decoding reflects its more complex parser, which must handle the full CBOR data model including container tracking and validation. These performance differences are a direct consequence of PSON's reduced format complexity, not implementation quality -- NanoCBOR is a well-optimized, production-quality CBOR library specifically designed for constrained devices. B.5. Summary +===============+=======================+==================+ | Metric | PSON vs NanoCBOR | PSON vs TinyCBOR | +===============+=======================+==================+ | Wire size | Comparable (+2 bytes) | Equal | +---------------+-----------------------+------------------+ | Compiled code | 32% smaller | 61% smaller | +---------------+-----------------------+------------------+ | Encode speed | 2.0x faster | 2.0x faster | +---------------+-----------------------+------------------+ | Decode speed | 2.8x faster | 8.9x faster | +---------------+-----------------------+------------------+ | Source lines | 6.5x fewer | 16.3x fewer | +---------------+-----------------------+------------------+ Table 20 *Note:* The primary justification for PSON is not performance superiority over CBOR, but the architectural integration with the IOTMP protocol -- shared encoding primitives eliminate the need for two independent type systems on constrained devices (see Section 1.3). The performance and complexity advantages documented here are a consequence of that narrower design scope. Appendix C. Revision History +=========+============+=======================+ | Version | Date | Changes | +=========+============+=======================+ | 0.1 | 2026-03-30 | Initial public draft. | +---------+------------+-----------------------+ Table 21 Luis Bustamante Expires 1 October 2026 [Page 24] Internet-Draft PSON March 2026 Author's Address Alvaro Luis Bustamante Internet of Thinger SL Spain Email: alvaro@thinger.io Luis Bustamante Expires 1 October 2026 [Page 25]