Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generic response parsing #31

Merged
merged 20 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions linux/wpa-controller/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ target_sources(wpa-controller
${CMAKE_CURRENT_LIST_DIR}/WpaCommandStatus.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaController.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaCore.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaKeyValuePair.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaResponse.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaResponseParser.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}/ProtocolHostapd.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpa.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}/WpaKeyValuePair.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponseParser.hxx
)

target_include_directories(wpa-controller
Expand All @@ -34,9 +38,9 @@ target_include_directories(wpa-controller
target_link_libraries(wpa-controller
PUBLIC
libwpa-client
notstd
PRIVATE
magic_enum::magic_enum
notstd
)

list(APPEND WPA_CONTROLLER_PUBLIC_HEADERS
Expand All @@ -48,7 +52,9 @@ list(APPEND WPA_CONTROLLER_PUBLIC_HEADERS
${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}/WpaKeyValuePair.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponseParser.hxx
)

set_target_properties(wpa-controller PROPERTIES
Expand Down
27 changes: 5 additions & 22 deletions linux/wpa-controller/Hostapd.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <Wpa/Hostapd.hxx>
#include <Wpa/ProtocolHostapd.hxx>
#include <Wpa/WpaCommandStatus.hxx>
#include <Wpa/WpaResponseStatus.hxx>

using namespace Wpa;

Expand Down Expand Up @@ -35,34 +37,15 @@ bool Hostapd::Ping()

HostapdStatus Hostapd::GetStatus()
{
static constexpr WpaCommand StatusCommand(ProtocolHostapd::CommandPayloadStatus);
static constexpr WpaCommandStatus StatusCommand;

auto response = m_controller.SendCommand(StatusCommand);
auto response = m_controller.SendCommand<WpaResponseStatus>(StatusCommand);
if (!response)
{
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=";

// Find interface state string in payload.
auto keyPosition = response->Payload.find(StateKey);
if (keyPosition == response->Payload.npos)
{
// The response should always have this field and not much can be done without it.
throw HostapdException("hostapd 'status' command response missing state field");
}

// Convert value string to corresponding enum value.
const auto valuePosition = keyPosition + std::size(StateKey);
const auto* value = std::data(response->Payload) + valuePosition;
hostapdStatus.State = HostapdInterfaceStateFromString(value);

return hostapdStatus;
return response->Status;
}

bool Hostapd::Enable()
Expand Down
24 changes: 24 additions & 0 deletions linux/wpa-controller/WpaCommand.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,27 @@
#include <Wpa/WpaCommand.hxx>

using namespace Wpa;

std::shared_ptr<WpaResponse>
WpaCommand::ParseResponse(std::string_view responsePayload) const
{
std::shared_ptr<WpaResponse> response;

auto parser = CreateResponseParser(this, responsePayload);
if (parser != nullptr)
{
response = parser->Parse();
}
else
{
response = std::make_shared<WpaResponse>(responsePayload);
}

return response;
}

std::unique_ptr<WpaResponseParser> WpaCommand::CreateResponseParser([[maybe_unused]] const WpaCommand* command, [[maybe_unused]] std::string_view responsePayload) const
{
// Basic commands don't need a response parser since they only provide OK/FAIL results.
return nullptr;
}
31 changes: 28 additions & 3 deletions linux/wpa-controller/WpaCommandStatus.cxx
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@

#include <Wpa/WpaCommandStatus.hxx>
#include <Wpa/ProtocolHostapd.hxx>
#include <Wpa/ProtocolWpa.hxx>
#include <Wpa/WpaCommandStatus.hxx>
#include <Wpa/WpaResponseStatus.hxx>

using namespace Wpa;

constexpr WpaCommandStatus::WpaCommandStatus() :
WpaCommand(ProtocolWpa::CommandPayloadStatus)
std::unique_ptr<WpaResponseParser> WpaCommandStatus::CreateResponseParser(const WpaCommand* command, std::string_view responsePayload) const
{
return std::make_unique<WpaStatusResponseParser>(command, responsePayload);
}

WpaStatusResponseParser::WpaStatusResponseParser(const WpaCommand* command, std::string_view responsePayload) :
WpaResponseParser(command, responsePayload, {
{ ProtocolHostapd::ResponseStatusPropertyKeyState, WpaValuePresence::Required },
})
{
}

std::shared_ptr<WpaResponse> WpaStatusResponseParser::ParsePayload()
{
const auto properties = GetProperties();
const auto response = std::make_shared<WpaResponseStatus>();

for (const auto& [key, value] : properties)
{
if (key == ProtocolHostapd::ResponseStatusPropertyKeyState)
{
response->Status.State = HostapdInterfaceStateFromString(value);
}
}

return response;
}
11 changes: 8 additions & 3 deletions linux/wpa-controller/WpaController.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,23 @@ WpaController::SendCommand(const WpaCommand& command)
// Send the command and receive the response.
std::array<char, WpaControlSocket::MessageSizeMax> responseBuffer;
std::size_t responseSize = std::size(responseBuffer);

int ret = wpa_ctrl_request(controlSocket, std::data(command.Data), std::size(command.Data), std::data(responseBuffer), &responseSize, nullptr);
switch (ret)
{
case 0:
break;
{
std::string_view responsePayload{std::data(responseBuffer), responseSize};
return command.ParseResponse(responsePayload);
}
case -1:
std::cerr << std::format("Failed to send or receive command to {} interface.", m_interfaceName) << std::endl;
return nullptr;
case -2:
std::cerr << std::format("Sending command to {} interface timed out.", m_interfaceName) << std::endl;
return nullptr;
default:
std::cerr << std::format("Unknown error sending command to {} interface (ret={}).", m_interfaceName, ret) << std::endl;
return nullptr;
}

return std::make_shared<WpaResponse>(std::string_view{std::data(responseBuffer), responseSize});
}
24 changes: 24 additions & 0 deletions linux/wpa-controller/WpaKeyValuePair.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

#include <format>
#include <stdexcept>

#include <Wpa/WpaKeyValuePair.hxx>

using namespace Wpa;

std::optional<std::string_view> WpaKeyValuePair::TryParseValue(std::string_view input)
{
// If not already parsed...
if (!Value.has_value())
{
// Find the starting position of the key.
const auto keyPosition = input.find(Key);
if (keyPosition != input.npos)
{
// Assign the starting position of the value, advancing past the key.
Value = std::data(input) + keyPosition + std::size(Key);
}
}

return Value;
}
63 changes: 63 additions & 0 deletions linux/wpa-controller/WpaResponseParser.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

#include <format>
#include <iostream>

#include <Wpa/WpaResponseParser.hxx>

using namespace Wpa;

WpaResponseParser::WpaResponseParser(const WpaCommand* command, std::string_view responsePayload, std::initializer_list<WpaKeyValuePair> propertiesToParse) :
Command(command),
ResponsePayload(responsePayload),
m_propertiesToParse(propertiesToParse)
{
}

const std::unordered_map<std::string_view, std::string_view>& WpaResponseParser::GetProperties() const noexcept
{
return m_properties;
}

std::shared_ptr<WpaResponse> WpaResponseParser::Parse()
{
// Attempt to parse the properties, bailing on an error.
auto propertiesParsed = TryParseProperties();
if (!propertiesParsed)
{
return nullptr;
}

// Parse the payload with the resolved properties.
return ParsePayload();
}

bool WpaResponseParser::TryParseProperties()
{
if (std::empty(m_propertiesToParse))
{
return true;
}

for (auto propertyToParseIterator = std::begin(m_propertiesToParse); propertyToParseIterator != std::end(m_propertiesToParse); )
{
auto& propertyToParse = *propertyToParseIterator;
auto propertyValue = propertyToParse.TryParseValue(ResponsePayload);
if (propertyValue.has_value())
{
m_properties[propertyToParse.Key] = *propertyValue;
propertyToParseIterator = m_propertiesToParse.erase(propertyToParseIterator);
continue;
}
else if (propertyToParse.IsRequired)
{
std::cerr << std::format("Failed to parse required property: {}", propertyToParse.Key) << std::endl;
return false;
}
else
{
++propertyToParseIterator;
}
}

return true;
}
4 changes: 4 additions & 0 deletions linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ struct ProtocolHostapd :
static constexpr auto ResponsePayloadStatusDfs = "DFS";
static constexpr auto ResponsePayloadStatusNoIr = "NO_IR";
static constexpr auto ResponsePayloadStatusUnknown = "UNKNOWN";

// Response properties for the "STATUS" command.
// Note: all properties must be terminated with the key-value delimeter (=).
static constexpr auto ResponseStatusPropertyKeyState = "state=";
};

/**
Expand Down
3 changes: 3 additions & 0 deletions linux/wpa-controller/include/Wpa/ProtocolWpa.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ namespace Wpa
*/
struct ProtocolWpa
{
// The delimeter used to separate key-value pairs in the WPA control protocol.
static constexpr auto KeyValueDelimiter = '=';

// Command payloads.
static constexpr auto CommandPayloadPing = "PING";
static constexpr auto CommandPayloadEnable = "ENABLE";
Expand Down
27 changes: 26 additions & 1 deletion linux/wpa-controller/include/Wpa/WpaCommand.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,49 @@
#ifndef WPA_COMMAND_HXX
#define WPA_COMMAND_HXX

#include <memory>
#include <string_view>
#include <string>

#include <Wpa/WpaResponseParser.hxx>

namespace Wpa
{
/**
* @brief Object to hold generic command data for a wpa_supplicant or hostapd
* request.
*/
struct WpaCommand
struct WpaCommand :
private WpaResponseParserFactory
{
virtual ~WpaCommand() = default;

constexpr WpaCommand() = default;
constexpr WpaCommand(std::string_view data) :
Data(data)
{
}

/**
* @brief Parse the response payload into a WpaResponse object.
*
* @param responsePayload The response payload to parse.
* @return std::shared_ptr<WpaResponse>
*/
std::shared_ptr<WpaResponse>
ParseResponse(std::string_view responsePayload) const;

std::string Data;

private:
/**
* @brief Create a response parser object for the given command.
*
* @param command The command to create a response parser for.
* @param responsePayload The response payload to parse.
* @return std::unique_ptr<WpaResponseParser>
*/
std::unique_ptr<WpaResponseParser> CreateResponseParser(const WpaCommand* command, std::string_view responsePayload) const override;
};
} // namespace Wpa

Expand Down
Loading