Internet-Draft FULMEN 1.0 August 2026
Leopizzi, et al. Expires 6 February 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-leopizzi-fulmen-00
Published:
Intended Status:
Experimental
Expires:
Authors:
G. Leopizzi
Riga Technical University
J. Grabis
Riga Technical University
K. Ābelnīca
Riga Technical University
S. Martorelli
IBM

FULMEN 1.0

Abstract

This document specifies FULMEN version 1.0, an event-based, bi-directional client-server communication protocol with a binary wire format. Within a FULMEN connection, both the client and the server can send events. An event is a message frame identified by a sequential identifier and addressed by a UTF-8 path used for routing and dispatching; its sender can request an acknowledgment, a response correlated to the event that carries a status code describing the outcome of its processing. Events and acknowledgments can carry a binary payload, either inline within the frame or delivered incrementally in chunks through a stream. The protocol version in use is negotiated during the connection handshake. FULMEN also defines an extension mechanism, based on typed data units attached to protocol messages, through which additional functionality can be introduced without changes to the wire format.

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 6 February 2027.

Table of Contents

1. Introduction

FULMEN is a client-server protocol. Similarly to HTTP, which is based on the concept of request-response, FULMEN is designed around the concept of event acknowledgment. In HTTP, the client strictly sends a request, and the server is responsible for the responses. In FULMEN, both the client and server can send events and acknowledgments. Before proceeding, it is necessary to provide the reader with definitions of such terms in the context of this protocol:

Both event and acknowledgment MAY include a payload. The protocol supports the payload as included within the frame, sent later, or in chunks with separate future frames. In the latter case, a "stream" is used, and the payload body delivery can be deferred to a future point.

The protocol supports extensions to guarantee extensibility. An extension is a modular, typed data unit that can be added to some frames to add or adapt core protocol functionalities without altering the protocol wire format or breaking compatibility. Extensions are serialized using a TLV format attached to the payload of some protocol messages.

2. Conventions and Notation

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 [BCP14] (RFC2119) (RFC8174) when, and only when, they appear in all capitals, as shown here.

The protocol exclusively uses big-endian byte order.

This document uses a pseudo-C struct in listings to clarify how fields are serialized and deserialized from the wire format. uint8, uint16, uint32, uint64 represent an unsigned integer serialized respectively using exactly 1, 2, 4, and 8 bytes. string is used to represent an encoded UTF-8 string. The string is serialized as a uint16 representing the length of the string in bytes, followed by the bytes representing the string. For example, the string "IETF" would be serialized as:

[0x00, 0x04, 0x49, 0x45, 0x54, 0x46]

data represents an encoded buffer of binary bytes. As per the string, it is serialized as a uint16 representing the length of the buffer in bytes, followed by the buffer itself. For example, the buffer [0x01, 0x02, 0x03] would be serialized as:

[0x00, 0x03, 0x01, 0x02, 0x03]

opaque name[n] represents exactly n bytes, serialized as-is without any length prefix. More generally, the array notation type name[count] represents count elements of the indicated type, serialized back-to-back in order and without any length prefix; when count is the name of another field of the structure, the number of elements is given by the value of that field.

When declaring a pseudo-C struct, a field (or multiple fields) might be wrapped within an "if" block. If so, the field (or the fields) are only present whenever the bit flag is set in the if condition. The name of the bit flag might be preceded by a "!" indicating the logical NOT operation. For example, to represent that a field is there only if the bit flag named "hello" is not set, the struct would contain the following:

if (!hello) {
    string example;
}

A C-like bit-field notation is used in the listings to represent bit flags. Despite its appearance, it is not an actual C bit-field: it is only a notation to describe the meaning of the individual bits of a single byte, and no C compiler layout, padding, or memory alignment rule applies. The bits are listed from the most significant to the least significant: the first element of the bit-field is the most significant bit of the byte. For example, in a structure declared as:

struct {
    bool a : 1;
    void reserved : 7;
} FlagsExample

the flag a is the most significant bit of the byte (0x80), and the remaining seven bits are reserved. The notation uses the void type to reserve space for future flags. Reserved bits MUST be set to zero when a frame is sent and MUST be ignored upon receipt.

Unless otherwise specified, enumerated types (enum) and bit-flag structures are serialized as a single byte (uint8).

3. Transport

FULMEN is an application-layer protocol and does not define its own mechanisms for reliability, ordering, or congestion control. It therefore requires an underlying transport that provides such guarantees.

