ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
stomp.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file stomp.h
6 * @brief The STOMP 1.2 frame codec: the sec 9 grammar, built and parsed over caller buffers.
7 *
8 * STOMP 1.2 is a community specification published at
9 * https://stomp.github.io/stomp-specification-1.2.html. It is not an IETF document and carries no
10 * RFC number; every citation here names that specification and its section.
11 *
12 * Sec 9 Augmented BNF gives the frame:
13 *
14 * frame = command EOL *( header EOL ) EOL *OCTET NULL *( EOL )
15 * header = header-name ":" header-value
16 * header-name = 1*<any OCTET except CR or LF or ":">
17 * EOL = [CR] LF
18 *
19 * Sec 4 states the octets: a command terminated by an EOL, "an OPTIONAL carriage return (octet 13)
20 * followed by a REQUIRED line feed (octet 10)", then zero or more header entries in
21 * `<key>:<value>` format, then "a blank line (i.e. an extra EOL)" ending the headers and beginning
22 * the body. Sec 6 names the client commands, sec 7 the server commands.
23 *
24 * Sec 4.3.1: a content-length header "is an octet count for the length of the message body. If a
25 * content-length header is included, this number of octets MUST be read, regardless of whether or
26 * not there are NULL octets in the body. The frame still needs to be terminated with a NULL
27 * octet." With no such header the body runs to the first NULL.
28 *
29 * Sec 4.1 Value Encoding escapes four octets inside a header-name or header-value: `\r` is CR,
30 * `\n` is LF, `\c` is colon, `\\` is backslash. An undefined escape sequence "MUST be treated as a
31 * fatal protocol error", so an unescape reports zero octets on one.
32 *
33 * Sec 4.4 Repeated Header Entries: "only the first header entry SHOULD be used as the value of
34 * header entry" - the entry a lookup reports.
35 *
36 * Sec 5.4 Heart-beating: a sender with no frame to send "MUST send an end-of-line (EOL)", and sec 9
37 * trails a frame with `*( EOL )`. A parse steps over those octets ahead of a command and counts
38 * them in @c consumed.
39 *
40 * The parser copies nothing: the command, every header-name and header-value, and the body are
41 * slices into the caller's buffer, and a header-value arrives still escaped. The builder escapes
42 * every header-name and header-value it writes; sec 4.1 exempts the CONNECT and CONNECTED frames
43 * from escaping for STOMP 1.0 compatibility.
44 *
45 * The module exports one symbol, @ref Stomp. Everything in stomp.c has internal linkage.
46 *
47 * @author Douglas Quigg (dstroy0)
48 * @date 2026
49 */
50
51#ifndef PROTOCORE_STOMP_H
52#define PROTOCORE_STOMP_H
53
54#include "protocore_config.h" // the entry point: protocore_types.h for the widths
55
56#if PROTOCORE_ENABLE_STOMP
57
59
60/** @brief One header entry (sec 9 `header = header-name ":" header-value`), sliced from the source. */
61typedef struct
62{
63 const char *name; ///< the header-name octets, not NUL-terminated
64 size_t name_len; ///< how many
65 const char *value; ///< the header-value octets, still escaped (sec 4.1)
66 size_t value_len; ///< how many
67} StompHeader;
68
69/** @brief One parsed frame (sec 9). Every pointer slices the source buffer; nothing is copied. */
70typedef struct
71{
72 const char *command; ///< the command octets (sec 6, sec 7)
73 size_t command_len; ///< how many
74 StompHeader headers[PROTOCORE_STOMP_MAX_HEADERS]; ///< the header entries, in wire order
75 size_t header_count; ///< how many, capped at PROTOCORE_STOMP_MAX_HEADERS
76 const char *body; ///< the body octets (sec 4.2)
77 size_t body_len; ///< how many
78} StompFrame;
79
80/** @brief The caller buffer a codec runs over. */
81typedef struct
82{
83 char *out; ///< where a build or an unescape writes its octets
84 size_t cap; ///< how many it holds
85 const char *in; ///< the octets a parse or an unescape reads
86 size_t len; ///< how many
87} StompBufArgs;
88
89/** @brief The frame a build emits: its command, its header entries, and its body (sec 9). */
90typedef struct
91{
92 const char *command; ///< the command it writes (sec 6 client-command, sec 7 server-command)
93 const char *const *header_names; ///< NUL-terminated header-name strings, @c header_count of them
94 const char *const *header_values; ///< NUL-terminated header-value strings, parallel to @c header_names
95 size_t header_count; ///< how many header entries
96 const char *body; ///< the body octets (sec 4.2); NULL for an empty body
97 size_t body_len; ///< how many
98} StompBuildArgs;
99
100/** @brief The header entry a lookup names (sec 4.4 takes the first entry with that name). */
101typedef struct
102{
103 const char *name; ///< the header-name to match, NUL-terminated
104} StompLookupArgs;
105
106/**
107 * @brief The STOMP 1.2 frame codec (stomp.github.io, not an IETF document).
108 *
109 * A caller points @c frame at its own ::StompFrame, sets the members a call takes, invokes it
110 * through ::Stomp, and reads the outcome off the same handle.
111 *
112 * No storage member: both buffers and the frame are the caller's, and the codec holds nothing
113 * between calls.
114 *
115 * @var StompNs::frame the frame a parse fills and a lookup searches
116 * @var StompNs::buf the caller buffer a build writes and a parse reads
117 * @var StompNs::build_args the command, header entries and body a build emits
118 * @var StompNs::lookup the header-name a lookup matches
119 * @var StompNs::ok a call's true/false outcome
120 * @var StompNs::n octets a build wrote including the NULL, or octets an unescape decoded; 0 on failure
121 * @var StompNs::consumed octets the parsed frame occupied, its leading EOLs included
122 * @var StompNs::value the raw, still escaped header-value a lookup found
123 * @var StompNs::value_len how many
124 * @var StompNs::build write `command EOL *( header EOL ) EOL body NULL` into @c buf.out, every
125 * header-name and header-value escaped (sec 4.1, sec 9)
126 * @var StompNs::parse take one frame from the head of @c buf.in into @c *frame, the body sized by
127 * content-length when present (sec 4.3.1)
128 * @var StompNs::header find @c lookup.name among @c *frame header entries, first match wins (sec 4.4)
129 * @var StompNs::unescape decode the sec 4.1 escapes in @c buf.in into @c buf.out
130 */
131typedef struct
132{
133 StompFrame *frame; ///< the frame a parse fills and a lookup searches
134 StompBufArgs buf; ///< the caller buffer a codec runs over
135 StompBuildArgs build_args; ///< what a build emits
136 StompLookupArgs lookup; ///< what a lookup matches
137 proto_bool ok;
138 size_t n;
139 size_t consumed;
140 const char *value;
141 size_t value_len;
142} StompVars;
143
144/** @brief The operands and the outcome. */
145extern StompVars StompV;
146
147/** @brief The entries. */
148typedef struct
149{
150 void (*const build)(uint8_t *work);
151 void (*const parse)(uint8_t *work);
152 void (*const header)(uint8_t *work);
153 void (*const unescape)(uint8_t *work);
154} StompNs;
155
156// What the table binds, defined once in the .c and taking one parameter each: everything
157// else an entry needs is an operand in StompV or a region of the borrow at a fixed offset.
158void protocore_stomp_build(uint8_t *work);
159void protocore_stomp_parse(uint8_t *work);
160void protocore_stomp_header(uint8_t *work);
161void protocore_stomp_unescape(uint8_t *work);
162
163// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
164// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
165// `Stomp.build(work)` resolves to a named function and becomes a DIRECT call. An extern table
166// leaves the call indirect and the symbol live at every level, -O2 -flto included.
167static const StompNs Stomp __attribute__((unused)) = {
168 .build = protocore_stomp_build,
169 .parse = protocore_stomp_parse,
170 .header = protocore_stomp_header,
171 .unescape = protocore_stomp_unescape,
172};
173
175
176#endif // PROTOCORE_ENABLE_STOMP
177
178#endif // PROTOCORE_STOMP_H
#define PROTOCORE_STOMP_MAX_HEADERS
Max header lines parsed per STOMP frame (extras beyond this are ignored).
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97