| /* |
| * |
| * Copyright (c) 2021-2022 Project CHIP Authors |
| * Copyright 2023 NXP |
| * |
| * 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 <platform/CHIPDeviceLayer.h> |
| |
| #include "NetworkCommissioningDriver.h" |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/SafeInt.h> |
| #include <wlan.h> |
| |
| using namespace ::chip; |
| |
| #if CHIP_DEVICE_CONFIG_ENABLE_WPA |
| namespace chip { |
| namespace DeviceLayer { |
| namespace NetworkCommissioning { |
| |
| namespace { |
| constexpr char kWiFiSSIDKeyName[] = "wifi-ssid"; |
| constexpr char kWiFiCredentialsKeyName[] = "wifi-pass"; |
| } // namespace |
| |
| class NXPScanResponseIterator : public Iterator<WiFiScanResponse> |
| { |
| public: |
| NXPScanResponseIterator(const size_t size, WiFiScanResponse * responses) : mSize(size), mResponses(responses) {} |
| size_t Count() override { return mSize; } |
| bool Next(WiFiScanResponse & item) override |
| { |
| if (mIternum >= mSize) |
| { |
| return false; |
| } |
| |
| item = mResponses[mIternum]; |
| mIternum++; |
| |
| return true; |
| } |
| void Release() override {} |
| |
| private: |
| const size_t mSize; |
| WiFiScanResponse * mResponses; |
| size_t mIternum = 0; |
| }; |
| |
| CHIP_ERROR NXPWiFiDriver::Init(NetworkStatusChangeCallback * networkStatusChangeCallback) |
| { |
| CHIP_ERROR err; |
| size_t ssidLen = 0; |
| size_t credentialsLen = 0; |
| |
| err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiSSIDKeyName, mSavedNetwork.ssid, sizeof(mSavedNetwork.ssid), &ssidLen); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogProgress(DeviceLayer, "WiFi network SSID not retrieved from persisted storage: %" CHIP_ERROR_FORMAT, err.Format()); |
| return err; |
| } |
| |
| err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiCredentialsKeyName, mSavedNetwork.credentials, |
| sizeof(mSavedNetwork.credentials), &credentialsLen); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogProgress(DeviceLayer, "WiFi network credentials not retrieved from persisted storage: %" CHIP_ERROR_FORMAT, |
| err.Format()); |
| return err; |
| } |
| |
| mSavedNetwork.credentialsLen = credentialsLen; |
| mSavedNetwork.ssidLen = ssidLen; |
| |
| mStagingNetwork = mSavedNetwork; |
| mpScanCallback = nullptr; |
| mpConnectCallback = nullptr; |
| mpStatusChangeCallback = networkStatusChangeCallback; |
| |
| // Connect to saved network |
| err = ConnectWiFiNetwork(mSavedNetwork.ssid, ssidLen, mSavedNetwork.credentials, credentialsLen); |
| |
| return err; |
| } |
| |
| void NXPWiFiDriver::Shutdown() |
| { |
| mpStatusChangeCallback = nullptr; |
| } |
| |
| CHIP_ERROR NXPWiFiDriver::CommitConfiguration() |
| { |
| ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiSSIDKeyName, mStagingNetwork.ssid, mStagingNetwork.ssidLen)); |
| ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiCredentialsKeyName, mStagingNetwork.credentials, |
| mStagingNetwork.credentialsLen)); |
| mSavedNetwork = mStagingNetwork; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR NXPWiFiDriver::RevertConfiguration() |
| { |
| mStagingNetwork = mSavedNetwork; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| bool NXPWiFiDriver::NetworkMatch(const WiFiNetwork & network, ByteSpan networkId) |
| { |
| return networkId.size() == network.ssidLen && memcmp(networkId.data(), network.ssid, network.ssidLen) == 0; |
| } |
| |
| Status NXPWiFiDriver::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); |
| 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 NXPWiFiDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) |
| { |
| int err_code = 0; |
| |
| outDebugText.reduce_size(0); |
| outNetworkIndex = 0; |
| VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound); |
| |
| err_code = wlan_remove_network((char *) networkId.data()); |
| |
| switch (err_code) |
| { |
| case -WM_E_INVAL: |
| ChipLogError(DeviceLayer, "Error: Network not found"); |
| break; |
| |
| case WM_SUCCESS: |
| /* Use empty ssid for representing invalid network */ |
| mStagingNetwork.ssidLen = 0; |
| memset(mStagingNetwork.ssid, 0, DeviceLayer::Internal::kMaxWiFiSSIDLength); |
| memset(mStagingNetwork.credentials, 0, DeviceLayer::Internal::kMaxWiFiKeyLength); |
| /* Save to persistent memory */ |
| CommitConfiguration(); |
| ChipLogProgress(DeviceLayer, "Successfully removed network"); |
| break; |
| |
| case WLAN_ERROR_STATE: |
| ChipLogError(DeviceLayer, "Error: Can't remove network in this state"); |
| break; |
| |
| default: |
| ChipLogError(DeviceLayer, "Error: Unable to remove network"); |
| break; |
| } |
| |
| return Status::kSuccess; |
| } |
| |
| /* Returns the network SSID. User needs to allocate a buffer of size >= DeviceLayer::Internal::kMaxWiFiSSIDLength. |
| * ssid - pointer to the returned SSID |
| */ |
| Status NXPWiFiDriver::GetNetworkSSID(char * ssid) |
| { |
| VerifyOrReturnError(ssid != NULL, Status::kOutOfRange); |
| |
| memcpy(ssid, mStagingNetwork.ssid, mStagingNetwork.ssidLen); |
| return Status::kSuccess; |
| } |
| |
| /* Returns the network password. User needs to allocate a buffer of size >= DeviceLayer::Internal::kMaxWiFiKeyLength. |
| * credentials - pointer to the returned password |
| */ |
| Status NXPWiFiDriver::GetNetworkPassword(char * credentials) |
| { |
| VerifyOrReturnError(credentials != NULL, Status::kOutOfRange); |
| |
| memcpy(credentials, mStagingNetwork.credentials, mStagingNetwork.credentialsLen); |
| return Status::kSuccess; |
| } |
| |
| Status NXPWiFiDriver::ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) |
| { |
| outDebugText.reduce_size(0); |
| |
| // Only one network is supported now |
| VerifyOrReturnError(index == 0, Status::kOutOfRange); |
| VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound); |
| |
| return Status::kSuccess; |
| } |
| |
| CHIP_ERROR NXPWiFiDriver::ConnectWiFiNetwork(const char * ssid, uint8_t ssidLen, const char * key, uint8_t keyLen) |
| { |
| return ConnectivityMgrImpl().ProvisionWiFiNetwork(ssid, ssidLen, key, keyLen); |
| } |
| |
| void NXPWiFiDriver::OnConnectWiFiNetwork(Status commissioningError, CharSpan debugText, int32_t connectStatus) |
| { |
| CommitConfiguration(); |
| |
| if (mpConnectCallback != nullptr) |
| { |
| mpConnectCallback->OnResult(commissioningError, debugText, connectStatus); |
| mpConnectCallback = nullptr; |
| } |
| } |
| |
| void NXPWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| Status networkingStatus = Status::kSuccess; |
| |
| ChipLogProgress(NetworkProvisioning, "Connecting to WiFi network: SSID: %.*s", static_cast<int>(networkId.size()), |
| networkId.data()); |
| |
| VerifyOrExit(NetworkMatch(mStagingNetwork, networkId), networkingStatus = Status::kNetworkIDNotFound); |
| VerifyOrExit(mpConnectCallback == nullptr, networkingStatus = Status::kUnknownError); |
| |
| mpConnectCallback = callback; |
| err = ConnectWiFiNetwork(reinterpret_cast<const char *>(mStagingNetwork.ssid), mStagingNetwork.ssidLen, |
| reinterpret_cast<const char *>(mStagingNetwork.credentials), mStagingNetwork.credentialsLen); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| networkingStatus = Status::kUnknownError; |
| } |
| |
| if (networkingStatus != Status::kSuccess) |
| { |
| ChipLogError(NetworkProvisioning, "Failed to start connecting to WiFi network: %" CHIP_ERROR_FORMAT, err.Format()); |
| mpConnectCallback = nullptr; |
| } |
| |
| /* Always inform the cluster of the network status so that in case of success, |
| * we have time send the response to the controller before switching to a new network. |
| */ |
| if (callback != nullptr) |
| { |
| callback->OnResult(networkingStatus, CharSpan(), 0); |
| } |
| } |
| |
| CHIP_ERROR NXPWiFiDriver::StartScanWiFiNetworks(ByteSpan ssid) |
| { |
| wlan_scan_params_v2_t wlan_scan_param; |
| |
| ChipLogProgress(DeviceLayer, "Scan for WiFi network(s) requested"); |
| |
| (void) memset(&wlan_scan_param, 0, sizeof(wlan_scan_params_v2_t)); |
| wlan_scan_param.cb = &NXPWiFiDriver::OnScanWiFiNetworkDone; |
| |
| if ((ssid.size() > 0) && (ssid.size() < MLAN_MAX_SSID_LENGTH)) |
| { |
| #ifdef CONFIG_COMBO_SCAN |
| (void) memcpy(wlan_scan_param.ssid[0], ssid.data(), ssid.size()); |
| #else |
| (void) memcpy(wlan_scan_param.ssid, ssid.data(), ssid.size()); |
| #endif |
| } |
| |
| int status = wlan_scan_with_opt(wlan_scan_param); |
| if (status != WM_SUCCESS) |
| { |
| ChipLogError(DeviceLayer, "Failed to start scan for WiFi network(s): %d", status); |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| // TODO should be modified to do it in the context of the Matter stack |
| int NXPWiFiDriver::OnScanWiFiNetworkDone(unsigned int count) |
| { |
| ChipLogProgress(DeviceLayer, "Scan for WiFi network(s) done, found: %u", count); |
| |
| if (count == 0) |
| { |
| if (GetInstance().mpScanCallback != nullptr) |
| { |
| GetInstance().mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), nullptr); |
| GetInstance().mpScanCallback = nullptr; |
| } |
| return WM_SUCCESS; |
| } |
| |
| std::unique_ptr<struct WiFiScanResponse[]> response_list_ptr(new struct WiFiScanResponse[count]); |
| if (response_list_ptr == nullptr) |
| { |
| ChipLogError(DeviceLayer, "Can't allocate memory for scan response_list"); |
| if (GetInstance().mpScanCallback) |
| { |
| GetInstance().mpScanCallback->OnFinished(Status::kUnknownError, CharSpan(), nullptr); |
| GetInstance().mpScanCallback = nullptr; |
| } |
| return WM_SUCCESS; |
| } |
| |
| struct WiFiScanResponse * response_list = response_list_ptr.get(); |
| |
| int valid = 0; |
| |
| for (int i = 0; i < count; i++) |
| { |
| struct wlan_scan_result res; |
| struct WiFiScanResponse response; |
| |
| int status = wlan_get_scan_result(i, &res); |
| |
| if (status != WM_SUCCESS) |
| { |
| ChipLogError(DeviceLayer, "Can't get scan result: %d", status); |
| continue; |
| } |
| |
| response.security = WiFiSecurity::kUnencrypted; |
| |
| if (res.wep != 0U) |
| { |
| response.security = WiFiSecurity::kWep; |
| } |
| else if (res.wpa != 0U) |
| { |
| response.security = WiFiSecurity::kWpaPersonal; |
| } |
| else if (res.wpa2 != 0U) |
| { |
| response.security = WiFiSecurity::kWpa2Personal; |
| } |
| else if (res.wpa3_sae != 0U) |
| { |
| response.security = WiFiSecurity::kWpa3Personal; |
| } |
| else if (res.wpa2_entp != 0U) |
| { |
| // ChipLogProgress(DeviceLayer, "Ignoring scanned network with WPA2-Enterprise security"); |
| // continue; |
| } |
| |
| if (res.ssid_len > DeviceLayer::Internal::kMaxWiFiSSIDLength) |
| { |
| ChipLogError(DeviceLayer, "Ignoring scanned network with too long SSID: %u", res.ssid_len); |
| continue; |
| } |
| memcpy(response.ssid, res.ssid, res.ssid_len); |
| response.ssidLen = res.ssid_len; |
| |
| memcpy(response.bssid, res.bssid, DeviceLayer::Internal::kWiFiBSSIDLength); |
| |
| response.channel = (uint16_t) res.channel; |
| response.wiFiBand = chip::DeviceLayer::NetworkCommissioning::WiFiBand::k2g4; // TODO 5 GHz also possible, but results don't |
| // show this information |
| response.rssi = -static_cast<int8_t>(res.rssi); |
| |
| response_list[valid] = response; |
| |
| valid++; |
| } |
| |
| if (CHIP_NO_ERROR == DeviceLayer::SystemLayer().ScheduleLambda([valid, response_list]() { |
| std::unique_ptr<struct WiFiScanResponse[]> auto_free(response_list); |
| NXPScanResponseIterator iter(valid, response_list); |
| if (GetInstance().mpScanCallback) |
| { |
| GetInstance().mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); |
| GetInstance().mpScanCallback = nullptr; |
| } |
| else |
| { |
| ChipLogError(DeviceLayer, "Can't find the ScanCallback function"); |
| } |
| })) |
| { |
| response_list_ptr.release(); |
| } |
| |
| return WM_SUCCESS; |
| } |
| |
| void NXPWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callback) |
| { |
| if (callback != nullptr) |
| { |
| mpScanCallback = callback; |
| if (StartScanWiFiNetworks(ssid) != CHIP_NO_ERROR) |
| { |
| mpScanCallback = nullptr; |
| callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr); |
| } |
| } |
| } |
| |
| uint32_t NXPWiFiDriver::GetSupportedWiFiBandsMask() const |
| { |
| uint32_t bands = static_cast<uint32_t>(1UL << chip::to_underlying(WiFiBandEnum::k2g4)); |
| #ifdef CONFIG_5GHz_SUPPORT |
| bands |= (1UL << chip::to_underlying(WiFiBandEnum::k5g)); |
| #endif |
| return bands; |
| } |
| |
| static CHIP_ERROR GetConnectedNetwork(Network & network) |
| { |
| struct wlan_network wlan_network; |
| int result; |
| |
| result = wlan_get_current_network(&wlan_network); |
| if (result != WM_SUCCESS) |
| { |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| uint8_t length = strnlen(reinterpret_cast<const char *>(wlan_network.ssid), DeviceLayer::Internal::kMaxWiFiSSIDLength); |
| |
| if (length > sizeof(network.networkID)) |
| { |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| memcpy(network.networkID, wlan_network.ssid, length); |
| network.networkIDLen = length; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| size_t NXPWiFiDriver::WiFiNetworkIterator::Count() |
| { |
| return mDriver->mStagingNetwork.ssidLen == 0 ? 0 : 1; |
| } |
| |
| bool NXPWiFiDriver::WiFiNetworkIterator::Next(Network & item) |
| { |
| if (mExhausted || mDriver->mStagingNetwork.ssidLen == 0) |
| { |
| return false; |
| } |
| |
| memcpy(item.networkID, mDriver->mStagingNetwork.ssid, mDriver->mStagingNetwork.ssidLen); |
| item.networkIDLen = mDriver->mStagingNetwork.ssidLen; |
| item.connected = false; |
| mExhausted = true; |
| |
| Network connectedNetwork; |
| CHIP_ERROR err = GetConnectedNetwork(connectedNetwork); |
| |
| if (err == CHIP_NO_ERROR) |
| { |
| if (connectedNetwork.networkIDLen == item.networkIDLen && |
| memcmp(connectedNetwork.networkID, item.networkID, item.networkIDLen) == 0) |
| { |
| item.connected = true; |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace NetworkCommissioning |
| } // namespace DeviceLayer |
| } // namespace chip |
| #endif // CHIP_DEVICE_CONFIG_ENABLE_WPA |