This document defines the use of FULMEN over two transports, TCP (Section 3.1) and WebSocket (Section 3.2). The lifetime of a FULMEN connection coincides with the lifetime of the underlying connection.

3.1. TCP

When FULMEN is carried over TCP [RFC9293], the frames (Section 5) are sent back-to-back over the TCP connection, with no additional framing. A frame can be split across several TCP segments, and several frames can be carried by a single TCP segment; a receiver MUST NOT rely on any relationship between frame boundaries and transport-level boundaries.

Implementations SHOULD disable the Nagle algorithm by setting the TCP_NODELAY socket option.

When the connection is established over TLS, the ALPN extension [RFC7301] SHOULD be used with the following identifier:

fulmen/1

3.2. WebSocket

When FULMEN is carried over WebSocket [RFC6455], the client SHOULD include the subprotocol name fulmen/1 in the Sec-WebSocket-Protocol header field of its opening handshake, and a server that accepts the connection echoes it as prescribed by [RFC6455].

FULMEN is carried exclusively by WebSocket messages of type binary: a party that receives a text message MUST terminate the connection with an Alert message (Section 6.10) with code MALFORMED_FRAME.

The FULMEN byte stream is the concatenation, in the order received, of the payloads of the binary messages. A receiver MUST NOT assume that a message boundary coincides with a frame boundary. However, sending each frame as a single binary message is RECOMMENDED.

4. Connection Establishment and Major Version Negotiation

A FULMEN connection, regardless of the transport, begins with a handshake. The handshake starts with a fixed sequence of 8 bytes sent by each party, and proceeds with the Hello and Welcome messages (Section 6), in which the minor version is negotiated. The 8 handshake bytes are:

This 8-byte sequence is invariant: its format MUST remain identical across all FULMEN major versions.

The client MUST send the 8 handshake bytes as the first bytes of the connection, carrying the single major version it intends to use within the connection. The client is free to decide whether to send the Hello message (Section 6.1) immediately after its handshake bytes, without waiting for the server: doing so is RECOMMENDED.

The server MUST respond with its own 8 handshake bytes. If the server supports the major version indicated by the client, it MUST echo the same major version; it is RECOMMENDED for the server to send the Welcome message (Section 6.2) immediately after its handshake bytes. If the server does not support the major version indicated by the client, it MUST send the handshake bytes carrying the major version 0 and terminate the connection; in this case, the server MUST NOT attempt to interpret any byte received after the client's handshake bytes.

A party that receives handshake bytes that do not begin with the six octets "FULMEN" MUST terminate the connection.

Likewise, a client that receives handshake bytes carrying a major version that is neither the one it sent nor 0 MUST terminate the connection.

Figure 1 shows the handshake bytes exchanged in a FULMEN 1.0 connection.

Client -> Server:
    46 55 4C 4D 45 4E 01 00      ("FULMEN", major version 1)

Server -> Client:
    46 55 4C 4D 45 4E 01 00      ("FULMEN", major version 1)
Figure 1: FULMEN 1.0 handshake bytes

5. Frame

Figure 2 represents the generic FULMEN frame structure. The frame is the unit sent over the wire. Thus, all the bytes received from the transport (Section 3) after the handshake bytes MUST be decoded as frames and later interpreted as described based on the type field.

All the fields of a message are serialized within the frame's payload field. The sizes of the variable-length fields within a message are constrained by the enclosing frame.

A party that decodes a frame whose payload does not conform to the structure of the message indicated by the type field MUST terminate the connection with an Alert message (Section 6.10) with code MALFORMED_FRAME.

struct {
    uint8 type;
    data  payload;
} FulmenFrame
Figure 2: FULMEN frame structure

6. Messages

A message is a unit sent using a frame. Each message is represented by a unique type identifier (8-bit unsigned integer). The type identifier 0x00 is reserved and MUST NOT be used. A party that receives a frame whose type is 0x00 or is not supported MUST terminate the connection with an Alert message (Section 6.10) with code UNSUPPORTED_FRAME_TYPE.

The connection follows a strict lifecycle. The first message sent by the client MUST be a Hello message (Section 6.1), and the first message sent by the server MUST be a Welcome message (Section 6.2). Once the Welcome message has been sent, the handshake is complete, and Hello and Welcome messages MUST NOT be sent again within the connection lifetime. With the exception of the Alert message (Section 6.10), which can be sent by either party at any point of the connection, no other message can be sent before the Welcome message has been received. A party that receives a message violating this lifecycle MUST terminate the connection with an Alert message with code UNEXPECTED_MESSAGE.

