ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
wamp.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file wamp.cpp
6 * @brief WAMP message builders (over JsonWriter) + positional array parser (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_WAMP
12
14
15// Emit a uint64 as a JSON number (JsonWriter's integer() is only platform-long wide).
16static void emit_uint(JsonWriter &w, uint64_t v)
17{
18 char rev[20];
19 size_t r = 0;
20 char tmp[21];
21 size_t n = 0;
22 if (v == 0)
23 {
24 w.raw("0");
25 return;
26 }
27 while (v)
28 {
29 rev[r++] = (char)('0' + (int)(v % 10));
30 v /= 10;
31 }
32 while (r)
33 {
34 tmp[n++] = rev[--r];
35 }
36 tmp[n] = '\0';
37 w.raw(tmp);
38}
39
40static size_t finish(JsonWriter &w)
41{
42 return w.ok() ? w.length() : 0;
43}
44
45// Append the trailing Arguments / ArgumentsKw of a PUBLISH / CALL / YIELD.
46static void emit_args(JsonWriter &w, const char *args_json, const char *kwargs_json)
47{
48 if (!args_json && !kwargs_json)
49 {
50 return;
51 }
52 w.raw(args_json ? args_json : "[]"); // kwargs without args still needs a positional Arguments
53 if (kwargs_json)
54 {
55 w.raw(kwargs_json);
56 }
57}
58
59size_t pc_wamp_build_hello(char *buf, size_t cap, const char *realm, const char *details_json)
60{
61 if (!buf || !realm)
62 {
63 return 0;
64 }
65 JsonWriter w(buf, cap);
66 w.begin_array();
67 w.integer(WAMP_HELLO);
68 w.str(realm);
69 w.raw(details_json ? details_json : "{}");
70 w.end_array();
71 return finish(w);
72}
73
74size_t pc_wamp_build_goodbye(char *buf, size_t cap, const char *reason_uri, const char *details_json)
75{
76 if (!buf || !reason_uri)
77 {
78 return 0;
79 }
80 JsonWriter w(buf, cap);
81 w.begin_array();
82 w.integer(WAMP_GOODBYE);
83 w.raw(details_json ? details_json : "{}");
84 w.str(reason_uri);
85 w.end_array();
86 return finish(w);
87}
88
89size_t pc_wamp_build_subscribe(char *buf, size_t cap, uint64_t request, const char *topic, const char *options_json)
90{
91 if (!buf || !topic)
92 {
93 return 0;
94 }
95 JsonWriter w(buf, cap);
96 w.begin_array();
97 w.integer(WAMP_SUBSCRIBE);
98 emit_uint(w, request);
99 w.raw(options_json ? options_json : "{}");
100 w.str(topic);
101 w.end_array();
102 return finish(w);
103}
104
105size_t pc_wamp_build_unsubscribe(char *buf, size_t cap, uint64_t request, uint64_t subscription_id)
106{
107 if (!buf)
108 {
109 return 0;
110 }
111 JsonWriter w(buf, cap);
112 w.begin_array();
113 w.integer(WAMP_UNSUBSCRIBE);
114 emit_uint(w, request);
115 emit_uint(w, subscription_id);
116 w.end_array();
117 return finish(w);
118}
119
120size_t pc_wamp_build_unregister(char *buf, size_t cap, uint64_t request, uint64_t registration_id)
121{
122 if (!buf)
123 {
124 return 0;
125 }
126 JsonWriter w(buf, cap);
127 w.begin_array();
128 w.integer(WAMP_UNREGISTER);
129 emit_uint(w, request);
130 emit_uint(w, registration_id);
131 w.end_array();
132 return finish(w);
133}
134
135size_t pc_wamp_build_publish(char *buf, size_t cap, uint64_t request, const char *topic, const char *options_json,
136 const char *args_json, const char *kwargs_json)
137{
138 if (!buf || !topic)
139 {
140 return 0;
141 }
142 JsonWriter w(buf, cap);
143 w.begin_array();
144 w.integer(WAMP_PUBLISH);
145 emit_uint(w, request);
146 w.raw(options_json ? options_json : "{}");
147 w.str(topic);
148 emit_args(w, args_json, kwargs_json);
149 w.end_array();
150 return finish(w);
151}
152
153size_t pc_wamp_build_call(char *buf, size_t cap, uint64_t request, const char *procedure, const char *options_json,
154 const char *args_json, const char *kwargs_json)
155{
156 if (!buf || !procedure)
157 {
158 return 0;
159 }
160 JsonWriter w(buf, cap);
161 w.begin_array();
162 w.integer(WAMP_CALL);
163 emit_uint(w, request);
164 w.raw(options_json ? options_json : "{}");
165 w.str(procedure);
166 emit_args(w, args_json, kwargs_json);
167 w.end_array();
168 return finish(w);
169}
170
171size_t pc_wamp_build_register(char *buf, size_t cap, uint64_t request, const char *procedure, const char *options_json)
172{
173 if (!buf || !procedure)
174 {
175 return 0;
176 }
177 JsonWriter w(buf, cap);
178 w.begin_array();
179 w.integer(WAMP_REGISTER);
180 emit_uint(w, request);
181 w.raw(options_json ? options_json : "{}");
182 w.str(procedure);
183 w.end_array();
184 return finish(w);
185}
186
187size_t pc_wamp_build_yield(char *buf, size_t cap, uint64_t request, const char *options_json, const char *args_json,
188 const char *kwargs_json)
189{
190 if (!buf)
191 {
192 return 0;
193 }
194 JsonWriter w(buf, cap);
195 w.begin_array();
196 w.integer(WAMP_YIELD);
197 emit_uint(w, request);
198 w.raw(options_json ? options_json : "{}");
199 emit_args(w, args_json, kwargs_json);
200 w.end_array();
201 return finish(w);
202}
203
204// ---- positional parser ----
205
206static size_t skip_ws(const char *s, size_t i)
207{
208 while (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')
209 {
210 i++;
211 }
212 return i;
213}
214
215// Scan a JSON string at s[i]=='"'; return the index past the closing quote, or 0 on error.
216static size_t scan_string(const char *s, size_t i)
217{
218 i++; // past the opening quote
219 while (s[i])
220 {
221 if (s[i] == '\\')
222 {
223 if (!s[i + 1])
224 {
225 return 0;
226 }
227 i += 2;
228 continue;
229 }
230 if (s[i] == '"')
231 {
232 return i + 1;
233 }
234 i++;
235 }
236 return 0;
237}
238
239// Scan one JSON value at s[i] (no leading ws); return the index just past it, or 0 on error.
240static size_t scan_value(const char *s, size_t i)
241{
242 if (s[i] == '"')
243 {
244 return scan_string(s, i);
245 }
246 if (s[i] == '{' || s[i] == '[')
247 {
248 char open = s[i], close = (open == '{') ? '}' : ']';
249 int depth = 0;
250 while (s[i])
251 {
252 if (s[i] == '"')
253 {
254 size_t e = scan_string(s, i);
255 if (!e)
256 {
257 return 0;
258 }
259 i = e;
260 continue;
261 }
262 if (s[i] == open)
263 {
264 depth++;
265 }
266 else if (s[i] == close)
267 {
268 depth--;
269 if (depth == 0)
270 {
271 return i + 1;
272 }
273 }
274 i++;
275 }
276 return 0;
277 }
278 // bare token: number / true / false / null
279 size_t start = i;
280 while (s[i] && s[i] != ',' && s[i] != ']' && s[i] != '}' && s[i] != ' ' && s[i] != '\t' && s[i] != '\n' &&
281 s[i] != '\r')
282 {
283 i++;
284 }
285 return i > start ? i : 0;
286}
287
288bool pc_wamp_element(const char *msg, size_t index, const char **start, size_t *len)
289{
290 if (!msg)
291 {
292 return false;
293 }
294 size_t i = skip_ws(msg, 0);
295 if (msg[i] != '[')
296 {
297 return false;
298 }
299 i++;
300 for (size_t idx = 0;; idx++)
301 {
302 i = skip_ws(msg, i);
303 if (msg[i] == ']' || msg[i] == '\0')
304 {
305 return false; // ran out before reaching index
306 }
307 size_t s = i;
308 size_t e = scan_value(msg, i);
309 if (!e)
310 {
311 return false;
312 }
313 if (idx == index)
314 {
315 if (start)
316 {
317 *start = msg + s;
318 }
319 if (len)
320 {
321 *len = e - s;
322 }
323 return true;
324 }
325 i = skip_ws(msg, e);
326 if (msg[i] == ',')
327 {
328 i++;
329 }
330 else
331 {
332 return false; // ']' (index past end), NUL, or malformed
333 }
334 }
335}
336
337bool pc_wamp_get_uint(const char *msg, size_t index, uint64_t *out)
338{
339 const char *s;
340 size_t n;
341 // n == 0 is defensive only: scan_value() either fails (0, rejected inside pc_wamp_element) or
342 // returns an index strictly past where it started, so a returned element is never empty.
343 if (!pc_wamp_element(msg, index, &s, &n) || n == 0) // GCOVR_EXCL_LINE n==0 arm unreachable (see above)
344 {
345 return false;
346 }
347 uint64_t v = 0;
348 for (size_t i = 0; i < n; i++)
349 {
350 if (s[i] < '0' || s[i] > '9')
351 {
352 return false;
353 }
354 v = v * 10 + (uint64_t)(s[i] - '0');
355 }
356 if (out)
357 {
358 *out = v;
359 }
360 return true;
361}
362
363bool pc_wamp_get_type(const char *msg, int *out)
364{
365 uint64_t v;
366 if (!pc_wamp_get_uint(msg, 0, &v))
367 {
368 return false;
369 }
370 if (out)
371 {
372 *out = (int)v;
373 }
374 return true;
375}
376
377bool pc_wamp_get_uri(const char *msg, size_t index, char *out, size_t out_cap)
378{
379 const char *s;
380 size_t n;
381 if (!out || out_cap == 0 || !pc_wamp_element(msg, index, &s, &n))
382 {
383 return false;
384 }
385 // The trailing-quote arm is defensive only: an element that starts with '"' was scanned by
386 // scan_string(), which returns the index just past the CLOSING quote or fails outright, so
387 // s[n-1] is always '"' once s[0] is.
388 if (n < 2 || s[0] != '"' || s[n - 1] != '"') // GCOVR_EXCL_LINE s[n-1] arm unreachable (see above)
389 {
390 return false;
391 }
392 size_t body = n - 2;
393 if (body + 1 > out_cap) // need room for the NUL
394 {
395 return false;
396 }
397 for (size_t i = 0; i < body; i++)
398 {
399 out[i] = s[i + 1];
400 }
401 out[body] = '\0';
402 return true;
403}
404
405#endif // PC_ENABLE_WAMP
Builds a JSON document into a fixed caller buffer, no heap.
Definition json.h:59
void str(const char *v)
Emit a quoted, escaped string value.
Definition json.cpp:188
void raw(const char *literal)
Emit a pre-formatted literal verbatim.
Definition json.cpp:234
void end_array()
Close ].
Definition json.cpp:173
void integer(long v)
Emit a signed integer value.
Definition json.cpp:196
bool ok() const
< False after any overflow / structural error.
Definition json.h:90
size_t length() const
< Bytes written so far (excludes the NUL).
Definition json.h:94
void begin_array()
Open [.
Definition json.cpp:169
Layer 6 (Presentation) - zero-heap JSON: a bounded writer and top-level reader.
WAMP (Web Application Messaging Protocol) codec (PC_ENABLE_WAMP) - zero-heap builders + a positional ...