From 3bf0f6a63b732a1a1ff856d4dc636ded9159e6ac Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 11:13:33 -0700 Subject: [PATCH 01/24] Add notstd lib and NotImplementedException. --- src/shared/CMakeLists.txt | 1 + src/shared/notstd/CMakeLists.txt | 19 +++++++++++++++ .../notstd/include/notstd/Exceptions.hxx | 24 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/shared/notstd/CMakeLists.txt create mode 100644 src/shared/notstd/include/notstd/Exceptions.hxx diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index 0df0d2ef..5b57c34e 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -1,2 +1,3 @@ +add_subdirectory(notstd) add_subdirectory(strings) diff --git a/src/shared/notstd/CMakeLists.txt b/src/shared/notstd/CMakeLists.txt new file mode 100644 index 00000000..9fa1a435 --- /dev/null +++ b/src/shared/notstd/CMakeLists.txt @@ -0,0 +1,19 @@ + +add_library(notstd INTERFACE) + +set(NOTSTD_PUBLIC_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/include) +set(NOTSTD_PUBLIC_INCLUDE_PREFIX ${NOTSTD_PUBLIC_INCLUDE}/notstd) + +target_sources(notstd + PUBLIC + ${NOTSTD_PUBLIC_INCLUDE_PREFIX}/Exceptions.hxx +) + +list(APPEND NOTSTD_PUBLIC_HEADERS + ${NOTSTD_PUBLIC_INCLUDE_PREFIX}/Exceptions.hxx +) + +set_target_properties(notstd PROPERTIES + PUBLIC_HEADER "${NOTSTD_PUBLIC_HEADERS}" + INTERFACE_INCLUDE_DIRECTORIES "${NOTSTD_PUBLIC_INCLUDE}" +) diff --git a/src/shared/notstd/include/notstd/Exceptions.hxx b/src/shared/notstd/include/notstd/Exceptions.hxx new file mode 100644 index 00000000..7773c07f --- /dev/null +++ b/src/shared/notstd/include/notstd/Exceptions.hxx @@ -0,0 +1,24 @@ + +#ifndef EXCEPTIONS_HXX +#define EXCEPTIONS_HXX + +#include +#include +#include + +namespace notstd +{ +/** + * @brief Simple exception to indicate a function is not implemented. + */ +struct NotImplementedException : + public std::logic_error +{ + NotImplementedException(std::source_location location = std::source_location::current(), std::string_view message = "") : + std::logic_error(std::format("{}({}:{}) `{}` not implemented {}", location.file_name(), location.line(), location.column(), location.function_name(), (!std::empty(message) ? std::string(": ").append(message) : ""))) + { + } +}; +} // namespace notstd + +#endif // EXCEPTIONS_HXX From 3ab5a3182c1a244ca36b48a257cfd64c24db33aa Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 11:41:23 -0700 Subject: [PATCH 02/24] Ass basic interface for hostapd control. --- linux/wpa-controller/CMakeLists.txt | 5 ++ linux/wpa-controller/Hostapd.cxx | 38 ++++++++++ linux/wpa-controller/include/Wpa/Hostapd.hxx | 69 +++++++++++++++++++ linux/wpa-controller/include/Wpa/IHostapd.hxx | 61 ++++++++++++++++ .../unit/linux/wpa-controller/CMakeLists.txt | 1 + .../unit/linux/wpa-controller/TestHostapd.cxx | 18 +++++ 6 files changed, 192 insertions(+) create mode 100644 linux/wpa-controller/Hostapd.cxx create mode 100644 linux/wpa-controller/include/Wpa/Hostapd.hxx create mode 100644 linux/wpa-controller/include/Wpa/IHostapd.hxx create mode 100644 tests/unit/linux/wpa-controller/TestHostapd.cxx diff --git a/linux/wpa-controller/CMakeLists.txt b/linux/wpa-controller/CMakeLists.txt index 713396ce..87b69c7b 100644 --- a/linux/wpa-controller/CMakeLists.txt +++ b/linux/wpa-controller/CMakeLists.txt @@ -6,11 +6,14 @@ set(WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX ${WPA_CONTROLLER_PUBLIC_INCLUDE}/Wpa) target_sources(wpa-controller PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/Hostapd.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaCommand.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaCore.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaController.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaResponse.cxx PUBLIC + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/Hostapd.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommand.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx @@ -25,7 +28,9 @@ target_include_directories(wpa-controller target_link_libraries(wpa-controller PUBLIC libwpa-client + PRIVATE magic_enum::magic_enum + notstd ) list(APPEND WPA_CONTROLLER_PUBLIC_HEADERS diff --git a/linux/wpa-controller/Hostapd.cxx b/linux/wpa-controller/Hostapd.cxx new file mode 100644 index 00000000..9ad20f6f --- /dev/null +++ b/linux/wpa-controller/Hostapd.cxx @@ -0,0 +1,38 @@ + +#include + +#include + +using namespace Wpa; + +Hostapd::Hostapd(std::string_view interfaceName) : + m_interface(interfaceName), + m_controller(interfaceName, WpaType::Hostapd) +{ +} + +std::string_view Hostapd::GetInterface() +{ + return m_interface; +} + +bool Hostapd::Ping() +{ + throw notstd::NotImplementedException(); +} + +bool Hostapd::IsEnabled() +{ + // TODO: mutual exclusion + return m_isEnabled; +} + +bool Hostapd::Enable() +{ + throw notstd::NotImplementedException(); +} + +bool Hostapd::Disable() +{ + throw notstd::NotImplementedException(); +} \ No newline at end of file diff --git a/linux/wpa-controller/include/Wpa/Hostapd.hxx b/linux/wpa-controller/include/Wpa/Hostapd.hxx new file mode 100644 index 00000000..a5a17a4d --- /dev/null +++ b/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -0,0 +1,69 @@ + +#ifndef HOSTAPD_HXX +#define HOSTAPD_HXX + +#include +#include + +#include +#include + +namespace Wpa +{ +/** + * @brief Concrete implementation of the IHostapd interface. + */ +struct Hostapd : + public IHostapd +{ + /** + * @brief Construct a new Hostapd object. + * + * @param interfaceName The name of the intrerface to control. Eg. wlan1. + */ + Hostapd(std::string_view interfaceName); + + /** + * @brief Get the name of the interface hostapd is managing. + * + * @return std::string_view + */ + std::string_view GetInterface() override; + + /** + * @brief Checks connectivity to the hostapd daemon. + */ + bool Ping() override; + + /** + * @brief Determines if the interface is enabled for use. + * + * @return true + * @return false + */ + bool IsEnabled() override; + + /** + * @brief Enables the interface for use. + * + * @return true + * @return false + */ + bool Enable() override; + + /** + * @brief Disables the interface for use. + * + * @return true + * @return false + */ + bool Disable() override; + +private: + bool m_isEnabled{false}; + const std::string m_interface; + WpaController m_controller; +}; +} // namespace Wpa + +#endif // HOSTAPD_HXX diff --git a/linux/wpa-controller/include/Wpa/IHostapd.hxx b/linux/wpa-controller/include/Wpa/IHostapd.hxx new file mode 100644 index 00000000..416265ab --- /dev/null +++ b/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -0,0 +1,61 @@ + +#ifndef I_HOSTAPD_HXX +#define I_HOSTAPD_HXX + +#include + +namespace Wpa +{ +/** + * @brief Interface for interacting with the hostapd daemon. + * + * The documentation for the control interface is sparse, so the source code + * hostapd_ctrl_iface_receiv_process() was used as a reference: + * https://w1.fi/cgit/hostap/tree/hostapd/ctrl_iface.c?h=hostap_2_10#n3503. + */ +struct IHostapd +{ + /** + * @brief Default destructor supporting polymorphic destruction. + */ + virtual ~IHostapd() = default; + + /** + * @brief Get the name of the interface hostapd is managing. + * + * @return std::string_view + */ + virtual std::string_view GetInterface() = 0; + + /** + * @brief Checks connectivity to the hostapd daemon. + */ + virtual bool Ping() = 0; + + /** + * @brief Determines if the interface is enabled for use. + * + * @return true + * @return false + */ + virtual bool IsEnabled() = 0; + + /** + * @brief Enables the interface for use. + * + * @return true + * @return false + */ + virtual bool Enable() = 0; + + /** + * @brief Disables the interface for use. + * + * @return true + * @return false + */ + virtual bool Disable() = 0; +}; +} // namespace Wpa + +#endif // I_HOSTAPD_HXX diff --git a/tests/unit/linux/wpa-controller/CMakeLists.txt b/tests/unit/linux/wpa-controller/CMakeLists.txt index 23edc767..a1015cad 100644 --- a/tests/unit/linux/wpa-controller/CMakeLists.txt +++ b/tests/unit/linux/wpa-controller/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(wpa-controller-test-unit detail/WifiVirtualDeviceManager.cxx detail/WpaDaemonCatch2EventListener.cxx detail/WpaDaemonManager.cxx + TestHostapd.cxx TestWpaController.cxx ) diff --git a/tests/unit/linux/wpa-controller/TestHostapd.cxx b/tests/unit/linux/wpa-controller/TestHostapd.cxx new file mode 100644 index 00000000..6cb03f5e --- /dev/null +++ b/tests/unit/linux/wpa-controller/TestHostapd.cxx @@ -0,0 +1,18 @@ + +#include + +#include +#include + +#include "detail/WpaDaemonManager.hxx" + +TEST_CASE("Create a Hostapd", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + SECTION("Create doesn't cause a crash") + { + std::optional hostapd; + REQUIRE_NOTHROW(hostapd.emplace(WpaDaemonManager::InterfaceNameDefault)); + } +} From 1795e88d0f240740cfa8b91749ea4b1073b0cc93 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 11:42:10 -0700 Subject: [PATCH 03/24] Add test for interface name. --- tests/unit/linux/wpa-controller/TestHostapd.cxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/linux/wpa-controller/TestHostapd.cxx b/tests/unit/linux/wpa-controller/TestHostapd.cxx index 6cb03f5e..99d44122 100644 --- a/tests/unit/linux/wpa-controller/TestHostapd.cxx +++ b/tests/unit/linux/wpa-controller/TestHostapd.cxx @@ -15,4 +15,10 @@ TEST_CASE("Create a Hostapd", "[wpa][hostapd][client][remote]") std::optional hostapd; REQUIRE_NOTHROW(hostapd.emplace(WpaDaemonManager::InterfaceNameDefault)); } + + SECTION("Create reflects correct interface name") + { + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + REQUIRE(hostapd.GetInterface() == WpaDaemonManager::InterfaceNameDefault); + } } From 57ae1e62f2f4e3ef9ee4106de5c600ecfc23575f Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 11:49:11 -0700 Subject: [PATCH 04/24] Add constexpr ctor for WpaCommand. --- linux/wpa-controller/WpaCommand.cxx | 5 ----- linux/wpa-controller/include/Wpa/WpaCommand.hxx | 7 +++++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/linux/wpa-controller/WpaCommand.cxx b/linux/wpa-controller/WpaCommand.cxx index d38391f5..26fe5f90 100644 --- a/linux/wpa-controller/WpaCommand.cxx +++ b/linux/wpa-controller/WpaCommand.cxx @@ -2,8 +2,3 @@ #include using namespace Wpa; - -WpaCommand::WpaCommand(std::string_view data) : - Data(data) -{ -} diff --git a/linux/wpa-controller/include/Wpa/WpaCommand.hxx b/linux/wpa-controller/include/Wpa/WpaCommand.hxx index 9021e633..4d681ac3 100644 --- a/linux/wpa-controller/include/Wpa/WpaCommand.hxx +++ b/linux/wpa-controller/include/Wpa/WpaCommand.hxx @@ -13,8 +13,11 @@ namespace Wpa */ struct WpaCommand { - WpaCommand() = default; - WpaCommand(std::string_view data); + constexpr WpaCommand() = default; + constexpr WpaCommand(std::string_view data) : + Data(data) + { + } std::string Data; }; From 447e075f212ccc05fcf243e4294c896bc740aec4 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 12:09:48 -0700 Subject: [PATCH 05/24] Implement Hostapd::Ping(). --- linux/wpa-controller/Hostapd.cxx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/linux/wpa-controller/Hostapd.cxx b/linux/wpa-controller/Hostapd.cxx index 9ad20f6f..b8dd1098 100644 --- a/linux/wpa-controller/Hostapd.cxx +++ b/linux/wpa-controller/Hostapd.cxx @@ -18,7 +18,17 @@ std::string_view Hostapd::GetInterface() bool Hostapd::Ping() { - throw notstd::NotImplementedException(); + static constexpr auto PingCommandPayload = "PING"; + static constexpr auto PingResponsePayload = "PONG"; + static constexpr WpaCommand PingCommand(PingCommandPayload); + + const auto response = m_controller.SendCommand(PingCommand); + if (!response) + { + return false; + } + + return (response->Payload.starts_with(PingResponsePayload)); } bool Hostapd::IsEnabled() @@ -35,4 +45,4 @@ bool Hostapd::Enable() bool Hostapd::Disable() { throw notstd::NotImplementedException(); -} \ No newline at end of file +} From 2895107d73b11f304870f86367c0a7be3e7e9d80 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 12:09:54 -0700 Subject: [PATCH 06/24] Add basic tests. --- .../unit/linux/wpa-controller/TestHostapd.cxx | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/unit/linux/wpa-controller/TestHostapd.cxx b/tests/unit/linux/wpa-controller/TestHostapd.cxx index 99d44122..27544e66 100644 --- a/tests/unit/linux/wpa-controller/TestHostapd.cxx +++ b/tests/unit/linux/wpa-controller/TestHostapd.cxx @@ -6,7 +6,7 @@ #include "detail/WpaDaemonManager.hxx" -TEST_CASE("Create a Hostapd", "[wpa][hostapd][client][remote]") +TEST_CASE("Create a Hostapd instance", "[wpa][hostapd][client][remote]") { using namespace Wpa; @@ -16,9 +16,42 @@ TEST_CASE("Create a Hostapd", "[wpa][hostapd][client][remote]") REQUIRE_NOTHROW(hostapd.emplace(WpaDaemonManager::InterfaceNameDefault)); } + SECTION("Create multiple for same interface doesn't cause a crash") + { + std::optional hostapd1; + REQUIRE_NOTHROW(hostapd1.emplace(WpaDaemonManager::InterfaceNameDefault)); + + std::optional hostapd2; + REQUIRE_NOTHROW(hostapd2.emplace(WpaDaemonManager::InterfaceNameDefault)); + } + + SECTION("Sending command on invalid interface fails") + { + Hostapd hostapd("invalid"); + REQUIRE_FALSE(hostapd.Ping()); + } + SECTION("Create reflects correct interface name") { Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); REQUIRE(hostapd.GetInterface() == WpaDaemonManager::InterfaceNameDefault); } } + +TEST_CASE("Send Ping() command", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + + SECTION("Send Ping() command") + { + REQUIRE(hostapd.Ping()); + } + + SECTION("Send Ping() command on second instance") + { + Hostapd hostapd2(WpaDaemonManager::InterfaceNameDefault); + REQUIRE(hostapd2.Ping()); + } +} From 386301df56e7a5505c394030bf3215380c659ab9 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 13:22:31 -0700 Subject: [PATCH 07/24] Fix typo. --- linux/wpa-controller/include/Wpa/IHostapd.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/wpa-controller/include/Wpa/IHostapd.hxx b/linux/wpa-controller/include/Wpa/IHostapd.hxx index 416265ab..b19f7bae 100644 --- a/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -10,7 +10,7 @@ namespace Wpa * @brief Interface for interacting with the hostapd daemon. * * The documentation for the control interface is sparse, so the source code - * hostapd_ctrl_iface_receiv_process() was used as a reference: + * hostapd_ctrl_iface_receive_process() was used as a reference: * https://w1.fi/cgit/hostap/tree/hostapd/ctrl_iface.c?h=hostap_2_10#n3503. */ struct IHostapd From 7a1ed95e91e45c8d5d70a4b7a49773293bec2499 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 14:28:31 -0700 Subject: [PATCH 08/24] Add helpers to WpaResponse. --- linux/wpa-controller/WpaResponse.cxx | 19 ++++++- .../include/Wpa/WpaResponse.hxx | 50 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/linux/wpa-controller/WpaResponse.cxx b/linux/wpa-controller/WpaResponse.cxx index 9eec8776..3953723e 100644 --- a/linux/wpa-controller/WpaResponse.cxx +++ b/linux/wpa-controller/WpaResponse.cxx @@ -1,9 +1,26 @@ #include +#include using namespace Wpa; WpaResponse::WpaResponse(std::string_view payload) : - Payload(payload) + m_payload(payload), + Payload(m_payload) { } + +WpaResponse::operator bool() const +{ + return IsOk(); +} + +bool WpaResponse::IsOk() const +{ + return (ProtocolWpa::IsResponseOk(Payload)); +} + +bool WpaResponse::Failed() const +{ + return (ProtocolWpa::IsResponseFail(Payload)); +} diff --git a/linux/wpa-controller/include/Wpa/WpaResponse.hxx b/linux/wpa-controller/include/Wpa/WpaResponse.hxx index a3346bbd..6ea8eefe 100644 --- a/linux/wpa-controller/include/Wpa/WpaResponse.hxx +++ b/linux/wpa-controller/include/Wpa/WpaResponse.hxx @@ -13,12 +13,60 @@ namespace Wpa */ struct WpaResponse { + /** + * @brief Destroy the WpaResponse object. + */ virtual ~WpaResponse() = default; + /** + * @brief Construct an empty WpaResponse object. + * + */ WpaResponse() = default; + + /** + * @brief Construct a WpaResponse object with the specified payload. + * + * @param payload The payload of a WPA control command response. + */ WpaResponse(std::string_view payload); - std::string Payload; + /** + * @brief Implicit bool operator allowing WpaResponse to be used directly in + * condition statements (eg. if (response) // ...). + * + * @return true + * @return false + */ + operator bool() const; + + /** + * @brief Indicates whether the response describes a successful result. + * + * @return true + * @return false + */ + bool IsOk() const; + + /** + * @brief Indicates whether the response describes a failed result. + * + * @return true + * @return false + */ + bool Failed() const; + +private: + /** + * @brief Note: this must be declared prior to Payload since Payload is + * initialized with its contents and the constructor initialization order + * follows the order of declaration irrespective of the order of + * initialization in the constructor. + */ + const std::string m_payload; + +public: + std::string_view Payload; }; } // namespace Wpa From 4610d5753310b8d91e334c32f74747f9c613cfc5 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 14:30:19 -0700 Subject: [PATCH 09/24] Add common wpa protocol definitions. --- linux/wpa-controller/ProtocolWpa.cxx | 16 +++++ .../include/Wpa/ProtocolWpa.hxx | 71 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 linux/wpa-controller/ProtocolWpa.cxx create mode 100644 linux/wpa-controller/include/Wpa/ProtocolWpa.hxx diff --git a/linux/wpa-controller/ProtocolWpa.cxx b/linux/wpa-controller/ProtocolWpa.cxx new file mode 100644 index 00000000..7c311064 --- /dev/null +++ b/linux/wpa-controller/ProtocolWpa.cxx @@ -0,0 +1,16 @@ + +#include + +using namespace Wpa; + +/* static */ +bool ProtocolWpa::IsResponseOk(std::string_view response) +{ + return (response.starts_with(ResponsePayloadOk)); +} + +/* static */ +bool ProtocolWpa::IsResponseFail(std::string_view response) +{ + return (response.starts_with(ResponsePayloadFail)); +} diff --git a/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx b/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx new file mode 100644 index 00000000..1bf852a9 --- /dev/null +++ b/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx @@ -0,0 +1,71 @@ + +#ifndef PROTOCOL_WPA_HXX +#define PROTOCOL_WPA_HXX + +#include + +namespace Wpa +{ +/** + * @brief Shared/common WPA daemon protocol data and helpers. + */ +struct ProtocolWpa +{ + // Command payloads. + static constexpr auto CommandPayloadPing = "PING"; + static constexpr auto CommandPayloadEnable = "ENABLE"; + static constexpr auto CommandPayloadDisable = "DISABLE"; + static constexpr auto CommandPayloadStatus = "STATUS"; + static constexpr auto CommandPayloadTerminate = "TERMINATE"; + static constexpr auto CommandPayloadRelog = "RELOG"; + static constexpr auto CommandPayloadGet = "GET"; + static constexpr auto CommandPayloadSet = "SET"; + static constexpr auto CommandPayloadNote = "NOTE"; + static constexpr auto CommandPayloadMib = "MIB"; + static constexpr auto CommandPayloadPmksa = "PMKSA"; + static constexpr auto CommandPayloadPmksaFlush = "PMKSA_FLUSH"; + static constexpr auto CommandPayloadGetCapability = "GET_CAPABILITY"; + static constexpr auto CommandPayloadUpdateBeacon = "UPDATE_BEACON"; + static constexpr auto CommandPayloadChannelSwitch = "CHAN_SWITCH"; + static constexpr auto CommandPayloadVendor = "VENDOR"; + static constexpr auto CommandPayloadFlush = "FLUSH"; + static constexpr auto CommandPayloadInterfaces = "INTERFACES"; + static constexpr auto CommandPayloadDuplicateNetwork = "DUP_NETWORK"; + static constexpr auto CommandPayloadDriverFlags = "DRIVER_FLAGS"; + static constexpr auto CommandPayloadDriverFlags2 = "DRIVER_FLAGS2"; + static constexpr auto CommandPayloadDeauthenticate = "DEAUTHENTICATE"; + static constexpr auto CommandPayloadDisassociate = "DISASSOCIATE"; + static constexpr auto CommandPayloadAcceptAcl = "ACCEPT_ACL"; + static constexpr auto CommandPayloadDenyAcl = "DENY_ACL"; + static constexpr auto CommandPayloadAclAddMac = "ADD_MAC"; + static constexpr auto CommandPayloadAclDeleteMac = "DEL_MAC"; + static constexpr auto CommandPayloadAclShow = "SHOW"; + static constexpr auto CommandPayloadAclClear = "CLEAR"; + static constexpr auto CommandPayloadLogLevel = "LOG_LEVEL"; + + // Response payloads. + static constexpr auto ResponsePayloadOk = "OK"; + static constexpr auto ResponsePayloadFail = "FAIL"; + static constexpr auto ResponsePayloadPing = "PONG"; + + /** + * @brief Determines if a response payload indicates success. + * + * @param response The response payload to check. + * @return true + * @return false + */ + static bool IsResponseOk(std::string_view response); + + /** + * @brief Determines if a response payload indicates failure. + * + * @param response The response payload to check. + * @return true + * @return false + */ + static bool IsResponseFail(std::string_view response); +}; +} // namespace Wpa + +#endif // PROTOCOL_WPA_HXX From 33121c2cf29dc445b7d380d7a3bf86298fdca523 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 14:30:51 -0700 Subject: [PATCH 10/24] Add+impl terminate command. --- linux/wpa-controller/CMakeLists.txt | 10 +++++++ linux/wpa-controller/Hostapd.cxx | 27 ++++++++++++++----- linux/wpa-controller/include/Wpa/Hostapd.hxx | 8 ++++++ linux/wpa-controller/include/Wpa/IHostapd.hxx | 8 ++++++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/linux/wpa-controller/CMakeLists.txt b/linux/wpa-controller/CMakeLists.txt index 87b69c7b..b8b486ff 100644 --- a/linux/wpa-controller/CMakeLists.txt +++ b/linux/wpa-controller/CMakeLists.txt @@ -7,12 +7,16 @@ set(WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX ${WPA_CONTROLLER_PUBLIC_INCLUDE}/Wpa) target_sources(wpa-controller PRIVATE ${CMAKE_CURRENT_LIST_DIR}/Hostapd.cxx + ${CMAKE_CURRENT_LIST_DIR}/ProtocolWpa.cxx + ${CMAKE_CURRENT_LIST_DIR}/ProtocolHostapd.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaCommand.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaCore.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaController.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaResponse.cxx PUBLIC ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/Hostapd.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpa.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommand.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx @@ -34,8 +38,14 @@ target_link_libraries(wpa-controller ) list(APPEND WPA_CONTROLLER_PUBLIC_HEADERS + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/Hostapd.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpa.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolHostapd.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IHostapd.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommand.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx ) set_target_properties(wpa-controller PROPERTIES diff --git a/linux/wpa-controller/Hostapd.cxx b/linux/wpa-controller/Hostapd.cxx index b8dd1098..b6c89fa9 100644 --- a/linux/wpa-controller/Hostapd.cxx +++ b/linux/wpa-controller/Hostapd.cxx @@ -2,6 +2,7 @@ #include #include +#include using namespace Wpa; @@ -18,9 +19,7 @@ std::string_view Hostapd::GetInterface() bool Hostapd::Ping() { - static constexpr auto PingCommandPayload = "PING"; - static constexpr auto PingResponsePayload = "PONG"; - static constexpr WpaCommand PingCommand(PingCommandPayload); + static constexpr WpaCommand PingCommand(ProtocolHostapd::CommandPayloadPing); const auto response = m_controller.SendCommand(PingCommand); if (!response) @@ -28,7 +27,7 @@ bool Hostapd::Ping() return false; } - return (response->Payload.starts_with(PingResponsePayload)); + return response->IsOk(); } bool Hostapd::IsEnabled() @@ -39,10 +38,26 @@ bool Hostapd::IsEnabled() bool Hostapd::Enable() { - throw notstd::NotImplementedException(); + static constexpr WpaCommand EnableCommand(ProtocolHostapd::CommandPayloadEnable); + + const auto response = m_controller.SendCommand(EnableCommand); + m_isEnabled = response->IsOk(); + return m_isEnabled; } bool Hostapd::Disable() { - throw notstd::NotImplementedException(); + static constexpr WpaCommand DisableCommand(ProtocolHostapd::CommandPayloadDisable); + + const auto response = m_controller.SendCommand(DisableCommand); + m_isEnabled = !response->IsOk(); + return !m_isEnabled; +} + +bool Hostapd::Terminate() +{ + static constexpr WpaCommand TerminateCommand(ProtocolHostapd::CommandPayloadTerminate); + + const auto response = m_controller.SendCommand(TerminateCommand); + return response->IsOk(); } diff --git a/linux/wpa-controller/include/Wpa/Hostapd.hxx b/linux/wpa-controller/include/Wpa/Hostapd.hxx index a5a17a4d..572c5b7c 100644 --- a/linux/wpa-controller/include/Wpa/Hostapd.hxx +++ b/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -59,6 +59,14 @@ struct Hostapd : */ bool Disable() override; + /** + * @brief Terminates the process hosting the daemon. + * + * @return true + * @return false + */ + bool Terminate() override; + private: bool m_isEnabled{false}; const std::string m_interface; diff --git a/linux/wpa-controller/include/Wpa/IHostapd.hxx b/linux/wpa-controller/include/Wpa/IHostapd.hxx index b19f7bae..3a1986fd 100644 --- a/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -55,6 +55,14 @@ struct IHostapd * @return false */ virtual bool Disable() = 0; + + /** + * @brief Terminates the process hosting the daemon. + * + * @return true + * @return false + */ + virtual bool Terminate() = 0; }; } // namespace Wpa From e98f50b4bfc385a63d7bc9a5f640c63d5624624e Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 14:48:11 -0700 Subject: [PATCH 11/24] Add empty hostapd protocol object. --- linux/wpa-controller/ProtocolHostapd.cxx | 4 ++++ .../include/Wpa/ProtocolHostapd.hxx | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 linux/wpa-controller/ProtocolHostapd.cxx create mode 100644 linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx diff --git a/linux/wpa-controller/ProtocolHostapd.cxx b/linux/wpa-controller/ProtocolHostapd.cxx new file mode 100644 index 00000000..9ef5e5c8 --- /dev/null +++ b/linux/wpa-controller/ProtocolHostapd.cxx @@ -0,0 +1,4 @@ + +#include + +using namespace Wpa; diff --git a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx new file mode 100644 index 00000000..ff818308 --- /dev/null +++ b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -0,0 +1,16 @@ + +#ifndef HOSTAPD_PROTOCOL_HXX +#define HOSTAPD_PROTOCOL_HXX + +#include + +namespace Wpa +{ +struct ProtocolHostapd : + public ProtocolWpa +{ + +}; +} // namespace Wpa + +#endif // HOSTAPD_PROTOCOL_HXX From 0b101cb544d3967a7175a8598cdfeb000984822b Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 17:03:56 -0700 Subject: [PATCH 12/24] Add a lot of protocol definitions. --- linux/wpa-controller/CMakeLists.txt | 7 +- linux/wpa-controller/WpaCommandStatus.cxx | 10 ++ .../include/Wpa/ProtocolHostapd.hxx | 59 ++++++++- .../include/Wpa/WpaCommandStatus.hxx | 16 +++ .../include/Wpa/WpaResponseStatus.hxx | 121 ++++++++++++++++++ 5 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 linux/wpa-controller/WpaCommandStatus.cxx create mode 100644 linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx create mode 100644 linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx diff --git a/linux/wpa-controller/CMakeLists.txt b/linux/wpa-controller/CMakeLists.txt index b8b486ff..11391c30 100644 --- a/linux/wpa-controller/CMakeLists.txt +++ b/linux/wpa-controller/CMakeLists.txt @@ -7,11 +7,12 @@ set(WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX ${WPA_CONTROLLER_PUBLIC_INCLUDE}/Wpa) target_sources(wpa-controller PRIVATE ${CMAKE_CURRENT_LIST_DIR}/Hostapd.cxx - ${CMAKE_CURRENT_LIST_DIR}/ProtocolWpa.cxx ${CMAKE_CURRENT_LIST_DIR}/ProtocolHostapd.cxx + ${CMAKE_CURRENT_LIST_DIR}/ProtocolWpa.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaCommand.cxx - ${CMAKE_CURRENT_LIST_DIR}/WpaCore.cxx + ${CMAKE_CURRENT_LIST_DIR}/WpaCommandStatus.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaController.cxx + ${CMAKE_CURRENT_LIST_DIR}/WpaCore.cxx ${CMAKE_CURRENT_LIST_DIR}/WpaResponse.cxx PUBLIC ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/Hostapd.hxx @@ -19,6 +20,7 @@ target_sources(wpa-controller ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommand.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommandStatus.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx @@ -43,6 +45,7 @@ list(APPEND WPA_CONTROLLER_PUBLIC_HEADERS ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommand.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommandStatus.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx diff --git a/linux/wpa-controller/WpaCommandStatus.cxx b/linux/wpa-controller/WpaCommandStatus.cxx new file mode 100644 index 00000000..78c0b5c3 --- /dev/null +++ b/linux/wpa-controller/WpaCommandStatus.cxx @@ -0,0 +1,10 @@ + +#include +#include + +using namespace Wpa; + +constexpr WpaCommandStatus::WpaCommandStatus() : + WpaCommand(ProtocolWpa::CommandPayloadStatus) +{ +} diff --git a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx index ff818308..bbb9376f 100644 --- a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -2,14 +2,71 @@ #ifndef HOSTAPD_PROTOCOL_HXX #define HOSTAPD_PROTOCOL_HXX +#include +#include + #include namespace Wpa { +enum class HostapdInterfaceState +{ + Uninitialized, + Disabled, + Enabled, + CountryUpdate, + Acs, + HtScan, + Dfs, + NoIr, + Unknown +}; + +enum class HostapdHwMode +{ + Unknown, + Ieee80211b, + Ieee80211g, // 2.4 GHz + Ieee80211a, // 5 GHz + Ieee80211ad, + Ieee80211any, +}; + +struct MulticastListenerDiscoveryProtocolInfo +{ + int Id; + int LinkId; + std::string MacAddress; // Colon separated hex string with 6 bytes, eg. '%02x:%02x:%02x:%02x:%02x:%02x'. +}; + +using MldInfo = MulticastListenerDiscoveryProtocolInfo; + +struct BssInfo +{ + int Index; + int NumStations{ 0 }; + std::string Bssid; + std::string Interface; + + // Present with: + // - CONFIG_IEEE80211BE compilation option + // - The AP is part of an AP MLD. + std::optional Mld; +}; + struct ProtocolHostapd : public ProtocolWpa { - + // Response values for the 'STATE=' property from the "STATUS" command response. + static constexpr auto ResponsePayloadStatusUninitialized = "UNINITIALIZED"; + static constexpr auto ResponsePayloadStatusDisabled = "DISABLED"; + static constexpr auto ResponsePayloadStatusEnabled = "ENABLED"; + static constexpr auto ResponsePayloadStatusCountryUpdate = "COUNTRY_UPDATE"; + static constexpr auto ResponsePayloadStatusAcs = "ACS"; + static constexpr auto ResponsePayloadStatusHtScan = "HT_SCAN"; + static constexpr auto ResponsePayloadStatusDfs = "DFS"; + static constexpr auto ResponsePayloadStatusNoIr = "NO_IR"; + static constexpr auto ResponsePayloadStatusUnknown = "UNKNOWN"; }; } // namespace Wpa diff --git a/linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx b/linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx new file mode 100644 index 00000000..7104ed44 --- /dev/null +++ b/linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx @@ -0,0 +1,16 @@ + +#ifndef WPA_COMMAND_STATUS_HXX +#define WPA_COMMAND_STATUS_HXX + +#include + +namespace Wpa +{ +struct WpaCommandStatus : + public WpaCommand +{ + constexpr WpaCommandStatus(); +}; +} // namespace Wpa + +#endif // WPA_COMMAND_STATUS_HXX diff --git a/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx b/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx new file mode 100644 index 00000000..479c7e50 --- /dev/null +++ b/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx @@ -0,0 +1,121 @@ + +#ifndef WPA_RESPONSE_STATUS_HXX +#define WPA_RESPONSE_STATUS_HXX + +#include +#include +#include +#include +#include + +#include +#include + +namespace Wpa +{ +struct WpaResponseStatus + : public WpaResponse +{ + WpaResponseStatus() = default; + + // TODO: All types used below are direct translations of the types used in + // the hostapd code; there may be better representations of them. + +// Always-present fields. + HostapdInterfaceState State{ HostapdInterfaceState::Unknown }; + std::string PhyRadio; // eg. phy=34 + int Frequency{ 0 }; // eg. 2412 + int NumberOfStationsNonErp{ 0 }; + int NumberOfStationsNoShortSlotTime{ 0 }; + int NumberOfStationsNoShortPreamble{ 0 }; + int OverlappingLegacyBssCondition{ 0 }; + int NumberOfStationsHtNoGreenfield{ 0 }; + int NumberOfStationsNonHt{ 0 }; + int NumberOfStationsHt20MHz{ 0 }; + int NumberOfStationsHt40Intolerant{ 0 }; + int OverlappingLegacyBssConditionInformation{ 0 }; + uint16_t HtOperationalMode{ 0 }; + + // Present only if a current hwmode is set. + std::optional HwMode; + // Present only if first rwo chars of country code are non-zero. + std::optional> CountryCode; + + int CacTimeSeconds{ 0 }; + // Present only if CAC is in progress. + std::optional CacTimeRemainingSeconds; + + uint8_t Channel{ 0 }; + int EdmgEnable{ 0 }; + uint8_t EdmgChannel{ 0 }; + int SecondaryChannel{ 0 }; + int Ieee80211n{ 0 }; + int Ieee80211ac{ 0 }; + int Ieee80211ax{ 0 }; + int Ieee80211be{ 0 }; + uint16_t BeaconInterval{ 0 }; + int DtimPeriod{ 0 }; + + // Present with: + // - CONFIG_IEEE80211BE compilation option + // - 'ieee80211be=1' configuration file option + std::optional EhtOperationalChannelWidth; + std::optional EhtOperationalCenterFrequencySegment0; + + // Present with: + // - CONFIG_IEEE80211AX compilation option + // - 'ieee80211ax=1' configuration file option + std::optional HeOperationalChannelWidth; + std::optional HeOperationalCenterFrequencySegment0; + std::optional HeOperationalCenterFrequencySegment1; + // Present with: + // - CONFIG_IEEE80211AX compilation option + // - 'ieee80211ax=1' configuration file option + // - 'he_bss_color=1' configuration file option (default: enabled) + std::optional HeBssColor; + + // Present with: + // - 'ieee80211ac=1' configuration file option + // - 'disable_11ac=0' configuration file option + std::optional VhtOperationalChannelWidth; + std::optional VhtOperationalCenterFrequencySegment0; + std::optional VhtOperationalCenterFrequencySegment1; + std::optional VhtCapabilitiesInfo; + + // Present with: + // - 'ieee80211ac=1' configuration file option + // - 'disable_11ac=0' configuration file option + // - hostapd current operational mode is set to 'ac' + std::optional Bss; + + // Present with: + // - Non-zero channel utilization. + unsigned ChannelUtilitzationAverage; +}; +} // namespace Wpa + +#endif // WPA_RESPONSE_COMMAND_STATUS_HXX From 0518c465e5c83ef77dc458f5bcdce8cbbf887e98 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 17:06:24 -0700 Subject: [PATCH 13/24] Partially implement Hostapd::IsEnabled. --- linux/wpa-controller/Hostapd.cxx | 47 ++++++++++++++++++- linux/wpa-controller/include/Wpa/Hostapd.hxx | 11 ++++- linux/wpa-controller/include/Wpa/IHostapd.hxx | 12 ++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/linux/wpa-controller/Hostapd.cxx b/linux/wpa-controller/Hostapd.cxx index b6c89fa9..b02dfd87 100644 --- a/linux/wpa-controller/Hostapd.cxx +++ b/linux/wpa-controller/Hostapd.cxx @@ -1,4 +1,7 @@ +#include +#include + #include #include @@ -30,9 +33,43 @@ bool Hostapd::Ping() return response->IsOk(); } -bool Hostapd::IsEnabled() +bool Hostapd::IsEnabled(bool forceCheck) { - // TODO: mutual exclusion + static constexpr WpaCommand StatusCommand(ProtocolHostapd::CommandPayloadStatus); + + if (!forceCheck) + { + std::shared_lock stateLockShared{ m_stateGate }; + return m_isEnabled; + } + + std::scoped_lock stateLockExclusive{ m_stateGate }; + auto response = m_controller.SendCommand(StatusCommand); + if (!response->IsOk()) + { + // Return false, but don't update the cached state since we didn't + // actually get a valid response. + // TODO: log this + return false; + } + + static constexpr std::string_view StateKey = "state="; + + std::optional isEnabled; + auto keyPosition = response->Payload.find(StateKey); + if (keyPosition == response->Payload.npos) + { + // TODO: the response should *always* have this field, so this is normally + // catastrophic. For now, log and move on. + return false; + } + + // Obtain the value as a string. + const auto valuePosition = keyPosition + std::size(StateKey); + const char * const value = std::data(response->Payload) + valuePosition; + + // Attempt to parse the string value as a state response value. + return m_isEnabled; } @@ -40,6 +77,12 @@ bool Hostapd::Enable() { static constexpr WpaCommand EnableCommand(ProtocolHostapd::CommandPayloadEnable); + std::scoped_lock stateLockExclusive{ m_stateGate }; + if (m_isEnabled) + { + return true; + } + const auto response = m_controller.SendCommand(EnableCommand); m_isEnabled = response->IsOk(); return m_isEnabled; diff --git a/linux/wpa-controller/include/Wpa/Hostapd.hxx b/linux/wpa-controller/include/Wpa/Hostapd.hxx index 572c5b7c..28042d85 100644 --- a/linux/wpa-controller/include/Wpa/Hostapd.hxx +++ b/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -2,6 +2,7 @@ #ifndef HOSTAPD_HXX #define HOSTAPD_HXX +#include #include #include @@ -38,10 +39,14 @@ struct Hostapd : /** * @brief Determines if the interface is enabled for use. * + * @param forceCheck Whether or not the interface should be probed for its + * state. When this is false, the cached state will be used. Otherwise, it + * will be determined directly by probing it from the remote daemon + * instance. * @return true * @return false */ - bool IsEnabled() override; + bool IsEnabled(bool forceCheck = false) override; /** * @brief Enables the interface for use. @@ -69,6 +74,10 @@ struct Hostapd : private: bool m_isEnabled{false}; + // Protects any state that can be modified by multiple threads including: + // - m_isEnabled + std::shared_mutex m_stateGate; + const std::string m_interface; WpaController m_controller; }; diff --git a/linux/wpa-controller/include/Wpa/IHostapd.hxx b/linux/wpa-controller/include/Wpa/IHostapd.hxx index 3a1986fd..facae45b 100644 --- a/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -32,13 +32,23 @@ struct IHostapd */ virtual bool Ping() = 0; + /** + * + * @return true + * @return false + */ + /** * @brief Determines if the interface is enabled for use. * + * @param forceCheck Whether or not the interface should be probed for its + * state. When this is false, the cached state will be used. Otherwise, it + * will be determined directly by probing it from the remote daemon + * instance. * @return true * @return false */ - virtual bool IsEnabled() = 0; + virtual bool IsEnabled(bool forceCheck) = 0; /** * @brief Enables the interface for use. From 091530642e6048cd9771a5576783a2149e2b4530 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 18:53:30 -0700 Subject: [PATCH 14/24] Add helpers for conversion from strings. --- linux/wpa-controller/ProtocolHostapd.cxx | 59 +++++++++++++++++++ .../include/Wpa/ProtocolHostapd.hxx | 18 ++++++ 2 files changed, 77 insertions(+) diff --git a/linux/wpa-controller/ProtocolHostapd.cxx b/linux/wpa-controller/ProtocolHostapd.cxx index 9ef5e5c8..b4dd4a4e 100644 --- a/linux/wpa-controller/ProtocolHostapd.cxx +++ b/linux/wpa-controller/ProtocolHostapd.cxx @@ -2,3 +2,62 @@ #include using namespace Wpa; + +HostapdInterfaceState Wpa::HostapdInterfaceStateFromString(std::string_view state) noexcept +{ + // Implementation uses start_with() instead of equals() to accommodate + // unparsed payloads from command responses. + if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusUninitialized)) + { + return HostapdInterfaceState::Uninitialized; + } + else if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusDisabled)) + { + return HostapdInterfaceState::Disabled; + } + else if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusEnabled)) + { + return HostapdInterfaceState::Enabled; + } + else if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusCountryUpdate)) + { + return HostapdInterfaceState::CountryUpdate; + } + else if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusAcs)) + { + return HostapdInterfaceState::Acs; + } + else if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusHtScan)) + { + return HostapdInterfaceState::HtScan; + } + else if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusDfs)) + { + return HostapdInterfaceState::Dfs; + } + else if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusNoIr)) + { + return HostapdInterfaceState::NoIr; + } + + return HostapdInterfaceState::Unknown; +} + +bool Wpa::IsHostapdStateOperational(HostapdInterfaceState state) noexcept +{ + switch (state) + { + case HostapdInterfaceState::Enabled: + case HostapdInterfaceState::CountryUpdate: + case HostapdInterfaceState::Acs: + case HostapdInterfaceState::HtScan: + case HostapdInterfaceState::Dfs: + case HostapdInterfaceState::NoIr: + return true; + case HostapdInterfaceState::Uninitialized: + case HostapdInterfaceState::Disabled: + case HostapdInterfaceState::Unknown: + default: + return false; + } +} diff --git a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx index bbb9376f..ba0ea16c 100644 --- a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -22,6 +22,24 @@ enum class HostapdInterfaceState Unknown }; +/** + * @brief Converts a string to a HostapdInterfaceState. + * HostapdInterfaceState::Unknown is returned for invalid input. + * + * @param state The state string to convert. + * @return HostapdInterfaceState + */ +HostapdInterfaceState HostapdInterfaceStateFromString(std::string_view state) noexcept; + +/** + * @brief Indicates if the given state describes an operational interface. + * + * @param state The state to check. + * @return true If the interface is operational. + * @return false If the interface is not operational. + */ +bool IsHostapdStateOperational(HostapdInterfaceState state) noexcept; + enum class HostapdHwMode { Unknown, From eb9925f69d472fd24ef6e7eec3f8e157cea783b8 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:08:29 -0700 Subject: [PATCH 15/24] Move HostapdStatus into own structure. --- .../include/Wpa/ProtocolHostapd.hxx | 142 +++++++++++++++--- .../include/Wpa/WpaResponseStatus.hxx | 104 +------------ 2 files changed, 125 insertions(+), 121 deletions(-) diff --git a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx index ba0ea16c..861300d8 100644 --- a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -2,13 +2,19 @@ #ifndef HOSTAPD_PROTOCOL_HXX #define HOSTAPD_PROTOCOL_HXX +#include +#include #include #include +#include #include namespace Wpa { +/** + * @brief Describes the state of the interface. + */ enum class HostapdInterfaceState { Uninitialized, @@ -22,24 +28,6 @@ enum class HostapdInterfaceState Unknown }; -/** - * @brief Converts a string to a HostapdInterfaceState. - * HostapdInterfaceState::Unknown is returned for invalid input. - * - * @param state The state string to convert. - * @return HostapdInterfaceState - */ -HostapdInterfaceState HostapdInterfaceStateFromString(std::string_view state) noexcept; - -/** - * @brief Indicates if the given state describes an operational interface. - * - * @param state The state to check. - * @return true If the interface is operational. - * @return false If the interface is not operational. - */ -bool IsHostapdStateOperational(HostapdInterfaceState state) noexcept; - enum class HostapdHwMode { Unknown, @@ -72,6 +60,106 @@ struct BssInfo std::optional Mld; }; +struct HostapdStatus +{ + // TODO: All types used below are direct translations of the types used in + // the hostapd code; there may be better representations of them. + + HostapdInterfaceState State{ HostapdInterfaceState::Unknown }; + std::string PhyRadio; // eg. phy=34 + int Frequency{ 0 }; // eg. 2412 + int NumberOfStationsNonErp{ 0 }; + int NumberOfStationsNoShortSlotTime{ 0 }; + int NumberOfStationsNoShortPreamble{ 0 }; + int OverlappingLegacyBssCondition{ 0 }; + int NumberOfStationsHtNoGreenfield{ 0 }; + int NumberOfStationsNonHt{ 0 }; + int NumberOfStationsHt20MHz{ 0 }; + int NumberOfStationsHt40Intolerant{ 0 }; + int OverlappingLegacyBssConditionInformation{ 0 }; + uint16_t HtOperationalMode{ 0 }; + + // Present only if a current hwmode is set. + std::optional HwMode; + // Present only if first rwo chars of country code are non-zero. + std::optional> CountryCode; + + int CacTimeSeconds{ 0 }; + // Present only if CAC is in progress. + std::optional CacTimeRemainingSeconds; + + uint8_t Channel{ 0 }; + int EdmgEnable{ 0 }; + uint8_t EdmgChannel{ 0 }; + int SecondaryChannel{ 0 }; + int Ieee80211n{ 0 }; + int Ieee80211ac{ 0 }; + int Ieee80211ax{ 0 }; + int Ieee80211be{ 0 }; + uint16_t BeaconInterval{ 0 }; + int DtimPeriod{ 0 }; + + // Present with: + // - CONFIG_IEEE80211BE compilation option + // - 'ieee80211be=1' configuration file option + std::optional EhtOperationalChannelWidth; + std::optional EhtOperationalCenterFrequencySegment0; + + // Present with: + // - CONFIG_IEEE80211AX compilation option + // - 'ieee80211ax=1' configuration file option + std::optional HeOperationalChannelWidth; + std::optional HeOperationalCenterFrequencySegment0; + std::optional HeOperationalCenterFrequencySegment1; + // Present with: + // - CONFIG_IEEE80211AX compilation option + // - 'ieee80211ax=1' configuration file option + // - 'he_bss_color=1' configuration file option (default: enabled) + std::optional HeBssColor; + + // Present with: + // - 'ieee80211ac=1' configuration file option + // - 'disable_11ac=0' configuration file option + std::optional VhtOperationalChannelWidth; + std::optional VhtOperationalCenterFrequencySegment0; + std::optional VhtOperationalCenterFrequencySegment1; + std::optional VhtCapabilitiesInfo; + + // Present with: + // - 'ieee80211ac=1' configuration file option + // - 'disable_11ac=0' configuration file option + // - hostapd current operational mode is set to 'ac' + std::optional RxVhtMcsMap; + std::optional TxVhtMcsMap; + + // Present with: + // - 'ieee80211n=1' configuration file option + // - 'disable_11n=0' configuration file option + std::optional HtCapabilitiesInfo; + + // Present with: + // - 'ieee80211n=1' configuration file option + // - 'disable_11n=0' configuration file option + // hostapd current operational mode is set to 'n' + std::optional HtMcsBitmask; // Unseparated hex string. + + // Present with: + // - At least one rate is supported with current configuration. + std::optional SupportedRates; // Space separated hex string, eg. '%02x %02x ....'. + + + // Present with: + // - The current operational mode supports the currently active frequency. + std::optional MaxTxPower; + + // Always present (but may be empty). + std::vector Bss; + + // Present with: + // - Non-zero channel utilization. + unsigned ChannelUtilitzationAverage; +}; + struct ProtocolHostapd : public ProtocolWpa { @@ -86,6 +174,24 @@ struct ProtocolHostapd : static constexpr auto ResponsePayloadStatusNoIr = "NO_IR"; static constexpr auto ResponsePayloadStatusUnknown = "UNKNOWN"; }; + +/** + * @brief Converts a string to a HostapdInterfaceState. + * HostapdInterfaceState::Unknown is returned for invalid input. + * + * @param state The state string to convert. + * @return HostapdInterfaceState + */ +HostapdInterfaceState HostapdInterfaceStateFromString(std::string_view state) noexcept; + +/** + * @brief Indicates if the given state describes an operational interface. + * + * @param state The state to check. + * @return true If the interface is operational. + * @return false If the interface is not operational. + */ +bool IsHostapdStateOperational(HostapdInterfaceState state) noexcept; } // namespace Wpa #endif // HOSTAPD_PROTOCOL_HXX diff --git a/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx b/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx index 479c7e50..611feba0 100644 --- a/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx +++ b/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx @@ -2,12 +2,6 @@ #ifndef WPA_RESPONSE_STATUS_HXX #define WPA_RESPONSE_STATUS_HXX -#include -#include -#include -#include -#include - #include #include @@ -18,103 +12,7 @@ struct WpaResponseStatus { WpaResponseStatus() = default; - // TODO: All types used below are direct translations of the types used in - // the hostapd code; there may be better representations of them. - -// Always-present fields. - HostapdInterfaceState State{ HostapdInterfaceState::Unknown }; - std::string PhyRadio; // eg. phy=34 - int Frequency{ 0 }; // eg. 2412 - int NumberOfStationsNonErp{ 0 }; - int NumberOfStationsNoShortSlotTime{ 0 }; - int NumberOfStationsNoShortPreamble{ 0 }; - int OverlappingLegacyBssCondition{ 0 }; - int NumberOfStationsHtNoGreenfield{ 0 }; - int NumberOfStationsNonHt{ 0 }; - int NumberOfStationsHt20MHz{ 0 }; - int NumberOfStationsHt40Intolerant{ 0 }; - int OverlappingLegacyBssConditionInformation{ 0 }; - uint16_t HtOperationalMode{ 0 }; - - // Present only if a current hwmode is set. - std::optional HwMode; - // Present only if first rwo chars of country code are non-zero. - std::optional> CountryCode; - - int CacTimeSeconds{ 0 }; - // Present only if CAC is in progress. - std::optional CacTimeRemainingSeconds; - - uint8_t Channel{ 0 }; - int EdmgEnable{ 0 }; - uint8_t EdmgChannel{ 0 }; - int SecondaryChannel{ 0 }; - int Ieee80211n{ 0 }; - int Ieee80211ac{ 0 }; - int Ieee80211ax{ 0 }; - int Ieee80211be{ 0 }; - uint16_t BeaconInterval{ 0 }; - int DtimPeriod{ 0 }; - - // Present with: - // - CONFIG_IEEE80211BE compilation option - // - 'ieee80211be=1' configuration file option - std::optional EhtOperationalChannelWidth; - std::optional EhtOperationalCenterFrequencySegment0; - - // Present with: - // - CONFIG_IEEE80211AX compilation option - // - 'ieee80211ax=1' configuration file option - std::optional HeOperationalChannelWidth; - std::optional HeOperationalCenterFrequencySegment0; - std::optional HeOperationalCenterFrequencySegment1; - // Present with: - // - CONFIG_IEEE80211AX compilation option - // - 'ieee80211ax=1' configuration file option - // - 'he_bss_color=1' configuration file option (default: enabled) - std::optional HeBssColor; - - // Present with: - // - 'ieee80211ac=1' configuration file option - // - 'disable_11ac=0' configuration file option - std::optional VhtOperationalChannelWidth; - std::optional VhtOperationalCenterFrequencySegment0; - std::optional VhtOperationalCenterFrequencySegment1; - std::optional VhtCapabilitiesInfo; - - // Present with: - // - 'ieee80211ac=1' configuration file option - // - 'disable_11ac=0' configuration file option - // - hostapd current operational mode is set to 'ac' - std::optional Bss; - - // Present with: - // - Non-zero channel utilization. - unsigned ChannelUtilitzationAverage; + HostapdStatus Status; }; } // namespace Wpa From e7bfd5a0c63a5d0a991ce6660caec0c3cc7ae778 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:08:44 -0700 Subject: [PATCH 16/24] Add GetStatus() interface command. --- linux/wpa-controller/Hostapd.cxx | 85 ++++++++++++------- linux/wpa-controller/include/Wpa/Hostapd.hxx | 21 ++--- linux/wpa-controller/include/Wpa/IHostapd.hxx | 34 ++++++-- 3 files changed, 90 insertions(+), 50 deletions(-) diff --git a/linux/wpa-controller/Hostapd.cxx b/linux/wpa-controller/Hostapd.cxx index b02dfd87..6e3161b9 100644 --- a/linux/wpa-controller/Hostapd.cxx +++ b/linux/wpa-controller/Hostapd.cxx @@ -1,5 +1,5 @@ -#include +#include #include #include @@ -27,65 +27,70 @@ bool Hostapd::Ping() const auto response = m_controller.SendCommand(PingCommand); if (!response) { - return false; + throw HostapdException("Failed to ping hostapd"); } - return response->IsOk(); + return response->Payload.starts_with(ProtocolHostapd::ResponsePayloadPing); } -bool Hostapd::IsEnabled(bool forceCheck) +HostapdStatus Hostapd::GetStatus() { static constexpr WpaCommand StatusCommand(ProtocolHostapd::CommandPayloadStatus); - if (!forceCheck) - { - std::shared_lock stateLockShared{ m_stateGate }; - return m_isEnabled; - } - - std::scoped_lock stateLockExclusive{ m_stateGate }; auto response = m_controller.SendCommand(StatusCommand); - if (!response->IsOk()) + if (!response) { - // Return false, but don't update the cached state since we didn't - // actually get a valid response. - // TODO: log this - return false; + throw HostapdException("Failed to send hostapd 'status' command"); } + HostapdStatus hostapdStatus{}; + + // Parse the response for the state field. Note that this code will later be + // replaced by proper generic response parsing code, so this is quick-and-dirty. static constexpr std::string_view StateKey = "state="; - std::optional isEnabled; + // Find interface state string in payload. auto keyPosition = response->Payload.find(StateKey); if (keyPosition == response->Payload.npos) { - // TODO: the response should *always* have this field, so this is normally - // catastrophic. For now, log and move on. - return false; + // The response should always have this field and not much can be done without it. + throw HostapdException("hostapd 'status' command response missing state field"); } - // Obtain the value as a string. + // Convert value string to corresponding enum value. const auto valuePosition = keyPosition + std::size(StateKey); - const char * const value = std::data(response->Payload) + valuePosition; + const auto* value = std::data(response->Payload) + valuePosition; + hostapdStatus.State = HostapdInterfaceStateFromString(value); - // Attempt to parse the string value as a state response value. + return hostapdStatus; +} - return m_isEnabled; +bool Hostapd::IsEnabled() +{ + auto hostapdStatus = GetStatus(); + return IsHostapdStateOperational(hostapdStatus.State); } bool Hostapd::Enable() { static constexpr WpaCommand EnableCommand(ProtocolHostapd::CommandPayloadEnable); - std::scoped_lock stateLockExclusive{ m_stateGate }; - if (m_isEnabled) + const auto response = m_controller.SendCommand(EnableCommand); + if (!response) + { + throw HostapdException("Failed to send hostapd 'enable' command"); + } + else if (response->IsOk()) { return true; } - const auto response = m_controller.SendCommand(EnableCommand); - m_isEnabled = response->IsOk(); - return m_isEnabled; + // The response will indicate failure if the interface is already enabled. + // Check if this is the case by validating the interface status. + // This is only done if the 'enable' command fails since the GetStatus() + // command is fairly heavy-weight in terms of its payload. + const auto status = GetStatus(); + return IsHostapdStateOperational(status.State); } bool Hostapd::Disable() @@ -93,8 +98,21 @@ bool Hostapd::Disable() static constexpr WpaCommand DisableCommand(ProtocolHostapd::CommandPayloadDisable); const auto response = m_controller.SendCommand(DisableCommand); - m_isEnabled = !response->IsOk(); - return !m_isEnabled; + if (!response) + { + throw HostapdException("Failed to send hostapd 'disable' command"); + } + else if (response->IsOk()) + { + return true; + } + + // The response will indicate failure if the interface is already disabled. + // Check if this is the case by validating the interface status. + // This is only done if the 'disable' command fails since the GetStatus() + // command is fairly heavy-weight in terms of its payload. + const auto status = GetStatus(); + return !IsHostapdStateOperational(status.State); } bool Hostapd::Terminate() @@ -102,5 +120,10 @@ bool Hostapd::Terminate() static constexpr WpaCommand TerminateCommand(ProtocolHostapd::CommandPayloadTerminate); const auto response = m_controller.SendCommand(TerminateCommand); + if (!response) + { + throw HostapdException("Failed to send hostapd 'terminate' command"); + } + return response->IsOk(); } diff --git a/linux/wpa-controller/include/Wpa/Hostapd.hxx b/linux/wpa-controller/include/Wpa/Hostapd.hxx index 28042d85..832d9246 100644 --- a/linux/wpa-controller/include/Wpa/Hostapd.hxx +++ b/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -2,7 +2,6 @@ #ifndef HOSTAPD_HXX #define HOSTAPD_HXX -#include #include #include @@ -37,16 +36,19 @@ struct Hostapd : bool Ping() override; /** - * @brief Determines if the interface is enabled for use. + * @brief Get the status for the interface. * - * @param forceCheck Whether or not the interface should be probed for its - * state. When this is false, the cached state will be used. Otherwise, it - * will be determined directly by probing it from the remote daemon - * instance. + * @return HostapdStatus + */ + HostapdStatus GetStatus() override; + + /** + * @brief Determines if the interface is enabled for use. + * * @return true * @return false */ - bool IsEnabled(bool forceCheck = false) override; + bool IsEnabled() override; /** * @brief Enables the interface for use. @@ -73,11 +75,6 @@ struct Hostapd : bool Terminate() override; private: - bool m_isEnabled{false}; - // Protects any state that can be modified by multiple threads including: - // - m_isEnabled - std::shared_mutex m_stateGate; - const std::string m_interface; WpaController m_controller; }; diff --git a/linux/wpa-controller/include/Wpa/IHostapd.hxx b/linux/wpa-controller/include/Wpa/IHostapd.hxx index facae45b..731be856 100644 --- a/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -2,8 +2,11 @@ #ifndef I_HOSTAPD_HXX #define I_HOSTAPD_HXX +#include #include +#include + namespace Wpa { /** @@ -33,22 +36,19 @@ struct IHostapd virtual bool Ping() = 0; /** + * @brief Get the status for the interface. * - * @return true - * @return false + * @return HostapdStatus */ + virtual HostapdStatus GetStatus() = 0; /** * @brief Determines if the interface is enabled for use. * - * @param forceCheck Whether or not the interface should be probed for its - * state. When this is false, the cached state will be used. Otherwise, it - * will be determined directly by probing it from the remote daemon - * instance. * @return true * @return false */ - virtual bool IsEnabled(bool forceCheck) = 0; + virtual bool IsEnabled() = 0; /** * @brief Enables the interface for use. @@ -74,6 +74,26 @@ struct IHostapd */ virtual bool Terminate() = 0; }; + +/** + * @brief Generic exception that may be thrown by any of the function in IHostapd + * + */ +struct HostapdException : std::exception +{ + HostapdException(std::string_view message) : + m_message(message) + { + } + + const char* what() const noexcept override + { + return m_message.c_str(); + } + +private: + const std::string m_message; +}; } // namespace Wpa #endif // I_HOSTAPD_HXX From e1fd71149d829cdec40f2de0588eaec99ff82831 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:08:56 -0700 Subject: [PATCH 17/24] Add more tests. --- .../unit/linux/wpa-controller/TestHostapd.cxx | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/tests/unit/linux/wpa-controller/TestHostapd.cxx b/tests/unit/linux/wpa-controller/TestHostapd.cxx index 27544e66..a7b797ea 100644 --- a/tests/unit/linux/wpa-controller/TestHostapd.cxx +++ b/tests/unit/linux/wpa-controller/TestHostapd.cxx @@ -1,8 +1,10 @@ #include +#include #include #include +#include #include "detail/WpaDaemonManager.hxx" @@ -28,7 +30,7 @@ TEST_CASE("Create a Hostapd instance", "[wpa][hostapd][client][remote]") SECTION("Sending command on invalid interface fails") { Hostapd hostapd("invalid"); - REQUIRE_FALSE(hostapd.Ping()); + REQUIRE_THROWS_AS(hostapd.Ping(), HostapdException); } SECTION("Create reflects correct interface name") @@ -55,3 +57,121 @@ TEST_CASE("Send Ping() command", "[wpa][hostapd][client][remote]") REQUIRE(hostapd2.Ping()); } } + +TEST_CASE("Send command: GetStatus()", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + REQUIRE(hostapd.Enable()); + + SECTION("GetStatus() doesn't throw") + { + REQUIRE_NOTHROW(hostapd.GetStatus()); + } + + SECTION("GetStatus() reflects initial 'state' to be operational") + { + const auto status = hostapd.GetStatus(); + REQUIRE(IsHostapdStateOperational(status.State)); + } + + SECTION("GetStatus() reflects changes in interface state (disabled -> not disabled)") + { + REQUIRE(hostapd.Disable()); + const auto statusInitial = hostapd.GetStatus(); + REQUIRE(statusInitial.State == HostapdInterfaceState::Disabled); + REQUIRE(hostapd.Enable()); + const auto statusDisabled = hostapd.GetStatus(); + REQUIRE(statusDisabled.State == HostapdInterfaceState::Enabled); + } + + SECTION("GetStatus() reflects changes in interface state (operational -> not operational)") + { + const auto statusInitial = hostapd.GetStatus(); + REQUIRE(IsHostapdStateOperational(statusInitial.State)); + REQUIRE(hostapd.Disable()); + const auto statusDisabled = hostapd.GetStatus(); + REQUIRE(!IsHostapdStateOperational(statusDisabled.State)); + } +} + +TEST_CASE("Send control commands: Enable(), Disable()", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + const auto hostapdStatusInitial = hostapd.GetStatus(); + + SECTION("Enable() doesn't throw") + { + REQUIRE_NOTHROW(hostapd.Enable()); + } + + SECTION("Enable() enables the interface") + { + CHECK(hostapd.Disable()); + auto hostapdStatus = hostapd.GetStatus(); + REQUIRE(hostapdStatus.State != HostapdInterfaceState::Enabled); + REQUIRE(hostapd.Enable()); + hostapdStatus = hostapd.GetStatus(); + REQUIRE(hostapdStatus.State == HostapdInterfaceState::Enabled); + } + + SECTION("Enable() preserves further communication") + { + CHECK(hostapd.Disable()); + CHECK(hostapd.Enable()); + REQUIRE(hostapd.Ping()); + } + + SECTION("Disable() doesn't throw") + { + REQUIRE_NOTHROW(hostapd.Disable()); + } + + SECTION("Disable() disables the interface") + { + CHECK(hostapd.Disable()); + const auto hostapdStatus = hostapd.GetStatus(); + REQUIRE(hostapdStatus.State == HostapdInterfaceState::Disabled); + } + + SECTION("Disable() doesn't prevent further communication") + { + CHECK(hostapd.Disable()); + REQUIRE(hostapd.Ping()); + } +} + +// Note: Terminate() tests kill the hostapd process and so need to have +// individual TEST_CASE()'es to ensure the daemon is started up each time. +// Also, keep Terminate() test cases at end-of-file since Catch2 will run +// tests in declaration order by default, which minimizes the possibility +// of a test case being run after the daemon has been terminated. +TEST_CASE("Send command: Terminate() doesn't throw", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + REQUIRE_NOTHROW(hostapd.Terminate()); +} + +TEST_CASE("Send command: Terminate() ping failure", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + using namespace std::chrono_literals; + + static constexpr auto TerminationWaitTime{ 2s }; + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + REQUIRE(hostapd.Ping()); + REQUIRE(hostapd.Terminate()); + + // The terminate command merely requests hostapd to shut down. The daemon's + // polling loop must enter its next cycle before the termination process is + // executed, so wait a little bit for that to happen. + std::this_thread::sleep_for(TerminationWaitTime); + + REQUIRE_THROWS_AS(hostapd.Ping(), HostapdException); +} From 7fce16cbf25f4e45d205ddfed0cd45c16979e99e Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:10:09 -0700 Subject: [PATCH 18/24] Remove IsEnabled() interface function. --- linux/wpa-controller/Hostapd.cxx | 6 ------ linux/wpa-controller/include/Wpa/Hostapd.hxx | 8 -------- linux/wpa-controller/include/Wpa/IHostapd.hxx | 8 -------- 3 files changed, 22 deletions(-) diff --git a/linux/wpa-controller/Hostapd.cxx b/linux/wpa-controller/Hostapd.cxx index 6e3161b9..f2851c40 100644 --- a/linux/wpa-controller/Hostapd.cxx +++ b/linux/wpa-controller/Hostapd.cxx @@ -65,12 +65,6 @@ HostapdStatus Hostapd::GetStatus() return hostapdStatus; } -bool Hostapd::IsEnabled() -{ - auto hostapdStatus = GetStatus(); - return IsHostapdStateOperational(hostapdStatus.State); -} - bool Hostapd::Enable() { static constexpr WpaCommand EnableCommand(ProtocolHostapd::CommandPayloadEnable); diff --git a/linux/wpa-controller/include/Wpa/Hostapd.hxx b/linux/wpa-controller/include/Wpa/Hostapd.hxx index 832d9246..aa56d5b3 100644 --- a/linux/wpa-controller/include/Wpa/Hostapd.hxx +++ b/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -42,14 +42,6 @@ struct Hostapd : */ HostapdStatus GetStatus() override; - /** - * @brief Determines if the interface is enabled for use. - * - * @return true - * @return false - */ - bool IsEnabled() override; - /** * @brief Enables the interface for use. * diff --git a/linux/wpa-controller/include/Wpa/IHostapd.hxx b/linux/wpa-controller/include/Wpa/IHostapd.hxx index 731be856..1efbdf05 100644 --- a/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -42,14 +42,6 @@ struct IHostapd */ virtual HostapdStatus GetStatus() = 0; - /** - * @brief Determines if the interface is enabled for use. - * - * @return true - * @return false - */ - virtual bool IsEnabled() = 0; - /** * @brief Enables the interface for use. * From f6472a0920c404eb976c697b62e346da96c76853 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:14:41 -0700 Subject: [PATCH 19/24] Comment out unparsed HpstapdStatus fields. --- .../include/Wpa/ProtocolHostapd.hxx | 193 +++++++++--------- 1 file changed, 100 insertions(+), 93 deletions(-) diff --git a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx index 861300d8..79d25c8f 100644 --- a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -66,98 +66,104 @@ struct HostapdStatus // the hostapd code; there may be better representations of them. HostapdInterfaceState State{ HostapdInterfaceState::Unknown }; - std::string PhyRadio; // eg. phy=34 - int Frequency{ 0 }; // eg. 2412 - int NumberOfStationsNonErp{ 0 }; - int NumberOfStationsNoShortSlotTime{ 0 }; - int NumberOfStationsNoShortPreamble{ 0 }; - int OverlappingLegacyBssCondition{ 0 }; - int NumberOfStationsHtNoGreenfield{ 0 }; - int NumberOfStationsNonHt{ 0 }; - int NumberOfStationsHt20MHz{ 0 }; - int NumberOfStationsHt40Intolerant{ 0 }; - int OverlappingLegacyBssConditionInformation{ 0 }; - uint16_t HtOperationalMode{ 0 }; - - // Present only if a current hwmode is set. - std::optional HwMode; - // Present only if first rwo chars of country code are non-zero. - std::optional> CountryCode; - - int CacTimeSeconds{ 0 }; - // Present only if CAC is in progress. - std::optional CacTimeRemainingSeconds; - - uint8_t Channel{ 0 }; - int EdmgEnable{ 0 }; - uint8_t EdmgChannel{ 0 }; - int SecondaryChannel{ 0 }; - int Ieee80211n{ 0 }; - int Ieee80211ac{ 0 }; - int Ieee80211ax{ 0 }; - int Ieee80211be{ 0 }; - uint16_t BeaconInterval{ 0 }; - int DtimPeriod{ 0 }; - // Present with: - // - CONFIG_IEEE80211BE compilation option - // - 'ieee80211be=1' configuration file option - std::optional EhtOperationalChannelWidth; - std::optional EhtOperationalCenterFrequencySegment0; - - // Present with: - // - CONFIG_IEEE80211AX compilation option - // - 'ieee80211ax=1' configuration file option - std::optional HeOperationalChannelWidth; - std::optional HeOperationalCenterFrequencySegment0; - std::optional HeOperationalCenterFrequencySegment1; - // Present with: - // - CONFIG_IEEE80211AX compilation option - // - 'ieee80211ax=1' configuration file option - // - 'he_bss_color=1' configuration file option (default: enabled) - std::optional HeBssColor; - - // Present with: - // - 'ieee80211ac=1' configuration file option - // - 'disable_11ac=0' configuration file option - std::optional VhtOperationalChannelWidth; - std::optional VhtOperationalCenterFrequencySegment0; - std::optional VhtOperationalCenterFrequencySegment1; - std::optional VhtCapabilitiesInfo; - - // Present with: - // - 'ieee80211ac=1' configuration file option - // - 'disable_11ac=0' configuration file option - // - hostapd current operational mode is set to 'ac' - std::optional RxVhtMcsMap; - std::optional TxVhtMcsMap; - - // Present with: - // - 'ieee80211n=1' configuration file option - // - 'disable_11n=0' configuration file option - std::optional HtCapabilitiesInfo; - - // Present with: - // - 'ieee80211n=1' configuration file option - // - 'disable_11n=0' configuration file option - // hostapd current operational mode is set to 'n' - std::optional HtMcsBitmask; // Unseparated hex string. - - // Present with: - // - At least one rate is supported with current configuration. - std::optional SupportedRates; // Space separated hex string, eg. '%02x %02x ....'. - - - // Present with: - // - The current operational mode supports the currently active frequency. - std::optional MaxTxPower; - - // Always present (but may be empty). - std::vector Bss; - - // Present with: - // - Non-zero channel utilization. - unsigned ChannelUtilitzationAverage; + // Note: The following fields do not yet have parsing code so they are + // commented out for the time being. Many of them may not be kept if they + // have no use in the implementation, but for now, this is a complete list + // of all fields that are returned from the 'STATUS' message. + + // std::string PhyRadio; // eg. phy=34 + // int Frequency{ 0 }; // eg. 2412 + // int NumberOfStationsNonErp{ 0 }; + // int NumberOfStationsNoShortSlotTime{ 0 }; + // int NumberOfStationsNoShortPreamble{ 0 }; + // int OverlappingLegacyBssCondition{ 0 }; + // int NumberOfStationsHtNoGreenfield{ 0 }; + // int NumberOfStationsNonHt{ 0 }; + // int NumberOfStationsHt20MHz{ 0 }; + // int NumberOfStationsHt40Intolerant{ 0 }; + // int OverlappingLegacyBssConditionInformation{ 0 }; + // uint16_t HtOperationalMode{ 0 }; + + // // Present only if a current hwmode is set. + // std::optional HwMode; + // // Present only if first rwo chars of country code are non-zero. + // std::optional> CountryCode; + + // int CacTimeSeconds{ 0 }; + // // Present only if CAC is in progress. + // std::optional CacTimeRemainingSeconds; + + // uint8_t Channel{ 0 }; + // int EdmgEnable{ 0 }; + // uint8_t EdmgChannel{ 0 }; + // int SecondaryChannel{ 0 }; + // int Ieee80211n{ 0 }; + // int Ieee80211ac{ 0 }; + // int Ieee80211ax{ 0 }; + // int Ieee80211be{ 0 }; + // uint16_t BeaconInterval{ 0 }; + // int DtimPeriod{ 0 }; + + // // Present with: + // // - CONFIG_IEEE80211BE compilation option + // // - 'ieee80211be=1' configuration file option + // std::optional EhtOperationalChannelWidth; + // std::optional EhtOperationalCenterFrequencySegment0; + + // // Present with: + // // - CONFIG_IEEE80211AX compilation option + // // - 'ieee80211ax=1' configuration file option + // std::optional HeOperationalChannelWidth; + // std::optional HeOperationalCenterFrequencySegment0; + // std::optional HeOperationalCenterFrequencySegment1; + // // Present with: + // // - CONFIG_IEEE80211AX compilation option + // // - 'ieee80211ax=1' configuration file option + // // - 'he_bss_color=1' configuration file option (default: enabled) + // std::optional HeBssColor; + + // // Present with: + // // - 'ieee80211ac=1' configuration file option + // // - 'disable_11ac=0' configuration file option + // std::optional VhtOperationalChannelWidth; + // std::optional VhtOperationalCenterFrequencySegment0; + // std::optional VhtOperationalCenterFrequencySegment1; + // std::optional VhtCapabilitiesInfo; + + // // Present with: + // // - 'ieee80211ac=1' configuration file option + // // - 'disable_11ac=0' configuration file option + // // - hostapd current operational mode is set to 'ac' + // std::optional RxVhtMcsMap; + // std::optional TxVhtMcsMap; + + // // Present with: + // // - 'ieee80211n=1' configuration file option + // // - 'disable_11n=0' configuration file option + // std::optional HtCapabilitiesInfo; + + // // Present with: + // // - 'ieee80211n=1' configuration file option + // // - 'disable_11n=0' configuration file option + // // hostapd current operational mode is set to 'n' + // std::optional HtMcsBitmask; // Unseparated hex string. + + // // Present with: + // // - At least one rate is supported with current configuration. + // std::optional SupportedRates; // Space separated hex string, eg. '%02x %02x ....'. + + + // // Present with: + // // - The current operational mode supports the currently active frequency. + // std::optional MaxTxPower; + + // // Always present (but may be empty). + // std::vector Bss; + + // // Present with: + // // - Non-zero channel utilization. + // unsigned ChannelUtilitzationAverage; }; struct ProtocolHostapd : @@ -177,10 +183,11 @@ struct ProtocolHostapd : /** * @brief Converts a string to a HostapdInterfaceState. - * HostapdInterfaceState::Unknown is returned for invalid input. * * @param state The state string to convert. - * @return HostapdInterfaceState + * @return HostapdInterfaceState The corresponding HostapdInterfaceState + * enumeration value, or HostapdInterfaceState::Unknown if an invalid state was + * provided. */ HostapdInterfaceState HostapdInterfaceStateFromString(std::string_view state) noexcept; From b21ce2e36a3f2ab9831478efcd67729dd9b44418 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:18:14 -0700 Subject: [PATCH 20/24] Fix formatting. --- linux/wpa-controller/ProtocolHostapd.cxx | 2 +- linux/wpa-controller/include/Wpa/IHostapd.hxx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/linux/wpa-controller/ProtocolHostapd.cxx b/linux/wpa-controller/ProtocolHostapd.cxx index b4dd4a4e..6960ddc9 100644 --- a/linux/wpa-controller/ProtocolHostapd.cxx +++ b/linux/wpa-controller/ProtocolHostapd.cxx @@ -5,7 +5,7 @@ using namespace Wpa; HostapdInterfaceState Wpa::HostapdInterfaceStateFromString(std::string_view state) noexcept { - // Implementation uses start_with() instead of equals() to accommodate + // Implementation uses starts_with() instead of equals() to accommodate // unparsed payloads from command responses. if (state.starts_with(ProtocolHostapd::ResponsePayloadStatusUninitialized)) { diff --git a/linux/wpa-controller/include/Wpa/IHostapd.hxx b/linux/wpa-controller/include/Wpa/IHostapd.hxx index 1efbdf05..0d11a278 100644 --- a/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -68,8 +68,8 @@ struct IHostapd }; /** - * @brief Generic exception that may be thrown by any of the function in IHostapd - * + * @brief Generic exception that may be thrown by any of the functions in + * IHostapd. */ struct HostapdException : std::exception { From bc9a3f78858431d4e319c8e5f17d7f9cbcab3155 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:24:11 -0700 Subject: [PATCH 21/24] Add comments. --- linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx | 3 +++ linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx b/linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx index 7104ed44..20f7d948 100644 --- a/linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx +++ b/linux/wpa-controller/include/Wpa/WpaCommandStatus.hxx @@ -6,6 +6,9 @@ namespace Wpa { +/** + * @brief Representation of the "STATUS" command. + */ struct WpaCommandStatus : public WpaCommand { diff --git a/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx b/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx index 611feba0..c3dbe750 100644 --- a/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx +++ b/linux/wpa-controller/include/Wpa/WpaResponseStatus.hxx @@ -7,6 +7,9 @@ namespace Wpa { +/** + * @brief Representation of the response to the "STATUS" command. + */ struct WpaResponseStatus : public WpaResponse { From 2c058e9a0c0d1aea1eb81886678db583b6ae969f Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:27:50 -0700 Subject: [PATCH 22/24] Fix typo. --- linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx index 79d25c8f..12719ad6 100644 --- a/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx +++ b/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -87,7 +87,7 @@ struct HostapdStatus // // Present only if a current hwmode is set. // std::optional HwMode; - // // Present only if first rwo chars of country code are non-zero. + // // Present only if first two chars of country code are non-zero. // std::optional> CountryCode; // int CacTimeSeconds{ 0 }; From 0b3d418fce101be2da3ea2d2a1cab3b5fe82ee75 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:55:12 -0700 Subject: [PATCH 23/24] Add test readme. --- tests/unit/linux/wpa-controller/README.md | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/unit/linux/wpa-controller/README.md diff --git a/tests/unit/linux/wpa-controller/README.md b/tests/unit/linux/wpa-controller/README.md new file mode 100644 index 00000000..677a4075 --- /dev/null +++ b/tests/unit/linux/wpa-controller/README.md @@ -0,0 +1,25 @@ + +# WPA Controller Tests + +## Wi-Fi Drivers and WPA Daemons + +Some of the tests that exercise wpa daemon (hostapd, wpa_supplicant) functionality require a wlan device driver and a running instance of the daemon. To satisfy these requirements, the mac80211_hwsim driver is used to create a virtualized wlan device that the wpa daemon can then control. Helpers are provided for managing the virtualized wlan device(s) with [WifiVirtualDeviceManager](./detail/WifiVirtualDeviceManager.hxx) and for managing the wpa daemons with [WpaDaemonManager](./detail/WpaDaemonManager.hxx) and [WpaDaemonInstance](./detail/WpaDaemonInstance.hxx). + +Presently, the tests use [event listeners](https://github.com/catchorg/Catch2/blob/devel/docs/event-listeners.md) from the [Catch2](https://github.com/catchorg/Catch2) unit test framework instead of traditional, class-based setup/teardown fixtures. The event listeners have access to test case information such as the test case name and their tags. The [WpaDaemonCatch2EventListener](./detail/WpaDaemonCatch2EventListener.hxx) was developed to detect when a test case requires either of the daemons, and automatically creates a wlan driver and starts the required daemon . Similarly, it handles removing the driver instances and stopping the daemons on test case completion. + +To trigger this behavior, the test case must include a tag that corresponds to the name of a [WpaType](../../../../linux/wpa-controller/include/Wpa/WpaCore.hxx) enumeration value (a case insensitive comparison is done). For example, if the hostapd daemon is needed for the test, then a tag corresponding to WpaType::Hosapd is needed in the test case definition, for example '**hostapd**' as shown below: + +```C++ +TEST_CASE("Send command: GetStatus()", "[wpa][hostapd][client][remote]") +``` + +Only the first `WpaType` will be used. Failure to add the appropriate tag will cause the test to fail since no daemon will be running and thus, no connection to it will be possible. + +### Future Work + +This method will be improved in the future, with some possible improvements including: + +1. Allow multiple wpa daemon types. +2. Allow a more generic daemon specification (eg. `[wpaDaemon=hostapd]`) +3. Replacement of event-based fixtures to class-based fixtures. + From fcd804cd98d2ae8d695d4de87bce52afd2cd11bd Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 21 Nov 2023 21:58:03 -0700 Subject: [PATCH 24/24] Fix formatting. --- tests/unit/linux/wpa-controller/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/unit/linux/wpa-controller/README.md b/tests/unit/linux/wpa-controller/README.md index 677a4075..ac9b25eb 100644 --- a/tests/unit/linux/wpa-controller/README.md +++ b/tests/unit/linux/wpa-controller/README.md @@ -5,9 +5,9 @@ Some of the tests that exercise wpa daemon (hostapd, wpa_supplicant) functionality require a wlan device driver and a running instance of the daemon. To satisfy these requirements, the mac80211_hwsim driver is used to create a virtualized wlan device that the wpa daemon can then control. Helpers are provided for managing the virtualized wlan device(s) with [WifiVirtualDeviceManager](./detail/WifiVirtualDeviceManager.hxx) and for managing the wpa daemons with [WpaDaemonManager](./detail/WpaDaemonManager.hxx) and [WpaDaemonInstance](./detail/WpaDaemonInstance.hxx). -Presently, the tests use [event listeners](https://github.com/catchorg/Catch2/blob/devel/docs/event-listeners.md) from the [Catch2](https://github.com/catchorg/Catch2) unit test framework instead of traditional, class-based setup/teardown fixtures. The event listeners have access to test case information such as the test case name and their tags. The [WpaDaemonCatch2EventListener](./detail/WpaDaemonCatch2EventListener.hxx) was developed to detect when a test case requires either of the daemons, and automatically creates a wlan driver and starts the required daemon . Similarly, it handles removing the driver instances and stopping the daemons on test case completion. +Presently, the tests use [event listeners](https://github.com/catchorg/Catch2/blob/devel/docs/event-listeners.md) from the [Catch2](https://github.com/catchorg/Catch2) unit test framework instead of traditional, class-based setup/teardown fixtures. The event listeners have access to test case information such as the test case name and their tags. The [WpaDaemonCatch2EventListener](./detail/WpaDaemonCatch2EventListener.hxx) was developed to detect when a test case requires either of the daemons, and automatically creates a wlan driver and starts the required daemon. Similarly, it handles removing the driver instances and stopping the daemons on test case completion. -To trigger this behavior, the test case must include a tag that corresponds to the name of a [WpaType](../../../../linux/wpa-controller/include/Wpa/WpaCore.hxx) enumeration value (a case insensitive comparison is done). For example, if the hostapd daemon is needed for the test, then a tag corresponding to WpaType::Hosapd is needed in the test case definition, for example '**hostapd**' as shown below: +To trigger this behavior, the test case must include a tag that corresponds to the name of a [`WpaType`](../../../../linux/wpa-controller/include/Wpa/WpaCore.hxx) enumeration value (a case insensitive comparison is done). For example, if the hostapd daemon is needed for the test, then a tag corresponding to `WpaType::Hostapd` is needed in the test case definition, for example '**hostapd**' as shown below: ```C++ TEST_CASE("Send command: GetStatus()", "[wpa][hostapd][client][remote]") @@ -17,9 +17,8 @@ Only the first `WpaType` will be used. Failure to add the appropriate tag will c ### Future Work -This method will be improved in the future, with some possible improvements including: +This method will be updated in the future, with some possible improvements including: 1. Allow multiple wpa daemon types. -2. Allow a more generic daemon specification (eg. `[wpaDaemon=hostapd]`) -3. Replacement of event-based fixtures to class-based fixtures. - +2. Allow a more generic daemon specification (eg. `[wpaDaemons=hostapd,wpaSupplicant]`) +3. Replacement of event-based fixtures with class-based fixtures.