6.1. Hello

Type:

0x01

Sender:

client

Extensions:

supported

It is the first message that MUST be sent over the open connection, after the handshake bytes (Section 4). In this message, the client lists all the supported minor versions of the protocol.

It contains the following fields:

  • flags, currently the only flag supported is has_extensions, indicating if the message has some extensions included.

  • ver_length, an 8-bit unsigned integer representing the length in bytes of the supported_minor_versions array.

  • supported_minor_versions, an array of 8-bit unsigned integers, each one representing a minor version supported by the client. The array MUST NOT be empty: a receiver of a Hello message with a ver_length of 0 MUST terminate the connection with an Alert message (Section 6.10) with code MALFORMED_FRAME.

  • extensions, a data array containing the Data Transfer Objects (DTOs) of the extensions carried by this message. Each DTO is encoded as later described in this document.

Figure 3 shows the message's payload.

struct {
    bool has_extensions : 1;
    void reserved       : 7;
} HelloFlags

struct {
    HelloFlags flags;
    uint8      ver_length;
    opaque     supported_minor_versions[ver_length];
    if (has_extensions) {
    data extensions;
    }
} HelloPayload
Figure 3: Hello message payload

6.2. Welcome

Type:

0x02

Sender:

server

Extensions:

supported

It is the message sent by the server in response to the Hello message. As shown by Figure 4, besides the extensions, the only field is the minor_version representing the minor version, among the ones supported by the client, that the server has picked to be used within this connection. The connection is opened after receiving a Welcome message, and the parties can begin sending other appropriate messages. If the server supports none of the minor versions listed in the Hello message, it MUST send an Alert message (Section 6.10) with code UNSUPPORTED_MINOR_VERSIONS and close the connection. Likewise, a client that receives a Welcome message whose minor_version was not listed in its Hello message MUST terminate the connection with an Alert message (Section 6.10) with code UNSUPPORTED_MINOR_VERSIONS.

struct {
    bool has_extensions : 1;
    void reserved       : 7;
} WelcomeFlags

struct {
    WelcomeFlags flags;
    uint8        minor_version;
    if (has_extensions) {
    data extensions;
    }
} WelcomePayload
Figure 4: Welcome message payload

6.3. Event

Type:

0x03

Sender:

client, server

Extensions:

supported

This message delivers an event (Figure 5).

struct {
    bool has_extensions     : 1;
    bool has_inline_payload : 1;
    bool requires_ack       : 1;
    void reserved           : 5;
} EventFlags

struct {
    uint64     event_id;
    EventFlags flags;
    string     path;
    if (!has_inline_payload) {
    uint32 stream_id;
    }
    if (has_inline_payload) {
    data payload;
    }
    if (has_extensions) {
    data extensions;
    }
} EventPayload
Figure 5: Event message payload

The event payload supports the following fields:

  • event_id, a 64-bit unsigned integer representing the progressive identifier of the current event. Each client and server has its own sequence that MUST be unique to the FULMEN connection. It starts from zero, and it MUST be incremented by one for each Event message sent. A party that receives an Event message whose event_id is not the expected next value of the sender's sequence MUST terminate the connection with an Alert message (Section 6.10) with code INVALID_EVENT_ID.

  • flags, the following flags are supported:

    • has_extensions, as previously described.

    • has_inline_payload, if set, the payload of the event is written inline. If not set, a stream identifier will be provided to retrieve the payload later.

    • requires_ack, if set the sender of this event is expecting an Acknowledgment message in response to the event.

  • path, the path of the event. It MUST be a UTF-8 encoded string and MUST NOT be empty. A path, to be valid, MUST contain exclusively the following characters:

    A-Z a-z 0-9 - _ . ~ /
    

    A path MUST NOT begin or end with the "/" character and MUST NOT contain two consecutive "/" characters. A party that receives an Event message with an invalid path MUST terminate the connection with an Alert message (Section 6.10) with code INVALID_EVENT_PATH.

  • stream_id, only present if the flag has_inline_payload is not set. If so, the 32-bit unsigned integer identifies the stream that will later carry this event's payload.

  • payload, only present if the flag has_inline_payload is set. Represents the binary payload of the event. A payload of length 0 is allowed: an event that carries no payload is sent with the has_inline_payload flag set and a zero-length payload.

  • extensions, same as the Hello message.

