ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mdns_adaptive.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 mdns_adaptive.h
6 * @brief Adaptive mDNS beacon scheduling: RF-aware backoff, TTL refresher, auto-sleep beacon
7 * (PROTOCORE_ENABLE_MDNS_ADAPTIVE).
8 *
9 * The mDNS service (shipped) announces records with a TTL; caches on the network evict a record when its
10 * TTL lapses, so a device must re-announce to stay discoverable. Two pressures shape *when* to announce:
11 *
12 * - **Crowded RF**: on a busy 2.4 GHz channel, hammering announces just adds collisions. So back the
13 * announce interval off (toward a ceiling) when contention is high, and recover it toward the nominal
14 * cadence when the air is quiet.
15 * - **A continuous refresher**: re-announce at ~half the record TTL (RFC 6762 cache eviction is at TTL)
16 * so caches never lapse in steady state.
17 * - **Auto-sleep beacons**: before entering a sleep window that would run past the next refresh, announce
18 * *now* so the record survives the sleep instead of lapsing while the radio is off.
19 *
20 * These are the pure scheduling decisions - what interval, and is an announce due (incl. before a sleep).
21 * The app owns the actual mDNS transmit. Wrap-safe time math, no heap, no stdlib, host-testable.
22 */
23
24#ifndef PROTOCORE_MDNS_ADAPTIVE_H
25#define PROTOCORE_MDNS_ADAPTIVE_H
26
27#include "protocore_config.h" // the entry point: protocore_types.h for the widths
28
29#if PROTOCORE_ENABLE_MDNS_ADAPTIVE
30
32
33// PROTOCORE_MDNS_ADAPTIVE_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
34// it into its arena. A caller takes them once and passes the pointer to every call. How they
35// are carved is this module's and is never named here.
36
37/** @brief Adaptive beacon state. */
38typedef struct
39{
40 uint32_t base_ms; ///< nominal refresh cadence (e.g. TTL/2), and the backoff floor.
41 uint32_t max_ms; ///< backoff ceiling under heavy contention.
42 uint32_t cur_ms; ///< current adaptive interval.
43 uint16_t hi_thresh; ///< contention count at/above which the interval backs off.
44} MdnsBeacon;
45
46/**
47 * @brief Turns a free-running frame counter into a per-window contention value.
48 *
49 * The RF-contention signal is "how many 802.11 frames went by in the last window". A promiscuous
50 * capture only offers a monotonic running total, so this converts that total into a delta over a
51 * fixed window and clamps it to the uint16 the adapt step takes. Pure and wrap-safe (the counter and
52 * the clock both wrap), so the whole sampling policy is host-testable with synthetic inputs.
53 */
54typedef struct
55{
56 uint32_t last_count; ///< frame-counter value at the last emitted sample.
57 uint32_t last_ms; ///< time of the last emitted sample.
58 uint32_t window_ms; ///< how long a sampling window is.
59} MdnsContentionWindow;
60
61/** @brief What to advertise and how aggressively to adapt. */
62typedef struct
63{
64 const char *key; ///< TXT key re-applied to re-announce (must already exist on the service).
65 const char *value; ///< its value (re-applied unchanged; this is the no-bye refresh).
66 uint32_t ttl_s; ///< record TTL; the base cadence is TTL/2.
67 uint32_t max_interval_ms; ///< requested backoff ceiling; capped at ~7/8 of the TTL so the most
68 ///< backed-off refresh still beats cache eviction (a longer TTL buys range).
69 uint16_t hi_contention; ///< frames-per-window at/above which the interval backs off.
70 uint32_t window_ms; ///< contention sampling window (0 => a 1000 ms default).
71} MdnsAdaptiveCfg;
72
73/** @brief What refresh_interval takes: ttl_s. */
74typedef struct
75{
76 uint32_t ttl_s;
77} MdnsAdaptiveRefreshIntervalArgs;
78
79/** @brief What beacon_init takes: b, base_ms, max_ms, hi_thresh. */
80typedef struct
81{
82 MdnsBeacon *b;
83 uint32_t base_ms;
84 uint32_t max_ms;
85 uint16_t hi_thresh;
86} MdnsAdaptiveBeaconInitArgs;
87
88/** @brief What beacon_adapt takes: b, contention. */
89typedef struct
90{
91 MdnsBeacon *b;
92 uint16_t contention;
93} MdnsAdaptiveBeaconAdaptArgs;
94
95/** @brief What beacon_due takes: b, last_ms, now_ms. */
96typedef struct
97{
98 const MdnsBeacon *b;
99 uint32_t last_ms;
100 uint32_t now_ms;
101} MdnsAdaptiveBeaconDueArgs;
102
103/** @brief What beacon_presleep_due takes: b, last_ms, now_ms, sleep_ms. */
104typedef struct
105{
106 const MdnsBeacon *b;
107 uint32_t last_ms;
108 uint32_t now_ms;
109 uint32_t sleep_ms;
110} MdnsAdaptiveBeaconPresleepDueArgs;
111
112/** @brief What contention_init takes: w, window_ms, frames_now, now_ms. */
113typedef struct
114{
115 MdnsContentionWindow *w;
116 uint32_t window_ms;
117 uint32_t frames_now;
118 uint32_t now_ms;
119} MdnsAdaptiveContentionInitArgs;
120
121/** @brief What contention_sample takes: w, frames_now, now_ms, out. */
122typedef struct
123{
124 MdnsContentionWindow *w;
125 uint32_t frames_now; ///< the current running frame total (monotonic; a wrap is handled)
126 uint32_t now_ms;
127 uint16_t *out; ///< receives the frame count for the window (saturated at 0xFFFF)
128} MdnsAdaptiveContentionSampleArgs;
129
130/** @brief What begin takes: cfg. */
131typedef struct
132{
133 const MdnsAdaptiveCfg *cfg;
134} MdnsAdaptiveBeginArgs;
135
136/**
137 * @brief Adaptive mDNS beacon scheduling: RF-aware backoff, TTL refresher, auto-sleep beacon
138 * (PROTOCORE_ENABLE_MDNS_ADAPTIVE).
139 *
140 * A caller sets the members a call takes, invokes it through ::MdnsAdaptive with the bytes it runs
141 * out of, and reads the outcome off the same handle.
142 *
143 * MdnsAdaptive.refresh_interval_args.ttl_s = ...;
144 * MdnsAdaptive.refresh_interval(work);
145 * // MdnsAdaptive.ms is what the call reports
146 *
147 * @var MdnsAdaptiveNs::refresh_interval_args what refresh_interval takes: ttl_s
148 * @var MdnsAdaptiveNs::beacon_init_args what beacon_init takes: b, base_ms, max_ms, hi_thresh
149 * @var MdnsAdaptiveNs::beacon_adapt_args what beacon_adapt takes: b, contention
150 * @var MdnsAdaptiveNs::beacon_due_args what beacon_due takes: b, last_ms, now_ms
151 * @var MdnsAdaptiveNs::beacon_presleep_due_args what beacon_presleep_due takes: b, last_ms, now_ms, sleep_ms
152 * @var MdnsAdaptiveNs::contention_init_args what contention_init takes: w, window_ms, frames_now, now_ms
153 * @var MdnsAdaptiveNs::contention_sample_args what contention_sample takes: w, frames_now, now_ms, out
154 * @var MdnsAdaptiveNs::begin_args what begin takes: cfg
155 * @var MdnsAdaptiveNs::ok true when a window closed and out was written; false if the window ...
156 * @var MdnsAdaptiveNs::ms the new interval (ms)
157 * @var MdnsAdaptiveNs::value the value a call reports
158 * @var MdnsAdaptiveNs::refresh_interval the continuous-refresher cadence for a record TTL: half the TTL, in ...
159 * @var MdnsAdaptiveNs::beacon_init initialize a beacon. cur_ms starts at base_ms
160 * @var MdnsAdaptiveNs::beacon_adapt adapt the interval to observed RF contention (announces/collisions ...
161 * @var MdnsAdaptiveNs::beacon_due is an announce due now? (wrap-safe: elapsed since last_ms >= the ...
162 * @var MdnsAdaptiveNs::beacon_presleep_due auto-sleep beacon: should we announce *before* sleeping for ...
163 * @var MdnsAdaptiveNs::contention_init start sampling at now_ms, anchored to the current counter frames_now
164 * @var MdnsAdaptiveNs::contention_sample if a window has elapsed, report the frames counted in it and start ...
165 * @var MdnsAdaptiveNs::begin start adaptive announcing: begin promiscuous capture on the ...
166 * @var MdnsAdaptiveNs::tick advance the schedule: sample contention, adapt the interval, follow ...
167 * @var MdnsAdaptiveNs::end stop adaptive announcing and release promiscuous mode
168 * @var MdnsAdaptiveNs::interval_ms current adaptive announce interval (ms) - for a diagnostics panel
169 * @var MdnsAdaptiveNs::contention frames counted in the most recently closed window - the live ...
170 * @var MdnsAdaptiveNs::announces total announces sent since begin()
171 *
172 * @c work is PROTOCORE_MDNS_ADAPTIVE_BORROW bytes the CALLER took, at an address it knows. It is not held past the
173 * call, so nothing here aliases it. How those bytes are carved is this module's and is never named here.
174 */
175typedef struct
176{
177 MdnsAdaptiveRefreshIntervalArgs refresh_interval_args;
178 MdnsAdaptiveBeaconInitArgs beacon_init_args;
179 MdnsAdaptiveBeaconAdaptArgs beacon_adapt_args;
180 MdnsAdaptiveBeaconDueArgs beacon_due_args;
181 MdnsAdaptiveBeaconPresleepDueArgs beacon_presleep_due_args;
182 MdnsAdaptiveContentionInitArgs contention_init_args;
183 MdnsAdaptiveContentionSampleArgs contention_sample_args;
184 MdnsAdaptiveBeginArgs begin_args;
185 proto_bool ok;
186 uint32_t ms;
187 uint16_t value;
188} MdnsAdaptiveVars;
189
190/** @brief The operands and the outcome. */
191extern MdnsAdaptiveVars MdnsAdaptiveV;
192
193/** @brief The entries. */
194typedef struct
195{
196 void (*const refresh_interval)(uint8_t *work);
197 void (*const beacon_init)(uint8_t *work);
198 void (*const beacon_adapt)(uint8_t *work);
199 void (*const beacon_due)(uint8_t *work);
200 void (*const beacon_presleep_due)(uint8_t *work);
201 void (*const contention_init)(uint8_t *work);
202 void (*const contention_sample)(uint8_t *work);
203 void (*const begin)(uint8_t *work);
204 void (*const tick)(uint8_t *work);
205 void (*const end)(uint8_t *work);
206 void (*const interval_ms)(uint8_t *work);
207 void (*const contention)(uint8_t *work);
208 void (*const announces)(uint8_t *work);
209} MdnsAdaptiveNs;
210
211// What the table binds, defined once in the .c and taking one parameter each: everything
212// else an entry needs is an operand in MdnsAdaptiveV or a region of the borrow at a fixed offset.
213void protocore_mdns_adaptive_refresh_interval(uint8_t *work);
214void protocore_mdns_adaptive_beacon_init(uint8_t *work);
215void protocore_mdns_adaptive_beacon_adapt(uint8_t *work);
216void protocore_mdns_adaptive_beacon_due(uint8_t *work);
217void protocore_mdns_adaptive_beacon_presleep_due(uint8_t *work);
218void protocore_mdns_adaptive_contention_init(uint8_t *work);
219void protocore_mdns_adaptive_contention_sample(uint8_t *work);
220void protocore_mdns_adaptive_begin(uint8_t *work);
221void protocore_mdns_adaptive_tick(uint8_t *work);
222void protocore_mdns_adaptive_end(uint8_t *work);
223void protocore_mdns_adaptive_interval_ms(uint8_t *work);
224void protocore_mdns_adaptive_contention(uint8_t *work);
225void protocore_mdns_adaptive_announces(uint8_t *work);
226
227// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
228// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
229// `MdnsAdaptive.refresh_interval(work)` resolves to a named function and becomes a DIRECT call. An extern table
230// leaves the call indirect and the symbol live at every level, -O2 -flto included.
231static const MdnsAdaptiveNs MdnsAdaptive __attribute__((unused)) = {
232 .refresh_interval = protocore_mdns_adaptive_refresh_interval,
233 .beacon_init = protocore_mdns_adaptive_beacon_init,
234 .beacon_adapt = protocore_mdns_adaptive_beacon_adapt,
235 .beacon_due = protocore_mdns_adaptive_beacon_due,
236 .beacon_presleep_due = protocore_mdns_adaptive_beacon_presleep_due,
237 .contention_init = protocore_mdns_adaptive_contention_init,
238 .contention_sample = protocore_mdns_adaptive_contention_sample,
239 .begin = protocore_mdns_adaptive_begin,
240 .tick = protocore_mdns_adaptive_tick,
241 .end = protocore_mdns_adaptive_end,
242 .interval_ms = protocore_mdns_adaptive_interval_ms,
243 .contention = protocore_mdns_adaptive_contention,
244 .announces = protocore_mdns_adaptive_announces,
245};
246
247/**
248 * @brief The PROTOCORE_MDNS_ADAPTIVE_BORROW bytes this module's state lives in.
249 *
250 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
251 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
252 * walks, so the state lasts the life of the program.
253 *
254 * @return the span.
255 */
256uint8_t *protocore_mdns_adaptive_span(void);
257
259
260#endif // PROTOCORE_ENABLE_MDNS_ADAPTIVE
261
262#endif // PROTOCORE_MDNS_ADAPTIVE_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