fix(network): read AP security flags in WiFi scan and unit-test parseAccessPoint

parseAccessPoint() is extracted from an anonymous namespace so the pure
property->AccessPointInfo mapping is testable. getAccessPoints() now reads
the Flags/WpaFlags/RsnFlags properties (they were never read, so every
network was reported Open and connections lacked a security section) and
fixes the double container-enter on the SSID 'ay' read that silently
emptied the scan list.
This commit is contained in:
NeilClack
2026-08-12 12:00:32 -05:00
parent 666a4eca97
commit 442565508e
6 changed files with 164 additions and 42 deletions
+54 -37
View File
@@ -1,6 +1,7 @@
#include "nm_device.h"
#include "nm_connection.h"
#include "logger/logger.h"
#include <cstring>
#include <iostream>
#include <stdexcept>
@@ -13,8 +14,9 @@ namespace radiantwave::network {
// ─── Access Point Helper ───────────────────────────────────────────────
namespace {
// Build an AccessPointInfo from raw D-Bus AccessPoint property values (see
// nm_device.h). The `ssid` argument carries the raw SSID bytes decoded from
// the AccessPoint.Ssid `ay` property as a std::string.
AccessPointInfo parseAccessPoint(
const std::string& object_path,
const std::string& ssid,
@@ -36,8 +38,6 @@ AccessPointInfo parseAccessPoint(
return ap;
}
} // anonymous namespace
// ─── WifiDevice Implementation ─────────────────────────────────────────
#ifdef __linux__
@@ -229,13 +229,40 @@ std::vector<AccessPointInfo> WifiDevice::getAccessPoints() const {
return result;
}
// Read a single numeric AccessPoint property. Strength is a byte ("y"),
// the security flag properties are uint32 ("u"). Leaves `out` untouched on
// failure, so callers can rely on zero-initialized defaults.
auto readApProperty = [this](const char* ap_path, const char* prop,
const char* type, void* out) {
sd_bus_message* prop_msg = nullptr;
sd_bus_error prop_error = SD_BUS_ERROR_NULL;
int pr = sd_bus_get_property(
impl_->bus,
"org.freedesktop.NetworkManager",
ap_path,
"org.freedesktop.NetworkManager.AccessPoint",
prop,
&prop_error,
&prop_msg,
type
);
if (pr >= 0 && prop_msg) {
if (strcmp(type, "u") == 0) {
sd_bus_message_read(prop_msg, "u", static_cast<uint32_t*>(out));
} else {
sd_bus_message_read(prop_msg, "y", static_cast<uint8_t*>(out));
}
sd_bus_message_unref(prop_msg);
}
sd_bus_error_free(&prop_error);
return pr >= 0;
};
const char* ap_path = nullptr;
while ((r = sd_bus_message_read(msg, "o", &ap_path)) > 0 && ap_path) {
// Get AP properties
sd_bus_message* ap_msg = nullptr;
sd_bus_error ap_error = SD_BUS_ERROR_NULL;
// Get all properties at once
r = sd_bus_get_property(
impl_->bus,
"org.freedesktop.NetworkManager",
@@ -246,51 +273,41 @@ std::vector<AccessPointInfo> WifiDevice::getAccessPoints() const {
&ap_msg,
"ay"
);
if (r >= 0) {
// Read SSID byte array
sd_bus_message_enter_container(ap_msg, 'a', "y");
// Read the SSID byte array. sd_bus_get_property() already leaves
// the message positioned at the array (inside the variant wrapper),
// and sd_bus_message_read_array() enters AND exits the array
// container itself. Do NOT call sd_bus_message_enter_container()
// here first, or the read fails and every network silently drops
// out of the scan list (it only ever shows up as an empty page with
// no error logs).
const uint8_t* ssid_data = nullptr;
size_t ssid_len = 0;
r = sd_bus_message_read_array(ap_msg, 'y', (const void**)&ssid_data, &ssid_len);
std::string ssid;
if (r >= 0 && ssid_data && ssid_len > 0) {
ssid = std::string(reinterpret_cast<const char*>(ssid_data), ssid_len);
}
sd_bus_message_unref(ap_msg);
// Get signal strength
sd_bus_message* sig_msg = nullptr;
sd_bus_error sig_error = SD_BUS_ERROR_NULL;
// Remaining properties: signal strength plus the three flags that
// drive detectSecurityType() (otherwise every network is reported
// as Open and secured connections are built without a security
// section).
uint8_t signal = 0;
r = sd_bus_get_property(
impl_->bus,
"org.freedesktop.NetworkManager",
ap_path,
"org.freedesktop.NetworkManager.AccessPoint",
"Strength",
&sig_error,
&sig_msg,
"y"
);
if (r >= 0) {
sd_bus_message_read(sig_msg, "y", &signal);
sd_bus_message_unref(sig_msg);
}
sd_bus_error_free(&sig_error);
// Get flags
uint32_t flags = 0, wpa_flags = 0, rsn_flags = 0;
// ... (would need similar property reads for flags)
readApProperty(ap_path, "Strength", "y", &signal);
readApProperty(ap_path, "Flags", "u", &flags);
readApProperty(ap_path, "WpaFlags", "u", &wpa_flags);
readApProperty(ap_path, "RsnFlags", "u", &rsn_flags);
if (!ssid.empty()) {
result.push_back(parseAccessPoint(ap_path, ssid, signal, flags, wpa_flags, rsn_flags));
}
}
sd_bus_error_free(&ap_error);
}