Internet Engineering Task Force A. Luis Bustamante Internet-Draft Internet of Thinger SL Intended status: Standards Track 30 March 2026 Expires: 1 October 2026 Internet of Things Message Protocol (IOTMP) draft-bustamante-iotmp-00 Abstract IOTMP (Internet of Things Message Protocol) is a compact, binary, application-layer protocol for bidirectional communication between IoT devices and servers over persistent connections. It provides a resource-oriented model with native support for remote procedure calls, real-time data streaming, and API introspection. IOTMP uses PSON (Packed Sensor Object Notation) as its data encoding format, achieving significantly lower wire overhead than text-based alternatives. The protocol is designed for constrained devices with limited memory and bandwidth, while remaining expressive enough for complex IoT applications. IOTMP operates over TCP, TLS, or WebSocket transports and supports symmetric operation where both clients and servers may expose and invoke resources. 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. Luis Bustamante Expires 1 October 2026 [Page 1] Internet-Draft IOTMP March 2026 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. 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 . . . . . . . . . . . . . . . . . . . . . . . . 5 1.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2. Design Goals . . . . . . . . . . . . . . . . . . . . . . 5 1.3. Rationale for PSON . . . . . . . . . . . . . . . . . . . 5 1.4. Scope . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.5. Applicability . . . . . . . . . . . . . . . . . . . . . . 6 2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 7 3. Protocol Overview . . . . . . . . . . . . . . . . . . . . . . 8 4. Transport Layer . . . . . . . . . . . . . . . . . . . . . . . 9 4.1. Supported Transports . . . . . . . . . . . . . . . . . . 9 4.2. Connection Establishment . . . . . . . . . . . . . . . . 10 4.3. WebSocket Transport . . . . . . . . . . . . . . . . . . . 10 4.4. Byte Ordering . . . . . . . . . . . . . . . . . . . . . . 10 5. Message Framing . . . . . . . . . . . . . . . . . . . . . . . 11 5.1. Frame Format . . . . . . . . . . . . . . . . . . . . . . 11 5.2. Varint Encoding . . . . . . . . . . . . . . . . . . . . . 11 5.3. Maximum Message Size . . . . . . . . . . . . . . . . . . 12 6. Message Types . . . . . . . . . . . . . . . . . . . . . . . . 13 6.1. Response Correlation . . . . . . . . . . . . . . . . . . 14 6.2. Stream ID Partitioning . . . . . . . . . . . . . . . . . 14 6.3. Message Direction . . . . . . . . . . . . . . . . . . . . 15 7. Message Fields . . . . . . . . . . . . . . . . . . . . . . . 15 7.1. Field Tag Encoding . . . . . . . . . . . . . . . . . . . 15 7.2. Wire Types . . . . . . . . . . . . . . . . . . . . . . . 16 7.3. Defined Fields . . . . . . . . . . . . . . . . . . . . . 16 7.4. Field Presence . . . . . . . . . . . . . . . . . . . . . 17 7.5. Field Encoding Rules . . . . . . . . . . . . . . . . . . 17 8. Data Encoding: PSON . . . . . . . . . . . . . . . . . . . . . 17 8.1. Summary . . . . . . . . . . . . . . . . . . . . . . . . . 18 9. Connection Lifecycle . . . . . . . . . . . . . . . . . . . . 18 9.1. State Machine . . . . . . . . . . . . . . . . . . . . . . 18 9.2. CONNECT Message . . . . . . . . . . . . . . . . . . . . . 19 9.3. Version Negotiation . . . . . . . . . . . . . . . . . . . 22 9.4. Keepalive . . . . . . . . . . . . . . . . . . . . . . . . 23 9.5. Disconnection . . . . . . . . . . . . . . . . . . . . . . 23 9.6. Server Redirect . . . . . . . . . . . . . . . . . . . . . 24 9.7. Reconnection . . . . . . . . . . . . . . . . . . . . . . 25 Luis Bustamante Expires 1 October 2026 [Page 2] Internet-Draft IOTMP March 2026 10. Resource Model . . . . . . . . . . . . . . . . . . . . . . . 25 10.1. Overview . . . . . . . . . . . . . . . . . . . . . . . . 26 10.2. I/O Types . . . . . . . . . . . . . . . . . . . . . . . 26 10.3. Resource Invocation (RUN Message) . . . . . . . . . . . 26 10.4. Resource Description (DESCRIBE Message) . . . . . . . . 27 10.4.1. Description Versioning . . . . . . . . . . . . . . . 27 10.4.2. Full API Description . . . . . . . . . . . . . . . . 28 10.4.3. Single Resource Description . . . . . . . . . . . . 28 10.5. Stream Echo . . . . . . . . . . . . . . . . . . . . . . 32 10.6. Resource Hashing . . . . . . . . . . . . . . . . . . . . 33 11. Streaming . . . . . . . . . . . . . . . . . . . . . . . . . . 35 11.1. Stream Lifecycle . . . . . . . . . . . . . . . . . . . . 36 11.1.1. Stream State Machine . . . . . . . . . . . . . . . . 36 11.1.2. Interaction Diagram . . . . . . . . . . . . . . . . 38 11.2. START_STREAM Message . . . . . . . . . . . . . . . . . . 39 11.3. STREAM_DATA Message . . . . . . . . . . . . . . . . . . 40 11.4. Compact Encoding Mode . . . . . . . . . . . . . . . . . 40 11.4.1. Negotiation . . . . . . . . . . . . . . . . . . . . 40 11.4.2. Schema Establishment . . . . . . . . . . . . . . . . 41 11.4.3. Compact STREAM_DATA . . . . . . . . . . . . . . . . 41 11.4.4. Rules . . . . . . . . . . . . . . . . . . . . . . . 41 11.4.5. Recursive Compaction . . . . . . . . . . . . . . . . 42 11.4.6. Example . . . . . . . . . . . . . . . . . . . . . . 43 11.4.7. Wire Efficiency . . . . . . . . . . . . . . . . . . 43 11.4.8. Related Work . . . . . . . . . . . . . . . . . . . . 44 11.5. STOP_STREAM Message . . . . . . . . . . . . . . . . . . 45 11.6. Multiple Concurrent Streams . . . . . . . . . . . . . . 46 11.7. Flow Control . . . . . . . . . . . . . . . . . . . . . . 46 12. Extensibility . . . . . . . . . . . . . . . . . . . . . . . . 46 12.1. Symmetric Resource Model . . . . . . . . . . . . . . . . 46 12.2. Application Profiles . . . . . . . . . . . . . . . . . . 47 12.3. Bidirectional Stream Channels . . . . . . . . . . . . . 47 12.3.1. Channel Establishment . . . . . . . . . . . . . . . 47 12.3.2. Use Case: Interactive Sessions (Terminal, Proxy) . . 48 12.3.3. Use Case: Bulk Data Transfer (Files, Firmware) . . . 49 12.3.4. Use Case: Command Execution . . . . . . . . . . . . 51 12.3.5. Design Principle . . . . . . . . . . . . . . . . . . 51 12.4. Example: Client Invoking a Server Resource . . . . . . . 51 12.5. Example: Server Invoking a Client Resource . . . . . . . 51 13. Error Handling . . . . . . . . . . . . . . . . . . . . . . . 51 13.1. ERROR Message . . . . . . . . . . . . . . . . . . . . . 52 13.2. Status Codes . . . . . . . . . . . . . . . . . . . . . . 52 13.3. Error Payload . . . . . . . . . . . . . . . . . . . . . 54 13.4. OK Message with Status Code . . . . . . . . . . . . . . 54 13.5. Connection-Level Errors . . . . . . . . . . . . . . . . 54 14. Security Considerations . . . . . . . . . . . . . . . . . . . 54 14.1. Threat Model . . . . . . . . . . . . . . . . . . . . . . 54 14.2. Transport Security . . . . . . . . . . . . . . . . . . . 57 Luis Bustamante Expires 1 October 2026 [Page 3] Internet-Draft IOTMP March 2026 14.3. Authentication . . . . . . . . . . . . . . . . . . . . . 57 14.3.1. Certificate Authentication Security . . . . . . . . 58 14.4. Authorization . . . . . . . . . . . . . . . . . . . . . 59 14.5. Denial of Service . . . . . . . . . . . . . . . . . . . 59 14.6. Resource Exhaustion . . . . . . . . . . . . . . . . . . 59 14.7. Secure Credential Storage . . . . . . . . . . . . . . . 60 14.8. Privacy . . . . . . . . . . . . . . . . . . . . . . . . 60 15. Conformance Requirements . . . . . . . . . . . . . . . . . . 60 15.1. Client Conformance . . . . . . . . . . . . . . . . . . . 61 15.2. Server Conformance . . . . . . . . . . . . . . . . . . . 61 15.3. Connection Lifecycle Conformance . . . . . . . . . . . . 62 15.4. Conformance Test Vectors . . . . . . . . . . . . . . . . 64 15.4.1. KEEP_ALIVE . . . . . . . . . . . . . . . . . . . . . 65 15.4.2. CONNECT . . . . . . . . . . . . . . . . . . . . . . 65 15.4.3. OK Response . . . . . . . . . . . . . . . . . . . . 65 15.4.4. RUN with Resource Name . . . . . . . . . . . . . . . 66 15.4.5. RUN with Resource Hash . . . . . . . . . . . . . . . 66 15.4.6. ERROR with Status Code and Payload . . . . . . . . . 66 15.4.7. START_STREAM with Compact Mode . . . . . . . . . . . 67 16. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 67 16.1. Port Number Registration . . . . . . . . . . . . . . . . 67 16.2. WebSocket Subprotocol Registration . . . . . . . . . . . 68 16.3. Message Type Registry . . . . . . . . . . . . . . . . . 68 16.4. Authentication Type Registry . . . . . . . . . . . . . . 69 16.5. Field Number Registry . . . . . . . . . . . . . . . . . 70 16.6. Wire Type Registry . . . . . . . . . . . . . . . . . . . 71 16.7. Media Type . . . . . . . . . . . . . . . . . . . . . . . 71 17. References . . . . . . . . . . . . . . . . . . . . . . . . . 71 17.1. Normative References . . . . . . . . . . . . . . . . . . 71 17.2. Informative References . . . . . . . . . . . . . . . . . 72 Appendix A. Wire Format Examples . . . . . . . . . . . . . . . . 73 A.1. KEEP_ALIVE Message . . . . . . . . . . . . . . . . . . . 73 A.2. CONNECT Message . . . . . . . . . . . . . . . . . . . . . 73 A.3. RUN Message (Resource Invocation) . . . . . . . . . . . . 74 A.4. OK Response with Payload . . . . . . . . . . . . . . . . 74 A.5. ERROR Response with Status Code . . . . . . . . . . . . . 74 Appendix B. Formal Message Grammar (CDDL) . . . . . . . . . . . 75 B.1. Message Frame . . . . . . . . . . . . . . . . . . . . . . 75 B.2. Field Tag Encoding . . . . . . . . . . . . . . . . . . . 75 B.3. Varint Encoding . . . . . . . . . . . . . . . . . . . . . 76 B.4. CDDL Definitions . . . . . . . . . . . . . . . . . . . . 76 B.5. Field Requirements per Message Type . . . . . . . . . . . 80 Appendix C. PSON vs JSON Size Comparison . . . . . . . . . . . . 81 Appendix D. Protocol Comparisons . . . . . . . . . . . . . . . . 81 Appendix E. Revision History . . . . . . . . . . . . . . . . . . 82 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 82 Luis Bustamante Expires 1 October 2026 [Page 4] Internet-Draft IOTMP March 2026 1. Introduction 1.1. Purpose IOTMP (Internet of Things Message Protocol) is an application-layer protocol designed for efficient, bidirectional communication between IoT devices and servers. It provides a resource-oriented, request- response model with native support for real-time data streaming, optimized for constrained devices with minimal memory and bandwidth. 1.2. Design Goals * *Compactness:* Minimize wire overhead through binary encoding (PSON) and varint-based framing. * *Bidirectional RPC:* Enable both client-to-server and server-to- client remote procedure calls over a single persistent connection. * *Resource Orientation:* Model device capabilities as named resources with typed I/O, enabling auto-discovery and introspection. * *Native Streaming:* Support periodic and event-driven data streams without additional protocol layers. * *Transport Agnostic:* Operate over TCP, TLS, or WebSocket transports. * *Embedded-Friendly:* Fit within the memory and processing constraints of microcontrollers (e.g., ESP32, Arduino, Zephyr- based devices). 1.3. Rationale for PSON IOTMP uses PSON (Packed Sensor Object Notation) as its data encoding format rather than an existing binary encoding such as CBOR [RFC8949]. The primary motivation is architectural: PSON and IOTMP share the same encoding primitives, eliminating the need for two independent encoding systems on constrained devices. *Protocol-Encoding Integration.* IOTMP's message framing layer (Section 7) and PSON use identical encoding primitives: a tag-byte structure where type bits and an inline value are packed into a single byte, and varint encoding for variable-length integers. A single encoder/decoder implementation on a constrained device serves both the protocol framing layer (message fields) and the application data layer (payloads). Adopting an external encoding like CBOR would require a second, independent type system alongside the protocol's own field encoding -- two tag formats, two integer encodings, two sets of type definitions -- increasing code size and implementation complexity for no functional benefit. Luis Bustamante Expires 1 October 2026 [Page 5] Internet-Draft IOTMP March 2026 This integration is particularly valuable on the most constrained targets (Cortex-M0/M0+, 2-16 KB RAM), where every kilobyte of code matters. A complete IOTMP + PSON implementation shares encoding routines between framing and payload, keeping the combined footprint under 400 lines of C. *Comparison with CBOR.* In terms of wire size, PSON and CBOR produce comparable encodings for typical IoT data. Neither format has a significant size advantage over the other. The differences are in scope and complexity: CBOR is a general-purpose encoding designed to cover a wide range of use cases, including semantic tags (CBOR type 6, with hundreds of IANA-registered values), indefinite-length encoding, half-precision floats, and extensible simple values. PSON deliberately omits these features, which add decoder complexity without benefit for IoT applications. This reduced scope, combined with the shared primitives with IOTMP, is what makes PSON the appropriate choice for this protocol -- not a claim of superior compression. PSON is specified in a companion document: [PSON]. 1.4. Scope This specification defines: * The message framing format * The set of message types and their semantics * The PSON binary encoding format * The connection lifecycle (authentication, keepalive, disconnection) * The resource model and streaming semantics This specification does NOT define: * Transport-level security configuration (deferred to TLS) * Application-level authorization policies * Specific resource naming conventions 1.5. Applicability IOTMP is designed for scenarios where IoT devices maintain persistent connections with a server (broker) and require bidirectional communication. The protocol is most appropriate when: * *Devices need to be both controlled and observed* over the same connection -- reading sensor data, invoking actions, and streaming telemetry without separate protocols for each pattern. Luis Bustamante Expires 1 October 2026 [Page 6] Internet-Draft IOTMP March 2026 * *Low wire overhead matters* -- constrained devices on cellular, satellite, or low-bandwidth links where every byte counts. * *The server needs to initiate operations on the device* -- pushing configuration, invoking remote procedures, or starting/stopping data streams, without the device polling. * *Real-time interaction is required* -- interactive sessions (terminals, proxies), live dashboards, or event-driven streaming with sub-second latency. * *API introspection is valuable* -- auto-discovery of device capabilities, schema-based validation, and automatic UI or API generation from resource descriptions. IOTMP is NOT the best fit when: * *One-to-many broadcast is the primary pattern* -- MQTT's publish/ subscribe model is better suited for fan-out messaging to many subscribers on a topic. * *Devices cannot maintain persistent connections* -- battery- powered sensors that wake briefly to send a single reading are better served by CoAP over UDP, which supports connectionless operation. * *Interoperability with existing ecosystems is required* -- deployments that must integrate with existing MQTT or LwM2M infrastructure should use those protocols unless a bridge is acceptable. * *The application is purely request-response with no device state* -- plain HTTP may be simpler when there is no need for persistent connections, streaming, or server-initiated operations. 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. * *Client:* An IoT device or application that initiates a connection to a server. * *Server:* The endpoint that accepts client connections and manages device state. * *Resource:* A named endpoint on a client that can be invoked (read, written, or called) by the server. * *Stream:* A continuous flow of data from a resource, initiated by a START_STREAM message and terminated by STOP_STREAM. Luis Bustamante Expires 1 October 2026 [Page 7] Internet-Draft IOTMP March 2026 * *Stream ID:* A 16-bit unsigned integer used to correlate requests with responses and to identify active streams. The ID space is partitioned: client-initiated requests use even IDs (0, 2, 4, ...), server-initiated requests use odd IDs (1, 3, 5, ...). * *PSON:* Packed Sensor Object Notation -- the binary encoding format used for structured data within IOTMP messages. * *Varint:* A variable-length integer encoding where each byte uses 7 bits for data and 1 bit (MSB) as a continuation flag. 3. Protocol Overview IOTMP operates over a persistent, full-duplex connection. After transport establishment, the client authenticates via a CONNECT/OK handshake. Once authenticated, either side can send messages at any time. The protocol supports five interaction patterns: 1. *Request-Response:* A RUN or DESCRIBE message is sent with a Stream ID; the peer responds with OK or ERROR carrying the same Stream ID. 2. *Streaming:* A START_STREAM message initiates periodic or event- driven STREAM_DATA messages from a resource. STOP_STREAM terminates the stream. 3. *Fire-and-Forget:* DISCONNECT messages require no response. 4. *Keepalive:* The client periodically sends a KEEP_ALIVE message; the server MUST echo it back. This mechanism does not use Stream IDs. The server determines client liveness by monitoring incoming messages of any type. 5. *Server-Initiated RPC:* The server MAY send RUN messages to the client to read properties, set values, or invoke device resources. Luis Bustamante Expires 1 October 2026 [Page 8] Internet-Draft IOTMP March 2026 +--------+ +--------+ | Client | | Server | +---+----+ +---+----+ | CONNECT [credentials] | |-------------------------------------->| | OK | |<--------------------------------------| | | | DESCRIBE (full API) | |<--------------------------------------| | OK [resource list] | |-------------------------------------->| | | | START_STREAM [resource, interval]| |<--------------------------------------| | OK | |-------------------------------------->| | STREAM_DATA [payload] | |-------------------------------------->| | STREAM_DATA [payload] | |-------------------------------------->| | ... | | STOP_STREAM | |<--------------------------------------| | OK | |-------------------------------------->| | | | RUN [WRITE_BUCKET, data] | |-------------------------------------->| | OK | |<--------------------------------------| | | | KEEP_ALIVE | |-------------------------------------->| | KEEP_ALIVE | |<--------------------------------------| | | | DISCONNECT | |-------------------------------------->| +---------------------------------------+ 4. Transport Layer 4.1. Supported Transports IOTMP is designed to operate over reliable, ordered, byte-stream transports: Luis Bustamante Expires 1 October 2026 [Page 9] Internet-Draft IOTMP March 2026 +==============+==============+==============================+ | Transport | Default Port | Description | +==============+==============+==============================+ | TCP | 25204 | Unencrypted TCP connection | +--------------+--------------+------------------------------+ | TLS over TCP | 25206 | TLS-encrypted TCP connection | +--------------+--------------+------------------------------+ | WebSocket | 443 | WebSocket upgrade over | | | | HTTPS, for NAT traversal | +--------------+--------------+------------------------------+ Table 1 Implementations SHOULD support TLS. Unencrypted TCP SHOULD only be used in trusted networks or during development. 4.2. Connection Establishment 1. The client establishes a transport-level connection (TCP handshake, TLS negotiation, or WebSocket upgrade). 2. Upon successful transport connection, the client MUST send a CONNECT message as the first IOTMP message. 3. The server MUST respond with OK or ERROR. 4. No other message types SHALL be sent by the client before receiving a successful CONNECT response. 4.3. WebSocket Transport When operating over WebSocket, the following requirements apply: * The client MUST request the subprotocol "iotmp" in the Sec- WebSocket-Protocol header during the WebSocket handshake. * The server MUST confirm the "iotmp" subprotocol in the handshake response. If the server does not confirm it, the client MUST close the connection. * Each WebSocket message MUST contain exactly one complete IOTMP message. IOTMP messages MUST NOT be fragmented across multiple WebSocket frames. * The WebSocket message type MUST be binary (opcode 0x02). 4.4. Byte Ordering All multi-byte numeric values (floats, doubles) MUST be encoded in *little-endian* byte order. Varint encoding is byte-order independent by design. Luis Bustamante Expires 1 October 2026 [Page 10] Internet-Draft IOTMP March 2026 *Rationale:* Traditional Internet protocols use big-endian ("network byte order"), a convention established in the 1980s when the dominant networking hardware (Motorola 68000, SPARC, IBM mainframes) was big- endian. Today, virtually all microcontrollers and processors used in IoT are little-endian: ARM Cortex-M (the dominant embedded architecture), ESP32 (Xtensa), RISC-V, and x86. No widely deployed IoT microcontroller uses big-endian as its native byte order. On the most constrained ARM cores (Cortex-M0/M0+), which lack a hardware byte-reversal instruction (REV), swapping a 32-bit float requires 4 instructions per value. For streaming telemetry -- the most common IoT workload -- this overhead is incurred on every sample. Little-endian encoding allows constrained devices to write float and double values directly from memory to the wire with no conversion. This approach only affects [IEEE754] floats and doubles. Integers use varint encoding, which is byte-order independent. The same design choice is made by Protocol Buffers [ProtocolBuffers], which encodes fixed-width floats and doubles in little-endian regardless of platform. 5. Message Framing Every IOTMP message on the wire consists of a *header* followed by a *body*. 5.1. Frame Format +------------------+------------------+---------------------+ | Message Type | Body Size | Body | | (varint) | (varint) | (Body Size bytes) | +------------------+------------------+---------------------+ * *Message Type:* A varint encoding one of the defined message type values Section 6. * *Body Size:* A varint indicating the number of bytes in the body. A value of 0 indicates an empty body (used for KEEP_ALIVE). * *Body:* A sequence of tagged fields Section 7, encoded as Body Size bytes. 5.2. Varint Encoding IOTMP uses Protocol Buffers-style variable-length integer encoding: * Each byte uses bits [6:0] for data and bit [7] as a continuation flag. * If bit [7] is set, more bytes follow. Luis Bustamante Expires 1 October 2026 [Page 11] Internet-Draft IOTMP March 2026 * If bit [7] is clear, this is the last byte. * Least significant group comes first (little-endian byte order). *Examples:* +=========+========+====================+ | Decimal | Hex | Varint Bytes (hex) | +=========+========+====================+ | 0 | 0x00 | 00 | +---------+--------+--------------------+ | 1 | 0x01 | 01 | +---------+--------+--------------------+ | 127 | 0x7F | 7F | +---------+--------+--------------------+ | 128 | 0x80 | 80 01 | +---------+--------+--------------------+ | 300 | 0x012C | AC 02 | +---------+--------+--------------------+ | 16384 | 0x4000 | 80 80 01 | +---------+--------+--------------------+ Table 2 Implementations MUST NOT encode varints longer than 4 bytes, representing values up to 2^28 - 1 (268,435,455). A receiver that encounters a varint that does not terminate within 4 bytes MUST treat it as a decode error and close the connection. 5.3. Maximum Message Size Implementations MUST support messages up to at least 32,768 bytes (32 KB). Implementations MAY support larger messages. Both sides MAY declare their maximum message size using the "ms" parameter: the client in the CONNECT message and the server in the OK response Section 9.2. Each side MUST NOT send messages exceeding the peer's declared maximum. If "ms" is not declared, the default of 32,768 bytes is assumed. A receiver that encounters a message exceeding its maximum size SHOULD close the connection. IOTMP does not define message-level fragmentation. For data transfers that exceed the maximum message size, applications SHOULD use streaming Section 11: a START_STREAM opens a persistent channel over which arbitrarily large data can be sent as a sequence of STREAM_DATA messages, each within the negotiated size limit. Luis Bustamante Expires 1 October 2026 [Page 12] Internet-Draft IOTMP March 2026 6. Message Types The Message Type field identifies the purpose of each message: +=======+==============+==================+=========================+ | Value | Name | Direction | Description | +=======+==============+==================+=========================+ | 0x00 | RESERVED | -- | Reserved for future | | | | | use. MUST NOT be | | | | | sent. | +-------+--------------+------------------+-------------------------+ | 0x01 | OK | Both | Successful response | | | | | to a request. | +-------+--------------+------------------+-------------------------+ | 0x02 | ERROR | Both | Error response to a | | | | | request. | +-------+--------------+------------------+-------------------------+ | 0x03 | CONNECT | Client -> Server | Authentication | | | | | request with | | | | | credentials. | +-------+--------------+------------------+-------------------------+ | 0x04 | DISCONNECT | Both | Graceful connection | | | | | termination. | +-------+--------------+------------------+-------------------------+ | 0x05 | KEEP_ALIVE | Client -> Server | Connection liveness | | | | (echo: Server -> | probe. Body MUST | | | | Client) | be empty. | +-------+--------------+------------------+-------------------------+ | 0x06 | RUN | Both | Execute a resource | | | | | on the peer. | +-------+--------------+------------------+-------------------------+ | 0x07 | DESCRIBE | Both | Request resource | | | | | metadata/API | | | | | description. | +-------+--------------+------------------+-------------------------+ | 0x08 | START_STREAM | Both | Begin streaming | | | | | data from a | | | | | resource. | +-------+--------------+------------------+-------------------------+ | 0x09 | STOP_STREAM | Both | Stop an active | | | | | stream. | +-------+--------------+------------------+-------------------------+ | 0x0A | STREAM_DATA | Both | Carry stream | | | | | payload data. | +-------+--------------+------------------+-------------------------+ Table 3 Luis Bustamante Expires 1 October 2026 [Page 13] Internet-Draft IOTMP March 2026 Values 0x0B through 0xFF are reserved for future use. 6.1. Response Correlation Messages that expect a response (CONNECT, RUN, DESCRIBE, START_STREAM, STOP_STREAM) MUST include a Stream ID field. The response (OK or ERROR) MUST carry the same Stream ID to correlate with the request. *Note:* The Stream ID serves two roles depending on the message type. For one-time request-response exchanges (RUN, DESCRIBE), the Stream ID is a short-lived correlation identifier that is released when the response is received. For streaming operations (START_STREAM), the same Stream ID becomes a long-lived identifier for the stream, used in subsequent STREAM_DATA and STOP_STREAM messages until the stream is terminated. In both cases, the lifecycle rules in Section 6.2 apply. *Request Timeout:* If a response (OK or ERROR) is not received within a reasonable time, the sender SHOULD consider the request failed and release the Stream ID. Implementations SHOULD use a default timeout of 30 seconds for RUN, DESCRIBE, START_STREAM, and STOP_STREAM requests. The CONNECT message has a separate timeout (RECOMMENDED: 10 seconds, Section 14.5). Implementations MAY use longer timeouts for specific resources that are known to require extended processing. 6.2. Stream ID Partitioning Since IOTMP is a symmetric protocol where both sides may initiate requests concurrently, the Stream ID space is partitioned to avoid collisions: * *Client-initiated requests* MUST use *even* Stream IDs (0, 2, 4, ..., 65534). * *Server-initiated requests* MUST use *odd* Stream IDs (1, 3, 5, ..., 65535). This partitioning ensures that responses (OK, ERROR) and stream data (STREAM_DATA) can be unambiguously correlated with the originating request, even when both sides send requests simultaneously with the same numeric value. *Stream ID Lifecycle:* A Stream ID is considered *active* from the moment the request message is sent until: Luis Bustamante Expires 1 October 2026 [Page 14] Internet-Draft IOTMP March 2026 * For request-response (RUN, DESCRIBE): the corresponding OK or ERROR is received, OR * For streams (START_STREAM): the stream is terminated by STOP_STREAM and its OK response. An endpoint MUST NOT reuse a Stream ID that is currently active. Once a Stream ID is released, it MAY be reused for a new request. Implementations SHOULD favor low-valued Stream IDs to minimize varint encoding overhead. A receiver that encounters a request with a Stream ID from the wrong partition (e.g., a client-initiated message with an odd Stream ID) MUST respond with ERROR (400) and SHOULD log the violation. 6.3. Message Direction IOTMP is a symmetric protocol. After authentication, both sides MAY send any message type except CONNECT and KEEP_ALIVE. The directional constraints are: * *Client -> Server only:* CONNECT (authentication request), KEEP_ALIVE (liveness probe). * *Server -> Client only (in response):* KEEP_ALIVE echo Section 9.4. * *Both directions:* OK, ERROR, DISCONNECT, RUN, DESCRIBE, START_STREAM, STOP_STREAM, STREAM_DATA. 7. Message Fields The message body consists of zero or more tagged fields. Each field is encoded as: +---------------------------+ | Field Tag (1 byte) | +---------------------------+ | Field Value (variable) | +---------------------------+ 7.1. Field Tag Encoding The field tag is a single byte combining the field number and wire type: Tag = (field_number << 3) | wire_type * *field_number* (bits [7:3]): Identifies which field this is (5 bits, values 0-31). Luis Bustamante Expires 1 October 2026 [Page 15] Internet-Draft IOTMP March 2026 * *wire_type* (bits [2:0]): Determines how the field value is encoded. 7.2. Wire Types +=======+========+========================================+ | Value | Name | Description | +=======+========+========================================+ | 0x00 | varint | Value is a varint-encoded unsigned | | | | integer. | +-------+--------+----------------------------------------+ | 0x01 | bytes | Value is a varint-encoded length | | | | followed by raw bytes. The content is | | | | opaque to the protocol and MAY carry | | | | any application-defined binary format. | +-------+--------+----------------------------------------+ | 0x02 | pson | Value is PSON encoded Section 8. | +-------+--------+----------------------------------------+ Table 4 Wire types 0x03-0x07 are reserved for future use. 7.3. Defined Fields +========+============+============+===============================+ | Field | Name | Allowed | Description | | Number | | Wire Types | | +========+============+============+===============================+ | 0x01 | STREAM_ID | varint | 16-bit stream identifier for | | | | | request/response correlation. | +--------+------------+------------+-------------------------------+ | 0x02 | PARAMETERS | varint, | Request parameters (e.g., | | | | pson | server operation code, stream | | | | | interval). | +--------+------------+------------+-------------------------------+ | 0x03 | PAYLOAD | pson, | Primary data payload | | | | bytes | (credentials, resource data, | | | | | error info). | +--------+------------+------------+-------------------------------+ | 0x04 | RESOURCE | varint, | Resource identifier (string | | | | pson | name or 16-bit hash). | +--------+------------+------------+-------------------------------+ Table 5 Luis Bustamante Expires 1 October 2026 [Page 16] Internet-Draft IOTMP March 2026 Field 0x00 is reserved and MUST NOT be used. Reserving field zero allows zero-initialized field variables to be distinguished from valid field numbers and serves as a sentinel value for error detection -- a convention shared with Protocol Buffers and other binary protocols. Fields 0x05-0x07 are reserved for future use. A maximum of 8 fields (0x00-0x07) are supported per message. 7.4. Field Presence Fields are OPTIONAL. A receiver MUST handle messages with any subset of fields present. The field_mask is implicit in the encoding -- a field is present if and only if its tag appears in the body. Fields MAY appear in any order within the message body. A receiver MUST NOT assume a specific field ordering. 7.5. Field Encoding Rules * *STREAM_ID:* MUST be encoded as varint (wire type 0x00). Values range from 0 to 65535. * *PARAMETERS:* When carrying a simple integer (e.g., a server operation code or stream interval), SHOULD be encoded as varint. When carrying structured data (e.g., CONNECT parameters), MUST be encoded as pson. * *PAYLOAD:* MUST be encoded as pson. Implementations MAY use wire type bytes (0x01) to carry opaque binary data in application- defined formats. * *RESOURCE:* When carrying a resource name (string), MUST be encoded as pson. When carrying a resource hash (unsigned integer), SHOULD be encoded as varint for compactness; implementations MAY also encode it as pson. Receivers MUST accept both wire types for integer values. See Section 10.6 for resource hashing. A formal CDDL grammar defining all message structures and field requirements is provided in Appendix B. 8. Data Encoding: PSON IOTMP uses PSON (Packed Sensor Object Notation) as its data encoding format. PSON is a compact, self-describing binary encoding that efficiently represents the data types commonly found in IoT applications: integers, floating-point numbers, booleans, strings, binary data, arrays, and key-value maps. PSON is specified in a separate document: [PSON]. This section provides a brief summary; the PSON specification is the normative reference for encoding and decoding rules. Luis Bustamante Expires 1 October 2026 [Page 17] Internet-Draft IOTMP March 2026 8.1. Summary Each PSON value begins with a tag byte that encodes both the wire type (3 bits) and an inline value (5 bits). Small values (0-30) are encoded in a single byte. Eight wire types are defined: +===========+============+==========================+ | Wire Type | Name | Description | +===========+============+==========================+ | 0 | unsigned_t | Unsigned integer | +-----------+------------+--------------------------+ | 1 | signed_t | Negative integer (stored | | | | as absolute value) | +-----------+------------+--------------------------+ | 2 | floating_t | IEEE 754 float or double | +-----------+------------+--------------------------+ | 3 | discrete_t | Boolean (false/true) or | | | | null | +-----------+------------+--------------------------+ | 4 | string_t | UTF-8 string | +-----------+------------+--------------------------+ | 5 | bytes_t | Raw binary data | +-----------+------------+--------------------------+ | 6 | map_t | Key-value map (keys are | | | | strings) | +-----------+------------+--------------------------+ | 7 | array_t | Ordered array | +-----------+------------+--------------------------+ Table 6 For the complete encoding rules, wire format, and examples, see [PSON]. 9. Connection Lifecycle 9.1. State Machine Luis Bustamante Expires 1 October 2026 [Page 18] Internet-Draft IOTMP March 2026 +------------------+ | DISCONNECTED | +--------+---------+ | Transport connect +--------v---------+ | SOCKET_CONNECTING| +--------+---------+ | Success +--------v---------+ | SOCKET_CONNECTED | +--------+---------+ | Send CONNECT +--------v---------+ | AUTHENTICATING | +--------+---------+ OK / \ ERROR +---------v+ +v----------+ |AUTHENTICATED| |AUTH_FAILED | +---------+-+ +------------+ | +-------v-------+ | READY |<--------------+ +-------+-------+ | | | +---------v---------+ | | Process Messages |------------+ | Keepalive | | Check Streams | +---------+---------+ | Transport error / DISCONNECT +--------v----------+ |SOCKET_DISCONNECTED| +--------+----------+ | Exponential backoff +------> (reconnect) 9.2. CONNECT Message The client MUST send a CONNECT message as the first message after transport establishment. *CONNECT Fields:* Luis Bustamante Expires 1 October 2026 [Page 19] Internet-Draft IOTMP March 2026 +============+==============================================+ | Field | Content | +============+==============================================+ | STREAM_ID | An even 16-bit value for response | | | correlation (client partition, Section 6.2). | +------------+----------------------------------------------+ | PAYLOAD | Authentication data. Format depends on the | | | authentication type ("at" parameter). | +------------+----------------------------------------------+ | PARAMETERS | (OPTIONAL) A PSON map with connection | | | options. | +------------+----------------------------------------------+ Table 7 *Connection Parameters (PARAMETERS field):* +======+======+========================================+=========+ | Key | Type | Description | Default | +======+======+========================================+=========+ | "v" | uint | Protocol version. Current version: 1. | 1 | +------+------+----------------------------------------+---------+ | "ka" | uint | Keepalive interval in seconds. Max: | 60 | | | | 1800. | | +------+------+----------------------------------------+---------+ | "at" | uint | Authentication type (see below). | 0 | +------+------+----------------------------------------+---------+ | "ms" | uint | Maximum message size in bytes the | 32768 | | | | client can accept. Min: 1024. | | +------+------+----------------------------------------+---------+ Table 8 *Authentication Types:* The "at" parameter determines the format and interpretation of the PAYLOAD field: Luis Bustamante Expires 1 October 2026 [Page 20] Internet-Draft IOTMP March 2026 +======+=============+============================================+ | Code | Name | PAYLOAD Format | +======+=============+============================================+ | 0 | Credentials | A PSON array of 3 strings: [namespace, | | | | device_id, credential]. The namespace | | | | identifies the organizational scope | | | | (account, project, or tenant) under which | | | | the device is registered. The device_id | | | | uniquely identifies the device within the | | | | namespace. The credential is the shared | | | | secret. | +------+-------------+--------------------------------------------+ | 1 | Token | A PSON string containing a bearer token | | | | (e.g., JWT, API key, or opaque token). | | | | The server determines the device identity | | | | from the token claims or by lookup. | +------+-------------+--------------------------------------------+ | 2 | Certificate | Device identity is established via the | | | | client certificate presented during the | | | | TLS handshake (mTLS). This type MUST only | | | | be used over TLS with client certificate | | | | authentication. PAYLOAD is OPTIONAL: when | | | | the certificate identifies only the | | | | namespace (e.g., via CN or SAN), the | | | | PAYLOAD MUST be a PSON array of 2 strings | | | | [namespace, device_id] so the server can | | | | identify the specific device. When the | | | | certificate identifies both namespace and | | | | device, the PAYLOAD MAY be absent or | | | | empty. If PAYLOAD is present, the server | | | | MUST verify that the namespace matches the | | | | certificate identity and MUST reject the | | | | connection with ERROR (401) on mismatch. | | | | See Section 14.3.1 for security | | | | considerations on fleet certificates. | +------+-------------+--------------------------------------------+ Table 9 Authentication types 3-255 are reserved for future use (e.g., OAuth 2.0 device flow, challenge-response). A server that receives an unrecognized authentication type MUST respond with ERROR (400). If the "at" parameter is omitted, type 0 (Credentials) is assumed and the PAYLOAD MUST follow the Credentials format. *Server Response:* Luis Bustamante Expires 1 October 2026 [Page 21] Internet-Draft IOTMP March 2026 * *OK:* Authentication succeeded. The connection enters the READY state. The Stream ID used in the CONNECT message is released upon receiving this response and MAY be reused. The server MAY include a PARAMETERS field with a PSON map containing its own connection parameters (e.g., "ms" to declare the server's maximum message size). The client MUST NOT send messages exceeding the server's declared maximum. * *ERROR:* Authentication failed or version not supported. The Stream ID is released. The server SHOULD close the connection. *Extensibility of Connection Parameters:* The PARAMETERS maps in both the CONNECT and OK messages are extensible. Implementations MUST ignore unknown keys in PARAMETERS and process known keys normally. Future versions of this protocol or application profiles MAY define additional keys for capability negotiation (e.g., supported authentication types, maximum concurrent streams, or protocol extensions). This allows the connection handshake to evolve without breaking backward compatibility. 9.3. Version Negotiation The "v" parameter in the CONNECT message indicates the protocol version the client wishes to use. * If the "v" parameter is omitted, the server MUST assume protocol version 1. * If the server supports the requested version, it MUST proceed with authentication and respond with OK or ERROR based on the credentials. * If the server does not support the requested version, it MUST respond with an ERROR message with status code 400 and SHOULD include the supported versions in the PAYLOAD. The server MUST then close the connection. *Version Mismatch Response Example:* ERROR Message: STREAM_ID: (mirrors CONNECT) PARAMETERS: 400 PAYLOAD: {"error": "Unsupported protocol version", "supported": [1]} The client MAY retry the connection using a version from the supported list. Luis Bustamante Expires 1 October 2026 [Page 22] Internet-Draft IOTMP March 2026 9.4. Keepalive The keepalive mechanism verifies connection liveness for both sides. KEEP_ALIVE is a client-initiated message with an empty body (Body Size = 0). The server echoes each KEEP_ALIVE back, allowing the client to confirm reachability. The server determines client liveness by monitoring incoming traffic -- any message from the client (KEEP_ALIVE, STREAM_DATA, RUN, or any other type) resets the server's inactivity timer. This asymmetric design simplifies server implementation: the server does not need to maintain per-connection keepalive send timers -- it only needs a single inactivity timeout per connection. This is the same model used by [MQTT] (PINGREQ/PINGRESP) and WebSocket (Ping/ Pong). *Client behavior:* * The client MUST send a KEEP_ALIVE message if no other message has been sent to the server within the negotiated keepalive interval (default: 60 seconds). If the client sends other messages (RUN, STREAM_DATA, etc.) within the interval, it MAY skip the KEEP_ALIVE for that period, since the server treats any incoming message as proof of liveness. * If the client does not receive any message from the server (KEEP_ALIVE echo, STREAM_DATA, OK, RUN, or any other message type) within the keepalive interval plus a tolerance margin (RECOMMENDED: 15 seconds), it SHOULD consider the connection lost and initiate reconnection Section 9.7. *Server behavior:* * Upon receiving a KEEP_ALIVE message, the server MUST respond with a KEEP_ALIVE message (echo). This enables the client to verify that the server is still reachable. * The server MUST NOT initiate KEEP_ALIVE messages. The server's role is limited to echoing client-initiated KEEP_ALIVE messages. * The server SHOULD consider a client disconnected if no message of any type is received within the keepalive interval plus a tolerance margin (RECOMMENDED: 15 seconds). The server MUST then close the connection and release all associated resources (streams, Stream IDs). 9.5. Disconnection Either side MAY send a DISCONNECT message to initiate graceful shutdown. Upon receiving DISCONNECT, the peer SHOULD close the transport connection. No response is expected. Luis Bustamante Expires 1 October 2026 [Page 23] Internet-Draft IOTMP March 2026 *DISCONNECT Fields:* +============+===================================================+ | Field | Content | +============+===================================================+ | PARAMETERS | (OPTIONAL) Status code (varint). | +------------+---------------------------------------------------+ | PAYLOAD | (OPTIONAL) Additional information (PSON-encoded). | +------------+---------------------------------------------------+ Table 10 A DISCONNECT message with no fields indicates a normal graceful shutdown. 9.6. Server Redirect A server MAY redirect a client to a different server in two scenarios: *At connection time:* The server responds to CONNECT with an ERROR message containing a redirect status code and the target server information in the PAYLOAD. *During an active connection:* The server sends a DISCONNECT message with a redirect status code and the target server in the PAYLOAD. The client SHOULD close the current connection and connect to the indicated server. *Redirect Status Codes:* +======+=============+==========================================+ | Code | Meaning | Client Behavior | +======+=============+==========================================+ | 301 | Moved | Client MUST update its stored server | | | Permanently | address and connect to the new server | | | | for all future connections. | +------+-------------+------------------------------------------+ | 307 | Temporary | Client SHOULD connect to the indicated | | | Redirect | server now, but MUST retain the original | | | | server address for future connections. | +------+-------------+------------------------------------------+ Table 11 *Redirect Payload:* Luis Bustamante Expires 1 October 2026 [Page 24] Internet-Draft IOTMP March 2026 The PAYLOAD MUST be a PSON map containing the target server information: +========+========+========================+==========+ | Key | Type | Description | Required | +========+========+========================+==========+ | "host" | string | Hostname or IP address | Yes | | | | of the target. | | +--------+--------+------------------------+----------+ | "port" | uint | Port number of the | No | | | | target. | | +--------+--------+------------------------+----------+ Table 12 If "port" is omitted, the client SHOULD use the same port as the current connection. *Redirect Example (at connection time):* Client -> Server: CONNECT [credentials] Server -> Client: ERROR STREAM_ID: (mirrors CONNECT) PARAMETERS: 307 PAYLOAD: {"host": "server2.example.com", "port": 25206} *Redirect Example (during active connection):* Server -> Client: DISCONNECT PARAMETERS: 301 PAYLOAD: {"host": "new-server.example.com"} 9.7. Reconnection Clients SHOULD implement automatic reconnection with exponential backoff: * Initial delay: 5 seconds. * Doubling on each failed attempt. * Maximum delay: 60 seconds. * Reset to initial delay upon successful authentication. 10. Resource Model Luis Bustamante Expires 1 October 2026 [Page 25] Internet-Draft IOTMP March 2026 10.1. Overview IOTMP models capabilities as *resources* -- named endpoints that can be invoked by the peer. Both clients and servers MAY expose resources. Each resource has a defined I/O type that determines how data flows through it. 10.2. I/O Types +==============+======+===============+============================+ | Type | Code | Data Flow | Description | +==============+======+===============+============================+ | none | 0 | -- | No callback registered. | +--------------+------+---------------+----------------------------+ | run | 1 | -> (trigger) | Action with no data input | | | | | or output. | +--------------+------+---------------+----------------------------+ | input | 2 | Caller -> | Receives data (actuator, | | | | Resource | setter). | +--------------+------+---------------+----------------------------+ | output | 3 | Resource -> | Produces data (sensor, | | | | Caller | getter). | +--------------+------+---------------+----------------------------+ | input_output | 4 | Bidirectional | Accepts input and produces | | | | | output (property). | +--------------+------+---------------+----------------------------+ Table 13 10.3. Resource Invocation (RUN Message) Either side MAY send a RUN message to invoke a resource on the peer: 1. The receiver looks up the resource by the RESOURCE field value. 2. The receiver executes the resource callback, passing PAYLOAD as input and collecting output. 3. The receiver responds with OK (including any output in PAYLOAD) or ERROR. *RUN Fields:* Luis Bustamante Expires 1 October 2026 [Page 26] Internet-Draft IOTMP March 2026 +===========+========================================+ | Field | Content | +===========+========================================+ | STREAM_ID | Request correlation identifier. | +-----------+----------------------------------------+ | RESOURCE | Resource name (string) or resource | | | hash (unsigned integer, Section 10.6). | +-----------+----------------------------------------+ | PAYLOAD | (OPTIONAL) Input data for the | | | resource. | +-----------+----------------------------------------+ Table 14 10.4. Resource Description (DESCRIBE Message) The DESCRIBE message enables API introspection. Either peer can request: 1. *Full API:* DESCRIBE with no RESOURCE field -> the receiving peer responds with a map of all resource names and their I/O types. 2. *Single Resource:* DESCRIBE with a RESOURCE field -> the receiving peer responds with the resource's input/output data and optional schema information. 10.4.1. Description Versioning DESCRIBE responses MUST include a "v" (version) field at the root level to indicate the description format version. The current version is *1*. +=========+========================================================+ | Version | Description | +=========+========================================================+ | 1 | Resource descriptions with optional [JSON-Schema] | | | support for input/output validation and introspection. | +---------+--------------------------------------------------------+ Table 15 Receivers that encounter an unrecognized version SHOULD ignore unknown fields and interpret the response on a best-effort basis. The version field enables forward-compatible evolution of the description format as the protocol evolves (e.g., adding richer metadata, new keywords, or alternative schema languages in future versions). Luis Bustamante Expires 1 October 2026 [Page 27] Internet-Draft IOTMP March 2026 10.4.2. Full API Description A DESCRIBE with no RESOURCE field returns a map containing the description version and a "res" field with all resource names and their metadata. { "v": 1, "res": { "temperature": { "fn": 3, "description": "Room temperature sensor" }, "led": {"fn": 2, "description": "Status LED control"}, "relay": {"fn": 4}, "reboot": {"fn": 1} } } The "res" field is a PSON map where each key is a resource name and each value is a resource descriptor. This separation ensures that protocol-level fields (such as "v") do not collide with resource names. Each resource entry MUST include a "fn" field with the I/O type code Section 10.2. Each resource entry MAY include a "description" field with a human-readable string describing the resource's purpose. 10.4.3. Single Resource Description A DESCRIBE with a RESOURCE field returns the resource's input/output information. The response contains "in" and/or "out" fields (depending on the resource's I/O type), each structured as an object with the following fields: +========+==========+===================================+ | Field | Required | Description | +========+==========+===================================+ | value | YES | Current or sample data from the | | | | resource callback. | +--------+----------+-----------------------------------+ | schema | NO | A [JSON-Schema] object describing | | | | the expected data structure. | +--------+----------+-----------------------------------+ Table 16 *Example -- Sensor with input and output (I/O type input_output):* Luis Bustamante Expires 1 October 2026 [Page 28] Internet-Draft IOTMP March 2026 { "v": 1, "in": { "value": {"brightness": 128}, "schema": { "type": "object", "properties": { "brightness": { "type": "integer", "minimum": 0, "maximum": 255, "description": "LED brightness level" } } } }, "out": { "value": {"celsius": 22.5, "fahrenheit": 72.5}, "schema": { "type": "object", "properties": { "celsius": { "type": "number", "minimum": -40, "maximum": 125, "description": "Temperature in Celsius" }, "fahrenheit": { "type": "number", "minimum": -40, "maximum": 257, "description": "Temperature in Fahrenheit" } } } } } *Example -- Output-only sensor (I/O type output):* Luis Bustamante Expires 1 October 2026 [Page 29] Internet-Draft IOTMP March 2026 { "v": 1, "out": { "value": { "latitude": 40.4168, "longitude": -3.7038, "altitude": 650.0 }, "schema": { "type": "object", "properties": { "latitude": { "type": "number", "minimum": -90, "maximum": 90 }, "longitude": { "type": "number", "minimum": -180, "maximum": 180 }, "altitude": { "type": "number", "description": "Altitude in meters" } } } } } *Example -- Input-only actuator (I/O type input):* { "v": 1, "in": { "value": {"on": false}, "schema": { "type": "object", "properties": { "on": {"type": "boolean", "description": "Relay state"} } } } } *Example -- Minimal description without schema:* Luis Bustamante Expires 1 October 2026 [Page 30] Internet-Draft IOTMP March 2026 { "v": 1, "out": { "value": {"celsius": 22.5, "fahrenheit": 72.5} } } The "schema" field, when present, MUST be a valid [JSON-Schema] object. Implementations SHOULD use the following JSON Schema keywords where applicable: +=============+=================================+ | Keyword | Purpose | +=============+=================================+ | type | Data type (boolean, integer, | | | number, string, object, array). | +-------------+---------------------------------+ | properties | Named fields within an object. | +-------------+---------------------------------+ | items | Element schema for arrays. | +-------------+---------------------------------+ | description | Human-readable field | | | description. | +-------------+---------------------------------+ | minimum | Lower bound for numeric values. | +-------------+---------------------------------+ | maximum | Upper bound for numeric values. | +-------------+---------------------------------+ | enum | Array of allowed values. | +-------------+---------------------------------+ | readOnly | Field cannot be written. | +-------------+---------------------------------+ | writeOnly | Field cannot be read. | +-------------+---------------------------------+ | default | Default value. | +-------------+---------------------------------+ | required | Array of required property | | | names. | +-------------+---------------------------------+ Table 17 The "schema" field is OPTIONAL. When absent, receivers SHOULD infer data types from the sample values in "value". Resources that only have output (e.g., sensors) include only "out"; resources that only have input (e.g., actuators) include only "in"; resources with no data (type run) MAY omit both. Luis Bustamante Expires 1 October 2026 [Page 31] Internet-Draft IOTMP March 2026 Servers MAY use the schema information to validate incoming data before forwarding it to the device, generate user interfaces automatically, or produce API documentation compatible with standards such as OpenAPI. 10.5. Stream Echo When a resource with one or more active streams receives input via a RUN message, the resource owner (typically the client) SHOULD immediately send a STREAM_DATA message with the updated resource state on *each* active stream for that resource. This enables real- time synchronization -- for example, when a server sets a property value via RUN, all dashboards subscribed to that resource's stream receive the updated state without waiting for the next periodic sample. *Behavior:* * Stream echo MUST occur *after* the resource callback has executed and the RUN response (OK or ERROR) has been sent. The echo reflects the resource state after the operation. * The STREAM_DATA message MUST use the Stream ID of the active stream, not the Stream ID of the RUN request. * If the resource has multiple active streams (from different server requests or stream configurations), the resource owner MUST send a separate STREAM_DATA on each active stream. * If compact mode Section 11.4 is active on a stream, the echo STREAM_DATA MUST follow the compact encoding rules (PSON array with positional values). * Stream echo is enabled by default. Implementations MAY allow disabling echo on a per-resource basis if the application does not require immediate synchronization. *Example:* Luis Bustamante Expires 1 October 2026 [Page 32] Internet-Draft IOTMP March 2026 S -> C: START_STREAM (Stream 0x01, resource: "relay", interval: 5000) C -> S: OK (Stream 0x01) C -> S: STREAM_DATA (Stream 0x01, {"on": false}) <- initial state S -> C: RUN (Stream 0x03, resource: "relay", payload: {"on": true}) C -> S: OK (Stream 0x03) C -> S: STREAM_DATA (Stream 0x01, {"on": true}) <- echo (immediate) ... 5 seconds later ... C -> S: STREAM_DATA (Stream 0x01, {"on": true}) <- periodic sample In this example, the server has an active stream (Stream ID 0x01) observing the "relay" resource. When the server changes the relay state via RUN (Stream ID 0x03), the client sends the RUN response (OK) first, then immediately echoes the updated state on the active stream (Stream ID 0x01). This ensures that any dashboard or application consuming the stream sees the change in real time, without waiting for the next periodic sample. 10.6. Resource Hashing As an optimization for high-frequency interactions, IOTMP supports identifying resources by a numeric hash of their name instead of the full string. This eliminates the need for prior DESCRIBE exchanges or state synchronization between peers. The RESOURCE field in any message (RUN, DESCRIBE, START_STREAM, STOP_STREAM) accepts both: * A *PSON string*: the resource name. Always works, self- documenting. Required for resources with dynamic path parameters (e.g., paths containing /). * A *PSON unsigned integer*: a 16-bit hash of the resource name. Compact (1-3 bytes in varint), requires no prior negotiation. The receiver MUST support both forms. When a PSON unsigned integer is received, the receiver computes the hash of each of its defined resources and matches the incoming value. When a PSON string is received, the receiver looks up the resource by name. *Hash Function:* Luis Bustamante Expires 1 October 2026 [Page 33] Internet-Draft IOTMP March 2026 Implementations MUST use FNV-1a (Fowler-Noll-Vo) truncated to 16 bits: hash = 0x811C9DC5 // FNV offset basis (32-bit) for each byte b in name: hash = hash XOR b hash = hash * 0x01000193 // FNV prime (32-bit) return hash AND 0xFFFF // truncate to 16 bits This function is simple to implement on constrained devices and provides good distribution across typical IoT resource names. *Reference Implementation (C):* uint16_t fnv1a_16(const char *name) { uint32_t hash = 0x811C9DC5; while (*name) { hash ^= (uint8_t)*name++; hash *= 0x01000193; } return (uint16_t)(hash & 0xFFFF); } *Test Vectors:* +===============+=================+====================+======+ | Resource Name | FNV-1a (32-bit) | Truncated (16-bit) | Hex | +===============+=================+====================+======+ | "temperature" | 0xE9F2A935 | 0xA935 | A935 | +---------------+-----------------+--------------------+------+ | "humidity" | 0x25C5B9A0 | 0xB9A0 | B9A0 | +---------------+-----------------+--------------------+------+ | "led" | 0x406AEACA | 0xEACA | EACA | +---------------+-----------------+--------------------+------+ | "relay" | 0x16B481C2 | 0x81C2 | 81C2 | +---------------+-----------------+--------------------+------+ | "reboot" | 0x87729FB8 | 0x9FB8 | 9FB8 | +---------------+-----------------+--------------------+------+ Table 18 *Example:* RUN with string: RESOURCE = "temperature" (12 bytes in PSON) RUN with hash: RESOURCE = 0xA935 (3 bytes in PSON) Both identify the same resource. The hash form saves 9 bytes per message. Luis Bustamante Expires 1 October 2026 [Page 34] Internet-Draft IOTMP March 2026 *Applicability:* Resource hashing is best suited for simple resource names without dynamic parameters. Resources with path-based parameters (e.g., fs/home/config.txt) SHOULD use the string form, since the path contains dynamic segments that cannot be pre-hashed. *Collision Handling:* With a 16-bit hash space (65,536 values), the probability of at least one collision follows the birthday problem. For typical IoT devices with small resource sets, the probability is very low: +===========+==============+ | Resources | P(collision) | +===========+==============+ | 10 | < 0.1% | +-----------+--------------+ | 20 | _(0.3%) | +-----------+--------------+ | 30 | _(0.7%) | +-----------+--------------+ | 50 | _(1.9%) | +-----------+--------------+ | 100 | _(7.3%) | +-----------+--------------+ Table 19 For constrained devices with fewer than 30 resources, hash-based identification is safe for practical purposes. Implementations MUST verify at device startup that no two resource names produce the same hash. If a collision is detected, the implementation MUST fall back to string-based identification for the affected resources and SHOULD emit a diagnostic warning (e.g., via log output or assertion). If resources are added dynamically at runtime, the implementation MUST check for hash collisions against all existing resources before enabling hash-based identification for the new resource. *Unmatched Hash Handling:* If a receiver receives a PSON unsigned integer in the RESOURCE field and no defined resource matches the hash value, the receiver MUST respond with ERROR (404). This is consistent with the behavior for unrecognized string resource names Section 15.3. 11. Streaming Luis Bustamante Expires 1 October 2026 [Page 35] Internet-Draft IOTMP March 2026 11.1. Stream Lifecycle 11.1.1. Stream State Machine Each stream is identified by a Stream ID and transitions through the following states: +-----------+ | IDLE | +-----+-----+ | Send/Receive START_STREAM +-----v-----+ | OPENING | +-----+-----+ OK / \ ERROR / Timeout +-------v---+ +---v---------+ | ACTIVE | | FAILED | +-------+---+ +---+---------+ | | Stream ID released | +------> (IDLE) | | Send/Receive STOP_STREAM +----v-----+ | CLOSING | +----+-----+ | OK / ERROR / Timeout +----v-----+ | IDLE | Stream ID released +----------+ *State Definitions:* Luis Bustamante Expires 1 October 2026 [Page 36] Internet-Draft IOTMP March 2026 +=========+==============================================+ | State | Description | +=========+==============================================+ | IDLE | No active stream for this Stream ID. The ID | | | is available for reuse. | +---------+----------------------------------------------+ | OPENING | START_STREAM has been sent; awaiting OK or | | | ERROR response. | +---------+----------------------------------------------+ | ACTIVE | Stream is established. STREAM_DATA messages | | | are being sent by the resource owner. | +---------+----------------------------------------------+ | CLOSING | STOP_STREAM has been sent; awaiting OK or | | | ERROR response. | +---------+----------------------------------------------+ | FAILED | START_STREAM was rejected (ERROR) or timed | | | out. Stream ID is released. | +---------+----------------------------------------------+ Table 20 *Transition Rules:* * *IDLE -> OPENING:* A START_STREAM message is sent or received. The Stream ID is now active and MUST NOT be reused until released. * *OPENING -> ACTIVE:* An OK response is received for the START_STREAM. The resource owner MUST send an initial STREAM_DATA immediately Section 11.2. * *OPENING -> FAILED -> IDLE:* An ERROR response is received, or the request times out Section 6.1. The Stream ID is released. * *ACTIVE -> CLOSING (initiator):* The stream initiator sends a STOP_STREAM to terminate the stream. * *ACTIVE -> CLOSING (resource owner):* The resource owner MAY also send a STOP_STREAM with the stream's Stream ID to terminate a stream it did not initiate. This allows the resource owner to signal that the resource is no longer available (e.g., a terminal session has ended, a sensor has failed, or the device is running low on resources). The stream initiator MUST respond with OK and release the Stream ID. * *CLOSING -> IDLE:* An OK or ERROR response is received for the STOP_STREAM request. The Stream ID is released. If no response is received within the request timeout, the stream is considered closed and the Stream ID is released. * *Any state -> IDLE (transport loss):* If the transport connection is lost, all streams are immediately terminated and all Stream IDs are released Section 15.3. *Edge Cases:* Luis Bustamante Expires 1 October 2026 [Page 37] Internet-Draft IOTMP March 2026 +==================+=============================================+ | Condition | Required Behavior | +==================+=============================================+ | STREAM_DATA | Receiver SHOULD buffer or discard; sender | | received while | MUST NOT send STREAM_DATA before receiving | | in OPENING state | OK. | +------------------+---------------------------------------------+ | START_STREAM | Receiver MUST respond with ERROR (409 | | received for an | Conflict). | | already ACTIVE | | | Stream ID | | +------------------+---------------------------------------------+ | STOP_STREAM | Receiver MUST respond with ERROR (409 | | received for a | Conflict). | | Stream ID not in | | | ACTIVE state | | +------------------+---------------------------------------------+ | STOP_STREAM | Initiator MUST still consider the stream | | rejected with | terminated and release the Stream ID. | | ERROR | | +------------------+---------------------------------------------+ | RUN received | Resource executes normally. If stream echo | | targeting a | is enabled Section 10.5, the client MUST | | resource with an | send a STREAM_DATA with the updated state | | ACTIVE stream | on all ACTIVE streams for that resource. | +------------------+---------------------------------------------+ Table 21 11.1.2. Interaction Diagram Server Client | START_STREAM [resource, interval] | |-------------------------------------->| | OK | |<--------------------------------------| | STREAM_DATA [payload] | |<--------------------------------------| (initial state) | STREAM_DATA [payload] | |<--------------------------------------| (after interval) | ... | | STOP_STREAM | |-------------------------------------->| | OK | |<--------------------------------------| Luis Bustamante Expires 1 October 2026 [Page 38] Internet-Draft IOTMP March 2026 11.2. START_STREAM Message +============+=================================+ | Field | Content | +============+=================================+ | STREAM_ID | Identifies this stream. Used | | | in subsequent STREAM_DATA/STOP. | +------------+---------------------------------+ | RESOURCE | Name of the resource to stream. | +------------+---------------------------------+ | PARAMETERS | (OPTIONAL) Stream | | | configuration. See below. | +------------+---------------------------------+ Table 22 *PARAMETERS Encoding:* When PARAMETERS is a simple unsigned integer (varint), it represents the stream interval in milliseconds. A value of 0 indicates event- driven streaming (no periodic sampling). When PARAMETERS is a PSON map, it MAY contain the following fields: +======+======+=================================+=========+ | Key | Type | Description | Default | +======+======+=================================+=========+ | "i" | uint | Stream interval in | 0 | | | | milliseconds. 0 = event-driven. | | +------+------+---------------------------------+---------+ | "cm" | bool | Compact mode. Request compact | false | | | | encoding for STREAM_DATA. | | +------+------+---------------------------------+---------+ Table 23 For backward compatibility, a server that only needs to set the interval MAY send PARAMETERS as a plain varint. A client MUST accept both encodings. Upon receiving START_STREAM: 1. The client registers the stream (Stream ID -> resource + interval + encoding mode). 2. The client evaluates whether the resource supports compact mode (see Section 11.4). Luis Bustamante Expires 1 October 2026 [Page 39] Internet-Draft IOTMP March 2026 3. The client responds with OK. If compact mode was activated, the OK MUST include a PARAMETERS field as a PSON map with "cm": true. If compact mode is not active, the "cm" field SHOULD be omitted. 4. The client immediately sends one STREAM_DATA with the current resource state. 5. If an interval is specified, the client sends STREAM_DATA at that interval. 11.3. STREAM_DATA Message +===========+==========================================+ | Field | Content | +===========+==========================================+ | STREAM_ID | The stream identifier from START_STREAM. | +-----------+------------------------------------------+ | PAYLOAD | The resource output data, PSON-encoded. | +-----------+------------------------------------------+ Table 24 STREAM_DATA messages do NOT require a response. 11.4. Compact Encoding Mode Compact encoding mode reduces per-message overhead for resources that consistently return PSON maps with the same set of keys. It eliminates the repetition of string keys in every STREAM_DATA message after the first. 11.4.1. Negotiation 1. The server requests compact mode by setting "cm": true in the START_STREAM PARAMETERS. If "cm" is absent or false, the server does not want compact mode and the client MUST NOT activate it. 2. The client evaluates whether the resource output is suitable for compaction (i.e., it produces a PSON map with a stable set of keys). 3. If the server requested compact mode and the resource supports it, the client activates it for this stream and confirms by including "cm": true in the OK response PARAMETERS. If the resource does not support compact mode, the client omits "cm" from the OK response and sends all STREAM_DATA messages as full PSON maps. 4. The server MUST check the OK response for "cm": true before assuming compact encoding is active. If "cm" is absent from the OK response, the server MUST decode all STREAM_DATA messages as standard PSON values. Luis Bustamante Expires 1 October 2026 [Page 40] Internet-Draft IOTMP March 2026 11.4.2. Schema Establishment The *first STREAM_DATA* message on a compact stream MUST be a full PSON map with string keys. This message establishes the *key order* (schema) for all subsequent messages on this stream. First STREAM_DATA: PAYLOAD: {"temperature": 23.5, "humidity": 60} (PSON map -- schema) Both the client and server MUST store the key order from this first map. The key order is determined by the iteration order of the PSON map fields. 11.4.3. Compact STREAM_DATA All subsequent STREAM_DATA messages on this stream MUST encode the PAYLOAD as a *PSON array* with values in the same positional order as the keys in the schema message. Subsequent STREAM_DATA: PAYLOAD: [23.6, 61] (PSON array -- values only) The server reconstructs the full map by matching each array position to the corresponding key from the schema. 11.4.4. Rules * The resource owner MUST NOT change the structure of the output (add, remove, or reorder keys at any nesting level) during the lifetime of a compact stream. If the output structure changes, the resource owner MUST terminate the stream with STOP_STREAM. The stream initiator MAY then open a new stream to renegotiate the schema. * If a value is absent or unavailable for a given key, the client MUST encode null at that position to maintain alignment. * The schema is valid for the lifetime of the stream. It is reset only when the stream is stopped (STOP_STREAM) and a new stream is started (START_STREAM). * If the resource output is not a PSON map (e.g., a single number, boolean, string, or binary), compact mode has no effect. The client SHOULD send the value as-is. * The PSON array MUST contain exactly the same number of elements as keys in the schema. If the array length does not match the schema length, the receiver SHOULD treat the STREAM_DATA as malformed and MAY close the stream with STOP_STREAM. Luis Bustamante Expires 1 October 2026 [Page 41] Internet-Draft IOTMP March 2026 11.4.5. Recursive Compaction Compact mode applies recursively to nested PSON maps. When the schema message contains a map value nested within the top-level map, that nested map is also converted to a positional array in subsequent compact messages. This process applies at all nesting levels: * A value that is a PSON map in the schema message becomes a PSON array in compact messages, with its own positional order derived from the schema. * A value that is a scalar (number, boolean, string, null) or a PSON array remains encoded as-is. * The null placeholder rule applies at every nesting level for absent values. *Distinguishing arrays from compacted maps:* The receiver MUST store the type of each value from the schema message (the first STREAM_DATA). In subsequent compact messages, when the receiver encounters a PSON array at a given position, it checks the schema to determine the original type: * If the schema value at that position was a *PSON map*, the array is a compacted map and MUST be expanded using the stored key order. * If the schema value at that position was a *PSON array*, the array is a real array and MUST be kept as-is. Real arrays MAY change in length between messages -- they are not subject to compaction or positional alignment. This distinction is unambiguous because the schema message provides the complete type information for every value at every nesting level. *Example -- Mixed maps and arrays:* Schema message (first STREAM_DATA): PAYLOAD: {"temperature": 23.5, "tags": ["indoor", "sensor"], "location": {"lat": 40.4168, "lon": -3.7038}} Compact messages (subsequent STREAM_DATA): PAYLOAD: [23.6, ["indoor", "active", "new"], [40.4200, -3.7035]] The receiver reconstructs the full structure using the schema: * Position 0: "temperature" -- scalar in schema, value is 23.6 (kept as-is). Luis Bustamante Expires 1 October 2026 [Page 42] Internet-Draft IOTMP March 2026 * Position 1: "tags" -- array in schema, value is ["indoor", "active", "new"] (kept as-is, length may vary). * Position 2: "location" -- map in schema, value is [40.4200, -3.7035] (expanded to {"lat": 40.4200, "lon": -3.7035} using stored key order). 11.4.6. Example Server -> Client: START_STREAM STREAM_ID: 0x00A1 RESOURCE: "environment" PARAMETERS: {"i": 5000, "cm": true} Client -> Server: OK STREAM_ID: 0x00A1 PARAMETERS: {"cm": true} Client -> Server: STREAM_DATA (schema message) STREAM_ID: 0x00A1 PAYLOAD: {"temperature": 23.5, "humidity": 60, "pressure": 1013} (PSON map: 38 bytes) Client -> Server: STREAM_DATA (compact) STREAM_ID: 0x00A1 PAYLOAD: [23.6, 61, 1013] (PSON array: 10 bytes -- 74% smaller) Client -> Server: STREAM_DATA (compact) STREAM_ID: 0x00A1 PAYLOAD: [23.7, 62, 1014] (PSON array: 10 bytes) 11.4.7. Wire Efficiency Compact mode is most effective when: * The resource returns a map with many keys (more string bytes eliminated per sample). * The stream has many samples (the schema overhead is amortized over all samples). * Keys are long (e.g., "temperature", "humidity" vs. short keys like "t", "h"). Luis Bustamante Expires 1 October 2026 [Page 43] Internet-Draft IOTMP March 2026 +=================+================+================+=========+ | Scenario | Normal (bytes/ | Compact | Savings | | | sample) | (bytes/sample) | | +=================+================+================+=========+ | 2 sensors (temp | 35 | 14 | 60% | | + humidity) | | | | +-----------------+----------------+----------------+---------+ | 8 sensors | 83 | 27 | 67% | | (environmental | | | | | station) | | | | +-----------------+----------------+----------------+---------+ Table 25 For 100 samples of 2 sensors: +=========+=============+===============+====================+ | Mode | Total bytes | vs MQTT 3.1.1 | vs MQTT v5 (alias) | +=========+=============+===============+====================+ | Normal | _(3,816) | 40% savings | 11% savings | +---------+-------------+---------------+--------------------+ | Compact | _(1,421) | 78% savings | 67% savings | +---------+-------------+---------------+--------------------+ Table 26 11.4.8. Related Work The problem of eliminating repeated keys in serialized data has been addressed by several existing standards and technologies. IOTMP's compact mode differs from these approaches in how schema negotiation is integrated into the streaming lifecycle. *CBOR Packed* [RFC9543] defines a mechanism for reducing repetition in CBOR by using shared value tables. Frequently occurring strings or values are stored in a table and referenced by index. This is a general-purpose approach that works on any CBOR data but requires an explicit packing/unpacking step and does not integrate with a streaming protocol. *SenML* [RFC8428] is a data format for sensor measurements that reduces repetition through base values and delta encoding (e.g., a base name shared across multiple records). SenML is widely used in CoAP and LwM2M but operates at the data model level, not the transport level, and each record still carries field identifiers. Luis Bustamante Expires 1 October 2026 [Page 44] Internet-Draft IOTMP March 2026 *Protocol Buffers* [ProtocolBuffers] and Apache Avro use pre-shared schema files (.proto, .avsc) to eliminate field names entirely from the wire format. This achieves excellent compactness but requires the schema to be agreed upon at build time, making it unsuitable for dynamic IoT resources whose structure may vary across firmware versions or device types. *MQTT Sparkplug* defines a metric registration mechanism over MQTT where metrics are registered with numeric aliases on first publish, and subsequent messages use aliases instead of full metric names. This is the closest existing approach to IOTMP's compact mode. IOTMP's compact mode combines dynamic schema establishment (from the first STREAM_DATA message), recursive compaction (nested maps become nested arrays), and protocol-integrated negotiation (via START_STREAM parameters) into a single mechanism that requires no pre-shared schema files, no external registries, and no additional protocol messages beyond the standard streaming lifecycle. 11.5. STOP_STREAM Message +===========+================================+ | Field | Content | +===========+================================+ | STREAM_ID | The stream identifier to stop. | +-----------+--------------------------------+ Table 27 Either side MAY send STOP_STREAM to terminate an active stream. The stream initiator sends STOP_STREAM when it no longer needs the data. The resource owner sends STOP_STREAM when the resource is no longer available (e.g., a terminal session has ended or the device cannot sustain the stream). Upon receiving STOP_STREAM: 1. The receiver removes the stream registration and compact schema state (if any). 2. The receiver clears the stream ID on the resource. 3. The receiver responds with OK. Luis Bustamante Expires 1 October 2026 [Page 45] Internet-Draft IOTMP March 2026 11.6. Multiple Concurrent Streams A client MUST support multiple simultaneous streams. Different resources MAY have different stream intervals and encoding modes (normal or compact). The Stream ID space is partitioned between client and server Section 6.2, providing 32,768 IDs per side -- ample for concurrent streams on a single connection. 11.7. Flow Control IOTMP does not define an application-level flow control mechanism (such as credit-based windows). Flow control is delegated to the transport layer: TCP's built-in backpressure naturally throttles the sender when the receiver cannot consume data fast enough. If a device cannot accept a new stream due to resource constraints (memory, CPU, or maximum stream limit), it MUST respond to the START_STREAM request with ERROR (429). Similarly, if a device cannot execute a RUN request due to overload, it MAY respond with ERROR (429). The server controls the data rate of each stream through the interval parameter in START_STREAM. Implementations SHOULD use appropriate intervals to avoid overwhelming constrained devices. IOTMP does not define application-level delivery guarantees (such as QoS levels). Request-response exchanges (RUN, DESCRIBE, START_STREAM, STOP_STREAM) provide implicit delivery confirmation through OK/ERROR responses. Streaming data (STREAM_DATA) is ephemeral by nature -- each sample supersedes the previous one -- so retransmission of individual samples is unnecessary. Connection- level reliability is delegated to the transport layer (TCP/TLS). 12. Extensibility 12.1. Symmetric Resource Model IOTMP is a symmetric protocol: both clients and servers MAY expose resources. After authentication, either side can invoke resources on the peer using RUN, DESCRIBE, START_STREAM, and STOP_STREAM messages. The resource namespace is defined by each implementation. This symmetry enables diverse use cases without protocol-level changes: * A client can expose sensor resources that the server reads or streams. Luis Bustamante Expires 1 October 2026 [Page 46] Internet-Draft IOTMP March 2026 * A server can expose storage, notification, or inter-device communication resources that clients invoke via RUN. * Both sides discover available resources through DESCRIBE. 12.2. Application Profiles The specific resources exposed by a server or client are outside the scope of this specification. Implementations MAY define application profiles that specify well-known resource names, their expected I/O types, and PAYLOAD formats. 12.3. Bidirectional Stream Channels While Section 11 describes streaming primarily in the context of periodic telemetry (resource owner sends STREAM_DATA at regular intervals), IOTMP streams are general-purpose bidirectional data channels. Once a stream is active, *both sides* MAY send STREAM_DATA messages on the same Stream ID simultaneously. This enables a wide range of application patterns beyond periodic sampling, using only existing protocol primitives. This bidirectional capability has been validated in production implementations supporting remote terminals, file transfers of multiple gigabytes, TCP proxy tunneling, and firmware updates -- all over the same IOTMP connection used for sensor telemetry, with no protocol modifications. 12.3.1. Channel Establishment All bidirectional stream use cases follow the same lifecycle: 1. The stream initiator sends START_STREAM with a resource name and application-specific parameters. 2. The resource owner responds with OK (optionally confirming negotiated parameters). 3. Both sides exchange STREAM_DATA messages on the Stream ID for the duration of the session. 4. Either side terminates the channel with STOP_STREAM. Luis Bustamante Expires 1 October 2026 [Page 47] Internet-Draft IOTMP March 2026 Initiator Resource Owner | START_STREAM (resource, params) | |------------------------------------------->| | OK (negotiated params) | |<-------------------------------------------| | STREAM_DATA <-------------------------- | (both directions, | ----------------------------> STREAM_DATA | concurrent, on | STREAM_DATA <-------------------------- | the same Stream ID) | ----------------------------> STREAM_DATA | | ... | | STOP_STREAM | |------------------------------------------->| | OK | |<-------------------------------------------| The content and semantics of the STREAM_DATA messages are defined by the application profile for each resource. The protocol layer treats them identically regardless of the use case. 12.3.2. Use Case: Interactive Sessions (Terminal, Proxy) For interactive sessions, STREAM_DATA carries raw binary data in both directions simultaneously. This includes: * *Remote terminal:* A PTY-backed shell session where user input flows as binary STREAM_DATA from initiator to resource owner, and terminal output flows back. START_STREAM parameters specify initial terminal dimensions (columns, rows). * *TCP proxy tunneling:* A transparent relay where STREAM_DATA carries raw TCP bytes between the initiator and a local TCP endpoint on the device (e.g., a router admin panel, PLC interface, or database port). START_STREAM parameters specify the target host and port. In both cases, data flows continuously in both directions with no application-level acknowledgment -- TCP backpressure provides sufficient flow control for interactive traffic. Initiator Resource Owner | START_STREAM ("terminal", {"cols":80}) | |------------------------------------------->| opens PTY | OK | |<-------------------------------------------| | STREAM_DATA ("ls -la\n") | user input |------------------------------------------->| | STREAM_DATA ("total 42\ndrwx...") | terminal output |<-------------------------------------------| | ... | Luis Bustamante Expires 1 October 2026 [Page 48] Internet-Draft IOTMP March 2026 *Updating stream parameters at runtime:* Some interactive sessions require runtime parameter updates without interrupting the data flow (e.g., resizing a terminal window). Since START_STREAM cannot be re-sent on an active Stream ID, the recommended application-level pattern is to expose a companion resource that accepts parameter updates via RUN: Initiator Resource Owner | START_STREAM ("terminal/main") | |------------------------------------------->| opens PTY (80x24) | OK | |<-------------------------------------------| | STREAM_DATA <---------------------------> | (bidirectional data) | ... | | RUN ("terminal/main/params", | | {"size":{"cols":120,"rows":40}}) | resize request |------------------------------------------->| ioctl(TIOCSWINSZ) | OK | |<-------------------------------------------| | STREAM_DATA <---------------------------> | (stream continues) The companion resource (e.g., terminal/main/params) is a regular IOTMP resource with I/O type input that updates the session configuration. This pattern keeps the protocol simple -- no new message types or stream renegotiation mechanisms are needed -- while allowing applications to define whatever runtime parameters they require. 12.3.3. Use Case: Bulk Data Transfer (Files, Firmware) For large data transfers (files, firmware images, logs), one side sends data chunks as binary STREAM_DATA and the other side sends acknowledgments as PSON maps in the opposite direction. This provides application-level flow control suitable for multi-gigabyte transfers. *Data flow:* * The *sender* transmits data as a sequence of STREAM_DATA messages, each carrying a PSON binary value within the negotiated message size limit. * The *receiver* periodically sends ACK messages as STREAM_DATA in the reverse direction. Each ACK is a PSON map: {"ack": , "bytes": }. * The stream initiator sends STOP_STREAM when the transfer is complete. Luis Bustamante Expires 1 October 2026 [Page 49] Internet-Draft IOTMP March 2026 *Application-level flow control:* The recommended approach uses a byte-based sliding window: * The stream initiator specifies a window_size in the START_STREAM parameters -- the maximum number of unacknowledged bytes allowed in flight. * The sender tracks bytes_in_flight (bytes sent minus bytes acknowledged). When bytes_in_flight reaches window_size, the sender pauses until an ACK is received. * The ACK frequency is adaptive: implementations SHOULD send ACKs approximately every 1% of total transfer size, bounded between one chunk size and 1 MB. *Recommended defaults:* +=============+=========+=================================+ | Parameter | Default | Description | +=============+=========+=================================+ | chunk_size | 64 KB | Size of each binary STREAM_DATA | | | | payload. Confirmed by resource | | | | owner in OK response. | +-------------+---------+---------------------------------+ | window_size | 2 MB | Maximum unacknowledged bytes in | | | | flight. | +-------------+---------+---------------------------------+ | ack_timeout | 10 s | Timeout waiting for an ACK | | | | before aborting the transfer. | +-------------+---------+---------------------------------+ Table 28 *Bandwidth limiting:* Implementations MAY limit transfer rate by introducing controlled delays between chunks (sender-side) or between ACKs (receiver-side). Delaying ACKs naturally throttles the sender through the flow control window. *Small data optimization:* For data that fits within a single message (less than or equal to the negotiated maximum message size), implementations SHOULD use an inline RUN request/response instead of opening a stream, avoiding the START_STREAM/STOP_STREAM overhead. Luis Bustamante Expires 1 October 2026 [Page 50] Internet-Draft IOTMP March 2026 12.3.4. Use Case: Command Execution For operations that are self-contained (short-lived, bounded output), a simple RUN request/response is sufficient. The client exposes a resource that accepts a command as input and returns the result (stdout, stderr, exit code) in the OK payload. No streaming is needed. S -> C: RUN ("cmd", {"exec":"uname","args":["-a"],"timeout":10}) C -> S: OK ({"stdout": "Linux device 5.15.0 ...", "stderr": "", "exit_code": 0}) 12.3.5. Design Principle These use cases illustrate a key IOTMP design property: the protocol provides a small set of general-purpose primitives (RUN for one-shot operations, START_STREAM/STREAM_DATA for persistent channels) that application profiles combine to address diverse requirements. No protocol-level changes are needed to support new use cases -- only the definition of new resource names and their data formats. 12.4. Example: Client Invoking a Server Resource RUN Message: STREAM_ID: 0x1234 RESOURCE: "storage/sensor_data" (PSON string) PAYLOAD: {"temperature": 25.3, "humidity": 60} (PSON map) Response: OK STREAM_ID: 0x1234 12.5. Example: Server Invoking a Client Resource RUN Message: STREAM_ID: 0x5679 RESOURCE: "led" (PSON string) PAYLOAD: true (PSON boolean) Response: OK STREAM_ID: 0x5679 13. Error Handling Luis Bustamante Expires 1 October 2026 [Page 51] Internet-Draft IOTMP March 2026 13.1. ERROR Message An ERROR message is sent in response to a failed request. It carries the same Stream ID as the request. +============+==========================================+ | Field | Content | +============+==========================================+ | STREAM_ID | Mirrors the request Stream ID. | +------------+------------------------------------------+ | PARAMETERS | (OPTIONAL) Status code (varint). | +------------+------------------------------------------+ | PAYLOAD | (OPTIONAL) Error details (PSON-encoded). | +------------+------------------------------------------+ Table 29 13.2. Status Codes The PARAMETERS field in OK and ERROR messages MAY carry a numeric status code. Implementations SHOULD use HTTP status codes as defined in [RFC9110]. *Rationale:* IOTMP reuses HTTP status codes rather than defining a separate code space for two reasons. First, IOTMP's resource- oriented model (named resources with read/write/invoke operations) maps naturally to HTTP semantics, making HTTP status codes directly applicable: a missing resource is 404, a malformed request is 400, a timeout is 408. Second, IOTMP is designed to support transparent HTTP bridging, where an HTTP gateway translates between HTTP requests and IOTMP messages. Reusing HTTP status codes enables this bridge to pass status codes through without a translation table, preserving the original error semantics end-to-end. If the PARAMETERS field is absent or zero, a generic success (for OK) or generic error (for ERROR) is assumed. The following status codes are commonly used: Luis Bustamante Expires 1 October 2026 [Page 52] Internet-Draft IOTMP March 2026 +======+==============+========================================+ | Code | Name | Usage | +======+==============+========================================+ | 200 | OK | Successful operation (implicit if | | | | omitted in OK). | +------+--------------+----------------------------------------+ | 301 | Moved | Server redirect: client must update | | | Permanently | stored address Section 9.6. | +------+--------------+----------------------------------------+ | 307 | Temporary | Server redirect: client should connect | | | Redirect | to indicated server Section 9.6. | +------+--------------+----------------------------------------+ | 400 | Bad Request | Malformed request or invalid | | | | parameters. | +------+--------------+----------------------------------------+ | 401 | Unauthorized | Authentication required or credentials | | | | invalid. | +------+--------------+----------------------------------------+ | 403 | Forbidden | Insufficient permissions for the | | | | requested action. | +------+--------------+----------------------------------------+ | 404 | Not Found | Resource does not exist. | +------+--------------+----------------------------------------+ | 408 | Request | Operation timed out. | | | Timeout | | +------+--------------+----------------------------------------+ | 409 | Conflict | Resource state conflict (e.g., already | | | | exists). | +------+--------------+----------------------------------------+ | 413 | Content Too | Payload exceeds allowed size. | | | Large | | +------+--------------+----------------------------------------+ | 429 | Too Many | Rate limit exceeded. | | | Requests | | +------+--------------+----------------------------------------+ | 500 | Internal | Unexpected error during processing. | | | Server Error | | +------+--------------+----------------------------------------+ Table 30 Implementations MAY use other HTTP status codes as defined in [RFC9110]. Receivers that encounter an unrecognized status code SHOULD treat it according to the class of the code (2xx = success, 3xx = redirect, 4xx = client error, 5xx = server error). Luis Bustamante Expires 1 October 2026 [Page 53] Internet-Draft IOTMP March 2026 13.3. Error Payload When the PAYLOAD field is present in an ERROR message, it SHOULD be a PSON map containing an "error" key with a human-readable error description: ERROR Message: STREAM_ID: 0x1234 PARAMETERS: 404 PAYLOAD: {"error": "Resource 'sensor' does not exist"} Implementations MAY include additional keys in the error payload for application-specific diagnostics. 13.4. OK Message with Status Code An OK message MAY also carry a status code in the PARAMETERS field to provide more specific success information. If omitted, a generic 200 (OK) is assumed. 13.5. Connection-Level Errors * *Transport failure:* If the transport connection is lost, all active streams are terminated. The client SHOULD attempt reconnection with exponential backoff. * *Message too large:* If a received message exceeds the maximum size, the receiver MUST close the connection. * *Invalid message type:* If a receiver encounters an unknown message type, it SHOULD ignore the message. * *Decode failure:* If a message body cannot be decoded, the receiver MUST close the connection. 14. Security Considerations This section follows the guidelines of [RFC3552] for security considerations. 14.1. Threat Model IOTMP is designed for client-server communication between IoT devices and infrastructure servers (brokers). The following threat model identifies the primary attack surfaces and the protocol's defenses. *Actors:* * *Device (Client):* A constrained IoT device connecting to a broker over a potentially untrusted network. Luis Bustamante Expires 1 October 2026 [Page 54] Internet-Draft IOTMP March 2026 * *Server (Broker):* An infrastructure server managing device connections, authentication, and resource routing. * *External Attacker:* An entity with network access who can observe, inject, or modify traffic between the device and the server. * *Compromised Device:* A legitimate device whose credentials or firmware have been compromised. *Threat Analysis:* +=====================+==============+=============+===============+ | Threat |Attack Vector |Impact |Mitigation | +=====================+==============+=============+===============+ | *Eavesdropping* |Passive |Credential |TLS encryption | | |observation of|theft, data |Section 14.2 | | |unencrypted |leakage | | | |traffic | | | +---------------------+--------------+-------------+---------------+ | *Credential Replay* |Attacker |Unauthorized |TLS prevents | | |captures and |access, |replay; servers| | |replays a |device |SHOULD reject | | |valid CONNECT |impersonation|duplicate | | |message | |CONNECT on same| | | | |connection | +---------------------+--------------+-------------+---------------+ | *Man-in-the-Middle* |Attacker |Data |TLS with server| | |intercepts and|manipulation,|certificate | | |modifies |command |validation; | | |messages in |injection |mTLS for mutual| | |transit | |authentication | +---------------------+--------------+-------------+---------------+ | *Device |Attacker uses |Unauthorized |Strong unique | | Impersonation* |stolen or |data |credentials per| | |guessed |injection, |device; rate | | |credentials |control of |limiting on | | | |resources |authentication;| | | | |credential | | | | |rotation | | | | |Section 14.3 | +---------------------+--------------+-------------+---------------+ | *Message Injection* |Attacker |Unauthorized |TLS integrity | | |injects |resource |protection; | | |crafted IOTMP |invocation, |servers MUST | | |messages into |data |validate Stream| | |a connection |corruption |ID correlation | +---------------------+--------------+-------------+---------------+ | *Message Tampering* |Modification |Altered |TLS message | | |of messages in|sensor data, |integrity | Luis Bustamante Expires 1 October 2026 [Page 55] Internet-Draft IOTMP March 2026 | |transit |modified | | | | |commands | | +---------------------+--------------+-------------+---------------+ | *Denial of Service |Attacker opens|Server |CONNECT | | (Connection Flood)* |many TCP |resource |handshake | | |connections |exhaustion |timeout; per-IP| | |without | |connection | | |authenticating| |limits | | | | |Section 14.5 | +---------------------+--------------+-------------+---------------+ | *Denial of Service |Compromised |Server CPU/ |Rate limiting; | | (Message Flood)* |device sends |memory |maximum message| | |excessive |exhaustion |size | | |messages | |enforcement | | | | |Section 14.5 | +---------------------+--------------+-------------+---------------+ | *Stream Exhaustion* |Client opens |Server memory|Per-device | | |maximum number|exhaustion |stream limits; | | |of streams | |idle stream | | |without | |timeout | | |closing them | |Section 14.6 | +---------------------+--------------+-------------+---------------+ | *Malformed Payload* |Crafted PSON |Stack |PSON | | |data with |overflow, |validation; | | |extreme |memory |nesting depth | | |nesting, |exhaustion on|limits; length | | |oversized |constrained |bounds checking| | |lengths |devices |Section 14.6 | +---------------------+--------------+-------------+---------------+ | *Resource |Attacker uses |Information |Authorization | | Enumeration* |DESCRIBE to |disclosure; |on DESCRIBE; | | |discover all |aids targeted|TLS to prevent | | |device |attacks |passive | | |capabilities | |discovery | | | | |Section 14.8 | +---------------------+--------------+-------------+---------------+ | *Unauthorized |Authenticated |Privilege |Per-resource | | Resource Access* |device invokes|escalation, |authorization; | | |resources |unauthorized |least privilege| | |beyond its |control |principle | | |scope | |Section 14.4 | +---------------------+--------------+-------------+---------------+ Table 31 Luis Bustamante Expires 1 October 2026 [Page 56] Internet-Draft IOTMP March 2026 14.2. Transport Security IOTMP itself does not provide encryption or integrity protection at the application layer. Implementations MUST rely on TLS (version 1.2 or later, as specified in [RFC8446]) for: * *Confidentiality:* Encryption of credentials and application data. * *Server Authentication:* Verification of the server's identity via X.509 certificates. * *Mutual Authentication:* (OPTIONAL) Verification of the client's identity via client certificates (mTLS). * *Message Integrity:* Protection against message tampering and replay. Unencrypted connections (port 25204) SHOULD only be used in isolated, trusted networks or during development. Production deployments MUST use TLS. Implementations SHOULD support TLS 1.3 [RFC8446] for improved handshake performance (1-RTT vs 2-RTT) and stronger cipher suites. TLS 1.2 MAY be supported for compatibility with constrained devices that lack TLS 1.3 implementations. 14.3. Authentication The CONNECT message transmits authentication data in the PAYLOAD field. The format of this data depends on the authentication type ("at" parameter, Section 9.2). For type 0 (Credentials), the payload contains namespace, device ID, and credential in cleartext. For type 1 (Token), the payload contains a bearer token. Without TLS, this data is vulnerable to eavesdropping. Implementations MUST use TLS when transmitting over untrusted networks. Implementations MUST: * Reject CONNECT messages with empty, malformed, or missing authentication data. * Respond with ERROR (401) when authentication credentials are invalid, expired, or do not match any registered device. * Close the connection after a failed CONNECT response (ERROR). The client MUST NOT retry authentication on the same transport connection. * Respond with ERROR (400) if the "at" value is not supported by the server. Implementations SHOULD: Luis Bustamante Expires 1 October 2026 [Page 57] Internet-Draft IOTMP March 2026 * Use strong, unique credentials per device (type 0) or short-lived tokens (type 1). * Store credentials securely on the device (e.g., in a secure element or encrypted storage). * Implement rate limiting on authentication attempts to mitigate brute-force attacks (RECOMMENDED: maximum 3 attempts per minute per source IP). * Support credential rotation without requiring device firmware updates. * Log failed authentication attempts for security monitoring. * Prefer certificate-based authentication (type 2) or token-based authentication (type 1) over plain credentials (type 0) in production deployments. The "at" parameter is extensible. Additional authentication types MAY be defined in separate specifications. 14.3.1. Certificate Authentication Security When using authentication type 2 (Certificate), deployments may use either per-device certificates or fleet certificates (a single certificate shared across multiple devices within a namespace). Each approach has different security properties: *Per-device certificates:* The certificate's subject (CN or SAN) identifies both the namespace and the device. The server extracts the full device identity from the certificate. If a PAYLOAD is present, the server MUST verify that the declared namespace and device_id match the certificate identity and MUST reject the connection with ERROR (401) on mismatch. This is the RECOMMENDED approach for production deployments. *Fleet certificates:* The certificate identifies only the namespace (e.g., a shared CA per organizational scope). The device MUST send a PAYLOAD with [namespace, device_id] so the server can identify the specific device. In this model, any device holding the fleet certificate can claim any device_id within the namespace. Deployments using fleet certificates SHOULD be aware that compromise of a single device's certificate allows impersonation of any other device in the same namespace. To mitigate this risk, operators SHOULD: * Limit the scope of fleet certificates to the smallest practical namespace. * Implement server-side device registries that reject unknown device_ids even when the certificate is valid. * Monitor for anomalous device_id claims (e.g., a single certificate claiming multiple device_ids in rapid succession). Luis Bustamante Expires 1 October 2026 [Page 58] Internet-Draft IOTMP March 2026 * Prefer per-device certificates when the PKI infrastructure supports it. 14.4. Authorization Authorization (which operations a device may perform) is outside the scope of this specification and is determined by the server implementation. However, implementations SHOULD: * Follow the principle of least privilege: devices SHOULD only be authorized to access the resources they require. * Enforce authorization on all message types, including DESCRIBE, RUN, START_STREAM, and STOP_STREAM. * Support per-resource access control (e.g., device X may read resource Y but not write to it). * Apply authorization checks on both client-to-server and server-to- client operations in symmetric mode. 14.5. Denial of Service *Connection-level mitigations:* * Servers SHOULD enforce a maximum time for the CONNECT handshake (RECOMMENDED: 10 seconds). If the client does not send a valid CONNECT message within this time, the server MUST close the connection. * Servers SHOULD limit the number of concurrent connections per account or IP address (RECOMMENDED: maximum 100 connections per IP). * Servers SHOULD implement connection rate limiting to prevent SYN flood attacks (RECOMMENDED: maximum 10 new connections per second per IP). * Servers SHOULD close idle connections that have not sent any message within twice the negotiated keepalive interval. *Message-level mitigations:* * Implementations MUST enforce the negotiated maximum message size. A receiver that encounters a message exceeding its maximum MUST close the connection. * Servers SHOULD implement per-device message rate limiting (RECOMMENDED: configurable, default 100 messages per second). * Implementations MUST validate the Message Type field. Unknown message types SHOULD be ignored; malformed framing (e.g., varint that does not terminate) MUST cause connection closure. 14.6. Resource Exhaustion Luis Bustamante Expires 1 October 2026 [Page 59] Internet-Draft IOTMP March 2026 * Servers SHOULD limit the number of concurrent streams per device (RECOMMENDED: maximum 256 active streams). * Servers SHOULD implement idle stream detection and close streams that have not produced data within a configurable timeout. * Implementations SHOULD validate PSON data structures to prevent deeply nested or excessively large payloads from consuming excessive processing time or memory. A maximum nesting depth of 16 levels is RECOMMENDED for constrained devices. * Implementations MUST validate that varint encoding terminates within 4 bytes (representing values up to 2^28 - 1). A varint that exceeds this limit MUST be treated as a decode error and the connection MUST be closed. 14.7. Secure Credential Storage Devices operating in physically accessible environments face the risk of credential extraction through hardware attacks (JTAG, flash dumping, side-channel analysis). Implementations SHOULD: * Store credentials in hardware secure elements or trusted execution environments when available. * Use derived keys rather than storing master credentials on devices. * Support remote credential revocation so that compromised devices can be disabled without physical access. 14.8. Privacy * Device identifiers, resource names, and DESCRIBE responses are visible to any entity with access to the network traffic unless TLS is used. TLS MUST be used when privacy of device metadata is required. * Implementations SHOULD minimize the exposure of device metadata in resource names and DESCRIBE responses when operating over untrusted networks. * The DESCRIBE message can reveal the full API surface of a device, including resource names, I/O types, data schemas, and human- readable descriptions. Servers MUST enforce authorization on DESCRIBE requests and SHOULD allow operators to restrict which clients can perform API discovery. * Servers SHOULD NOT log secrets (credentials, tokens). For type 0 (Credentials), implementations SHOULD log only the namespace and device ID for audit purposes. 15. Conformance Requirements Luis Bustamante Expires 1 October 2026 [Page 60] Internet-Draft IOTMP March 2026 15.1. Client Conformance A conformant IOTMP client implementation MUST: * Support TCP and TLS transports. * Send a CONNECT message as the first message after transport establishment. * Support all message types defined in Section 6. * Correctly encode and decode PSON values as defined in [PSON]. * Support at least one concurrent stream. * Implement keepalive as defined in Section 9.4. * Implement reconnection with exponential backoff as defined in Section 9.7. * Support messages up to at least 32,768 bytes (or the negotiated maximum). * Include a Stream ID in all request messages (CONNECT, RUN, DESCRIBE, START_STREAM, STOP_STREAM). * Use even Stream IDs for all client-initiated requests Section 6.2. * Not reuse a Stream ID that is currently active. * Close the connection upon receiving an ERROR response to CONNECT. A conformant client SHOULD: * Support WebSocket transport with the "iotmp" subprotocol. * Support multiple concurrent streams. * Support compact streaming mode Section 11.4. * Support DESCRIBE with [JSON-Schema] Section 10.4. 15.2. Server Conformance A conformant IOTMP server implementation MUST: * Support TCP and TLS transports. * Process CONNECT messages and respond with OK or ERROR. * Support all message types defined in Section 6. * Correctly encode and decode PSON values as defined in [PSON]. * Respect the client's declared maximum message size ("ms" parameter). * Use odd Stream IDs for all server-initiated requests Section 6.2. * Validate that client-initiated requests use even Stream IDs. * Echo KEEP_ALIVE messages as defined in Section 9.4. * Implement keepalive timeout detection. * Enforce the CONNECT handshake timeout Section 14.5. * Enforce maximum message size Section 14.5. * Validate the Message Type field and close the connection on malformed framing. * Enforce authorization on all resource operations. Luis Bustamante Expires 1 October 2026 [Page 61] Internet-Draft IOTMP March 2026 A conformant server SHOULD: * Support WebSocket transport with the "iotmp" subprotocol. * Support server redirect Section 9.6. * Include status codes in OK and ERROR responses Section 13.2. * Support compact streaming mode negotiation Section 11.4. * Validate incoming data against [JSON-Schema] when available from DESCRIBE Section 10.4. 15.3. Connection Lifecycle Conformance The following rules govern connection state transitions and edge cases: *Stream Recovery on Reconnect:* When a transport connection is lost, all active streams are terminated. Stream state (Stream IDs, compact mode schemas) is NOT preserved across connections. After reconnection: 1. The client MUST send a new CONNECT message. 2. The server MUST re-establish any desired streams by sending new START_STREAM messages. 3. Both sides MUST NOT reference Stream IDs from the previous connection. *In-Flight Message Handling:* * If a request message (RUN, DESCRIBE, START_STREAM, STOP_STREAM) has been sent but no response received when the connection drops, the request is considered failed. The client SHOULD NOT assume the operation was executed. * If a response (OK, ERROR) is lost due to connection failure, the server MAY have already executed the operation. The client MAY safely re-send the request after reconnection, provided the operation is idempotent. *Idempotency Guidance:* IOTMP's request-response model (RUN -> OK/ERROR) provides implicit delivery confirmation for the common case: if the client receives OK, the operation succeeded; if it receives ERROR, it failed. The only ambiguous scenario occurs when the connection drops after the server executes the operation but before the client receives the response. Luis Bustamante Expires 1 October 2026 [Page 62] Internet-Draft IOTMP March 2026 IOTMP does not define protocol-level delivery guarantees (such as QoS levels) because its request-response model already covers the vast majority of cases. For the rare connection-loss scenario, the recommended approach is to design resource operations as *absolute state transitions* rather than relative or toggling operations: +================+============+=================================+ | Pattern | Example | Safe to retry? | +================+============+=================================+ | Absolute state | {"relay": | Yes -- setting the same state | | (RECOMMENDED) | true} | twice has no additional effect. | +----------------+------------+---------------------------------+ | Relative/ | {"action": | No -- retrying may reverse the | | toggle (AVOID) | "toggle"} | intended state. | +----------------+------------+---------------------------------+ Table 32 Resources that follow this pattern are inherently idempotent: re- executing the same RUN after reconnection produces the same result whether the original request was executed or not. This eliminates the need for protocol-level deduplication mechanisms. Additionally, if the client needs to verify the outcome of an ambiguous request after reconnection, it can query the current resource state by sending a RUN (for output resources) or DESCRIBE to the resource. This allows the client to confirm whether the previous operation took effect before deciding whether to retry. For the rare cases where non-idempotent operations are unavoidable, application profiles MAY implement their own deduplication (e.g., including a unique request identifier in the PAYLOAD that the receiver checks against recently processed requests). *Invalid State Handling:* Luis Bustamante Expires 1 October 2026 [Page 63] Internet-Draft IOTMP March 2026 +=============================+==================================+ | Condition | Required Behavior | +=============================+==================================+ | Client sends a message | Server MUST close the | | before CONNECT | connection. | +-----------------------------+----------------------------------+ | Client sends CONNECT after | Server MUST respond with ERROR | | authentication | (400) and close the connection. | +-----------------------------+----------------------------------+ | Message received with | Receiver SHOULD ignore the | | unknown message type | message. | +-----------------------------+----------------------------------+ | Message received with | Receiver MUST ignore the unknown | | unknown field number | field and process known fields. | +-----------------------------+----------------------------------+ | STREAM_DATA received for | Receiver SHOULD ignore the | | unknown Stream ID | message. | +-----------------------------+----------------------------------+ | RUN/DESCRIBE received for | Receiver MUST respond with ERROR | | non-existent resource | (404). | +-----------------------------+----------------------------------+ | Message exceeds negotiated | Receiver MUST close the | | maximum size | connection. | +-----------------------------+----------------------------------+ | Varint does not terminate | Receiver MUST close the | | within 4 bytes | connection. | +-----------------------------+----------------------------------+ | Keepalive timeout exceeded | Server MUST close the | | | connection. | +-----------------------------+----------------------------------+ | Request with Stream ID from | Receiver MUST respond with ERROR | | wrong partition Section 6.2 | (400). | +-----------------------------+----------------------------------+ | Request with a Stream ID | Receiver MUST respond with ERROR | | that is already active | (409). | +-----------------------------+----------------------------------+ Table 33 15.4. Conformance Test Vectors The following test vectors allow implementations to verify correct encoding and decoding. Each vector specifies the input, the expected wire encoding (hex), and the total byte count. Luis Bustamante Expires 1 October 2026 [Page 64] Internet-Draft IOTMP March 2026 15.4.1. KEEP_ALIVE *Input:* KEEP_ALIVE message (empty body). Expected encoding: 05 00 Total: 2 bytes * 05: Message type KEEP_ALIVE (varint 5). * 00: Body size 0 (varint 0). 15.4.2. CONNECT *Input:* CONNECT (auth type 0) with credentials ["acme1", "device1", "secret123"] (namespace, device_id, credential), Stream ID = 42 (client-initiated, even). Expected encoding: 03 1C 08 2A 1A E3 85 61 63 6D 65 31 87 64 65 76 69 63 65 31 89 73 65 63 72 65 74 31 32 33 Total: 30 bytes Decoding verification: * 03: Message type CONNECT. * 1C: Body size 28. * 08: Field tag (STREAM_ID, varint). 2A: Stream ID = 42. * 1A: Field tag (PAYLOAD, pson). * E3: PSON array, 3 elements. * 85 61 63 6D 65 31: PSON string "acme1" (namespace, 5 bytes). * 87 64 65 76 69 63 65 31: PSON string "device1" (device_id, 7 bytes). * 89 73 65 63 72 65 74 31 32 33: PSON string "secret123" (credential, 9 bytes). 15.4.3. OK Response *Input:* OK response to Stream ID 42, no status code, no payload. Expected encoding: 01 02 08 2A Total: 4 bytes * 01: Message type OK. * 02: Body size 2. * 08: Field tag (STREAM_ID, varint). 2A: Stream ID = 42. Luis Bustamante Expires 1 October 2026 [Page 65] Internet-Draft IOTMP March 2026 15.4.4. RUN with Resource Name *Input:* RUN resource "led" with payload {"on": true}, Stream ID = 100 (client-initiated, even). Expected encoding: 06 0D 08 64 22 83 6C 65 64 1A C1 82 6F 6E 61 Total: 15 bytes Decoding verification: * 06: Message type RUN. * 0D: Body size 13. * 08 64: STREAM_ID = 100. * 22 83 6C 65 64: RESOURCE = PSON string "led" (3 bytes). * 1A C1 82 6F 6E 61: PAYLOAD = PSON map {1 entry: "on" -> true}. 15.4.5. RUN with Resource Hash *Input:* RUN resource hash 0x1A2B (FNV-1a hash of a resource name), no payload, Stream ID = 7 (server-initiated, odd). Expected encoding: 06 05 08 07 20 AB 34 Total: 7 bytes Decoding verification: * 06: Message type RUN. * 05: Body size 5. * 08 07: STREAM_ID = 7. * 20 AB 34: RESOURCE = varint 6699 (0x1A2B). 15.4.6. ERROR with Status Code and Payload *Input:* ERROR for Stream ID 42, status 404, payload {"error": "Not found"}. Expected encoding: 02 17 08 2A 10 94 03 1A C1 85 65 72 72 6F 72 89 4E 6F 74 20 66 6F 75 6E 64 Total: 25 bytes Decoding verification: * 02: Message type ERROR. * 17: Body size 23. * 08 2A: STREAM_ID = 42. Luis Bustamante Expires 1 October 2026 [Page 66] Internet-Draft IOTMP March 2026 * 10 94 03: PARAMETERS (varint) = 404. * 1A C1 85 65 72 72 6F 72 89 4E 6F 74 20 66 6F 75 6E 64: PAYLOAD = PSON map {"error": "Not found"}. 15.4.7. START_STREAM with Compact Mode *Input:* START_STREAM resource "temperature", interval 5000ms, compact mode, Stream ID = 161 (server-initiated, odd). Expected encoding: 08 1B 08 A1 01 12 C2 81 69 1F 88 27 82 63 6D 61 22 8B 74 65 6D 70 65 72 61 74 75 72 65 Total: 29 bytes Decoding verification: * 08: Message type START_STREAM. * 1B: Body size 27. * 08 A1 01: STREAM_ID = 161 (varint). * 12 C2 81 69 1F 88 27 82 63 6D 61: PARAMETERS (pson map) = {"i": 5000, "cm": true}. * 22 8B 74 65 6D 70 65 72 61 74 75 72 65: RESOURCE = PSON string "temperature" (11 bytes). 16. IANA Considerations 16.1. Port Number Registration This specification requests the assignment of the following TCP port numbers from IANA's Service Name and Transport Protocol Port Number Registry: +==============+=======+===========+================+===========+ | Service Name | Port | Transport | Description | Reference | +==============+=======+===========+================+===========+ | iotmp | 25204 | TCP | IOTMP over TCP | This | | | | | (unencrypted) | document | +--------------+-------+-----------+----------------+-----------+ | iotmps | 25206 | TCP | IOTMP over TLS | This | | | | | | document | +--------------+-------+-----------+----------------+-----------+ Table 34 *Registration details:* * *Service Name:* iotmp / iotmps * *Transport Protocol:* TCP Luis Bustamante Expires 1 October 2026 [Page 67] Internet-Draft IOTMP March 2026 * *Assignee:* Alvaro Luis Bustamante / Internet of Thinger SL * *Contact:* alvaro@thinger.io * *Description:* Internet of Things Message Protocol -- a binary application-layer protocol for bidirectional communication between IoT devices and servers. * *Reference:* This document * *Assignment Notes:* Port 25204 is for unencrypted connections (development and trusted networks only). Port 25206 is for TLS- encrypted connections (production use). Implementations SHOULD default to port 25206 (TLS). 16.2. WebSocket Subprotocol Registration This specification requests registration of the following entry in the IANA WebSocket Subprotocol Name Registry, as defined in [RFC6455], Section 11.5: * *Subprotocol Identifier:* iotmp * *Subprotocol Common Name:* Internet of Things Message Protocol * *Subprotocol Definition:* This document * *Reference:* This document When operating over WebSocket, the client MUST include "iotmp" in the Sec-WebSocket-Protocol header during the handshake. The server MUST confirm the subprotocol in the response. All WebSocket messages MUST use binary opcode (0x02) and each frame MUST contain exactly one complete IOTMP message Section 4.3. 16.3. Message Type Registry IANA is requested to create a new registry entitled "IOTMP Message Types" in a new "Internet of Things Message Protocol (IOTMP)" registry group. The registry contains the following columns: Value, Name, and Reference. New registrations in the range 0x0B-0xFF require Standards Action [RFC8126]. Initial values: Luis Bustamante Expires 1 October 2026 [Page 68] Internet-Draft IOTMP March 2026 +=======+==============+===============+ | Value | Name | Reference | +=======+==============+===============+ | 0x00 | RESERVED | This document | +-------+--------------+---------------+ | 0x01 | OK | This document | +-------+--------------+---------------+ | 0x02 | ERROR | This document | +-------+--------------+---------------+ | 0x03 | CONNECT | This document | +-------+--------------+---------------+ | 0x04 | DISCONNECT | This document | +-------+--------------+---------------+ | 0x05 | KEEP_ALIVE | This document | +-------+--------------+---------------+ | 0x06 | RUN | This document | +-------+--------------+---------------+ | 0x07 | DESCRIBE | This document | +-------+--------------+---------------+ | 0x08 | START_STREAM | This document | +-------+--------------+---------------+ | 0x09 | STOP_STREAM | This document | +-------+--------------+---------------+ | 0x0A | STREAM_DATA | This document | +-------+--------------+---------------+ Table 35 16.4. Authentication Type Registry IANA is requested to create a new registry entitled "IOTMP Authentication Types" in the "Internet of Things Message Protocol (IOTMP)" registry group. The registry contains the following columns: Code, Name, PAYLOAD Format, and Reference. New registrations in the range 3-255 require Specification Required [RFC8126]. Initial values: Luis Bustamante Expires 1 October 2026 [Page 69] Internet-Draft IOTMP March 2026 +======+=============+==================================+===========+ | Code | Name | PAYLOAD Format | Reference | +======+=============+==================================+===========+ | 0 | Credentials | PSON array: [namespace, | This | | | | device_id, credential] | document | +------+-------------+----------------------------------+-----------+ | 1 | Token | PSON string (bearer | This | | | | token) | document | +------+-------------+----------------------------------+-----------+ | 2 | Certificate | Optional PSON array: | This | | | | [namespace, device_id] | document | | | | or absent | | +------+-------------+----------------------------------+-----------+ Table 36 16.5. Field Number Registry IANA is requested to create a new registry entitled "IOTMP Field Numbers" in the "Internet of Things Message Protocol (IOTMP)" registry group. The registry contains the following columns: Number, Name, Allowed Wire Types, and Reference. New registrations in the range 0x05-0x07 require Standards Action [RFC8126]. Initial values: +========+============+====================+===============+ | Number | Name | Allowed Wire Types | Reference | +========+============+====================+===============+ | 0x00 | RESERVED | -- | This document | +--------+------------+--------------------+---------------+ | 0x01 | STREAM_ID | varint | This document | +--------+------------+--------------------+---------------+ | 0x02 | PARAMETERS | varint, pson | This document | +--------+------------+--------------------+---------------+ | 0x03 | PAYLOAD | pson, bytes | This document | +--------+------------+--------------------+---------------+ | 0x04 | RESOURCE | varint, pson | This document | +--------+------------+--------------------+---------------+ Table 37 Luis Bustamante Expires 1 October 2026 [Page 70] Internet-Draft IOTMP March 2026 16.6. Wire Type Registry IANA is requested to create a new registry entitled "IOTMP Wire Types" in the "Internet of Things Message Protocol (IOTMP)" registry group. The registry contains the following columns: Value, Name, Description, and Reference. New registrations in the range 0x03-0x07 require Standards Action [RFC8126]. Initial values: +=======+========+==================================+===============+ | Value | Name | Description | Reference | +=======+========+==================================+===============+ | 0x00 | varint | Variable-length unsigned integer | This | | | | | document | +-------+--------+----------------------------------+---------------+ | 0x01 | bytes | Length-prefixed raw bytes | This | | | | | document | +-------+--------+----------------------------------+---------------+ | 0x02 | pson | PSON-encoded value | This | | | | | document | +-------+--------+----------------------------------+---------------+ Table 38 16.7. Media Type The application/pson media type is defined in [PSON]. 17. References 17.1. Normative References [IEEE754] IEEE, "IEEE Standard for Floating-Point Arithmetic", IEEE 754-2019, 2019. [JSON-Schema] Wright, A., Andrews, H., Hutton, B., and G. Dennis, "JSON Schema: A Media Type for Describing JSON Documents", Work in Progress, Internet-Draft, draft-bhutton-json-schema-01, June 2022, . [PSON] Bustamante, A.L., "PSON: Packed Sensor Object Notation", Work in Progress, Internet-Draft, draft-bustamante-pson- 00, March 2026, . Luis Bustamante Expires 1 October 2026 [Page 71] Internet-Draft IOTMP 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, . [RFC6455] Fette, I. and A. Melnikov, "The WebSocket Protocol", RFC 6455, DOI 10.17487/RFC6455, December 2011, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC8446] Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, . [RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, . 17.2. Informative References [LwM2M] Open Mobile Alliance, "Lightweight Machine to Machine Technical Specification", OMA TS-LightweightM2M_Core-V1_2. [MQTT] OASIS, "MQTT Version 5.0", OASIS Standard, March 2019, . [ProtocolBuffers] Google, "Protocol Buffers Encoding", . [RFC3552] Rescorla, E. and B. Korver, "Guidelines for Writing RFC Text on Security Considerations", BCP 72, RFC 3552, DOI 10.17487/RFC3552, July 2003, . [RFC7252] Shelby, Z., Hartke, K., and C. Bormann, "The Constrained Application Protocol (CoAP)", RFC 7252, DOI 10.17487/RFC7252, June 2014, . Luis Bustamante Expires 1 October 2026 [Page 72] Internet-Draft IOTMP March 2026 [RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017, . [RFC8428] Jennings, C., Shelby, Z., Arkko, J., Keranen, A., and C. Bormann, "Sensor Measurement Lists (SenML)", RFC 8428, DOI 10.17487/RFC8428, August 2018, . [RFC8610] Birkholz, H., Vigano, C., and C. Bormann, "Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures", RFC 8610, DOI 10.17487/RFC8610, June 2019, . [RFC8949] Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, December 2020, . [RFC9543] Farrel, A., Ed., Drake, J., Ed., Rokui, R., Homma, S., Makhijani, K., Contreras, L., and J. Tantsura, "A Framework for Network Slices in Networks Built from IETF Technologies", RFC 9543, DOI 10.17487/RFC9543, March 2024, . Appendix A. Wire Format Examples A.1. KEEP_ALIVE Message 05 00 | +- Body Size: 0 (varint) +---- Message Type: 0x05 = KEEP_ALIVE (varint) Total: *2 bytes*. A.2. CONNECT Message For auth type 0 credentials ["acme1", "device1", "secret123"] (namespace, device_id, credential) with Stream ID 42: Luis Bustamante Expires 1 October 2026 [Page 73] Internet-Draft IOTMP March 2026 03 # Message Type: CONNECT (varint) 1C # Body Size: 28 (varint) 08 # STREAM_ID (field=1, wire=varint) 2A # Stream ID: 42 (varint) 1A # PAYLOAD (field=3, wire=pson) E3 # PSON: array of 3 elements 85 61 63 6D 65 31 # string "acme1" (namespace, len=5) 87 64 65 76 69 63 65 31 # string "device1" (device_id, len=7) # PSON: string "secret123" 89 73 65 63 72 65 74 31 32 33 # (credential, len=9) Total: *30 bytes*. A.3. RUN Message (Resource Invocation) Peer asks for resource "temperature": 06 # Message Type: RUN (varint) 0F # Body Size: 15 (varint) 08 # STREAM_ID (field=1, wire=varint) 2A # Stream ID: 42 22 # RESOURCE (field=4, wire=pson) # PSON: string "temperature" (len=11) 8B 74 65 6D 70 65 72 61 74 75 72 65 Total: *17 bytes*. A.4. OK Response with Payload Response with {"temperature": 25.3}: 01 # Message Type: OK (varint) 15 # Body Size: 21 (varint) 08 # STREAM_ID (field=1, wire=varint) 2A # Stream ID: 42 1A # PAYLOAD (field=3, wire=pson) C1 # PSON: map with 1 entry # key: "temperature" (len=11) 8B 74 65 6D 70 65 72 61 74 75 72 65 # value: float 25.3 (IEEE 754, LE) 40 66 66 CA 41 Total: *23 bytes*. A.5. ERROR Response with Status Code Response with 404 status and error message: Luis Bustamante Expires 1 October 2026 [Page 74] Internet-Draft IOTMP March 2026 02 # Message Type: ERROR (varint) 20 # Body Size: 32 (varint) 08 # STREAM_ID (field=1, wire=varint) 2A # Stream ID: 42 10 # PARAMETERS (field=2, wire=varint) 94 03 # Status code: 404 (varint) 1A # PAYLOAD (field=3, wire=pson) C1 # PSON: map with 1 entry 85 65 72 72 6F 72 # key: "error" (len=5) # value: "Resource not found" (len=18) 92 52 65 73 6F 75 72 63 65 20 6E 6F 74 20 66 6F 75 6E 64 Total: *34 bytes*. Appendix B. Formal Message Grammar (CDDL) This appendix provides a formal definition of IOTMP messages using CDDL (Concise Data Definition Language, [RFC8610]). This grammar is normative and defines the *logical structure* of all IOTMP messages -- the required fields, their types, and their constraints. *Note on scope:* CDDL is designed for CBOR-based protocols, but is used here as a structural description language. The CDDL definitions describe the semantic structure of each message (which fields exist, their types, and cardinality), NOT the wire encoding. The actual wire format uses IOTMP's own framing Section 5, field tag encoding Section 7, and PSON data encoding Section 8, which differ from CBOR. Implementations MUST follow the wire format defined in Section 5, Section 7, and Section 8; the CDDL grammar serves as a complementary formal reference for message structure validation. B.1. Message Frame +-------------------------------------------------------------+ | IOTMP Message Frame | +--------------+--------------+--------------------------------+ | Message Type | Body Size | Body | | (varint) | (varint) | (Body Size bytes) | | 1-2 bytes | 1-4 bytes | 0-N bytes | +--------------+--------------+--------------------------------+ B.2. Field Tag Encoding Luis Bustamante Expires 1 October 2026 [Page 75] Internet-Draft IOTMP March 2026 +---------------------------------------+ | Field Tag (1 byte) | +------------------+--------------------+ | Field Number | Wire Type | | (bits 7-3) | (bits 2-0) | | 5 bits (0-31) | 3 bits (0-7) | +------------------+--------------------+ Wire Type values: 0x00 = varint (variable-length unsigned integer) 0x01 = bytes (length-prefixed raw bytes) 0x02 = pson (PSON-encoded value) Defined Field Tags: 0x08 = STREAM_ID (field 1, wire type varint) 0x10 = PARAMETERS (field 2, wire type varint) 0x12 = PARAMETERS (field 2, wire type pson) 0x1A = PAYLOAD (field 3, wire type pson) 0x19 = PAYLOAD (field 3, wire type bytes) 0x20 = RESOURCE (field 4, wire type varint -- resource hash) 0x22 = RESOURCE (field 4, wire type pson -- resource name or hash) B.3. Varint Encoding Single-byte varint (values 0-127): +---+-----------+ | 0 | value | | | (7 bits) | +---+-----------+ Multi-byte varint (values >= 128): +---+-----------+ +---+-----------+ +---+-----------+ | 1 | bits 0-6 | | 1 | bits 7-13 | ... | 0 | bits N-N+6| +---+-----------+ +---+-----------+ +---+-----------+ MSB = 1: more MSB = 1: more MSB = 0: last bytes follow bytes follow byte B.4. CDDL Definitions ; =========================================================== ; IOTMP Message Grammar -- CDDL (RFC 8610) ; =========================================================== ; --- Top-level frame --- iotmp-frame = ( message-type: message-type-id, body-size: uint, Luis Bustamante Expires 1 October 2026 [Page 76] Internet-Draft IOTMP March 2026 body: bstr .size body-size, ) message-type-id = &( RESERVED: 0x00, OK: 0x01, ERROR: 0x02, CONNECT: 0x03, DISCONNECT: 0x04, KEEP_ALIVE: 0x05, RUN: 0x06, DESCRIBE: 0x07, START_STREAM: 0x08, STOP_STREAM: 0x09, STREAM_DATA: 0x0A, ) ; --- Field definitions --- stream-id = uint .size 2 ; 0-65535 (even=client, odd=server) status-code = uint ; HTTP status code (RFC 9110) resource-id = tstr / uint .size 2 ; string name or 16-bit FNV-1a hash ; --- Per-message field requirements --- ; CONNECT (0x03): Client -> Server connect-body = { stream_id: stream-id, ? payload: connect-payload, ? parameters: { ? "v": uint .default 1, ; protocol version ? "ka": uint .le 1800 .default 60, ; keepalive (seconds) ? "at": auth-type .default 0, ; authentication type ? "ms": uint .ge 1024 .default 32768, ; max message size (bytes) }, } auth-type = &( credentials: 0, ; PAYLOAD = [ns, device_id, cred] token: 1, ; PAYLOAD = bearer token string certificate: 2, ; PAYLOAD optional: [ns, device_id] ; or absent ) connect-payload = credentials-payload / token-payload / certificate-payload Luis Bustamante Expires 1 October 2026 [Page 77] Internet-Draft IOTMP March 2026 credentials-payload = [ namespace: tstr, device_id: tstr, credential: tstr ] token-payload = tstr ; bearer token (JWT, API key, etc.) certificate-payload = [namespace: tstr, device_id: tstr] / nil / empty ; OK (0x01): Response to any request ok-body = { stream_id: stream-id, ? parameters: status-code / pson-value, ; status code or structured data ? payload: pson-value, } ; ERROR (0x02): Error response to any request error-body = { stream_id: stream-id, ? parameters: status-code, ? payload: { "error": tstr, * tstr => any }, ; error message + optional diagnostics } ; DISCONNECT (0x04) disconnect-body = { ? parameters: status-code, ; redirect code (301, 307) ? payload: pson-value, ; redirect target or reason } ; KEEP_ALIVE (0x05) keep-alive-body = empty ; body MUST be empty (size = 0) ; RUN (0x06): Execute a resource run-body = { stream_id: stream-id, resource: resource-id, ? payload: pson-value, ; input data for the resource } ; DESCRIBE (0x07): Request resource metadata describe-body = { stream_id: stream-id, ? resource: resource-id, ; absent = full API, present = single resource } ; START_STREAM (0x08): Begin streaming start-stream-body = { Luis Bustamante Expires 1 October 2026 [Page 78] Internet-Draft IOTMP March 2026 stream_id: stream-id, resource: resource-id, ? parameters: uint / { ; interval or structured params ? "i": uint, ; interval in ms (0 = event-driven) ? "cm": bool .default false, ; compact mode request }, } ; STOP_STREAM (0x09): End streaming stop-stream-body = { stream_id: stream-id, } ; STREAM_DATA (0x0A): Stream payload stream-data-body = { stream_id: stream-id, payload: pson-value, ; resource data (map or compact arr) } ; --- DESCRIBE response structures --- ; Full API DESCRIBE response (no RESOURCE in request) full-api-describe-response = { "v": uint, ; description format version "res": { ; resource map (not protocol fields) * resource-name => { "fn": io-type-code, ; I/O type code ? "description": tstr, ; human-readable description }, }, } resource-name = tstr io-type-code = &( none: 0, run: 1, input: 2, output: 3, input_output: 4, ) ; Single Resource DESCRIBE response (RESOURCE present in request) single-resource-describe-response = { "v": uint, ? "in": io-descriptor, ? "out": io-descriptor, } Luis Bustamante Expires 1 October 2026 [Page 79] Internet-Draft IOTMP March 2026 io-descriptor = { "value": pson-value, ; current/sample data ? "schema": json-schema, ; JSON Schema definition (optional) } json-schema = { ; subset of JSON Schema keywords ? "type": schema-type, ? "properties": { * tstr => json-schema }, ? "items": json-schema, ? "description": tstr, ? "minimum": number, ? "maximum": number, ? "enum": [+ any], ? "readOnly": bool, ? "writeOnly": bool, ? "default": any, ? "required": [+ tstr], } schema-type = "boolean" / "integer" / "number" / "string" / "object" / "array" ; --- Generic types --- pson-value = any ; any PSON-encoded value (see PSON) empty = nil ; zero-length body B.5. Field Requirements per Message Type The following table summarizes which fields are required (R), optional (O), conditional (C), or not used (--) for each message type: Luis Bustamante Expires 1 October 2026 [Page 80] Internet-Draft IOTMP March 2026 +==============+===========+============+=========+==========+ | Message Type | STREAM_ID | PARAMETERS | PAYLOAD | RESOURCE | +==============+===========+============+=========+==========+ | OK | R | O | O | -- | +--------------+-----------+------------+---------+----------+ | ERROR | R | O | O | -- | +--------------+-----------+------------+---------+----------+ | CONNECT | R | O | C | -- | +--------------+-----------+------------+---------+----------+ | DISCONNECT | -- | O | O | -- | +--------------+-----------+------------+---------+----------+ | KEEP_ALIVE | -- | -- | -- | -- | +--------------+-----------+------------+---------+----------+ | RUN | R | -- | O | R | +--------------+-----------+------------+---------+----------+ | DESCRIBE | R | -- | -- | O | +--------------+-----------+------------+---------+----------+ | START_STREAM | R | O | -- | R | +--------------+-----------+------------+---------+----------+ | STOP_STREAM | R | -- | -- | -- | +--------------+-----------+------------+---------+----------+ | STREAM_DATA | R | -- | R | -- | +--------------+-----------+------------+---------+----------+ Table 39 C = Conditional: CONNECT PAYLOAD is required for authentication types 0 (Credentials) and 1 (Token). For type 2 (Certificate), PAYLOAD is optional: required when the certificate identifies only the namespace, and may be absent when the certificate identifies both namespace and device. Appendix C. PSON vs JSON Size Comparison For a detailed size comparison between PSON and JSON for typical IoT payloads, see Section 10 of [PSON]. In summary, PSON achieves 40-75% size reduction for typical IoT data patterns. Appendix D. Protocol Comparisons Detailed protocol comparisons between IOTMP and other IoT protocols [MQTT], [RFC7252], [LwM2M] are available as separate companion documents. Luis Bustamante Expires 1 October 2026 [Page 81] Internet-Draft IOTMP March 2026 Appendix E. Revision History +=========+============+=======================+ | Version | Date | Changes | +=========+============+=======================+ | 0.1 | 2026-03-30 | Initial public draft. | +---------+------------+-----------------------+ Table 40 Author's Address Alvaro Luis Bustamante Internet of Thinger SL Spain Email: alvaro@thinger.io Luis Bustamante Expires 1 October 2026 [Page 82]