/* * wifi.h * Saved Wi-Fi networks (0:/config/wifi.toml) and small formatting helpers * shared by the desktop panel, the Network app and the wifi command. * * The file looks like this: * * [wifi] * autoconnect = true * * [network.0] * ssid = "Home" * psk = "passphrase" * * The passphrase is stored as typed, because that is what the join needs: * the kernel derives the PMK from it (or takes a 64-character hex string as * a raw PSK). Anyone who can read 0:/config can read the keys. * * Copyright (c) 2026 Daniel Hammer */ #pragma once #include #include #include namespace montauk { namespace wifi { // The scan table the kernel keeps is 64 entries; saving that many networks // is already far more than a laptop accumulates. static constexpr int MAX_SAVED = 32; static constexpr int SSID_CAP = 36; static constexpr int PSK_CAP = 72; // 64-character hex PSK plus NUL struct SavedNetwork { char ssid[SSID_CAP]; char psk[PSK_CAP]; }; struct SavedList { SavedNetwork items[MAX_SAVED]; int count; bool autoconnect; }; // ---- helpers ----------------------------------------------------------- inline void copy_str(char* dst, int cap, const char* src) { int i = 0; for (; src && src[i] && i < cap - 1; i++) dst[i] = src[i]; dst[i] = '\0'; } // "network.." inline void network_key(char* out, int cap, int index, const char* field) { char idx[8]; int n = 0; if (index == 0) { idx[n++] = '0'; } else { char tmp[8]; int t = 0; for (int v = index; v > 0 && t < (int)sizeof(tmp); v /= 10) tmp[t++] = (char)('0' + (v % 10)); while (t > 0) idx[n++] = tmp[--t]; } idx[n] = '\0'; int p = 0; const char* prefix = "network."; while (*prefix && p < cap - 1) out[p++] = *prefix++; for (int i = 0; i < n && p < cap - 1; i++) out[p++] = idx[i]; if (p < cap - 1) out[p++] = '.'; while (field && *field && p < cap - 1) out[p++] = *field++; out[p] = '\0'; } // ---- load / store ------------------------------------------------------ inline void saved_load(SavedList* out) { if (!out) return; out->count = 0; out->autoconnect = true; auto doc = montauk::config::load("wifi"); out->autoconnect = doc.get_bool("wifi.autoconnect", true); for (int i = 0; i < MAX_SAVED; i++) { char key[64]; network_key(key, sizeof(key), i, "ssid"); const char* ssid = doc.get_string(key, nullptr); if (!ssid || !ssid[0]) break; // entries are written contiguously network_key(key, sizeof(key), i, "psk"); const char* psk = doc.get_string(key, ""); SavedNetwork& n = out->items[out->count++]; copy_str(n.ssid, SSID_CAP, ssid); copy_str(n.psk, PSK_CAP, psk); } doc.destroy(); } // Rewrite the file from the list. Returns 0 on success. inline int saved_store(const SavedList* list) { if (!list) return -1; montauk::toml::Doc doc; doc.init(); montauk::config::set_bool(&doc, "wifi.autoconnect", list->autoconnect); for (int i = 0; i < list->count && i < MAX_SAVED; i++) { char key[64]; network_key(key, sizeof(key), i, "ssid"); montauk::config::set_string(&doc, key, list->items[i].ssid); network_key(key, sizeof(key), i, "psk"); montauk::config::set_string(&doc, key, list->items[i].psk); } int rc = montauk::config::save("wifi", &doc); doc.destroy(); return rc; } inline int saved_index_of(const SavedList* list, const char* ssid) { if (!list || !ssid) return -1; for (int i = 0; i < list->count; i++) { if (montauk::streq(list->items[i].ssid, ssid)) return i; } return -1; } inline const SavedNetwork* saved_find(const SavedList* list, const char* ssid) { int idx = saved_index_of(list, ssid); return idx < 0 ? nullptr : &list->items[idx]; } // Add or update an entry in memory. Returns false when the list is full. inline bool saved_set(SavedList* list, const char* ssid, const char* psk) { if (!list || !ssid || !ssid[0]) return false; int idx = saved_index_of(list, ssid); if (idx < 0) { if (list->count >= MAX_SAVED) return false; idx = list->count++; copy_str(list->items[idx].ssid, SSID_CAP, ssid); } copy_str(list->items[idx].psk, PSK_CAP, psk ? psk : ""); return true; } inline bool saved_remove(SavedList* list, const char* ssid) { int idx = saved_index_of(list, ssid); if (idx < 0) return false; for (int i = idx; i < list->count - 1; i++) list->items[i] = list->items[i + 1]; list->count--; return true; } // Convenience wrappers that touch the file directly. inline bool remember(const char* ssid, const char* psk) { SavedList list; saved_load(&list); if (!saved_set(&list, ssid, psk)) return false; return saved_store(&list) == 0; } inline bool forget(const char* ssid) { SavedList list; saved_load(&list); if (!saved_remove(&list, ssid)) return false; return saved_store(&list) == 0; } // Copy the saved passphrase for `ssid` into out. False when not saved. inline bool lookup(const char* ssid, char* out, int cap) { SavedList list; saved_load(&list); const SavedNetwork* n = saved_find(&list, ssid); if (!n) return false; copy_str(out, cap, n->psk); return true; } // ---- presentation ------------------------------------------------------ inline const char* security_name(uint8_t security) { switch (security) { case montauk::abi::WIFI_SEC_OPEN: return "Open"; case montauk::abi::WIFI_SEC_WEP: return "WEP"; case montauk::abi::WIFI_SEC_WPA: return "WPA"; case montauk::abi::WIFI_SEC_WPA2: return "WPA2"; case montauk::abi::WIFI_SEC_WPA3: return "WPA3"; default: return "Unknown"; } } inline bool needs_key(uint8_t security) { return security != montauk::abi::WIFI_SEC_OPEN; } // 0-4 bars from an RSSI in dBm. inline int signal_bars(int8_t rssi) { if (rssi >= -55) return 4; if (rssi >= -67) return 3; if (rssi >= -75) return 2; if (rssi >= -85) return 1; return 0; } inline const char* state_name(uint8_t state) { switch (state) { case montauk::abi::WIFI_STATE_ABSENT: return "No adapter"; case montauk::abi::WIFI_STATE_DETECTED: return "Loading firmware"; case montauk::abi::WIFI_STATE_BOOTING: return "Starting"; case montauk::abi::WIFI_STATE_RUNNING: return "Ready"; case montauk::abi::WIFI_STATE_ERROR: return "Adapter error"; case montauk::abi::WIFI_STATE_RFKILL: return "Radio off"; default: return "Unknown"; } } // What the join is doing right now, for a progress line. inline const char* conn_state_name(uint32_t connState) { switch (connState) { case montauk::abi::WIFI_CONN_IDLE: return "Not connected"; case montauk::abi::WIFI_CONN_CONTEXTS_UP: return "Preparing radio..."; case montauk::abi::WIFI_CONN_AUTHENTICATING: return "Authenticating..."; case montauk::abi::WIFI_CONN_AUTHENTICATED: return "Authenticated"; case montauk::abi::WIFI_CONN_ASSOCIATING: return "Associating..."; case montauk::abi::WIFI_CONN_ASSOCIATED: return "Associated"; case montauk::abi::WIFI_CONN_HANDSHAKING: return "Exchanging keys..."; case montauk::abi::WIFI_CONN_CONNECTED: return "Connected"; case montauk::abi::WIFI_CONN_FAILED: return "Connection failed"; default: return ""; } } inline const char* error_message(int err) { switch (err) { case 0: return ""; case montauk::abi::WIFI_ERR_NO_ADAPTER: return "No Wi-Fi adapter is ready"; case montauk::abi::WIFI_ERR_NOT_FOUND: return "That network is out of range"; case montauk::abi::WIFI_ERR_NEED_KEY: return "This network needs a password"; case montauk::abi::WIFI_ERR_UNSUPPORTED: return "This security type is not supported"; case montauk::abi::WIFI_ERR_AUTH: return "Wrong password"; case montauk::abi::WIFI_ERR_TIMEOUT: return "The network did not respond"; default: return "Could not join the network"; } } } // namespace wifi } // namespace montauk