Skip to content

hal::wifi

Wi-Fi station abstraction — the contract both radio backends satisfy.

FlintOS is growing two ways to be a Wi-Fi station, and this is the seam between them and the application:

  • The blob backend (radio/esp32): Espressif’s binary radio driver plus a supplicant. It works only on the ESP32 and pulls in vendor binaries, so it is an opt-in (“unsafe”) build.
  • The pure-Rust backend (later, in drivers/): a first-party supplicant over crypto and a radio driver trait, portable to any FlintOS target.

Both implement [Station]. An application says “join this network” against the trait and does not know or care which backend answers — exactly as a logical driver talks to Bus without knowing which controller is underneath. The trait lives in hal for that reason: hal is contracts and nothing else, and api re-exports it so a driver crate (which may name api but not hal) can still implement it.

Associating is not a function that returns when it is done — it is a request, and the answer arrives later. [Station::connect] returns as soon as the attempt is accepted; whether it succeeded comes back as a [StationEvent::Connected] or [StationEvent::Disconnected] through the handler the caller registered. Scanning is the same. This matches how the driver actually behaves and avoids pretending a two-second, failure-prone radio exchange is a blocking call.

struct ApInfo {
pub ssid: Ssid,
pub bssid: Bssid,
pub channel: u8,
pub rssi: i8,
pub security: Security,
}

One access point a scan turned up.

Implements: Clone, Copy, Debug

References: Bssid, Security, Ssid

struct ConnectRequest<'a> {
pub ssid: Ssid,
pub security: Security,
pub credentials: Credentials<'a>,
pub bssid: Option<Bssid>,
pub channel: Option<u8>,
}

What to connect to.

Implements: Clone, Copy

References: Bssid, Credentials, Security, Ssid

struct ScanRequest {
pub ssid: Option<Ssid>,
pub channel: Option<u8>,
pub passive: bool,
}

How to scan.

Implements: Clone, Copy, Default

References: Ssid

struct Ssid { /* private fields */ }

A network name.

Held as bytes and a length, not a string, because an SSID is octets and is not required to be UTF-8 — a station that could only join UTF-8 networks would be quietly broken in parts of the world.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

struct StationStatus {
pub state: StationState,
pub ssid: Option<Ssid>,
pub bssid: Option<Bssid>,
pub channel: u8,
pub rssi: i8,
pub security: Security,
}

A live view of the station, read synchronously at any time.

Implements: Clone, Copy, Debug

References: Bssid, Security, Ssid, StationState

enum Credentials<'a> {
None,
Passphrase(&'a [u8]),
Psk([u8; 32]),
}

What a station presents to join a network.

Borrowed rather than owned: a passphrase lives in the caller’s memory for the moment of the connect call and should not be copied into a long-lived struct that outlives its usefulness.

Implements: Clone, Copy

enum DisconnectReason {
Unspecified,
NoApFound,
AuthFailed,
AssocFailed,
FourWayTimeout,
HandshakeTimeout,
ConnectionLost,
AssocExpired,
NotAssociated,
Requested,
}

Why a station is not, or is no longer, connected.

A generalisation of the reason codes every driver reports, kept small and meaningful rather than mirroring 802.11’s fifty-odd. The distinction that matters to an application is whose fault and whether retrying helps: a wrong passphrase (AuthFailed) will never succeed on retry, a missing AP (NoApFound) might.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

enum Security {
Open,
Wpa2Psk,
Wpa3Sae,
Wpa2Wpa3Psk,
}

The security a network uses, or a station asks for.

Deliberately small to start: the personal (pre-shared-key) modes that cover the overwhelming majority of networks. WPA3-only, enterprise/EAP and the legacy WEP modes are named as future work rather than half-supported — a security enum that lists a mode it cannot actually join is a lie the application would act on.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

enum StationEvent {
ScanDone { count: u16 },
Connected { bssid: Bssid, channel: u8 },
Disconnected { reason: DisconnectReason },
}

Something the station is telling the application about.

Delivered through the handler registered with [Station::set_event_handler], on whatever task the backend dispatches events on — never in an interrupt. Small and Copy so it can cross that boundary by value.

Implements: Clone, Copy, Debug

References: Bssid, DisconnectReason

enum StationState {
Disconnected,
Connecting,
Connected,
}

Where the station is in the connect lifecycle.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

enum WifiError {
NotReady,
BadCredentials,
Busy,
NotConnected,
Backend(i32),
}

Why a station operation could not even be started.

Distinct from [DisconnectReason]: these are refusals at the call, before the radio does anything — a malformed request, or the wrong state. The outcome of an accepted request arrives as a [StationEvent], not here.

Implements: Clone, Copy, Debug, Display, Eq, From, PartialEq, StructuralPartialEq

trait Station {
fn scan(&mut self, request: &ScanRequest) -> WifiResult<()>
fn scan_results(&mut self, out: &mut [ApInfo]) -> WifiResult<usize>
fn connect(&mut self, request: &ConnectRequest<'_>) -> WifiResult<()>
fn disconnect(&mut self) -> WifiResult<()>
fn status(&self) -> StationStatus
fn set_event_handler(&mut self, handler: Option<EventHandler>) -> Option<EventHandler>
}

A Wi-Fi station: the operations an application performs to be on a network.

Every method that involves the air is initiating, not completing — see the module docs. A backend implements this; an application holds a &mut dyn Station and does not know which one.

const SSID_MAX: usize;

The largest an SSID may be: 32 octets (IEEE 802.11).

type Bssid = [u8; 6];

A BSSID — the MAC address of an access point.

type EventHandler = fn(..);

The handler an application registers to hear [StationEvent]s.

A plain fn, not a closure, because it runs on the backend’s event task and must carry no borrowed environment across that boundary — the same constraint the existing radio event dispatch has.

type WifiResult<T> = Result<T, WifiError>;

The result of a station operation.

References: WifiError