6.4. Acknowledgment

Type:

0x04

Sender:

client, server

Extensions:

supported

As shown in Figure 6, the message delivers an event acknowledgment to the sender party. It contains the following fields:

  • event_id, the 64-bit unsigned integer representing the identifier of the event this message is acknowledging. It correlates an event with its Acknowledgment message.

  • flags, the following are currently supported:

    • has_extensions, same as the Hello message.

    • has_inline_payload, same as the Event message.

    • has_status_description, if set, the payload includes a string to clarify the meaning of the status further.

  • status, an 8-bit unsigned integer representing the status of the message. It works similarly to the HTTP Response Status Code. Its purpose is to provide the sender with information about how the processing of the Event is completed. The status code 0 is reserved and MUST NOT be used.

  • status_description, a string present if the flag has_status_description is set. It represents an optional message that can be sent along the status to clarify it further (for example, by providing an error description).

  • stream_id, same as for the Event message.

  • payload, same as for the Event message.

  • extensions, same as for the Hello message.

A party that receives an Acknowledgment message whose event_id does not correspond to an event it sent with the requires_ack flag set, or that has already been acknowledged, MUST terminate the connection with an Alert message (Section 6.10) with code UNEXPECTED_EVENT_ACK.

enum Status {
    OK                  =  1,
    INVALID_ARGUMENT    =  2,
    UNAUTHENTICATED     =  3,
    PERMISSION_DENIED   =  4,
    NOT_FOUND           =  5,
    ALREADY_EXISTS      =  6,
    FAILED_PRECONDITION =  7,
    OUT_OF_RANGE        =  8,
    CONFLICT            =  9,
    RESOURCE_EXHAUSTED  = 10,
    CANCELLED           = 11,
    DEADLINE_EXCEEDED   = 12,
    RATE_LIMITED        = 13,
    INTERNAL_ERROR      = 14,
    UNAVAILABLE         = 15,
    PROTOCOL_VIOLATION  = 16
}

struct {
    bool has_extensions         : 1;
    bool has_inline_payload     : 1;
    bool has_status_description : 1;
    void reserved               : 5;
} AckFlags

struct {
    uint64   event_id;
    AckFlags flags;
    Status   status;
    if (has_status_description) {
    string status_description;
    }
    if (!has_inline_payload) {
    uint32 stream_id;
    }
    if (has_inline_payload) {
    data payload;
    }
    if (has_extensions) {
    data extensions;
    }
} AcknowledgmentPayload
Figure 6: Acknowledgment message payload

6.5. Stream

Type:

0x05

Sender:

client, server

Extensions:

supported

A message that delivers a chunk of the payload whenever the event or the acknowledgment message declares its intention to use it for payload streaming functionality. Figure 7 shows the message's payload.

The stream status 0 is reserved and MUST NOT be used.

In a Stream message, the stream_id always refers to a stream initiated by the message's sender. A stream consists of zero or more chunks with status HAS_MORE followed by a final chunk with status ENDED; the complete payload of the stream is the concatenation, in the order received, of the payload fields of all its chunks. The payload of an ENDED chunk can be empty.

The initiator can terminate a stream abnormally at any moment by sending a chunk with status ABORTED, whose payload MUST be empty; upon receiving it, the receiver MUST discard all the previously received chunks of the stream. A stream ends when a chunk with status ENDED or ABORTED is received.

Each party allocates stream identifiers from its own sequence, which SHOULD start from zero and SHOULD be incremented by one for each new stream the party initiates (by declaring it in an Event or Acknowledgment message). A stream identifier can be reused upon reception of a Stream Ack message with status RECEIVED from the counterpart (Section 6.6). A party that receives an Event, Acknowledgment, or Stream message violating these rules MUST terminate the connection with an Alert message (Section 6.10) with code INVALID_STREAM_ID.

FULMEN version 1.0 does not provide per-stream flow control. A receiver that cannot keep up with an incoming stream can cancel it using the Stream Ack message (Section 6.6).

Stream messages of different streams, as well as Stream messages and other messages, MAY be freely interleaved; the chunks of a single stream MUST be sent in order. Implementations MAY adopt a prioritization system to decide the order in which frames are sent.

