blob: 0a0e50cfdd0b1cbf0aa7979e508a4ae638147faa [file] [log] [blame]
/*
*
* Copyright (c) 2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* Provides the wrapper for nRF WiFi API
*/
#include "WiFiManager.h"
#include <inet/InetInterface.h>
#include <inet/UDPEndPointImplSockets.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/Zephyr/InetUtils.h>
#include <net/net_stats.h>
#include <zephyr.h>
extern "C" {
#include <common/defs.h>
#include <wpa_supplicant/config.h>
#include <wpa_supplicant/driver_i.h>
#include <wpa_supplicant/scan.h>
#include <zephyr/net/wifi_mgmt.h>
}
extern struct wpa_global * global;
static struct wpa_supplicant * wpa_s;
namespace chip {
namespace DeviceLayer {
namespace {
NetworkCommissioning::WiFiScanResponse ToScanResponse(wifi_scan_result * result)
{
NetworkCommissioning::WiFiScanResponse response = {};
if (result != nullptr)
{
static_assert(sizeof(response.ssid) == sizeof(result->ssid), "SSID length mismatch");
static_assert(sizeof(response.bssid) == sizeof(result->mac), "BSSID length mismatch");
// TODO: Distinguish WPA versions
response.security.Set(result->security == WIFI_SECURITY_TYPE_PSK ? NetworkCommissioning::WiFiSecurity::kWpaPersonal
: NetworkCommissioning::WiFiSecurity::kUnencrypted);
response.channel = result->channel;
response.rssi = result->rssi;
response.ssidLen = result->ssid_length;
memcpy(response.ssid, result->ssid, result->ssid_length);
// TODO: MAC/BSSID is not filled by the Wi-Fi driver
memcpy(response.bssid, result->mac, result->mac_length);
}
return response;
}
} // namespace
// These enums shall reflect the overall ordered disconnected->connected flow
const Map<wpa_states, WiFiManager::StationStatus, 10>
WiFiManager::sStatusMap({ { WPA_DISCONNECTED, WiFiManager::StationStatus::DISCONNECTED },
{ WPA_INTERFACE_DISABLED, WiFiManager::StationStatus::DISABLED },
{ WPA_INACTIVE, WiFiManager::StationStatus::DISABLED },
{ WPA_SCANNING, WiFiManager::StationStatus::SCANNING },
{ WPA_AUTHENTICATING, WiFiManager::StationStatus::CONNECTING },
{ WPA_ASSOCIATING, WiFiManager::StationStatus::CONNECTING },
{ WPA_ASSOCIATED, WiFiManager::StationStatus::CONNECTED },
{ WPA_4WAY_HANDSHAKE, WiFiManager::StationStatus::PROVISIONING },
{ WPA_GROUP_HANDSHAKE, WiFiManager::StationStatus::PROVISIONING },
{ WPA_COMPLETED, WiFiManager::StationStatus::FULLY_PROVISIONED } });
// Map WiFi center frequency to the corresponding channel number
const Map<uint16_t, uint8_t, 42> WiFiManager::sFreqChannelMap(
{ { 4915, 183 }, { 4920, 184 }, { 4925, 185 }, { 4935, 187 }, { 4940, 188 }, { 4945, 189 }, { 4960, 192 },
{ 4980, 196 }, { 5035, 7 }, { 5040, 8 }, { 5045, 9 }, { 5055, 11 }, { 5060, 12 }, { 5080, 16 },
{ 5170, 34 }, { 5180, 36 }, { 5190, 38 }, { 5200, 40 }, { 5210, 42 }, { 5220, 44 }, { 5230, 46 },
{ 5240, 48 }, { 5260, 52 }, { 5280, 56 }, { 5300, 60 }, { 5320, 64 }, { 5500, 100 }, { 5520, 104 },
{ 5540, 108 }, { 5560, 112 }, { 5580, 116 }, { 5600, 120 }, { 5620, 124 }, { 5640, 128 }, { 5660, 132 },
{ 5680, 136 }, { 5700, 140 }, { 5745, 149 }, { 5765, 153 }, { 5785, 157 }, { 5805, 161 }, { 5825, 165 } });
CHIP_ERROR WiFiManager::Init()
{
// wpa_supplicant instance is initialized in dedicated supplicant thread, so wait until
// the initialization is completed.
// TODO: fix thread-safety of the solution.
constexpr size_t kInitTimeoutMs = 5000;
const int64_t initStartTime = k_uptime_get();
// TODO: Handle multiple VIFs
const char * ifname = "wlan0";
while (!global || !(wpa_s = wpa_supplicant_get_iface(global, ifname)))
{
if (k_uptime_get() > initStartTime + kInitTimeoutMs)
{
ChipLogError(DeviceLayer, "wpa_supplicant is not initialized!");
return CHIP_ERROR_INTERNAL;
}
k_msleep(200);
}
// TODO: consider moving these to ConnectivityManagerImpl to be prepared for handling multiple interfaces on a single device.
Inet::UDPEndPointImplSockets::SetJoinMulticastGroupHandler([](Inet::InterfaceId interfaceId, const Inet::IPAddress & address) {
const in6_addr addr = InetUtils::ToZephyrAddr(address);
net_if * iface = InetUtils::GetInterface(interfaceId);
VerifyOrReturnError(iface != nullptr, INET_ERROR_UNKNOWN_INTERFACE);
net_if_mcast_addr * maddr = net_if_ipv6_maddr_add(iface, &addr);
if (maddr && !net_if_ipv6_maddr_is_joined(maddr) && !net_ipv6_is_addr_mcast_link_all_nodes(&addr))
{
net_if_ipv6_maddr_join(maddr);
}
return CHIP_NO_ERROR;
});
Inet::UDPEndPointImplSockets::SetLeaveMulticastGroupHandler([](Inet::InterfaceId interfaceId, const Inet::IPAddress & address) {
const in6_addr addr = InetUtils::ToZephyrAddr(address);
net_if * iface = InetUtils::GetInterface(interfaceId);
VerifyOrReturnError(iface != nullptr, INET_ERROR_UNKNOWN_INTERFACE);
if (!net_ipv6_is_addr_mcast_link_all_nodes(&addr) && !net_if_ipv6_maddr_rm(iface, &addr))
{
return CHIP_ERROR_INVALID_ADDRESS;
}
return CHIP_NO_ERROR;
});
ChipLogDetail(DeviceLayer, "wpa_supplicant has been initialized");
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::AddNetwork(const ByteSpan & ssid, const ByteSpan & credentials)
{
ChipLogDetail(DeviceLayer, "Adding WiFi network");
mpWpaNetwork = wpa_supplicant_add_network(wpa_s);
if (mpWpaNetwork)
{
static constexpr size_t kMaxSsidLen{ 32 };
mpWpaNetwork->ssid = (u8 *) k_malloc(kMaxSsidLen);
if (mpWpaNetwork->ssid)
{
memcpy(mpWpaNetwork->ssid, ssid.data(), ssid.size());
mpWpaNetwork->ssid_len = ssid.size();
mpWpaNetwork->key_mgmt = WPA_KEY_MGMT_NONE;
mpWpaNetwork->disabled = 1;
wpa_s->conf->filter_ssids = 1;
return AddPsk(credentials);
}
}
return CHIP_ERROR_INTERNAL;
}
CHIP_ERROR WiFiManager::Scan(const ByteSpan & ssid, ScanCallback callback)
{
const StationStatus stationStatus = GetStationStatus();
VerifyOrReturnError(stationStatus != StationStatus::DISABLED && stationStatus != StationStatus::SCANNING &&
stationStatus != StationStatus::CONNECTING,
CHIP_ERROR_INCORRECT_STATE);
net_if * const iface = InetUtils::GetInterface();
VerifyOrReturnError(iface != nullptr, CHIP_ERROR_INTERNAL);
const device * dev = net_if_get_device(iface);
VerifyOrReturnError(dev != nullptr, CHIP_ERROR_INTERNAL);
const net_wifi_mgmt_offload * ops = static_cast<const net_wifi_mgmt_offload *>(dev->api);
VerifyOrReturnError(ops != nullptr, CHIP_ERROR_INTERNAL);
mScanCallback = callback;
// TODO: Use saner API once such exists.
// TODO: Take 'ssid' into account.
VerifyOrReturnError(ops->scan(dev,
[](net_if *, int status, wifi_scan_result * result) {
VerifyOrReturn(Instance().mScanCallback != nullptr);
NetworkCommissioning::WiFiScanResponse response = ToScanResponse(result);
Instance().mScanCallback(status, result != nullptr ? &response : nullptr);
}) == 0,
CHIP_ERROR_INTERNAL);
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::Connect(const ByteSpan & ssid, const ByteSpan & credentials, const ConnectionHandling & handling)
{
ChipLogDetail(DeviceLayer, "Connecting to WiFi network");
mConnectionSuccessClbk = handling.mOnConnectionSuccess;
mConnectionFailedClbk = handling.mOnConnectionFailed;
mConnectionTimeoutMs = handling.mConnectionTimeoutMs;
CHIP_ERROR err = AddNetwork(ssid, credentials);
if (CHIP_NO_ERROR == err)
{
EnableStation(true);
wpa_supplicant_select_network(wpa_s, mpWpaNetwork);
WaitForConnectionAsync();
}
else
{
OnConnectionFailed();
}
return err;
}
void WiFiManager::OnConnectionSuccess()
{
if (mConnectionSuccessClbk)
mConnectionSuccessClbk();
}
void WiFiManager::OnConnectionFailed()
{
if (mConnectionFailedClbk)
mConnectionFailedClbk();
}
CHIP_ERROR WiFiManager::AddPsk(const ByteSpan & credentials)
{
mpWpaNetwork->key_mgmt = WPA_KEY_MGMT_PSK;
str_clear_free(mpWpaNetwork->passphrase);
mpWpaNetwork->passphrase = dup_binstr(credentials.data(), credentials.size());
if (mpWpaNetwork->passphrase)
{
wpa_config_update_psk(mpWpaNetwork);
return CHIP_NO_ERROR;
}
return CHIP_ERROR_INTERNAL;
}
WiFiManager::StationStatus WiFiManager::GetStationStatus() const
{
if (wpa_s)
{
return StatusFromWpaStatus(wpa_s->wpa_state);
}
else
{
ChipLogError(DeviceLayer, "wpa_supplicant is not initialized!");
return StationStatus::NONE;
}
}
WiFiManager::StationStatus WiFiManager::StatusFromWpaStatus(const wpa_states & status)
{
ChipLogDetail(DeviceLayer, "WPA internal status: %d", static_cast<int>(status));
return WiFiManager::sStatusMap[status];
}
CHIP_ERROR WiFiManager::EnableStation(bool enable)
{
VerifyOrReturnError(nullptr != wpa_s && nullptr != mpWpaNetwork, CHIP_ERROR_INTERNAL);
if (enable)
{
wpa_supplicant_enable_network(wpa_s, mpWpaNetwork);
}
else
{
wpa_supplicant_disable_network(wpa_s, mpWpaNetwork);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::ClearStationProvisioningData()
{
VerifyOrReturnError(nullptr != wpa_s && nullptr != mpWpaNetwork, CHIP_ERROR_INTERNAL);
wpa_supplicant_cancel_scan(wpa_s);
wpa_clear_keys(wpa_s, mpWpaNetwork->bssid);
str_clear_free(mpWpaNetwork->passphrase);
wpa_config_update_psk(mpWpaNetwork);
wpa_supplicant_set_state(wpa_s, WPA_INACTIVE);
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::DisconnectStation()
{
VerifyOrReturnError(nullptr != wpa_s, CHIP_ERROR_INTERNAL);
wpa_supplicant_cancel_scan(wpa_s);
wpas_request_disconnection(wpa_s);
return CHIP_NO_ERROR;
}
void WiFiManager::WaitForConnectionAsync()
{
chip::DeviceLayer::SystemLayer().StartTimer(
static_cast<System::Clock::Timeout>(1000), [](System::Layer *, void *) { Instance().PollTimerCallback(); }, nullptr);
}
void WiFiManager::PollTimerCallback()
{
const uint32_t kMaxRetriesNumber{ mConnectionTimeoutMs.count() / 1000 };
static uint32_t retriesNumber{ 0 };
if (WiFiManager::StationStatus::FULLY_PROVISIONED == GetStationStatus())
{
retriesNumber = 0;
OnConnectionSuccess();
}
else
{
if (retriesNumber++ < kMaxRetriesNumber)
{
// wait more time
WaitForConnectionAsync();
}
else
{
// connection timeout
retriesNumber = 0;
OnConnectionFailed();
}
}
}
CHIP_ERROR WiFiManager::GetWiFiInfo(WiFiInfo & info) const
{
VerifyOrReturnError(nullptr != wpa_s, CHIP_ERROR_INTERNAL);
VerifyOrReturnError(nullptr != mpWpaNetwork, CHIP_ERROR_INTERNAL);
static uint8_t sBssid[ETH_ALEN];
if (WiFiManager::StationStatus::CONNECTED <= GetStationStatus())
{
memcpy(sBssid, wpa_s->bssid, ETH_ALEN);
info.mBssId = ByteSpan(sBssid, ETH_ALEN);
info.mSecurityType = GetSecurityType();
// TODO: this should reflect the real connection compliance
// i.e. the AP might support WiFi 5 only even though the station
// is WiFi 6 ready (so the connection is WiFi 5 effectively).
// For now just return what the station supports.
info.mWiFiVersion = EMBER_ZCL_WI_FI_VERSION_TYPE_802__11AX;
wpa_signal_info signalInfo{};
if (0 == wpa_drv_signal_poll(wpa_s, &signalInfo))
{
info.mRssi = signalInfo.current_signal; // dBm
info.mChannel = FrequencyToChannel(signalInfo.frequency);
}
else
{
// this values should be nullable according to the Matter spec
info.mRssi = std::numeric_limits<decltype(info.mRssi)>::min();
info.mChannel = std::numeric_limits<decltype(info.mChannel)>::min();
}
memcpy(info.mSsid, mpWpaNetwork->ssid, mpWpaNetwork->ssid_len);
info.mSsidLen = mpWpaNetwork->ssid_len;
return CHIP_NO_ERROR;
}
return CHIP_ERROR_INTERNAL;
}
uint8_t WiFiManager::GetSecurityType() const
{
VerifyOrReturnValue(nullptr != mpWpaNetwork, EMBER_ZCL_SECURITY_TYPE_UNSPECIFIED);
if ((mpWpaNetwork->key_mgmt & WPA_KEY_MGMT_NONE) || !wpa_key_mgmt_wpa_any(mpWpaNetwork->key_mgmt))
{
return EMBER_ZCL_SECURITY_TYPE_NONE;
}
else if (wpa_key_mgmt_wpa_psk_no_sae(mpWpaNetwork->key_mgmt))
{
return (mpWpaNetwork->pairwise_cipher & (WPA_CIPHER_TKIP | WPA_CIPHER_CCMP)) ? EMBER_ZCL_SECURITY_TYPE_WPA2
: EMBER_ZCL_SECURITY_TYPE_WPA3;
}
else if (wpa_key_mgmt_sae(mpWpaNetwork->key_mgmt))
{
return EMBER_ZCL_SECURITY_TYPE_WPA3;
}
else
{
return EMBER_ZCL_SECURITY_TYPE_WEP;
}
return EMBER_ZCL_SECURITY_TYPE_UNSPECIFIED;
}
uint8_t WiFiManager::FrequencyToChannel(uint16_t freq)
{
static constexpr uint16_t k24MinFreq{ 2401 };
static constexpr uint16_t k24MaxFreq{ 2484 };
static constexpr uint8_t k24FreqConstDiff{ 5 };
if (freq >= k24MinFreq && freq < k24MaxFreq)
{
return static_cast<uint8_t>((freq - k24MinFreq) / k24FreqConstDiff + 1);
}
else if (freq == k24MaxFreq)
{
return 14;
}
else if (freq > k24MaxFreq)
{
// assume we are in 5GH band
return sFreqChannelMap[freq];
}
return 0;
}
CHIP_ERROR WiFiManager::GetNetworkStatistics(NetworkStatistics & stats) const
{
// TODO: below will not work (result will be all zeros) until
// the get_stats handler is implemented in WiFi driver
net_stats_eth data{};
net_mgmt(NET_REQUEST_STATS_GET_ETHERNET, InetUtils::GetInterface(), &data, sizeof(data));
stats.mPacketMulticastRxCount = data.multicast.rx;
stats.mPacketMulticastTxCount = data.multicast.tx;
stats.mPacketUnicastRxCount = data.pkts.rx - data.multicast.rx - data.broadcast.rx;
stats.mPacketUnicastTxCount = data.pkts.tx - data.multicast.tx - data.broadcast.tx;
stats.mOverruns = 0; // TODO: clarify if this can be queried from mgmt API (e.g. data.tx_dropped)
return CHIP_NO_ERROR;
}
} // namespace DeviceLayer
} // namespace chip