refactor #1 + adding documentation (#36)
Build and publish release / metadata (push) Successful in 44s
Build and publish release / create-release (push) Successful in 49s
Build and publish release / publish-release (push) Canceled after 0s
Build and publish release / build (push) Canceled after 7m44s

Reviewed-on: #36
Co-authored-by: Koda YeenBean <n122330@gmail.com>
This commit was merged in pull request #36.
This commit is contained in:
2026-07-19 21:26:28 +01:00
committed by Koda
parent db81260d7f
commit 3fcd99af1f
31 changed files with 4068 additions and 2598 deletions
+229
View File
@@ -0,0 +1,229 @@
/*
* Shared translations and validators used by Wi-Fi provisioning and
* telemetry. This unit does not commit controller configuration itself.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <uci.h>
#include <json-c/json.h>
#include "wlan.h"
#include "ufmodel.h"
#include "crypto.h"
#include "wlan_internal.h"
const char *wlan_security_to_uci(const char *uf)
{
if (!uf || !strcmp(uf,"open")) return "none";
if (!strcmp(uf,"wpapsk")) return "psk";
if (!strcmp(uf,"wpa2psk")) return "psk2";
if (!strcmp(uf,"wpapskwpa2psk")) return "psk-mixed";
if (!strcmp(uf,"wpa3")) return "sae";
if (!strcmp(uf,"wpa3transition")) return "sae-mixed";
if (!strcmp(uf,"wpa2enterprise")) return "wpa2";
if (!strcmp(uf,"wpa3enterprise")) return "wpa3";
return "psk2"; /* default */
}
/* OpenWrt UCI encryption names to UniFi security names. */
const char *wlan_security_to_unifi(const char *uci)
{
if (!uci || !strcmp(uci,"none")) return "open";
if (!strcmp(uci,"psk")) return "wpapsk";
if (!strcmp(uci,"psk2")) return "wpa2psk";
if (!strcmp(uci,"psk-mixed")) return "wpapskwpa2psk";
if (!strcmp(uci,"sae")) return "wpa3";
if (!strcmp(uci,"sae-mixed")) return "wpa3transition";
if (!strcmp(uci,"wpa2")) return "wpa2enterprise";
if (!strcmp(uci,"wpa3")) return "wpa3enterprise";
return "wpa2psk";
}
/* Return true only for the 24-character hexadecimal IDs used by UniFi. */
int wlan_valid_object_id(const char *id)
{
if (!id || strlen(id) != 24)
return 0;
for (size_t i = 0; i < 24; i++)
if (!((id[i] >= '0' && id[i] <= '9') ||
(id[i] >= 'a' && id[i] <= 'f') ||
(id[i] >= 'A' && id[i] <= 'F')))
return 0;
return 1;
}
/*
* UniFi uses a 24-character hexadecimal MongoDB ObjectId to associate VAP
* telemetry and stations with a WLAN. Older legacy system_cfg payloads do
* not include that ID, so allocate a local one and keep it stable in UCI.
* All radio instances of the same WLAN must report the same ID.
*/
int wlan_ensure_vap_ids(struct json_object *vaps)
{
if (!vaps || !json_object_is_type(vaps, json_type_array))
return 0;
struct uci_context *ctx = uci_alloc_context();
struct uci_package *pkg = NULL;
if (ctx)
uci_load(ctx, "wireless", &pkg);
int count = json_object_array_length(vaps);
for (int i = 0; i < count; i++) {
struct json_object *vap = json_object_array_get_idx(vaps, i);
struct json_object *value;
if (!vap)
continue;
const char *id = NULL;
const char *id_keys[] = { "_id", "id", "wlanconf_id" };
for (size_t n = 0; n < sizeof(id_keys) / sizeof(id_keys[0]); n++) {
if (json_object_object_get_ex(vap, id_keys[n], &value)) {
const char *candidate = json_object_get_string(value);
if (wlan_valid_object_id(candidate)) {
id = candidate;
break;
}
}
}
if (id)
continue;
const char *ssid = "";
if (json_object_object_get_ex(vap, "essid", &value))
ssid = json_object_get_string(value);
/* Reuse an ID already assigned to this WLAN in the same payload. */
for (int j = 0; j < i && !id; j++) {
struct json_object *previous = json_object_array_get_idx(vaps, j);
struct json_object *previous_ssid;
struct json_object *previous_id;
if (previous &&
json_object_object_get_ex(previous, "essid", &previous_ssid) &&
!strcmp(json_object_get_string(previous_ssid), ssid) &&
json_object_object_get_ex(previous, "id", &previous_id) &&
wlan_valid_object_id(json_object_get_string(previous_id)))
id = json_object_get_string(previous_id);
}
/* Reuse the ID committed by an earlier provisioning cycle. */
if (!id && pkg) {
struct uci_element *element;
uci_foreach_element(&pkg->sections, element) {
struct uci_section *section = uci_to_section(element);
const char *stored_ssid;
const char *stored_id;
if (strcmp(section->type, "wifi-iface") ||
strncmp(section->e.name, "openuf_", 7))
continue;
stored_ssid = uci_lookup_option_string(ctx, section, "ssid");
stored_id = uci_lookup_option_string(ctx, section,
"openuf_vap_id");
if (stored_ssid && !strcmp(stored_ssid, ssid) &&
wlan_valid_object_id(stored_id)) {
id = stored_id;
break;
}
}
}
char generated[25];
if (!id) {
if (crypto_random_hex((unsigned char *)generated, 12) != 0) {
printf("[openuf] Failed to generate a VAP ID for '%s'\n", ssid);
if (pkg) uci_unload(ctx, pkg);
if (ctx) uci_free_context(ctx);
return -1;
}
id = generated;
printf("[openuf] Generated persistent VAP ID %s for '%s'\n",
id, ssid);
}
json_object_object_add(vap, "id", json_object_new_string(id));
json_object_object_add(vap, "wlanconf_id", json_object_new_string(id));
}
if (pkg) uci_unload(ctx, pkg);
if (ctx) uci_free_context(ctx);
return 0;
}
/* Build one stable 802.11r mobility domain shared by every AP for an SSID. */
void wlan_mobility_domain_for_ssid(const char *ssid, char out[5])
{
unsigned int hash = 2166136261u;
const unsigned char *p = (const unsigned char *)(ssid ? ssid : "");
while (*p) {
hash ^= *p++;
hash *= 16777619u;
}
snprintf(out, 5, "%04x", (hash ^ (hash >> 16)) & 0xffffu);
}
/* Return true when an OpenWrt radio is backed by the ath9k kernel driver. */
int wlan_radio_uses_ath9k(const char *device_name)
{
int phy_index;
if (!device_name || sscanf(device_name, "radio%d", &phy_index) != 1)
return 0;
char path[128];
char target[256];
snprintf(path, sizeof(path),
"/sys/class/ieee80211/phy%d/device/driver", phy_index);
ssize_t length = readlink(path, target, sizeof(target) - 1);
if (length < 0)
return 0;
target[length] = '\0';
return strstr(target, "ath9k") != NULL;
}
/* Read a UniFi boolean while accepting names used by controller versions. */
int wlan_json_boolean_any(struct json_object *object,
const char *const *keys, size_t key_count)
{
struct json_object *value;
for (size_t i = 0; i < key_count; i++) {
if (!json_object_object_get_ex(object, keys[i], &value))
continue;
if (json_object_is_type(value, json_type_string)) {
const char *text = json_object_get_string(value);
if (!text || !text[0] || !strcasecmp(text, "disabled") ||
!strcasecmp(text, "false") || !strcasecmp(text, "off") ||
!strcasecmp(text, "none") || !strcmp(text, "0"))
return 0;
/* Also accepts controller modes such as "prefer_5g". */
return 1;
}
return json_object_get_boolean(value) ? 1 : 0;
}
return 0;
}
/* Interpret Boolean text and UniFi feature modes such as "prefer_5g". */
int wlan_feature_enabled(const char *text)
{
return text && text[0] && strcasecmp(text, "disabled") &&
strcasecmp(text, "false") && strcasecmp(text, "off") &&
strcasecmp(text, "none") && strcmp(text, "0");
}
/* Safe UCI identifier fragment (maximum 15 characters). */
void wlan_safe_section_name(const char *ssid, char *out, size_t sz)
{
size_t j = 0;
for (size_t i = 0; ssid[i] && j < sz-1 && j < 15; i++) {
char c = ssid[i];
if ((c>='a'&&c<='z')||(c>='A'&&c<='Z')||
(c>='0'&&c<='9')||c=='_')
out[j++] = c;
else
out[j++] = '_';
}
out[j] = '\0';
}