blob: a63df5cb88321f2ba426c6e1f3986736fb5e5025 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* 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 "network-commissioning.h"
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app/CommandHandlerInterface.h>
#include <app/InteractionModelEngine.h>
#include <app/clusters/general-commissioning-server/general-commissioning-server.h>
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
#include <lib/support/SafeInt.h>
#include <lib/support/SortUtils.h>
#include <lib/support/ThreadOperationalDataset.h>
#include <platform/DeviceControlServer.h>
#include <platform/PlatformManager.h>
#include <platform/internal/DeviceNetworkInfo.h>
#include <trace/trace.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::NetworkCommissioning;
namespace chip {
namespace app {
namespace Clusters {
namespace NetworkCommissioning {
using namespace DeviceLayer::NetworkCommissioning;
namespace {
// For WiFi and Thread scan results, each item will cose ~60 bytes in TLV, thus 15 is a safe upper bound of scan results.
constexpr size_t kMaxNetworksInScanResponse = 15;
enum ValidWiFiCredentialLength
{
kOpen = 0,
kWEP64 = 5,
kMinWPAPSK = 8,
kMaxWPAPSK = 63,
kWPAPSKHex = 64,
};
} // namespace
CHIP_ERROR Instance::Init()
{
ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->RegisterCommandHandler(this));
VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler, reinterpret_cast<intptr_t>(this)));
ReturnErrorOnFailure(mpBaseDriver->Init(this));
mLastNetworkingStatusValue.SetNull();
mLastConnectErrorValue.SetNull();
mLastNetworkIDLen = 0;
return CHIP_NO_ERROR;
}
void Instance::Shutdown()
{
mpBaseDriver->Shutdown();
}
void Instance::InvokeCommand(HandlerContext & ctxt)
{
if (mAsyncCommandHandle.Get() != nullptr)
{
// We have a command processing in the backend, reject all incoming commands.
ctxt.mCommandHandler.AddStatus(ctxt.mRequestPath, Protocols::InteractionModel::Status::Busy);
ctxt.SetCommandHandled();
return;
}
// Since mPath is used for building the response command, and we have checked that we are not pending the response of another
// command above. So it is safe to set the mPath here and not clear it when return.
mPath = ctxt.mRequestPath;
switch (ctxt.mRequestPath.mCommandId)
{
case Commands::ScanNetworks::Id:
VerifyOrReturn(mFeatureFlags.Has(NetworkCommissioningFeature::kWiFiNetworkInterface) ||
mFeatureFlags.Has(NetworkCommissioningFeature::kThreadNetworkInterface));
HandleCommand<Commands::ScanNetworks::DecodableType>(
ctxt, [this](HandlerContext & ctx, const auto & req) { HandleScanNetworks(ctx, req); });
return;
case Commands::AddOrUpdateWiFiNetwork::Id:
VerifyOrReturn(mFeatureFlags.Has(NetworkCommissioningFeature::kWiFiNetworkInterface));
HandleCommand<Commands::AddOrUpdateWiFiNetwork::DecodableType>(
ctxt, [this](HandlerContext & ctx, const auto & req) { HandleAddOrUpdateWiFiNetwork(ctx, req); });
return;
case Commands::AddOrUpdateThreadNetwork::Id:
VerifyOrReturn(mFeatureFlags.Has(NetworkCommissioningFeature::kThreadNetworkInterface));
HandleCommand<Commands::AddOrUpdateThreadNetwork::DecodableType>(
ctxt, [this](HandlerContext & ctx, const auto & req) { HandleAddOrUpdateThreadNetwork(ctx, req); });
return;
case Commands::RemoveNetwork::Id:
VerifyOrReturn(mFeatureFlags.Has(NetworkCommissioningFeature::kWiFiNetworkInterface) ||
mFeatureFlags.Has(NetworkCommissioningFeature::kThreadNetworkInterface));
HandleCommand<Commands::RemoveNetwork::DecodableType>(
ctxt, [this](HandlerContext & ctx, const auto & req) { HandleRemoveNetwork(ctx, req); });
return;
case Commands::ConnectNetwork::Id:
VerifyOrReturn(mFeatureFlags.Has(NetworkCommissioningFeature::kWiFiNetworkInterface) ||
mFeatureFlags.Has(NetworkCommissioningFeature::kThreadNetworkInterface));
HandleCommand<Commands::ConnectNetwork::DecodableType>(
ctxt, [this](HandlerContext & ctx, const auto & req) { HandleConnectNetwork(ctx, req); });
return;
case Commands::ReorderNetwork::Id:
VerifyOrReturn(mFeatureFlags.Has(NetworkCommissioningFeature::kWiFiNetworkInterface) ||
mFeatureFlags.Has(NetworkCommissioningFeature::kThreadNetworkInterface));
HandleCommand<Commands::ReorderNetwork::DecodableType>(
ctxt, [this](HandlerContext & ctx, const auto & req) { HandleReorderNetwork(ctx, req); });
return;
}
}
CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
switch (aPath.mAttributeId)
{
case Attributes::MaxNetworks::Id:
return aEncoder.Encode(mpBaseDriver->GetMaxNetworks());
case Attributes::Networks::Id:
return aEncoder.EncodeList([this](const auto & encoder) {
auto networks = mpBaseDriver->GetNetworks();
CHIP_ERROR err = CHIP_NO_ERROR;
Structs::NetworkInfo::Type networkForEncode;
NetworkCommissioning::Network network;
for (; networks != nullptr && networks->Next(network);)
{
networkForEncode.networkID = ByteSpan(network.networkID, network.networkIDLen);
networkForEncode.connected = network.connected;
SuccessOrExit(err = encoder.Encode(networkForEncode));
}
exit:
if (networks != nullptr)
{
networks->Release();
}
return err;
});
case Attributes::ScanMaxTimeSeconds::Id:
if (mpWirelessDriver != nullptr)
{
return aEncoder.Encode(mpWirelessDriver->GetScanNetworkTimeoutSeconds());
}
return CHIP_NO_ERROR;
case Attributes::ConnectMaxTimeSeconds::Id:
if (mpWirelessDriver != nullptr)
{
return aEncoder.Encode(mpWirelessDriver->GetConnectNetworkTimeoutSeconds());
}
return CHIP_NO_ERROR;
case Attributes::InterfaceEnabled::Id:
return aEncoder.Encode(mpBaseDriver->GetEnabled());
case Attributes::LastNetworkingStatus::Id:
return aEncoder.Encode(mLastNetworkingStatusValue);
case Attributes::LastNetworkID::Id:
if (mLastNetworkIDLen == 0)
{
return aEncoder.EncodeNull();
}
else
{
return aEncoder.Encode(ByteSpan(mLastNetworkID, mLastNetworkIDLen));
}
case Attributes::LastConnectErrorValue::Id:
return aEncoder.Encode(mLastConnectErrorValue);
case Attributes::FeatureMap::Id:
return aEncoder.Encode(mFeatureFlags);
default:
return CHIP_NO_ERROR;
}
}
CHIP_ERROR Instance::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder)
{
switch (aPath.mAttributeId)
{
case Attributes::InterfaceEnabled::Id:
bool value;
ReturnErrorOnFailure(aDecoder.Decode(value));
return mpBaseDriver->SetEnabled(value);
default:
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}
}
void Instance::OnNetworkingStatusChange(NetworkCommissioning::Status aCommissioningError, Optional<ByteSpan> aNetworkId,
Optional<int32_t> aConnectStatus)
{
if (aNetworkId.HasValue() && aNetworkId.Value().size() > kMaxNetworkIDLen)
{
ChipLogError(DeviceLayer, "Invalid network id received when calling OnNetworkingStatusChange");
return;
}
mLastNetworkingStatusValue.SetNonNull(aCommissioningError);
if (aNetworkId.HasValue())
{
memcpy(mLastNetworkID, aNetworkId.Value().data(), aNetworkId.Value().size());
mLastNetworkIDLen = static_cast<uint8_t>(aNetworkId.Value().size());
}
else
{
mLastNetworkIDLen = 0;
}
if (aConnectStatus.HasValue())
{
mLastConnectErrorValue.SetNonNull(aConnectStatus.Value());
}
else
{
mLastConnectErrorValue.SetNull();
}
}
void Instance::HandleScanNetworks(HandlerContext & ctx, const Commands::ScanNetworks::DecodableType & req)
{
MATTER_TRACE_EVENT_SCOPE("HandleScanNetwork", "NetworkCommissioning");
if (mFeatureFlags.Has(NetworkCommissioningFeature::kWiFiNetworkInterface))
{
ByteSpan ssid;
if (req.ssid.HasValue())
{
const auto & nullableSSID = req.ssid.Value();
if (!nullableSSID.IsNull())
{
ssid = nullableSSID.Value();
if (ssid.empty())
{
// Normalize empty span value to null ByteSpan.
// Spec 7.17.1. Empty string is an equivalent of null.
ssid = ByteSpan();
}
}
}
if (ssid.size() > DeviceLayer::Internal::kMaxWiFiSSIDLength)
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::InvalidCommand);
return;
}
mCurrentOperationBreadcrumb = req.breadcrumb;
mAsyncCommandHandle = CommandHandler::Handle(&ctx.mCommandHandler);
ctx.mCommandHandler.FlushAcksRightAwayOnSlowCommand();
mpDriver.Get<WiFiDriver *>()->ScanNetworks(ssid, this);
}
else if (mFeatureFlags.Has(NetworkCommissioningFeature::kThreadNetworkInterface))
{
mCurrentOperationBreadcrumb = req.breadcrumb;
mAsyncCommandHandle = CommandHandler::Handle(&ctx.mCommandHandler);
ctx.mCommandHandler.FlushAcksRightAwayOnSlowCommand();
mpDriver.Get<ThreadDriver *>()->ScanNetworks(this);
}
else
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::UnsupportedCommand);
}
}
namespace {
void FillDebugTextAndNetworkIndex(Commands::NetworkConfigResponse::Type & response, MutableCharSpan debugText, uint8_t networkIndex)
{
if (!debugText.empty())
{
response.debugText.SetValue(CharSpan(debugText.data(), debugText.size()));
}
if (response.networkingStatus == NetworkCommissioningStatus::kSuccess)
{
response.networkIndex.SetValue(networkIndex);
}
}
bool CheckFailSafeArmed(CommandHandlerInterface::HandlerContext & ctx)
{
auto & failSafeContext = chip::Server::GetInstance().GetFailSafeContext();
if (failSafeContext.IsFailSafeArmed(ctx.mCommandHandler.GetAccessingFabricIndex()))
{
return true;
}
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::FailsafeRequired);
return false;
}
} // namespace
void Instance::HandleAddOrUpdateWiFiNetwork(HandlerContext & ctx, const Commands::AddOrUpdateWiFiNetwork::DecodableType & req)
{
MATTER_TRACE_EVENT_SCOPE("HandleAddOrUpdateWiFiNetwork", "NetworkCommissioning");
VerifyOrReturn(CheckFailSafeArmed(ctx));
// Spec 11.8.8.4
// Valid Credentials length are:
// - 0 bytes: Unsecured (open) connection
// - 5 bytes: WEP-64 passphrase
// - 10 hexadecimal ASCII characters: WEP-64 40-bit hex raw PSK
// - 13 bytes: WEP-128 passphrase
// - 26 hexadecimal ASCII characters: WEP-128 104-bit hex raw PSK
// - 8..63 bytes: WPA/WPA2/WPA3 passphrase
// - 64 bytes: WPA/WPA2/WPA3 raw hex PSK
// Note 10 hex WEP64 and 13 bytes / 26 hex WEP128 passphrase are covered by 8~63 bytes WPA passphrase, so we don't check WEP64
// hex and WEP128 passphrase.
if (req.credentials.size() == ValidWiFiCredentialLength::kOpen || req.credentials.size() == ValidWiFiCredentialLength::kWEP64 ||
(req.credentials.size() >= ValidWiFiCredentialLength::kMinWPAPSK &&
req.credentials.size() <= ValidWiFiCredentialLength::kMaxWPAPSK))
{
// Valid length, the credentials can have any characters.
}
else if (req.credentials.size() == ValidWiFiCredentialLength::kWPAPSKHex)
{
for (size_t d = 0; d < req.credentials.size(); d++)
{
if (!isxdigit(req.credentials.data()[d]))
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::InvalidCommand);
return;
}
}
}
else
{
// Invalid length
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::InvalidCommand);
return;
}
Commands::NetworkConfigResponse::Type response;
MutableCharSpan debugText;
#if CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE
char debugTextBuffer[CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE];
debugText = MutableCharSpan(debugTextBuffer);
#endif
uint8_t outNetworkIndex = 0;
response.networkingStatus =
mpDriver.Get<WiFiDriver *>()->AddOrUpdateNetwork(req.ssid, req.credentials, debugText, outNetworkIndex);
FillDebugTextAndNetworkIndex(response, debugText, outNetworkIndex);
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
if (response.networkingStatus == NetworkCommissioningStatus::kSuccess)
{
UpdateBreadcrumb(req.breadcrumb);
}
}
void Instance::HandleAddOrUpdateThreadNetwork(HandlerContext & ctx, const Commands::AddOrUpdateThreadNetwork::DecodableType & req)
{
MATTER_TRACE_EVENT_SCOPE("HandleAddOrUpdateThreadNetwork", "NetworkCommissioning");
VerifyOrReturn(CheckFailSafeArmed(ctx));
Commands::NetworkConfigResponse::Type response;
MutableCharSpan debugText;
#if CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE
char debugTextBuffer[CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE];
debugText = MutableCharSpan(debugTextBuffer);
#endif
uint8_t outNetworkIndex = 0;
response.networkingStatus =
mpDriver.Get<ThreadDriver *>()->AddOrUpdateNetwork(req.operationalDataset, debugText, outNetworkIndex);
FillDebugTextAndNetworkIndex(response, debugText, outNetworkIndex);
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
if (response.networkingStatus == NetworkCommissioningStatus::kSuccess)
{
UpdateBreadcrumb(req.breadcrumb);
}
}
void Instance::UpdateBreadcrumb(const Optional<uint64_t> & breadcrumb)
{
VerifyOrReturn(breadcrumb.HasValue());
GeneralCommissioning::SetBreadcrumb(breadcrumb.Value());
}
void Instance::CommitSavedBreadcrumb()
{
// We rejected the command when there is another ongoing command, so mCurrentOperationBreadcrumb reflects the breadcrumb
// argument in the only background command.
UpdateBreadcrumb(mCurrentOperationBreadcrumb);
mCurrentOperationBreadcrumb.ClearValue();
}
void Instance::HandleRemoveNetwork(HandlerContext & ctx, const Commands::RemoveNetwork::DecodableType & req)
{
MATTER_TRACE_EVENT_SCOPE("HandleRemoveNetwork", "NetworkCommissioning");
VerifyOrReturn(CheckFailSafeArmed(ctx));
Commands::NetworkConfigResponse::Type response;
MutableCharSpan debugText;
#if CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE
char debugTextBuffer[CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE];
debugText = MutableCharSpan(debugTextBuffer);
#endif
uint8_t outNetworkIndex = 0;
response.networkingStatus = mpWirelessDriver->RemoveNetwork(req.networkID, debugText, outNetworkIndex);
FillDebugTextAndNetworkIndex(response, debugText, outNetworkIndex);
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
if (response.networkingStatus == NetworkCommissioningStatus::kSuccess)
{
UpdateBreadcrumb(req.breadcrumb);
}
}
void Instance::HandleConnectNetwork(HandlerContext & ctx, const Commands::ConnectNetwork::DecodableType & req)
{
MATTER_TRACE_EVENT_SCOPE("HandleConnectNetwork", "NetworkCommissioning");
if (req.networkID.size() > DeviceLayer::NetworkCommissioning::kMaxNetworkIDLen)
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::InvalidValue);
return;
}
VerifyOrReturn(CheckFailSafeArmed(ctx));
mConnectingNetworkIDLen = static_cast<uint8_t>(req.networkID.size());
memcpy(mConnectingNetworkID, req.networkID.data(), mConnectingNetworkIDLen);
mAsyncCommandHandle = CommandHandler::Handle(&ctx.mCommandHandler);
mCurrentOperationBreadcrumb = req.breadcrumb;
mpWirelessDriver->ConnectNetwork(req.networkID, this);
}
void Instance::HandleReorderNetwork(HandlerContext & ctx, const Commands::ReorderNetwork::DecodableType & req)
{
MATTER_TRACE_EVENT_SCOPE("HandleReorderNetwork", "NetworkCommissioning");
Commands::NetworkConfigResponse::Type response;
MutableCharSpan debugText;
#if CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE
char debugTextBuffer[CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE];
debugText = MutableCharSpan(debugTextBuffer);
#endif
response.networkingStatus = mpWirelessDriver->ReorderNetwork(req.networkID, req.networkIndex, debugText);
FillDebugTextAndNetworkIndex(response, debugText, req.networkIndex);
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
if (response.networkingStatus == NetworkCommissioningStatus::kSuccess)
{
UpdateBreadcrumb(req.breadcrumb);
}
}
void Instance::OnResult(Status commissioningError, CharSpan debugText, int32_t interfaceStatus)
{
auto commandHandleRef = std::move(mAsyncCommandHandle);
auto commandHandle = commandHandleRef.Get();
if (commandHandle == nullptr)
{
// When the platform shutted down, interaction model engine will invalidate all commandHandle to avoid dangling references.
// We may receive the callback after it and should make it noop.
return;
}
Commands::ConnectNetworkResponse::Type response;
response.networkingStatus = commissioningError;
if (!debugText.empty())
{
response.debugText.SetValue(debugText);
}
if (commissioningError == Status::kSuccess)
{
DeviceLayer::DeviceControlServer::DeviceControlSvr().PostConnectedToOperationalNetworkEvent(
ByteSpan(mLastNetworkID, mLastNetworkIDLen));
mLastConnectErrorValue.SetNull();
}
else
{
response.errorValue.SetNonNull(interfaceStatus);
mLastConnectErrorValue.SetNonNull(interfaceStatus);
}
mLastNetworkIDLen = mConnectingNetworkIDLen;
memcpy(mLastNetworkID, mConnectingNetworkID, mLastNetworkIDLen);
mLastNetworkingStatusValue.SetNonNull(commissioningError);
commandHandle->AddResponse(mPath, response);
if (commissioningError == NetworkCommissioningStatus::kSuccess)
{
CommitSavedBreadcrumb();
}
}
void Instance::OnFinished(Status status, CharSpan debugText, ThreadScanResponseIterator * networks)
{
CHIP_ERROR err = CHIP_NO_ERROR;
auto commandHandleRef = std::move(mAsyncCommandHandle);
auto commandHandle = commandHandleRef.Get();
if (commandHandle == nullptr)
{
// When the platform shutted down, interaction model engine will invalidate all commandHandle to avoid dangling references.
// We may receive the callback after it and should make it noop.
return;
}
mLastNetworkingStatusValue.SetNonNull(status);
mLastConnectErrorValue.SetNull();
mLastNetworkIDLen = 0;
TLV::TLVWriter * writer;
TLV::TLVType listContainerType;
ThreadScanResponse scanResponse;
chip::Platform::ScopedMemoryBuffer<ThreadScanResponse> scanResponseArray;
size_t scanResponseArrayLength = 0;
uint8_t extendedAddressBuffer[Thread::kSizeExtendedPanId];
SuccessOrExit(err = commandHandle->PrepareCommand(
ConcreteCommandPath(mPath.mEndpointId, NetworkCommissioning::Id, Commands::ScanNetworksResponse::Id)));
VerifyOrExit((writer = commandHandle->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(
err = writer->Put(TLV::ContextTag(to_underlying(Commands::ScanNetworksResponse::Fields::kNetworkingStatus)), status));
if (debugText.size() != 0)
{
SuccessOrExit(err = DataModel::Encode(
*writer, TLV::ContextTag(to_underlying(Commands::ScanNetworksResponse::Fields::kDebugText)), debugText));
}
SuccessOrExit(
err = writer->StartContainer(TLV::ContextTag(to_underlying(Commands::ScanNetworksResponse::Fields::kThreadScanResults)),
TLV::TLVType::kTLVType_Array, listContainerType));
VerifyOrExit(scanResponseArray.Alloc(chip::min(networks->Count(), kMaxNetworksInScanResponse)), err = CHIP_ERROR_NO_MEMORY);
for (; networks != nullptr && networks->Next(scanResponse);)
{
if ((scanResponseArrayLength == kMaxNetworksInScanResponse) &&
(scanResponseArray[scanResponseArrayLength - 1].rssi > scanResponse.rssi))
{
continue;
}
bool isDuplicated = false;
for (size_t i = 0; i < scanResponseArrayLength; i++)
{
if ((scanResponseArray[i].panId == scanResponse.panId) &&
(scanResponseArray[i].extendedPanId == scanResponse.extendedPanId))
{
if (scanResponseArray[i].rssi < scanResponse.rssi)
{
scanResponseArray[i] = scanResponseArray[--scanResponseArrayLength];
}
else
{
isDuplicated = true;
}
break;
}
}
if (isDuplicated)
{
continue;
}
if (scanResponseArrayLength < kMaxNetworksInScanResponse)
{
scanResponseArrayLength++;
}
scanResponseArray[scanResponseArrayLength - 1] = scanResponse;
Sorting::InsertionSort(scanResponseArray.Get(), scanResponseArrayLength,
[](const ThreadScanResponse & a, const ThreadScanResponse & b) -> bool { return a.rssi > b.rssi; });
}
for (size_t i = 0; i < scanResponseArrayLength; i++)
{
Structs::ThreadInterfaceScanResult::Type result;
Encoding::BigEndian::Put64(extendedAddressBuffer, scanResponseArray[i].extendedAddress);
result.panId = scanResponseArray[i].panId;
result.extendedPanId = scanResponseArray[i].extendedPanId;
result.networkName = CharSpan(scanResponseArray[i].networkName, scanResponseArray[i].networkNameLen);
result.channel = scanResponseArray[i].channel;
result.version = scanResponseArray[i].version;
result.extendedAddress = ByteSpan(extendedAddressBuffer);
result.rssi = scanResponseArray[i].rssi;
result.lqi = scanResponseArray[i].lqi;
SuccessOrExit(err = DataModel::Encode(*writer, TLV::AnonymousTag(), result));
}
SuccessOrExit(err = writer->EndContainer(listContainerType));
SuccessOrExit(err = commandHandle->FinishCommand());
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response: %s", err.AsString());
}
if (status == NetworkCommissioningStatus::kSuccess)
{
CommitSavedBreadcrumb();
}
networks->Release();
}
void Instance::OnFinished(Status status, CharSpan debugText, WiFiScanResponseIterator * networks)
{
CHIP_ERROR err = CHIP_NO_ERROR;
auto commandHandleRef = std::move(mAsyncCommandHandle);
auto commandHandle = commandHandleRef.Get();
if (commandHandle == nullptr)
{
// When the platform shutted down, interaction model engine will invalidate all commandHandle to avoid dangling references.
// We may receive the callback after it and should make it noop.
return;
}
mLastNetworkingStatusValue.SetNonNull(status);
mLastConnectErrorValue.SetNull();
mLastNetworkIDLen = 0;
TLV::TLVWriter * writer;
TLV::TLVType listContainerType;
WiFiScanResponse scanResponse;
size_t networksEncoded = 0;
SuccessOrExit(err = commandHandle->PrepareCommand(
ConcreteCommandPath(mPath.mEndpointId, NetworkCommissioning::Id, Commands::ScanNetworksResponse::Id)));
VerifyOrExit((writer = commandHandle->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(
err = writer->Put(TLV::ContextTag(to_underlying(Commands::ScanNetworksResponse::Fields::kNetworkingStatus)), status));
if (debugText.size() != 0)
{
SuccessOrExit(err = DataModel::Encode(
*writer, TLV::ContextTag(to_underlying(Commands::ScanNetworksResponse::Fields::kDebugText)), debugText));
}
SuccessOrExit(
err = writer->StartContainer(TLV::ContextTag(to_underlying(Commands::ScanNetworksResponse::Fields::kWiFiScanResults)),
TLV::TLVType::kTLVType_Array, listContainerType));
for (; networks != nullptr && networks->Next(scanResponse) && networksEncoded < kMaxNetworksInScanResponse; networksEncoded++)
{
Structs::WiFiInterfaceScanResult::Type result;
result.security = scanResponse.security;
result.ssid = ByteSpan(scanResponse.ssid, scanResponse.ssidLen);
result.bssid = ByteSpan(scanResponse.bssid, sizeof(scanResponse.bssid));
result.channel = scanResponse.channel;
result.wiFiBand = scanResponse.wiFiBand;
result.rssi = scanResponse.rssi;
SuccessOrExit(err = DataModel::Encode(*writer, TLV::AnonymousTag(), result));
}
SuccessOrExit(err = writer->EndContainer(listContainerType));
SuccessOrExit(err = commandHandle->FinishCommand());
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response: %s", err.AsString());
}
if (status == NetworkCommissioningStatus::kSuccess)
{
CommitSavedBreadcrumb();
}
if (networks != nullptr)
{
networks->Release();
}
}
void Instance::OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
Instance * this_ = reinterpret_cast<Instance *>(arg);
if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete)
{
this_->OnCommissioningComplete();
}
else if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired)
{
this_->OnFailSafeTimerExpired();
}
}
void Instance::OnCommissioningComplete()
{
VerifyOrReturn(mpWirelessDriver != nullptr);
ChipLogDetail(Zcl, "Commissioning complete, notify platform driver to persist network credentials.");
mpWirelessDriver->CommitConfiguration();
}
void Instance::OnFailSafeTimerExpired()
{
VerifyOrReturn(mpWirelessDriver != nullptr);
ChipLogDetail(Zcl, "Failsafe timeout, tell platform driver to revert network credentials.");
mpWirelessDriver->RevertConfiguration();
mAsyncCommandHandle.Release();
}
CHIP_ERROR Instance::EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context)
{
using namespace Clusters::NetworkCommissioning::Commands;
constexpr CommandId acceptedCommandsListWiFi[] = {
ScanNetworks::Id, AddOrUpdateWiFiNetwork::Id, RemoveNetwork::Id, ConnectNetwork::Id, ReorderNetwork::Id,
};
constexpr CommandId acceptedCommandsListThread[] = {
ScanNetworks::Id, AddOrUpdateThreadNetwork::Id, RemoveNetwork::Id, ConnectNetwork::Id, ReorderNetwork::Id,
};
if (mFeatureFlags.Has(NetworkCommissioningFeature::kThreadNetworkInterface))
{
for (const auto & cmd : acceptedCommandsListThread)
{
if (callback(cmd, context) != Loop::Continue)
{
break;
}
}
return CHIP_NO_ERROR;
}
if (mFeatureFlags.Has(NetworkCommissioningFeature::kWiFiNetworkInterface))
{
for (const auto & cmd : acceptedCommandsListWiFi)
{
if (callback(cmd, context) != Loop::Continue)
{
break;
}
}
return CHIP_NO_ERROR;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR Instance::EnumerateGeneratedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context)
{
using namespace Clusters::NetworkCommissioning::Commands;
constexpr CommandId generatedCommandsListWireless[] = { ScanNetworksResponse::Id, NetworkConfigResponse::Id,
ConnectNetworkResponse::Id };
if (mFeatureFlags.HasAny(NetworkCommissioningFeature::kWiFiNetworkInterface,
NetworkCommissioningFeature::kThreadNetworkInterface))
{
for (const auto & cmd : generatedCommandsListWireless)
{
if (callback(cmd, context) != Loop::Continue)
{
break;
}
}
}
return CHIP_NO_ERROR;
}
bool NullNetworkDriver::GetEnabled()
{
// Disable the interface and it cannot be enabled since there are no physical interfaces.
return false;
}
uint8_t NullNetworkDriver::GetMaxNetworks()
{
// The minimal value of MaxNetworks should be 1 per spec.
return 1;
}
DeviceLayer::NetworkCommissioning::NetworkIterator * NullNetworkDriver::GetNetworks()
{
// Instance::Read accepts nullptr as an empty NetworkIterator.
return nullptr;
}
} // namespace NetworkCommissioning
} // namespace Clusters
} // namespace app
} // namespace chip
// These functions are ember interfaces, they should never be implemented since all network commissioning cluster functions are
// implemented in NetworkCommissioning::Instance.
bool emberAfNetworkCommissioningClusterAddOrUpdateThreadNetworkCallback(
CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::AddOrUpdateThreadNetwork::DecodableType & commandData)
{
return false;
}
bool emberAfNetworkCommissioningClusterAddOrUpdateWiFiNetworkCallback(
CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::AddOrUpdateWiFiNetwork::DecodableType & commandData)
{
return false;
}
bool emberAfNetworkCommissioningClusterConnectNetworkCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::ConnectNetwork::DecodableType & commandData)
{
return false;
}
bool emberAfNetworkCommissioningClusterRemoveNetworkCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::RemoveNetwork::DecodableType & commandData)
{
return false;
}
bool emberAfNetworkCommissioningClusterScanNetworksCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::ScanNetworks::DecodableType & commandData)
{
return false;
}
bool emberAfNetworkCommissioningClusterReorderNetworkCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::ReorderNetwork::DecodableType & commandData)
{
return false;
}
void MatterNetworkCommissioningPluginServerInitCallback()
{
// Nothing to do, the server init routine will be done in Instance::Init()
}