blob: 6a4585a57723f8517b1e289a1a79dbafe91e9b3f [file]
/*
*
* 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 <crypto/RandUtils.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 <zephyr/kernel.h>
#include <zephyr/net/net_event.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_stats.h>
extern "C" {
#include <common/defs.h>
#include <wpa_supplicant/config.h>
#include <wpa_supplicant/driver_i.h>
#include <wpa_supplicant/scan.h>
// extern function to obtain bssid from status buffer
// It is defined in zephyr/subsys/net/ip/utils.c
extern char * net_sprint_ll_addr_buf(const uint8_t * ll, uint8_t ll_len, char * buf, int buflen);
}
namespace chip {
namespace DeviceLayer {
namespace {
NetworkCommissioning::WiFiScanResponse ToScanResponse(const 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
const Map<wifi_iface_state, WiFiManager::StationStatus, 10>
WiFiManager::sStatusMap({ { WIFI_STATE_DISCONNECTED, WiFiManager::StationStatus::DISCONNECTED },
{ WIFI_STATE_INTERFACE_DISABLED, WiFiManager::StationStatus::DISABLED },
{ WIFI_STATE_INACTIVE, WiFiManager::StationStatus::DISABLED },
{ WIFI_STATE_SCANNING, WiFiManager::StationStatus::SCANNING },
{ WIFI_STATE_AUTHENTICATING, WiFiManager::StationStatus::CONNECTING },
{ WIFI_STATE_ASSOCIATING, WiFiManager::StationStatus::CONNECTING },
{ WIFI_STATE_ASSOCIATED, WiFiManager::StationStatus::CONNECTED },
{ WIFI_STATE_4WAY_HANDSHAKE, WiFiManager::StationStatus::PROVISIONING },
{ WIFI_STATE_GROUP_HANDSHAKE, WiFiManager::StationStatus::PROVISIONING },
{ WIFI_STATE_COMPLETED, WiFiManager::StationStatus::FULLY_PROVISIONED } });
const Map<uint32_t, WiFiManager::NetEventHandler, 4>
WiFiManager::sEventHandlerMap({ { NET_EVENT_WIFI_SCAN_RESULT, WiFiManager::ScanResultHandler },
{ NET_EVENT_WIFI_SCAN_DONE, WiFiManager::ScanDoneHandler },
{ NET_EVENT_WIFI_CONNECT_RESULT, WiFiManager::ConnectHandler },
{ NET_EVENT_WIFI_DISCONNECT_RESULT, WiFiManager::DisconnectHandler } });
void WiFiManager::WifiMgmtEventHandler(net_mgmt_event_callback * cb, uint32_t mgmtEvent, net_if * iface)
{
if (0 == strcmp(iface->if_dev->dev->name, "wlan0"))
{
Platform::UniquePtr<uint8_t> eventData(new uint8_t[cb->info_length]);
VerifyOrReturn(eventData);
memcpy(eventData.get(), cb->info, cb->info_length);
CHIP_ERROR status = SystemLayer().ScheduleLambda([data = eventData.get(), mgmtEvent]() {
if (data)
{
sEventHandlerMap[mgmtEvent](data);
// cleanup
delete[] data;
}
});
if (CHIP_NO_ERROR == status)
{
// the ownership has been transferred to the worker thread - release the buffer
eventData.release();
}
}
}
CHIP_ERROR WiFiManager::Init()
{
// 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;
});
net_mgmt_init_event_callback(&mWiFiMgmtClbk, WifiMgmtEventHandler, kWifiManagementEvents);
net_mgmt_add_event_callback(&mWiFiMgmtClbk);
ChipLogDetail(DeviceLayer, "WiFiManager has been initialized");
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::Scan(const ByteSpan & ssid, ScanResultCallback resultCallback, ScanDoneCallback doneCallback,
bool internalScan)
{
net_if * iface = InetUtils::GetInterface();
VerifyOrReturnError(nullptr != iface, CHIP_ERROR_INTERNAL);
mInternalScan = internalScan;
mScanResultCallback = resultCallback;
mScanDoneCallback = doneCallback;
mWiFiState = WIFI_STATE_SCANNING;
if (net_mgmt(NET_REQUEST_WIFI_SCAN, iface, NULL, 0))
{
ChipLogError(DeviceLayer, "Scan request failed");
return CHIP_ERROR_INTERNAL;
}
ChipLogDetail(DeviceLayer, "WiFi scanning started...");
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::ClearStationProvisioningData()
{
mWiFiParams.mRssi = std::numeric_limits<int8_t>::min();
memset(&mWiFiParams.mParams, 0, sizeof(mWiFiParams.mParams));
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::Connect(const ByteSpan & ssid, const ByteSpan & credentials, const ConnectionHandling & handling)
{
ChipLogDetail(DeviceLayer, "Connecting to WiFi network: %*s", ssid.size(), ssid.data());
mHandling.mOnConnectionSuccess = handling.mOnConnectionSuccess;
mHandling.mOnConnectionFailed = handling.mOnConnectionFailed;
mHandling.mConnectionTimeout = handling.mConnectionTimeout;
mWiFiState = WIFI_STATE_ASSOCIATING;
// Store SSID and credentials and perform the scan to detect the security mode supported by the AP.
// Zephyr WiFi connect request will be issued in the callback when we have the SSID match.
mWantedNetwork.Erase();
memcpy(mWantedNetwork.ssid, ssid.data(), ssid.size());
memcpy(mWantedNetwork.pass, credentials.data(), credentials.size());
mWantedNetwork.ssidLen = ssid.size();
mWantedNetwork.passLen = credentials.size();
return Scan(ssid, nullptr, nullptr, true /* internal scan */);
}
CHIP_ERROR WiFiManager::Disconnect()
{
net_if * iface = InetUtils::GetInterface();
VerifyOrReturnError(nullptr != iface, CHIP_ERROR_INTERNAL);
int status = net_mgmt(NET_REQUEST_WIFI_DISCONNECT, iface, NULL, 0);
if (status)
{
if (status == -EALREADY)
{
ChipLogDetail(DeviceLayer, "Already disconnected");
}
else
{
ChipLogDetail(DeviceLayer, "Disconnect request failed");
return CHIP_ERROR_INTERNAL;
}
}
else
{
ChipLogDetail(DeviceLayer, "Disconnect requested");
}
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiManager::GetWiFiInfo(WiFiInfo & info) const
{
net_if * iface = InetUtils::GetInterface();
VerifyOrReturnError(nullptr != iface, CHIP_ERROR_INTERNAL);
struct wifi_iface_status status = { 0 };
if (net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, iface, &status, sizeof(struct wifi_iface_status)))
{
ChipLogError(DeviceLayer, "Status request failed");
return CHIP_ERROR_INTERNAL;
}
if (status.state >= WIFI_STATE_ASSOCIATED)
{
uint8_t mac_string_buf[sizeof("xx:xx:xx:xx:xx:xx")];
net_sprint_ll_addr_buf(reinterpret_cast<const uint8_t *>(status.bssid), WIFI_MAC_ADDR_LEN,
reinterpret_cast<char *>(mac_string_buf), sizeof(mac_string_buf));
info.mBssId = ByteSpan(mac_string_buf, sizeof(mac_string_buf));
info.mSecurityType = static_cast<uint8_t>(status.security);
info.mWiFiVersion = static_cast<uint8_t>(status.link_mode);
info.mRssi = static_cast<int8_t>(status.rssi);
info.mChannel = static_cast<uint16_t>(status.channel);
info.mSsidLen = status.ssid_len;
memcpy(info.mSsid, status.ssid, status.ssid_len);
return CHIP_NO_ERROR;
}
return CHIP_ERROR_INTERNAL;
}
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;
}
void WiFiManager::ScanResultHandler(uint8_t * data)
{
const struct wifi_scan_result * scanResult = reinterpret_cast<const struct wifi_scan_result *>(data);
if (Instance().mInternalScan &&
Instance().mWantedNetwork.GetSsidSpan().data_equal(ByteSpan(scanResult->ssid, scanResult->ssid_length)))
{
// Prepare the connection parameters
// In case there are many networks with the same SSID choose the one with the best RSSI
if (scanResult->rssi > Instance().mWiFiParams.mRssi)
{
Instance().ClearStationProvisioningData();
Instance().mWiFiParams.mParams.ssid_length = static_cast<uint8_t>(Instance().mWantedNetwork.ssidLen);
Instance().mWiFiParams.mParams.ssid = Instance().mWantedNetwork.ssid;
// Fallback to the WIFI_SECURITY_TYPE_PSK if the security is unknown
Instance().mWiFiParams.mParams.security =
scanResult->security <= WIFI_SECURITY_TYPE_MAX ? scanResult->security : WIFI_SECURITY_TYPE_PSK;
Instance().mWiFiParams.mParams.psk_length = static_cast<uint8_t>(Instance().mWantedNetwork.passLen);
// If the security is none, WiFi driver expects the psk to be nullptr
if (Instance().mWiFiParams.mParams.security == WIFI_SECURITY_TYPE_NONE)
{
Instance().mWiFiParams.mParams.psk = nullptr;
}
else
{
Instance().mWiFiParams.mParams.psk = Instance().mWantedNetwork.pass;
}
Instance().mWiFiParams.mParams.timeout = Instance().mHandling.mConnectionTimeout.count();
Instance().mWiFiParams.mParams.channel = scanResult->channel;
Instance().mWiFiParams.mRssi = scanResult->rssi;
}
}
if (Instance().mScanResultCallback && !Instance().mInternalScan)
{
Instance().mScanResultCallback(ToScanResponse(scanResult));
}
}
void WiFiManager::ScanDoneHandler(uint8_t * data)
{
const wifi_status * status = reinterpret_cast<const wifi_status *>(data);
WiFiRequestStatus requestStatus = static_cast<WiFiRequestStatus>(status->status);
if (Instance().mScanDoneCallback && !Instance().mInternalScan)
{
Instance().mScanDoneCallback(requestStatus);
}
if (requestStatus == WiFiRequestStatus::FAILURE)
{
ChipLogDetail(DeviceLayer, "Scan request failed (%d)", status->status);
}
else
{
ChipLogDetail(DeviceLayer, "Scan request done (%d)", status->status);
// Internal scan is supposed to be followed by connection request
if (Instance().mInternalScan)
{
Instance().mWiFiState = WIFI_STATE_ASSOCIATING;
net_if * iface = InetUtils::GetInterface();
VerifyOrReturn(nullptr != iface, CHIP_ERROR_INTERNAL);
if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &(Instance().mWiFiParams.mParams), sizeof(wifi_connect_req_params)))
{
ChipLogError(DeviceLayer, "Connection request failed");
if (Instance().mHandling.mOnConnectionFailed)
{
Instance().mHandling.mOnConnectionFailed();
}
return;
}
ChipLogError(DeviceLayer, "Connection to %*s requested", Instance().mWiFiParams.mParams.ssid_length,
Instance().mWiFiParams.mParams.ssid);
Instance().mInternalScan = false;
}
}
}
void WiFiManager::SendRouterSolicitation(System::Layer * layer, void * param)
{
net_if * iface = InetUtils::GetInterface();
if (iface && iface->if_dev->link_addr.type == NET_LINK_ETHERNET)
{
net_if_start_rs(iface);
Instance().mRouterSolicitationCounter++;
if (Instance().mRouterSolicitationCounter < kRouterSolicitationMaxCount)
{
DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kRouterSolicitationIntervalMs),
SendRouterSolicitation, nullptr);
}
else
{
Instance().mRouterSolicitationCounter = 0;
}
}
}
void WiFiManager::ConnectHandler(uint8_t * data)
{
const wifi_status * status = reinterpret_cast<const wifi_status *>(data);
WiFiRequestStatus requestStatus = static_cast<WiFiRequestStatus>(status->status);
if (requestStatus == WiFiRequestStatus::FAILURE || requestStatus == WiFiRequestStatus::TERMINATED)
{
ChipLogDetail(DeviceLayer, "Connection to WiFi network failed or was terminated by another request");
Instance().mWiFiState = WIFI_STATE_DISCONNECTED;
if (Instance().mHandling.mOnConnectionFailed)
{
Instance().mHandling.mOnConnectionFailed();
}
}
else
{
// Workaround needed until sending Router Solicitation after connect will be done by the driver.
DeviceLayer::SystemLayer().StartTimer(
System::Clock::Milliseconds32(chip::Crypto::GetRandU16() % kMaxInitialRouterSolicitationDelayMs),
SendRouterSolicitation, nullptr);
ChipLogDetail(DeviceLayer, "Connected to WiFi network");
Instance().mWiFiState = WIFI_STATE_COMPLETED;
if (Instance().mHandling.mOnConnectionSuccess)
{
Instance().mHandling.mOnConnectionSuccess();
}
Instance().PostConnectivityStatusChange(kConnectivity_Established);
}
// cleanup the provisioning data as it is configured per each connect request
Instance().ClearStationProvisioningData();
}
void WiFiManager::DisconnectHandler(uint8_t * data)
{
ChipLogDetail(DeviceLayer, "WiFi station disconnected");
Instance().mWiFiState = WIFI_STATE_DISCONNECTED;
Instance().PostConnectivityStatusChange(kConnectivity_Lost);
}
WiFiManager::StationStatus WiFiManager::GetStationStatus() const
{
return WiFiManager::sStatusMap[mWiFiState];
}
void WiFiManager::PostConnectivityStatusChange(ConnectivityChange changeType)
{
ChipDeviceEvent networkEvent{};
networkEvent.Type = DeviceEventType::kWiFiConnectivityChange;
networkEvent.WiFiConnectivityChange.Result = changeType;
PlatformMgr().PostEventOrDie(&networkEvent);
}
} // namespace DeviceLayer
} // namespace chip