- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
579 lines
17 KiB
C++
579 lines
17 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright Pionix GmbH and Contributors to EVerest
|
|
#include "WiFiSetup.hpp"
|
|
|
|
#include <filesystem>
|
|
#include <functional>
|
|
#include <regex>
|
|
#include <sstream>
|
|
#include <thread>
|
|
#include <utility>
|
|
|
|
#include <everest/run_application/run_application.hpp>
|
|
|
|
using namespace everest::run_application;
|
|
|
|
/**
|
|
* @file
|
|
* @brief wpa_cli command failure detection
|
|
*
|
|
* `wpa_cli` sets an exit code of 0 unless the command is malformed.
|
|
* Failures are presented via text to stdout.
|
|
* Hence checking for failure to remove a network would mean checking
|
|
* the output for OK or FAIL.
|
|
*
|
|
* This is common across all calls to `wpa_cli`.
|
|
*/
|
|
|
|
namespace {
|
|
inline int hex_digit_to_nibble(std::uint8_t c) {
|
|
int result{-1};
|
|
if ((c >= '0') && (c <= '9')) {
|
|
result = static_cast<std::uint8_t>(c - '0');
|
|
} else if ((c >= 'a') && (c <= 'f')) {
|
|
result = static_cast<std::uint8_t>(c - 'a') + 10;
|
|
} else if ((c >= 'A') && (c <= 'F')) {
|
|
result = static_cast<std::uint8_t>(c - 'A') + 10;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline int hex_to_int(std::uint8_t high, std::uint8_t low) {
|
|
int result{-1};
|
|
const auto h = hex_digit_to_nibble(high);
|
|
const auto l = hex_digit_to_nibble(low);
|
|
if ((h != -1) && (l != -1)) {
|
|
const auto hc = static_cast<std::uint8_t>(h) << 4U;
|
|
const auto lc = static_cast<std::uint8_t>(l);
|
|
result = static_cast<int>(hc | lc);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline std::uint8_t nibble_to_hex_digit(std::uint8_t c) {
|
|
std::uint8_t result{};
|
|
c &= 0x0fU;
|
|
if (c <= 9) {
|
|
result = c + '0';
|
|
} else {
|
|
result = (c - 10) + 'a';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline void int_to_hex(std::uint8_t& high, std::uint8_t& low, std::uint8_t c) {
|
|
high = nibble_to_hex_digit(c >> 4U);
|
|
low = nibble_to_hex_digit(c);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace module {
|
|
|
|
constexpr const char* wpa_cli = "/usr/sbin/wpa_cli";
|
|
constexpr const int not_connected_rssi = -100; // -100 dBm is the minimum for wifi
|
|
|
|
bool WpaCliSetup::do_scan(const std::string& interface) {
|
|
if (!is_wifi_interface(interface)) {
|
|
return false;
|
|
}
|
|
|
|
auto output = run_application(wpa_cli, {"-i", interface, "scan"});
|
|
return output.exit_code == 0;
|
|
}
|
|
|
|
WpaCliSetup::WifiScanList WpaCliSetup::do_scan_results(const std::string& interface) {
|
|
WifiScanList result = {};
|
|
auto output = run_application(wpa_cli, {"-i", interface, "scan_results"});
|
|
if (output.exit_code == 0) {
|
|
auto scan_results = output.split_output;
|
|
if (scan_results.size() >= 2) {
|
|
// skip header
|
|
for (auto scan_results_it = std::next(scan_results.begin()); scan_results_it != scan_results.end();
|
|
++scan_results_it) {
|
|
|
|
std::vector<std::string> columns;
|
|
std::istringstream stream(*scan_results_it);
|
|
for (std::string value; std::getline(stream, value, '\t');) {
|
|
columns.push_back(std::move(value));
|
|
}
|
|
|
|
if (columns.size() >= 5) {
|
|
WifiScan info;
|
|
info.bssid = columns[0];
|
|
info.ssid = columns[4];
|
|
info.frequency = std::stoi(columns[1]);
|
|
info.signal_level = std::stoi(columns[2]);
|
|
info.flags = std::move(parse_flags(columns[3]));
|
|
result.push_back(std::move(info));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
WpaCliSetup::Status WpaCliSetup::do_status(const std::string& interface) {
|
|
Status result = {};
|
|
if (is_wifi_interface(interface)) {
|
|
auto output = run_application(wpa_cli, {"-i", interface, "status"});
|
|
if (output.exit_code == 0) {
|
|
auto scan_results = output.split_output;
|
|
for (auto& scan_result : scan_results) {
|
|
std::vector<std::string> columns;
|
|
std::istringstream ss(scan_result);
|
|
for (std::string value; std::getline(ss, value, '=');) {
|
|
columns.push_back(std::move(value));
|
|
}
|
|
|
|
if (columns.size() == 2) {
|
|
result[columns[0]] = columns[1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
WpaCliSetup::Poll WpaCliSetup::do_signal_poll(const std::string& interface) {
|
|
Poll result = {};
|
|
if (is_wifi_interface(interface)) {
|
|
auto output = run_application(wpa_cli, {"-i", interface, "signal_poll"});
|
|
if (output.exit_code == 0) {
|
|
auto scan_results = output.split_output;
|
|
for (auto& scan_result : scan_results) {
|
|
std::vector<std::string> columns;
|
|
std::istringstream ss(scan_result);
|
|
for (std::string value; std::getline(ss, value, '=');) {
|
|
columns.push_back(std::move(value));
|
|
}
|
|
|
|
if (columns.size() == 2) {
|
|
result[columns[0]] = columns[1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
WpaCliSetup::flags_t WpaCliSetup::parse_flags(const std::string& flags) {
|
|
const std::regex flags_regex("\\[(.*?)\\]");
|
|
|
|
flags_t parsed_flags;
|
|
|
|
for (auto it = std::sregex_iterator(flags.begin(), flags.end(), flags_regex); it != std::sregex_iterator(); ++it) {
|
|
parsed_flags.push_back((*it).str(1));
|
|
}
|
|
|
|
return parsed_flags;
|
|
}
|
|
|
|
int WpaCliSetup::add_network(const std::string& interface) {
|
|
if (!is_wifi_interface(interface)) {
|
|
return -1;
|
|
}
|
|
|
|
auto output = run_application(wpa_cli, {"-i", interface, "add_network"});
|
|
|
|
if ((output.exit_code != 0) || (output.split_output.size() != 1)) {
|
|
return -1;
|
|
}
|
|
|
|
return std::stoi(output.split_output.at(0));
|
|
}
|
|
|
|
bool WpaCliSetup::set_network(const std::string& interface, int network_id, const std::string& ssid,
|
|
const std::string& psk, network_security_t mode, bool hidden) {
|
|
/*
|
|
* configuring a network needs:
|
|
* - ssid "<SSID>"
|
|
* - psk "<Passphrase>" or ABCDEF0123456789... (for WPA2)
|
|
* - sae_password "<Passphrase>" (for WPA3)
|
|
* - key_mgmt NONE (for open networks)
|
|
* - scan_ssid 1 (for hidden networks)
|
|
*
|
|
* Support for WPA3 requires:
|
|
* - key_mgmt WPA-PSK WPA-PSK-SHA256 SAE or SAE if WPA3 only
|
|
* - sae_password replaces psk, WPA3 doesn't support PreSharedKey (64 hex digits)
|
|
* the passphrase is required
|
|
* - for interworking WPA2 and WPA3 the passphrase is needed
|
|
* - psk with hex digits (PreSharedKey) doesn't work
|
|
*/
|
|
|
|
/*
|
|
* From wpa_supplicant/hostapd
|
|
* ieee80211w: Whether management frame protection (MFP) is enabled
|
|
* 0 = disabled (default)
|
|
* 1 = optional
|
|
* 2 = required
|
|
* The most common configuration options for this based on the PMF (protected
|
|
* management frames) certification program are:
|
|
* PMF enabled: ieee80211w=1 and wpa_key_mgmt=WPA-EAP WPA-EAP-SHA256
|
|
* PMF required: ieee80211w=2 and wpa_key_mgmt=WPA-EAP-SHA256
|
|
* (and similarly for WPA-PSK and WPA-PSK-SHA256 if WPA2-Personal is used)
|
|
* WPA3-Personal-only mode: ieee80211w=2 and wpa_key_mgmt=SAE
|
|
*/
|
|
|
|
constexpr std::uint8_t wpa2_psk_size = 64U;
|
|
|
|
if (!is_wifi_interface(interface)) {
|
|
return false;
|
|
}
|
|
|
|
if (psk.empty()) {
|
|
// force security mode to none
|
|
mode = network_security_t::none;
|
|
}
|
|
|
|
const char* key_mgt = nullptr;
|
|
const char* psk_name = nullptr;
|
|
const char* ieee80211w = nullptr;
|
|
|
|
switch (mode) {
|
|
case network_security_t::none:
|
|
key_mgt = "NONE";
|
|
break;
|
|
case network_security_t::wpa2_only:
|
|
key_mgt = "WPA-PSK";
|
|
psk_name = "psk";
|
|
break;
|
|
case network_security_t::wpa3_only:
|
|
key_mgt = "SAE";
|
|
psk_name = "sae_password";
|
|
ieee80211w = "2";
|
|
break;
|
|
case network_security_t::wpa2_and_wpa3:
|
|
default:
|
|
if (psk.size() == wpa2_psk_size) {
|
|
// WPA3 doesn't support PSK (hex digits), it needs a passphrase
|
|
key_mgt = "WPA-PSK";
|
|
psk_name = "psk";
|
|
} else if (psk.size() > wpa2_psk_size) {
|
|
// WPA2 doesn't support passphrases > 63 characters
|
|
key_mgt = "SAE";
|
|
psk_name = "sae_password";
|
|
ieee80211w = "2";
|
|
} else {
|
|
key_mgt = "WPA-PSK WPA-PSK-SHA256 SAE";
|
|
psk_name = "psk";
|
|
ieee80211w = "1";
|
|
}
|
|
break;
|
|
}
|
|
|
|
auto network_id_string = std::to_string(network_id);
|
|
|
|
// de-escaping SSID strings in wpa_supplicant is not reliable.
|
|
// hence providing the SSID as a string of hex digits
|
|
auto ssid_parameter = ssid_to_hex(ssid);
|
|
|
|
auto output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "ssid", ssid_parameter});
|
|
|
|
if ((output.exit_code == 0) && (psk_name != nullptr)) {
|
|
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, psk_name, psk});
|
|
}
|
|
|
|
if (output.exit_code == 0) {
|
|
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "key_mgmt", key_mgt});
|
|
}
|
|
|
|
if ((output.exit_code == 0) && (ieee80211w != nullptr)) {
|
|
output =
|
|
run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "ieee80211w", ieee80211w});
|
|
}
|
|
|
|
if (hidden && (output.exit_code == 0)) {
|
|
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "scan_ssid", "1"});
|
|
}
|
|
|
|
return output.exit_code == 0;
|
|
}
|
|
|
|
bool WpaCliSetup::enable_network(const std::string& interface, int network_id) {
|
|
if (!is_wifi_interface(interface)) {
|
|
return false;
|
|
}
|
|
|
|
auto network_id_string = std::to_string(network_id);
|
|
auto output = run_application(wpa_cli, {"-i", interface, "enable_network", network_id_string});
|
|
return output.exit_code == 0;
|
|
}
|
|
|
|
bool WpaCliSetup::disable_network(const std::string& interface, int network_id) {
|
|
if (!is_wifi_interface(interface)) {
|
|
return false;
|
|
}
|
|
|
|
auto network_id_string = std::to_string(network_id);
|
|
auto output = run_application(wpa_cli, {"-i", interface, "disable_network", network_id_string});
|
|
return output.exit_code == 0;
|
|
}
|
|
|
|
bool WpaCliSetup::select_network(const std::string& interface, int network_id) {
|
|
if (!is_wifi_interface(interface)) {
|
|
return false;
|
|
}
|
|
|
|
auto network_id_string = std::to_string(network_id);
|
|
auto output = run_application(wpa_cli, {"-i", interface, "select_network", network_id_string});
|
|
return output.exit_code == 0;
|
|
}
|
|
|
|
bool WpaCliSetup::remove_network(const std::string& interface, int network_id) {
|
|
if (!is_wifi_interface(interface)) {
|
|
return false;
|
|
}
|
|
|
|
auto network_id_string = std::to_string(network_id);
|
|
auto output = run_application(wpa_cli, {"-i", interface, "remove_network", network_id_string});
|
|
return output.exit_code == 0;
|
|
}
|
|
|
|
bool WpaCliSetup::save_config(const std::string& interface) {
|
|
if (!is_wifi_interface(interface)) {
|
|
return false;
|
|
}
|
|
|
|
auto output = run_application(wpa_cli, {"-i", interface, "save_config"});
|
|
return output.exit_code == 0;
|
|
}
|
|
|
|
WpaCliSetup::WifiScanList WpaCliSetup::scan_wifi(const std::string& interface) {
|
|
WifiScanList result = {};
|
|
|
|
if (do_scan(interface)) {
|
|
// FIXME: is there a proper signal to check if the scan is ready? Maybe in the socket based interface
|
|
std::this_thread::sleep_for(std::chrono::seconds(3));
|
|
result = std::move(do_scan_results(interface));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
WpaCliSetup::WifiNetworkList WpaCliSetup::list_networks(const std::string& interface) {
|
|
WifiNetworkList result = {};
|
|
if (is_wifi_interface(interface)) {
|
|
auto output = run_application(wpa_cli, {"-i", interface, "list_networks"});
|
|
if (output.exit_code == 0) {
|
|
auto scan_results = output.split_output;
|
|
if (scan_results.size() >= 2) {
|
|
// skip header
|
|
for (auto scan_results_it = std::next(scan_results.begin()); scan_results_it != scan_results.end();
|
|
++scan_results_it) {
|
|
|
|
std::vector<std::string> columns;
|
|
std::istringstream ss(*scan_results_it);
|
|
for (std::string value; std::getline(ss, value, '\t');) {
|
|
columns.push_back(std::move(value));
|
|
}
|
|
|
|
if (columns.size() >= 2) {
|
|
WifiNetwork info;
|
|
info.network_id = std::stoi(columns[0]);
|
|
info.ssid = columns[1];
|
|
result.push_back(std::move(info));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
WpaCliSetup::WifiNetworkStatusList WpaCliSetup::list_networks_status(const std::string& interface) {
|
|
WifiNetworkStatusList result = {};
|
|
if (is_wifi_interface(interface)) {
|
|
auto network_list = list_networks(interface);
|
|
auto status_map = do_status(interface);
|
|
int connected_rssi = not_connected_rssi;
|
|
|
|
// signal_poll raises errors when not connected
|
|
if (status_map["wpa_state"] == "COMPLETED") {
|
|
auto signal_map = do_signal_poll(interface);
|
|
if (auto it = signal_map.find("RSSI"); it != signal_map.end()) {
|
|
connected_rssi = std::stoi(it->second);
|
|
}
|
|
}
|
|
|
|
for (auto& i : network_list) {
|
|
WifiNetworkStatus net;
|
|
net.interface = interface;
|
|
net.network_id = i.network_id;
|
|
net.ssid = i.ssid;
|
|
net.connected = false;
|
|
net.signal_level = not_connected_rssi;
|
|
|
|
auto id_it = status_map.find("id");
|
|
auto ssid_it = status_map.find("ssid");
|
|
|
|
if ((id_it != status_map.end()) && (ssid_it != status_map.end()) &&
|
|
(std::stoi(id_it->second) == i.network_id) && (ssid_it->second == i.ssid)) {
|
|
net.connected = true;
|
|
net.signal_level = connected_rssi;
|
|
}
|
|
result.push_back(net);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool WpaCliSetup::is_wifi_interface(const std::string& interface) {
|
|
// check if /sys/class/net/<interface>/wireless exists
|
|
|
|
auto path = std::filesystem::path("/sys/class/net");
|
|
path /= interface;
|
|
path /= "wireless";
|
|
|
|
return std::filesystem::exists(path);
|
|
}
|
|
|
|
int Ssid::standard(std::uint8_t c) {
|
|
int output{-1};
|
|
if (c == '\\') {
|
|
handler = &Ssid::backslash;
|
|
} else {
|
|
output = static_cast<int>(c);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
int Ssid::backslash(std::uint8_t c) {
|
|
int output{static_cast<int>(c)};
|
|
handler = &Ssid::standard;
|
|
switch (c) {
|
|
case '"':
|
|
case '\\':
|
|
break;
|
|
case 'n':
|
|
output = '\n';
|
|
break;
|
|
case 'r':
|
|
output = '\r';
|
|
break;
|
|
case 't':
|
|
output = '\t';
|
|
break;
|
|
case 'e':
|
|
output = '\033';
|
|
break;
|
|
case 'x':
|
|
handler = &Ssid::hex_1;
|
|
output = -1;
|
|
break;
|
|
default:
|
|
// malformed
|
|
error = true;
|
|
output = -1;
|
|
break;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
int Ssid::hex_1(std::uint8_t c) {
|
|
tmp = static_cast<std::uint8_t>(c);
|
|
handler = &Ssid::hex_2;
|
|
return -1;
|
|
}
|
|
|
|
int Ssid::hex_2(std::uint8_t c) {
|
|
int output{-1};
|
|
handler = &Ssid::standard;
|
|
const auto res = hex_to_int(tmp, c);
|
|
if (res != -1) {
|
|
output = res;
|
|
} else {
|
|
error = true;
|
|
// malformed
|
|
}
|
|
return output;
|
|
}
|
|
|
|
std::string Ssid::to_hex(const std::string& ssid) {
|
|
std::ostringstream ss;
|
|
handler = &Ssid::standard;
|
|
error = false;
|
|
|
|
for (const auto& c : ssid) {
|
|
const auto output = std::invoke(handler, this, static_cast<std::uint8_t>(c));
|
|
if (error) {
|
|
break;
|
|
}
|
|
if (output != -1) {
|
|
ss << std::hex << std::setw(2) << std::setfill('0') << output;
|
|
}
|
|
}
|
|
return (error) ? std::string{} : ss.str();
|
|
}
|
|
|
|
void Ssid::encode(int c, std::ostringstream& ss) {
|
|
if ((c < 0) || (c > 255)) {
|
|
error = true;
|
|
} else {
|
|
switch (c) {
|
|
case '"':
|
|
ss << R"(\")";
|
|
break;
|
|
case '\\':
|
|
ss << R"(\\)";
|
|
break;
|
|
case '\033':
|
|
ss << R"(\e)";
|
|
break;
|
|
case '\n':
|
|
ss << R"(\n)";
|
|
break;
|
|
case '\r':
|
|
ss << R"(\r)";
|
|
break;
|
|
case '\t':
|
|
ss << R"(\t)";
|
|
break;
|
|
default: {
|
|
if ((c >= 32) && (c <= 126)) {
|
|
ss << static_cast<char>(c);
|
|
} else {
|
|
std::uint8_t high{};
|
|
std::uint8_t low{};
|
|
int_to_hex(high, low, c);
|
|
ss << R"(\x)" << static_cast<char>(high) << static_cast<char>(low);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string Ssid::from_hex(const std::string& hex) {
|
|
std::ostringstream ss;
|
|
bool high{true};
|
|
|
|
error = (hex.size() % 2) != 0;
|
|
for (const auto& c : hex) {
|
|
if (error) {
|
|
break;
|
|
}
|
|
if (high) {
|
|
tmp = static_cast<std::uint8_t>(c);
|
|
high = false;
|
|
} else {
|
|
const auto output = hex_to_int(tmp, static_cast<std::uint8_t>(c));
|
|
encode(output, ss);
|
|
high = true;
|
|
}
|
|
}
|
|
return (error) ? std::string{} : ss.str();
|
|
}
|
|
|
|
std::string WpaCliSetup::hex_to_ssid(const std::string& hex) {
|
|
Ssid converter;
|
|
return converter.from_hex(hex);
|
|
}
|
|
|
|
std::string WpaCliSetup::ssid_to_hex(const std::string& ssid) {
|
|
Ssid converter;
|
|
return converter.to_hex(ssid);
|
|
}
|
|
|
|
} // namespace module
|