enum StreamStatus {
    HAS_MORE = 1,
    ENDED    = 2,
    ABORTED  = 3
}

struct {
    bool         has_extensions : 1;
    void         reserved       : 7;
} StreamFlags

struct {
    uint32       stream_id;
    StreamFlags  flags;
    StreamStatus status;
    data payload;
    if (has_extensions) {
    data extensions;
    }
} StreamPayload
Figure 7: Stream message payload

6.6. Stream Ack

Type:

0x06

Sender:

client, server

Extensions:

supported

A message sent by the receiver of a stream to its initiator (Figure 8). Similarly to the Acknowledgment message, the stream_id of a Stream Ack message MUST always be the same as that of the received Stream message.

FULMEN version 1.0 supports two status values:

  • ABORT, used to request the cancellation of an incoming stream. Upon receiving it, the initiator of the stream MUST NOT send further HAS_MORE chunks and MUST confirm the cancellation by sending a final Stream message with status ABORTED for the stream. The party that requested the cancellation MUST ignore any HAS_MORE chunk of the stream received while waiting; the stream ends when a final chunk is received.

  • RECEIVED, used to acknowledge the termination of a stream. A party that receives a final Stream message (status ENDED or ABORTED) MUST respond with a Stream Ack message with status RECEIVED.

After receiving a Stream Ack message with status RECEIVED, the initiator can consider that stream identifier as valid for reuse.

Once a party considers a stream terminated, it MUST ignore every message referring to that stream identifier, with the exception of the reception of a Stream Ack message with status RECEIVED. Only upon the re-declaration of the stream identifier by a new Event or Acknowledgment message do messages referring to that identifier become valid again, as they refer to the new stream.

A Stream Ack message that refers to a stream identifier that its receiver has never allocated MUST be treated as a violation, and the receiving party MUST terminate the connection with an Alert message (Section 6.10) with code INVALID_STREAM_ID.

enum StreamAckStatus {
    ABORT = 1,
    RECEIVED = 2
}

struct {
    bool            has_extensions : 1;
    void            reserved       : 7;
} StreamAckFlags

struct {
    uint32          stream_id;
    StreamAckFlags  flags;
    StreamAckStatus status;
    if (has_extensions) {
    data extensions;
    }
} StreamAckPayload
Figure 8: Stream Ack message payload

6.7. Heartbeat

Type:

0x07

Sender:

client, server

Extensions:

not supported

Either party sends it as a "keep-alive" mechanism. As shown in Figure 9, it includes a token; the receiving party MUST respond with a Heartbeat Ack message (Section 6.8) carrying the same token, allowing the sender to correlate the two messages. There is no restriction on token reuse: the token generation strategy is left to the sender. The message does not support extensions.

struct {
    void reserved : 8;
} HeartbeatFlags

struct {
    HeartbeatFlags flags;
    uint8          token;
} HeartbeatPayload
Figure 9: Heartbeat message payload

6.8. Heartbeat Ack

Type:

0x08

Sender:

client, server

Extensions:

not supported

Sent upon reception of a Heartbeat message. As shown in Figure 10, it MUST include the same token received in the Heartbeat message. The message does not support extensions.

struct {
    void reserved : 8;
} HeartbeatAckFlags

struct {
    HeartbeatAckFlags flags;
    uint8             token;
} HeartbeatAckPayload
Figure 10: Heartbeat Ack message payload

6.9. Extensions

Type:

0x09

Sender:

client, server

Extensions:

supported

A message that only consists of extensions. Its purpose is to deliver the information needed to allow an additional mechanism to work correctly. As shown by Figure 11, it works as the Hello message, but it does not include a flag since the data are always present due to the nature of the message itself. The extensions field MUST contain at least one DTO (Section 7.1): a receiver of an Extensions message carrying no extensions MUST terminate the connection with an Alert message (Section 6.10) with code MALFORMED_FRAME.

struct {
    data extensions;
} ExtensionPayload
Figure 11: Extensions message payload

6.10. Alert

Type:

0x0B

Sender:

client, server

Extensions:

not supported

Shown in Figure 12, a message used to deliver an alert code to the other party. The Alert message always terminates the connection; after sending or receiving an Alert message, a party MUST consider the connection closed, and no further message can be sent. Consequently, every orderly connection termination ends with an Alert message; a graceful termination is signaled with the code CONNECTION_CLOSE. The alert code 0 is reserved and MUST NOT be used.

