ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
roaming.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 roaming.h
6 * @brief Layer 2 (Data Link) - the roam decision: which BSS to transition to (PROTOCORE_ENABLE_ROAMING).
7 *
8 * IEEE Std 802.11 defines every field this module reads, not an IETF RFC: the Neighbor Report element
9 * (Element ID 52, IEEE 802.11 sec 9.4.2.36) that Radio Resource Measurement (802.11k) carries, and the
10 * BSS Transition Management Request frame (WNM Action category 10, action 7) that Wireless Network
11 * Management (802.11v) carries. Fast BSS Transition (802.11r) performs the transition and belongs to the
12 * supplicant and the radio.
13 *
14 * The module decodes those two, then fuses the serving BSS's signal strength, the BSS Transition
15 * Candidate List, and the BTM Request into one verdict: transition or stay, to which BSSID, on which
16 * Channel Number, and under which reason. It reads no radio and keeps nothing between calls, so it runs
17 * on a host against synthetic input; the caller measures the link and performs the transition.
18 *
19 * @author Douglas Quigg (dstroy0)
20 * @date 2026
21 */
22
23#ifndef PROTOCORE_ROAMING_H
24#define PROTOCORE_ROAMING_H
25
26#include "protocore_config.h" // the entry point: protocore_types.h for the widths
27
28#if PROTOCORE_ENABLE_ROAMING
29
31
32/** @brief One BSS Transition Candidate: a Neighbor Report entry (IEEE 802.11 sec 9.4.2.36) or a scan row. */
33typedef struct
34{
35 uint8_t bssid[6]; ///< the candidate's BSSID
36 uint8_t channel; ///< its Channel Number
37 int8_t rssi_dbm; ///< its signal strength (dBm; more negative is weaker)
38} protocore_roam_neighbor;
39
40/** @brief What a BSS Transition Management Request asks of us (IEEE 802.11 WNM, 802.11v). */
41typedef struct
42{
43 proto_bool present; ///< a BTM Request decoded this cycle
44 proto_bool disassoc_imminent; ///< Request Mode bit 2: the BSS disassociates us shortly
45 proto_bool has_preferred; ///< a Preferred Candidate List named @ref preferred_bssid
46 uint8_t preferred_bssid[6]; ///< the highest-preference candidate's BSSID
47} protocore_roam_btm;
48
49/** @brief The thresholds a decision applies, carried by the caller rather than a global. */
50typedef struct
51{
52 int8_t roam_rssi_threshold_dbm; ///< a signal-driven transition needs the serving BSS at/below this
53 uint8_t hysteresis_db; ///< a candidate must beat the serving BSS by this margin
54} protocore_roam_policy;
55
56/** @brief Which rule produced the decision. */
57typedef enum
58{
59 PROTOCORE_ROAM_NONE = 0, ///< stay on the serving BSS
60 PROTOCORE_ROAM_BTM_IMMINENT, ///< Request Mode bit 2 (disassociation imminent) forced the transition
61 PROTOCORE_ROAM_BTM_SUGGESTED, ///< a BTM Request named a preferred, no-weaker candidate
62 PROTOCORE_ROAM_LOW_RSSI, ///< the serving signal is at/below threshold and a candidate clears hysteresis
63} protocore_roam_reason;
64
65/** @brief The verdict: transition or stay, and to what. */
66typedef struct
67{
68 proto_bool roam; ///< true to transition to @ref target_bssid
69 uint8_t target_bssid[6]; ///< the BSSID to transition to (valid only when @ref roam)
70 uint8_t target_channel; ///< that candidate's Channel Number
71 protocore_roam_reason reason; ///< which rule produced it (see @ref protocore_roam_reason)
72} protocore_roam_decision;
73
74/** @brief Neighbor Report Element ID (IEEE 802.11 sec 9.4.2.36). */
75#define PROTOCORE_ROAM_NR_ELEM_ID 52
76/** @brief Sentinel RSSI: the candidate carries no signal reading yet, and the caller supplies one. */
77#define PROTOCORE_ROAM_RSSI_UNKNOWN ((int8_t)-128)
78
79// BSS Transition Management Request, a WNM Action frame (IEEE 802.11, 802.11v).
80#define PROTOCORE_ROAM_WNM_CATEGORY 0x0A ///< WNM Action category
81#define PROTOCORE_ROAM_BTM_REQ_ACTION 0x07 ///< BSS Transition Management Request action code
82#define PROTOCORE_ROAM_BTM_PREF_LIST 0x01u ///< Request Mode bit 0: Preferred Candidate List Included
83#define PROTOCORE_ROAM_BTM_DISASSOC 0x04u ///< Request Mode bit 2: Disassociation Imminent
84#define PROTOCORE_ROAM_BTM_TERM_INCL 0x08u ///< Request Mode bit 3: BSS Termination Included
85#define PROTOCORE_ROAM_BTM_ESS_DISASSOC 0x10 ///< Request Mode bit 4: ESS Disassociation Imminent
86
87/** @brief The serving BSS a decision measures against (IEEE 802.11 association). */
88typedef struct
89{
90 const uint8_t *bssid; ///< the BSSID we are associated with; never chosen as a target
91 int8_t rssi_dbm; ///< its signal strength (dBm)
92} RoamLinkArgs;
93
94/** @brief The BSS Transition Candidate List a decision picks from. Nothing a parse reads. */
95typedef struct
96{
97 const protocore_roam_neighbor *list; ///< the candidates
98 uint8_t n; ///< how many entries @ref list holds
99} RoamCandArgs;
100
101/** @brief What constrains the choice: the network's request and the local thresholds. */
102typedef struct
103{
104 const protocore_roam_btm *request; ///< the BTM Request to honor, or NULL for none
105 const protocore_roam_policy *policy; ///< the thresholds to apply, or NULL for the built-in default
106} RoamRuleArgs;
107
108/** @brief A Neighbor Report element list to decode, and where its candidates land (802.11k). */
109typedef struct
110{
111 const uint8_t *elems; ///< the element list, action header already stripped
112 size_t len; ///< its length in octets
113 protocore_roam_neighbor *out; ///< where the decoded candidates are written
114 uint8_t max; ///< how many entries @ref out holds
115} RoamNrArgs;
116
117/** @brief A BSS Transition Management Request frame to decode (802.11v). */
118typedef struct
119{
120 const uint8_t *frame; ///< the action frame, starting at its Category octet
121 size_t len; ///< its length in octets
122} RoamBtmArgs;
123
124/** @brief The policy's handle, described only in roaming.c. */
125
126/**
127 * @brief The roam decision layer.
128 *
129 * A caller sets the members a call takes, invokes it through ::Roam, and reads the outcome off the same
130 * handle.
131 *
132 * @var RoamNs::link the serving BSS a decision measures against
133 * @var RoamNs::cand the BSS Transition Candidate List a decision picks from
134 * @var RoamNs::rules the BTM Request to honor and the thresholds to apply
135 * @var RoamNs::nr the Neighbor Report element list to decode, and where its candidates land
136 * @var RoamNs::btm the BSS Transition Management Request frame to decode
137 * @var RoamNs::decision the verdict a decide reports
138 * @var RoamNs::hint the BTM Request a decode reports, cleared when @ref ok is false
139 * @var RoamNs::n candidates a Neighbor Report decode wrote
140 * @var RoamNs::ok true only for a well-formed BTM Request
141 *
142 * @var RoamNs::decide
143 * Read @ref link, @ref cand and @ref rules and write @ref decision. Rules in order: a BTM Request with
144 * Request Mode bit 2 (Disassociation Imminent) transitions to the preferred candidate when the list holds
145 * it, else to the strongest; a BTM Request naming a preferred candidate that is not weaker than the
146 * serving BSS transitions to it; a serving signal at or below the policy threshold with a strongest
147 * candidate beating it by at least the hysteresis margin transitions to that candidate; otherwise stay.
148 * The serving BSSID is excluded from the candidates. A null @c rules.request skips the first two rules, a
149 * null @c rules.policy takes the built-in default thresholds, and a null @c link.bssid stays.
150 *
151 * @var RoamNs::parse_neighbor_report
152 * Decode @c nr.elems into up to @c nr.max entries at @c nr.out and report the count in @ref n. Each
153 * Neighbor Report element (Element ID 52) supplies BSSID and Channel Number; every other Element ID is
154 * skipped, and a truncated element ends the walk. The report carries no signal reading, so each
155 * candidate's @c rssi_dbm comes back as @ref PROTOCORE_ROAM_RSSI_UNKNOWN for the caller to fill before
156 * @ref RoamNs::decide reads the list.
157 *
158 * @var RoamNs::parse_btm_request
159 * Decode @c btm.frame into @ref hint and set @ref ok. @c btm.frame starts at the Category octet (WNM
160 * category 10, BTM Request action 7), followed by Dialog Token, Request Mode, Disassociation Timer and
161 * Validity Interval. Request Mode bit 2 sets @c disassoc_imminent; with bit 0 set, the first Neighbor
162 * Report element of the Preferred Candidate List supplies @c preferred_bssid, read past the optional BSS
163 * Termination Duration and Session Information URL.
164 *
165 *
166 * No storage member: every call reads its inputs off this handle and writes its result back, so the
167 * module holds nothing between calls.
168 */
169typedef struct
170{
171 RoamLinkArgs link; ///< IEEE 802.11: the serving BSS
172 RoamCandArgs cand; ///< IEEE 802.11: the BSS Transition Candidate List
173 RoamRuleArgs rules; ///< the BTM Request and the local thresholds
174 RoamNrArgs nr; ///< 802.11k: a Neighbor Report element list
175 RoamBtmArgs btm; ///< 802.11v: a BSS Transition Management Request frame
176 protocore_roam_decision decision;
177 protocore_roam_btm hint;
178 uint8_t n;
179 proto_bool ok;
180} RoamVars;
181
182/** @brief The operands and the outcome. */
183extern RoamVars RoamV;
184
185/** @brief The entries. */
186typedef struct
187{
188 void (*const decide)(uint8_t *work);
189 void (*const parse_neighbor_report)(uint8_t *work);
190 void (*const parse_btm_request)(uint8_t *work);
191} RoamNs;
192
193// What the table binds, defined once in the .c and taking one parameter each: everything
194// else an entry needs is an operand in RoamV or a region of the borrow at a fixed offset.
195void protocore_roam_decide(uint8_t *work);
196void protocore_roam_parse_neighbor_report(uint8_t *work);
197void protocore_roam_parse_btm_request(uint8_t *work);
198
199// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
200// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
201// `Roam.decide(work)` resolves to a named function and becomes a DIRECT call. An extern table
202// leaves the call indirect and the symbol live at every level, -O2 -flto included.
203static const RoamNs Roam __attribute__((unused)) = {
204 .decide = protocore_roam_decide,
205 .parse_neighbor_report = protocore_roam_parse_neighbor_report,
206 .parse_btm_request = protocore_roam_parse_btm_request,
207};
208
210
211#endif // PROTOCORE_ENABLE_ROAMING
212
213#endif // PROTOCORE_ROAMING_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