ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
iface_bridge.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 iface_bridge.h
6 * @brief User-defined address:port -> hardware-bus translation (PROTOCORE_ENABLE_IFACE_BRIDGE).
7 *
8 * A configurable "device server": the application registers rules mapping a listen address:port (plus
9 * TCP/UDP) to a hardware endpoint - a UART, an SPI chip-select, or an I2C address - so a network client
10 * talking to `x.x.x.x:nnnn` is transparently bridged to that bus. Two payload models:
11 *
12 * - STREAM (UART): raw bidirectional passthrough. Socket bytes are written to the UART and UART bytes
13 * flow back to the socket, with no framing (a classic serial-device server / ser2net).
14 * - TRANSACTION (SPI / I2C, also usable for UART): the socket carries framed write-then-read
15 * transactions, which is what master-initiated buses need. Each request frame is
16 * uint16 write_len (big-endian) || uint16 read_len (big-endian) || write_bytes[write_len]
17 * and the reply is the read_len bytes clocked/read back. The bus address (I2C 7-bit addr) or
18 * chip-select (SPI CS gpio) + clock/mode come from the rule's target, so the frame stays generic.
19 *
20 * This header is the pure, host-tested core: the fixed-capacity rule table (zero heap) and the
21 * transaction frame codec. The actual bus I/O (uart.h / spi.h / i2c.h) and the PROTO_BRIDGE listener are
22 * the bus step (iface_bridge_hw.*), kept separate exactly like the peripheral services.
23 *
24 * @author Douglas Quigg (dstroy0)
25 * @date 2026
26 */
27
28#ifndef PROTOCORE_IFACE_BRIDGE_H
29#define PROTOCORE_IFACE_BRIDGE_H
30
31#include "protocore_config.h" // the entry point: protocore_types.h for the widths
32
33#if PROTOCORE_ENABLE_IFACE_BRIDGE
34
35#include "shared/ip/ip.h" // the complete type a public struct below holds by value
36
38
39// PROTOCORE_IFACE_BRIDGE_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
40// it into its arena. A caller takes them once and passes the pointer to every call. How they
41// are carved is this module's and is never named here.
42
43#define PROTOCORE_BRIDGE_TXN_HDR 4 ///< transaction frame header: write_len(2) + read_len(2), big-endian
44
45typedef enum PROTO_ENUM_PACKED
46{
47 BRIDGE_BUS_UART = 0,
48 BRIDGE_BUS_SPI = 1,
49 BRIDGE_BUS_I2C = 2
50} BridgeBus;
51
52typedef enum PROTO_ENUM_PACKED
53{
54 BRIDGE_MODE_STREAM = 0, ///< raw bidirectional passthrough (UART)
55 BRIDGE_MODE_TRANSACTION = 1 ///< framed write-then-read (SPI / I2C; also usable for UART)
56} BridgeMode;
57
58typedef enum PROTO_ENUM_PACKED
59{
60 BRIDGE_PROTO_TCP = 0,
61 BRIDGE_PROTO_UDP = 1
62} BridgeProto;
63
64typedef struct BridgeTarget
65{
66 BridgeBus bus;
67 BridgeMode mode;
68 uint8_t unit; ///< UART port # / SPI host # / I2C bus #
69 uint16_t addr_cs; ///< I2C 7-bit address, or SPI chip-select GPIO
70 uint32_t rate; ///< UART baud, or SPI/I2C clock (Hz)
71 uint8_t spi_mode; ///< SPI mode 0..3 (SPI only)
72 uint8_t bit_order; ///< 0 = MSB-first, 1 = LSB-first (SPI only)
73} BridgeTarget;
74
75typedef struct
76{
77 protocore_ip listen_ip; ///< bind address (x.x.x.x / [v6]); family PROTOCORE_IP_NONE = any interface
78 uint16_t listen_port; ///< nnnn
79 BridgeProto proto; ///< TCP or UDP
80 BridgeTarget target;
81 proto_bool used;
82} BridgeRule;
83
84/** @brief What add takes: rule. */
85typedef struct
86{
87 const BridgeRule *rule;
88} IfaceBridgeAddArgs;
89
90/** @brief What map takes: ip, port, proto, target. */
91typedef struct
92{
93 const char *ip;
94 uint16_t port;
95 BridgeProto proto;
96 const BridgeTarget *target;
97} IfaceBridgeMapArgs;
98
99/** @brief What find takes: port, proto. */
100typedef struct
101{
102 uint16_t port;
103 BridgeProto proto;
104} IfaceBridgeFindArgs;
105
106/** @brief What txn_parse takes: buf, len, write_len, read_len, ... */
107typedef struct
108{
109 const uint8_t *buf;
110 size_t len;
111 uint16_t *write_len;
112 uint16_t *read_len;
113 const uint8_t **write_data;
114} IfaceBridgeTxnParseArgs;
115
116/** @brief What txn_build takes: out, cap, write_data, write_len, ... */
117typedef struct
118{
119 uint8_t *out;
120 size_t cap;
121 const uint8_t *write_data;
122 uint16_t write_len;
123 uint16_t read_len;
124} IfaceBridgeTxnBuildArgs;
125
126/**
127 * @brief User-defined address:port -> hardware-bus translation (PROTOCORE_ENABLE_IFACE_BRIDGE). A configurable "device
128 * ...
129 *
130 * A caller sets the members a call takes, invokes it through ::IfaceBridge with the bytes it runs
131 * out of, and reads the outcome off the same handle.
132 *
133 * IfaceBridge.clear(work);
134 *
135 * @var IfaceBridgeNs::add_args what add takes: rule
136 * @var IfaceBridgeNs::map_args what map takes: ip, port, proto, target
137 * @var IfaceBridgeNs::find_args what find takes: port, proto
138 * @var IfaceBridgeNs::txn_parse_args what txn_parse takes: buf, len, write_len, read_len,
139 * @var IfaceBridgeNs::txn_build_args what txn_build takes: out, cap, write_data, write_len,
140 * @var IfaceBridgeNs::ok a call's true/false outcome
141 * @var IfaceBridgeNs::rule what a call reports
142 * @var IfaceBridgeNs::u8 what a call reports
143 * @var IfaceBridgeNs::n bytes written, or 0 if out is too small
144 * @var IfaceBridgeNs::clear clear
145 * @var IfaceBridgeNs::add add
146 * @var IfaceBridgeNs::map map
147 * @var IfaceBridgeNs::find find
148 * @var IfaceBridgeNs::count count
149 * @var IfaceBridgeNs::txn_parse parse a transaction request from a socket buffer. On a complete ...
150 * @var IfaceBridgeNs::txn_build build a transaction request frame (header + write payload) into out
151 *
152 * @c work is PROTOCORE_IFACE_BRIDGE_BORROW bytes the CALLER took, at an address it knows. It is not held past the call,
153 * so nothing here aliases it. How those bytes are carved is this module's and is never named here.
154 */
155typedef struct
156{
157 IfaceBridgeAddArgs add_args;
158 IfaceBridgeMapArgs map_args;
159 IfaceBridgeFindArgs find_args;
160 IfaceBridgeTxnParseArgs txn_parse_args;
161 IfaceBridgeTxnBuildArgs txn_build_args;
162 proto_bool ok;
163 const BridgeRule *rule;
164 uint8_t u8;
165 size_t n;
166} IfaceBridgeVars;
167
168/** @brief The operands and the outcome. */
169extern IfaceBridgeVars IfaceBridgeV;
170
171/** @brief The entries. */
172typedef struct
173{
174 void (*const clear)(uint8_t *work);
175 void (*const add)(uint8_t *work);
176 void (*const map)(uint8_t *work);
177 void (*const find)(uint8_t *work);
178 void (*const count)(uint8_t *work);
179 void (*const txn_parse)(uint8_t *work);
180 void (*const txn_build)(uint8_t *work);
181} IfaceBridgeNs;
182
183// What the table binds, defined once in the .c and taking one parameter each: everything
184// else an entry needs is an operand in IfaceBridgeV or a region of the borrow at a fixed offset.
185void protocore_iface_bridge_clear(uint8_t *work);
186void protocore_iface_bridge_add(uint8_t *work);
187void protocore_iface_bridge_map(uint8_t *work);
188void protocore_iface_bridge_find(uint8_t *work);
189void protocore_iface_bridge_count(uint8_t *work);
190void protocore_iface_bridge_txn_parse(uint8_t *work);
191void protocore_iface_bridge_txn_build(uint8_t *work);
192
193// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
194// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
195// `IfaceBridge.clear(work)` resolves to a named function and becomes a DIRECT call. An extern table
196// leaves the call indirect and the symbol live at every level, -O2 -flto included.
197static const IfaceBridgeNs IfaceBridge __attribute__((unused)) = {
198 .clear = protocore_iface_bridge_clear,
199 .add = protocore_iface_bridge_add,
200 .map = protocore_iface_bridge_map,
201 .find = protocore_iface_bridge_find,
202 .count = protocore_iface_bridge_count,
203 .txn_parse = protocore_iface_bridge_txn_parse,
204 .txn_build = protocore_iface_bridge_txn_build,
205};
206
207/**
208 * @brief The PROTOCORE_IFACE_BRIDGE_BORROW bytes this module's state lives in.
209 *
210 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
211 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
212 * walks, so the state lasts the life of the program.
213 *
214 * @return the span.
215 */
216uint8_t *protocore_iface_bridge_span(void);
217
219
220#endif // PROTOCORE_ENABLE_IFACE_BRIDGE
221
222#endif // PROTOCORE_IFACE_BRIDGE_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful text parsing,...
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:56
#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