enum AlertCode {
    CONNECTION_CLOSE           =   1,
    UNSUPPORTED_MINOR_VERSIONS =   2,
    UNKNOWN_EXTENSION          =   3,
    UNSUPPORTED_FRAME_TYPE     =   4,
    MALFORMED_FRAME            =   5,
    INVALID_EVENT_ID           =   6,
    INVALID_STREAM_ID          =   7,
    INVALID_EVENT_PATH         =   8,
    HEARTBEAT_FAILURE          =   9,
    UNEXPECTED_EVENT_ACK       =  10,
    MISSING_EVENT_ACK          =  11,
    LIMITS_EXCEEDED            =  12,
    UNEXPECTED_MESSAGE         =  13,
    EXTENSION_FAILURE          =  14,

    UNKNOWN_ERROR              = 255
}

struct {
    bool has_description : 1;
    void reserved        : 7;
} AlertFlags

struct {
    AlertFlags flags;
    AlertCode  code;
    if (has_description) {
    string     description;
    }
} AlertPayload
Figure 12: Alert message payload

7. Extensions

The protocol supports extensions to transport additional typed payload. Extensions can be of two categories:

A 16-bit unsigned integer identifies each extension type. The most significant bit of the extension type determines its category: when it is not set (types 0x0001 to 0x7FFF), the extension is a PE; when it is set (types 0x8000 to 0xFFFF), the extension is an AE. The extension type 0x0000 is reserved and MUST NOT be used.

The use of Application Extensions MUST be declared in advance. A connection in which AEs are to be used MUST include the Application Extensions List extension (Section 7.5) in its Hello message. If the Hello message does not carry the Application Extensions List extension, no AE can be used within the connection. A party that receives an AE type that was not declared MUST terminate the connection with an Alert message (Section 6.10) with code UNKNOWN_EXTENSION.

A party that receives an extension type (either PE or AE) that it does not support MUST ignore it.

When the processing of a supported extension fails in a way that prevents the connection from continuing, the party MAY terminate the connection with an Alert message (Section 6.10) with code EXTENSION_FAILURE and SHOULD include an appropriate description.

7.1. Data Transfer Object

Each extension is serialized using the structure shown in Figure 13. When a message carries more than one extension, the DTOs are encoded back-to-back within the extensions field; a receiver MUST parse ExtensionFrame structures until the length of the enclosing data field is exhausted.

A message MUST NOT carry more than one extension of the same type; a receiver of a message violating this rule MAY terminate the connection with an Alert message (Section 6.10) with code EXTENSION_FAILURE.

When the has_extensions flag of a message supporting it is set, the extensions field MUST contain at least one DTO: a receiver of a message whose has_extensions flag is set and whose extensions field contains no DTOs MUST terminate the connection with an Alert message (Section 6.10) with code MALFORMED_FRAME.

The definition of each extension lists the messages that can carry it. An extension MUST NOT be attached to a message that its definition does not list; a party that receives an extension carried by a message that is not allowed to carry it MUST terminate the connection with an Alert message (Section 6.10) with code EXTENSION_FAILURE.

struct {
    uint16 type;
    data   payload;
} ExtensionFrame
Figure 13: Extension Frame

7.2. Authentication

Extension type:

0x01

Messages:

Hello, Event

The extension payload begins with a one-byte (unsigned 8-bit integer) value indicating the type of authentication method used. The interpretation of the subsequent payload depends on this value. Version 1.0 defines the following types:

  • Type 0, basic authentication. The payload consists of two back-to-back encoded strings. The first represents a username and the second its password.

  • Type 1, token-based authentication. The payload consists of a single string representing the authentication token.

When the extension is carried by a Hello message and the authentication fails, the server MUST terminate the connection with an Alert message (Section 6.10) with code EXTENSION_FAILURE. When the extension is carried by an Event message and the authentication fails, the receiver SHOULD reject the event with an Acknowledgment message with status UNAUTHENTICATED (when an acknowledgment is expected) and MAY terminate the connection with an Alert message with code EXTENSION_FAILURE.

7.3. ContentType

Extension type:

0x02

Messages:

Event, Acknowledgment

The payload of the extension consists of bytes representing the UTF-8 encoded IANA Media Type [RFC6838] of the payload.

7.4. Metadata

Extension type:

0x03

Messages:

Event, Acknowledgment

