blob: 75c95ee20abc063d36a9f27c29d9c887d3fe638c [file] [log] [blame]
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
* Copyright (c) 2013-2017 Nest Labs, Inc.
* 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.
*/
/**
* @file
* Implementation of CHIP Device Controller, a common class
* that implements discovery, pairing and provisioning of CHIP
* devices.
*
*/
// module header, comes first
#include <controller/CHIPDeviceController.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/InteractionModelEngine.h>
#include <app/OperationalSessionSetup.h>
#include <app/server/Dnssd.h>
#include <controller/CurrentFabricRemover.h>
#include <controller/InvokeInteraction.h>
#include <credentials/CHIPCert.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <crypto/CHIPCryptoPAL.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/core/ErrorStr.h>
#include <lib/core/NodeId.h>
#include <lib/support/Base64.h>
#include <lib/support/CHIPArgParser.hpp>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/PersistentStorageMacros.h>
#include <lib/support/SafeInt.h>
#include <lib/support/ScopedBuffer.h>
#include <lib/support/ThreadOperationalDataset.h>
#include <lib/support/TimeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ExchangeContext.h>
#include <platform/LockTracker.h>
#include <protocols/secure_channel/MessageCounterManager.h>
#include <setup_payload/QRCodeSetupPayloadParser.h>
#include <tracing/macros.h>
#include <tracing/metric_event.h>
#if CONFIG_NETWORK_LAYER_BLE
#include <ble/Ble.h>
#include <transport/raw/BLE.h>
#endif
#include <errno.h>
#include <inttypes.h>
#include <memory>
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <time.h>
using namespace chip::Inet;
using namespace chip::System;
using namespace chip::Transport;
using namespace chip::Credentials;
using namespace chip::app::Clusters;
using namespace chip::Crypto;
using namespace chip::Tracing;
namespace chip {
namespace Controller {
using namespace chip::Encoding;
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
using namespace chip::Protocols::UserDirectedCommissioning;
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
DeviceController::DeviceController()
{
mState = State::NotInitialized;
}
CHIP_ERROR DeviceController::Init(ControllerInitParams params)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mState == State::NotInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(params.systemState != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(params.systemState->SystemLayer() != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(params.systemState->UDPEndPointManager() != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
#if CONFIG_NETWORK_LAYER_BLE
VerifyOrReturnError(params.systemState->BleLayer() != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
#endif
VerifyOrReturnError(params.systemState->TransportMgr() != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(mDNSResolver.Init(params.systemState->UDPEndPointManager()));
mDNSResolver.SetDiscoveryDelegate(this);
RegisterDeviceDiscoveryDelegate(params.deviceDiscoveryDelegate);
mVendorId = params.controllerVendorId;
if (params.operationalKeypair != nullptr || !params.controllerNOC.empty() || !params.controllerRCAC.empty())
{
ReturnErrorOnFailure(InitControllerNOCChain(params));
}
else if (params.fabricIndex.HasValue())
{
VerifyOrReturnError(params.systemState->Fabrics()->FabricCount() > 0, CHIP_ERROR_INVALID_ARGUMENT);
if (params.systemState->Fabrics()->FindFabricWithIndex(params.fabricIndex.Value()) != nullptr)
{
mFabricIndex = params.fabricIndex.Value();
}
else
{
ChipLogError(Controller, "There is no fabric corresponding to the given fabricIndex");
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
mSystemState = params.systemState->Retain();
mState = State::Initialized;
mRemoveFromFabricTableOnShutdown = params.removeFromFabricTableOnShutdown;
mDeleteFromFabricTableOnShutdown = params.deleteFromFabricTableOnShutdown;
if (GetFabricIndex() != kUndefinedFabricIndex)
{
ChipLogProgress(Controller,
"Joined the fabric at index %d. Fabric ID is 0x" ChipLogFormatX64
" (Compressed Fabric ID: " ChipLogFormatX64 ")",
GetFabricIndex(), ChipLogValueX64(GetFabricId()), ChipLogValueX64(GetCompressedFabricId()));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceController::InitControllerNOCChain(const ControllerInitParams & params)
{
FabricInfo newFabric;
constexpr uint32_t chipCertAllocatedLen = kMaxCHIPCertLength;
chip::Platform::ScopedMemoryBuffer<uint8_t> rcacBuf;
chip::Platform::ScopedMemoryBuffer<uint8_t> icacBuf;
chip::Platform::ScopedMemoryBuffer<uint8_t> nocBuf;
Credentials::P256PublicKeySpan rootPublicKeySpan;
FabricId fabricId;
NodeId nodeId;
bool hasExternallyOwnedKeypair = false;
Crypto::P256Keypair * externalOperationalKeypair = nullptr;
VendorId newFabricVendorId = params.controllerVendorId;
// There are three possibilities here in terms of what happens with our
// operational key:
// 1) We have an externally owned operational keypair.
// 2) We have an operational keypair that the fabric table should clone via
// serialize/deserialize.
// 3) We have no keypair at all, and the fabric table has been initialized
// with a key store.
if (params.operationalKeypair != nullptr)
{
hasExternallyOwnedKeypair = params.hasExternallyOwnedOperationalKeypair;
externalOperationalKeypair = params.operationalKeypair;
}
ReturnErrorCodeIf(!rcacBuf.Alloc(chipCertAllocatedLen), CHIP_ERROR_NO_MEMORY);
ReturnErrorCodeIf(!icacBuf.Alloc(chipCertAllocatedLen), CHIP_ERROR_NO_MEMORY);
ReturnErrorCodeIf(!nocBuf.Alloc(chipCertAllocatedLen), CHIP_ERROR_NO_MEMORY);
MutableByteSpan rcacSpan(rcacBuf.Get(), chipCertAllocatedLen);
ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerRCAC, rcacSpan));
ReturnErrorOnFailure(Credentials::ExtractPublicKeyFromChipCert(rcacSpan, rootPublicKeySpan));
Crypto::P256PublicKey rootPublicKey{ rootPublicKeySpan };
MutableByteSpan icacSpan;
if (params.controllerICAC.empty())
{
ChipLogProgress(Controller, "Intermediate CA is not needed");
}
else
{
icacSpan = MutableByteSpan(icacBuf.Get(), chipCertAllocatedLen);
ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerICAC, icacSpan));
}
MutableByteSpan nocSpan = MutableByteSpan(nocBuf.Get(), chipCertAllocatedLen);
ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerNOC, nocSpan));
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(nocSpan, &nodeId, &fabricId));
auto * fabricTable = params.systemState->Fabrics();
const FabricInfo * fabricInfo = nullptr;
//
// When multiple controllers are permitted on the same fabric, we need to find fabrics with
// nodeId as an extra discriminant since we can have multiple FabricInfo objects that all
// collide on the same fabric. Not doing so may result in a match with an existing FabricInfo
// instance that matches the fabric in the provided NOC but is associated with a different NodeId
// that is already in use by another active controller instance. That will effectively cause it
// to change its identity inadvertently, which is not acceptable.
//
// TODO: Figure out how to clean up unreclaimed FabricInfos restored from persistent
// storage that are not in use by active DeviceController instances. Also, figure out
// how to reclaim FabricInfo slots when a DeviceController instance is deleted.
//
if (params.permitMultiControllerFabrics)
{
fabricInfo = fabricTable->FindIdentity(rootPublicKey, fabricId, nodeId);
}
else
{
fabricInfo = fabricTable->FindFabric(rootPublicKey, fabricId);
}
bool fabricFoundInTable = (fabricInfo != nullptr);
FabricIndex fabricIndex = fabricFoundInTable ? fabricInfo->GetFabricIndex() : kUndefinedFabricIndex;
CHIP_ERROR err = CHIP_NO_ERROR;
auto advertiseOperational =
params.enableServerInteractions ? FabricTable::AdvertiseIdentity::Yes : FabricTable::AdvertiseIdentity::No;
//
// We permit colliding fabrics when multiple controllers are present on the same logical fabric
// since each controller is associated with a unique FabricInfo 'identity' object and consequently,
// a unique FabricIndex.
//
// This sets a flag that will be cleared automatically when the fabric is committed/reverted later
// in this function.
//
if (params.permitMultiControllerFabrics)
{
fabricTable->PermitCollidingFabrics();
}
// We have 4 cases to handle legacy usage of direct operational key injection
if (externalOperationalKeypair)
{
// Cases 1 and 2: Injected operational keys
// CASE 1: Fabric update with injected key
if (fabricFoundInTable)
{
err = fabricTable->UpdatePendingFabricWithProvidedOpKey(fabricIndex, nocSpan, icacSpan, externalOperationalKeypair,
hasExternallyOwnedKeypair, advertiseOperational);
}
else
// CASE 2: New fabric with injected key
{
err = fabricTable->AddNewPendingTrustedRootCert(rcacSpan);
if (err == CHIP_NO_ERROR)
{
err = fabricTable->AddNewPendingFabricWithProvidedOpKey(nocSpan, icacSpan, newFabricVendorId,
externalOperationalKeypair, hasExternallyOwnedKeypair,
&fabricIndex, advertiseOperational);
}
}
}
else
{
// Cases 3 and 4: OperationalKeystore has the keys
// CASE 3: Fabric update with operational keystore
if (fabricFoundInTable)
{
VerifyOrReturnError(fabricTable->HasOperationalKeyForFabric(fabricIndex), CHIP_ERROR_KEY_NOT_FOUND);
err = fabricTable->UpdatePendingFabricWithOperationalKeystore(fabricIndex, nocSpan, icacSpan, advertiseOperational);
}
else
// CASE 4: New fabric with operational keystore
{
err = fabricTable->AddNewPendingTrustedRootCert(rcacSpan);
if (err == CHIP_NO_ERROR)
{
err = fabricTable->AddNewPendingFabricWithOperationalKeystore(nocSpan, icacSpan, newFabricVendorId, &fabricIndex,
advertiseOperational);
}
if (err == CHIP_NO_ERROR)
{
// Now that we know our planned fabric index, verify that the
// keystore has a key for it.
if (!fabricTable->HasOperationalKeyForFabric(fabricIndex))
{
err = CHIP_ERROR_KEY_NOT_FOUND;
}
}
}
}
// Commit after setup, error-out on failure.
if (err == CHIP_NO_ERROR)
{
// No need to revert on error: CommitPendingFabricData reverts internally on *any* error.
err = fabricTable->CommitPendingFabricData();
}
else
{
fabricTable->RevertPendingFabricData();
}
ReturnErrorOnFailure(err);
VerifyOrReturnError(fabricIndex != kUndefinedFabricIndex, CHIP_ERROR_INTERNAL);
mFabricIndex = fabricIndex;
mAdvertiseIdentity = advertiseOperational;
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceController::UpdateControllerNOCChain(const ByteSpan & noc, const ByteSpan & icac,
Crypto::P256Keypair * operationalKeypair,
bool operationalKeypairExternalOwned)
{
VerifyOrReturnError(mFabricIndex != kUndefinedFabricIndex, CHIP_ERROR_INTERNAL);
VerifyOrReturnError(mSystemState != nullptr, CHIP_ERROR_INTERNAL);
FabricTable * fabricTable = mSystemState->Fabrics();
CHIP_ERROR err = CHIP_NO_ERROR;
FabricId fabricId;
NodeId nodeId;
CATValues oldCats;
CATValues newCats;
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &nodeId, &fabricId));
ReturnErrorOnFailure(fabricTable->FetchCATs(mFabricIndex, oldCats));
ReturnErrorOnFailure(ExtractCATsFromOpCert(noc, newCats));
bool needCloseSession = true;
if (GetFabricInfo()->GetNodeId() == nodeId && oldCats == newCats)
{
needCloseSession = false;
}
if (operationalKeypair != nullptr)
{
err = fabricTable->UpdatePendingFabricWithProvidedOpKey(mFabricIndex, noc, icac, operationalKeypair,
operationalKeypairExternalOwned, mAdvertiseIdentity);
}
else
{
VerifyOrReturnError(fabricTable->HasOperationalKeyForFabric(mFabricIndex), CHIP_ERROR_KEY_NOT_FOUND);
err = fabricTable->UpdatePendingFabricWithOperationalKeystore(mFabricIndex, noc, icac, mAdvertiseIdentity);
}
if (err == CHIP_NO_ERROR)
{
err = fabricTable->CommitPendingFabricData();
}
else
{
fabricTable->RevertPendingFabricData();
}
ReturnErrorOnFailure(err);
if (needCloseSession)
{
// If the node id or CATs have changed, our existing CASE sessions are no longer valid,
// because the other side will think anything coming over those sessions comes from our
// old node ID, and the new CATs might not satisfy the ACL requirements of the other side.
mSystemState->SessionMgr()->ExpireAllSessionsForFabric(mFabricIndex);
}
ChipLogProgress(Controller, "Controller NOC chain has updated");
return CHIP_NO_ERROR;
}
void DeviceController::Shutdown()
{
assertChipStackLockedByCurrentThread();
VerifyOrReturn(mState != State::NotInitialized);
// If our state is initialialized it means mSystemState is valid,
// and we can use it below before we release our reference to it.
ChipLogDetail(Controller, "Shutting down the controller");
mState = State::NotInitialized;
if (mFabricIndex != kUndefinedFabricIndex)
{
// Shut down any subscription clients for this fabric.
app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(mFabricIndex);
// Shut down any ongoing CASE session activity we have. We're going to
// assume that all sessions for our fabric belong to us here.
mSystemState->CASESessionMgr()->ReleaseSessionsForFabric(mFabricIndex);
// TODO: The CASE session manager does not shut down existing CASE
// sessions. It just shuts down any ongoing CASE session establishment
// we're in the middle of as initiator. Maybe it should shut down
// existing sessions too?
mSystemState->SessionMgr()->ExpireAllSessionsForFabric(mFabricIndex);
if (mDeleteFromFabricTableOnShutdown)
{
mSystemState->Fabrics()->Delete(mFabricIndex);
}
else if (mRemoveFromFabricTableOnShutdown)
{
mSystemState->Fabrics()->Forget(mFabricIndex);
}
}
mSystemState->Release();
mSystemState = nullptr;
mDNSResolver.Shutdown();
mDeviceDiscoveryDelegate = nullptr;
}
CHIP_ERROR DeviceController::GetPeerAddressAndPort(NodeId peerId, Inet::IPAddress & addr, uint16_t & port)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
Transport::PeerAddress peerAddr;
ReturnErrorOnFailure(mSystemState->CASESessionMgr()->GetPeerAddress(GetPeerScopedId(peerId), peerAddr));
addr = peerAddr.GetIPAddress();
port = peerAddr.GetPort();
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceController::GetPeerAddress(NodeId nodeId, Transport::PeerAddress & addr)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(mSystemState->CASESessionMgr()->GetPeerAddress(GetPeerScopedId(nodeId), addr));
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceController::ComputePASEVerifier(uint32_t iterations, uint32_t setupPincode, const ByteSpan & salt,
Spake2pVerifier & outVerifier)
{
ReturnErrorOnFailure(PASESession::GeneratePASEVerifier(outVerifier, iterations, salt, /* useRandomPIN= */ false, setupPincode));
return CHIP_NO_ERROR;
}
ControllerDeviceInitParams DeviceController::GetControllerDeviceInitParams()
{
return ControllerDeviceInitParams{
.sessionManager = mSystemState->SessionMgr(),
.exchangeMgr = mSystemState->ExchangeMgr(),
};
}
DeviceCommissioner::DeviceCommissioner() :
mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this),
#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
mOnDeviceConnectionRetryCallback(OnDeviceConnectionRetryFn, this),
#endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
mDeviceAttestationInformationVerificationCallback(OnDeviceAttestationInformationVerification, this),
mDeviceNOCChainCallback(OnDeviceNOCChainGeneration, this), mSetUpCodePairer(this)
{}
CHIP_ERROR DeviceCommissioner::Init(CommissionerInitParams params)
{
VerifyOrReturnError(params.operationalCredentialsDelegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mOperationalCredentialsDelegate = params.operationalCredentialsDelegate;
ReturnErrorOnFailure(DeviceController::Init(params));
mPairingDelegate = params.pairingDelegate;
// Configure device attestation validation
mDeviceAttestationVerifier = params.deviceAttestationVerifier;
if (mDeviceAttestationVerifier == nullptr)
{
mDeviceAttestationVerifier = Credentials::GetDeviceAttestationVerifier();
if (mDeviceAttestationVerifier == nullptr)
{
ChipLogError(Controller,
"Missing DeviceAttestationVerifier configuration at DeviceCommissioner init and none set with "
"Credentials::SetDeviceAttestationVerifier()!");
return CHIP_ERROR_INVALID_ARGUMENT;
}
// We fell back on a default from singleton accessor.
ChipLogProgress(Controller,
"*** Missing DeviceAttestationVerifier configuration at DeviceCommissioner init: using global default, "
"consider passing one in CommissionerInitParams.");
}
if (params.defaultCommissioner != nullptr)
{
mDefaultCommissioner = params.defaultCommissioner;
}
else
{
mDefaultCommissioner = &mAutoCommissioner;
}
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY // make this commissioner discoverable
mUdcTransportMgr = chip::Platform::New<UdcTransportMgr>();
ReturnErrorOnFailure(mUdcTransportMgr->Init(Transport::UdpListenParameters(mSystemState->UDPEndPointManager())
.SetAddressType(Inet::IPAddressType::kIPv6)
.SetListenPort(static_cast<uint16_t>(mUdcListenPort))
#if INET_CONFIG_ENABLE_IPV4
,
Transport::UdpListenParameters(mSystemState->UDPEndPointManager())
.SetAddressType(Inet::IPAddressType::kIPv4)
.SetListenPort(static_cast<uint16_t>(mUdcListenPort))
#endif // INET_CONFIG_ENABLE_IPV4
));
mUdcServer = chip::Platform::New<UserDirectedCommissioningServer>();
mUdcTransportMgr->SetSessionManager(mUdcServer);
mUdcServer->SetTransportManager(mUdcTransportMgr);
mUdcServer->SetInstanceNameResolver(this);
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
mSetUpCodePairer.SetSystemLayer(mSystemState->SystemLayer());
#if CONFIG_NETWORK_LAYER_BLE
mSetUpCodePairer.SetBleLayer(mSystemState->BleLayer());
#endif // CONFIG_NETWORK_LAYER_BLE
return CHIP_NO_ERROR;
}
void DeviceCommissioner::Shutdown()
{
VerifyOrReturn(mState != State::NotInitialized);
ChipLogDetail(Controller, "Shutting down the commissioner");
mSetUpCodePairer.StopPairing();
// Check to see if pairing in progress before shutting down
CommissioneeDeviceProxy * device = mDeviceInPASEEstablishment;
if (device != nullptr && device->IsSessionSetupInProgress())
{
ChipLogDetail(Controller, "Setup in progress, stopping setup before shutting down");
OnSessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
}
CancelCommissioningInteractions();
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY // make this commissioner discoverable
if (mUdcTransportMgr != nullptr)
{
chip::Platform::Delete(mUdcTransportMgr);
mUdcTransportMgr = nullptr;
}
if (mUdcServer != nullptr)
{
mUdcServer->SetInstanceNameResolver(nullptr);
chip::Platform::Delete(mUdcServer);
mUdcServer = nullptr;
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
// Release everything from the commissionee device pool here.
// Make sure to use ReleaseCommissioneeDevice so we don't keep dangling
// pointers to the device objects.
mCommissioneeDevicePool.ForEachActiveObject([this](auto * commissioneeDevice) {
ReleaseCommissioneeDevice(commissioneeDevice);
return Loop::Continue;
});
DeviceController::Shutdown();
}
CommissioneeDeviceProxy * DeviceCommissioner::FindCommissioneeDevice(NodeId id)
{
MATTER_TRACE_SCOPE("FindCommissioneeDevice", "DeviceCommissioner");
CommissioneeDeviceProxy * foundDevice = nullptr;
mCommissioneeDevicePool.ForEachActiveObject([&](auto * deviceProxy) {
if (deviceProxy->GetDeviceId() == id)
{
foundDevice = deviceProxy;
return Loop::Break;
}
return Loop::Continue;
});
return foundDevice;
}
CommissioneeDeviceProxy * DeviceCommissioner::FindCommissioneeDevice(const Transport::PeerAddress & peerAddress)
{
CommissioneeDeviceProxy * foundDevice = nullptr;
mCommissioneeDevicePool.ForEachActiveObject([&](auto * deviceProxy) {
if (deviceProxy->GetPeerAddress() == peerAddress)
{
foundDevice = deviceProxy;
return Loop::Break;
}
return Loop::Continue;
});
return foundDevice;
}
void DeviceCommissioner::ReleaseCommissioneeDevice(CommissioneeDeviceProxy * device)
{
#if CONFIG_NETWORK_LAYER_BLE
if (mSystemState->BleLayer() != nullptr && device->GetDeviceTransportType() == Transport::Type::kBle)
{
// We only support one BLE connection, so if this is BLE, close it
ChipLogProgress(Discovery, "Closing all BLE connections");
mSystemState->BleLayer()->CloseAllBleConnections();
}
#endif
// Make sure that there will be no dangling pointer
if (mDeviceInPASEEstablishment == device)
{
mDeviceInPASEEstablishment = nullptr;
}
if (mDeviceBeingCommissioned == device)
{
mDeviceBeingCommissioned = nullptr;
}
// Release the commissionee device after we have nulled out our pointers,
// because that can call back in to us with error notifications as the
// session is released.
mCommissioneeDevicePool.ReleaseObject(device);
}
CHIP_ERROR DeviceCommissioner::GetDeviceBeingCommissioned(NodeId deviceId, CommissioneeDeviceProxy ** out_device)
{
VerifyOrReturnError(out_device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
CommissioneeDeviceProxy * device = FindCommissioneeDevice(deviceId);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
*out_device = device;
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, const char * setUpCode, const CommissioningParameters & params,
DiscoveryType discoveryType, Optional<Dnssd::CommonResolutionData> resolutionData)
{
MATTER_TRACE_SCOPE("PairDevice", "DeviceCommissioner");
if (mDefaultCommissioner == nullptr)
{
ChipLogError(Controller, "No default commissioner is specified");
return CHIP_ERROR_INCORRECT_STATE;
}
ReturnErrorOnFailure(mDefaultCommissioner->SetCommissioningParameters(params));
return mSetUpCodePairer.PairDevice(remoteDeviceId, setUpCode, SetupCodePairerBehaviour::kCommission, discoveryType,
resolutionData);
}
CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, const char * setUpCode, DiscoveryType discoveryType,
Optional<Dnssd::CommonResolutionData> resolutionData)
{
MATTER_TRACE_SCOPE("PairDevice", "DeviceCommissioner");
return mSetUpCodePairer.PairDevice(remoteDeviceId, setUpCode, SetupCodePairerBehaviour::kCommission, discoveryType,
resolutionData);
}
CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & params)
{
MATTER_TRACE_SCOPE("PairDevice", "DeviceCommissioner");
ReturnErrorOnFailureWithMetric(kMetricDeviceCommissionerCommission, EstablishPASEConnection(remoteDeviceId, params));
auto errorCode = Commission(remoteDeviceId);
VerifyOrDoWithMetric(kMetricDeviceCommissionerCommission, CHIP_NO_ERROR == errorCode, errorCode);
return errorCode;
}
CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & rendezvousParams,
CommissioningParameters & commissioningParams)
{
MATTER_TRACE_SCOPE("PairDevice", "DeviceCommissioner");
ReturnErrorOnFailureWithMetric(kMetricDeviceCommissionerCommission, EstablishPASEConnection(remoteDeviceId, rendezvousParams));
auto errorCode = Commission(remoteDeviceId, commissioningParams);
VerifyOrDoWithMetric(kMetricDeviceCommissionerCommission, CHIP_NO_ERROR == errorCode, errorCode);
return errorCode;
}
CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, const char * setUpCode, DiscoveryType discoveryType,
Optional<Dnssd::CommonResolutionData> resolutionData)
{
MATTER_TRACE_SCOPE("EstablishPASEConnection", "DeviceCommissioner");
return mSetUpCodePairer.PairDevice(remoteDeviceId, setUpCode, SetupCodePairerBehaviour::kPaseOnly, discoveryType,
resolutionData);
}
CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, RendezvousParameters & params)
{
MATTER_TRACE_SCOPE("EstablishPASEConnection", "DeviceCommissioner");
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissionerPASESession);
CHIP_ERROR err = CHIP_NO_ERROR;
CommissioneeDeviceProxy * device = nullptr;
CommissioneeDeviceProxy * current = nullptr;
Transport::PeerAddress peerAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any);
Messaging::ExchangeContext * exchangeCtxt = nullptr;
Optional<SessionHandle> session;
VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mDeviceInPASEEstablishment == nullptr, err = CHIP_ERROR_INCORRECT_STATE);
// TODO(#13940): We need to specify the peer address for BLE transport in bindings.
if (params.GetPeerAddress().GetTransportType() == Transport::Type::kBle ||
params.GetPeerAddress().GetTransportType() == Transport::Type::kUndefined)
{
#if CONFIG_NETWORK_LAYER_BLE
#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
ConnectBleTransportToSelf();
#endif // CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
if (!params.HasBleLayer())
{
params.SetPeerAddress(Transport::PeerAddress::BLE());
}
peerAddress = Transport::PeerAddress::BLE();
#endif // CONFIG_NETWORK_LAYER_BLE
}
else if (params.GetPeerAddress().GetTransportType() == Transport::Type::kTcp ||
params.GetPeerAddress().GetTransportType() == Transport::Type::kUdp)
{
peerAddress = Transport::PeerAddress::UDP(params.GetPeerAddress().GetIPAddress(), params.GetPeerAddress().GetPort(),
params.GetPeerAddress().GetInterface());
}
current = FindCommissioneeDevice(peerAddress);
if (current != nullptr)
{
if (current->GetDeviceId() == remoteDeviceId)
{
// We might be able to just reuse its connection if it has one or is
// working on one.
if (current->IsSecureConnected())
{
if (mPairingDelegate)
{
// We already have an open secure session to this device, call the callback immediately and early return.
mPairingDelegate->OnPairingComplete(CHIP_NO_ERROR);
}
MATTER_LOG_METRIC_END(kMetricDeviceCommissionerPASESession, CHIP_NO_ERROR);
return CHIP_NO_ERROR;
}
if (current->IsSessionSetupInProgress())
{
// We're not connected yet, but we're in the process of connecting. Pairing delegate will get a callback when
// connection completes
return CHIP_NO_ERROR;
}
}
// Either the consumer wants to assign a different device id to this
// peer address now (so we can't reuse the commissionee device we have
// already) or something has gone strange. Delete the old device, try
// again.
ChipLogError(Controller, "Found unconnected device, removing");
ReleaseCommissioneeDevice(current);
}
device = mCommissioneeDevicePool.CreateObject();
VerifyOrExit(device != nullptr, err = CHIP_ERROR_NO_MEMORY);
mDeviceInPASEEstablishment = device;
device->Init(GetControllerDeviceInitParams(), remoteDeviceId, peerAddress);
device->UpdateDeviceData(params.GetPeerAddress(), params.GetMRPConfig());
#if CONFIG_NETWORK_LAYER_BLE
if (params.GetPeerAddress().GetTransportType() == Transport::Type::kBle)
{
if (params.HasConnectionObject())
{
SuccessOrExit(err = mSystemState->BleLayer()->NewBleConnectionByObject(params.GetConnectionObject()));
}
else if (params.HasDiscoveredObject())
{
// The RendezvousParameters argument needs to be recovered if the search succeed, so save them
// for later.
mRendezvousParametersForDeviceDiscoveredOverBle = params;
SuccessOrExit(err = mSystemState->BleLayer()->NewBleConnectionByObject(params.GetDiscoveredObject(), this,
OnDiscoveredDeviceOverBleSuccess,
OnDiscoveredDeviceOverBleError));
ExitNow(CHIP_NO_ERROR);
}
else if (params.HasDiscriminator())
{
// The RendezvousParameters argument needs to be recovered if the search succeed, so save them
// for later.
mRendezvousParametersForDeviceDiscoveredOverBle = params;
SetupDiscriminator discriminator;
discriminator.SetLongValue(params.GetDiscriminator());
SuccessOrExit(err = mSystemState->BleLayer()->NewBleConnectionByDiscriminator(
discriminator, this, OnDiscoveredDeviceOverBleSuccess, OnDiscoveredDeviceOverBleError));
ExitNow(CHIP_NO_ERROR);
}
else
{
ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT);
}
}
#endif
session = mSystemState->SessionMgr()->CreateUnauthenticatedSession(params.GetPeerAddress(), params.GetMRPConfig());
VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NO_MEMORY);
// Allocate the exchange immediately before calling PASESession::Pair.
//
// PASESession::Pair takes ownership of the exchange and will free it on
// error, but can only do this if it is actually called. Allocating the
// exchange context right before calling Pair ensures that if allocation
// succeeds, PASESession has taken ownership.
exchangeCtxt = mSystemState->ExchangeMgr()->NewContext(session.Value(), &device->GetPairing());
VerifyOrExit(exchangeCtxt != nullptr, err = CHIP_ERROR_INTERNAL);
err = device->GetPairing().Pair(*mSystemState->SessionMgr(), params.GetSetupPINCode(), GetLocalMRPConfig(), exchangeCtxt, this);
SuccessOrExit(err);
exit:
if (err != CHIP_NO_ERROR)
{
if (device != nullptr)
{
ReleaseCommissioneeDevice(device);
}
MATTER_LOG_METRIC_END(kMetricDeviceCommissionerPASESession, err);
}
return err;
}
#if CONFIG_NETWORK_LAYER_BLE
void DeviceCommissioner::OnDiscoveredDeviceOverBleSuccess(void * appState, BLE_CONNECTION_OBJECT connObj)
{
auto self = static_cast<DeviceCommissioner *>(appState);
auto device = self->mDeviceInPASEEstablishment;
if (nullptr != device && device->GetDeviceTransportType() == Transport::Type::kBle)
{
auto remoteId = device->GetDeviceId();
auto params = self->mRendezvousParametersForDeviceDiscoveredOverBle;
params.SetConnectionObject(connObj);
self->mRendezvousParametersForDeviceDiscoveredOverBle = RendezvousParameters();
self->ReleaseCommissioneeDevice(device);
LogErrorOnFailure(self->EstablishPASEConnection(remoteId, params));
}
}
void DeviceCommissioner::OnDiscoveredDeviceOverBleError(void * appState, CHIP_ERROR err)
{
auto self = static_cast<DeviceCommissioner *>(appState);
auto device = self->mDeviceInPASEEstablishment;
if (nullptr != device && device->GetDeviceTransportType() == Transport::Type::kBle)
{
self->ReleaseCommissioneeDevice(device);
self->mRendezvousParametersForDeviceDiscoveredOverBle = RendezvousParameters();
// Callback is required when BLE discovery fails, otherwise the caller will always be in a suspended state
// A better way to handle it should define a new error code
if (self->mPairingDelegate != nullptr)
{
self->mPairingDelegate->OnPairingComplete(err);
}
}
}
#endif // CONFIG_NETWORK_LAYER_BLE
CHIP_ERROR DeviceCommissioner::Commission(NodeId remoteDeviceId, CommissioningParameters & params)
{
if (mDefaultCommissioner == nullptr)
{
ChipLogError(Controller, "No default commissioner is specified");
return CHIP_ERROR_INCORRECT_STATE;
}
ReturnErrorOnFailureWithMetric(kMetricDeviceCommissionerCommission, mDefaultCommissioner->SetCommissioningParameters(params));
auto errorCode = Commission(remoteDeviceId);
VerifyOrDoWithMetric(kMetricDeviceCommissionerCommission, CHIP_NO_ERROR == errorCode, errorCode);
return errorCode;
}
CHIP_ERROR DeviceCommissioner::Commission(NodeId remoteDeviceId)
{
MATTER_TRACE_SCOPE("Commission", "DeviceCommissioner");
if (mDefaultCommissioner == nullptr)
{
ChipLogError(Controller, "No default commissioner is specified");
return CHIP_ERROR_INCORRECT_STATE;
}
CommissioneeDeviceProxy * device = FindCommissioneeDevice(remoteDeviceId);
if (device == nullptr || (!device->IsSecureConnected() && !device->IsSessionSetupInProgress()))
{
ChipLogError(Controller, "Invalid device for commissioning " ChipLogFormatX64, ChipLogValueX64(remoteDeviceId));
return CHIP_ERROR_INCORRECT_STATE;
}
if (!device->IsSecureConnected() && device != mDeviceInPASEEstablishment)
{
// We should not end up in this state because we won't attempt to establish more than one connection at a time.
ChipLogError(Controller, "Device is not connected and not being paired " ChipLogFormatX64, ChipLogValueX64(remoteDeviceId));
return CHIP_ERROR_INCORRECT_STATE;
}
if (mCommissioningStage != CommissioningStage::kSecurePairing)
{
ChipLogError(Controller, "Commissioning already in progress (stage '%s') - not restarting",
StageToString(mCommissioningStage));
return CHIP_ERROR_INCORRECT_STATE;
}
ChipLogProgress(Controller, "Commission called for node ID 0x" ChipLogFormatX64, ChipLogValueX64(remoteDeviceId));
mDefaultCommissioner->SetOperationalCredentialsDelegate(mOperationalCredentialsDelegate);
if (device->IsSecureConnected())
{
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissionerCommission);
mDefaultCommissioner->StartCommissioning(this, device);
}
else
{
mRunCommissioningAfterConnection = true;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR
DeviceCommissioner::ContinueCommissioningAfterDeviceAttestation(DeviceProxy * device,
Credentials::AttestationVerificationResult attestationResult)
{
MATTER_TRACE_SCOPE("continueCommissioningDevice", "DeviceCommissioner");
if (mDefaultCommissioner == nullptr)
{
ChipLogError(Controller, "No default commissioner is specified");
return CHIP_ERROR_INCORRECT_STATE;
}
if (device == nullptr || device != mDeviceBeingCommissioned)
{
ChipLogError(Controller, "Invalid device for commissioning %p", device);
return CHIP_ERROR_INCORRECT_STATE;
}
CommissioneeDeviceProxy * commissioneeDevice = FindCommissioneeDevice(device->GetDeviceId());
if (commissioneeDevice == nullptr)
{
ChipLogError(Controller, "Couldn't find commissionee device");
return CHIP_ERROR_INCORRECT_STATE;
}
if (!commissioneeDevice->IsSecureConnected() || commissioneeDevice != mDeviceBeingCommissioned)
{
ChipLogError(Controller, "Invalid device for commissioning after attestation failure: 0x" ChipLogFormatX64,
ChipLogValueX64(commissioneeDevice->GetDeviceId()));
return CHIP_ERROR_INCORRECT_STATE;
}
if (mCommissioningStage != CommissioningStage::kAttestationRevocationCheck)
{
ChipLogError(Controller, "Commissioning is not attestation verification phase");
return CHIP_ERROR_INCORRECT_STATE;
}
ChipLogProgress(Controller, "Continuing commissioning after attestation failure for device ID 0x" ChipLogFormatX64,
ChipLogValueX64(commissioneeDevice->GetDeviceId()));
if (attestationResult != AttestationVerificationResult::kSuccess)
{
ChipLogError(Controller, "Client selected error: %u for failed 'Attestation Information' for device",
to_underlying(attestationResult));
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationErrorInfo>(attestationResult);
CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
}
else
{
ChipLogProgress(Controller, "Overriding attestation failure per client and continuing commissioning");
CommissioningStageComplete(CHIP_NO_ERROR);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::StopPairing(NodeId remoteDeviceId)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(remoteDeviceId != kUndefinedNodeId, CHIP_ERROR_INVALID_ARGUMENT);
ChipLogProgress(Controller, "StopPairing called for node ID 0x" ChipLogFormatX64, ChipLogValueX64(remoteDeviceId));
// If we're still in the process of discovering the device, just stop the SetUpCodePairer
if (mSetUpCodePairer.StopPairing(remoteDeviceId))
{
mRunCommissioningAfterConnection = false;
return CHIP_NO_ERROR;
}
// Otherwise we might be pairing and / or commissioning it.
CommissioneeDeviceProxy * device = FindCommissioneeDevice(remoteDeviceId);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
if (mDeviceBeingCommissioned == device)
{
CancelCommissioningInteractions();
CommissioningStageComplete(CHIP_ERROR_CANCELLED);
}
else
{
ReleaseCommissioneeDevice(device);
}
return CHIP_NO_ERROR;
}
void DeviceCommissioner::CancelCommissioningInteractions()
{
if (mReadClient)
{
ChipLogDetail(Controller, "Cancelling read request for step '%s'", StageToString(mCommissioningStage));
mReadClient.reset(); // destructor cancels
}
if (mInvokeCancelFn)
{
ChipLogDetail(Controller, "Cancelling command invocation for step '%s'", StageToString(mCommissioningStage));
mInvokeCancelFn();
mInvokeCancelFn = nullptr;
}
if (mOnDeviceConnectedCallback.IsRegistered())
{
ChipLogDetail(Controller, "Cancelling CASE setup for step '%s'", StageToString(mCommissioningStage));
CancelCASECallbacks();
}
}
void DeviceCommissioner::CancelCASECallbacks()
{
mOnDeviceConnectedCallback.Cancel();
mOnDeviceConnectionFailureCallback.Cancel();
#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
mOnDeviceConnectionRetryCallback.Cancel();
#endif
}
CHIP_ERROR DeviceCommissioner::UnpairDevice(NodeId remoteDeviceId)
{
MATTER_TRACE_SCOPE("UnpairDevice", "DeviceCommissioner");
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
return AutoCurrentFabricRemover::RemoveCurrentFabric(this, remoteDeviceId);
}
void DeviceCommissioner::RendezvousCleanup(CHIP_ERROR status)
{
if (mDeviceInPASEEstablishment != nullptr)
{
// Release the commissionee device. For BLE, this is stored,
// for IP commissioning, we have taken a reference to the
// operational node to send the completion command.
ReleaseCommissioneeDevice(mDeviceInPASEEstablishment);
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnPairingComplete(status);
}
}
}
void DeviceCommissioner::OnSessionEstablishmentError(CHIP_ERROR err)
{
MATTER_LOG_METRIC_END(kMetricDeviceCommissionerPASESession, err);
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingFailed);
}
RendezvousCleanup(err);
}
void DeviceCommissioner::OnSessionEstablished(const SessionHandle & session)
{
// PASE session established.
CommissioneeDeviceProxy * device = mDeviceInPASEEstablishment;
// We are in the callback for this pairing. Reset so we can pair another device.
mDeviceInPASEEstablishment = nullptr;
VerifyOrReturn(device != nullptr, OnSessionEstablishmentError(CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR));
CHIP_ERROR err = device->SetConnected(session);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed in setting up secure channel: err %s", ErrorStr(err));
OnSessionEstablishmentError(err);
return;
}
ChipLogDetail(Controller, "Remote device completed SPAKE2+ handshake");
MATTER_LOG_METRIC_END(kMetricDeviceCommissionerPASESession, CHIP_NO_ERROR);
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnPairingComplete(CHIP_NO_ERROR);
}
if (mRunCommissioningAfterConnection)
{
mRunCommissioningAfterConnection = false;
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissionerCommission);
mDefaultCommissioner->StartCommissioning(this, device);
}
}
CHIP_ERROR DeviceCommissioner::SendCertificateChainRequestCommand(DeviceProxy * device,
Credentials::CertificateType certificateType,
Optional<System::Clock::Timeout> timeout)
{
MATTER_TRACE_SCOPE("SendCertificateChainRequestCommand", "DeviceCommissioner");
ChipLogDetail(Controller, "Sending Certificate Chain request to %p device", device);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
OperationalCredentials::Commands::CertificateChainRequest::Type request;
request.certificateType = static_cast<OperationalCredentials::CertificateChainTypeEnum>(certificateType);
return SendCommissioningCommand(device, request, OnCertificateChainResponse, OnCertificateChainFailureResponse, kRootEndpointId,
timeout);
}
void DeviceCommissioner::OnCertificateChainFailureResponse(void * context, CHIP_ERROR error)
{
MATTER_TRACE_SCOPE("OnCertificateChainFailureResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Device failed to receive the Certificate Chain request Response: %s", chip::ErrorStr(error));
DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(error);
}
void DeviceCommissioner::OnCertificateChainResponse(
void * context, const chip::app::Clusters::OperationalCredentials::Commands::CertificateChainResponse::DecodableType & response)
{
MATTER_TRACE_SCOPE("OnCertificateChainResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Received certificate chain from the device");
DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
CommissioningDelegate::CommissioningReport report;
report.Set<RequestedCertificate>(RequestedCertificate(response.certificate));
commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
}
CHIP_ERROR DeviceCommissioner::SendAttestationRequestCommand(DeviceProxy * device, const ByteSpan & attestationNonce,
Optional<System::Clock::Timeout> timeout)
{
MATTER_TRACE_SCOPE("SendAttestationRequestCommand", "DeviceCommissioner");
ChipLogDetail(Controller, "Sending Attestation request to %p device", device);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
OperationalCredentials::Commands::AttestationRequest::Type request;
request.attestationNonce = attestationNonce;
ReturnErrorOnFailure(
SendCommissioningCommand(device, request, OnAttestationResponse, OnAttestationFailureResponse, kRootEndpointId, timeout));
ChipLogDetail(Controller, "Sent Attestation request, waiting for the Attestation Information");
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnAttestationFailureResponse(void * context, CHIP_ERROR error)
{
MATTER_TRACE_SCOPE("OnAttestationFailureResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Device failed to receive the Attestation Information Response: %s", chip::ErrorStr(error));
DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(error);
}
void DeviceCommissioner::OnAttestationResponse(void * context,
const OperationalCredentials::Commands::AttestationResponse::DecodableType & data)
{
MATTER_TRACE_SCOPE("OnAttestationResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Received Attestation Information from the device");
DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationResponse>(AttestationResponse(data.attestationElements, data.attestationSignature));
commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
}
void DeviceCommissioner::OnDeviceAttestationInformationVerification(
void * context, const Credentials::DeviceAttestationVerifier::AttestationInfo & info, AttestationVerificationResult result)
{
MATTER_TRACE_SCOPE("OnDeviceAttestationInformationVerification", "DeviceCommissioner");
DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
if (commissioner->mCommissioningStage == CommissioningStage::kAttestationVerification)
{
// Check for revoked DAC Chain before calling delegate. Enter next stage.
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationErrorInfo>(result);
return commissioner->CommissioningStageComplete(
result == AttestationVerificationResult::kSuccess ? CHIP_NO_ERROR : CHIP_ERROR_INTERNAL, report);
}
if (!commissioner->mDeviceBeingCommissioned)
{
ChipLogError(Controller, "Device attestation verification result received when we're not commissioning a device");
return;
}
auto & params = commissioner->mDefaultCommissioner->GetCommissioningParameters();
Credentials::DeviceAttestationDelegate * deviceAttestationDelegate = params.GetDeviceAttestationDelegate();
if (params.GetCompletionStatus().attestationResult.HasValue())
{
auto previousResult = params.GetCompletionStatus().attestationResult.Value();
if (previousResult != AttestationVerificationResult::kSuccess)
{
result = previousResult;
}
}
if (result != AttestationVerificationResult::kSuccess)
{
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationErrorInfo>(result);
if (result == AttestationVerificationResult::kNotImplemented)
{
ChipLogError(Controller,
"Failed in verifying 'Attestation Information' command received from the device due to default "
"DeviceAttestationVerifier Class not being overridden by a real implementation.");
commissioner->CommissioningStageComplete(CHIP_ERROR_NOT_IMPLEMENTED, report);
return;
}
ChipLogError(Controller,
"Failed in verifying 'Attestation Information' command received from the device: err %hu. Look at "
"AttestationVerificationResult enum to understand the errors",
static_cast<uint16_t>(result));
// Go look at AttestationVerificationResult enum in src/credentials/attestation_verifier/DeviceAttestationVerifier.h to
// understand the errors.
// If a device attestation status delegate is installed, delegate handling of failure to the client and let them
// decide on whether to proceed further or not.
if (deviceAttestationDelegate)
{
commissioner->ExtendArmFailSafeForDeviceAttestation(info, result);
}
else
{
commissioner->CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
}
}
else
{
if (deviceAttestationDelegate && deviceAttestationDelegate->ShouldWaitAfterDeviceAttestation())
{
commissioner->ExtendArmFailSafeForDeviceAttestation(info, result);
}
else
{
ChipLogProgress(Controller, "Successfully validated 'Attestation Information' command received from the device.");
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
}
}
void DeviceCommissioner::OnArmFailSafeExtendedForDeviceAttestation(
void * context, const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType &)
{
ChipLogProgress(Controller, "Successfully extended fail-safe timer to handle DA failure");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
// We have completed our command invoke, but we're not going to finish the
// commissioning step until our client examines the attestation
// information. Clear out mInvokeCancelFn (which points at the
// CommandSender we just finished using) now, so it's not dangling.
commissioner->mInvokeCancelFn = nullptr;
commissioner->HandleDeviceAttestationCompleted();
}
void DeviceCommissioner::HandleDeviceAttestationCompleted()
{
if (!mDeviceBeingCommissioned)
{
return;
}
auto & params = mDefaultCommissioner->GetCommissioningParameters();
Credentials::DeviceAttestationDelegate * deviceAttestationDelegate = params.GetDeviceAttestationDelegate();
if (deviceAttestationDelegate)
{
ChipLogProgress(Controller, "Device attestation completed, delegating continuation to client");
deviceAttestationDelegate->OnDeviceAttestationCompleted(this, mDeviceBeingCommissioned, *mAttestationDeviceInfo,
mAttestationResult);
}
else
{
ChipLogProgress(Controller, "Device attestation failed and no delegate set, failing commissioning");
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationErrorInfo>(mAttestationResult);
CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
}
}
void DeviceCommissioner::OnFailedToExtendedArmFailSafeDeviceAttestation(void * context, CHIP_ERROR error)
{
ChipLogProgress(Controller, "Failed to extend fail-safe timer to handle attestation failure %s", chip::ErrorStr(error));
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationErrorInfo>(commissioner->mAttestationResult);
commissioner->CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
}
void DeviceCommissioner::OnICDManagementRegisterClientResponse(
void * context, const app::Clusters::IcdManagement::Commands::RegisterClientResponse::DecodableType & data)
{
CHIP_ERROR err = CHIP_NO_ERROR;
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
VerifyOrExit(commissioner != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(commissioner->mCommissioningStage == CommissioningStage::kICDRegistration, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(commissioner->mDeviceBeingCommissioned != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
if (commissioner->mPairingDelegate != nullptr)
{
commissioner->mPairingDelegate->OnICDRegistrationComplete(
ScopedNodeId(commissioner->mDeviceBeingCommissioned->GetDeviceId(), commissioner->GetFabricIndex()), data.ICDCounter);
}
exit:
CommissioningDelegate::CommissioningReport report;
commissioner->CommissioningStageComplete(err, report);
}
void DeviceCommissioner::OnICDManagementStayActiveResponse(
void * context, const app::Clusters::IcdManagement::Commands::StayActiveResponse::DecodableType & data)
{
CHIP_ERROR err = CHIP_NO_ERROR;
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
VerifyOrExit(commissioner != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(commissioner->mCommissioningStage == CommissioningStage::kICDSendStayActive, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(commissioner->mDeviceBeingCommissioned != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
if (commissioner->mPairingDelegate != nullptr)
{
commissioner->mPairingDelegate->OnICDStayActiveComplete(
ScopedNodeId(commissioner->mDeviceBeingCommissioned->GetDeviceId(), commissioner->GetFabricIndex()),
data.promisedActiveDuration);
}
exit:
CommissioningDelegate::CommissioningReport report;
commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
}
bool DeviceCommissioner::ExtendArmFailSafeInternal(DeviceProxy * proxy, CommissioningStage step, uint16_t armFailSafeTimeout,
Optional<System::Clock::Timeout> commandTimeout,
OnExtendFailsafeSuccess onSuccess, OnExtendFailsafeFailure onFailure,
bool fireAndForget)
{
using namespace System;
using namespace System::Clock;
auto now = SystemClock().GetMonotonicTimestamp();
auto newFailSafeTimeout = now + Seconds16(armFailSafeTimeout);
if (newFailSafeTimeout < proxy->GetFailSafeExpirationTimestamp())
{
ChipLogProgress(
Controller, "Skipping arming failsafe: new time (%u seconds from now) before old time (%u seconds from now)",
armFailSafeTimeout, std::chrono::duration_cast<Seconds16>(proxy->GetFailSafeExpirationTimestamp() - now).count());
return false;
}
uint64_t breadcrumb = static_cast<uint64_t>(step);
GeneralCommissioning::Commands::ArmFailSafe::Type request;
request.expiryLengthSeconds = armFailSafeTimeout;
request.breadcrumb = breadcrumb;
ChipLogProgress(Controller, "Arming failsafe (%u seconds)", request.expiryLengthSeconds);
CHIP_ERROR err = SendCommissioningCommand(proxy, request, onSuccess, onFailure, kRootEndpointId, commandTimeout, fireAndForget);
if (err != CHIP_NO_ERROR)
{
onFailure((!fireAndForget) ? this : nullptr, err);
return true; // we have called onFailure already
}
// Note: The stored timestamp may become invalid if we fail asynchronously
proxy->SetFailSafeExpirationTimestamp(newFailSafeTimeout);
return true;
}
void DeviceCommissioner::ExtendArmFailSafeForDeviceAttestation(const Credentials::DeviceAttestationVerifier::AttestationInfo & info,
Credentials::AttestationVerificationResult result)
{
mAttestationResult = result;
auto & params = mDefaultCommissioner->GetCommissioningParameters();
Credentials::DeviceAttestationDelegate * deviceAttestationDelegate = params.GetDeviceAttestationDelegate();
mAttestationDeviceInfo = Platform::MakeUnique<Credentials::DeviceAttestationVerifier::AttestationDeviceInfo>(info);
auto expiryLengthSeconds = deviceAttestationDelegate->FailSafeExpiryTimeoutSecs();
bool waitForFailsafeExtension = expiryLengthSeconds.HasValue();
if (waitForFailsafeExtension)
{
ChipLogProgress(Controller, "Changing fail-safe timer to %u seconds to handle DA failure", expiryLengthSeconds.Value());
// Per spec, anything we do with the fail-safe armed must not time out
// in less than kMinimumCommissioningStepTimeout.
waitForFailsafeExtension =
ExtendArmFailSafeInternal(mDeviceBeingCommissioned, mCommissioningStage, expiryLengthSeconds.Value(),
MakeOptional(kMinimumCommissioningStepTimeout), OnArmFailSafeExtendedForDeviceAttestation,
OnFailedToExtendedArmFailSafeDeviceAttestation, /* fireAndForget = */ false);
}
else
{
ChipLogProgress(Controller, "Proceeding without changing fail-safe timer value as delegate has not set it");
}
if (!waitForFailsafeExtension)
{
HandleDeviceAttestationCompleted();
}
}
CHIP_ERROR DeviceCommissioner::ValidateAttestationInfo(const Credentials::DeviceAttestationVerifier::AttestationInfo & info)
{
MATTER_TRACE_SCOPE("ValidateAttestationInfo", "DeviceCommissioner");
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDeviceAttestationVerifier != nullptr, CHIP_ERROR_INCORRECT_STATE);
mDeviceAttestationVerifier->VerifyAttestationInformation(info, &mDeviceAttestationInformationVerificationCallback);
// TODO: Validate Firmware Information
return CHIP_NO_ERROR;
}
CHIP_ERROR
DeviceCommissioner::CheckForRevokedDACChain(const Credentials::DeviceAttestationVerifier::AttestationInfo & info)
{
MATTER_TRACE_SCOPE("CheckForRevokedDACChain", "DeviceCommissioner");
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDeviceAttestationVerifier != nullptr, CHIP_ERROR_INCORRECT_STATE);
mDeviceAttestationVerifier->CheckForRevokedDACChain(info, &mDeviceAttestationInformationVerificationCallback);
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::ValidateCSR(DeviceProxy * proxy, const ByteSpan & NOCSRElements,
const ByteSpan & AttestationSignature, const ByteSpan & dac, const ByteSpan & csrNonce)
{
MATTER_TRACE_SCOPE("ValidateCSR", "DeviceCommissioner");
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDeviceAttestationVerifier != nullptr, CHIP_ERROR_INCORRECT_STATE);
P256PublicKey dacPubkey;
ReturnErrorOnFailure(ExtractPubkeyFromX509Cert(dac, dacPubkey));
// Retrieve attestation challenge
ByteSpan attestationChallenge =
proxy->GetSecureSession().Value()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge();
// The operational CA should also verify this on its end during NOC generation, if end-to-end attestation is desired.
return mDeviceAttestationVerifier->VerifyNodeOperationalCSRInformation(NOCSRElements, attestationChallenge,
AttestationSignature, dacPubkey, csrNonce);
}
CHIP_ERROR DeviceCommissioner::SendOperationalCertificateSigningRequestCommand(DeviceProxy * device, const ByteSpan & csrNonce,
Optional<System::Clock::Timeout> timeout)
{
MATTER_TRACE_SCOPE("SendOperationalCertificateSigningRequestCommand", "DeviceCommissioner");
ChipLogDetail(Controller, "Sending CSR request to %p device", device);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
OperationalCredentials::Commands::CSRRequest::Type request;
request.CSRNonce = csrNonce;
ReturnErrorOnFailure(SendCommissioningCommand(device, request, OnOperationalCertificateSigningRequest, OnCSRFailureResponse,
kRootEndpointId, timeout));
ChipLogDetail(Controller, "Sent CSR request, waiting for the CSR");
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnCSRFailureResponse(void * context, CHIP_ERROR error)
{
MATTER_TRACE_SCOPE("OnCSRFailureResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Device failed to receive the CSR request Response: %s", chip::ErrorStr(error));
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(error);
}
void DeviceCommissioner::OnOperationalCertificateSigningRequest(
void * context, const OperationalCredentials::Commands::CSRResponse::DecodableType & data)
{
MATTER_TRACE_SCOPE("OnOperationalCertificateSigningRequest", "DeviceCommissioner");
ChipLogProgress(Controller, "Received certificate signing request from the device");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
CommissioningDelegate::CommissioningReport report;
report.Set<CSRResponse>(CSRResponse(data.NOCSRElements, data.attestationSignature));
commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
}
void DeviceCommissioner::OnDeviceNOCChainGeneration(void * context, CHIP_ERROR status, const ByteSpan & noc, const ByteSpan & icac,
const ByteSpan & rcac, Optional<IdentityProtectionKeySpan> ipk,
Optional<NodeId> adminSubject)
{
MATTER_TRACE_SCOPE("OnDeviceNOCChainGeneration", "DeviceCommissioner");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
// The placeholder IPK is not satisfactory, but is there to fill the NocChain struct on error. It will still fail.
const uint8_t placeHolderIpk[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
if (status == CHIP_NO_ERROR && !ipk.HasValue())
{
ChipLogError(Controller, "Did not have an IPK from the OperationalCredentialsIssuer! Cannot commission.");
status = CHIP_ERROR_INVALID_ARGUMENT;
}
ChipLogProgress(Controller, "Received callback from the CA for NOC Chain generation. Status %s", ErrorStr(status));
if (status == CHIP_NO_ERROR && commissioner->mState != State::Initialized)
{
status = CHIP_ERROR_INCORRECT_STATE;
}
if (status != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed in generating device's operational credentials. Error %s", ErrorStr(status));
}
// TODO - Verify that the generated root cert matches with commissioner's root cert
CommissioningDelegate::CommissioningReport report;
report.Set<NocChain>(NocChain(noc, icac, rcac, ipk.HasValue() ? ipk.Value() : IdentityProtectionKeySpan(placeHolderIpk),
adminSubject.HasValue() ? adminSubject.Value() : commissioner->GetNodeId()));
commissioner->CommissioningStageComplete(status, report);
}
CHIP_ERROR DeviceCommissioner::IssueNOCChain(const ByteSpan & NOCSRElements, NodeId nodeId,
chip::Callback::Callback<OnNOCChainGeneration> * callback)
{
MATTER_TRACE_SCOPE("IssueNOCChain", "DeviceCommissioner");
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
ChipLogProgress(Controller, "Getting certificate chain for the device on fabric idx %u", static_cast<unsigned>(mFabricIndex));
mOperationalCredentialsDelegate->SetNodeIdForNextNOCRequest(nodeId);
if (mFabricIndex != kUndefinedFabricIndex)
{
mOperationalCredentialsDelegate->SetFabricIdForNextNOCRequest(GetFabricId());
}
// Note: we don't have attestationSignature, attestationChallenge, DAC, PAI so we are just providing an empty ByteSpan
// for those arguments.
return mOperationalCredentialsDelegate->GenerateNOCChain(NOCSRElements, ByteSpan(), ByteSpan(), ByteSpan(), ByteSpan(),
ByteSpan(), callback);
}
CHIP_ERROR DeviceCommissioner::ProcessCSR(DeviceProxy * proxy, const ByteSpan & NOCSRElements,
const ByteSpan & AttestationSignature, const ByteSpan & dac, const ByteSpan & pai,
const ByteSpan & csrNonce)
{
MATTER_TRACE_SCOPE("ProcessOpCSR", "DeviceCommissioner");
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
ChipLogProgress(Controller, "Getting certificate chain for the device from the issuer");
P256PublicKey dacPubkey;
ReturnErrorOnFailure(ExtractPubkeyFromX509Cert(dac, dacPubkey));
// Retrieve attestation challenge
ByteSpan attestationChallenge =
proxy->GetSecureSession().Value()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge();
mOperationalCredentialsDelegate->SetNodeIdForNextNOCRequest(proxy->GetDeviceId());
if (mFabricIndex != kUndefinedFabricIndex)
{
mOperationalCredentialsDelegate->SetFabricIdForNextNOCRequest(GetFabricId());
}
return mOperationalCredentialsDelegate->GenerateNOCChain(NOCSRElements, csrNonce, AttestationSignature, attestationChallenge,
dac, pai, &mDeviceNOCChainCallback);
}
CHIP_ERROR DeviceCommissioner::SendOperationalCertificate(DeviceProxy * device, const ByteSpan & nocCertBuf,
const Optional<ByteSpan> & icaCertBuf,
const IdentityProtectionKeySpan ipk, const NodeId adminSubject,
Optional<System::Clock::Timeout> timeout)
{
MATTER_TRACE_SCOPE("SendOperationalCertificate", "DeviceCommissioner");
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
OperationalCredentials::Commands::AddNOC::Type request;
request.NOCValue = nocCertBuf;
request.ICACValue = icaCertBuf;
request.IPKValue = ipk;
request.caseAdminSubject = adminSubject;
request.adminVendorId = mVendorId;
ReturnErrorOnFailure(SendCommissioningCommand(device, request, OnOperationalCertificateAddResponse, OnAddNOCFailureResponse,
kRootEndpointId, timeout));
ChipLogProgress(Controller, "Sent operational certificate to the device");
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::ConvertFromOperationalCertStatus(OperationalCredentials::NodeOperationalCertStatusEnum err)
{
using OperationalCredentials::NodeOperationalCertStatusEnum;
switch (err)
{
case NodeOperationalCertStatusEnum::kOk:
return CHIP_NO_ERROR;
case NodeOperationalCertStatusEnum::kInvalidPublicKey:
return CHIP_ERROR_INVALID_PUBLIC_KEY;
case NodeOperationalCertStatusEnum::kInvalidNodeOpId:
return CHIP_ERROR_WRONG_NODE_ID;
case NodeOperationalCertStatusEnum::kInvalidNOC:
return CHIP_ERROR_UNSUPPORTED_CERT_FORMAT;
case NodeOperationalCertStatusEnum::kMissingCsr:
return CHIP_ERROR_INCORRECT_STATE;
case NodeOperationalCertStatusEnum::kTableFull:
return CHIP_ERROR_NO_MEMORY;
case NodeOperationalCertStatusEnum::kInvalidAdminSubject:
return CHIP_ERROR_INVALID_ADMIN_SUBJECT;
case NodeOperationalCertStatusEnum::kFabricConflict:
return CHIP_ERROR_FABRIC_EXISTS;
case NodeOperationalCertStatusEnum::kLabelConflict:
return CHIP_ERROR_INVALID_ARGUMENT;
case NodeOperationalCertStatusEnum::kInvalidFabricIndex:
return CHIP_ERROR_INVALID_FABRIC_INDEX;
case NodeOperationalCertStatusEnum::kUnknownEnumValue:
// Is this a reasonable value?
return CHIP_ERROR_CERT_LOAD_FAILED;
}
return CHIP_ERROR_CERT_LOAD_FAILED;
}
void DeviceCommissioner::OnAddNOCFailureResponse(void * context, CHIP_ERROR error)
{
MATTER_TRACE_SCOPE("OnAddNOCFailureResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Device failed to receive the operational certificate Response: %s", chip::ErrorStr(error));
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(error);
}
void DeviceCommissioner::OnOperationalCertificateAddResponse(
void * context, const OperationalCredentials::Commands::NOCResponse::DecodableType & data)
{
MATTER_TRACE_SCOPE("OnOperationalCertificateAddResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Device returned status %d on receiving the NOC", to_underlying(data.statusCode));
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(commissioner->mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(commissioner->mDeviceBeingCommissioned != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
err = ConvertFromOperationalCertStatus(data.statusCode);
SuccessOrExit(err);
err = commissioner->OnOperationalCredentialsProvisioningCompletion(commissioner->mDeviceBeingCommissioned);
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogProgress(Controller, "Add NOC failed with error %s", ErrorStr(err));
commissioner->CommissioningStageComplete(err);
}
}
CHIP_ERROR DeviceCommissioner::SendTrustedRootCertificate(DeviceProxy * device, const ByteSpan & rcac,
Optional<System::Clock::Timeout> timeout)
{
MATTER_TRACE_SCOPE("SendTrustedRootCertificate", "DeviceCommissioner");
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ChipLogProgress(Controller, "Sending root certificate to the device");
OperationalCredentials::Commands::AddTrustedRootCertificate::Type request;
request.rootCACertificate = rcac;
ReturnErrorOnFailure(
SendCommissioningCommand(device, request, OnRootCertSuccessResponse, OnRootCertFailureResponse, kRootEndpointId, timeout));
ChipLogProgress(Controller, "Sent root certificate to the device");
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnRootCertSuccessResponse(void * context, const chip::app::DataModel::NullObjectType &)
{
MATTER_TRACE_SCOPE("OnRootCertSuccessResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Device confirmed that it has received the root certificate");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::OnRootCertFailureResponse(void * context, CHIP_ERROR error)
{
MATTER_TRACE_SCOPE("OnRootCertFailureResponse", "DeviceCommissioner");
ChipLogProgress(Controller, "Device failed to receive the root certificate Response: %s", chip::ErrorStr(error));
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(error);
}
CHIP_ERROR DeviceCommissioner::OnOperationalCredentialsProvisioningCompletion(DeviceProxy * device)
{
MATTER_TRACE_SCOPE("OnOperationalCredentialsProvisioningCompletion", "DeviceCommissioner");
ChipLogProgress(Controller, "Operational credentials provisioned on device %p", device);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingSuccess);
}
CommissioningStageComplete(CHIP_NO_ERROR);
return CHIP_NO_ERROR;
}
#if CONFIG_NETWORK_LAYER_BLE
#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
void DeviceCommissioner::ConnectBleTransportToSelf()
{
Transport::BLEBase & transport = std::get<Transport::BLE<1>>(mSystemState->TransportMgr()->GetTransport().GetTransports());
if (!transport.IsBleLayerTransportSetToSelf())
{
transport.SetBleLayerTransportToSelf();
}
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
void DeviceCommissioner::CloseBleConnection()
{
// It is fine since we can only commission one device at the same time.
// We should be able to distinguish different BLE connections if we want
// to commission multiple devices at the same time over BLE.
mSystemState->BleLayer()->CloseAllBleConnections();
}
#endif
CHIP_ERROR DeviceCommissioner::DiscoverCommissionableNodes(Dnssd::DiscoveryFilter filter)
{
ReturnErrorOnFailure(SetUpNodeDiscovery());
return mDNSResolver.DiscoverCommissionableNodes(filter);
}
CHIP_ERROR DeviceCommissioner::StopCommissionableDiscovery()
{
return mDNSResolver.StopDiscovery();
}
const Dnssd::CommissionNodeData * DeviceCommissioner::GetDiscoveredDevice(int idx)
{
return GetDiscoveredNode(idx);
}
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY // make this commissioner discoverable
CHIP_ERROR DeviceCommissioner::SetUdcListenPort(uint16_t listenPort)
{
if (mState == State::Initialized)
{
return CHIP_ERROR_INCORRECT_STATE;
}
mUdcListenPort = listenPort;
return CHIP_NO_ERROR;
}
void DeviceCommissioner::FindCommissionableNode(char * instanceName)
{
Dnssd::DiscoveryFilter filter(Dnssd::DiscoveryFilterType::kInstanceName, instanceName);
DiscoverCommissionableNodes(filter);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
void DeviceCommissioner::OnNodeDiscovered(const chip::Dnssd::DiscoveredNodeData & nodeData)
{
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
if (mUdcServer != nullptr)
{
mUdcServer->OnCommissionableNodeFound(nodeData);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
AbstractDnssdDiscoveryController::OnNodeDiscovered(nodeData);
mSetUpCodePairer.NotifyCommissionableDeviceDiscovered(nodeData);
}
void DeviceCommissioner::OnBasicSuccess(void * context, const chip::app::DataModel::NullObjectType &)
{
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::OnBasicFailure(void * context, CHIP_ERROR error)
{
ChipLogProgress(Controller, "Received failure response %s\n", chip::ErrorStr(error));
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(error);
}
static GeneralCommissioning::Commands::ArmFailSafe::Type DisarmFailsafeRequest()
{
GeneralCommissioning::Commands::ArmFailSafe::Type request;
request.expiryLengthSeconds = 0; // Expire immediately.
request.breadcrumb = 0;
return request;
}
static void MarkForEviction(const Optional<SessionHandle> & session)
{
if (session.HasValue())
{
session.Value()->AsSecureSession()->MarkForEviction();
}
}
void DeviceCommissioner::CleanupCommissioning(DeviceProxy * proxy, NodeId nodeId, const CompletionStatus & completionStatus)
{
// At this point, proxy == mDeviceBeingCommissioned, nodeId == mDeviceBeingCommissioned->GetDeviceId()
mCommissioningCompletionStatus = completionStatus;
if (completionStatus.err == CHIP_NO_ERROR)
{
CommissioneeDeviceProxy * commissionee = FindCommissioneeDevice(nodeId);
if (commissionee != nullptr)
{
ReleaseCommissioneeDevice(commissionee);
}
// Send the callbacks, we're done.
CommissioningStageComplete(CHIP_NO_ERROR);
SendCommissioningCompleteCallbacks(nodeId, mCommissioningCompletionStatus);
}
else if (completionStatus.err == CHIP_ERROR_CANCELLED)
{
// If we're cleaning up because cancellation has been requested via StopPairing(), expire the failsafe
// in the background and reset our state synchronously, so a new commissioning attempt can be started.
CommissioneeDeviceProxy * commissionee = FindCommissioneeDevice(nodeId);
SessionHolder session((commissionee == proxy) ? commissionee->DetachSecureSession().Value()
: proxy->GetSecureSession().Value());
auto request = DisarmFailsafeRequest();
auto onSuccessCb = [session](const app::ConcreteCommandPath & aPath, const app::StatusIB & aStatus,
const decltype(request)::ResponseType & responseData) {
ChipLogProgress(Controller, "Failsafe disarmed");
MarkForEviction(session.Get());
};
auto onFailureCb = [session](CHIP_ERROR aError) {
ChipLogProgress(Controller, "Ignoring failure to disarm failsafe: %" CHIP_ERROR_FORMAT, aError.Format());
MarkForEviction(session.Get());
};
ChipLogProgress(Controller, "Disarming failsafe on device %p in background", proxy);
CHIP_ERROR err = InvokeCommandRequest(proxy->GetExchangeManager(), session.Get().Value(), kRootEndpointId, request,
onSuccessCb, onFailureCb);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to send command to disarm fail-safe: %" CHIP_ERROR_FORMAT, err.Format());
}
CleanupDoneAfterError();
}
else if (completionStatus.failedStage.HasValue() && completionStatus.failedStage.Value() >= kWiFiNetworkSetup)
{
// If we were already doing network setup, we need to retain the pase session and start again from network setup stage.
// We do not need to reset the failsafe here because we want to keep everything on the device up to this point, so just
// send the completion callbacks (see "Commissioning Flows Error Handling" in the spec).
CommissioningStageComplete(CHIP_NO_ERROR);
SendCommissioningCompleteCallbacks(nodeId, mCommissioningCompletionStatus);
}
else
{
// If we've failed somewhere in the early stages (or we don't have a failedStage specified), we need to start from the
// beginning. However, because some of the commands can only be sent once per arm-failsafe, we also need to force a reset on
// the failsafe so we can start fresh on the next attempt.
ChipLogProgress(Controller, "Disarming failsafe on device %p", proxy);
auto request = DisarmFailsafeRequest();
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnDisarmFailsafe, OnDisarmFailsafeFailure, kRootEndpointId);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just pretend like the command errored out async.
ChipLogError(Controller, "Failed to send command to disarm fail-safe: %" CHIP_ERROR_FORMAT, err.Format());
CleanupDoneAfterError();
}
}
}
void DeviceCommissioner::OnDisarmFailsafe(void * context,
const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data)
{
ChipLogProgress(Controller, "Failsafe disarmed");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CleanupDoneAfterError();
}
void DeviceCommissioner::OnDisarmFailsafeFailure(void * context, CHIP_ERROR error)
{
ChipLogProgress(Controller, "Ignoring failure to disarm failsafe: %" CHIP_ERROR_FORMAT, error.Format());
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CleanupDoneAfterError();
}
void DeviceCommissioner::CleanupDoneAfterError()
{
// If someone nulled out our mDeviceBeingCommissioned, there's nothing else
// to do here.
VerifyOrReturn(mDeviceBeingCommissioned != nullptr);
NodeId nodeId = mDeviceBeingCommissioned->GetDeviceId();
// At this point, we also want to close off the pase session so we need to re-establish
CommissioneeDeviceProxy * commissionee = FindCommissioneeDevice(nodeId);
// Signal completion - this will reset mDeviceBeingCommissioned.
CommissioningStageComplete(CHIP_NO_ERROR);
// If we've disarmed the failsafe, it's because we're starting again, so kill the pase connection.
if (commissionee != nullptr)
{
ReleaseCommissioneeDevice(commissionee);
}
// Invoke callbacks last, after we have cleared up all state.
SendCommissioningCompleteCallbacks(nodeId, mCommissioningCompletionStatus);
}
void DeviceCommissioner::SendCommissioningCompleteCallbacks(NodeId nodeId, const CompletionStatus & completionStatus)
{
MATTER_LOG_METRIC_END(kMetricDeviceCommissionerCommission, completionStatus.err);
ChipLogProgress(Controller, "Commissioning complete for node ID 0x" ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId),
(completionStatus.err == CHIP_NO_ERROR ? "success" : completionStatus.err.AsString()));
mCommissioningStage = CommissioningStage::kSecurePairing;
if (mPairingDelegate == nullptr)
{
return;
}
mPairingDelegate->OnCommissioningComplete(nodeId, completionStatus.err);
PeerId peerId(GetCompressedFabricId(), nodeId);
if (completionStatus.err == CHIP_NO_ERROR)
{
mPairingDelegate->OnCommissioningSuccess(peerId);
}
else
{
// TODO: We should propogate detailed error information (commissioningError, networkCommissioningStatus) from
// completionStatus.
mPairingDelegate->OnCommissioningFailure(peerId, completionStatus.err, completionStatus.failedStage.ValueOr(kError),
completionStatus.attestationResult);
}
}
void DeviceCommissioner::CommissioningStageComplete(CHIP_ERROR err, CommissioningDelegate::CommissioningReport report)
{
// Once this stage is complete, reset mDeviceBeingCommissioned - this will be reset when the delegate calls the next step.
MATTER_TRACE_SCOPE("CommissioningStageComplete", "DeviceCommissioner");
MATTER_LOG_METRIC_END(MetricKeyForCommissioningStage(mCommissioningStage), err);
VerifyOrDie(mDeviceBeingCommissioned);
NodeId nodeId = mDeviceBeingCommissioned->GetDeviceId();
DeviceProxy * proxy = mDeviceBeingCommissioned;
mDeviceBeingCommissioned = nullptr;
mInvokeCancelFn = nullptr;
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnCommissioningStatusUpdate(PeerId(GetCompressedFabricId(), nodeId), mCommissioningStage, err);
}
if (mCommissioningDelegate == nullptr)
{
return;
}
report.stageCompleted = mCommissioningStage;
CHIP_ERROR status = mCommissioningDelegate->CommissioningStepFinished(err, report);
if (status != CHIP_NO_ERROR && mCommissioningStage != CommissioningStage::kCleanup)
{
// Commissioning delegate will only return error if it failed to perform the appropriate commissioning step.
// In this case, we should complete the commissioning for it.
CompletionStatus completionStatus;
completionStatus.err = status;
completionStatus.failedStage = MakeOptional(report.stageCompleted);
mCommissioningStage = CommissioningStage::kCleanup;
mDeviceBeingCommissioned = proxy;
CleanupCommissioning(proxy, nodeId, completionStatus);
}
}
void DeviceCommissioner::OnDeviceConnectedFn(void * context, Messaging::ExchangeManager & exchangeMgr,
const SessionHandle & sessionHandle)
{
// CASE session established.
MATTER_LOG_METRIC_END(kMetricDeviceCommissioningOperationalSetup, CHIP_NO_ERROR);
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
VerifyOrDie(commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForStayActive ||
commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForCommissioningComplete);
VerifyOrDie(commissioner->mDeviceBeingCommissioned->GetDeviceId() == sessionHandle->GetPeer().GetNodeId());
commissioner->CancelCASECallbacks(); // ensure all CASE callbacks are unregistered
CommissioningDelegate::CommissioningReport report;
report.Set<OperationalNodeFoundData>(OperationalNodeFoundData(OperationalDeviceProxy(&exchangeMgr, sessionHandle)));
commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
}
void DeviceCommissioner::OnDeviceConnectionFailureFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR error)
{
// CASE session establishment failed.
MATTER_LOG_METRIC_END(kMetricDeviceCommissioningOperationalSetup, error);
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
VerifyOrDie(commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForStayActive ||
commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForCommissioningComplete);
VerifyOrDie(commissioner->mDeviceBeingCommissioned->GetDeviceId() == peerId.GetNodeId());
commissioner->CancelCASECallbacks(); // ensure all CASE callbacks are unregistered
if (error != CHIP_NO_ERROR)
{
ChipLogProgress(Controller, "Device connection failed. Error %" CHIP_ERROR_FORMAT, error.Format());
}
else
{
// Ensure that commissioning stage advancement is done based on seeing an error.
ChipLogError(Controller, "Device connection failed without a valid error code.");
error = CHIP_ERROR_INTERNAL;
}
commissioner->CommissioningStageComplete(error);
}
#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
// No specific action to take on either success or failure here; we're just
// trying to bump the fail-safe, and if that fails it's not clear there's much
// we can to with that.
static void OnExtendFailsafeForCASERetryFailure(void * context, CHIP_ERROR error)
{
ChipLogError(Controller, "Failed to extend fail-safe for CASE retry: %" CHIP_ERROR_FORMAT, error.Format());
}
static void
OnExtendFailsafeForCASERetrySuccess(void * context,
const app::Clusters::GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data)
{
ChipLogProgress(Controller, "Status of extending fail-safe for CASE retry: %u", to_underlying(data.errorCode));
}
void DeviceCommissioner::OnDeviceConnectionRetryFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR error,
System::Clock::Seconds16 retryTimeout)
{
ChipLogError(Controller,
"Session establishment failed for " ChipLogFormatScopedNodeId ", error: %" CHIP_ERROR_FORMAT
". Next retry expected to get a response to Sigma1 or fail within %d seconds",
ChipLogValueScopedNodeId(peerId), error.Format(), retryTimeout.count());
auto self = static_cast<DeviceCommissioner *>(context);
VerifyOrDie(self->GetCommissioningStage() == CommissioningStage::kFindOperationalForStayActive ||
self->GetCommissioningStage() == CommissioningStage::kFindOperationalForCommissioningComplete);
VerifyOrDie(self->mDeviceBeingCommissioned->GetDeviceId() == peerId.GetNodeId());
// We need to do the fail-safe arming over the PASE session.
auto * commissioneeDevice = self->FindCommissioneeDevice(peerId.GetNodeId());
if (!commissioneeDevice)
{
// Commissioning canceled, presumably. Just ignore the notification,
// not much we can do here.
return;
}
// Extend by the default failsafe timeout plus our retry timeout, so we can
// be sure the fail-safe will not expire before we try the next time, if
// there will be a next time.
//
// TODO: Make it possible for our clients to control the exact timeout here?
uint16_t failsafeTimeout;
if (UINT16_MAX - retryTimeout.count() < kDefaultFailsafeTimeout)
{
failsafeTimeout = UINT16_MAX;
}
else
{
failsafeTimeout = static_cast<uint16_t>(retryTimeout.count() + kDefaultFailsafeTimeout);
}
// A false return is fine; we don't want to make the fail-safe shorter here.
self->ExtendArmFailSafeInternal(commissioneeDevice, self->GetCommissioningStage(), failsafeTimeout,
MakeOptional(kMinimumCommissioningStepTimeout), OnExtendFailsafeForCASERetrySuccess,
OnExtendFailsafeForCASERetryFailure, /* fireAndForget = */ true);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
// ClusterStateCache::Callback impl
void DeviceCommissioner::OnDone(app::ReadClient * readClient)
{
VerifyOrDie(readClient != nullptr && readClient == mReadClient.get());
mReadClient.reset();
switch (mCommissioningStage)
{
case CommissioningStage::kReadCommissioningInfo:
// Silently complete the stage, data will be saved in attribute cache and
// will be parsed after all ReadCommissioningInfo stages are completed.
CommissioningStageComplete(CHIP_NO_ERROR);
break;
case CommissioningStage::kReadCommissioningInfo2:
// Note: Only parse commissioning info in the last ReadCommissioningInfo stage.
ParseCommissioningInfo();
break;
default:
VerifyOrDie(false);
break;
}
}
void DeviceCommissioner::ParseCommissioningInfo()
{
CHIP_ERROR err = CHIP_NO_ERROR;
ReadCommissioningInfo info;
err = ParseCommissioningInfo1(info);
if (err == CHIP_NO_ERROR)
{
err = ParseCommissioningInfo2(info);
}
// Move ownership of mAttributeCache to the stack, but don't release it until this function returns.
// This way we don't have to make a copy while parsing commissioning info, and it won't
// affect future commissioning steps.
//
// The stack reference needs to survive until CommissioningStageComplete and OnReadCommissioningInfo
// return.
auto attributeCache = std::move(mAttributeCache);
if (mPairingDelegate != nullptr && err == CHIP_NO_ERROR)
{
mPairingDelegate->OnReadCommissioningInfo(info);
}
CommissioningDelegate::CommissioningReport report;
report.Set<ReadCommissioningInfo>(info);
CommissioningStageComplete(err, report);
}
CHIP_ERROR DeviceCommissioner::ParseCommissioningInfo1(ReadCommissioningInfo & info)
{
CHIP_ERROR err;
CHIP_ERROR return_err = CHIP_NO_ERROR;
// Try to parse as much as we can here before returning, even if attributes
// are missing or cannot be decoded.
{
using namespace chip::app::Clusters::GeneralCommissioning;
using namespace chip::app::Clusters::GeneralCommissioning::Attributes;
BasicCommissioningInfo::TypeInfo::DecodableType basicInfo;
err = mAttributeCache->Get<BasicCommissioningInfo::TypeInfo>(kRootEndpointId, basicInfo);
if (err == CHIP_NO_ERROR)
{
info.general.recommendedFailsafe = basicInfo.failSafeExpiryLengthSeconds;
}
else
{
ChipLogError(Controller, "Failed to read BasicCommissioningInfo: %" CHIP_ERROR_FORMAT, err.Format());
return_err = err;
}
err = mAttributeCache->Get<RegulatoryConfig::TypeInfo>(kRootEndpointId, info.general.currentRegulatoryLocation);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to read RegulatoryConfig: %" CHIP_ERROR_FORMAT, err.Format());
return_err = err;
}
err = mAttributeCache->Get<LocationCapability::TypeInfo>(kRootEndpointId, info.general.locationCapability);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to read LocationCapability: %" CHIP_ERROR_FORMAT, err.Format());
return_err = err;
}
err = mAttributeCache->Get<Breadcrumb::TypeInfo>(kRootEndpointId, info.general.breadcrumb);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to read Breadcrumb: %" CHIP_ERROR_FORMAT, err.Format());
return_err = err;
}
}
{
using namespace chip::app::Clusters::BasicInformation;
using namespace chip::app::Clusters::BasicInformation::Attributes;
err = mAttributeCache->Get<VendorID::TypeInfo>(kRootEndpointId, info.basic.vendorId);
return_err = err == CHIP_NO_ERROR ? return_err : err;
err = mAttributeCache->Get<ProductID::TypeInfo>(kRootEndpointId, info.basic.productId);
return_err = err == CHIP_NO_ERROR ? return_err : err;
}
// Try to parse as much as we can here before returning, even if this is an error.
return_err = err == CHIP_NO_ERROR ? return_err : err;
// Set the network cluster endpoints first so we can match up the connection
// times. Note that here we don't know what endpoints the network
// commissioning clusters might be on.
err = mAttributeCache->ForEachAttribute(
app::Clusters::NetworkCommissioning::Id, [this, &info](const app::ConcreteAttributePath & path) {
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::NetworkCommissioning::Attributes;
if (path.mAttributeId != FeatureMap::Id)
{
return CHIP_NO_ERROR;
}
TLV::TLVReader reader;
if (this->mAttributeCache->Get(path, reader) == CHIP_NO_ERROR)
{
BitFlags<NetworkCommissioning::Feature> features;
if (app::DataModel::Decode(reader, features) == CHIP_NO_ERROR)
{
if (features.Has(NetworkCommissioning::Feature::kWiFiNetworkInterface))
{
ChipLogProgress(Controller, "----- NetworkCommissioning Features: has WiFi. endpointid = %u",
path.mEndpointId);
info.network.wifi.endpoint = path.mEndpointId;
}
else if (features.Has(NetworkCommissioning::Feature::kThreadNetworkInterface))
{
ChipLogProgress(Controller, "----- NetworkCommissioning Features: has Thread. endpointid = %u",
path.mEndpointId);
info.network.thread.endpoint = path.mEndpointId;
}
else if (features.Has(NetworkCommissioning::Feature::kEthernetNetworkInterface))
{
ChipLogProgress(Controller, "----- NetworkCommissioning Features: has Ethernet. endpointid = %u",
path.mEndpointId);
info.network.eth.endpoint = path.mEndpointId;
}
else
{
ChipLogProgress(Controller, "----- NetworkCommissioning Features: no features.");
// TODO: Gross workaround for the empty feature map on all clusters. Remove.
if (info.network.thread.endpoint == kInvalidEndpointId)
{
info.network.thread.endpoint = path.mEndpointId;
}
if (info.network.wifi.endpoint == kInvalidEndpointId)
{
info.network.wifi.endpoint = path.mEndpointId;
}
}
}
}
return CHIP_NO_ERROR;
});
return_err = err == CHIP_NO_ERROR ? return_err : err;
err = mAttributeCache->ForEachAttribute(
app::Clusters::NetworkCommissioning::Id, [this, &info](const app::ConcreteAttributePath & path) {
using namespace chip::app::Clusters::NetworkCommissioning::Attributes;
if (path.mAttributeId != ConnectMaxTimeSeconds::Id)
{
return CHIP_NO_ERROR;
}
ConnectMaxTimeSeconds::TypeInfo::DecodableArgType time;
ReturnErrorOnFailure(this->mAttributeCache->Get<ConnectMaxTimeSeconds::TypeInfo>(path, time));
if (path.mEndpointId == info.network.wifi.endpoint)
{
info.network.wifi.minConnectionTime = time;
}
else if (path.mEndpointId == info.network.thread.endpoint)
{
info.network.thread.minConnectionTime = time;
}
else if (path.mEndpointId == info.network.eth.endpoint)
{
info.network.eth.minConnectionTime = time;
}
return CHIP_NO_ERROR;
});
return_err = err == CHIP_NO_ERROR ? return_err : err;
ParseTimeSyncInfo(info);
if (return_err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Error parsing commissioning information");
}
return return_err;
}
void DeviceCommissioner::ParseTimeSyncInfo(ReadCommissioningInfo & info)
{
using namespace app::Clusters;
CHIP_ERROR err;
// If we fail to get the feature map, there's no viable time cluster, don't set anything.
TimeSynchronization::Attributes::FeatureMap::TypeInfo::DecodableType featureMap;
err = mAttributeCache->Get<TimeSynchronization::Attributes::FeatureMap::TypeInfo>(kRootEndpointId, featureMap);
if (err != CHIP_NO_ERROR)
{
info.requiresUTC = false;
info.requiresTimeZone = false;
info.requiresDefaultNTP = false;
info.requiresTrustedTimeSource = false;
return;
}
info.requiresUTC = true;
info.requiresTimeZone = featureMap & chip::to_underlying(TimeSynchronization::Feature::kTimeZone);
info.requiresDefaultNTP = featureMap & chip::to_underlying(TimeSynchronization::Feature::kNTPClient);
info.requiresTrustedTimeSource = featureMap & chip::to_underlying(TimeSynchronization::Feature::kTimeSyncClient);
if (info.requiresTimeZone)
{
err = mAttributeCache->Get<TimeSynchronization::Attributes::TimeZoneListMaxSize::TypeInfo>(kRootEndpointId,
info.maxTimeZoneSize);
if (err != CHIP_NO_ERROR)
{
// This information should be available, let's do our best with what we have, but we can't set
// the time zone without this information
info.requiresTimeZone = false;
}
err =
mAttributeCache->Get<TimeSynchronization::Attributes::DSTOffsetListMaxSize::TypeInfo>(kRootEndpointId, info.maxDSTSize);
if (err != CHIP_NO_ERROR)
{
info.requiresTimeZone = false;
}
}
if (info.requiresDefaultNTP)
{
TimeSynchronization::Attributes::DefaultNTP::TypeInfo::DecodableType defaultNTP;
err = mAttributeCache->Get<TimeSynchronization::Attributes::DefaultNTP::TypeInfo>(kRootEndpointId, defaultNTP);
if (err == CHIP_NO_ERROR && (!defaultNTP.IsNull()) && (defaultNTP.Value().size() != 0))
{
info.requiresDefaultNTP = false;
}
}
if (info.requiresTrustedTimeSource)
{
TimeSynchronization::Attributes::TrustedTimeSource::TypeInfo::DecodableType trustedTimeSource;
err =
mAttributeCache->Get<TimeSynchronization::Attributes::TrustedTimeSource::TypeInfo>(kRootEndpointId, trustedTimeSource);
if (err == CHIP_NO_ERROR && !trustedTimeSource.IsNull())
{
info.requiresTrustedTimeSource = false;
}
}
}
CHIP_ERROR DeviceCommissioner::ParseCommissioningInfo2(ReadCommissioningInfo & info)
{
CHIP_ERROR err = CHIP_NO_ERROR;
using namespace chip::app::Clusters::GeneralCommissioning::Attributes;
if (mAttributeCache->Get<SupportsConcurrentConnection::TypeInfo>(kRootEndpointId, info.supportsConcurrentConnection) !=
CHIP_NO_ERROR)
{
// May not be present so don't return the error code, non fatal, default concurrent
ChipLogError(Controller, "Failed to read SupportsConcurrentConnection: %" CHIP_ERROR_FORMAT, err.Format());
info.supportsConcurrentConnection = true;
}
err = ParseFabrics(info);
if (err == CHIP_NO_ERROR)
{
err = ParseICDInfo(info);
}
return err;
}
CHIP_ERROR DeviceCommissioner::ParseFabrics(ReadCommissioningInfo & info)
{
CHIP_ERROR err;
CHIP_ERROR return_err = CHIP_NO_ERROR;
// We might not have requested a Fabrics attribute at all, so not having a
// value for it is not an error.
err = mAttributeCache->ForEachAttribute(OperationalCredentials::Id, [this, &info](const app::ConcreteAttributePath & path) {
using namespace chip::app::Clusters::OperationalCredentials::Attributes;
// this code is checking if the device is already on the commissioner's fabric.
// if a matching fabric is found, then remember the nodeId so that the commissioner
// can, if it decides to, cancel commissioning (before it fails in AddNoc) and know
// the device's nodeId on its fabric.
switch (path.mAttributeId)
{
case Fabrics::Id: {
Fabrics::TypeInfo::DecodableType fabrics;
ReturnErrorOnFailure(this->mAttributeCache->Get<Fabrics::TypeInfo>(path, fabrics));
// this is a best effort attempt to find a matching fabric, so no error checking on iter
auto iter = fabrics.begin();
while (iter.Next())
{
auto & fabricDescriptor = iter.GetValue();
ChipLogProgress(Controller,
"DeviceCommissioner::OnDone - fabric.vendorId=0x%04X fabric.fabricId=0x" ChipLogFormatX64
" fabric.nodeId=0x" ChipLogFormatX64,
fabricDescriptor.vendorID, ChipLogValueX64(fabricDescriptor.fabricID),
ChipLogValueX64(fabricDescriptor.nodeID));
if (GetFabricId() == fabricDescriptor.fabricID)
{
ChipLogProgress(Controller, "DeviceCommissioner::OnDone - found a matching fabric id");
chip::ByteSpan rootKeySpan = fabricDescriptor.rootPublicKey;
if (rootKeySpan.size() != Crypto::kP256_PublicKey_Length)
{
ChipLogError(Controller, "DeviceCommissioner::OnDone - fabric root key size mismatch %u != %u",
static_cast<unsigned>(rootKeySpan.size()),
static_cast<unsigned>(Crypto::kP256_PublicKey_Length));
continue;
}
P256PublicKeySpan rootPubKeySpan(rootKeySpan.data());
Crypto::P256PublicKey deviceRootPublicKey(rootPubKeySpan);
Crypto::P256PublicKey commissionerRootPublicKey;
if (CHIP_NO_ERROR != GetRootPublicKey(commissionerRootPublicKey))
{
ChipLogError(Controller, "DeviceCommissioner::OnDone - error reading commissioner root public key");
}
else if (commissionerRootPublicKey.Matches(deviceRootPublicKey))
{
ChipLogProgress(Controller, "DeviceCommissioner::OnDone - fabric root keys match");
info.remoteNodeId = fabricDescriptor.nodeID;
}
}
}
return CHIP_NO_ERROR;
}
default:
return CHIP_NO_ERROR;
}
});
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnFabricCheck(info.remoteNodeId);
}
return return_err;
}
CHIP_ERROR DeviceCommissioner::ParseICDInfo(ReadCommissioningInfo & info)
{
using chip::app::Clusters::IcdManagement::UserActiveModeTriggerBitmap;
CHIP_ERROR err;
IcdManagement::Attributes::FeatureMap::TypeInfo::DecodableType featureMap;
bool hasUserActiveModeTrigger = false;
bool isICD = false;
err = mAttributeCache->Get<IcdManagement::Attributes::FeatureMap::TypeInfo>(kRootEndpointId, featureMap);
if (err == CHIP_NO_ERROR)
{
info.icd.isLIT = !!(featureMap & to_underlying(IcdManagement::Feature::kLongIdleTimeSupport));
info.icd.checkInProtocolSupport = !!(featureMap & to_underlying(IcdManagement::Feature::kCheckInProtocolSupport));
hasUserActiveModeTrigger = !!(featureMap & to_underlying(IcdManagement::Feature::kUserActiveModeTrigger));
isICD = true;
}
else if (err == CHIP_ERROR_KEY_NOT_FOUND)
{
// This key is optional so not an error
info.icd.isLIT = false;
err = CHIP_NO_ERROR;
}
else if (err == CHIP_ERROR_IM_STATUS_CODE_RECEIVED)
{
app::StatusIB statusIB;
err = mAttributeCache->GetStatus(
app::ConcreteAttributePath(kRootEndpointId, IcdManagement::Id, IcdManagement::Attributes::FeatureMap::Id), statusIB);
if (err == CHIP_NO_ERROR)
{
if (statusIB.mStatus == Protocols::InteractionModel::Status::UnsupportedCluster)
{
info.icd.isLIT = false;
}
else
{
err = statusIB.ToChipError();
}
}
}
ReturnErrorOnFailure(err);
info.icd.userActiveModeTriggerHint.ClearAll();
info.icd.userActiveModeTriggerInstruction = CharSpan();
if (hasUserActiveModeTrigger)
{
// Intentionally ignore errors since they are not mandatory.
bool activeModeTriggerInstructionRequired = false;
err = mAttributeCache->Get<IcdManagement::Attributes::UserActiveModeTriggerHint::TypeInfo>(
kRootEndpointId, info.icd.userActiveModeTriggerHint);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "IcdManagement.UserActiveModeTriggerHint expected, but failed to read.");
return err;
}
activeModeTriggerInstructionRequired = info.icd.userActiveModeTriggerHint.HasAny(
UserActiveModeTriggerBitmap::kCustomInstruction, UserActiveModeTriggerBitmap::kActuateSensorSeconds,
UserActiveModeTriggerBitmap::kActuateSensorTimes, UserActiveModeTriggerBitmap::kActuateSensorLightsBlink,
UserActiveModeTriggerBitmap::kResetButtonLightsBlink, UserActiveModeTriggerBitmap::kResetButtonSeconds,
UserActiveModeTriggerBitmap::kResetButtonTimes, UserActiveModeTriggerBitmap::kSetupButtonSeconds,
UserActiveModeTriggerBitmap::kSetupButtonTimes, UserActiveModeTriggerBitmap::kSetupButtonTimes,
UserActiveModeTriggerBitmap::kAppDefinedButton);
if (activeModeTriggerInstructionRequired)
{
err = mAttributeCache->Get<IcdManagement::Attributes::UserActiveModeTriggerInstruction::TypeInfo>(
kRootEndpointId, info.icd.userActiveModeTriggerInstruction);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller,
"IcdManagement.UserActiveModeTriggerInstruction expected for given active mode trigger hint, but "
"failed to read.");
return err;
}
}
}
if (!isICD)
{
info.icd.idleModeDuration = 0;
info.icd.activeModeDuration = 0;
info.icd.activeModeThreshold = 0;
return CHIP_NO_ERROR;
}
err = mAttributeCache->Get<IcdManagement::Attributes::IdleModeDuration::TypeInfo>(kRootEndpointId, info.icd.idleModeDuration);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "IcdManagement.IdleModeDuration expected, but failed to read: %" CHIP_ERROR_FORMAT, err.Format());
return err;
}
err =
mAttributeCache->Get<IcdManagement::Attributes::ActiveModeDuration::TypeInfo>(kRootEndpointId, info.icd.activeModeDuration);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "IcdManagement.ActiveModeDuration expected, but failed to read: %" CHIP_ERROR_FORMAT,
err.Format());
return err;
}
err = mAttributeCache->Get<IcdManagement::Attributes::ActiveModeThreshold::TypeInfo>(kRootEndpointId,
info.icd.activeModeThreshold);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "IcdManagement.ActiveModeThreshold expected, but failed to read: %" CHIP_ERROR_FORMAT,
err.Format());
}
return err;
}
void DeviceCommissioner::OnArmFailSafe(void * context,
const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data)
{
CommissioningDelegate::CommissioningReport report;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogProgress(Controller, "Received ArmFailSafe response errorCode=%u", to_underlying(data.errorCode));
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
{
err = CHIP_ERROR_INTERNAL;
report.Set<CommissioningErrorInfo>(data.errorCode);
}
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(err, report);
}
void DeviceCommissioner::OnSetRegulatoryConfigResponse(
void * context, const GeneralCommissioning::Commands::SetRegulatoryConfigResponse::DecodableType & data)
{
CommissioningDelegate::CommissioningReport report;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogProgress(Controller, "Received SetRegulatoryConfig response errorCode=%u", to_underlying(data.errorCode));
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
{
err = CHIP_ERROR_INTERNAL;
report.Set<CommissioningErrorInfo>(data.errorCode);
}
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(err, report);
}
void DeviceCommissioner::OnSetTimeZoneResponse(void * context,
const TimeSynchronization::Commands::SetTimeZoneResponse::DecodableType & data)
{
CommissioningDelegate::CommissioningReport report;
CHIP_ERROR err = CHIP_NO_ERROR;
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
TimeZoneResponseInfo info;
info.requiresDSTOffsets = data.DSTOffsetRequired;
report.Set<TimeZoneResponseInfo>(info);
commissioner->CommissioningStageComplete(err, report);
}
void DeviceCommissioner::OnSetUTCError(void * context, CHIP_ERROR error)
{
// For SetUTCTime, we don't actually care if the commissionee didn't want out time, that's its choice
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::OnScanNetworksFailure(void * context, CHIP_ERROR error)
{
ChipLogProgress(Controller, "Received ScanNetworks failure response %" CHIP_ERROR_FORMAT, error.Format());
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
// advance to the kNeedsNetworkCreds waiting step
// clear error so that we don't abort the commissioning when ScanNetworks fails
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
if (commissioner->GetPairingDelegate() != nullptr)
{
commissioner->GetPairingDelegate()->OnScanNetworksFailure(error);
}
}
void DeviceCommissioner::OnScanNetworksResponse(void * context,
const NetworkCommissioning::Commands::ScanNetworksResponse::DecodableType & data)
{
CommissioningDelegate::CommissioningReport report;
ChipLogProgress(Controller, "Received ScanNetwork response, networkingStatus=%u debugText=%s",
to_underlying(data.networkingStatus),
(data.debugText.HasValue() ? std::string(data.debugText.Value().data(), data.debugText.Value().size()).c_str()
: "none provided"));
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
// advance to the kNeedsNetworkCreds waiting step
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
if (commissioner->GetPairingDelegate() != nullptr)
{
commissioner->GetPairingDelegate()->OnScanNetworksSuccess(data);
}
}
CHIP_ERROR DeviceCommissioner::NetworkCredentialsReady()
{
ReturnErrorCodeIf(mCommissioningStage != CommissioningStage::kNeedsNetworkCreds, CHIP_ERROR_INCORRECT_STATE);
// need to advance to next step
CommissioningStageComplete(CHIP_NO_ERROR);
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::ICDRegistrationInfoReady()
{
ReturnErrorCodeIf(mCommissioningStage != CommissioningStage::kICDGetRegistrationInfo, CHIP_ERROR_INCORRECT_STATE);
// need to advance to next step
CommissioningStageComplete(CHIP_NO_ERROR);
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnNetworkConfigResponse(void * context,
const NetworkCommissioning::Commands::NetworkConfigResponse::DecodableType & data)
{
CommissioningDelegate::CommissioningReport report;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogProgress(Controller, "Received NetworkConfig response, networkingStatus=%u", to_underlying(data.networkingStatus));
if (data.networkingStatus != NetworkCommissioning::NetworkCommissioningStatusEnum::kSuccess)
{
err = CHIP_ERROR_INTERNAL;
report.Set<NetworkCommissioningStatusInfo>(data.networkingStatus);
}
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(err, report);
}
void DeviceCommissioner::OnConnectNetworkResponse(
void * context, const NetworkCommissioning::Commands::ConnectNetworkResponse::DecodableType & data)
{
CommissioningDelegate::CommissioningReport report;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogProgress(Controller, "Received ConnectNetwork response, networkingStatus=%u", to_underlying(data.networkingStatus));
if (data.networkingStatus != NetworkCommissioning::NetworkCommissioningStatusEnum::kSuccess)
{
err = CHIP_ERROR_INTERNAL;
report.Set<NetworkCommissioningStatusInfo>(data.networkingStatus);
}
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(err, report);
}
void DeviceCommissioner::OnCommissioningCompleteResponse(
void * context, const GeneralCommissioning::Commands::CommissioningCompleteResponse::DecodableType & data)
{
CommissioningDelegate::CommissioningReport report;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogProgress(Controller, "Received CommissioningComplete response, errorCode=%u", to_underlying(data.errorCode));
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
{
err = CHIP_ERROR_INTERNAL;
report.Set<CommissioningErrorInfo>(data.errorCode);
}
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(err, report);
}
template <typename RequestObjectT>
CHIP_ERROR
DeviceCommissioner::SendCommissioningCommand(DeviceProxy * device, const RequestObjectT & request,
CommandResponseSuccessCallback<typename RequestObjectT::ResponseType> successCb,
CommandResponseFailureCallback failureCb, EndpointId endpoint,
Optional<System::Clock::Timeout> timeout, bool fireAndForget)
{
// Default behavior is to make sequential, cancellable calls tracked via mInvokeCancelFn.
// Fire-and-forget calls are not cancellable and don't receive `this` as context in callbacks.
VerifyOrDie(fireAndForget || !mInvokeCancelFn); // we don't make parallel (cancellable) calls
void * context = (!fireAndForget) ? this : nullptr;
auto onSuccessCb = [context, successCb](const app::ConcreteCommandPath & aPath, const app::StatusIB & aStatus,
const typename RequestObjectT::ResponseType & responseData) {
successCb(context, responseData);
};
auto onFailureCb = [context, failureCb](CHIP_ERROR aError) { failureCb(context, aError); };
return InvokeCommandRequest(device->GetExchangeManager(), device->GetSecureSession().Value(), endpoint, request, onSuccessCb,
onFailureCb, NullOptional, timeout, (!fireAndForget) ? &mInvokeCancelFn : nullptr);
}
void DeviceCommissioner::SendCommissioningReadRequest(DeviceProxy * proxy, Optional<System::Clock::Timeout> timeout,
app::AttributePathParams * readPaths, size_t readPathsSize)
{
VerifyOrDie(!mReadClient); // we don't perform parallel reads
app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
app::ReadPrepareParams readParams(proxy->GetSecureSession().Value());
readParams.mIsFabricFiltered = false;
if (timeout.HasValue())
{
readParams.mTimeout = timeout.Value();
}
readParams.mpAttributePathParamsList = readPaths;
readParams.mAttributePathParamsListSize = readPathsSize;
// Take ownership of the attribute cache, so it can be released when SendRequest fails.
auto attributeCache = std::move(mAttributeCache);
auto readClient = chip::Platform::MakeUnique<app::ReadClient>(
engine, proxy->GetExchangeManager(), attributeCache->GetBufferedCallback(), app::ReadClient::InteractionType::Read);
CHIP_ERROR err = readClient->SendRequest(readParams);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to send read request: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
mAttributeCache = std::move(attributeCache);
mReadClient = std::move(readClient);
}
void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, CommissioningStage step, CommissioningParameters & params,
CommissioningDelegate * delegate, EndpointId endpoint,
Optional<System::Clock::Timeout> timeout)
{
MATTER_LOG_METRIC(kMetricDeviceCommissionerCommissionStage, step);
MATTER_LOG_METRIC_BEGIN(MetricKeyForCommissioningStage(step));
if (params.GetCompletionStatus().err == CHIP_NO_ERROR)
{
ChipLogProgress(Controller, "Performing next commissioning step '%s'", StageToString(step));
}
else
{
ChipLogProgress(Controller, "Performing next commissioning step '%s' with completion status = '%s'", StageToString(step),
params.GetCompletionStatus().err.AsString());
}
mCommissioningStage = step;
mCommissioningDelegate = delegate;
mDeviceBeingCommissioned = proxy;
// TODO: Extend timeouts to the DAC and Opcert requests.
// TODO(cecille): We probably want something better than this for breadcrumbs.
uint64_t breadcrumb = static_cast<uint64_t>(step);
switch (step)
{
case CommissioningStage::kArmFailsafe: {
VerifyOrDie(endpoint == kRootEndpointId);
// Make sure the fail-safe value we set here actually ends up being used
// no matter what.
proxy->SetFailSafeExpirationTimestamp(System::Clock::kZero);
VerifyOrDie(ExtendArmFailSafeInternal(proxy, step, params.GetFailsafeTimerSeconds().ValueOr(kDefaultFailsafeTimeout),
timeout, OnArmFailSafe, OnBasicFailure, /* fireAndForget = */ false));
}
break;
case CommissioningStage::kReadCommissioningInfo: {
ChipLogProgress(Controller, "Sending read request for commissioning information");
// Allocate a new ClusterStateCache when starting reading the first batch of attributes.
// The cache will be released in:
// - SendCommissioningReadRequest when failing to send a read request.
// - ParseCommissioningInfo when the last ReadCommissioningInfo stage is completed.
// Currently, we have two ReadCommissioningInfo* stages.
mAttributeCache = Platform::MakeUnique<app::ClusterStateCache>(*this);
// NOTE: this array cannot have more than 9 entries, since the spec mandates that server only needs to support 9
// See R1.1, 2.11.2 Interaction Model Limits
app::AttributePathParams readPaths[9];
// Read all the feature maps for all the networking clusters on any endpoint to determine what is supported
readPaths[0] = app::AttributePathParams(app::Clusters::NetworkCommissioning::Id,
app::Clusters::NetworkCommissioning::Attributes::FeatureMap::Id);
// Get required general commissioning attributes on this endpoint (recommended failsafe time, regulatory location
// info, breadcrumb)
readPaths[1] = app::AttributePathParams(endpoint, app::Clusters::GeneralCommissioning::Id,
app::Clusters::GeneralCommissioning::Attributes::Breadcrumb::Id);
readPaths[2] = app::AttributePathParams(endpoint, app::Clusters::GeneralCommissioning::Id,
app::Clusters::GeneralCommissioning::Attributes::BasicCommissioningInfo::Id);
readPaths[3] = app::AttributePathParams(endpoint, app::Clusters::GeneralCommissioning::Id,
app::Clusters::GeneralCommissioning::Attributes::RegulatoryConfig::Id);
readPaths[4] = app::AttributePathParams(endpoint, app::Clusters::GeneralCommissioning::Id,
app::Clusters::GeneralCommissioning::Attributes::LocationCapability::Id);
// Read attributes from the basic info cluster (vendor id / product id / software version)
readPaths[5] = app::AttributePathParams(endpoint, app::Clusters::BasicInformation::Id,
app::Clusters::BasicInformation::Attributes::VendorID::Id);
readPaths[6] = app::AttributePathParams(endpoint, app::Clusters::BasicInformation::Id,
app::Clusters::BasicInformation::Attributes::ProductID::Id);
// Read the requested minimum connection times from all network commissioning clusters
readPaths[7] = app::AttributePathParams(app::Clusters::NetworkCommissioning::Id,
app::Clusters::NetworkCommissioning::Attributes::ConnectMaxTimeSeconds::Id);
// Read everything from the time cluster so we can assess what information needs to be set.
readPaths[8] = app::AttributePathParams(endpoint, app::Clusters::TimeSynchronization::Id);
SendCommissioningReadRequest(proxy, timeout, readPaths, 9);
}
break;
case CommissioningStage::kReadCommissioningInfo2: {
size_t numberOfAttributes = 0;
// This is done in a separate step since we've already used up all the available read paths in the previous read step
// NOTE: this array cannot have more than 9 entries, since the spec mandates that server only needs to support 9
// See R1.1, 2.11.2 Interaction Model Limits
// Currently, we have at most 8 attributes to read in this stage.
app::AttributePathParams readPaths[8];
// Mandatory attribute
readPaths[numberOfAttributes++] =
app::AttributePathParams(endpoint, app::Clusters::GeneralCommissioning::Id,
app::Clusters::GeneralCommissioning::Attributes::SupportsConcurrentConnection::Id);
// Read the current fabrics
if (params.GetCheckForMatchingFabric())
{
readPaths[numberOfAttributes++] =
app::AttributePathParams(OperationalCredentials::Id, OperationalCredentials::Attributes::Fabrics::Id);
}
if (params.GetICDRegistrationStrategy() != ICDRegistrationStrategy::kIgnore)
{
readPaths[numberOfAttributes++] =
app::AttributePathParams(endpoint, IcdManagement::Id, IcdManagement::Attributes::FeatureMap::Id);
}
// Always read the active mode trigger hint attributes to notify users about it.
readPaths[numberOfAttributes++] =
app::AttributePathParams(endpoint, IcdManagement::Id, IcdManagement::Attributes::UserActiveModeTriggerHint::Id);
readPaths[numberOfAttributes++] =
app::AttributePathParams(endpoint, IcdManagement::Id, IcdManagement::Attributes::UserActiveModeTriggerInstruction::Id);
readPaths[numberOfAttributes++] =
app::AttributePathParams(endpoint, IcdManagement::Id, IcdManagement::Attributes::IdleModeDuration::Id);
readPaths[numberOfAttributes++] =
app::AttributePathParams(endpoint, IcdManagement::Id, IcdManagement::Attributes::ActiveModeDuration::Id);
readPaths[numberOfAttributes++] =
app::AttributePathParams(endpoint, IcdManagement::Id, IcdManagement::Attributes::ActiveModeThreshold::Id);
SendCommissioningReadRequest(proxy, timeout, readPaths, numberOfAttributes);
}
break;
case CommissioningStage::kConfigureUTCTime: {
TimeSynchronization::Commands::SetUTCTime::Type request;
uint64_t kChipEpochUsSinceUnixEpoch = static_cast<uint64_t>(kChipEpochSecondsSinceUnixEpoch) * chip::kMicrosecondsPerSecond;
System::Clock::Microseconds64 utcTime;
if (System::SystemClock().GetClock_RealTime(utcTime) != CHIP_NO_ERROR || utcTime.count() <= kChipEpochUsSinceUnixEpoch)
{
// We have no time to give, but that's OK, just complete this stage
CommissioningStageComplete(CHIP_NO_ERROR);
return;
}
request.UTCTime = utcTime.count() - kChipEpochUsSinceUnixEpoch;
// For now, we assume a seconds granularity
request.granularity = TimeSynchronization::GranularityEnum::kSecondsGranularity;
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnBasicSuccess, OnSetUTCError, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send SetUTCTime command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kConfigureTimeZone: {
if (!params.GetTimeZone().HasValue())
{
ChipLogError(Controller, "ConfigureTimeZone stage called with no time zone data");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
TimeSynchronization::Commands::SetTimeZone::Type request;
request.timeZone = params.GetTimeZone().Value();
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnSetTimeZoneResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send SetTimeZone command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kConfigureDSTOffset: {
if (!params.GetDSTOffsets().HasValue())
{
ChipLogError(Controller, "ConfigureDSTOffset stage called with no DST data");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
TimeSynchronization::Commands::SetDSTOffset::Type request;
request.DSTOffset = params.GetDSTOffsets().Value();
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnBasicSuccess, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send SetDSTOffset command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kConfigureDefaultNTP: {
if (!params.GetDefaultNTP().HasValue())
{
ChipLogError(Controller, "ConfigureDefaultNTP stage called with no default NTP data");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
TimeSynchronization::Commands::SetDefaultNTP::Type request;
request.defaultNTP = params.GetDefaultNTP().Value();
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnBasicSuccess, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send SetDefaultNTP command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kScanNetworks: {
NetworkCommissioning::Commands::ScanNetworks::Type request;
if (params.GetWiFiCredentials().HasValue())
{
request.ssid.Emplace(params.GetWiFiCredentials().Value().ssid);
}
request.breadcrumb.Emplace(breadcrumb);
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnScanNetworksResponse, OnScanNetworksFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send ScanNetworks command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kNeedsNetworkCreds: {
// nothing to do, the OnScanNetworksSuccess and OnScanNetworksFailure callbacks provide indication to the
// DevicePairingDelegate that network credentials are needed.
break;
}
case CommissioningStage::kConfigRegulatory: {
// TODO(cecille): Worthwhile to keep this around as part of the class?
// TODO(cecille): Where is the country config actually set?
ChipLogProgress(Controller, "Setting Regulatory Config");
auto capability =
params.GetLocationCapability().ValueOr(app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum::kOutdoor);
app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum regulatoryConfig;
// Value is only switchable on the devices with indoor/outdoor capability
if (capability == app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum::kIndoorOutdoor)
{
// If the device supports indoor and outdoor configs, use the setting from the commissioner, otherwise fall back to
// the current device setting then to outdoor (most restrictive)
if (params.GetDeviceRegulatoryLocation().HasValue())
{
regulatoryConfig = params.GetDeviceRegulatoryLocation().Value();
ChipLogProgress(Controller, "Setting regulatory config to %u from commissioner override",
static_cast<uint8_t>(regulatoryConfig));
}
else if (params.GetDefaultRegulatoryLocation().HasValue())
{
regulatoryConfig = params.GetDefaultRegulatoryLocation().Value();
ChipLogProgress(Controller, "No regulatory config supplied by controller, leaving as device default (%u)",
static_cast<uint8_t>(regulatoryConfig));
}
else
{
regulatoryConfig = app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum::kOutdoor;
ChipLogProgress(Controller, "No overrride or device regulatory config supplied, setting to outdoor");
}
}
else
{
ChipLogProgress(Controller, "Device does not support configurable regulatory location");
regulatoryConfig = capability;
}
CharSpan countryCode;
const auto & providedCountryCode = params.GetCountryCode();
if (providedCountryCode.HasValue())
{
countryCode = providedCountryCode.Value();
}
else
{
// Default to "XX", for lack of anything better.
countryCode = "XX"_span;
}
GeneralCommissioning::Commands::SetRegulatoryConfig::Type request;
request.newRegulatoryConfig = regulatoryConfig;
request.countryCode = countryCode;
request.breadcrumb = breadcrumb;
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnSetRegulatoryConfigResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send SetRegulatoryConfig command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kSendPAICertificateRequest: {
ChipLogProgress(Controller, "Sending request for PAI certificate");
CHIP_ERROR err = SendCertificateChainRequestCommand(proxy, CertificateType::kPAI, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send CertificateChainRequest command to get PAI: %" CHIP_ERROR_FORMAT,
err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kSendDACCertificateRequest: {
ChipLogProgress(Controller, "Sending request for DAC certificate");
CHIP_ERROR err = SendCertificateChainRequestCommand(proxy, CertificateType::kDAC, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send CertificateChainRequest command to get DAC: %" CHIP_ERROR_FORMAT,
err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kSendAttestationRequest: {
ChipLogProgress(Controller, "Sending Attestation Request to the device.");
if (!params.GetAttestationNonce().HasValue())
{
ChipLogError(Controller, "No attestation nonce found");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
CHIP_ERROR err = SendAttestationRequestCommand(proxy, params.GetAttestationNonce().Value(), timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send AttestationRequest command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kAttestationVerification: {
ChipLogProgress(Controller, "Verifying attestation");
if (IsAttestationInformationMissing(params))
{
ChipLogError(Controller, "Missing attestation information");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
DeviceAttestationVerifier::AttestationInfo info(
params.GetAttestationElements().Value(),
proxy->GetSecureSession().Value()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge(),
params.GetAttestationSignature().Value(), params.GetPAI().Value(), params.GetDAC().Value(),
params.GetAttestationNonce().Value(), params.GetRemoteVendorId().Value(), params.GetRemoteProductId().Value());
if (ValidateAttestationInfo(info) != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Error validating attestation information");
CommissioningStageComplete(CHIP_ERROR_FAILED_DEVICE_ATTESTATION);
return;
}
}
break;
case CommissioningStage::kAttestationRevocationCheck: {
ChipLogProgress(Controller, "Verifying device's DAC chain revocation status");
if (IsAttestationInformationMissing(params))
{
ChipLogError(Controller, "Missing attestation information");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
DeviceAttestationVerifier::AttestationInfo info(
params.GetAttestationElements().Value(),
proxy->GetSecureSession().Value()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge(),
params.GetAttestationSignature().Value(), params.GetPAI().Value(), params.GetDAC().Value(),
params.GetAttestationNonce().Value(), params.GetRemoteVendorId().Value(), params.GetRemoteProductId().Value());
if (CheckForRevokedDACChain(info) != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Error validating device's DAC chain revocation status");
CommissioningStageComplete(CHIP_ERROR_FAILED_DEVICE_ATTESTATION);
return;
}
}
break;
case CommissioningStage::kSendOpCertSigningRequest: {
if (!params.GetCSRNonce().HasValue())
{
ChipLogError(Controller, "No CSR nonce found");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
CHIP_ERROR err = SendOperationalCertificateSigningRequestCommand(proxy, params.GetCSRNonce().Value(), timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send CSR request: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kValidateCSR: {
if (!params.GetNOCChainGenerationParameters().HasValue() || !params.GetDAC().HasValue() || !params.GetCSRNonce().HasValue())
{
ChipLogError(Controller, "Unable to validate CSR");
return CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
}
// This is non-blocking, so send the callback immediately.
CHIP_ERROR err = ValidateCSR(proxy, params.GetNOCChainGenerationParameters().Value().nocsrElements,
params.GetNOCChainGenerationParameters().Value().signature, params.GetDAC().Value(),
params.GetCSRNonce().Value());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Unable to validate CSR");
}
CommissioningStageComplete(err);
return;
}
break;
case CommissioningStage::kGenerateNOCChain: {
if (!params.GetNOCChainGenerationParameters().HasValue() || !params.GetDAC().HasValue() || !params.GetPAI().HasValue() ||
!params.GetCSRNonce().HasValue())
{
ChipLogError(Controller, "Unable to generate NOC chain parameters");
return CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
}
CHIP_ERROR err = ProcessCSR(proxy, params.GetNOCChainGenerationParameters().Value().nocsrElements,
params.GetNOCChainGenerationParameters().Value().signature, params.GetDAC().Value(),
params.GetPAI().Value(), params.GetCSRNonce().Value());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Unable to process Op CSR");
// Handle error, and notify session failure to the commissioner application.
ChipLogError(Controller, "Failed to process the certificate signing request");
// TODO: Map error status to correct error code
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kSendTrustedRootCert: {
if (!params.GetRootCert().HasValue() || !params.GetNoc().HasValue())
{
ChipLogError(Controller, "No trusted root cert or NOC specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
CHIP_ERROR err = SendTrustedRootCertificate(proxy, params.GetRootCert().Value(), timeout);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Error sending trusted root certificate: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
err = proxy->SetPeerId(params.GetRootCert().Value(), params.GetNoc().Value());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Error setting peer id: %s", err.AsString());
CommissioningStageComplete(err);
return;
}
if (!IsOperationalNodeId(proxy->GetDeviceId()))
{
ChipLogError(Controller, "Given node ID is not an operational node ID");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
}
break;
case CommissioningStage::kSendNOC: {
if (!params.GetNoc().HasValue() || !params.GetIpk().HasValue() || !params.GetAdminSubject().HasValue())
{
ChipLogError(Controller, "AddNOC contents not specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
CHIP_ERROR err = SendOperationalCertificate(proxy, params.GetNoc().Value(), params.GetIcac(), params.GetIpk().Value(),
params.GetAdminSubject().Value(), timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Error sending operational certificate: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kConfigureTrustedTimeSource: {
if (!params.GetTrustedTimeSource().HasValue())
{
ChipLogError(Controller, "ConfigureTrustedTimeSource stage called with no trusted time source data");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
TimeSynchronization::Commands::SetTrustedTimeSource::Type request;
request.trustedTimeSource = params.GetTrustedTimeSource().Value();
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnBasicSuccess, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send SendTrustedTimeSource command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
break;
}
case CommissioningStage::kWiFiNetworkSetup: {
if (!params.GetWiFiCredentials().HasValue())
{
ChipLogError(Controller, "No wifi credentials specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
NetworkCommissioning::Commands::AddOrUpdateWiFiNetwork::Type request;
request.ssid = params.GetWiFiCredentials().Value().ssid;
request.credentials = params.GetWiFiCredentials().Value().credentials;
request.breadcrumb.Emplace(breadcrumb);
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnNetworkConfigResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send AddOrUpdateWiFiNetwork command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kThreadNetworkSetup: {
if (!params.GetThreadOperationalDataset().HasValue())
{
ChipLogError(Controller, "No thread credentials specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
NetworkCommissioning::Commands::AddOrUpdateThreadNetwork::Type request;
request.operationalDataset = params.GetThreadOperationalDataset().Value();
request.breadcrumb.Emplace(breadcrumb);
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnNetworkConfigResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send AddOrUpdateThreadNetwork command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kFailsafeBeforeWiFiEnable:
FALLTHROUGH;
case CommissioningStage::kFailsafeBeforeThreadEnable:
// Before we try to do network enablement, make sure that our fail-safe
// is set far enough out that we can later try to do operational
// discovery without it timing out.
ExtendFailsafeBeforeNetworkEnable(proxy, params, step);
break;
case CommissioningStage::kWiFiNetworkEnable: {
if (!params.GetWiFiCredentials().HasValue())
{
ChipLogError(Controller, "No wifi credentials specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
NetworkCommissioning::Commands::ConnectNetwork::Type request;
request.networkID = params.GetWiFiCredentials().Value().ssid;
request.breadcrumb.Emplace(breadcrumb);
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogProgress(Controller, "SendCommand kWiFiNetworkEnable, supportsConcurrentConnection=%s",
params.GetSupportsConcurrentConnection().HasValue()
? (params.GetSupportsConcurrentConnection().Value() ? "true" : "false")
: "missing");
err = SendCommissioningCommand(proxy, request, OnConnectNetworkResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send WiFi ConnectNetwork command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kThreadNetworkEnable: {
ByteSpan extendedPanId;
chip::Thread::OperationalDataset operationalDataset;
if (!params.GetThreadOperationalDataset().HasValue() ||
operationalDataset.Init(params.GetThreadOperationalDataset().Value()) != CHIP_NO_ERROR ||
operationalDataset.GetExtendedPanIdAsByteSpan(extendedPanId) != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Unable to get extended pan ID for thread operational dataset\n");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
NetworkCommissioning::Commands::ConnectNetwork::Type request;
request.networkID = extendedPanId;
request.breadcrumb.Emplace(breadcrumb);
CHIP_ERROR err = SendCommissioningCommand(proxy, request, OnConnectNetworkResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send Thread ConnectNetwork command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kICDGetRegistrationInfo: {
GetPairingDelegate()->OnICDRegistrationInfoRequired();
return;
}
break;
case CommissioningStage::kICDRegistration: {
IcdManagement::Commands::RegisterClient::Type request;
if (!(params.GetICDCheckInNodeId().HasValue() && params.GetICDMonitoredSubject().HasValue() &&
params.GetICDSymmetricKey().HasValue()))
{
ChipLogError(Controller, "No ICD Registration information provided!");
CommissioningStageComplete(CHIP_ERROR_INCORRECT_STATE);
return;
}
request.checkInNodeID = params.GetICDCheckInNodeId().Value();
request.monitoredSubject = params.GetICDMonitoredSubject().Value();
request.key = params.GetICDSymmetricKey().Value();
CHIP_ERROR err =
SendCommissioningCommand(proxy, request, OnICDManagementRegisterClientResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send IcdManagement.RegisterClient command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kEvictPreviousCaseSessions: {
auto scopedPeerId = GetPeerScopedId(proxy->GetDeviceId());
// If we ever had a commissioned device with this node ID before, we may
// have stale sessions to it. Make sure we don't re-use any of those,
// because clearly they are not related to this new device we are
// commissioning. We only care about sessions we might reuse, so just
// clearing the ones associated with our fabric index is good enough and
// we don't need to worry about ExpireAllSessionsOnLogicalFabric.
mSystemState->SessionMgr()->ExpireAllSessions(scopedPeerId);
CommissioningStageComplete(CHIP_NO_ERROR);
return;
}
case CommissioningStage::kFindOperationalForStayActive:
case CommissioningStage::kFindOperationalForCommissioningComplete: {
// If there is an error, CommissioningStageComplete will be called from OnDeviceConnectionFailureFn.
auto scopedPeerId = GetPeerScopedId(proxy->GetDeviceId());
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissioningOperationalSetup);
mSystemState->CASESessionMgr()->FindOrEstablishSession(scopedPeerId, &mOnDeviceConnectedCallback,
&mOnDeviceConnectionFailureCallback
#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
,
/* attemptCount = */ 3, &mOnDeviceConnectionRetryCallback
#endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
);
}
break;
case CommissioningStage::kICDSendStayActive: {
if (!(params.GetICDStayActiveDurationMsec().HasValue()))
{
ChipLogProgress(Controller, "Skipping kICDSendStayActive");
CommissioningStageComplete(CHIP_NO_ERROR);
return;
}
// StayActive Command happens over CASE Connection
IcdManagement::Commands::StayActiveRequest::Type request;
request.stayActiveDuration = params.GetICDStayActiveDurationMsec().Value();
ChipLogError(Controller, "Send ICD StayActive with Duration %u", request.stayActiveDuration);
CHIP_ERROR err =
SendCommissioningCommand(proxy, request, OnICDManagementStayActiveResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send IcdManagement.StayActive command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kSendComplete: {
// CommissioningComplete command happens over the CASE connection.
GeneralCommissioning::Commands::CommissioningComplete::Type request;
CHIP_ERROR err =
SendCommissioningCommand(proxy, request, OnCommissioningCompleteResponse, OnBasicFailure, endpoint, timeout);
if (err != CHIP_NO_ERROR)
{
// We won't get any async callbacks here, so just complete our stage.
ChipLogError(Controller, "Failed to send CommissioningComplete command: %" CHIP_ERROR_FORMAT, err.Format());
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kCleanup:
CleanupCommissioning(proxy, proxy->GetDeviceId(), params.GetCompletionStatus());
break;
case CommissioningStage::kError:
mCommissioningStage = CommissioningStage::kSecurePairing;
break;
case CommissioningStage::kSecurePairing:
break;
}
}
void DeviceCommissioner::ExtendFailsafeBeforeNetworkEnable(DeviceProxy * device, CommissioningParameters & params,
CommissioningStage step)
{
auto * commissioneeDevice = FindCommissioneeDevice(device->GetDeviceId());
if (device != commissioneeDevice)
{
// Not a commissionee device; just return.
ChipLogError(Controller, "Trying to extend fail-safe for an unknown commissionee with device id " ChipLogFormatX64,
ChipLogValueX64(device->GetDeviceId()));
CommissioningStageComplete(CHIP_ERROR_INCORRECT_STATE, CommissioningDelegate::CommissioningReport());
return;
}
// Try to make sure we have at least enough time for our expected
// commissioning bits plus the MRP retries for a Sigma1.
uint16_t failSafeTimeoutSecs = params.GetFailsafeTimerSeconds().ValueOr(kDefaultFailsafeTimeout);
auto sigma1Timeout = CASESession::ComputeSigma1ResponseTimeout(commissioneeDevice->GetPairing().GetRemoteMRPConfig());
uint16_t sigma1TimeoutSecs = std::chrono::duration_cast<System::Clock::Seconds16>(sigma1Timeout).count();
if (UINT16_MAX - failSafeTimeoutSecs < sigma1TimeoutSecs)
{
failSafeTimeoutSecs = UINT16_MAX;
}
else
{
failSafeTimeoutSecs = static_cast<uint16_t>(failSafeTimeoutSecs + sigma1TimeoutSecs);
}
if (!ExtendArmFailSafeInternal(commissioneeDevice, step, failSafeTimeoutSecs, MakeOptional(kMinimumCommissioningStepTimeout),
OnArmFailSafe, OnBasicFailure, /* fireAndForget = */ false))
{
// A false return is fine; we don't want to make the fail-safe shorter here.
CommissioningStageComplete(CHIP_NO_ERROR, CommissioningDelegate::CommissioningReport());
}
}
bool DeviceCommissioner::IsAttestationInformationMissing(const CommissioningParameters & params)
{
if (!params.GetAttestationElements().HasValue() || !params.GetAttestationSignature().HasValue() ||
!params.GetAttestationNonce().HasValue() || !params.GetDAC().HasValue() || !params.GetPAI().HasValue() ||
!params.GetRemoteVendorId().HasValue() || !params.GetRemoteProductId().HasValue())
{
return true;
}
return false;
}
CHIP_ERROR DeviceController::GetCompressedFabricIdBytes(MutableByteSpan & outBytes) const
{
const auto * fabricInfo = GetFabricInfo();
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
return fabricInfo->GetCompressedFabricIdBytes(outBytes);
}
CHIP_ERROR DeviceController::GetRootPublicKey(Crypto::P256PublicKey & outRootPublicKey) const
{
const auto * fabricTable = GetFabricTable();
VerifyOrReturnError(fabricTable != nullptr, CHIP_ERROR_INCORRECT_STATE);
return fabricTable->FetchRootPubkey(mFabricIndex, outRootPublicKey);
}
} // namespace Controller
} // namespace chip