Updated WiFiDevice::getActiveAccessPoint() to read the AP's SSID so connectedSSID() can report which network is in use.
548 lines
16 KiB
C++
548 lines
16 KiB
C++
#include "nm_device.h"
|
|
#include "nm_connection.h"
|
|
#include "logger/logger.h"
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
// Only include sdbus on Linux
|
|
#ifdef __linux__
|
|
#include <systemd/sd-bus.h>
|
|
#endif
|
|
|
|
namespace radiantwave::network {
|
|
|
|
// ─── Access Point Helper ───────────────────────────────────────────────
|
|
|
|
// 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,
|
|
uint8_t signal,
|
|
uint32_t flags,
|
|
uint32_t wpa_flags,
|
|
uint32_t rsn_flags
|
|
) {
|
|
AccessPointInfo ap;
|
|
ap.object_path = object_path;
|
|
ap.ssid = ssid;
|
|
ap.ssid_bytes = ssidToBytes(ssid);
|
|
ap.signal_strength = signal;
|
|
ap.flags = flags;
|
|
ap.wpa_flags = wpa_flags;
|
|
ap.rsn_flags = rsn_flags;
|
|
ap.security = detectSecurityType(flags, wpa_flags, rsn_flags);
|
|
|
|
return ap;
|
|
}
|
|
|
|
// ─── WifiDevice Implementation ─────────────────────────────────────────
|
|
|
|
#ifdef __linux__
|
|
|
|
struct WifiDevice::Impl {
|
|
std::string object_path;
|
|
sd_bus* bus = nullptr;
|
|
|
|
explicit Impl(const std::string& path) : object_path(path) {
|
|
int r = sd_bus_open_system(&bus);
|
|
if (r < 0) {
|
|
throw std::runtime_error("Failed to open system bus: " + std::string(strerror(-r)));
|
|
}
|
|
}
|
|
|
|
~Impl() {
|
|
if (bus) {
|
|
sd_bus_unref(bus);
|
|
}
|
|
}
|
|
|
|
// Helper: call a D-Bus method and get response
|
|
sd_bus_message* callMethod(
|
|
const char* dest,
|
|
const char* path,
|
|
const char* iface,
|
|
const char* member,
|
|
const char* type,
|
|
...
|
|
) {
|
|
sd_bus_message* msg = nullptr;
|
|
sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
|
|
int r = sd_bus_call_method(
|
|
bus,
|
|
dest,
|
|
path,
|
|
iface,
|
|
member,
|
|
&error,
|
|
&msg,
|
|
type
|
|
);
|
|
|
|
if (r < 0) {
|
|
radiantwave::logger::Logger::instance().warning("WiFi",
|
|
std::string("D-Bus call failed: ") +
|
|
(error.message ? error.message : strerror(-r)));
|
|
sd_bus_error_free(&error);
|
|
return nullptr;
|
|
}
|
|
|
|
sd_bus_error_free(&error);
|
|
return msg;
|
|
}
|
|
|
|
// Helper: get a property value
|
|
sd_bus_message* getProperty(
|
|
const char* dest,
|
|
const char* path,
|
|
const char* iface,
|
|
const char* prop,
|
|
const char* type
|
|
) {
|
|
sd_bus_message* msg = nullptr;
|
|
sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
|
|
int r = sd_bus_get_property(
|
|
bus,
|
|
dest,
|
|
path,
|
|
iface,
|
|
prop,
|
|
&error,
|
|
&msg,
|
|
type
|
|
);
|
|
|
|
if (r < 0) {
|
|
// Transient D-Bus errors (e.g. a reply raced with a state change
|
|
// while NetworkManager churns the device during a failed auth)
|
|
// must never throw out of here — callers treat a null reply as
|
|
// "unknown", which keeps the UI and wait loops running.
|
|
radiantwave::logger::Logger::instance().warning("WiFi",
|
|
"D-Bus property get failed on " + std::string(path) + "." +
|
|
std::string(prop) + ": " +
|
|
(error.message ? error.message : strerror(-r)));
|
|
sd_bus_error_free(&error);
|
|
return nullptr;
|
|
}
|
|
|
|
sd_bus_error_free(&error);
|
|
return msg;
|
|
}
|
|
};
|
|
|
|
WifiDevice::WifiDevice(const std::string& object_path)
|
|
: impl_(std::make_unique<Impl>(object_path))
|
|
{
|
|
}
|
|
|
|
WifiDevice::~WifiDevice() = default;
|
|
|
|
WifiDevice::WifiDevice(WifiDevice&& other) noexcept = default;
|
|
WifiDevice& WifiDevice::operator=(WifiDevice&& other) noexcept = default;
|
|
|
|
std::string WifiDevice::getInterfaceName() const {
|
|
sd_bus_message* msg = impl_->getProperty(
|
|
"org.freedesktop.NetworkManager",
|
|
impl_->object_path.c_str(),
|
|
"org.freedesktop.NetworkManager.Device",
|
|
"Interface",
|
|
"s"
|
|
);
|
|
|
|
if (!msg) {
|
|
return "";
|
|
}
|
|
|
|
const char* iface = nullptr;
|
|
sd_bus_message_read(msg, "s", &iface);
|
|
std::string result = iface ? iface : "";
|
|
sd_bus_message_unref(msg);
|
|
|
|
return result;
|
|
}
|
|
|
|
uint32_t WifiDevice::getDeviceState() const {
|
|
sd_bus_message* msg = impl_->getProperty(
|
|
"org.freedesktop.NetworkManager",
|
|
impl_->object_path.c_str(),
|
|
"org.freedesktop.NetworkManager.Device",
|
|
"State",
|
|
"u"
|
|
);
|
|
|
|
if (!msg) {
|
|
// Unknown state keeps callers polling/falling back gracefully instead
|
|
// of aborting on a transient read failure.
|
|
return nm_device_state::Unknown;
|
|
}
|
|
|
|
uint32_t state = 0;
|
|
sd_bus_message_read(msg, "u", &state);
|
|
sd_bus_message_unref(msg);
|
|
|
|
return state;
|
|
}
|
|
|
|
std::string WifiDevice::getObjectPath() const {
|
|
return impl_->object_path;
|
|
}
|
|
|
|
bool WifiDevice::requestScan() {
|
|
sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
|
|
int r = sd_bus_call_method(
|
|
impl_->bus,
|
|
"org.freedesktop.NetworkManager",
|
|
impl_->object_path.c_str(),
|
|
"org.freedesktop.NetworkManager.Device.Wireless",
|
|
"RequestScan",
|
|
&error,
|
|
nullptr,
|
|
"a{sv}",
|
|
0 // Empty options dict
|
|
);
|
|
|
|
if (r < 0) {
|
|
radiantwave::logger::Logger::instance().warning("WiFi",
|
|
"RequestScan failed on device " + impl_->object_path + ": " +
|
|
(error.message ? error.message : strerror(-r)));
|
|
}
|
|
|
|
sd_bus_error_free(&error);
|
|
return r >= 0;
|
|
}
|
|
|
|
std::vector<AccessPointInfo> WifiDevice::getAccessPoints() const {
|
|
std::vector<AccessPointInfo> result;
|
|
|
|
// Get access point paths
|
|
sd_bus_message* msg = nullptr;
|
|
sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
|
|
int r = sd_bus_get_property(
|
|
impl_->bus,
|
|
"org.freedesktop.NetworkManager",
|
|
impl_->object_path.c_str(),
|
|
"org.freedesktop.NetworkManager.Device.Wireless",
|
|
"AccessPoints",
|
|
&error,
|
|
&msg,
|
|
"ao"
|
|
);
|
|
|
|
if (r < 0) {
|
|
radiantwave::logger::Logger::instance().warning("WiFi",
|
|
"Failed to read AccessPoints on device " + impl_->object_path + ": " +
|
|
(error.message ? error.message : strerror(-r)));
|
|
sd_bus_error_free(&error);
|
|
return result;
|
|
}
|
|
|
|
// Read array of object paths
|
|
r = sd_bus_message_enter_container(msg, 'a', "o");
|
|
if (r < 0) {
|
|
sd_bus_message_unref(msg);
|
|
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) {
|
|
sd_bus_message* ap_msg = nullptr;
|
|
sd_bus_error ap_error = SD_BUS_ERROR_NULL;
|
|
|
|
r = sd_bus_get_property(
|
|
impl_->bus,
|
|
"org.freedesktop.NetworkManager",
|
|
ap_path,
|
|
"org.freedesktop.NetworkManager.AccessPoint",
|
|
"Ssid",
|
|
&ap_error,
|
|
&ap_msg,
|
|
"ay"
|
|
);
|
|
|
|
if (r >= 0) {
|
|
// 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);
|
|
|
|
// 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;
|
|
uint32_t flags = 0, wpa_flags = 0, rsn_flags = 0;
|
|
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);
|
|
}
|
|
|
|
sd_bus_message_exit_container(msg);
|
|
sd_bus_message_unref(msg);
|
|
|
|
return result;
|
|
}
|
|
|
|
AccessPointInfo WifiDevice::getActiveAccessPoint() const {
|
|
sd_bus_message* msg = impl_->getProperty(
|
|
"org.freedesktop.NetworkManager",
|
|
impl_->object_path.c_str(),
|
|
"org.freedesktop.NetworkManager.Device.Wireless",
|
|
"ActiveAccessPoint",
|
|
"o"
|
|
);
|
|
|
|
if (!msg) {
|
|
return AccessPointInfo{}; // No active AP
|
|
}
|
|
|
|
const char* ap_path = nullptr;
|
|
sd_bus_message_read(msg, "o", &ap_path);
|
|
sd_bus_message_unref(msg);
|
|
|
|
if (!ap_path || std::string(ap_path) == "/") {
|
|
return AccessPointInfo{}; // No active AP
|
|
}
|
|
|
|
AccessPointInfo ap;
|
|
ap.object_path = ap_path;
|
|
|
|
// Read the active AP's SSID bytes so connectedSSID() (and the Home footer)
|
|
// can report which network is in use.
|
|
sd_bus_message* ssid_msg = nullptr;
|
|
sd_bus_error ssid_error = SD_BUS_ERROR_NULL;
|
|
int r = sd_bus_get_property(
|
|
impl_->bus,
|
|
"org.freedesktop.NetworkManager",
|
|
ap_path,
|
|
"org.freedesktop.NetworkManager.AccessPoint",
|
|
"Ssid",
|
|
&ssid_error,
|
|
&ssid_msg,
|
|
"ay"
|
|
);
|
|
if (r >= 0 && ssid_msg) {
|
|
const uint8_t* ssid_data = nullptr;
|
|
size_t ssid_len = 0;
|
|
r = sd_bus_message_read_array(ssid_msg, 'y', (const void**)&ssid_data, &ssid_len);
|
|
if (r >= 0 && ssid_data && ssid_len > 0) {
|
|
ap.ssid = std::string(reinterpret_cast<const char*>(ssid_data), ssid_len);
|
|
}
|
|
sd_bus_message_unref(ssid_msg);
|
|
}
|
|
sd_bus_error_free(&ssid_error);
|
|
|
|
return ap;
|
|
}
|
|
|
|
bool WifiDevice::isConnected() const {
|
|
return getDeviceState() == nm_device_state::Activated;
|
|
}
|
|
|
|
std::string WifiDevice::connectedSSID() const {
|
|
if (!isConnected()) {
|
|
return "";
|
|
}
|
|
|
|
auto ap = getActiveAccessPoint();
|
|
return ap.ssid;
|
|
}
|
|
|
|
std::unique_ptr<WifiDevice> WifiDevice::findFirstDevice() {
|
|
sd_bus* bus = nullptr;
|
|
int r = sd_bus_open_system(&bus);
|
|
if (r < 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
sd_bus_message* msg = nullptr;
|
|
sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
|
|
r = sd_bus_call_method(
|
|
bus,
|
|
"org.freedesktop.NetworkManager",
|
|
"/org/freedesktop/NetworkManager",
|
|
"org.freedesktop.NetworkManager",
|
|
"GetDevices",
|
|
&error,
|
|
&msg,
|
|
""
|
|
);
|
|
|
|
if (r < 0) {
|
|
sd_bus_error_free(&error);
|
|
sd_bus_unref(bus);
|
|
return nullptr;
|
|
}
|
|
|
|
// Read device paths
|
|
r = sd_bus_message_enter_container(msg, 'a', "o");
|
|
if (r < 0) {
|
|
sd_bus_message_unref(msg);
|
|
sd_bus_unref(bus);
|
|
return nullptr;
|
|
}
|
|
|
|
const char* dev_path = nullptr;
|
|
std::string wifi_path;
|
|
|
|
while ((r = sd_bus_message_read(msg, "o", &dev_path)) > 0 && dev_path) {
|
|
// Check device type
|
|
sd_bus_message* type_msg = nullptr;
|
|
sd_bus_error type_error = SD_BUS_ERROR_NULL;
|
|
uint32_t dev_type = 0;
|
|
|
|
r = sd_bus_get_property(
|
|
bus,
|
|
"org.freedesktop.NetworkManager",
|
|
dev_path,
|
|
"org.freedesktop.NetworkManager.Device",
|
|
"DeviceType",
|
|
&type_error,
|
|
&type_msg,
|
|
"u"
|
|
);
|
|
|
|
if (r >= 0) {
|
|
sd_bus_message_read(type_msg, "u", &dev_type);
|
|
sd_bus_message_unref(type_msg);
|
|
|
|
// NM_DEVICE_TYPE_WIFI = 2
|
|
if (dev_type == 2) {
|
|
wifi_path = dev_path;
|
|
break;
|
|
}
|
|
}
|
|
|
|
sd_bus_error_free(&type_error);
|
|
}
|
|
|
|
sd_bus_message_exit_container(msg);
|
|
sd_bus_message_unref(msg);
|
|
sd_bus_unref(bus);
|
|
|
|
if (wifi_path.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return std::make_unique<WifiDevice>(wifi_path);
|
|
}
|
|
|
|
#else // !__linux__
|
|
|
|
// ─── Stub implementation for non-Linux platforms ───────────────────────
|
|
|
|
struct WifiDevice::Impl {
|
|
std::string object_path;
|
|
|
|
explicit Impl(const std::string& path) : object_path(path) {}
|
|
};
|
|
|
|
WifiDevice::WifiDevice(const std::string& object_path)
|
|
: impl_(std::make_unique<Impl>(object_path))
|
|
{
|
|
}
|
|
|
|
WifiDevice::~WifiDevice() = default;
|
|
|
|
WifiDevice::WifiDevice(WifiDevice&& other) noexcept = default;
|
|
WifiDevice& WifiDevice::operator=(WifiDevice&& other) noexcept = default;
|
|
|
|
std::string WifiDevice::getInterfaceName() const {
|
|
return "wlan0"; // Stub
|
|
}
|
|
|
|
uint32_t WifiDevice::getDeviceState() const {
|
|
return nm_device_state::Disconnected; // Stub
|
|
}
|
|
|
|
std::string WifiDevice::getObjectPath() const {
|
|
return impl_->object_path;
|
|
}
|
|
|
|
bool WifiDevice::requestScan() {
|
|
std::cerr << "[WifiDevice] Stub: requestScan() called on non-Linux platform\n";
|
|
return false;
|
|
}
|
|
|
|
std::vector<AccessPointInfo> WifiDevice::getAccessPoints() const {
|
|
std::cerr << "[WifiDevice] Stub: getAccessPoints() called on non-Linux platform\n";
|
|
return {};
|
|
}
|
|
|
|
AccessPointInfo WifiDevice::getActiveAccessPoint() const {
|
|
std::cerr << "[WifiDevice] Stub: getActiveAccessPoint() called on non-Linux platform\n";
|
|
return {};
|
|
}
|
|
|
|
bool WifiDevice::isConnected() const {
|
|
return false;
|
|
}
|
|
|
|
std::string WifiDevice::connectedSSID() const {
|
|
return "";
|
|
}
|
|
|
|
std::unique_ptr<WifiDevice> WifiDevice::findFirstDevice() {
|
|
std::cerr << "[WifiDevice] Stub: findFirstDevice() called on non-Linux platform\n";
|
|
return nullptr;
|
|
}
|
|
|
|
#endif // __linux__
|
|
|
|
} // namespace radiantwave::network
|