ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
roaming.h
Go to the documentation of this file.
1// 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 Wi-Fi roaming decision layer (PC_ENABLE_ROAMING) - the policy that picks a roam target.
7 *
8 * The three 802.11 roaming primitives are supplicant / hardware territory: 802.11k surfaces a neighbor
9 * report (candidate APs), 802.11v delivers a BSS-Transition-Management hint from the network, and 802.11r
10 * does the fast transition. This module is the piece between them: the pure, deterministic policy that
11 * fuses the current link's RSSI, a candidate neighbor list, and an optional BTM hint into a decision -
12 * roam or stay, and to which AP and why. It holds no state and touches no radio, so it is fully
13 * host-testable with synthetic inputs; the caller feeds it real data and executes the transition.
14 *
15 * @author Douglas Quigg (dstroy0)
16 * @date 2026
17 */
18
19#ifndef PROTOCORE_ROAMING_H
20#define PROTOCORE_ROAMING_H
21
22#include "protocore_config.h"
23
24#if PC_ENABLE_ROAMING
25
26#include <stddef.h>
27#include <stdint.h>
28
29/** @brief A candidate access point (from an 802.11k neighbor report or a scan). */
30struct pc_roam_neighbor
31{
32 uint8_t bssid[6]; ///< the AP's BSSID
33 uint8_t channel; ///< operating channel
34 int8_t rssi_dbm; ///< measured signal strength (dBm; more negative is weaker)
35};
36
37/** @brief An 802.11v BSS-Transition-Management hint from the network. */
38struct pc_roam_btm
39{
40 bool present; ///< a BTM request was received this cycle
41 bool disassoc_imminent; ///< the AP will disassociate us shortly (we must leave)
42 bool has_preferred; ///< @ref preferred_bssid names a specific target
43 uint8_t preferred_bssid[6];
44};
45
46/** @brief Roaming policy thresholds (caller-supplied, so no global tuning knob). */
47struct pc_roam_policy
48{
49 int8_t roam_rssi_threshold_dbm; ///< only consider an RSSI-driven roam when the link is at/below this
50 uint8_t hysteresis_db; ///< a candidate must beat the current link by this margin to be worth it
51};
52
53/** @brief Why the decision was made. */
54enum pc_roam_reason
55{
56 PC_ROAM_NONE = 0, ///< stay on the current AP
57 PC_ROAM_BTM_IMMINENT, ///< forced off by a disassociation-imminent BTM request
58 PC_ROAM_BTM_SUGGESTED, ///< the network steered us (BTM) to a preferred, no-weaker AP
59 PC_ROAM_LOW_RSSI, ///< the link is weak and a candidate is clearly stronger
60};
61
62/** @brief The roaming decision. */
63struct pc_roam_decision
64{
65 bool roam; ///< true to transition to @ref target_bssid
66 uint8_t target_bssid[6]; ///< the AP to roam to (valid only when @ref roam)
67 uint8_t target_channel; ///< that AP's channel
68 pc_roam_reason reason; ///< why (see @ref pc_roam_reason)
69};
70
71/**
72 * @brief Decide whether and where to roam (pure, stateless).
73 *
74 * Priority order: a disassociation-imminent BTM forces a roam (to the preferred candidate if it is in the
75 * list, else the strongest); a non-imminent BTM with a preferred, no-weaker candidate is honoured next;
76 * otherwise, when the current RSSI is at/below the policy threshold and the strongest candidate beats it
77 * by at least the hysteresis margin, roam to that candidate. The current AP is never chosen as a target.
78 *
79 * @param current_bssid the BSSID we are associated with (excluded from the candidates).
80 * @param current_rssi_dbm the current link's RSSI.
81 * @param neighbors candidate APs (@p n of them).
82 * @param btm an optional BTM hint (may be null).
83 * @param policy the thresholds (may be null -> a conservative default is used).
84 * @param out receives the decision (never null).
85 */
86void pc_roam_decide(const uint8_t current_bssid[6], int8_t current_rssi_dbm, const pc_roam_neighbor *neighbors,
87 uint8_t n, const pc_roam_btm *btm, const pc_roam_policy *policy, pc_roam_decision *out);
88
89/** @brief 802.11 Neighbor Report element id (IEEE 802.11 ยง9.4.2.36). */
90#define PC_ROAM_NR_ELEM_ID 52
91/** @brief Sentinel RSSI meaning "not yet measured" - the caller fills it after scanning the candidate. */
92#define PC_ROAM_RSSI_UNKNOWN ((int8_t)-128)
93
94/**
95 * @brief Parse a sequence of 802.11k Neighbor Report elements into candidate APs.
96 *
97 * @p elems is the element list an 802.11k Radio Measurement Neighbor Report Response action frame carries
98 * (the caller strips the action header first). Each Neighbor Report element (id 52) supplies a candidate's
99 * BSSID and operating channel; other element ids are skipped. The report does not carry RSSI, so each
100 * candidate's @c rssi_dbm is set to @ref PC_ROAM_RSSI_UNKNOWN for the caller to fill after measuring, then
101 * feed the list to @ref pc_roam_decide.
102 * @param out receives up to @p max candidates.
103 * @return the number of candidates parsed (0..@p max).
104 */
105uint8_t pc_roam_parse_neighbor_report(const uint8_t *elems, size_t len, pc_roam_neighbor *out, uint8_t max);
106
107// 802.11v BSS Transition Management Request (WNM action frame).
108#define PC_ROAM_WNM_CATEGORY 0x0A ///< WNM action category
109#define PC_ROAM_BTM_REQ_ACTION 0x07 ///< BSS Transition Management Request action code
110#define PC_ROAM_BTM_PREF_LIST 0x01u ///< Request Mode bit 0: a preferred candidate list is included
111#define PC_ROAM_BTM_DISASSOC 0x04u ///< Request Mode bit 2: disassociation imminent
112#define PC_ROAM_BTM_TERM_INCL 0x08u ///< Request Mode bit 3: BSS Termination Duration is included
113#define PC_ROAM_BTM_ESS_DISASSOC 0x10 ///< Request Mode bit 4: an ESS-disassoc Session Info URL is included
114
115/**
116 * @brief Parse an 802.11v BSS Transition Management Request action frame into a @ref pc_roam_btm hint.
117 *
118 * @p frame starts at the action-frame Category octet (WNM category 0x0A, BTM-Request action 0x07). The
119 * Request Mode flags set @c disassoc_imminent (bit 2); when the preferred-candidate-list bit (bit 0) is
120 * set, the highest-preference candidate's BSSID (the first Neighbor Report element in the list, decoded
121 * past the optional BSS Termination Duration and Session Information URL) becomes @c preferred_bssid. The
122 * result feeds @ref pc_roam_decide.
123 * @return true iff @p frame is a well-formed BTM Request; false otherwise (@p out is cleared).
124 */
125bool pc_roam_parse_btm_request(const uint8_t *frame, size_t len, pc_roam_btm *out);
126
127#endif // PC_ENABLE_ROAMING
128#endif // PROTOCORE_ROAMING_H
User-facing configuration for ProtoCore.