The extension allows the sender to attach application metadata to an Event or Acknowledgment message, conceptually similar to HTTP header fields, while leveraging the binary wire format of FULMEN. The extension payload consists of a single CBOR [RFC8949] map. Each key of the map MUST be either a CBOR text string or a CBOR unsigned integer, and MUST be unique within the map; the value associated with a key can be of any CBOR type.

In FULMEN version 1.0, unsigned integer keys carry no meaning; a receiver MUST ignore every entry whose key is an unsigned integer, and a sender MUST NOT include such entries.

7.5. Application Extensions List

Extension type:

0x04

Messages:

Hello

The extension allows the client to declare, during the handshake, the Application Extensions available to the application (Figure 14). The declared AE types are the only Application Extensions that can be used within the connection, by either party, as described in Section 7. It contains the following fields:

  • application_uuid, a 16-byte UUID [RFC9562] identifying the application. Since AE types are for private use, the UUID qualifies the namespace in which the listed Application Extensions are defined.

  • application_extensions, the list of AE types available to the client, preceded by ae_count, the number of its elements.

If the server does not recognize the application_uuid, it MUST send an Alert message (Section 6.10) with code UNKNOWN_EXTENSION and close the connection.

struct {
    opaque application_uuid[16];
    uint8  ae_count;
    uint16 application_extensions[ae_count];
} ApplicationExtensionsListPayload
Figure 14: Application Extensions List extension payload

7.6. Allowed Event Paths

Extension type:

0x05

Messages:

Hello, Welcome, Extensions

The extension allows each party to declare the event paths it is willing to receive. As shown in Figure 15, the payload consists of a list of strings, each one representing a path pattern to be matched against event paths.

A path pattern is a sequence of segments separated by the "/" character, like the paths it is matched against. Each segment of a pattern is one of the following:

  • a literal segment, composed of the path characters listed in Section 6.3: it matches only an identical path segment;

  • the segment "*": it matches exactly one path segment, whatever its content;

  • the segment "**", which can only appear as the last segment of a pattern: it matches any number of path segments, including zero.

A path matches a pattern when its segments can be aligned with the pattern's segments according to the rules above; the alignment MUST always cover the entire path, never only a substring. For example, the pattern "sensors/*/temperature" matches the path "sensors/kitchen/temperature" but neither "sensors/temperature" nor "sensors/kitchen/hvac/temperature"; the pattern "logs/**" matches "logs", "logs/app", and "logs/app/error"; the pattern "config/reload" matches only the path "config/reload".

A pattern is invalid if it is empty, if any of its segments is empty (like a path, a pattern cannot begin or end with "/" or contain two consecutive "/" characters), if any of its segments contains the "*" character without being exactly "*" or "**" (for example, "foo*" and "***" are invalid segments), if a "**" segment appears in a position other than the last, or if it contains any character that is neither a valid path character nor "*". A party that receives a list containing an invalid pattern MUST terminate the connection with an Alert message (Section 6.10) with code EXTENSION_FAILURE.

A party MUST NOT send an Event message whose path does not match at least one of the patterns most recently declared by the other party. A party that receives such an Event MAY terminate the connection with an Alert message (Section 6.10) with code INVALID_EVENT_PATH.

A newly received list completely replaces the previously received one (for example, a list received within an Extensions message replaces the list received during the handshake). An empty list (path_count equal to 0) means that the other party cannot send any Event message at all. If a party has never sent this extension, no path restriction applies to the events it receives.

struct {
    uint8  path_count;
    string allowed_paths[path_count];
} AllowedEventPathsPayload
Figure 15: Allowed Event Paths extension payload

7.7. Configuration

Extension type:

0x06

Messages:

Welcome, Extensions

The extension allows the server to communicate its connection policy (Figure 16). It contains the following fields:

  • stale_timeout, the number of seconds after which the server considers the connection stale: if the server does not receive any frame for stale_timeout seconds, it MAY close the connection. The value 0 means that the server does not apply any stale timeout.

  • disable_heartbeats, controls whether the client can send Heartbeat messages. The value 0 indicates that the client can send them; any other value indicates that the client MUST NOT send them. If the client sends a Heartbeat message while not allowed to, the server MAY send an Alert message (Section 6.10) with code HEARTBEAT_FAILURE and close the connection.

  • ack_timeout, the maximum number of seconds a party SHOULD wait for the Acknowledgment of an Event message sent with the requires_ack flag set. Once the timeout has elapsed, the waiting party MAY terminate the connection with an Alert message (Section 6.10) with code MISSING_EVENT_ACK. The value 0 means that no deadline applies.

  • heartbeat_timeout, the maximum number of seconds a party SHOULD wait for the Heartbeat Ack message after sending a Heartbeat message. Once the timeout has elapsed, the waiting party MAY terminate the connection with an Alert message (Section 6.10) with code HEARTBEAT_FAILURE. The value 0 means that no deadline applies.

  • max_payload_size, the maximum size in bytes of the payload field of Event, Acknowledgment, and Stream messages sent by the client. The value 0 means that the server does not apply that limit.

  • max_streams, the maximum number of streams the client can keep simultaneously open. The value 0 means that the server does not apply that limit.

