ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
vxi11.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 vxi11.cpp
6 * @brief VXI-11 codec over ONC RPC / XDR (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_VXI11
12
14#include <string.h>
15
16static const uint32_t RPC_MSGTYPE_CALL = 0;
17static const uint32_t RPC_MSGTYPE_REPLY = 1;
18
19// ── XDR cursor helpers (big-endian, 4-byte aligned) ─────────────────────────────────────────────
20
21struct XdrW
22{
23 uint8_t *p;
24 size_t cap;
25 size_t off;
26 bool ok;
27};
28
29static void xw_u32(XdrW *w, uint32_t v)
30{
31 if (!w->ok || w->off + 4 > w->cap)
32 {
33 w->ok = false;
34 return;
35 }
36 pc_wr32be(w->p + w->off, v);
37 w->off += 4;
38}
39
40// A length-prefixed opaque/string: 4-byte length, the bytes, then 0-3 zero pad bytes to a word.
41static void xw_bytes(XdrW *w, const uint8_t *d, size_t n)
42{
43 size_t pad = (4 - (n & 3)) & 3;
44 if (!w->ok || w->off + 4 + n + pad > w->cap)
45 {
46 w->ok = false;
47 return;
48 }
49 pc_wr32be(w->p + w->off, (uint32_t)n);
50 w->off += 4;
51 if (n)
52 {
53 memcpy(w->p + w->off, d, n);
54 w->off += n;
55 }
56 for (size_t i = 0; i < pad; i++)
57 {
58 w->p[w->off++] = 0;
59 }
60}
61
62struct XdrR
63{
64 const uint8_t *p;
65 size_t len;
66 size_t off;
67 bool ok;
68};
69
70static uint32_t xr_u32(XdrR *r)
71{
72 if (!r->ok || r->off + 4 > r->len)
73 {
74 r->ok = false;
75 return 0;
76 }
77 uint32_t v = pc_rd32be(r->p + r->off);
78 r->off += 4;
79 return v;
80}
81
82// Read a length-prefixed opaque; @p d/@p n may be null (to skip). Advances past the pad.
83static bool xr_bytes(XdrR *r, const uint8_t **d, size_t *n)
84{
85 uint32_t len = xr_u32(r);
86 if (!r->ok)
87 {
88 return false;
89 }
90 size_t pad = (4 - (len & 3)) & 3;
91 if (r->off + len + pad > r->len)
92 {
93 r->ok = false;
94 return false;
95 }
96 if (d)
97 {
98 *d = r->p + r->off;
99 }
100 if (n)
101 {
102 *n = len;
103 }
104 r->off += len + pad;
105 return true;
106}
107
108// ── ONC RPC framing ─────────────────────────────────────────────────────────────────────────────
109
110size_t pc_rpc_record_mark(uint8_t *buf, size_t cap, uint32_t payload_len)
111{
112 if (!buf || cap < 4 || (payload_len & 0x80000000u)) // the length must fit in 31 bits
113 {
114 return 0;
115 }
116 pc_wr32be(buf, 0x80000000u | payload_len); // last-fragment flag set
117 return 4;
118}
119
120bool pc_rpc_parse_record_mark(const uint8_t *buf, size_t len, bool *last, uint32_t *frag_len)
121{
122 if (!buf || len < 4)
123 {
124 return false;
125 }
126 uint32_t rm = pc_rd32be(buf);
127 if (last)
128 {
129 *last = (rm & 0x80000000u) != 0;
130 }
131 if (frag_len)
132 {
133 *frag_len = rm & 0x7FFFFFFFu;
134 }
135 return true;
136}
137
138// Start a CALL: reserve 4 bytes for the record mark, then write the RPC call header (through the
139// AUTH_NONE cred + verf). Procedure parameters are written next; finish_call backfills the mark.
140static XdrW begin_call(uint8_t *buf, size_t cap, uint32_t xid, uint32_t prog, uint32_t vers, uint32_t proc)
141{
142 XdrW w = {buf, cap, 4, buf != nullptr};
143 xw_u32(&w, xid);
144 xw_u32(&w, RPC_MSGTYPE_CALL);
145 xw_u32(&w, 2); // rpcvers
146 xw_u32(&w, prog);
147 xw_u32(&w, vers);
148 xw_u32(&w, proc);
149 xw_u32(&w, PC_RPC_AUTH_NONE); // cred.flavor
150 xw_u32(&w, 0); // cred.length
151 xw_u32(&w, PC_RPC_AUTH_NONE); // verf.flavor
152 xw_u32(&w, 0); // verf.length
153 return w;
154}
155
156static size_t finish_call(XdrW *w)
157{
158 if (!w->ok)
159 {
160 return 0;
161 }
162 pc_rpc_record_mark(w->p, w->cap, (uint32_t)(w->off - 4));
163 return w->off;
164}
165
166bool pc_rpc_parse_reply(const uint8_t *rpc, size_t len, uint32_t *xid, uint32_t *accept_stat, size_t *result_off)
167{
168 if (!rpc)
169 {
170 return false;
171 }
172 XdrR r = {rpc, len, 0, true};
173 uint32_t x = xr_u32(&r);
174 uint32_t mtype = xr_u32(&r);
175 uint32_t reply_stat = xr_u32(&r);
176 xr_u32(&r); // verf.flavor
177 xr_bytes(&r, nullptr, nullptr); // verf.body
178 uint32_t astat = xr_u32(&r);
179 if (!r.ok || mtype != RPC_MSGTYPE_REPLY || reply_stat != PC_RPC_MSG_ACCEPTED)
180 {
181 return false;
182 }
183 if (xid)
184 {
185 *xid = x;
186 }
187 if (accept_stat)
188 {
189 *accept_stat = astat;
190 }
191 if (result_off)
192 {
193 *result_off = r.off;
194 }
195 return true;
196}
197
198// Parse the reply header and position a reader at the results; returns false unless SUCCESS.
199static bool reply_results(const uint8_t *rpc, size_t len, XdrR *r)
200{
201 uint32_t astat = 0;
202 size_t off = 0;
203 if (!pc_rpc_parse_reply(rpc, len, nullptr, &astat, &off) || astat != PC_RPC_ACCEPT_SUCCESS)
204 {
205 return false;
206 }
207 r->p = rpc;
208 r->len = len;
209 r->off = off;
210 r->ok = true;
211 return true;
212}
213
214// ── portmapper ──────────────────────────────────────────────────────────────────────────────────
215
216size_t pc_vxi11_build_getport(uint8_t *buf, size_t cap, uint32_t xid, uint32_t prog, uint32_t vers, uint32_t proto)
217{
218 XdrW w = begin_call(buf, cap, xid, PC_RPC_PMAP_PROG, PC_RPC_PMAP_VERS, PC_RPC_PMAP_GETPORT);
219 xw_u32(&w, prog); // mapping fields in order: prog, vers, prot, then port zero
220 xw_u32(&w, vers);
221 xw_u32(&w, proto);
222 xw_u32(&w, 0);
223 return finish_call(&w);
224}
225
226bool pc_vxi11_parse_getport_resp(const uint8_t *rpc, size_t len, uint32_t *port)
227{
228 XdrR r;
229 if (!reply_results(rpc, len, &r))
230 {
231 return false;
232 }
233 uint32_t p = xr_u32(&r);
234 if (!r.ok)
235 {
236 return false;
237 }
238 if (port)
239 {
240 *port = p;
241 }
242 return true;
243}
244
245// ── VXI-11 DEVICE_CORE ──────────────────────────────────────────────────────────────────────────
246
247size_t pc_vxi11_build_create_link(uint8_t *buf, size_t cap, uint32_t xid, int32_t client_id, bool lock_device,
248 uint32_t lock_timeout, const char *device)
249{
250 XdrW w = begin_call(buf, cap, xid, PC_VXI11_CORE_PROG, PC_VXI11_CORE_VERS, (uint32_t)Vxi11Proc::CREATE_LINK);
251 xw_u32(&w, (uint32_t)client_id);
252 xw_u32(&w, lock_device ? 1 : 0);
253 xw_u32(&w, lock_timeout);
254 size_t dlen = device ? strnlen(device, cap) : 0;
255 xw_bytes(&w, (const uint8_t *)device, dlen);
256 return finish_call(&w);
257}
258
259bool pc_vxi11_parse_create_link_resp(const uint8_t *rpc, size_t len, Vxi11CreateLinkResp *out)
260{
261 XdrR r;
262 if (!out || !reply_results(rpc, len, &r))
263 {
264 return false;
265 }
266 out->error = (int32_t)xr_u32(&r);
267 out->lid = (int32_t)xr_u32(&r);
268 out->abort_port = xr_u32(&r);
269 out->max_recv_size = xr_u32(&r);
270 return r.ok;
271}
272
273size_t pc_vxi11_build_device_write(uint8_t *buf, size_t cap, uint32_t xid, int32_t lid, uint32_t io_timeout,
274 uint32_t lock_timeout, uint32_t flags, const uint8_t *data, size_t data_len)
275{
276 if (data_len && !data)
277 {
278 return 0;
279 }
280 XdrW w = begin_call(buf, cap, xid, PC_VXI11_CORE_PROG, PC_VXI11_CORE_VERS, (uint32_t)Vxi11Proc::DEVICE_WRITE);
281 xw_u32(&w, (uint32_t)lid);
282 xw_u32(&w, io_timeout);
283 xw_u32(&w, lock_timeout);
284 xw_u32(&w, flags);
285 xw_bytes(&w, data, data_len);
286 return finish_call(&w);
287}
288
289bool pc_vxi11_parse_write_resp(const uint8_t *rpc, size_t len, Vxi11WriteResp *out)
290{
291 XdrR r;
292 if (!out || !reply_results(rpc, len, &r))
293 {
294 return false;
295 }
296 out->error = (int32_t)xr_u32(&r);
297 out->size = xr_u32(&r);
298 return r.ok;
299}
300
301size_t pc_vxi11_build_device_read(uint8_t *buf, size_t cap, uint32_t xid, int32_t lid, uint32_t request_size,
302 uint32_t io_timeout, uint32_t lock_timeout, uint32_t flags, uint8_t term_char)
303{
304 XdrW w = begin_call(buf, cap, xid, PC_VXI11_CORE_PROG, PC_VXI11_CORE_VERS, (uint32_t)Vxi11Proc::DEVICE_READ);
305 xw_u32(&w, (uint32_t)lid);
306 xw_u32(&w, request_size);
307 xw_u32(&w, io_timeout);
308 xw_u32(&w, lock_timeout);
309 xw_u32(&w, flags);
310 xw_u32(&w, term_char); // char is a full 4-byte XDR word
311 return finish_call(&w);
312}
313
314bool pc_vxi11_parse_read_resp(const uint8_t *rpc, size_t len, Vxi11ReadResp *out)
315{
316 XdrR r;
317 if (!out || !reply_results(rpc, len, &r))
318 {
319 return false;
320 }
321 out->error = (int32_t)xr_u32(&r);
322 out->reason = (int32_t)xr_u32(&r);
323 out->data = nullptr;
324 out->data_len = 0;
325 xr_bytes(&r, &out->data, &out->data_len);
326 return r.ok;
327}
328
329size_t pc_vxi11_build_device_readstb(uint8_t *buf, size_t cap, uint32_t xid, int32_t lid, uint32_t flags,
330 uint32_t lock_timeout, uint32_t io_timeout)
331{
332 XdrW w = begin_call(buf, cap, xid, PC_VXI11_CORE_PROG, PC_VXI11_CORE_VERS, (uint32_t)Vxi11Proc::DEVICE_READSTB);
333 xw_u32(&w, (uint32_t)lid); // Device_GenericParms fields in order: lid, flags, lock_timeout, io_timeout
334 xw_u32(&w, flags);
335 xw_u32(&w, lock_timeout);
336 xw_u32(&w, io_timeout);
337 return finish_call(&w);
338}
339
340bool pc_vxi11_parse_readstb_resp(const uint8_t *rpc, size_t len, Vxi11ReadStbResp *out)
341{
342 XdrR r;
343 if (!out || !reply_results(rpc, len, &r))
344 {
345 return false;
346 }
347 out->error = (int32_t)xr_u32(&r);
348 out->stb = (uint8_t)xr_u32(&r); // stb is a full 4-byte XDR word; the value is the low byte
349 return r.ok;
350}
351
352size_t pc_vxi11_build_device_clear(uint8_t *buf, size_t cap, uint32_t xid, int32_t lid, uint32_t flags,
353 uint32_t lock_timeout, uint32_t io_timeout)
354{
355 XdrW w = begin_call(buf, cap, xid, PC_VXI11_CORE_PROG, PC_VXI11_CORE_VERS, (uint32_t)Vxi11Proc::DEVICE_CLEAR);
356 xw_u32(&w, (uint32_t)lid); // Device_GenericParms: lid, flags, lock_timeout, io_timeout
357 xw_u32(&w, flags);
358 xw_u32(&w, lock_timeout);
359 xw_u32(&w, io_timeout);
360 return finish_call(&w);
361}
362
363size_t pc_vxi11_build_device_trigger(uint8_t *buf, size_t cap, uint32_t xid, int32_t lid, uint32_t flags,
364 uint32_t lock_timeout, uint32_t io_timeout)
365{
366 XdrW w = begin_call(buf, cap, xid, PC_VXI11_CORE_PROG, PC_VXI11_CORE_VERS, (uint32_t)Vxi11Proc::DEVICE_TRIGGER);
367 xw_u32(&w, (uint32_t)lid); // Device_GenericParms: lid, flags, lock_timeout, io_timeout
368 xw_u32(&w, flags);
369 xw_u32(&w, lock_timeout);
370 xw_u32(&w, io_timeout);
371 return finish_call(&w);
372}
373
374size_t pc_vxi11_build_destroy_link(uint8_t *buf, size_t cap, uint32_t xid, int32_t lid)
375{
376 XdrW w = begin_call(buf, cap, xid, PC_VXI11_CORE_PROG, PC_VXI11_CORE_VERS, (uint32_t)Vxi11Proc::DESTROY_LINK);
377 xw_u32(&w, (uint32_t)lid); // Device_Link
378 return finish_call(&w);
379}
380
381bool pc_vxi11_parse_error_resp(const uint8_t *rpc, size_t len, int32_t *error)
382{
383 XdrR r;
384 if (!reply_results(rpc, len, &r))
385 {
386 return false;
387 }
388 int32_t e = (int32_t)xr_u32(&r);
389 if (!r.ok)
390 {
391 return false;
392 }
393 if (error)
394 {
395 *error = e;
396 }
397 return true;
398}
399
400const char *pc_vxi11_error_str(int32_t error)
401{
402 switch (error)
403 {
404 case PC_VXI11_ERR_NONE:
405 return "no error";
406 case PC_VXI11_ERR_SYNTAX:
407 return "syntax error";
408 case PC_VXI11_ERR_NOT_ACCESSIBLE:
409 return "device not accessible";
410 case PC_VXI11_ERR_INVALID_LINK:
411 return "invalid link identifier";
412 case PC_VXI11_ERR_PARAMETER:
413 return "parameter error";
414 case 6:
415 return "channel not established";
416 case 8:
417 return "operation not supported";
418 case 9:
419 return "out of resources";
420 case 11:
421 return "device locked by another link";
422 case PC_VXI11_ERR_NO_LOCK:
423 return "no lock held by this link";
424 case PC_VXI11_ERR_IO_TIMEOUT:
425 return "I/O timeout";
426 case PC_VXI11_ERR_IO_ERROR:
427 return "I/O error";
428 case 21:
429 return "invalid address";
430 case PC_VXI11_ERR_ABORT:
431 return "abort";
432 case 29:
433 return "channel already established";
434 default:
435 return "unknown error";
436 }
437}
438
439#endif // PC_ENABLE_VXI11
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
uint32_t pc_rd32be(const uint8_t *p)
Read a big-endian u32 at p.
Definition endian.h:119
size_t pc_wr32be(uint8_t *p, uint32_t v)
Write v big-endian at p.
Definition endian.h:93
VXI-11 (TCP/IP Instrument Protocol) codec over ONC RPC / XDR (PC_ENABLE_VXI11) - a zero-heap codec fo...