262 lines
9.3 KiB
C++
262 lines
9.3 KiB
C++
/*
|
|
* 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.
|
|
*
|
|
* No default copy of this file ships in the image, deliberately. Every other
|
|
* config an app writes (bluetooth.toml, display.toml, session.toml) is
|
|
* created on demand for the same reason: a shipped copy is laid down again
|
|
* by anything that refreshes the system files, and it would overwrite the
|
|
* networks the user had saved.
|
|
*
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#pragma once
|
|
#include <montauk/config.h>
|
|
#include <montauk/string.h>
|
|
#include <montauk/syscall.h>
|
|
|
|
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.<index>.<field>"
|
|
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
|