Skip to content

Commit

Permalink
Merge pull request #30 from microsoft/wpactrlsettings
Browse files Browse the repository at this point in the history
Add hostapd interaction interface: define and implement basic commands
  • Loading branch information
abeltrano authored Nov 22, 2023
2 parents 478a498 + fcd804c commit e4ca4f7
Show file tree
Hide file tree
Showing 21 changed files with 1,031 additions and 10 deletions.
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

0 comments on commit e4ca4f7

Please sign in to comment.