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 hostapd interaction interface: define and implement basic commands #30

Merged
merged 24 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
20 changes: 19 additions & 1 deletion linux/wpa-controller/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@ 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}/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
${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}/WpaCommandStatus.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx
Expand All @@ -25,12 +34,21 @@ 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
${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}/WpaCommandStatus.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
Expand Down
123 changes: 123 additions & 0 deletions linux/wpa-controller/Hostapd.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@

#include <format>
#include <optional>

#include <notstd/Exceptions.hxx>

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

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()
{
static constexpr WpaCommand PingCommand(ProtocolHostapd::CommandPayloadPing);

const auto response = m_controller.SendCommand(PingCommand);
if (!response)
{
throw HostapdException("Failed to ping hostapd");
}

return response->Payload.starts_with(ProtocolHostapd::ResponsePayloadPing);
}

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

auto response = m_controller.SendCommand(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;
}

bool Hostapd::Enable()
{
static constexpr WpaCommand EnableCommand(ProtocolHostapd::CommandPayloadEnable);

const auto response = m_controller.SendCommand(EnableCommand);
if (!response)
{
throw HostapdException("Failed to send hostapd 'enable' command");
}
else if (response->IsOk())
{
return true;
}

// 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()
{
static constexpr WpaCommand DisableCommand(ProtocolHostapd::CommandPayloadDisable);

const auto response = m_controller.SendCommand(DisableCommand);
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()
{
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();
}
63 changes: 63 additions & 0 deletions linux/wpa-controller/ProtocolHostapd.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

#include <Wpa/ProtocolHostapd.hxx>

using namespace Wpa;

HostapdInterfaceState Wpa::HostapdInterfaceStateFromString(std::string_view state) noexcept
{
// Implementation uses starts_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;
}
}
16 changes: 16 additions & 0 deletions linux/wpa-controller/ProtocolWpa.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

#include <Wpa/ProtocolWpa.hxx>

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));
}
5 changes: 0 additions & 5 deletions linux/wpa-controller/WpaCommand.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,3 @@
#include <Wpa/WpaCommand.hxx>

using namespace Wpa;

WpaCommand::WpaCommand(std::string_view data) :
Data(data)
{
}
10 changes: 10 additions & 0 deletions linux/wpa-controller/WpaCommandStatus.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

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

using namespace Wpa;

constexpr WpaCommandStatus::WpaCommandStatus() :
WpaCommand(ProtocolWpa::CommandPayloadStatus)
{
}
19 changes: 18 additions & 1 deletion linux/wpa-controller/WpaResponse.cxx
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@

#include <Wpa/WpaResponse.hxx>
#include <Wpa/ProtocolWpa.hxx>

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));
}
75 changes: 75 additions & 0 deletions linux/wpa-controller/include/Wpa/Hostapd.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

#ifndef HOSTAPD_HXX
#define HOSTAPD_HXX

#include <string_view>
#include <string>

#include <Wpa/IHostapd.hxx>
#include <Wpa/WpaController.hxx>

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 Get the status for the interface.
*
* @return HostapdStatus
*/
HostapdStatus GetStatus() 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;

/**
* @brief Terminates the process hosting the daemon.
*
* @return true
* @return false
*/
bool Terminate() override;

private:
const std::string m_interface;
WpaController m_controller;
};
} // namespace Wpa

#endif // HOSTAPD_HXX
Loading