diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD index ed598fa7..604a9b66 100644 --- a/internal/platform/implementation/linux/BUILD +++ b/internal/platform/implementation/linux/BUILD @@ -115,9 +115,10 @@ cc_library( "network_manager_access_point.h", "stream.h", "tcp_server_socket.h", - # "wifi_direct.h", + "wifi_direct.h", + "wifi_direct_utils.h", # "wifi_direct_server_socket.h", - # "wifi_direct_socket.h", + "wifi_direct_socket.h", "wifi_hotspot.h", "wifi_hotspot_server_socket.h", "wifi_hotspot_socket.h", @@ -224,7 +225,8 @@ cc_library( "submittable_executor.cc", "thread_pool.cc", "utils.cc", - # "wifi_direct.cc", + "wifi_direct.cc", + "wifi_direct_utils.cc", # "wifi_direct_server_socket.cc", "wifi_hotspot.cc", "wifi_hotspot_server_socket.cc", @@ -308,6 +310,7 @@ cc_test( "atomic_reference_test.cc", "mutex_test.cc", "utils_test.cc", + "wifi_direct_utils_test.cc", "ble_l2cap_socket_test.cc", "bluetooth_classic_socket_test.cc", # "bluetooth_adapter_test.cc", diff --git a/internal/platform/implementation/linux/bluez_le_advertisement.h b/internal/platform/implementation/linux/bluez_le_advertisement.h index 32330090..0f3cc366 100644 --- a/internal/platform/implementation/linux/bluez_le_advertisement.h +++ b/internal/platform/implementation/linux/bluez_le_advertisement.h @@ -16,7 +16,6 @@ #define PLATFORM_IMPL_LINUX_API_BLUEZ_BLE_ADVERTISEMENT_H_ #include -#include #include #include #include diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.WifiP2PPeer.xml b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.WifiP2PPeer.xml new file mode 100644 index 00000000..20addf85 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.WifiP2PPeer.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/wifi_p2p_peer_client.h b/internal/platform/implementation/linux/generated/dbus/networkmanager/wifi_p2p_peer_client.h new file mode 100644 index 00000000..8757f017 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/wifi_p2p_peer_client.h @@ -0,0 +1,96 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__internal_platform_implementation_linux_generated_dbus_networkmanager_wifi_p2p_peer_client_h__proxy__H__ +#define __sdbuscpp__internal_platform_implementation_linux_generated_dbus_networkmanager_wifi_p2p_peer_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace NetworkManager { + +class WifiP2PPeer_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.WifiP2PPeer"; + +protected: + WifiP2PPeer_proxy(sdbus::IProxy& proxy) + : m_proxy(proxy) + { + } + + WifiP2PPeer_proxy(const WifiP2PPeer_proxy&) = delete; + WifiP2PPeer_proxy& operator=(const WifiP2PPeer_proxy&) = delete; + WifiP2PPeer_proxy(WifiP2PPeer_proxy&&) = delete; + WifiP2PPeer_proxy& operator=(WifiP2PPeer_proxy&&) = delete; + + ~WifiP2PPeer_proxy() = default; + + void registerProxy() + { + } + +public: + std::string Name() + { + return m_proxy.getProperty("Name").onInterface(INTERFACE_NAME).get(); + } + + uint32_t Flags() + { + return m_proxy.getProperty("Flags").onInterface(INTERFACE_NAME).get(); + } + + std::string Manufacturer() + { + return m_proxy.getProperty("Manufacturer").onInterface(INTERFACE_NAME).get(); + } + + std::string Model() + { + return m_proxy.getProperty("Model").onInterface(INTERFACE_NAME).get(); + } + + std::string ModelNumber() + { + return m_proxy.getProperty("ModelNumber").onInterface(INTERFACE_NAME).get(); + } + + std::string Serial() + { + return m_proxy.getProperty("Serial").onInterface(INTERFACE_NAME).get(); + } + + std::vector WfdIEs() + { + return m_proxy.getProperty("WfdIEs").onInterface(INTERFACE_NAME).get>(); + } + + std::string HwAddress() + { + return m_proxy.getProperty("HwAddress").onInterface(INTERFACE_NAME).get(); + } + + uint8_t Strength() + { + return m_proxy.getProperty("Strength").onInterface(INTERFACE_NAME).get(); + } + + int32_t LastSeen() + { + return m_proxy.getProperty("LastSeen").onInterface(INTERFACE_NAME).get(); + } + +private: + sdbus::IProxy& m_proxy; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/network_manager_active_connection.cc b/internal/platform/implementation/linux/network_manager_active_connection.cc index c44d7897..b4b5f2b0 100644 --- a/internal/platform/implementation/linux/network_manager_active_connection.cc +++ b/internal/platform/implementation/linux/network_manager_active_connection.cc @@ -79,7 +79,7 @@ std::vector ActiveConnection::GetIP4Addresses() { sdbus::ObjectPath ip4config_path; try { ip4config_path = Ip4Config(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_PROPERTY_GET_ERROR(this, "Ip4Config", e); return {}; } @@ -88,13 +88,13 @@ std::vector ActiveConnection::GetIP4Addresses() { std::vector> address_data; try { address_data = ip4config.AddressData(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_PROPERTY_GET_ERROR(&ip4config, "AddressData", e); return {}; } std::vector ip4addresses; - for (auto &data : address_data) { + for (auto& data : address_data) { if (data.count("address") == 1) { ip4addresses.push_back(data["address"].get()); } @@ -102,10 +102,32 @@ std::vector ActiveConnection::GetIP4Addresses() { return ip4addresses; } +std::string ActiveConnection::GetIP4Gateway() { + sdbus::ObjectPath ip4config_path; + try { + ip4config_path = Ip4Config(); + } catch (const sdbus::Error& e) { + DBUS_LOG_PROPERTY_GET_ERROR(this, "Ip4Config", e); + return {}; + } + + if (ip4config_path.empty() || ip4config_path == "/") { + return {}; + } + + IP4Config ip4config(system_bus_, ip4config_path); + try { + return ip4config.Gateway(); + } catch (const sdbus::Error& e) { + DBUS_LOG_PROPERTY_GET_ERROR(&ip4config, "Gateway", e); + return {}; + } +} + std::pair, bool> ActiveConnection::WaitForConnection(absl::Duration timeout) { LOG(INFO) << __func__ << ": Waiting for an update to " - << getProxy().getObjectPath() << "'s state"; + << getProxy().getObjectPath() << "'s state"; auto state_changed = [this]() { this->state_mutex_.AssertReaderHeld(); diff --git a/internal/platform/implementation/linux/network_manager_active_connection.h b/internal/platform/implementation/linux/network_manager_active_connection.h index f93b8e4d..07b5503f 100644 --- a/internal/platform/implementation/linux/network_manager_active_connection.h +++ b/internal/platform/implementation/linux/network_manager_active_connection.h @@ -61,10 +61,10 @@ class ActiveConnection std::string ToString() const; }; - ActiveConnection(const ActiveConnection &) = delete; - ActiveConnection(ActiveConnection &&) = delete; - ActiveConnection &operator=(const ActiveConnection &) = delete; - ActiveConnection &operator=(ActiveConnection &&) = delete; + ActiveConnection(const ActiveConnection&) = delete; + ActiveConnection(ActiveConnection&&) = delete; + ActiveConnection& operator=(const ActiveConnection&) = delete; + ActiveConnection& operator=(ActiveConnection&&) = delete; explicit ActiveConnection(std::shared_ptr system_bus, sdbus::ObjectPath active_connection_path) : ProxyInterfaces(*system_bus, @@ -80,14 +80,14 @@ class ActiveConnection if (state >= kStateUnknown && state <= kStateDeactivated) { state_ = static_cast(state); } - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_PROPERTY_GET_ERROR(this, "State", e); } } virtual ~ActiveConnection() { unregisterProxy(); } protected: - void onStateChanged(const uint32_t &state, const uint32_t &reason) override + void onStateChanged(const uint32_t& state, const uint32_t& reason) override ABSL_LOCKS_EXCLUDED(state_mutex_) { absl::MutexLock l(&state_mutex_); if (state >= kStateUnknown && state <= kStateDeactivated) { @@ -107,6 +107,7 @@ class ActiveConnection absl::Duration timeout = absl::Seconds(10)) ABSL_LOCKS_EXCLUDED(state_mutex_); std::vector GetIP4Addresses(); + std::string GetIP4Gateway(); private: std::shared_ptr system_bus_; @@ -116,9 +117,9 @@ class ActiveConnection ActiveConnectionStateReason reason_ ABSL_GUARDED_BY(state_mutex_); }; -extern std::ostream &operator<<( - std::ostream &stream, - const ActiveConnection::ActiveConnectionStateReason &reason); +extern std::ostream& operator<<( + std::ostream& stream, + const ActiveConnection::ActiveConnectionStateReason& reason); } // namespace networkmanager } // namespace linux diff --git a/internal/platform/implementation/linux/platform.cc b/internal/platform/implementation/linux/platform.cc index c0e34d6a..b81fb80d 100644 --- a/internal/platform/implementation/linux/platform.cc +++ b/internal/platform/implementation/linux/platform.cc @@ -29,7 +29,7 @@ #include "internal/platform/implementation/input_file.h" #include "internal/platform/implementation/linux/atomic_boolean.h" #include "internal/platform/implementation/linux/atomic_uint32.h" -//#include "internal/platform/implementation/linux/ble_v2_medium.h" +// #include "internal/platform/implementation/linux/ble_v2_medium.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h" #include "internal/platform/implementation/linux/bluetooth_classic_medium.h" #include "internal/platform/implementation/linux/bluez.h" @@ -40,7 +40,7 @@ #include "internal/platform/implementation/linux/preferences_manager.h" #include "internal/platform/implementation/linux/submittable_executor.h" #include "internal/platform/implementation/linux/timer.h" -// #include "internal/platform/implementation/linux/wifi_direct.h" +#include "internal/platform/implementation/linux/wifi_direct.h" // #include "internal/platform/implementation/linux/wifi_hotspot.h" #include "internal/platform/implementation/linux/wifi_lan.h" // #include "internal/platform/implementation/linux/wifi_medium.h" @@ -56,22 +56,22 @@ #include "internal/platform/implementation/wifi_lan.h" #include "internal/platform/payload_id.h" #include "scheduled_executor.h" -// #include "wifi_direct.h" +#include "wifi_direct.h" #include "wifi_hotspot.h" namespace nearby { namespace api { std::string ImplementationPlatform::GetCustomSavePath( - const std::string &parent_folder, const std::string &file_name) { + const std::string& parent_folder, const std::string& file_name) { auto fs = std::filesystem::path(parent_folder); return (fs / file_name).string(); } std::string ImplementationPlatform::GetDownloadPath( - const std::string &parent_folder, const std::string &file_name) { + const std::string& parent_folder, const std::string& file_name) { std::filesystem::path downloads; const char* download_dir = getenv("XDG_DOWNLOAD_DIR"); - + if (download_dir != nullptr) { downloads = std::filesystem::path(download_dir); } else { @@ -83,16 +83,17 @@ std::string ImplementationPlatform::GetDownloadPath( downloads = "/tmp/Downloads"; } } - + return (downloads / std::filesystem::path(parent_folder).filename() / - std::filesystem::path(file_name).filename()).string(); + std::filesystem::path(file_name).filename()) + .string(); } std::string ImplementationPlatform::GetDownloadPath( - const std::string &file_name) { + const std::string& file_name) { std::filesystem::path downloads; const char* download_dir = getenv("XDG_DOWNLOAD_DIR"); - + if (download_dir != nullptr) { downloads = std::filesystem::path(download_dir); } else { @@ -104,15 +105,15 @@ std::string ImplementationPlatform::GetDownloadPath( downloads = "/tmp/Downloads"; } } - + return (downloads / std::filesystem::path(file_name).filename()).string(); } std::string ImplementationPlatform::GetAppDataPath( - const std::string &file_name) { + const std::string& file_name) { std::filesystem::path state; const char* state_home = getenv("XDG_STATE_HOME"); - + if (state_home != nullptr) { state = std::filesystem::path(state_home); } else { @@ -124,11 +125,13 @@ std::string ImplementationPlatform::GetAppDataPath( state = "/tmp/state"; } } - + return (state / std::filesystem::path(file_name).filename()).string(); } -OSName ImplementationPlatform::GetCurrentOS() { return OSName::kWindows; } +OSName ImplementationPlatform::GetCurrentOS() { + return OSName::kWindows; +} std::unique_ptr ImplementationPlatform::CreateAtomicBoolean( bool initial_value) { @@ -154,7 +157,7 @@ std::unique_ptr ImplementationPlatform::CreateMutex( #pragma pop_macro("CreateMutex") std::unique_ptr -ImplementationPlatform::CreateConditionVariable(api::Mutex *mutex) { +ImplementationPlatform::CreateConditionVariable(api::Mutex* mutex) { return std::make_unique(mutex); } @@ -171,7 +174,7 @@ std::unique_ptr ImplementationPlatform::CreateInputFile( } std::unique_ptr ImplementationPlatform::CreateInputFile( - const std::string &file_path) { + const std::string& file_path) { return nearby::shared::IOFile::CreateInputFile(file_path); } @@ -182,20 +185,20 @@ std::unique_ptr ImplementationPlatform::CreateOutputFile( } std::unique_ptr ImplementationPlatform::CreateOutputFile( - const std::string &file_path) { + const std::string& file_path) { std::filesystem::path path(file_path); try { std::filesystem::create_directories(path.parent_path()); - } catch (std::filesystem::filesystem_error const &err) { + } catch (std::filesystem::filesystem_error const& err) { LOG(ERROR) << __func__ << ": error creating directory tree " - << path.parent_path() << ": " << err.what(); + << path.parent_path() << ": " << err.what(); } return nearby::shared::IOFile::CreateOutputFile(path.string()); } std::unique_ptr ImplementationPlatform::CreateLogMessage( - const char *file, int line, LogMessage::Severity severity) { + const char* file, int line, LogMessage::Severity severity) { return nullptr; // Disabled LogMessage // return std::make_unique(file, line, severity); @@ -223,33 +226,34 @@ ImplementationPlatform::CreateBluetoothAdapter() { auto manager = linux::bluez::BluezObjectManager(*system_bus); try { auto interfaces = manager.GetManagedObjects(); - for (auto &[object, properties] : interfaces) { - if (properties.count(sdbus::InterfaceName(org::bluez::Adapter1_proxy::INTERFACE_NAME)) == 1) { + for (auto& [object, properties] : interfaces) { + if (properties.count(sdbus::InterfaceName( + org::bluez::Adapter1_proxy::INTERFACE_NAME)) == 1) { LOG(INFO) << __func__ << ": found bluetooth adapter " << object; return std::make_unique(system_bus, object); } } - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(&manager, "GetManagedObjects", e); return nullptr; } LOG(ERROR) << __func__ - << ": couldn't find a bluetooth adapter on this system"; + << ": couldn't find a bluetooth adapter on this system"; return nullptr; } std::unique_ptr ImplementationPlatform::CreateBluetoothClassicMedium( - BluetoothAdapter &adapter) { + BluetoothAdapter& adapter) { return std::make_unique( - dynamic_cast(adapter)); + dynamic_cast(adapter)); } std::unique_ptr ImplementationPlatform::CreateBleMedium( - BluetoothAdapter &adapter) { + BluetoothAdapter& adapter) { return std::make_unique( - dynamic_cast(adapter)); + dynamic_cast(adapter)); } std::unique_ptr @@ -265,7 +269,7 @@ static std::unique_ptr createWifiMedium( try { device_paths = nm->GetAllDevices(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(nm, "GetAllDevices", e); return nullptr; } @@ -273,38 +277,39 @@ static std::unique_ptr createWifiMedium( auto manager = linux::networkmanager::ObjectManager(nm->GetConnection()); std::map>> + std::map>> objects; try { objects = manager.GetManagedObjects(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(nm, "GetManagedObjects", e); return nullptr; } - for (auto &device_path : device_paths) { + for (auto& device_path : device_paths) { if (objects.count(device_path) == 1) { auto device = objects[device_path]; - if (device.count(sdbus::InterfaceName(org::freedesktop::NetworkManager::Device:: - Wireless_proxy::INTERFACE_NAME)) == 1) { + if (device.count( + sdbus::InterfaceName(org::freedesktop::NetworkManager::Device:: + Wireless_proxy::INTERFACE_NAME)) == 1) { LOG(INFO) << __func__ - << ": Found a wireless device at :" << device_path; + << ": Found a wireless device at :" << device_path; return std::make_unique(nm, device_path); } } } - LOG(ERROR) << __func__ - << ": couldn't find a wireless device on this system"; + LOG(ERROR) << __func__ << ": couldn't find a wireless device on this system"; return nullptr; } } // namespace std::unique_ptr ImplementationPlatform::CreateWifiMedium() { // return nullptr; - auto nm = - std::make_shared(linux::getSystemBusConnection()); + auto nm = std::make_shared( + linux::getSystemBusConnection()); return createWifiMedium(nm); } @@ -317,8 +322,8 @@ ImplementationPlatform::CreateWifiLanMedium() { std::unique_ptr ImplementationPlatform::CreateWifiHotspotMedium() { - auto nm = - std::make_shared(linux::getSystemBusConnection()); + auto nm = std::make_shared( + linux::getSystemBusConnection()); auto wifiMedium = createWifiMedium(nm); if (wifiMedium == nullptr) { @@ -332,18 +337,45 @@ ImplementationPlatform::CreateWifiHotspotMedium() { std::unique_ptr ImplementationPlatform::CreateWifiDirectMedium() { + auto nm = std::make_shared( + linux::getSystemBusConnection()); + auto manager = linux::networkmanager::ObjectManager(nm->GetConnection()); + + std::map>> + objects; + try { + objects = manager.GetManagedObjects(); + } catch (const sdbus::Error& error) { + DBUS_LOG_METHOD_CALL_ERROR(nm, "GetManagedObjects", error); + return nullptr; + } + + const sdbus::InterfaceName wifi_p2p_interface( + org::freedesktop::NetworkManager::Device::WifiP2P_proxy::INTERFACE_NAME); + const sdbus::InterfaceName device_interface( + "org.freedesktop.NetworkManager.Device"); + const sdbus::PropertyName managed_property("Managed"); + for (const auto& [object_path, interfaces] : objects) { + if (!interfaces.contains(wifi_p2p_interface)) { + continue; + } + auto device = interfaces.find(device_interface); + if (device != interfaces.end()) { + auto managed = device->second.find(managed_property); + if (managed != device->second.end() && !managed->second.get()) { + continue; + } + } + LOG(INFO) << __func__ << ": Found Wi-Fi P2P device at " << object_path; + return std::make_unique( + nm, object_path); + } + + LOG(WARNING) << __func__ + << ": NetworkManager exposes no managed Wi-Fi P2P device"; return nullptr; - // auto nm = - // std::make_shared(linux::getSystemBusConnection()); - // auto wifiMedium = createWifiMedium(nm); - // - // if (wifiMedium == nullptr) { - // LOG(ERROR) << __func__ << ": Could not create a WiFi medium"; - // return nullptr; - // } - // - // return std::make_unique( - // nm, std::move(wifiMedium)); } std::unique_ptr ImplementationPlatform::CreateTimer() { @@ -354,18 +386,18 @@ std::unique_ptr ImplementationPlatform::CreateDeviceInfo() { return std::make_unique(linux::getSystemBusConnection()); } - std::unique_ptr ImplementationPlatform::CreateAwdlMedium() { +std::unique_ptr ImplementationPlatform::CreateAwdlMedium() { return nullptr; } absl::StatusOr ImplementationPlatform::SendRequest( - const WebRequest &request) { + const WebRequest& request) { if (request.body.size() >= (8 * 1024 * 1024)) { return absl::Status(absl::StatusCode::kResourceExhausted, "request body too large"); } - CURL *handle = curl_easy_init(); + CURL* handle = curl_easy_init(); char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0'; @@ -381,9 +413,9 @@ absl::StatusOr ImplementationPlatform::SendRequest( curl_easy_setopt(handle, CURLOPT_UPLOAD, request.body.c_str()); - struct curl_slist *headers_slist = nullptr; + struct curl_slist* headers_slist = nullptr; - for (auto &[key, value] : request.headers) { + for (auto& [key, value] : request.headers) { auto hdr = absl::StrCat(key, ": ", value); auto temp = curl_slist_append(headers_slist, hdr.c_str()); if (temp == nullptr) { @@ -400,27 +432,26 @@ absl::StatusOr ImplementationPlatform::SendRequest( api::WebResponse response; if (curl_easy_perform(handle) != CURLE_OK) { - LOG(ERROR) << __func__ - << ": Error performing HTTP request: " << errbuf; + LOG(ERROR) << __func__ << ": Error performing HTTP request: " << errbuf; return absl::Status(absl::StatusCode::kUnknown, errbuf); } - struct curl_header *prev = nullptr; - struct curl_header *h; + struct curl_header* prev = nullptr; + struct curl_header* h; h = curl_easy_nextheader(handle, CURLH_HEADER, 0, prev); while (h != nullptr) { response.headers.emplace(h->name, h->value); } - auto writefn = [](char *ptr, size_t size, size_t nmemb, void *userdata) { - std::string *body = static_cast(userdata); + auto writefn = [](char* ptr, size_t size, size_t nmemb, void* userdata) { + std::string* body = static_cast(userdata); body->append(ptr, size * nmemb); }; curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writefn); curl_easy_setopt(handle, CURLOPT_WRITEDATA, - static_cast(&response.body)); + static_cast(&response.body)); long status; curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); response.status_code = status; diff --git a/internal/platform/implementation/linux/tcp_server_socket.h b/internal/platform/implementation/linux/tcp_server_socket.h index cb016fc9..6bddf31e 100644 --- a/internal/platform/implementation/linux/tcp_server_socket.h +++ b/internal/platform/implementation/linux/tcp_server_socket.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "internal/platform/exception.h" @@ -33,18 +34,34 @@ class TCPSocket { public: explicit TCPSocket(int fd) : fd_(fd), closed_(false), output_stream_(fd), input_stream_(fd) {} + TCPSocket(const TCPSocket&) = delete; + TCPSocket& operator=(const TCPSocket&) = delete; + TCPSocket(TCPSocket&& other) noexcept + : fd_(std::exchange(other.fd_, -1)), + closed_(std::exchange(other.closed_, true)), + output_stream_(fd_), + input_stream_(fd_) {} + TCPSocket& operator=(TCPSocket&& other) noexcept { + if (this == &other) return *this; + Close(); + fd_ = std::exchange(other.fd_, -1); + closed_ = std::exchange(other.closed_, true); + output_stream_ = OutputStream(fd_); + input_stream_ = InputStream(fd_); + return *this; + } + ~TCPSocket() { Close(); } static std::optional Connect(const std::string& ip_address, int port) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { LOG(ERROR) << __func__ - << ": Error opening socket: " << std::strerror(errno); + << ": Error opening socket: " << std::strerror(errno); return std::nullopt; } - LOG(INFO) << __func__ << ": Connecting to " << ip_address << ":" - << port; + LOG(INFO) << __func__ << ": Connecting to " << ip_address << ":" << port; struct sockaddr_in addr; addr.sin_addr.s_addr = inet_addr(ip_address.c_str()); addr.sin_family = AF_INET; @@ -53,8 +70,8 @@ class TCPSocket { auto ret = connect(sock, reinterpret_cast(&addr), sizeof(addr)); if (ret < 0) { - LOG(ERROR) << __func__ << ": Error connecting to socket: " - << std::strerror(errno); + LOG(ERROR) << __func__ + << ": Error connecting to socket: " << std::strerror(errno); close(sock); return std::nullopt; } @@ -66,7 +83,7 @@ class TCPSocket { OutputStream& GetOutputStream() { return output_stream_; } Exception Close() { - if (closed_) return {Exception::kFailed}; + if (closed_) return {Exception::kSuccess}; closed_ = true; input_stream_.Close(); @@ -97,7 +114,7 @@ class TCPServerSocket { auto sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { LOG(ERROR) << __func__ - << ": Error opening socket: " << std::strerror(errno); + << ": Error opening socket: " << std::strerror(errno); return std::nullopt; } @@ -109,19 +126,19 @@ class TCPServerSocket { addr.sin_addr.s_addr = inet_addr(ip_address->get().c_str()); else addr.sin_addr.s_addr = htonl(INADDR_ANY); - + auto ret = bind(sock, reinterpret_cast(&addr), sizeof(addr)); if (ret < 0) { - LOG(ERROR) << __func__ << ": Error binding to socket: " - << std::strerror(errno); + LOG(ERROR) << __func__ + << ": Error binding to socket: " << std::strerror(errno); return std::nullopt; } ret = listen(sock, 0); if (ret < 0) { - LOG(ERROR) << __func__ << ": Error listening on socket: " - << std::strerror(errno); + LOG(ERROR) << __func__ + << ": Error listening on socket: " << std::strerror(errno); return std::nullopt; } @@ -131,12 +148,11 @@ class TCPServerSocket { struct sockaddr_in addr; socklen_t len = sizeof(addr); - auto conn = - accept(fd_, reinterpret_cast(&addr), &len); + auto conn = accept(fd_, reinterpret_cast(&addr), &len); if (conn < 0) { LOG(ERROR) << __func__ - << ": Error accepting incoming connections on socket " - << fd_ << ": " << std::strerror(errno); + << ": Error accepting incoming connections on socket " << fd_ + << ": " << std::strerror(errno); return std::nullopt; } @@ -153,7 +169,7 @@ class TCPServerSocket { auto ret = close(fd); if (ret < 0) { LOG(ERROR) << __func__ << ": Error closing socket " << fd << ": " - << std::strerror(errno); + << std::strerror(errno); return {Exception::kFailed}; } @@ -163,12 +179,10 @@ class TCPServerSocket { int GetPort() const { struct sockaddr_in sin; socklen_t len = sizeof(sin); - auto ret = - getsockname(fd_, reinterpret_cast(&sin), &len); + auto ret = getsockname(fd_, reinterpret_cast(&sin), &len); if (ret < 0) { - LOG(ERROR) << __func__ - << ": Error getting information for socket " - << fd_ << ": " << std::strerror(errno); + LOG(ERROR) << __func__ << ": Error getting information for socket " << fd_ + << ": " << std::strerror(errno); return 0; } diff --git a/internal/platform/implementation/linux/wifi_direct.cc b/internal/platform/implementation/linux/wifi_direct.cc index cd576a24..16aab265 100644 --- a/internal/platform/implementation/linux/wifi_direct.cc +++ b/internal/platform/implementation/linux/wifi_direct.cc @@ -12,127 +12,426 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include -#include - -#include "internal/platform/implementation/linux/tcp_server_socket.h" #include "internal/platform/implementation/linux/wifi_direct.h" -#include "internal/platform/implementation/linux/wifi_direct_server_socket.h" -#include "internal/platform/implementation/linux/wifi_direct_socket.h" -#include "internal/platform/implementation/linux/wifi_hotspot.h" -#include "internal/platform/implementation/linux/wifi_medium.h" -#include "internal/platform/implementation/wifi_direct.h" -#include "internal/platform/wifi_credential.h" -namespace nearby { -namespace linux { +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "internal/flags/nearby_flags.h" +#include "internal/platform/flags/nearby_platform_feature_flags.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/wifi_p2p_peer_client.h" +#include "internal/platform/implementation/linux/network_manager_active_connection.h" +#include "internal/platform/implementation/linux/tcp_server_socket.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/implementation/linux/wifi_direct_socket.h" +#include "internal/platform/implementation/linux/wifi_direct_utils.h" +#include "internal/platform/logging.h" + +namespace nearby::linux { +namespace { + +constexpr absl::Duration kPeerDiscoveryTimeout = absl::Seconds(30); +constexpr absl::Duration kActivationTimeout = absl::Seconds(30); +constexpr absl::Duration kPeerUpdatePollInterval = absl::Milliseconds(500); +constexpr int kCancellationPollMillis = 100; +class NetworkManagerWifiP2PPeer + : public sdbus::ProxyInterfaces< + org::freedesktop::NetworkManager::WifiP2PPeer_proxy> { + public: + NetworkManagerWifiP2PPeer( + const std::shared_ptr& system_bus, + const sdbus::ObjectPath& object_path) + : ProxyInterfaces(*system_bus, + sdbus::ServiceName("org.freedesktop.NetworkManager"), + object_path) { + registerProxy(); + } + ~NetworkManagerWifiP2PPeer() { unregisterProxy(); } +}; + +bool WaitForTcpConnect(int fd, absl::Duration timeout, + CancellationFlag* cancellation_flag) { + const absl::Time deadline = absl::Now() + timeout; + while (absl::Now() < deadline) { + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + return false; + } + + const int remaining_ms = static_cast(std::max( + 1, absl::ToInt64Milliseconds(deadline - absl::Now()))); + pollfd descriptor{.fd = fd, .events = POLLOUT, .revents = 0}; + int result; + do { + result = + poll(&descriptor, 1, std::min(kCancellationPollMillis, remaining_ms)); + } while (result < 0 && errno == EINTR); + + if (result < 0) { + return false; + } + if (result == 0) { + continue; + } + + int socket_error = 0; + socklen_t socket_error_length = sizeof(socket_error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, + &socket_error_length) != 0) { + return false; + } + if (socket_error == 0) { + return true; + } + errno = socket_error; + return false; + } + return false; +} + +std::optional ConnectTcp(const std::string& ip_address, int port, + absl::Duration timeout, + CancellationFlag* cancellation_flag) { + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_port = htons(port); + if (inet_pton(AF_INET, ip_address.c_str(), &address.sin_addr) != 1) { + LOG(ERROR) << __func__ << ": Invalid IPv4 address " << ip_address; + return std::nullopt; + } + + int fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + LOG(ERROR) << __func__ << ": socket failed: " << std::strerror(errno); + return std::nullopt; + } + + const int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + LOG(ERROR) << __func__ << ": fcntl failed: " << std::strerror(errno); + close(fd); + return std::nullopt; + } + + int result = + connect(fd, reinterpret_cast(&address), sizeof(address)); + if (result != 0 && errno != EINPROGRESS) { + close(fd); + return std::nullopt; + } + if (result != 0 && !WaitForTcpConnect(fd, timeout, cancellation_flag)) { + close(fd); + return std::nullopt; + } + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + close(fd); + return std::nullopt; + } + + return TCPSocket(fd); +} + +void SleepWithCancellation(absl::Duration duration, + CancellationFlag* cancellation_flag) { + const absl::Time deadline = absl::Now() + duration; + while (absl::Now() < deadline) { + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + return; + } + absl::SleepFor(std::min(absl::Milliseconds(kCancellationPollMillis), + deadline - absl::Now())); + } +} + +} // namespace + +NetworkManagerWifiDirectMedium::~NetworkManagerWifiDirectMedium() { + DisconnectWifiDirect(); + unregisterProxy(); +} + +void NetworkManagerWifiDirectMedium::onPeerAdded(const sdbus::ObjectPath&) { + absl::MutexLock lock(state_mutex_); + peer_changed_.SignalAll(); +} + +void NetworkManagerWifiDirectMedium::onPeerRemoved(const sdbus::ObjectPath&) { + absl::MutexLock lock(state_mutex_); + peer_changed_.SignalAll(); +} + +std::optional +NetworkManagerWifiDirectMedium::FindPeerByName(absl::string_view device_name) { + std::vector paths; + try { + paths = Peers(); + } catch (const sdbus::Error& error) { + DBUS_LOG_PROPERTY_GET_ERROR(this, "Peers", error); + return std::nullopt; + } + + std::vector peers; + peers.reserve(paths.size()); + for (const sdbus::ObjectPath& path : paths) { + NetworkManagerWifiP2PPeer peer(system_bus_, path); + try { + peers.push_back(Peer{.path = path, + .name = peer.Name(), + .hardware_address = peer.HwAddress(), + .strength = peer.Strength(), + .last_seen = peer.LastSeen()}); + } catch (const sdbus::Error& error) { + LOG(WARNING) << __func__ << ": Failed to inspect peer " << path << ": " + << error.getName() << ": " << error.getMessage(); + } + } + return wifi_direct_internal::SelectPeerByName(peers, device_name); +} + +void NetworkManagerWifiDirectMedium::StopDiscovery() { + bool should_stop = false; + { + absl::MutexLock lock(state_mutex_); + should_stop = discovering_; + discovering_ = false; + peer_changed_.SignalAll(); + } + if (!should_stop) { + return; + } + try { + StopFind(); + } catch (const sdbus::Error& error) { + DBUS_LOG_METHOD_CALL_ERROR(this, "StopFind", error); + } +} + +bool NetworkManagerWifiDirectMedium::ActivatePeer(const Peer& peer) { + auto uuid = NewUuidStr(); + if (!uuid.has_value()) { + LOG(ERROR) << __func__ << ": Could not generate connection UUID"; + return false; + } + + wifi_direct_internal::ConnectionSettings settings = + wifi_direct_internal::BuildGcConnectionSettings(*uuid, + peer.hardware_address); + + sdbus::ObjectPath active_path; + try { + auto [connection_path, returned_active_path, result] = + network_manager_->AddAndActivateConnection2( + settings, wifi_p2p_device_path_, peer.path, + {{"persist", sdbus::Variant(std::string("volatile"))}, + {"bind-activation", sdbus::Variant(std::string("dbus-client"))}}); + active_path = std::move(returned_active_path); + } catch (const sdbus::Error& error) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "AddAndActivateConnection2", + error); + return false; + } + + networkmanager::ActiveConnection active_connection(system_bus_, active_path); + auto [reason, timed_out] = + active_connection.WaitForConnection(kActivationTimeout); + if (timed_out || reason.has_value()) { + LOG(ERROR) << __func__ << ": Wi-Fi Direct activation failed for " + << active_path; + try { + network_manager_->DeactivateConnection(active_path); + } catch (const sdbus::Error& error) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", + error); + } + return false; + } + + std::vector addresses = active_connection.GetIP4Addresses(); + if (addresses.empty()) { + LOG(ERROR) << __func__ + << ": Activated Wi-Fi Direct connection has no IPv4 address"; + try { + network_manager_->DeactivateConnection(active_path); + } catch (const sdbus::Error& error) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", + error); + } + return false; + } + std::string gateway = active_connection.GetIP4Gateway(); + + absl::MutexLock lock(state_mutex_); + active_connection_path_ = active_path; + local_ip_address_ = addresses.front(); + remote_ip_address_ = std::move(gateway); + connected_ = true; + LOG(INFO) << __func__ + << ": Wi-Fi Direct GC connected; local IPv4=" << local_ip_address_ + << ", GO IPv4=" << remote_ip_address_; + return true; +} + +bool NetworkManagerWifiDirectMedium::ConnectWifiDirect( + const WifiDirectCredentials& wifi_direct_credentials) { + if (wifi_direct_credentials.GetDeviceName().empty()) { + LOG(ERROR) << __func__ << ": Device name is empty"; + return false; + } + + DisconnectWifiDirect(); + { + absl::MutexLock lock(state_mutex_); + discovering_ = true; + } + try { + StartFind({{"timeout", sdbus::Variant(std::int32_t(30))}}); + } catch (const sdbus::Error& error) { + { + absl::MutexLock lock(state_mutex_); + discovering_ = false; + } + DBUS_LOG_METHOD_CALL_ERROR(this, "StartFind", error); + return false; + } + + const absl::Time deadline = absl::Now() + kPeerDiscoveryTimeout; + std::optional peer; + while (absl::Now() < deadline) { + peer = FindPeerByName(wifi_direct_credentials.GetDeviceName()); + if (peer.has_value()) { + break; + } + + absl::MutexLock lock(state_mutex_); + if (!discovering_) { + break; + } + peer_changed_.WaitWithTimeout( + &state_mutex_, + std::min(kPeerUpdatePollInterval, deadline - absl::Now())); + } + StopDiscovery(); + + if (!peer.has_value()) { + LOG(WARNING) << __func__ << ": Timed out finding Wi-Fi Direct peer " + << wifi_direct_credentials.GetDeviceName(); + return false; + } + return ActivatePeer(*peer); +} + +bool NetworkManagerWifiDirectMedium::DisconnectWifiDirect() { + StopDiscovery(); + + std::string active_path; + { + absl::MutexLock lock(state_mutex_); + active_path = std::move(active_connection_path_); + connected_ = false; + local_ip_address_.clear(); + remote_ip_address_.clear(); + } + + if (active_path.empty()) { + return true; + } + try { + network_manager_->DeactivateConnection(sdbus::ObjectPath(active_path)); + return true; + } catch (const sdbus::Error& error) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", error); + return false; + } +} + std::unique_ptr NetworkManagerWifiDirectMedium::ConnectToService( absl::string_view ip_address, int port, - CancellationFlag *cancellation_flag) { - auto socket = TCPSocket::Connect(std::string(ip_address), port); - if (!socket.has_value()) return nullptr; + CancellationFlag* cancellation_flag) { + std::string remote_ip; + { + absl::MutexLock lock(state_mutex_); + if (!connected_) { + LOG(ERROR) << __func__ << ": Wi-Fi Direct GC is not connected"; + return nullptr; + } + remote_ip = remote_ip_address_; + } + if (remote_ip.empty()) { + remote_ip = std::string(ip_address); + } + if (remote_ip.empty() || port <= 0 || port > 65535) { + LOG(ERROR) << __func__ << ": Invalid service address or port"; + return nullptr; + } - return std::make_unique(std::move(*socket)); + const std::int64_t retries = std::max( + 1, NearbyFlags::GetInstance().GetInt64Flag( + platform::config_package_nearby::nearby_platform_feature:: + kWifiHotspotConnectionMaxRetries)); + const absl::Duration retry_interval = + absl::Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( + platform::config_package_nearby::nearby_platform_feature:: + kWifiHotspotConnectionIntervalMillis)); + const absl::Duration connect_timeout = + absl::Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( + platform::config_package_nearby::nearby_platform_feature:: + kWifiHotspotConnectionTimeoutMillis)); + + for (std::int64_t attempt = 0; attempt < retries; ++attempt) { + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + return nullptr; + } + auto socket = + ConnectTcp(remote_ip, port, connect_timeout, cancellation_flag); + if (socket.has_value()) { + return std::make_unique(std::move(*socket)); + } + if (attempt + 1 < retries) { + SleepWithCancellation(retry_interval, cancellation_flag); + } + } + + LOG(ERROR) << __func__ << ": Failed to connect to Wi-Fi Direct service " + << remote_ip << ":" << port; + return nullptr; } std::unique_ptr NetworkManagerWifiDirectMedium::ListenForService(int port) { - auto active_connection = wireless_device_->GetActiveConnection(); - if (active_connection == nullptr) { - return nullptr; - } - - auto ip4addresses = active_connection->GetIP4Addresses(); - if (ip4addresses.empty()) { - LOG(ERROR) - << __func__ - << "Could not find any IPv4 addresses for active connection " - << active_connection->getProxy().getObjectPath(); - return nullptr; - } - - auto socket = TCPServerSocket::Listen(std::ref(ip4addresses[0]), port); - if (!socket.has_value()) return nullptr; - - return std::make_unique( - std::move(*socket), std::move(active_connection), network_manager_); -} - -bool NetworkManagerWifiDirectMedium::ConnectWifiDirect( - WifiDirectCredentials *wifi_direct_credentials) { - if (wifi_direct_credentials == nullptr) { - LOG(ERROR) << __func__ << ": hotspot_credentials cannot be null"; - return false; - } - - auto ssid = wifi_direct_credentials->GetSSID(); - auto password = wifi_direct_credentials->GetPassword(); - - return wireless_device_->ConnectToNetwork(ssid, password, - api::WifiAuthType::kWpaPsk) == - api::WifiConnectionStatus::kConnected; -} - -bool NetworkManagerWifiDirectMedium::DisconnectWifiDirect() { - if (!ConnectedToWifi()) { - LOG(ERROR) << __func__ << ": Not connected to a WiFi hotspot"; - return false; - } - - auto active_connection = wireless_device_->GetActiveConnection(); - if (active_connection == nullptr) { - return false; - } - - try { - network_manager_->DeactivateConnection(active_connection->getProxy().getObjectPath()); - } catch (const sdbus::Error &e) { - DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", e); - return false; - } - - return true; -} - -bool NetworkManagerWifiDirectMedium::ConnectedToWifi() { - try { - auto mode = wireless_device_->Mode(); - return mode == 2; // NM_802_11_MODE_INFRA - } catch (const sdbus::Error &e) { - DBUS_LOG_PROPERTY_GET_ERROR(wireless_device_, "Mode", e); - return false; - } + LOG(WARNING) << __func__ + << ": Linux Wi-Fi Direct is GC-only; listening is unsupported"; + return nullptr; } bool NetworkManagerWifiDirectMedium::StartWifiDirect( - WifiDirectCredentials *wifi_direct_credentials) { - // According to the comments in the windows implementation, the wifi direct - // medium is currently just a regular wifi hotspot. - auto wireless_device = std::make_unique( - network_manager_, wireless_device_->getProxy().getObjectPath()); - auto hotspot = NetworkManagerWifiHotspotMedium(network_manager_, - std::move(wireless_device)); - - HotspotCredentials hotspot_creds; - if (!hotspot.StartWifiHotspot(&hotspot_creds)) return false; - - wifi_direct_credentials->SetSSID(hotspot_creds.GetSSID()); - wifi_direct_credentials->SetPassword(hotspot_creds.GetPassword()); - wifi_direct_credentials->SetFrequency(hotspot_creds.GetFrequency()); - return true; + WifiDirectCredentials* wifi_direct_credentials) { + LOG(WARNING) << __func__ + << ": Linux Wi-Fi Direct autonomous GO is unsupported"; + return false; } bool NetworkManagerWifiDirectMedium::StopWifiDirect() { - auto wireless_device = std::make_unique( - network_manager_, wireless_device_->getProxy().getObjectPath()); - auto hotspot = NetworkManagerWifiHotspotMedium(network_manager_, - std::move(wireless_device)); - - return hotspot.DisconnectWifiHotspot(); + return true; } -} // namespace linux -} // namespace nearby +} // namespace nearby::linux diff --git a/internal/platform/implementation/linux/wifi_direct.h b/internal/platform/implementation/linux/wifi_direct.h index 42d773e8..2b09c2ab 100644 --- a/internal/platform/implementation/linux/wifi_direct.h +++ b/internal/platform/implementation/linux/wifi_direct.h @@ -14,51 +14,89 @@ #ifndef PLATFORM_IMPL_LINUX_WIFI_DIRECT_H_ #define PLATFORM_IMPL_LINUX_WIFI_DIRECT_H_ -#include +#include +#include #include +#include #include +#include +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/device_wifip2p_client.h" #include "internal/platform/implementation/linux/network_manager.h" -#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/linux/wifi_direct_utils.h" #include "internal/platform/implementation/wifi_direct.h" namespace nearby { namespace linux { -class NetworkManagerWifiDirectMedium : public api::WifiDirectMedium { +class NetworkManagerWifiDirectMedium + : public api::WifiDirectMedium, + public sdbus::ProxyInterfaces< + org::freedesktop::NetworkManager::Device::WifiP2P_proxy> { public: NetworkManagerWifiDirectMedium( std::shared_ptr network_manager, - std::unique_ptr wireless_device) - : system_bus_(network_manager->GetConnection()), + const sdbus::ObjectPath& wifi_p2p_device_path) + : ProxyInterfaces(*network_manager->GetConnection(), + sdbus::ServiceName("org.freedesktop.NetworkManager"), + wifi_p2p_device_path), + system_bus_(network_manager->GetConnection()), network_manager_(std::move(network_manager)), - wireless_device_(std::move(wireless_device)) {} + wifi_p2p_device_path_(wifi_p2p_device_path) { + registerProxy(); + } + ~NetworkManagerWifiDirectMedium() override; - bool IsInterfaceValid() const override { return true; } + bool IsInterfaceValid() const override { + return !wifi_p2p_device_path_.empty(); + } std::unique_ptr ConnectToService( absl::string_view ip_address, int port, - CancellationFlag *cancellation_flag) override; + CancellationFlag* cancellation_flag) override; std::unique_ptr ListenForService( int port) override; bool ConnectWifiDirect( - WifiDirectCredentials *wifi_direct_credentials) override; + const WifiDirectCredentials& wifi_direct_credentials) override; bool DisconnectWifiDirect() override; - bool StartWifiDirect(WifiDirectCredentials *wifi_direct_credentials) override; + bool StartWifiDirect(WifiDirectCredentials* wifi_direct_credentials) override; bool StopWifiDirect() override; - absl::optional> GetDynamicPortRange() + std::optional> GetDynamicPortRange() override { return std::nullopt; } + std::vector GetSupportedWifiDirectAuthTypes() + const override { + return {WifiDirectAuthType::WIFI_DIRECT_WITH_DEVICE_NAME}; + } + + protected: + void onPeerAdded(const sdbus::ObjectPath& peer) override; + void onPeerRemoved(const sdbus::ObjectPath& peer) override; + private: - bool ConnectedToWifi(); + using Peer = wifi_direct_internal::PeerInfo; + + std::optional FindPeerByName(absl::string_view device_name); + bool ActivatePeer(const Peer& peer); + void StopDiscovery(); std::shared_ptr system_bus_; std::shared_ptr network_manager_; - std::unique_ptr wireless_device_; + sdbus::ObjectPath wifi_p2p_device_path_; + + mutable absl::Mutex state_mutex_; + bool discovering_ ABSL_GUARDED_BY(state_mutex_) = false; + bool connected_ ABSL_GUARDED_BY(state_mutex_) = false; + std::string active_connection_path_ ABSL_GUARDED_BY(state_mutex_); + std::string local_ip_address_ ABSL_GUARDED_BY(state_mutex_); + std::string remote_ip_address_ ABSL_GUARDED_BY(state_mutex_); + absl::CondVar peer_changed_; }; } // namespace linux } // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_direct_socket.h b/internal/platform/implementation/linux/wifi_direct_socket.h index 293d1789..ce025e04 100644 --- a/internal/platform/implementation/linux/wifi_direct_socket.h +++ b/internal/platform/implementation/linux/wifi_direct_socket.h @@ -25,9 +25,10 @@ namespace linux { class WifiDirectSocket : public api::WifiDirectSocket { public: explicit WifiDirectSocket(TCPSocket socket) : socket_(std::move(socket)) {} + ~WifiDirectSocket() override { socket_.Close(); } - InputStream &GetInputStream() override { return socket_.GetInputStream(); } - OutputStream &GetOutputStream() override { return socket_.GetOutputStream(); } + InputStream& GetInputStream() override { return socket_.GetInputStream(); } + OutputStream& GetOutputStream() override { return socket_.GetOutputStream(); } Exception Close() override { return socket_.Close(); }; diff --git a/internal/platform/implementation/linux/wifi_direct_utils.cc b/internal/platform/implementation/linux/wifi_direct_utils.cc new file mode 100644 index 00000000..97923a0f --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct_utils.cc @@ -0,0 +1,65 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/wifi_direct_utils.h" + +#include +#include +#include +#include +#include + +#include "absl/strings/match.h" + +namespace nearby::linux::wifi_direct_internal { +namespace { + +constexpr std::uint32_t kWpsMethodPushButton = 0x4; + +} // namespace + +std::optional SelectPeerByName(const std::vector& peers, + absl::string_view device_name) { + std::optional selected; + for (const PeerInfo& peer : peers) { + if (peer.hardware_address.empty() || + !absl::EqualsIgnoreCase(peer.name, device_name)) { + continue; + } + if (!selected.has_value() || peer.strength > selected->strength || + (peer.strength == selected->strength && + peer.last_seen > selected->last_seen)) { + selected = peer; + } + } + return selected; +} + +ConnectionSettings BuildGcConnectionSettings(absl::string_view uuid, + absl::string_view peer_address) { + return { + {"connection", + {{"uuid", sdbus::Variant(std::string(uuid))}, + {"id", sdbus::Variant(std::string("Nearby Wi-Fi Direct"))}, + {"type", sdbus::Variant(std::string("wifi-p2p"))}, + {"autoconnect", sdbus::Variant(false)}}}, + {"wifi-p2p", + {{"peer", sdbus::Variant(std::string(peer_address))}, + {"wps-method", sdbus::Variant(kWpsMethodPushButton)}}}, + {"ipv4", {{"method", sdbus::Variant(std::string("auto"))}}}, + {"ipv6", {{"method", sdbus::Variant(std::string("disabled"))}}}, + }; +} + +} // namespace nearby::linux::wifi_direct_internal diff --git a/internal/platform/implementation/linux/wifi_direct_utils.h b/internal/platform/implementation/linux/wifi_direct_utils.h new file mode 100644 index 00000000..333b0639 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct_utils.h @@ -0,0 +1,49 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_DIRECT_UTILS_H_ +#define PLATFORM_IMPL_LINUX_WIFI_DIRECT_UTILS_H_ + +#include +#include +#include +#include +#include + +#include + +#include "absl/strings/string_view.h" + +namespace nearby::linux::wifi_direct_internal { + +struct PeerInfo { + sdbus::ObjectPath path; + std::string name; + std::string hardware_address; + std::uint8_t strength = 0; + std::int32_t last_seen = -1; +}; + +using ConnectionSettings = + std::map>; + +std::optional SelectPeerByName(const std::vector& peers, + absl::string_view device_name); + +ConnectionSettings BuildGcConnectionSettings(absl::string_view uuid, + absl::string_view peer_address); + +} // namespace nearby::linux::wifi_direct_internal + +#endif // PLATFORM_IMPL_LINUX_WIFI_DIRECT_UTILS_H_ diff --git a/internal/platform/implementation/linux/wifi_direct_utils_test.cc b/internal/platform/implementation/linux/wifi_direct_utils_test.cc new file mode 100644 index 00000000..627e9917 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct_utils_test.cc @@ -0,0 +1,96 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/wifi_direct_utils.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace nearby::linux::wifi_direct_internal { +namespace { + +TEST(WifiDirectUtilsTest, SelectsPeerCaseInsensitively) { + std::vector peers{ + {.path = sdbus::ObjectPath("/peer/1"), + .name = "Android_1234", + .hardware_address = "02:00:00:00:00:01", + .strength = 50, + .last_seen = 10}, + }; + + auto selected = SelectPeerByName(peers, "android_1234"); + + ASSERT_TRUE(selected.has_value()); + EXPECT_EQ(selected->path, "/peer/1"); +} + +TEST(WifiDirectUtilsTest, SelectsStrongestThenMostRecentDuplicate) { + std::vector peers{ + {.path = sdbus::ObjectPath("/peer/old"), + .name = "Android", + .hardware_address = "02:00:00:00:00:01", + .strength = 60, + .last_seen = 10}, + {.path = sdbus::ObjectPath("/peer/weak"), + .name = "Android", + .hardware_address = "02:00:00:00:00:02", + .strength = 40, + .last_seen = 30}, + {.path = sdbus::ObjectPath("/peer/new"), + .name = "Android", + .hardware_address = "02:00:00:00:00:03", + .strength = 60, + .last_seen = 20}, + }; + + auto selected = SelectPeerByName(peers, "Android"); + + ASSERT_TRUE(selected.has_value()); + EXPECT_EQ(selected->path, "/peer/new"); +} + +TEST(WifiDirectUtilsTest, IgnoresWrongNameAndMissingAddress) { + std::vector peers{ + {.path = sdbus::ObjectPath("/peer/1"), + .name = "Other", + .hardware_address = "aa:bb"}, + {.path = sdbus::ObjectPath("/peer/2"), + .name = "Android", + .hardware_address = ""}, + }; + + EXPECT_FALSE(SelectPeerByName(peers, "Android").has_value()); +} + +TEST(WifiDirectUtilsTest, BuildsVolatileP2pProfilePayload) { + ConnectionSettings settings = + BuildGcConnectionSettings("uuid-1", "02:00:00:00:00:01"); + + EXPECT_EQ(settings.at("connection").at("uuid").get(), "uuid-1"); + EXPECT_EQ(settings.at("connection").at("type").get(), + "wifi-p2p"); + EXPECT_FALSE(settings.at("connection").at("autoconnect").get()); + EXPECT_EQ(settings.at("wifi-p2p").at("peer").get(), + "02:00:00:00:00:01"); + EXPECT_EQ(settings.at("wifi-p2p").at("wps-method").get(), + std::uint32_t{0x4}); + EXPECT_EQ(settings.at("ipv4").at("method").get(), "auto"); + EXPECT_EQ(settings.at("ipv6").at("method").get(), "disabled"); +} + +} // namespace +} // namespace nearby::linux::wifi_direct_internal diff --git a/internal/platform/implementation/linux/wifi_lan.cc b/internal/platform/implementation/linux/wifi_lan.cc index 87bec32b..b7630233 100644 --- a/internal/platform/implementation/linux/wifi_lan.cc +++ b/internal/platform/implementation/linux/wifi_lan.cc @@ -41,23 +41,23 @@ namespace { constexpr char kDeviceIpv4TxtRecord[] = "IPv4"; std::string GetActiveIpv4Address( - const std::shared_ptr &network_manager, - const std::shared_ptr &system_bus) { + const std::shared_ptr& network_manager, + const std::shared_ptr& system_bus) { std::vector connection_paths; try { connection_paths = network_manager->ActiveConnections(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_PROPERTY_GET_ERROR(network_manager, "ActiveConnections", e); return {}; } - for (auto &path : connection_paths) { + for (auto& path : connection_paths) { auto active_connection = std::make_unique(system_bus, path); std::string conn_type; try { conn_type = active_connection->Type(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_PROPERTY_GET_ERROR(active_connection, "Type", e); continue; } @@ -87,7 +87,7 @@ bool WifiLanMedium::IsNetworkConnected() const { } std::optional> entry_group_key( - const NsdServiceInfo &nsd_service_info) { + const NsdServiceInfo& nsd_service_info) { auto name = nsd_service_info.GetServiceName(); if (name.empty()) { LOG(ERROR) << __func__ << ": service name cannot be empty"; @@ -103,7 +103,7 @@ std::optional> entry_group_key( return std::make_pair(std::move(name), std::move(type)); } -bool WifiLanMedium::StartAdvertising(const NsdServiceInfo &nsd_service_info) { +bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) { auto key = entry_group_key(nsd_service_info); if (!key.has_value()) { return false; @@ -113,7 +113,7 @@ bool WifiLanMedium::StartAdvertising(const NsdServiceInfo &nsd_service_info) { absl::ReaderMutexLock l(&entry_groups_mutex_); if (entry_groups_.count(*key) == 1) { LOG(ERROR) << __func__ - << ": advertising is already active for this service"; + << ": advertising is already active for this service"; return false; } } @@ -137,14 +137,15 @@ bool WifiLanMedium::StartAdvertising(const NsdServiceInfo &nsd_service_info) { sdbus::ObjectPath entry_group_path; try { entry_group_path = avahi_->EntryGroupNew(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(avahi_, "EntryGroupNew", e); return false; } auto entry_group = std::make_unique(*system_bus_, entry_group_path); - LOG(INFO) << __func__ << ": Adding avahi service with service type: " << nsd_service_info.GetServiceType(); + LOG(INFO) << __func__ << ": Adding avahi service with service type: " + << nsd_service_info.GetServiceType(); try { entry_group->AddService( @@ -153,10 +154,10 @@ bool WifiLanMedium::StartAdvertising(const NsdServiceInfo &nsd_service_info) { 0, nsd_service_info.GetServiceName(), nsd_service_info.GetServiceType(), std::string(), std::string(), nsd_service_info.GetPort(), txt_records); entry_group->Commit(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { LOG(ERROR) << __func__ << ": Got error '" << e.getName() - << "' with message '" << e.getMessage() - << "' while adding service"; + << "' with message '" << e.getMessage() + << "' while adding service"; return false; } @@ -166,7 +167,7 @@ bool WifiLanMedium::StartAdvertising(const NsdServiceInfo &nsd_service_info) { return true; } -bool WifiLanMedium::StopAdvertising(const NsdServiceInfo &nsd_service_info) { +bool WifiLanMedium::StopAdvertising(const NsdServiceInfo& nsd_service_info) { auto key = entry_group_key(nsd_service_info); if (!key.has_value()) { return false; @@ -175,7 +176,7 @@ bool WifiLanMedium::StopAdvertising(const NsdServiceInfo &nsd_service_info) { absl::MutexLock l(&entry_groups_mutex_); if (entry_groups_.count(*key) == 0) { LOG(ERROR) << __func__ - << ": Advertising is already inactive for this service."; + << ": Advertising is already inactive for this service."; return false; } @@ -184,15 +185,15 @@ bool WifiLanMedium::StopAdvertising(const NsdServiceInfo &nsd_service_info) { } bool WifiLanMedium::StartDiscovery( - const std::string &service_type, + const std::string& service_type, api::WifiLanMedium::DiscoveredServiceCallback callback) { { absl::ReaderMutexLock l(&service_browsers_mutex_); if (service_browsers_.count(service_type) != 0) { - auto &object = service_browsers_[service_type]; + auto& object = service_browsers_[service_type]; LOG(ERROR) << __func__ << ": A service browser for service type " - << service_type << " already exists at " - << object->getProxy().getObjectPath(); + << service_type << " already exists at " + << object->getProxy().getObjectPath(); return false; } } @@ -210,24 +211,23 @@ bool WifiLanMedium::StartDiscovery( << browser_object_path << " for service_type: " << service_type; absl::MutexLock l(&service_browsers_mutex_); - service_browsers_.emplace( - service_type, - std::make_unique( - *system_bus_, browser_object_path, avahi_)); - } catch (const sdbus::Error &e) { + service_browsers_.emplace(service_type, + std::make_unique( + *system_bus_, browser_object_path, avahi_)); + } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(avahi_, "ServiceBrowserPrepare", e); return false; } service_browsers_mutex_.ReaderLock(); - auto &browser = service_browsers_[service_type]; + auto& browser = service_browsers_[service_type]; service_browsers_mutex_.ReaderUnlock(); try { LOG(INFO) << __func__ << ": Starting service discovery for " - << browser->getProxy().getObjectPath(); + << browser->getProxy().getObjectPath(); browser->Start(); - } catch (const sdbus::Error &e) { + } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(browser, "Start", e); return false; } @@ -235,12 +235,12 @@ bool WifiLanMedium::StartDiscovery( return true; } -bool WifiLanMedium::StopDiscovery(const std::string &service_type) { +bool WifiLanMedium::StopDiscovery(const std::string& service_type) { absl::MutexLock l(&service_browsers_mutex_); if (service_browsers_.count(service_type) == 0) { LOG(ERROR) << __func__ << ": Service type " << service_type - << " has not been registered for discovery"; + << " has not been registered for discovery"; return false; } service_browsers_.erase(service_type); @@ -249,16 +249,16 @@ bool WifiLanMedium::StopDiscovery(const std::string &service_type) { } std::unique_ptr WifiLanMedium::ConnectToService( - const std::string &ip_address, int port, - CancellationFlag *cancellation_flag) { + const std::string& ip_address, int port, + CancellationFlag* cancellation_flag) { auto socket = TCPSocket::Connect(ip_address, port); if (!socket.has_value()) return nullptr; - return std::make_unique(*socket); + return std::make_unique(std::move(*socket)); } std::unique_ptr WifiLanMedium::ListenForService( int port) { - LOG(INFO)<< __func__ << ": Listening for service WifiLanMedium"; + LOG(INFO) << __func__ << ": Listening for service WifiLanMedium"; auto socket = TCPServerSocket::Listen(std::nullopt, port); if (!socket.has_value()) return nullptr; @@ -330,7 +330,7 @@ api::UpgradeAddressInfo WifiLanMedium::GetUpgradeAddressCandidates( // Convert to vector of chars in network byte order std::vector addr_bytes(4); std::memcpy(addr_bytes.data(), &addr.s_addr, 4); - + ipv4_addresses.push_back( ServiceAddress{.address = std::move(addr_bytes), .port = port}); has_ipv4_address = true;