ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
udp_telemetry.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 udp_telemetry.h
6 * @brief One point in line protocol, cast to a collector as one UDP datagram
7 * (PROTOCORE_ENABLE_UDP_TELEMETRY).
8 *
9 * The payload is line protocol, the text format InfluxData specifies for a point. That is a vendor
10 * specification, published as "Line protocol" in the InfluxData documentation (InfluxDB v2,
11 * reference/syntax/line-protocol; InfluxDB v1, write_protocols/line_protocol_reference). No IETF
12 * document governs it and it carries no RFC number.
13 *
14 * The syntax that reference gives is
15 *
16 * <measurement>[,<tag_key>=<tag_value>...] <field_key>=<field_value>[,...] [<timestamp>]
17 *
18 * and it names four elements: measurement (required), tag set (optional), field set (required, at
19 * least one entry) and timestamp (optional, Unix nanoseconds by default). A field value carries its
20 * type in its suffix: `i` signed integer, `u` unsigned integer, no suffix float. Tag keys and tag
21 * values escape comma, equals and space with a backslash ("Special characters").
22 *
23 * The transport is UDP, RFC 768. Its "User Interface" section names the send: "an operation that
24 * allows a datagram to be sent, specifying the data, source and destination ports and addresses to
25 * be sent". RFC 768 also states "delivery and duplicate protection are not guaranteed", so a write
26 * reports only that the stack took the octets: nothing is acknowledged and nothing is retried.
27 *
28 * A line is built into a buffer the caller owns, one element per call, and this module holds the
29 * position in it. That buffer has to outlive the calls between a measurement and its write. Nothing
30 * here allocates. A build with no network stack builds lines and refuses every send.
31 *
32 * The module exports one symbol, @ref UdpTelemetry. Everything in udp_telemetry.c has internal
33 * linkage.
34 *
35 * @author Douglas Quigg (dstroy0)
36 * @date 2026
37 */
38
39#ifndef PROTOCORE_UDP_TELEMETRY_H
40#define PROTOCORE_UDP_TELEMETRY_H
41
42#include "protocore_config.h" // the entry point: protocore_types.h for the widths
43
44#if PROTOCORE_ENABLE_UDP_TELEMETRY
45
47
48/** @brief RFC 768 "User Interface": the destination address and port every datagram carries. */
49typedef struct
50{
51 const char *addr; ///< the collector's address as text, v4 or v6, parsed once by a begin
52 uint16_t port; ///< its UDP port
53} UdpTelemetryCollectorArgs;
54
55/** @brief Where one line is built, and the measurement it opens with (line protocol element 1). */
56typedef struct
57{
58 char *buf; ///< the caller's buffer; it outlives the calls that build into it
59 size_t cap; ///< how much room it has, the NUL included
60 const char *measurement; ///< the measurement name; NULL opens the line with nothing
61} UdpTelemetryLineArgs;
62
63/** @brief One tag set entry, `,tag_key=tag_value` (line protocol element 2). */
64typedef struct
65{
66 const char *key; ///< the tag key; comma, equals and space are escaped
67 const char *value; ///< its tag value, escaped the same way; NULL writes nothing after the equals
68} UdpTelemetryTagArgs;
69
70/** @brief One field set entry, `field_key=field_value` (line protocol element 3). */
71typedef struct
72{
73 const char *key; ///< the field key
74 int64_t i64; ///< the value a field_int writes, suffixed `i`
75 uint64_t u64; ///< the value a field_uint writes, suffixed `u`
76 float f32; ///< the value a field_float writes, unsuffixed
77 uint8_t decimals; ///< digits after the point a field_float writes
78} UdpTelemetryFieldArgs;
79
80/** @brief The trailing timestamp (line protocol element 4). */
81typedef struct
82{
83 int64_t unix_ns; ///< the point's time in Unix nanoseconds, the default precision
84} UdpTelemetryTimestampArgs;
85
86/** @brief The octets one datagram carries. */
87typedef struct
88{
89 const char *data; ///< the bytes a send hands the stack
90 size_t len; ///< how many
91} UdpTelemetryPayloadArgs;
92
93/**
94 * @brief The line protocol caster.
95 *
96 * A caller sets the members a call takes, invokes it through ::UdpTelemetry, and reads the outcome
97 * off the same handle. A measurement opens a line, tag and field_* append its elements in that
98 * order, and a write sends the whole line as one datagram.
99 *
100 * No slot member: one collector, one line at a time, so no call names a row.
101 *
102 * @var UdpTelemetryNs::collector where the datagrams go (RFC 768 "User Interface")
103 * @var UdpTelemetryNs::line the buffer a line is built in and the measurement it opens with
104 * @var UdpTelemetryNs::tags one tag set entry
105 * @var UdpTelemetryNs::fields one field set entry, in the width the call takes
106 * @var UdpTelemetryNs::time the trailing timestamp
107 * @var UdpTelemetryNs::payload the octets a send hands the stack
108 * @var UdpTelemetryNs::ok a call's true/false outcome; on a build call, the line is a
109 * complete point so far
110 * @var UdpTelemetryNs::overflow an append did not fit; every later append is a no-op
111 * @var UdpTelemetryNs::n octets the line holds, the NUL excluded
112 * @var UdpTelemetryNs::begin parse the collector address and store it with its port
113 * @var UdpTelemetryNs::measurement bind @c line and open it with the measurement
114 * @var UdpTelemetryNs::tag append one tag set entry, before any field
115 * @var UdpTelemetryNs::field_int append `field_key=<i64>i`
116 * @var UdpTelemetryNs::field_uint append `field_key=<u64>u`
117 * @var UdpTelemetryNs::field_float append `field_key=<f32>` to @c decimals places
118 * @var UdpTelemetryNs::timestamp append the trailing timestamp, after the field set
119 * @var UdpTelemetryNs::send send @c payload to the collector as one datagram
120 * @var UdpTelemetryNs::write send the built line as one datagram; an incomplete line sends
121 * nothing
122 */
123typedef struct
124{
125 UdpTelemetryCollectorArgs collector; ///< where the datagrams go
126 UdpTelemetryLineArgs line; ///< where a line is built
127 UdpTelemetryTagArgs tags; ///< one tag set entry
128 UdpTelemetryFieldArgs fields; ///< one field set entry
129 UdpTelemetryTimestampArgs time; ///< the trailing timestamp
130 UdpTelemetryPayloadArgs payload; ///< what a send carries
131 proto_bool ok;
132 proto_bool overflow;
133 size_t n;
134} UdpTelemetryVars;
135
136/** @brief The operands and the outcome. */
137extern UdpTelemetryVars UdpTelemetryV;
138
139/** @brief The entries. */
140typedef struct
141{
142 void (*const begin)(uint8_t *work);
143 void (*const measurement)(uint8_t *work);
144 void (*const tag)(uint8_t *work);
145 void (*const field_int)(uint8_t *work);
146 void (*const field_uint)(uint8_t *work);
147 void (*const field_float)(uint8_t *work);
148 void (*const timestamp)(uint8_t *work);
149 void (*const send)(uint8_t *work);
150 void (*const write)(uint8_t *work);
151} UdpTelemetryNs;
152
153// What the table binds, defined once in the .c and taking one parameter each: everything
154// else an entry needs is an operand in UdpTelemetryV or a region of the borrow at a fixed offset.
155void protocore_udp_telemetry_begin(uint8_t *work);
156void protocore_udp_telemetry_measurement(uint8_t *work);
157void protocore_udp_telemetry_tag(uint8_t *work);
158void protocore_udp_telemetry_field_int(uint8_t *work);
159void protocore_udp_telemetry_field_uint(uint8_t *work);
160void protocore_udp_telemetry_field_float(uint8_t *work);
161void protocore_udp_telemetry_timestamp(uint8_t *work);
162void protocore_udp_telemetry_send(uint8_t *work);
163void protocore_udp_telemetry_write(uint8_t *work);
164
165// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
166// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
167// `UdpTelemetry.begin(work)` resolves to a named function and becomes a DIRECT call. An extern table
168// leaves the call indirect and the symbol live at every level, -O2 -flto included.
169static const UdpTelemetryNs UdpTelemetry __attribute__((unused)) = {
170 .begin = protocore_udp_telemetry_begin,
171 .measurement = protocore_udp_telemetry_measurement,
172 .tag = protocore_udp_telemetry_tag,
173 .field_int = protocore_udp_telemetry_field_int,
174 .field_uint = protocore_udp_telemetry_field_uint,
175 .field_float = protocore_udp_telemetry_field_float,
176 .timestamp = protocore_udp_telemetry_timestamp,
177 .send = protocore_udp_telemetry_send,
178 .write = protocore_udp_telemetry_write,
179};
180
181/**
182 * @brief The PROTOCORE_UDP_TELEMETRY_BORROW bytes this module's state lives in.
183 *
184 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
185 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
186 * walks, so the state lasts the life of the program.
187 *
188 * @return the span.
189 */
190uint8_t *protocore_udp_telemetry_span(void);
191
193
194#endif // PROTOCORE_ENABLE_UDP_TELEMETRY
195
196#endif // PROTOCORE_UDP_TELEMETRY_H
#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