ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
nats.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 nats.h
6 * @brief The NATS client protocol (PROTOCORE_ENABLE_NATS): the client-to-server operations a device
7 * writes, and the server-to-client operations it reads.
8 *
9 * NATS is not an IETF protocol and no RFC governs it. The specification followed here is the NATS
10 * project's client protocol reference, "NATS Protocol" (docs.nats.io, Reference > Protocols >
11 * Client), whose Overview and one section per operation - INFO, CONNECT, PUB, HPUB, SUB, UNSUB, MSG,
12 * HMSG, PING/PONG, +OK/ERR - carry the syntax written below. The wire conventions come from that
13 * document's "Protocol conventions" section, kept in the nats-io/nats.docs repository at
14 * docs/nats_protocol/nats-protocol.html.
15 *
16 * Protocol conventions: a space or a tab delimits the fields of a protocol message and repeated
17 * whitespace counts as one delimiter; CR LF terminates every protocol message and ends a PUB or MSG
18 * payload; subject names, reply subject (INBOX) names included, are case-sensitive, non-empty
19 * alphanumeric strings with no embedded whitespace, optionally token-delimited by the dot.
20 *
21 * The operations, as the reference writes them:
22 *
23 * CONNECT {"option_name":option_value,...}<CRLF>
24 * PUB <subject> [reply-to] <#bytes><CRLF>[payload]<CRLF>
25 * HPUB <subject> [reply-to] <#header bytes> <#total bytes><CRLF>[headers]<CRLF><CRLF>[payload]<CRLF>
26 * SUB <subject> [queue group] <sid><CRLF>
27 * UNSUB <sid> [max_msgs]<CRLF>
28 * PING<CRLF>
29 * PONG<CRLF>
30 * INFO {"option_name":option_value,...}<CRLF>
31 * MSG <subject> <sid> [reply-to] <#bytes><CRLF>[payload]<CRLF>
32 * HMSG <subject> <sid> [reply-to] <#header bytes> <#total bytes><CRLF>[headers]<CRLF><CRLF>[payload]<CRLF>
33 * +OK<CRLF>
34 * -ERR <error message><CRLF>
35 *
36 * #header bytes counts the header section including its terminating CR LF CR LF, and #total bytes
37 * counts that section plus the payload, so an HPUB takes the whole section as one span.
38 *
39 * The Overview states operation names are case insensitive. The parser here matches them in the
40 * upper case a server writes.
41 *
42 * A builder writes one operation into the caller's buffer and reports its length; the parser decodes
43 * the operation at the head of the caller's inbound buffer and reports the octets it occupies.
44 * Pure: no state, no allocation, no I/O.
45 *
46 * The module exports one symbol, @ref Nats. Everything in nats.c has internal linkage.
47 *
48 * @author Douglas Quigg (dstroy0)
49 * @date 2026
50 */
51
52#ifndef PROTOCORE_NATS_H
53#define PROTOCORE_NATS_H
54
55#include "protocore_config.h" // the entry point: protocore_types.h for the widths
56
57#if PROTOCORE_ENABLE_NATS
58
60
61/** @brief The server-to-client operation a parse decoded (NATS Protocol). */
62typedef enum PROTO_ENUM_PACKED
63{
64 NATS_OP_MSG, ///< MSG, or HMSG when the header section is set: subject, sid, reply-to, payload
65 NATS_OP_INFO, ///< INFO: the options JSON lands in @c arg
66 NATS_OP_PING, ///< PING
67 NATS_OP_PONG, ///< PONG
68 NATS_OP_OK, ///< +OK
69 NATS_OP_ERR, ///< -ERR: the error message lands in @c arg
70 NATS_OP_UNKNOWN, ///< an operation name this decoder does not carry
71} NatsOp;
72
73/**
74 * @brief One decoded protocol message. Every span points into the caller's inbound buffer.
75 */
76typedef struct
77{
78 NatsOp op; ///< which operation the control line named
79 const char *subject; ///< MSG / HMSG subject
80 size_t subject_len; ///< its length
81 const char *sid; ///< MSG / HMSG subscription id
82 size_t sid_len; ///< its length
83 const char *reply_to; ///< MSG / HMSG reply-to subject, absent when the field was not written
84 size_t reply_to_len; ///< its length, 0 when absent
85 const char *headers; ///< HMSG header section, NULL for a header-less MSG
86 size_t header_bytes; ///< the #header bytes field, the terminating CR LF CR LF included
87 const uint8_t *payload; ///< MSG payload, the header section excluded
88 size_t payload_len; ///< its length: #bytes for a MSG, #total bytes less #header bytes for an HMSG
89 const char *arg; ///< the INFO options JSON or the -ERR error message
90 size_t arg_len; ///< its length
91} NatsMsg;
92
93/** @brief Where a builder writes one protocol message. */
94typedef struct
95{
96 char *buf; ///< the buffer the operation is written into
97 size_t cap; ///< octets it holds, the NUL a builder adds when there is room included
98} NatsOutArgs;
99
100/** @brief What a CONNECT tells the server about the client. */
101typedef struct
102{
103 const char *options; ///< the JSON object CONNECT carries: {"option_name":option_value,...}
104} NatsClientArgs;
105
106/** @brief What a PUB or an HPUB publishes. */
107typedef struct
108{
109 const char *subject; ///< the subject it publishes to
110 const char *reply_to; ///< the reply-to subject; NULL leaves the optional field off
111 const uint8_t *payload; ///< the payload octets
112 size_t payload_len; ///< how many, the #bytes a PUB writes
113} NatsPublishArgs;
114
115/** @brief The header section an HPUB carries (NATS Protocol, HPUB). */
116typedef struct
117{
118 const char *block; ///< the section: the NATS/1.0 version line, name: value lines, CR LF CR LF
119 size_t bytes; ///< its length, the #header bytes field, the terminator included
120} NatsHeadersArgs;
121
122/** @brief The subscription a SUB opens and an UNSUB ends. */
123typedef struct
124{
125 const char *subject; ///< SUB: the subject the subscription matches
126 const char *queue_group; ///< SUB: the queue group; NULL leaves the optional field off
127 const char *sid; ///< the alphanumeric subscription id the client generates
128 uint32_t max_msgs; ///< UNSUB: messages to deliver before the subscription ends
129 proto_bool with_max; ///< UNSUB: write max_msgs; false ends the subscription at once
130} NatsSubscriptionArgs;
131
132/** @brief The inbound octets a parse reads. */
133typedef struct
134{
135 const char *buf; ///< the receive buffer, one protocol message at its head
136 size_t len; ///< octets buffered
137} NatsInboundArgs;
138
139/**
140 * @brief The NATS client protocol codec.
141 *
142 * A caller sets the members a call takes, invokes it through ::Nats, and reads the outcome off the
143 * same handle.
144 *
145 * No slot member: the codec keeps no rows, so no call names one.
146 *
147 * A builder reports the octets it wrote in @c n and clears @c ok when the operation did not fit
148 * @c out.cap or an argument it needs is absent. It NUL-terminates when a byte is left over, and the
149 * reported length excludes that NUL.
150 *
151 * parse reports true once the whole operation is buffered: the control line for every operation,
152 * and the payload plus its trailing CR LF for a MSG or an HMSG. An operation name it does not carry
153 * reports ::NATS_OP_UNKNOWN and consumes the control line, which is what the server answers with
154 * `-ERR 'Unknown Protocol Operation'`.
155 *
156 * No storage member: every octet a call touches belongs to the caller, so nothing survives a call.
157 *
158 * @var NatsNs::out where a builder writes the operation
159 * @var NatsNs::client the options a CONNECT declares
160 * @var NatsNs::publish the subject, reply-to and payload a PUB or an HPUB carries
161 * @var NatsNs::headers the header section an HPUB carries
162 * @var NatsNs::subscription the subscription a SUB opens or an UNSUB ends
163 * @var NatsNs::in the inbound octets a parse reads
164 * @var NatsNs::ok a call's true/false outcome
165 * @var NatsNs::n octets a builder wrote, 0 when it wrote none
166 * @var NatsNs::consumed octets the decoded operation occupies, so a caller can advance @c in.buf
167 * @var NatsNs::msg the decoded operation, its spans pointing into @c in.buf
168 * @var NatsNs::connect write `CONNECT <options>` from @c client into @c out
169 * @var NatsNs::pub write `PUB <subject> [reply-to] <#bytes>` and the payload from @c publish
170 * @var NatsNs::hpub the same with @c headers ahead of the payload, both lengths written
171 * @var NatsNs::sub write `SUB <subject> [queue group] <sid>` from @c subscription
172 * @var NatsNs::unsub write `UNSUB <sid> [max_msgs]` from @c subscription
173 * @var NatsNs::ping write `PING`
174 * @var NatsNs::pong write `PONG`
175 * @var NatsNs::parse decode the operation at the head of @c in into @c msg
176 */
177typedef struct
178{
179 NatsOutArgs out; ///< where a builder writes
180 NatsClientArgs client; ///< what a CONNECT declares
181 NatsPublishArgs publish; ///< what a PUB or an HPUB carries
182 NatsHeadersArgs headers; ///< the header section an HPUB carries
183 NatsSubscriptionArgs subscription; ///< what a SUB opens or an UNSUB ends
184 NatsInboundArgs in; ///< what a parse reads
185 proto_bool ok;
186 size_t n;
187 size_t consumed;
188 NatsMsg msg;
189} NatsVars;
190
191/** @brief The operands and the outcome. */
192extern NatsVars NatsV;
193
194/** @brief The entries. */
195typedef struct
196{
197 void (*const connect)(uint8_t *work);
198 void (*const pub)(uint8_t *work);
199 void (*const hpub)(uint8_t *work);
200 void (*const sub)(uint8_t *work);
201 void (*const unsub)(uint8_t *work);
202 void (*const ping)(uint8_t *work);
203 void (*const pong)(uint8_t *work);
204 void (*const parse)(uint8_t *work);
205} NatsNs;
206
207// What the table binds, defined once in the .c and taking one parameter each: everything
208// else an entry needs is an operand in NatsV or a region of the borrow at a fixed offset.
209void protocore_nats_connect(uint8_t *work);
210void protocore_nats_pub(uint8_t *work);
211void protocore_nats_hpub(uint8_t *work);
212void protocore_nats_sub(uint8_t *work);
213void protocore_nats_unsub(uint8_t *work);
214void protocore_nats_ping(uint8_t *work);
215void protocore_nats_pong(uint8_t *work);
216void protocore_nats_parse(uint8_t *work);
217
218// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
219// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
220// `Nats.connect(work)` resolves to a named function and becomes a DIRECT call. An extern table
221// leaves the call indirect and the symbol live at every level, -O2 -flto included.
222static const NatsNs Nats __attribute__((unused)) = {
223 .connect = protocore_nats_connect,
224 .pub = protocore_nats_pub,
225 .hpub = protocore_nats_hpub,
226 .sub = protocore_nats_sub,
227 .unsub = protocore_nats_unsub,
228 .ping = protocore_nats_ping,
229 .pong = protocore_nats_pong,
230 .parse = protocore_nats_parse,
231};
232
234
235#endif // PROTOCORE_ENABLE_NATS
236
237#endif // PROTOCORE_NATS_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#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