If the client exceeds a declared limit, the server MAY send an Alert message (Section 6.10) with code LIMITS_EXCEEDED and close the connection.

The extension can also be carried by an Extensions message to update the policy previously communicated. If the server has never sent such an extension, no limits or timeouts apply.

struct {
    uint16 stale_timeout;
    uint8  disable_heartbeats;
    uint16 ack_timeout;
    uint16 heartbeat_timeout;
    uint16 max_payload_size;
    uint16 max_streams;
} ConfigurationPayload
Figure 16: Configuration extension payload

8. Security Considerations

FULMEN does not mandate the use of a specific security protocol. However, a FULMEN connection SHOULD be established over a protocol that ensures the confidentiality and integrity of the communication. For the transports defined in this document, this means running the TCP transport (Section 3.1) over TLS [RFC8446] and using the "wss" URI scheme for the WebSocket transport (Section 3.2).

9. IANA Considerations

9.1. ALPN Protocol Identifier

This document requests that IANA register the following entry in the "TLS Application-Layer Protocol Negotiation (ALPN) Protocol IDs" registry created by [RFC7301]:

Protocol:

FULMEN/1

Identification Sequence:

0x66 0x75 0x6c 0x6d 0x65 0x6e 0x2f 0x31 ("fulmen/1")

Reference:

This document

9.2. WebSocket Subprotocol Name

This document requests that IANA register the following entry in the "WebSocket Subprotocol Name Registry" created by [RFC6455]:

Subprotocol Identifier:

FULMEN/1

Subprotocol Common Name:

FULMEN version 1

Subprotocol Definition:

This document, Section 3.2

The identifier registered in the "Sec-WebSocket-Protocol" header field is "fulmen/1".

This document makes no other requests of IANA.

10. Normative References

[BCP14]
Best Current Practice 14, <https://www.rfc-editor.org/info/bcp14>.
At the time of writing, this BCP comprises the following:
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC6455]
Fette, I. and A. Melnikov, "The WebSocket Protocol", RFC 6455, DOI 10.17487/RFC6455, , <https://www.rfc-editor.org/rfc/rfc6455>.
[RFC6838]
Freed, N., Klensin, J., and T. Hansen, "Media Type Specifications and Registration Procedures", BCP 13, RFC 6838, DOI 10.17487/RFC6838, , <https://www.rfc-editor.org/rfc/rfc6838>.
[RFC7301]
Friedl, S., Popov, A., Langley, A., and E. Stephan, "Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension", RFC 7301, DOI 10.17487/RFC7301, , <https://www.rfc-editor.org/rfc/rfc7301>.
[RFC8446]
Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, , <https://www.rfc-editor.org/rfc/rfc8446>.
[RFC8949]
Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, , <https://www.rfc-editor.org/rfc/rfc8949>.
[RFC9293]
Eddy, W., Ed., "Transmission Control Protocol (TCP)", STD 7, RFC 9293, DOI 10.17487/RFC9293, , <https://www.rfc-editor.org/rfc/rfc9293>.
[RFC9562]
Davis, K., Peabody, B., and P. Leach, "Universally Unique IDentifiers (UUIDs)", RFC 9562, DOI 10.17487/RFC9562, , <https://www.rfc-editor.org/rfc/rfc9562>.

Authors' Addresses

Giacomo Leopizzi
Information Technology Institute, Riga Technical University
Riga, LV-1048
Latvia
Jānis Grabis
Information Technology Institute, Riga Technical University
Riga, LV-1048
Latvia
Kaspars Ābelnīca
Information Technology Institute, Riga Technical University
Riga, LV-1048
Latvia
Simone Martorelli
IBM
821 09 Bratislava
Slovakia