#include "nm_device.h" #include "nm_connection.h" #include "logger/logger.h" #include #include #include // Only include sdbus on Linux #ifdef __linux__ #include #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(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 WifiDevice::getAccessPoints() const { std::vector 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(out)); } else { sd_bus_message_read(prop_msg, "y", static_cast(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(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(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::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(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(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 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::findFirstDevice() { std::cerr << "[WifiDevice] Stub: findFirstDevice() called on non-Linux platform\n"; return nullptr; } #endif // __linux__ } // namespace radiantwave::network