ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
common.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 common.h
6 * @brief The UDP wire protocol: what a received datagram looks like in the ring, and how one goes
7 * in and comes back out.
8 *
9 * A datagram is a message, so a ring of them carries a fixed 21-byte header ahead of each payload
10 * rather than a byte stream:
11 *
12 * offset width field
13 * 0 1 family 4 for IPv4, 6 for IPv6
14 * 1 2 port big-endian
15 * 3 2 len big-endian, payload bytes that follow the header
16 * 5 16 addr network order, IPv4 in the first four
17 * 21 len payload
18 *
19 * Every field is written and read at a stated width in network byte order, so the bytes in the ring
20 * are the same bytes on every target. The header is built through an mmgr_span and read through an
21 * mmgr_cspan, which carry the bound and latch an overrun.
22 *
23 * The layout is the contract, so it is published rather than opaque. Internal to transport/udp: no
24 * table, no exported symbol.
25 *
26 * @author Douglas Quigg (dstroy0)
27 * @date 2026
28 */
29
30#ifndef PROTOCORE_UDP_COMMON_H
31#define PROTOCORE_UDP_COMMON_H
32
33#include "endian/endian.h" // magna_extremitas: the address as two big-endian words
34#include "memoria_anularis/memoria_anularis.h" // mmgr_ring: the SPSC ring the datagrams sit in
35#include "octetus_introitus_exitus/octetus_introitus_exitus.h" // byteio.put / put_be / take_be over a span
36#include "shared/ip/ip.h" // protocore_ip: the address a datagram carries, network order
37
39
40/** @brief Bytes a queued datagram spends on its header, ahead of the payload. */
41#define PROTOCORE_UDP_DGRAM_HDR 21u
42
43/** @brief Who a queued datagram is from or to, and how long its payload is. */
44typedef struct
45{
46 protocore_ip addr; ///< peer address, network order
47 uint16_t port; ///< peer port
48 uint16_t len; ///< payload bytes following the header
50
51/** @brief Write the header of @p d into @p w at its cursor. */
53{
54 EMBED_CALL(byteio.put, OctetusCfg, .write_span = w, .byte = (uint8_t)d->addr.family);
55 EMBED_CALL(byteio.put_be, OctetusCfg, .write_span = w, .value = d->port, .bytes = 2);
56 EMBED_CALL(byteio.put_be, OctetusCfg, .write_span = w, .value = d->len, .bytes = 2);
57 EMBED_CALL(byteio.put_be, OctetusCfg, .write_span = w,
58 .value = EMBED_CALL(magna_extremitas.rd, EndianCfg, .src = d->addr.bytes, .width = MMGR_ENDIAN_64),
59 .bytes = 8);
60 EMBED_CALL(byteio.put_be, OctetusCfg, .write_span = w,
61 .value = EMBED_CALL(magna_extremitas.rd, EndianCfg, .src = d->addr.bytes + 8, .width = MMGR_ENDIAN_64),
62 .bytes = 8);
63}
64
65/**
66 * @brief Read a header out of @p r at its cursor into @p d.
67 *
68 * A family byte that is neither 4 nor 6 leaves the address empty, so a caller cannot route on a
69 * value the parser did not recognize.
70 */
72{
73 uint64_t family = 0;
74 uint64_t port = 0;
75 uint64_t len = 0;
76 uint64_t hi = 0;
77 uint64_t lo = 0;
78 if (!EMBED_CALL(byteio.take_be, OctetusCfg, .read_span = r, .bytes = 1, .out = &family) ||
79 !EMBED_CALL(byteio.take_be, OctetusCfg, .read_span = r, .bytes = 2, .out = &port) ||
80 !EMBED_CALL(byteio.take_be, OctetusCfg, .read_span = r, .bytes = 2, .out = &len) ||
81 !EMBED_CALL(byteio.take_be, OctetusCfg, .read_span = r, .bytes = 8, .out = &hi) ||
82 !EMBED_CALL(byteio.take_be, OctetusCfg, .read_span = r, .bytes = 8, .out = &lo))
83 {
84 return PROTO_FALSE;
85 }
87 if (family == (uint64_t)PROTOCORE_IP_V4)
88 {
90 }
91 else if (family == (uint64_t)PROTOCORE_IP_V6)
92 {
94 }
95 (void)EMBED_CALL(magna_extremitas.wr, EndianCfg, .dst = d->addr.bytes, .val = hi, .width = MMGR_ENDIAN_64);
96 (void)EMBED_CALL(magna_extremitas.wr, EndianCfg, .dst = d->addr.bytes + 8, .val = lo, .width = MMGR_ENDIAN_64);
97 d->port = (uint16_t)port;
98 d->len = (uint16_t)len;
99 return PROTO_TRUE;
100}
101
102/**
103 * @brief Dequeue one datagram: @p d takes the header, @p stage takes the payload.
104 *
105 * @p hdr is caller-owned staging of at least ::PROTOCORE_UDP_DGRAM_HDR bytes, written only by the consumer.
106 * Peeks the header, consumes it, then reads exactly its payload length, so the tail always lands on
107 * the next entry boundary. Reports false when the ring holds no whole entry.
108 */
110 uint8_t *stage, size_t stage_cap)
111{
112 if (EMBED_CALL(anularis.available, AnularisCfg, .ring = ring) < PROTOCORE_UDP_DGRAM_HDR)
113 {
114 return PROTO_FALSE;
115 }
116 EMBED_CALL(anularis.peek, AnularisCfg, .ring = ring, .dst = hdr, .bytes = PROTOCORE_UDP_DGRAM_HDR, .offset = 0);
117 mmgr_cspan r = {.buf = hdr, .len = PROTOCORE_UDP_DGRAM_HDR, .pos = 0, .err = EMBED_FALSE};
118 if (!protocore_udp_dgram_decode(&r, d))
119 {
120 return PROTO_FALSE;
121 }
122 if (d->len > stage_cap)
123 {
124 // Nothing queues a payload longer than the stage, so a length past it means the ring lost its
125 // entry boundary. Drop the whole ring rather than read past one.
126 EMBED_CALL(anularis.consume, AnularisCfg, .ring = ring,
127 .bytes = EMBED_CALL(anularis.available, AnularisCfg, .ring = ring));
128 return PROTO_FALSE;
129 }
130 if (EMBED_CALL(anularis.available, AnularisCfg, .ring = ring) < (PROTOCORE_UDP_DGRAM_HDR + (size_t)d->len))
131 {
132 return PROTO_FALSE;
133 }
134 EMBED_CALL(anularis.consume, AnularisCfg, .ring = ring, .bytes = PROTOCORE_UDP_DGRAM_HDR);
135 (void)EMBED_CALL(anularis.read, AnularisCfg, .ring = ring, .dst = stage, .bytes = d->len);
136 return PROTO_TRUE;
137}
138
140
141#endif // PROTOCORE_UDP_COMMON_H
#define PROTOCORE_INLINE
Linkage for a leaf primitive whose body is cheaper than the call that reaches it.
Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful text parsing,...
@ PROTOCORE_IP_NONE
empty / unparsed
Definition ip.h:35
@ PROTOCORE_IP_V4
IPv4 (bytes[0..3])
Definition ip.h:36
@ PROTOCORE_IP_V6
IPv6 (bytes[0..15])
Definition ip.h:37
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:56
uint8_t bytes[16]
network order; v4 uses the first 4
Definition ip.h:58
protocore_ip_family family
address family tag
Definition ip.h:57
Who a queued datagram is from or to, and how long its payload is.
Definition common.h:45
protocore_ip addr
peer address, network order
Definition common.h:46
uint16_t port
peer port
Definition common.h:47
uint16_t len
payload bytes following the header
Definition common.h:48
PROTOCORE_INLINE proto_bool protocore_udp_dgram_take(mmgr_ring *ring, uint8_t *hdr, protocore_udp_dgram *d, uint8_t *stage, size_t stage_cap)
Dequeue one datagram: d takes the header, stage takes the payload.
Definition common.h:109
PROTOCORE_INLINE proto_bool protocore_udp_dgram_decode(mmgr_cspan *r, protocore_udp_dgram *d)
Read a header out of r at its cursor into d.
Definition common.h:71
#define PROTOCORE_UDP_DGRAM_HDR
Bytes a queued datagram spends on its header, ahead of the payload.
Definition common.h:41
PROTOCORE_INLINE void protocore_udp_dgram_encode(mmgr_span *w, const protocore_udp_dgram *d)
Write the header of d into w at its cursor.
Definition common.h:52
#define PROTO_FALSE
the false value
Definition types.h:68
#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 PROTO_TRUE
the true value, spelled so a caller never writes a bare 1
Definition types.h:67
#define PROTOCORE_END_DECLS
Definition types.h:97