diff --git a/src/common/net/wifi/core/AccessPointAttributes.cxx b/src/common/net/wifi/core/AccessPointAttributes.cxx new file mode 100644 index 00000000..c2a4b67a --- /dev/null +++ b/src/common/net/wifi/core/AccessPointAttributes.cxx @@ -0,0 +1,25 @@ + +#include +#include +#include +#include + +#include + +#include "AccessPointAttributesJsonSerialization.hxx" + +using namespace Microsoft::Net::Wifi; + +/* static */ +std::optional> +AccessPointAttributes::TryParseJson(const std::string& json) +{ + return ParseAccessPointAttributesFromJson(json); +} + +/* static */ +std::optional> +AccessPointAttributes::TryParseJson(std::istream& json) +{ + return ParseAccessPointAttributesFromJson(json); +} diff --git a/src/common/net/wifi/core/AccessPointAttributesJsonSerialization.cxx b/src/common/net/wifi/core/AccessPointAttributesJsonSerialization.cxx new file mode 100644 index 00000000..ac1028fe --- /dev/null +++ b/src/common/net/wifi/core/AccessPointAttributesJsonSerialization.cxx @@ -0,0 +1,23 @@ + +#include +#include + +#include "AccessPointAttributesJsonSerialization.hxx" + +namespace Microsoft::Net::Wifi +{ +void +to_json(nlohmann::json& accessPointAttributesJson, const AccessPointAttributes& accessPointAttributes) +{ + accessPointAttributesJson = nlohmann::json{ + { "Properties", accessPointAttributes.Properties } + }; +} + +void +from_json(const nlohmann::json& accessPointAttributesJson, AccessPointAttributes& accessPointAttributes) +{ + accessPointAttributesJson.at("Properties").get_to(accessPointAttributes.Properties); +} + +} // namespace Microsoft::Net::Wifi diff --git a/src/common/net/wifi/core/AccessPointAttributesJsonSerialization.hxx b/src/common/net/wifi/core/AccessPointAttributesJsonSerialization.hxx new file mode 100644 index 00000000..ca538cae --- /dev/null +++ b/src/common/net/wifi/core/AccessPointAttributesJsonSerialization.hxx @@ -0,0 +1,62 @@ + +#ifndef ACCESS_POINT_ATTRIBUTES_JSON_SERIALIZATION_HXX +#define ACCESS_POINT_ATTRIBUTES_JSON_SERIALIZATION_HXX + +#include +#include +#include +#include + +#include +#include +#include + +namespace Microsoft::Net::Wifi +{ +/** + * @brief Serialize access point attributes to JSON. + * + * @param accessPointAttributesJson The JSON object to hold the serialized data. + * @param accessPointAttributes The access point attributes to serialize. + */ +void +to_json(nlohmann::json& accessPointAttributesJson, const AccessPointAttributes& accessPointAttributes); + +/** + * @brief Deserialize access point attributes from JSON. + * + * @param accessPointAttributesJson The JSON object to deserialize. + * @param accessPointAttributes The access point attributes to populate. + */ +void +from_json(const nlohmann::json& accessPointAttributesJson, AccessPointAttributes& accessPointAttributes); + +/** + * @brief Parse access point attributes from a JSON input type. + * + * @tparam InputType The input type holding the JSON to parse. This must be one of the types supported by + * nlohmann::json::parse (input stream, string, iterator pair, contiguous container, character array). + * @param json The JSON input to parse. + * @return std::optional> + */ +template +std::optional> +ParseAccessPointAttributesFromJson(InputType& json) +{ + std::unordered_map accessPointAttributesMap; + nlohmann::json accessPointAttributesJson; + + try { + accessPointAttributesJson = nlohmann::json::parse(json); + accessPointAttributesJson.get_to(accessPointAttributesMap); + } catch (const nlohmann::json::parse_error& e) { + LOGE << std::format("Failed to parse access point access point attributes JSON: {}", e.what()); + return std::nullopt; + } + + return accessPointAttributesMap; +} + +} // namespace Microsoft::Net::Wifi + +#endif // ACCESS_POINT_ATTRIBUTES_JSON_SERIALIZATION_HXX diff --git a/src/common/net/wifi/core/CMakeLists.txt b/src/common/net/wifi/core/CMakeLists.txt index af2b5047..318a9a63 100644 --- a/src/common/net/wifi/core/CMakeLists.txt +++ b/src/common/net/wifi/core/CMakeLists.txt @@ -8,6 +8,9 @@ set(WIFI_CORE_PUBLIC_INCLUDE_PREFIX ${WIFI_CORE_PUBLIC_INCLUDE}/${WIFI_CORE_PUBL target_sources(wifi-core PRIVATE AccessPoint.cxx + AccessPointAttributes.cxx + AccessPointAttributesJsonSerialization.cxx + AccessPointAttributesJsonSerialization.hxx AccessPointController.cxx AccessPointOperationStatus.cxx AccessPointOperationStatusLogOnExit.cxx @@ -19,6 +22,7 @@ target_sources(wifi-core BASE_DIRS ${WIFI_CORE_PUBLIC_INCLUDE} FILES ${WIFI_CORE_PUBLIC_INCLUDE_PREFIX}/AccessPoint.hxx + ${WIFI_CORE_PUBLIC_INCLUDE_PREFIX}/AccessPointAttributes.hxx ${WIFI_CORE_PUBLIC_INCLUDE_PREFIX}/AccessPointController.hxx ${WIFI_CORE_PUBLIC_INCLUDE_PREFIX}/AccessPointOperationStatus.hxx ${WIFI_CORE_PUBLIC_INCLUDE_PREFIX}/AccessPointOperationStatusLogOnExit.hxx @@ -33,6 +37,7 @@ target_sources(wifi-core target_link_libraries(wifi-core PRIVATE magic_enum::magic_enum + nlohmann_json::nlohmann_json notstd strings PUBLIC diff --git a/src/common/net/wifi/core/include/microsoft/net/wifi/AccessPointAttributes.hxx b/src/common/net/wifi/core/include/microsoft/net/wifi/AccessPointAttributes.hxx new file mode 100644 index 00000000..062d5376 --- /dev/null +++ b/src/common/net/wifi/core/include/microsoft/net/wifi/AccessPointAttributes.hxx @@ -0,0 +1,39 @@ + +#ifndef ACCESS_POINT_ATTRIBUTES_HXX +#define ACCESS_POINT_ATTRIBUTES_HXX + +#include +#include +#include +#include + +namespace Microsoft::Net::Wifi +{ +/** + * @brief Container to hold static attributes about an access point. + */ +struct AccessPointAttributes +{ + /** + * @brief Attempt to deserialize a JSON string into a map of access point attributes. + * + * @param json The JSON input string to parse. + * @return std::optional> + */ + static std::optional> + TryParseJson(const std::string& json); + + /** + * @brief Attempt to deserialize a JSON stream into a map of access point attributes. + * + * @param json The JSON input stream to parse. + * @return std::optional> + */ + static std::optional> + TryParseJson(std::istream& json); + + std::unordered_map Properties{}; +}; +} // namespace Microsoft::Net::Wifi + +#endif // ACCESS_POINT_ATTRIBUTES_HXX diff --git a/src/common/net/wifi/core/include/microsoft/net/wifi/IAccessPoint.hxx b/src/common/net/wifi/core/include/microsoft/net/wifi/IAccessPoint.hxx index 936921d8..a553ea7e 100644 --- a/src/common/net/wifi/core/include/microsoft/net/wifi/IAccessPoint.hxx +++ b/src/common/net/wifi/core/include/microsoft/net/wifi/IAccessPoint.hxx @@ -6,20 +6,12 @@ #include #include +#include #include #include namespace Microsoft::Net::Wifi { - -/** - * @brief Container to hold static attributes about an access point. - */ -struct AccessPointAttributes -{ - std::unordered_map Properties{}; -}; - /** * @brief Represents a wireless access point. */ diff --git a/src/linux/net/wifi/apmanager/AccessPointDiscoveryAgentOperationsNetlink.cxx b/src/linux/net/wifi/apmanager/AccessPointDiscoveryAgentOperationsNetlink.cxx index 242d3d3a..e31ea46e 100644 --- a/src/linux/net/wifi/apmanager/AccessPointDiscoveryAgentOperationsNetlink.cxx +++ b/src/linux/net/wifi/apmanager/AccessPointDiscoveryAgentOperationsNetlink.cxx @@ -158,8 +158,8 @@ MakeAccessPoint(const std::shared_ptr &accessPointFacto std::future>> AccessPointDiscoveryAgentOperationsNetlink::ProbeAsync() { - const auto ToAccessPoint = [this](const Nl80211Interface &nl80211Interface) { - return detail::MakeAccessPoint(m_accessPointFactory, nl80211Interface); + const auto ToAccessPoint = [accessPointFactory = m_accessPointFactory](const Nl80211Interface &nl80211Interface) { + return detail::MakeAccessPoint(accessPointFactory, nl80211Interface); }; std::promise>> probePromise{}; diff --git a/tests/unit/net/wifi/core/CMakeLists.txt b/tests/unit/net/wifi/core/CMakeLists.txt index dbcbc8bf..abdf53e4 100644 --- a/tests/unit/net/wifi/core/CMakeLists.txt +++ b/tests/unit/net/wifi/core/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(wifi-core-test-unit PRIVATE Main.cxx TestAccessPoint.cxx + TestAccessPointAttributes.cxx TestAccessPointOperationStatus.cxx TestIeee80211.cxx ) @@ -18,6 +19,7 @@ target_link_libraries(wifi-core-test-unit PRIVATE Catch2::Catch2 magic_enum::magic_enum + nlohmann_json::nlohmann_json plog::plog strings wifi-core diff --git a/tests/unit/net/wifi/core/TestAccessPointAttributes.cxx b/tests/unit/net/wifi/core/TestAccessPointAttributes.cxx new file mode 100644 index 00000000..082f3605 --- /dev/null +++ b/tests/unit/net/wifi/core/TestAccessPointAttributes.cxx @@ -0,0 +1,75 @@ + +#include +#include + +#include +#include +#include + +TEST_CASE("AccessPointAttributes JSON Serialization and Deserialization", "[wifi][core][ap][serialization]") +{ + using namespace Microsoft::Net::Wifi; + + auto accessPointAttributesJson = R"( + { + "wlan0": { + "Properties": { + "key1": "value1", + "key2": "value2" + } + }, + "wlan1": { + "Properties": { + "key1": "value1", + "key2": "value2" + } + } + } + )"; + + // clang-format off + std::unordered_map AccessPointAttributesMap{ + { + "wlan0", + AccessPointAttributes{ + { + { "key1", "value1" }, + { "key2", "value2" } + } + }, + }, + { + "wlan1", + AccessPointAttributes{ + { + { "key1", "value1" }, + { "key2", "value2" } + } + } + } + }; + // clang-format on + + SECTION("Deserialization (direct) doesn't cause a crash") + { + REQUIRE_NOTHROW(nlohmann::json::parse(accessPointAttributesJson)); + } + + SECTION("Deserialization (wrapped) doesn't cause a crash") + { + REQUIRE_NOTHROW(AccessPointAttributes::TryParseJson(accessPointAttributesJson)); + } + + SECTION("Deserialization (wrapped) populates the access point attributes") + { + auto deserializedAccessPointAttributesOpt = AccessPointAttributes::TryParseJson(accessPointAttributesJson); + REQUIRE(deserializedAccessPointAttributesOpt.has_value()); + auto& deserializedAccessPointAttributes = deserializedAccessPointAttributesOpt.value(); + REQUIRE(std::size(deserializedAccessPointAttributes) == std::size(AccessPointAttributesMap)); + + for (const auto& [interfaceName, accessPointAttributes] : AccessPointAttributesMap) { + REQUIRE(deserializedAccessPointAttributes.contains(interfaceName)); + REQUIRE(deserializedAccessPointAttributes[interfaceName].Properties == accessPointAttributes.Properties); + } + } +}