blob: fa7b8322cff5ce21976455abb18d59e3c9f640e8 [file] [log] [blame]
/*
*
* Copyright (c) 2021-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.
*/
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/Linux/NetworkCommissioningDriver.h>
#include <limits>
#include <string>
#include <vector>
using namespace chip;
using namespace chip::Thread;
namespace chip {
namespace DeviceLayer {
namespace NetworkCommissioning {
#if CHIP_DEVICE_CONFIG_ENABLE_WPA
// TODO(#14172): Here, most interfaces are just calling ConnectivityManager interfaces, this is because the ConnectivityProvides
// some bootstrap code for the wpa_supplicant. However, we can wrap the wpa_supplicant dbus api directly (and remove the related
// code in ConnectivityManagerImpl).
namespace {
constexpr char kWiFiSSIDKeyName[] = "wifi-ssid";
constexpr char kWiFiCredentialsKeyName[] = "wifi-pass";
} // namespace
// NOTE: For WiFiDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will
// load the network config from k-v storage, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks,
// all changed are made on the staging network, and when the network is committed, it will update the mSavedNetwork to
// mStagingNetwork and persist the changes.
// NOTE: LinuxWiFiDriver uses network config with empty ssid (ssidLen = 0) for empty network config.
// NOTE: For now, the LinuxWiFiDriver only supports one network, this can be fixed by using the wpa_supplicant API directly (then
// wpa_supplicant will manage the networks for us.)
CHIP_ERROR LinuxWiFiDriver::Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback)
{
CHIP_ERROR err;
size_t ssidLen = 0;
size_t credentialsLen = 0;
err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiCredentialsKeyName, mSavedNetwork.credentials,
sizeof(mSavedNetwork.credentials), &credentialsLen);
if (err == CHIP_ERROR_KEY_NOT_FOUND)
{
return CHIP_NO_ERROR;
}
err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiSSIDKeyName, mSavedNetwork.ssid, sizeof(mSavedNetwork.ssid), &ssidLen);
if (err == CHIP_ERROR_KEY_NOT_FOUND)
{
return CHIP_NO_ERROR;
}
mSavedNetwork.credentialsLen = credentialsLen;
mSavedNetwork.ssidLen = ssidLen;
mStagingNetwork = mSavedNetwork;
ConnectivityMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback);
return CHIP_NO_ERROR;
}
void LinuxWiFiDriver::Shutdown()
{
ConnectivityMgrImpl().SetNetworkStatusChangeCallback(nullptr);
}
CHIP_ERROR LinuxWiFiDriver::CommitConfiguration()
{
ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiSSIDKeyName, mStagingNetwork.ssid, mStagingNetwork.ssidLen));
ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiCredentialsKeyName, mStagingNetwork.credentials,
mStagingNetwork.credentialsLen));
ReturnErrorOnFailure(ConnectivityMgrImpl().CommitConfig());
mSavedNetwork = mStagingNetwork;
return CHIP_NO_ERROR;
}
CHIP_ERROR LinuxWiFiDriver::RevertConfiguration()
{
mStagingNetwork = mSavedNetwork;
return CHIP_NO_ERROR;
}
bool LinuxWiFiDriver::NetworkMatch(const WiFiNetwork & network, ByteSpan networkId)
{
return networkId.size() == network.ssidLen && memcmp(networkId.data(), network.ssid, network.ssidLen) == 0;
}
Status LinuxWiFiDriver::AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText,
uint8_t & outNetworkIndex)
{
outDebugText.reduce_size(0);
outNetworkIndex = 0;
VerifyOrReturnError(mStagingNetwork.ssidLen == 0 || NetworkMatch(mStagingNetwork, ssid), Status::kBoundsExceeded);
static_assert(sizeof(WiFiNetwork::ssid) <= std::numeric_limits<decltype(WiFiNetwork::ssidLen)>::max(),
"Max length of WiFi ssid exceeds the limit of ssidLen field");
static_assert(sizeof(WiFiNetwork::credentials) <= std::numeric_limits<decltype(WiFiNetwork::credentialsLen)>::max(),
"Max length of WiFi credentials exceeds the limit of credentialsLen field");
// Do the check before setting the values, so the data is not updated on error.
VerifyOrReturnError(credentials.size() <= sizeof(mStagingNetwork.credentials), Status::kOutOfRange);
VerifyOrReturnError(ssid.size() <= sizeof(mStagingNetwork.ssid), Status::kOutOfRange);
memcpy(mStagingNetwork.credentials, credentials.data(), credentials.size());
mStagingNetwork.credentialsLen = static_cast<decltype(mStagingNetwork.credentialsLen)>(credentials.size());
memcpy(mStagingNetwork.ssid, ssid.data(), ssid.size());
mStagingNetwork.ssidLen = static_cast<decltype(mStagingNetwork.ssidLen)>(ssid.size());
return Status::kSuccess;
}
Status LinuxWiFiDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex)
{
outDebugText.reduce_size(0);
outNetworkIndex = 0;
VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound);
// Use empty ssid for representing invalid network
mStagingNetwork.ssidLen = 0;
return Status::kSuccess;
}
Status LinuxWiFiDriver::ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText)
{
outDebugText.reduce_size(0);
VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound);
// We only support one network, so reorder is actually no-op.
return Status::kSuccess;
}
void LinuxWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Status networkingStatus = Status::kSuccess;
VerifyOrExit(NetworkMatch(mStagingNetwork, networkId), networkingStatus = Status::kNetworkIDNotFound);
ChipLogProgress(NetworkProvisioning, "LinuxNetworkCommissioningDelegate: SSID: %s",
StringOrNullMarker((char *) networkId.data()));
err = ConnectivityMgrImpl().ConnectWiFiNetworkAsync(ByteSpan(mStagingNetwork.ssid, mStagingNetwork.ssidLen),
ByteSpan(mStagingNetwork.credentials, mStagingNetwork.credentialsLen),
callback);
exit:
if (err != CHIP_NO_ERROR)
{
networkingStatus = Status::kUnknownError;
}
if (networkingStatus != Status::kSuccess)
{
ChipLogError(NetworkProvisioning, "Failed to connect to WiFi network: %s", chip::ErrorStr(err));
callback->OnResult(networkingStatus, CharSpan(), 0);
}
}
void LinuxWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callback)
{
CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().StartWiFiScan(ssid, callback);
if (err != CHIP_NO_ERROR)
{
callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr);
}
}
size_t LinuxWiFiDriver::WiFiNetworkIterator::Count()
{
return driver->mStagingNetwork.ssidLen == 0 ? 0 : 1;
}
bool LinuxWiFiDriver::WiFiNetworkIterator::Next(Network & item)
{
if (exhausted || driver->mStagingNetwork.ssidLen == 0)
{
return false;
}
memcpy(item.networkID, driver->mStagingNetwork.ssid, driver->mStagingNetwork.ssidLen);
item.networkIDLen = driver->mStagingNetwork.ssidLen;
item.connected = false;
exhausted = true;
Network configuredNetwork;
CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().GetConfiguredNetwork(configuredNetwork);
if (err == CHIP_NO_ERROR)
{
if (DeviceLayer::ConnectivityMgrImpl().IsWiFiStationConnected() && configuredNetwork.networkIDLen == item.networkIDLen &&
memcmp(configuredNetwork.networkID, item.networkID, item.networkIDLen) == 0)
{
item.connected = true;
}
}
return true;
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
} // namespace NetworkCommissioning
} // namespace DeviceLayer
} // namespace chip