blob: 774f899eefa4d6b7432f2bd0cec14ba43065d2d9 [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.
*
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
// module header, comes first
#include <controller/CHIPDeviceController.h>
#include <app-common/zap-generated/enums.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <controller-clusters/zap-generated/CHIPClusters.h>
#if CONFIG_DEVICE_LAYER
#include <platform/CHIPDeviceLayer.h>
#include <platform/ConfigurationManager.h>
#endif
#include <app/InteractionModelEngine.h>
#include <app/OperationalDeviceProxy.h>
#include <app/util/error-mapping.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/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/ErrorStr.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 <protocols/secure_channel/MessageCounterManager.h>
#include <setup_payload/ManualSetupPayloadGenerator.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <setup_payload/QRCodeSetupPayloadParser.h>
#if CONFIG_NETWORK_LAYER_BLE
#include <ble/BleLayer.h>
#include <transport/raw/BLE.h>
#endif
#include <app/util/af-enums.h>
#include <errno.h>
#include <inttypes.h>
#include <memory>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
using namespace chip::Inet;
using namespace chip::System;
using namespace chip::Transport;
using namespace chip::Credentials;
using namespace chip::app::Clusters;
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
constexpr uint32_t kSessionEstablishmentTimeout = 40 * kMillisecondsPerSecond;
DeviceController::DeviceController()
{
mState = State::NotInitialized;
mStorageDelegate = nullptr;
mPairedDevicesInitialized = false;
}
CHIP_ERROR DeviceController::Init(ControllerInitParams params)
{
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);
mStorageDelegate = params.storageDelegate;
#if CONFIG_NETWORK_LAYER_BLE
VerifyOrReturnError(params.systemState->BleLayer() != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
#endif
VerifyOrReturnError(params.systemState->TransportMgr() != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
ReturnErrorOnFailure(mDNSResolver.Init(params.systemState->UDPEndPointManager()));
mDNSResolver.SetOperationalDelegate(this);
mDNSResolver.SetCommissioningDelegate(this);
RegisterDeviceAddressUpdateDelegate(params.deviceAddressUpdateDelegate);
RegisterDeviceDiscoveryDelegate(params.deviceDiscoveryDelegate);
#endif // CHIP_DEVICE_CONFIG_ENABLE_DNSSD
VerifyOrReturnError(params.operationalCredentialsDelegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mOperationalCredentialsDelegate = params.operationalCredentialsDelegate;
mVendorId = params.controllerVendorId;
if (params.operationalKeypair != nullptr || !params.controllerNOC.empty() || !params.controllerRCAC.empty())
{
ReturnErrorOnFailure(ProcessControllerNOCChain(params));
}
DeviceProxyInitParams deviceInitParams = {
.sessionManager = params.systemState->SessionMgr(),
.exchangeMgr = params.systemState->ExchangeMgr(),
.idAllocator = &mIDAllocator,
.fabricTable = params.systemState->Fabrics(),
.clientPool = &mCASEClientPool,
.mrpLocalConfig = Optional<ReliableMessageProtocolConfig>::Value(GetLocalMRPConfig()),
};
CASESessionManagerConfig sessionManagerConfig = {
.sessionInitParams = deviceInitParams,
.dnsCache = &mDNSCache,
.devicePool = &mDevicePool,
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
.dnsResolver = &mDNSResolver,
#else
.dnsResolver = nullptr,
#endif
};
mCASESessionManager = chip::Platform::New<CASESessionManager>(sessionManagerConfig);
VerifyOrReturnError(mCASESessionManager != nullptr, CHIP_ERROR_NO_MEMORY);
mSystemState = params.systemState->Retain();
mState = State::Initialized;
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceController::ProcessControllerNOCChain(const ControllerInitParams & params)
{
FabricInfo newFabric;
constexpr uint32_t chipCertAllocatedLen = kMaxCHIPCertLength;
chip::Platform::ScopedMemoryBuffer<uint8_t> chipCert;
Credentials::P256PublicKeySpan rootPublicKey;
FabricId fabricId;
ReturnErrorOnFailure(newFabric.SetOperationalKeypair(params.operationalKeypair));
newFabric.SetVendorId(params.controllerVendorId);
ReturnErrorCodeIf(!chipCert.Alloc(chipCertAllocatedLen), CHIP_ERROR_NO_MEMORY);
MutableByteSpan chipCertSpan(chipCert.Get(), chipCertAllocatedLen);
ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerRCAC, chipCertSpan));
ReturnErrorOnFailure(newFabric.SetRootCert(chipCertSpan));
if (params.controllerICAC.empty())
{
ChipLogProgress(Controller, "Intermediate CA is not needed");
}
else
{
chipCertSpan = MutableByteSpan(chipCert.Get(), chipCertAllocatedLen);
ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerICAC, chipCertSpan));
ReturnErrorOnFailure(newFabric.SetICACert(chipCertSpan));
}
chipCertSpan = MutableByteSpan(chipCert.Get(), chipCertAllocatedLen);
ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerNOC, chipCertSpan));
ReturnErrorOnFailure(newFabric.SetNOCCert(chipCertSpan));
ReturnErrorOnFailure(ExtractFabricIdFromCert(chipCertSpan, &fabricId));
ReturnErrorOnFailure(newFabric.GetRootPubkey(rootPublicKey));
mFabricInfo = params.systemState->Fabrics()->FindFabric(rootPublicKey, fabricId);
if (mFabricInfo != nullptr)
{
ReturnErrorOnFailure(mFabricInfo->SetFabricInfo(newFabric));
}
else
{
FabricIndex fabricIndex;
ReturnErrorOnFailure(params.systemState->Fabrics()->AddNewFabric(newFabric, &fabricIndex));
mFabricInfo = params.systemState->Fabrics()->FindFabricWithIndex(fabricIndex);
ReturnErrorCodeIf(mFabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE);
}
mLocalId = mFabricInfo->GetPeerId();
mFabricId = mFabricInfo->GetFabricId();
ChipLogProgress(Controller, "Joined the fabric at index %d. Compressed fabric ID is: 0x" ChipLogFormatX64,
mFabricInfo->GetFabricIndex(), ChipLogValueX64(GetCompressedFabricId()));
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceController::Shutdown()
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
ChipLogDetail(Controller, "Shutting down the controller");
mState = State::NotInitialized;
mStorageDelegate = nullptr;
if (mFabricInfo != nullptr)
{
mFabricInfo->Reset();
}
mSystemState->Release();
mSystemState = nullptr;
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
mDNSResolver.Shutdown();
mDeviceAddressUpdateDelegate = nullptr;
mDeviceDiscoveryDelegate = nullptr;
#endif // CHIP_DEVICE_CONFIG_ENABLE_DNSSD
chip::Platform::Delete(mCASESessionManager);
mCASESessionManager = nullptr;
return CHIP_NO_ERROR;
}
bool DeviceController::DoesDevicePairingExist(const PeerId & deviceId)
{
if (InitializePairedDeviceList() == CHIP_NO_ERROR)
{
return mPairedDevices.Contains(deviceId.GetNodeId());
}
return false;
}
void DeviceController::ReleaseOperationalDevice(NodeId remoteDeviceId)
{
VerifyOrReturn(mState == State::Initialized && mFabricInfo != nullptr,
ChipLogError(Controller, "ReleaseOperationalDevice was called in incorrect state"));
mCASESessionManager->ReleaseSession(mFabricInfo->GetPeerIdForNode(remoteDeviceId));
}
void DeviceController::OnFirstMessageDeliveryFailed(const SessionHandle & session)
{
VerifyOrReturn(mState == State::Initialized,
ChipLogError(Controller, "OnFirstMessageDeliveryFailed was called in incorrect state"));
VerifyOrReturn(session->GetSessionType() == Transport::Session::SessionType::kSecure);
CHIP_ERROR err = UpdateDevice(session->AsSecureSession()->GetPeerNodeId());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller,
"OnFirstMessageDeliveryFailed was called, but UpdateDevice did not succeed (%" CHIP_ERROR_FORMAT ")",
err.Format());
}
}
CHIP_ERROR DeviceController::InitializePairedDeviceList()
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t * buffer = nullptr;
VerifyOrExit(mStorageDelegate != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
if (!mPairedDevicesInitialized)
{
constexpr uint16_t max_size = sizeof(uint64_t) * kNumMaxPairedDevices;
buffer = static_cast<uint8_t *>(chip::Platform::MemoryCalloc(max_size, 1));
uint16_t size = max_size;
VerifyOrExit(buffer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
CHIP_ERROR lookupError = CHIP_NO_ERROR;
PERSISTENT_KEY_OP(static_cast<uint64_t>(0), kPairedDeviceListKeyPrefix, key,
lookupError = mStorageDelegate->SyncGetKeyValue(key, buffer, size));
// It's ok to not have an entry for the Paired Device list. We treat it the same as having an empty list.
if (lookupError != CHIP_ERROR_KEY_NOT_FOUND)
{
VerifyOrExit(size <= max_size, err = CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
err = SetPairedDeviceList(ByteSpan(buffer, size));
SuccessOrExit(err);
}
}
exit:
if (buffer != nullptr)
{
chip::Platform::MemoryFree(buffer);
}
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to initialize the device list with error: %" CHIP_ERROR_FORMAT, err.Format());
}
return err;
}
CHIP_ERROR DeviceController::SetPairedDeviceList(ByteSpan serialized)
{
CHIP_ERROR err = mPairedDevices.Deserialize(serialized);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to recreate the device list with buffer %.*s\n", static_cast<int>(serialized.size()),
serialized.data());
}
else
{
mPairedDevicesInitialized = true;
}
return err;
}
CHIP_ERROR DeviceController::GetPeerAddressAndPort(PeerId peerId, Inet::IPAddress & addr, uint16_t & port)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
Transport::PeerAddress peerAddr;
ReturnErrorOnFailure(mCASESessionManager->GetPeerAddress(peerId, peerAddr));
addr = peerAddr.GetIPAddress();
port = peerAddr.GetPort();
return CHIP_NO_ERROR;
}
void DeviceController::OnPIDReadResponse(void * context, uint16_t value)
{
ChipLogProgress(Controller, "Received PID for the device. Value %d", value);
DeviceController * controller = static_cast<DeviceController *>(context);
controller->mSetupPayload.productID = value;
if (controller->OpenCommissioningWindowInternal() != CHIP_NO_ERROR)
{
OnOpenPairingWindowFailureResponse(context, CHIP_NO_ERROR);
}
}
void DeviceController::OnVIDReadResponse(void * context, VendorId value)
{
ChipLogProgress(Controller, "Received VID for the device. Value %d", to_underlying(value));
DeviceController * controller = static_cast<DeviceController *>(context);
controller->mSetupPayload.vendorID = value;
OperationalDeviceProxy * device =
controller->mCASESessionManager->FindExistingSession(controller->GetPeerIdWithCommissioningWindowOpen());
if (device == nullptr)
{
ChipLogError(Controller, "Could not find device for opening commissioning window");
OnOpenPairingWindowFailureResponse(context, CHIP_NO_ERROR);
return;
}
constexpr EndpointId kBasicClusterEndpoint = 0;
chip::Controller::BasicCluster cluster;
cluster.Associate(device, kBasicClusterEndpoint);
if (cluster.ReadAttribute<app::Clusters::Basic::Attributes::ProductID::TypeInfo>(context, OnPIDReadResponse,
OnVIDPIDReadFailureResponse) != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Could not read PID for opening commissioning window");
OnOpenPairingWindowFailureResponse(context, CHIP_NO_ERROR);
}
}
void DeviceController::OnVIDPIDReadFailureResponse(void * context, CHIP_ERROR error)
{
ChipLogProgress(Controller, "Failed to read VID/PID for the device. error %" CHIP_ERROR_FORMAT, error.Format());
OnOpenPairingWindowFailureResponse(context, error);
}
void DeviceController::OnOpenPairingWindowSuccessResponse(void * context, const chip::app::DataModel::NullObjectType &)
{
ChipLogProgress(Controller, "Successfully opened pairing window on the device");
DeviceController * controller = static_cast<DeviceController *>(context);
if (controller->mCommissioningWindowCallback != nullptr)
{
controller->mCommissioningWindowCallback->mCall(controller->mCommissioningWindowCallback->mContext,
controller->mDeviceWithCommissioningWindowOpen, CHIP_NO_ERROR,
controller->mSetupPayload);
}
}
void DeviceController::OnOpenPairingWindowFailureResponse(void * context, CHIP_ERROR error)
{
ChipLogError(Controller, "Failed to open pairing window on the device. Status %s", chip::ErrorStr(error));
DeviceController * controller = static_cast<DeviceController *>(context);
if (controller->mCommissioningWindowCallback != nullptr)
{
controller->mCommissioningWindowCallback->mCall(controller->mCommissioningWindowCallback->mContext,
controller->mDeviceWithCommissioningWindowOpen, error, SetupPayload());
}
}
CHIP_ERROR DeviceController::ComputePASEVerifier(uint32_t iterations, uint32_t setupPincode, const ByteSpan & salt,
Spake2pVerifier & outVerifier, PasscodeId & outPasscodeId)
{
ReturnErrorOnFailure(PASESession::GeneratePASEVerifier(outVerifier, iterations, salt, /* useRandomPIN= */ false, setupPincode));
outPasscodeId = mPAKEVerifierID++;
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceController::OpenCommissioningWindowWithCallback(NodeId deviceId, uint16_t timeout, uint32_t iteration,
uint16_t discriminator, CommissioningWindowOption option,
chip::Callback::Callback<OnOpenCommissioningWindow> * callback,
bool readVIDPIDAttributes)
{
mSetupPayload = SetupPayload();
switch (option)
{
case CommissioningWindowOption::kOriginalSetupCode:
case CommissioningWindowOption::kTokenWithRandomPIN:
break;
case CommissioningWindowOption::kTokenWithProvidedPIN:
mSetupPayload.setUpPINCode = mSuggestedSetUpPINCode;
break;
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}
mSetupPayload.version = 0;
mSetupPayload.discriminator = discriminator;
mSetupPayload.rendezvousInformation = RendezvousInformationFlags(RendezvousInformationFlag::kOnNetwork);
mCommissioningWindowOption = option;
mCommissioningWindowCallback = callback;
mDeviceWithCommissioningWindowOpen = deviceId;
mCommissioningWindowTimeout = timeout;
mCommissioningWindowIteration = iteration;
if (callback != nullptr && mCommissioningWindowOption != CommissioningWindowOption::kOriginalSetupCode && readVIDPIDAttributes)
{
OperationalDeviceProxy * device = mCASESessionManager->FindExistingSession(GetPeerIdWithCommissioningWindowOpen());
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
constexpr EndpointId kBasicClusterEndpoint = 0;
chip::Controller::BasicCluster cluster;
cluster.Associate(device, kBasicClusterEndpoint);
return cluster.ReadAttribute<app::Clusters::Basic::Attributes::VendorID::TypeInfo>(this, OnVIDReadResponse,
OnVIDPIDReadFailureResponse);
}
return OpenCommissioningWindowInternal();
}
CHIP_ERROR DeviceController::OpenCommissioningWindowInternal()
{
ChipLogProgress(Controller, "OpenCommissioningWindow for device ID %" PRIu64, mDeviceWithCommissioningWindowOpen);
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
OperationalDeviceProxy * device = mCASESessionManager->FindExistingSession(GetPeerIdWithCommissioningWindowOpen());
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
constexpr EndpointId kAdministratorCommissioningClusterEndpoint = 0;
chip::Controller::AdministratorCommissioningCluster cluster;
cluster.Associate(device, kAdministratorCommissioningClusterEndpoint);
if (mCommissioningWindowOption != CommissioningWindowOption::kOriginalSetupCode)
{
// TODO: Salt should be provided as an input or it should be randomly generated when
// the PIN is randomly generated.
const char kSpake2pKeyExchangeSalt[] = "SPAKE2P Key Salt";
ByteSpan salt(Uint8::from_const_char(kSpake2pKeyExchangeSalt), sizeof(kSpake2pKeyExchangeSalt));
bool randomSetupPIN = (mCommissioningWindowOption == CommissioningWindowOption::kTokenWithRandomPIN);
Spake2pVerifier verifier;
ReturnErrorOnFailure(PASESession::GeneratePASEVerifier(verifier, mCommissioningWindowIteration, salt, randomSetupPIN,
mSetupPayload.setUpPINCode));
chip::Spake2pVerifierSerialized serializedVerifier;
MutableByteSpan serializedVerifierSpan(serializedVerifier);
ReturnErrorOnFailure(verifier.Serialize(serializedVerifierSpan));
AdministratorCommissioning::Commands::OpenCommissioningWindow::Type request;
request.commissioningTimeout = mCommissioningWindowTimeout;
request.PAKEVerifier = serializedVerifierSpan;
request.discriminator = mSetupPayload.discriminator;
request.iterations = mCommissioningWindowIteration;
request.salt = salt;
request.passcodeID = mPAKEVerifierID++;
// TODO: What should the timed invoke timeout here be?
uint16_t timedInvokeTimeoutMs = 10000;
ReturnErrorOnFailure(cluster.InvokeCommand(request, this, OnOpenPairingWindowSuccessResponse,
OnOpenPairingWindowFailureResponse, MakeOptional(timedInvokeTimeoutMs)));
char payloadBuffer[QRCodeBasicSetupPayloadGenerator::kMaxQRCodeBase38RepresentationLength];
MutableCharSpan manualCode(payloadBuffer);
ReturnErrorOnFailure(ManualSetupPayloadGenerator(mSetupPayload).payloadDecimalStringRepresentation(manualCode));
ChipLogProgress(Controller, "Manual pairing code: [%s]", payloadBuffer);
MutableCharSpan QRCode(payloadBuffer);
ReturnErrorOnFailure(QRCodeBasicSetupPayloadGenerator(mSetupPayload).payloadBase38Representation(QRCode));
ChipLogProgress(Controller, "SetupQRCode: [%s]", payloadBuffer);
}
else
{
AdministratorCommissioning::Commands::OpenBasicCommissioningWindow::Type request;
request.commissioningTimeout = mCommissioningWindowTimeout;
// TODO: What should the timed invoke timeout here be?
uint16_t timedInvokeTimeoutMs = 10000;
ReturnErrorOnFailure(cluster.InvokeCommand(request, this, OnOpenPairingWindowSuccessResponse,
OnOpenPairingWindowFailureResponse, MakeOptional(timedInvokeTimeoutMs)));
}
return CHIP_NO_ERROR;
}
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
void DeviceController::OnOperationalNodeResolved(const chip::Dnssd::ResolvedNodeData & nodeData)
{
VerifyOrReturn(mState == State::Initialized,
ChipLogError(Controller, "OnOperationalNodeResolved was called in incorrect state"));
mCASESessionManager->OnOperationalNodeResolved(nodeData);
if (mDeviceAddressUpdateDelegate != nullptr)
{
mDeviceAddressUpdateDelegate->OnAddressUpdateComplete(nodeData.mPeerId.GetNodeId(), CHIP_NO_ERROR);
}
};
void DeviceController::OnOperationalNodeResolutionFailed(const chip::PeerId & peer, CHIP_ERROR error)
{
ChipLogError(Controller, "Error resolving node id: %s", ErrorStr(error));
VerifyOrReturn(mState == State::Initialized,
ChipLogError(Controller, "OnOperationalNodeResolutionFailed was called in incorrect state"));
mCASESessionManager->OnOperationalNodeResolutionFailed(peer, error);
if (mDeviceAddressUpdateDelegate != nullptr)
{
mDeviceAddressUpdateDelegate->OnAddressUpdateComplete(peer.GetNodeId(), error);
}
};
#endif // CHIP_DEVICE_CONFIG_ENABLE_DNSSD
ControllerDeviceInitParams DeviceController::GetControllerDeviceInitParams()
{
return ControllerDeviceInitParams{
.transportMgr = mSystemState->TransportMgr(),
.sessionManager = mSystemState->SessionMgr(),
.exchangeMgr = mSystemState->ExchangeMgr(),
.udpEndPointManager = mSystemState->UDPEndPointManager(),
.storageDelegate = mStorageDelegate,
.idAllocator = &mIDAllocator,
.fabricsTable = mSystemState->Fabrics(),
};
}
DeviceCommissioner::DeviceCommissioner() :
mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this),
mDeviceAttestationInformationVerificationCallback(OnDeviceAttestationInformationVerification, this),
mDeviceNOCChainCallback(OnDeviceNOCChainGeneration, this), mSetUpCodePairer(this)
{
mPairingDelegate = nullptr;
mPairedDevicesUpdated = false;
mDeviceBeingCommissioned = nullptr;
}
CHIP_ERROR DeviceCommissioner::Init(CommissionerInitParams params)
{
ReturnErrorOnFailure(DeviceController::Init(params));
params.systemState->SessionMgr()->RegisterRecoveryDelegate(*this);
mPairingDelegate = params.pairingDelegate;
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<DeviceIPTransportMgr>();
ReturnErrorOnFailure(mUdcTransportMgr->Init(Transport::UdpListenParameters(mSystemState->UDPEndPointManager())
.SetAddressType(Inet::IPAddressType::kIPv6)
.SetListenPort((uint16_t)(mUdcListenPort))
#if INET_CONFIG_ENABLE_IPV4
,
Transport::UdpListenParameters(mSystemState->UDPEndPointManager())
.SetAddressType(Inet::IPAddressType::kIPv4)
.SetListenPort((uint16_t)(mUdcListenPort))
#endif // INET_CONFIG_ENABLE_IPV4
));
mUdcServer = chip::Platform::New<UserDirectedCommissioningServer>();
mUdcTransportMgr->SetSessionManager(mUdcServer);
mUdcServer->SetInstanceNameResolver(this);
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
#if CONFIG_NETWORK_LAYER_BLE
mSetUpCodePairer.SetBleLayer(mSystemState->BleLayer());
#endif // CONFIG_NETWORK_LAYER_BLE
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::Shutdown()
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
ChipLogDetail(Controller, "Shutting down the commissioner");
// Check to see if pairing in progress before shutting down
CommissioneeDeviceProxy * device = mDeviceBeingCommissioned;
if (device != nullptr && device->IsSessionSetupInProgress())
{
ChipLogDetail(Controller, "Setup in progress, stopping setup before shutting down");
OnSessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
}
mSystemState->SessionMgr()->UnregisterRecoveryDelegate(*this);
#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
DeviceController::Shutdown();
return CHIP_NO_ERROR;
}
CommissioneeDeviceProxy * DeviceCommissioner::FindCommissioneeDevice(const SessionHandle & session)
{
CommissioneeDeviceProxy * foundDevice = nullptr;
mCommissioneeDevicePool.ForEachActiveObject([&](auto * deviceProxy) {
if (deviceProxy->MatchesSession(session))
{
foundDevice = deviceProxy;
return Loop::Break;
}
return Loop::Continue;
});
return foundDevice;
}
CommissioneeDeviceProxy * DeviceCommissioner::FindCommissioneeDevice(NodeId id)
{
CommissioneeDeviceProxy * foundDevice = nullptr;
mCommissioneeDevicePool.ForEachActiveObject([&](auto * deviceProxy) {
if (deviceProxy->GetDeviceId() == id)
{
foundDevice = deviceProxy;
return Loop::Break;
}
return Loop::Continue;
});
return foundDevice;
}
void DeviceCommissioner::ReleaseCommissioneeDevice(CommissioneeDeviceProxy * device)
{
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::GetConnectedDevice(NodeId deviceId, chip::Callback::Callback<OnDeviceConnected> * onConnection,
chip::Callback::Callback<OnDeviceConnectionFailure> * onFailure)
{
return DeviceController::GetConnectedDevice(deviceId, onConnection, onFailure);
}
CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, const char * setUpCode)
{
return mSetUpCodePairer.PairDevice(remoteDeviceId, setUpCode);
}
CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & params)
{
CommissioningParameters commissioningParams;
return PairDevice(remoteDeviceId, params, commissioningParams);
}
CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & rendezvousParams,
CommissioningParameters & commissioningParams)
{
ReturnErrorOnFailure(EstablishPASEConnection(remoteDeviceId, rendezvousParams));
return Commission(remoteDeviceId, commissioningParams);
}
CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, RendezvousParameters & params)
{
CHIP_ERROR err = CHIP_NO_ERROR;
CommissioneeDeviceProxy * device = nullptr;
Transport::PeerAddress peerAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any);
Messaging::ExchangeContext * exchangeCtxt = nullptr;
Optional<SessionHandle> session;
uint16_t keyID = 0;
VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mDeviceBeingCommissioned == nullptr, err = CHIP_ERROR_INCORRECT_STATE);
err = InitializePairedDeviceList();
SuccessOrExit(err);
// 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 (!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());
}
device = mCommissioneeDevicePool.CreateObject();
VerifyOrExit(device != nullptr, err = CHIP_ERROR_NO_MEMORY);
mDeviceBeingCommissioned = device;
mIsIPRendezvous = (params.GetPeerAddress().GetTransportType() != Transport::Type::kBle);
{
FabricIndex fabricIndex = mFabricInfo != nullptr ? mFabricInfo->GetFabricIndex() : kUndefinedFabricIndex;
device->Init(GetControllerDeviceInitParams(), remoteDeviceId, peerAddress, fabricIndex);
}
if (params.GetPeerAddress().GetTransportType() != Transport::Type::kBle)
{
device->SetAddress(params.GetPeerAddress().GetIPAddress());
}
#if CONFIG_NETWORK_LAYER_BLE
else
{
if (params.HasConnectionObject())
{
SuccessOrExit(err = mSystemState->BleLayer()->NewBleConnectionByObject(params.GetConnectionObject()));
}
else if (params.HasDiscriminator())
{
SuccessOrExit(err = mSystemState->BleLayer()->NewBleConnectionByDiscriminator(params.GetDiscriminator()));
}
else
{
ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT);
}
}
#endif
// TODO: In some cases like PASE over IP, CRA and CRI values from commissionable node service should be used
session = mSystemState->SessionMgr()->CreateUnauthenticatedSession(params.GetPeerAddress(), device->GetMRPConfig());
VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NO_MEMORY);
err = mIDAllocator.Allocate(keyID);
SuccessOrExit(err);
// TODO - Remove use of SetActive/IsActive from CommissioneeDeviceProxy
device->SetActive(true);
// 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);
// TODO: Need to determine how PasscodeId is provided for a non-default case. i.e. ECM
err = device->GetPairing().Pair(params.GetPeerAddress(), params.GetSetupPINCode(), kDefaultCommissioningPasscodeId, keyID,
Optional<ReliableMessageProtocolConfig>::Value(GetLocalMRPConfig()), exchangeCtxt, this);
SuccessOrExit(err);
exit:
if (err != CHIP_NO_ERROR)
{
if (device != nullptr)
{
ReleaseCommissioneeDevice(device);
mDeviceBeingCommissioned = nullptr;
}
}
return err;
}
CHIP_ERROR DeviceCommissioner::Commission(NodeId remoteDeviceId, CommissioningParameters & params)
{
// TODO(cecille): Can we get rid of mDeviceBeingCommissioned and use the remote id instead? Would require storing the
// commissioning stage in the device.
CommissioneeDeviceProxy * device = mDeviceBeingCommissioned;
if (device == nullptr || device->GetDeviceId() != remoteDeviceId ||
(!device->IsSecureConnected() && !device->IsSessionSetupInProgress()))
{
ChipLogError(Controller, "Invalid device for commissioning " ChipLogFormatX64, ChipLogValueX64(remoteDeviceId));
return CHIP_ERROR_INCORRECT_STATE;
}
if (mCommissioningStage != CommissioningStage::kSecurePairing)
{
ChipLogError(Controller, "Commissioning already in progress - not restarting");
return CHIP_ERROR_INCORRECT_STATE;
}
if (!params.GetWiFiCredentials().HasValue() && !params.GetThreadOperationalDataset().HasValue() && !mIsIPRendezvous)
{
ChipLogError(Controller, "Network commissioning parameters are required for BLE auto commissioning.");
return CHIP_ERROR_INVALID_ARGUMENT;
}
ChipLogProgress(Controller, "Commission called for node ID 0x" ChipLogFormatX64, ChipLogValueX64(remoteDeviceId));
mSystemState->SystemLayer()->StartTimer(chip::System::Clock::Milliseconds32(kSessionEstablishmentTimeout),
OnSessionEstablishmentTimeoutCallback, this);
mDefaultCommissioner->SetOperationalCredentialsDelegate(mOperationalCredentialsDelegate);
ReturnErrorOnFailure(mDefaultCommissioner->SetCommissioningParameters(params));
if (device->IsSecureConnected())
{
mDefaultCommissioner->StartCommissioning(this, device);
}
else
{
mRunCommissioningAfterConnection = true;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::GetAttestationChallenge(ByteSpan & attestationChallenge)
{
Optional<SessionHandle> secureSessionHandle;
VerifyOrReturnError(mDeviceBeingCommissioned != nullptr, CHIP_ERROR_INCORRECT_STATE);
secureSessionHandle = mDeviceBeingCommissioned->GetSecureSession();
VerifyOrReturnError(secureSessionHandle.HasValue(), CHIP_ERROR_INCORRECT_STATE);
attestationChallenge = secureSessionHandle.Value()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge();
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::StopPairing(NodeId remoteDeviceId)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
CommissioneeDeviceProxy * device = FindCommissioneeDevice(remoteDeviceId);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
ReleaseCommissioneeDevice(device);
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::UnpairDevice(NodeId remoteDeviceId)
{
// TODO: Send unpairing message to the remote device.
return CHIP_NO_ERROR;
}
void DeviceCommissioner::RendezvousCleanup(CHIP_ERROR status)
{
if (mDeviceBeingCommissioned != 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(mDeviceBeingCommissioned);
mDeviceBeingCommissioned = nullptr;
}
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnPairingComplete(status);
}
}
void DeviceCommissioner::OnSessionEstablishmentError(CHIP_ERROR err)
{
// PASE session establishment failure.
mSystemState->SystemLayer()->CancelTimer(OnSessionEstablishmentTimeoutCallback, this);
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingFailed);
}
RendezvousCleanup(err);
}
void DeviceCommissioner::OnSessionEstablished()
{
// PASE session established.
VerifyOrReturn(mDeviceBeingCommissioned != nullptr, OnSessionEstablishmentError(CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR));
PASESession * pairing = &mDeviceBeingCommissioned->GetPairing();
// TODO: the session should know which peer we are trying to connect to when started
pairing->SetPeerNodeId(mDeviceBeingCommissioned->GetDeviceId());
CHIP_ERROR err = mDeviceBeingCommissioned->SetConnected();
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");
// TODO: Add code to receive CSR from the device, and process the signing request
// For IP rendezvous, this is sent as part of the state machine.
if (mRunCommissioningAfterConnection)
{
mRunCommissioningAfterConnection = false;
mDefaultCommissioner->StartCommissioning(this, mDeviceBeingCommissioned);
}
else
{
ChipLogProgress(Controller, "OnPairingComplete");
mPairingDelegate->OnPairingComplete(CHIP_NO_ERROR);
}
}
CHIP_ERROR DeviceCommissioner::SendCertificateChainRequestCommand(DeviceProxy * device,
Credentials::CertificateType certificateType)
{
ChipLogDetail(Controller, "Sending Certificate Chain request to %p device", device);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
OperationalCredentials::Commands::CertificateChainRequest::Type request;
request.certificateType = certificateType;
return SendCommand<OperationalCredentialsCluster>(device, request, OnCertificateChainResponse,
OnCertificateChainFailureResponse);
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnCertificateChainFailureResponse(void * context, CHIP_ERROR error)
{
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)
{
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)
{
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(
SendCommand<OperationalCredentialsCluster>(device, request, OnAttestationResponse, OnAttestationFailureResponse));
ChipLogDetail(Controller, "Sent Attestation request, waiting for the Attestation Information");
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnAttestationFailureResponse(void * context, CHIP_ERROR error)
{
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)
{
ChipLogProgress(Controller, "Received Attestation Information from the device");
DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationResponse>(AttestationResponse(data.attestationElements, data.signature));
commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
}
void DeviceCommissioner::OnDeviceAttestationInformationVerification(void * context, AttestationVerificationResult result)
{
DeviceCommissioner * commissioner = reinterpret_cast<DeviceCommissioner *>(context);
if (result != AttestationVerificationResult::kSuccess)
{
CommissioningDelegate::CommissioningReport report;
report.Set<AdditionalErrorInfo>(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;
}
else
{
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.
commissioner->CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
return;
}
}
ChipLogProgress(Controller, "Successfully validated 'Attestation Information' command received from the device.");
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
CHIP_ERROR DeviceCommissioner::ValidateAttestationInfo(const Credentials::DeviceAttestationVerifier::AttestationInfo & info)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
DeviceAttestationVerifier * dac_verifier = GetDeviceAttestationVerifier();
dac_verifier->VerifyAttestationInformation(info, &mDeviceAttestationInformationVerificationCallback);
// TODO: Validate Firmware Information
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::SendOperationalCertificateSigningRequestCommand(DeviceProxy * device, const ByteSpan & csrNonce)
{
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(
SendCommand<OperationalCredentialsCluster>(device, request, OnOperationalCertificateSigningRequest, OnCSRFailureResponse));
ChipLogDetail(Controller, "Sent CSR request, waiting for the CSR");
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnCSRFailureResponse(void * context, CHIP_ERROR error)
{
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)
{
ChipLogProgress(Controller, "Received certificate signing request from the device");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
CommissioningDelegate::CommissioningReport report;
report.Set<AttestationResponse>(AttestationResponse(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<AesCcm128KeySpan> ipk,
Optional<NodeId> adminSubject)
{
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
// TODO(#13825): If not passed by the signer, the commissioner should
// provide its current IPK to the commissionee in the AddNOC command.
const uint8_t placeHolderIpk[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
ChipLogProgress(Controller, "Received callback from the CA for NOC Chain generation. Status %s", ErrorStr(status));
if (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() : AesCcm128KeySpan(placeHolderIpk),
adminSubject.HasValue() ? adminSubject.Value() : commissioner->GetNodeId()));
commissioner->CommissioningStageComplete(status, report);
}
CHIP_ERROR DeviceCommissioner::ProcessCSR(DeviceProxy * proxy, const ByteSpan & NOCSRElements,
const ByteSpan & AttestationSignature, ByteSpan dac, ByteSpan csrNonce)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
ChipLogProgress(Controller, "Getting certificate chain for the device from the issuer");
DeviceAttestationVerifier * dacVerifier = GetDeviceAttestationVerifier();
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.
ReturnErrorOnFailure(dacVerifier->VerifyNodeOperationalCSRInformation(NOCSRElements, attestationChallenge, AttestationSignature,
dacPubkey, csrNonce));
mOperationalCredentialsDelegate->SetNodeIdForNextNOCRequest(proxy->GetDeviceId());
if (mFabricInfo != nullptr)
{
mOperationalCredentialsDelegate->SetFabricIdForNextNOCRequest(mFabricInfo->GetFabricId());
}
return mOperationalCredentialsDelegate->GenerateNOCChain(NOCSRElements, AttestationSignature, dac, ByteSpan(), ByteSpan(),
&mDeviceNOCChainCallback);
}
CHIP_ERROR DeviceCommissioner::SendOperationalCertificate(DeviceProxy * device, const ByteSpan & nocCertBuf,
const ByteSpan & icaCertBuf, const AesCcm128KeySpan ipk,
const NodeId adminSubject)
{
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
OperationalCredentials::Commands::AddNOC::Type request;
request.NOCValue = nocCertBuf;
request.ICACValue = chip::Optional<ByteSpan>(icaCertBuf);
request.IPKValue = ipk;
request.caseAdminNode = adminSubject;
request.adminVendorId = mVendorId;
ReturnErrorOnFailure(SendCommand<OperationalCredentialsCluster>(mDeviceBeingCommissioned, request,
OnOperationalCertificateAddResponse, OnAddNOCFailureResponse));
ChipLogProgress(Controller, "Sent operational certificate to the device");
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceCommissioner::ConvertFromOperationalCertStatus(OperationalCredentials::OperationalCertStatus err)
{
using OperationalCredentials::OperationalCertStatus;
switch (err)
{
case OperationalCertStatus::kSuccess:
return CHIP_NO_ERROR;
case OperationalCertStatus::kInvalidPublicKey:
return CHIP_ERROR_INVALID_PUBLIC_KEY;
case OperationalCertStatus::kInvalidNodeOpId:
return CHIP_ERROR_WRONG_NODE_ID;
case OperationalCertStatus::kInvalidNOC:
return CHIP_ERROR_UNSUPPORTED_CERT_FORMAT;
case OperationalCertStatus::kMissingCsr:
return CHIP_ERROR_INCORRECT_STATE;
case OperationalCertStatus::kTableFull:
return CHIP_ERROR_NO_MEMORY;
case OperationalCertStatus::kInsufficientPrivilege:
case OperationalCertStatus::kFabricConflict:
case OperationalCertStatus::kLabelConflict:
return CHIP_ERROR_INVALID_ARGUMENT;
case OperationalCertStatus::kInvalidFabricIndex:
return CHIP_ERROR_INVALID_FABRIC_ID;
}
return CHIP_ERROR_CERT_LOAD_FAILED;
}
void DeviceCommissioner::OnAddNOCFailureResponse(void * context, CHIP_ERROR error)
{
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)
{
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;
CommissioneeDeviceProxy * device = nullptr;
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);
device = commissioner->mDeviceBeingCommissioned;
err = commissioner->OnOperationalCredentialsProvisioningCompletion(device);
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)
{
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ChipLogProgress(Controller, "Sending root certificate to the device");
OperationalCredentials::Commands::AddTrustedRootCertificate::Type request;
request.rootCertificate = rcac;
ReturnErrorOnFailure(
SendCommand<OperationalCredentialsCluster>(device, request, OnRootCertSuccessResponse, OnRootCertFailureResponse));
ChipLogProgress(Controller, "Sent root certificate to the device");
return CHIP_NO_ERROR;
}
void DeviceCommissioner::OnRootCertSuccessResponse(void * context, const chip::app::DataModel::NullObjectType &)
{
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)
{
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(CommissioneeDeviceProxy * device)
{
ChipLogProgress(Controller, "Operational credentials provisioned on device %p", device);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mSystemState->SystemLayer()->CancelTimer(OnSessionEstablishmentTimeoutCallback, this);
mPairedDevices.Insert(device->GetDeviceId());
mPairedDevicesUpdated = true;
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingSuccess);
}
CommissioningStageComplete(CHIP_NO_ERROR);
return CHIP_NO_ERROR;
}
#if CONFIG_NETWORK_LAYER_BLE
CHIP_ERROR 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.
return mSystemState->BleLayer()->CloseAllBleConnections();
}
#endif
void DeviceCommissioner::OnSessionEstablishmentTimeout()
{
VerifyOrReturn(mState == State::Initialized);
VerifyOrReturn(mDeviceBeingCommissioned != nullptr);
CommissioneeDeviceProxy * device = mDeviceBeingCommissioned;
StopPairing(device->GetDeviceId());
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnPairingComplete(CHIP_ERROR_TIMEOUT);
}
}
void DeviceCommissioner::OnSessionEstablishmentTimeoutCallback(System::Layer * aLayer, void * aAppState)
{
static_cast<DeviceCommissioner *>(aAppState)->OnSessionEstablishmentTimeout();
}
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
CHIP_ERROR DeviceCommissioner::DiscoverCommissionableNodes(Dnssd::DiscoveryFilter filter)
{
ReturnErrorOnFailure(SetUpNodeDiscovery());
return mDNSResolver.FindCommissionableNodes(filter);
}
const Dnssd::DiscoveredNodeData * DeviceCommissioner::GetDiscoveredDevice(int idx)
{
return GetDiscoveredNode(idx);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_DNSSD
#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
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
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);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_DNSSD
void 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);
}
void DeviceCommissioner::CommissioningStageComplete(CHIP_ERROR err, CommissioningDelegate::CommissioningReport report)
{
if (mCommissioningDelegate == nullptr)
{
return;
}
report.stageCompleted = mCommissioningStage;
CHIP_ERROR status = mCommissioningDelegate->CommissioningStepFinished(err, report);
if (status != CHIP_NO_ERROR)
{
// Commissioning delegate will only return error if it failed to perform the appropriate commissioning step.
// In this case, we should call back the commissioning complete and call session error
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnCommissioningComplete(mDeviceBeingCommissioned->GetDeviceId(), status);
}
}
}
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
void DeviceCommissioner::OnOperationalNodeResolved(const chip::Dnssd::ResolvedNodeData & nodeData)
{
ChipLogProgress(Controller, "OperationalDiscoveryComplete for device ID 0x" ChipLogFormatX64,
ChipLogValueX64(nodeData.mPeerId.GetNodeId()));
VerifyOrReturn(mState == State::Initialized);
mDNSCache.Insert(nodeData);
mCASESessionManager->FindOrEstablishSession(nodeData.mPeerId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback);
DeviceController::OnOperationalNodeResolved(nodeData);
}
void DeviceCommissioner::OnOperationalNodeResolutionFailed(const chip::PeerId & peer, CHIP_ERROR error)
{
if (mDeviceBeingCommissioned != nullptr)
{
CommissioneeDeviceProxy * device = mDeviceBeingCommissioned;
if (device->GetDeviceId() == peer.GetNodeId() && mCommissioningStage == CommissioningStage::kFindOperational)
{
CommissioningStageComplete(error);
}
}
DeviceController::OnOperationalNodeResolutionFailed(peer, error);
}
#endif
void DeviceCommissioner::OnDeviceConnectedFn(void * context, OperationalDeviceProxy * device)
{
// CASE session established.
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
VerifyOrReturn(commissioner != nullptr, ChipLogProgress(Controller, "Device connected callback with null context. Ignoring"));
if (commissioner->mCommissioningStage == CommissioningStage::kFindOperational)
{
if (commissioner->mDeviceBeingCommissioned != nullptr &&
commissioner->mDeviceBeingCommissioned->GetDeviceId() == device->GetDeviceId())
{
// Let's release the device that's being paired, if pairing was successful,
// and the device is available on the operational network.
commissioner->ReleaseCommissioneeDevice(commissioner->mDeviceBeingCommissioned);
commissioner->mDeviceBeingCommissioned = nullptr;
if (commissioner->mCommissioningDelegate != nullptr)
{
CommissioningDelegate::CommissioningReport report;
report.Set<OperationalNodeFoundData>(OperationalNodeFoundData(device));
commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
}
}
}
else
{
VerifyOrReturn(commissioner->mPairingDelegate != nullptr,
ChipLogProgress(Controller, "Device connected callback with null pairing delegate. Ignoring"));
commissioner->mPairingDelegate->OnPairingComplete(CHIP_NO_ERROR);
}
}
void DeviceCommissioner::OnDeviceConnectionFailureFn(void * context, PeerId peerId, CHIP_ERROR error)
{
// CASE session establishment failed.
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
ChipLogProgress(Controller, "Device connection failed. Error %s", ErrorStr(error));
VerifyOrReturn(commissioner != nullptr,
ChipLogProgress(Controller, "Device connection failure callback with null context. Ignoring"));
VerifyOrReturn(commissioner->mPairingDelegate != nullptr,
ChipLogProgress(Controller, "Device connection failure callback with null pairing delegate. Ignoring"));
commissioner->mCASESessionManager->ReleaseSession(peerId);
if (commissioner->mCommissioningStage == CommissioningStage::kFindOperational &&
commissioner->mCommissioningDelegate != nullptr)
{
commissioner->CommissioningStageComplete(error);
}
else
{
commissioner->mPairingDelegate->OnPairingComplete(error);
}
}
void DeviceCommissioner::SetupCluster(ClusterBase & base, DeviceProxy * proxy, EndpointId endpoint,
Optional<System::Clock::Timeout> timeout)
{
base.Associate(proxy, endpoint);
base.SetCommandTimeout(timeout);
}
// AttributeCache::Callback impl
void DeviceCommissioner::OnDone()
{
CHIP_ERROR err;
CHIP_ERROR return_err = CHIP_NO_ERROR;
ReadCommissioningInfo info;
// Using ForEachAttribute because this attribute can be queried on any endpoint.
err = mAttributeCache->ForEachAttribute(
app::Clusters::GeneralCommissioning::Id, [this, &info](const app::ConcreteAttributePath & path) {
switch (path.mAttributeId)
{
case app::Clusters::GeneralCommissioning::Attributes::BasicCommissioningInfo::Id: {
app::Clusters::GeneralCommissioning::Attributes::BasicCommissioningInfo::TypeInfo::DecodableType basicInfo;
ReturnErrorOnFailure(
this->mAttributeCache->Get<app::Clusters::GeneralCommissioning::Attributes::BasicCommissioningInfo::TypeInfo>(
path, basicInfo));
info.general.recommendedFailsafe = basicInfo.failSafeExpiryLengthSeconds;
}
break;
case app::Clusters::GeneralCommissioning::Attributes::RegulatoryConfig::Id: {
ReturnErrorOnFailure(
this->mAttributeCache->Get<app::Clusters::GeneralCommissioning::Attributes::RegulatoryConfig::TypeInfo>(
path, info.general.currentRegulatoryLocation));
}
break;
case app::Clusters::GeneralCommissioning::Attributes::LocationCapability::Id: {
ReturnErrorOnFailure(
this->mAttributeCache->Get<app::Clusters::GeneralCommissioning::Attributes::LocationCapability::TypeInfo>(
path, info.general.locationCapability));
}
break;
case app::Clusters::GeneralCommissioning::Attributes::Breadcrumb::Id: {
ReturnErrorOnFailure(
this->mAttributeCache->Get<app::Clusters::GeneralCommissioning::Attributes::Breadcrumb::TypeInfo>(
path, info.general.breadcrumb));
}
break;
default:
return CHIP_NO_ERROR;
}
return CHIP_NO_ERROR;
});
// 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;
err = mAttributeCache->ForEachAttribute(app::Clusters::Basic::Id, [this, &info](const app::ConcreteAttributePath & path) {
if (path.mAttributeId != app::Clusters::Basic::Attributes::VendorID::Id &&
path.mAttributeId != app::Clusters::Basic::Attributes::ProductID::Id &&
path.mAttributeId != app::Clusters::Basic::Attributes::SoftwareVersion::Id)
{
// Continue on
return CHIP_NO_ERROR;
}
switch (path.mAttributeId)
{
case app::Clusters::Basic::Attributes::VendorID::Id:
return this->mAttributeCache->Get<app::Clusters::Basic::Attributes::VendorID::TypeInfo>(path, info.basic.vendorId);
case app::Clusters::Basic::Attributes::ProductID::Id:
return this->mAttributeCache->Get<app::Clusters::Basic::Attributes::ProductID::TypeInfo>(path, info.basic.productId);
case app::Clusters::Basic::Attributes::SoftwareVersion::Id:
return this->mAttributeCache->Get<app::Clusters::Basic::Attributes::SoftwareVersion::TypeInfo>(
path, info.basic.softwareVersion);
default:
return CHIP_NO_ERROR;
}
});
// 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;
err = mAttributeCache->ForEachAttribute(
app::Clusters::NetworkCommissioning::Id, [this, &info](const app::ConcreteAttributePath & path) {
if (path.mAttributeId != app::Clusters::NetworkCommissioning::Attributes::FeatureMap::Id)
{
return CHIP_NO_ERROR;
}
TLV::TLVReader reader;
if (this->mAttributeCache->Get(path, reader) == CHIP_NO_ERROR)
{
BitFlags<app::Clusters::NetworkCommissioning::NetworkCommissioningFeature> features;
if (app::DataModel::Decode(reader, features) == CHIP_NO_ERROR)
{
if (features.Has(app::Clusters::NetworkCommissioning::NetworkCommissioningFeature::kWiFiNetworkInterface))
{
info.network.wifi = path.mEndpointId;
}
else if (features.Has(
app::Clusters::NetworkCommissioning::NetworkCommissioningFeature::kThreadNetworkInterface))
{
info.network.thread = path.mEndpointId;
}
else if (features.Has(
app::Clusters::NetworkCommissioning::NetworkCommissioningFeature::kEthernetNetworkInterface))
{
info.network.eth = path.mEndpointId;
}
else
{
// TODO: Gross workaround for the empty feature map on all clusters. Remove.
if (info.network.thread == kInvalidEndpointId)
{
info.network.thread = path.mEndpointId;
}
if (info.network.wifi == kInvalidEndpointId)
{
info.network.wifi = path.mEndpointId;
}
}
}
}
return CHIP_NO_ERROR;
});
return_err = err == CHIP_NO_ERROR ? return_err : err;
if (return_err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Error parsing commissioning information");
}
mAttributeCache = nullptr;
mReadClient = nullptr;
CommissioningDelegate::CommissioningReport report;
report.Set<ReadCommissioningInfo>(info);
CommissioningStageComplete(return_err, report);
}
void DeviceCommissioner::OnArmFailSafe(void * context,
const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data)
{
// TODO: Use errorCode
ChipLogProgress(Controller, "Received ArmFailSafe response");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::OnSetRegulatoryConfigResponse(
void * context, const GeneralCommissioning::Commands::SetRegulatoryConfigResponse::DecodableType & data)
{
// TODO: Use errorCode
ChipLogProgress(Controller, "Received SetRegulatoryConfig response");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::OnNetworkConfigResponse(void * context,
const NetworkCommissioning::Commands::NetworkConfigResponse::DecodableType & data)
{
// TODO: Use networkingStatus
ChipLogProgress(Controller, "Received NetworkConfig response");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::OnConnectNetworkResponse(
void * context, const NetworkCommissioning::Commands::ConnectNetworkResponse::DecodableType & data)
{
// TODO: Use networkingStatus
ChipLogProgress(Controller, "Received ConnectNetwork response");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::OnCommissioningCompleteResponse(
void * context, const GeneralCommissioning::Commands::CommissioningCompleteResponse::DecodableType & data)
{
// TODO: Use errorCode
ChipLogProgress(Controller, "Received CommissioningComplete response");
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
commissioner->CommissioningStageComplete(CHIP_NO_ERROR);
}
void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, CommissioningStage step, CommissioningParameters & params,
CommissioningDelegate * delegate, EndpointId endpoint,
Optional<System::Clock::Timeout> timeout)
{
// For now, we ignore errors coming in from the device since not all commissioning clusters are implemented on the device
// side.
mCommissioningStage = step;
mCommissioningDelegate = delegate;
// 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);
// TODO(cecille): This should be customized per command.
constexpr uint32_t kCommandTimeoutMs = 3000;
switch (step)
{
case CommissioningStage::kArmFailsafe: {
GeneralCommissioning::Commands::ArmFailSafe::Type request;
request.expiryLengthSeconds = params.GetFailsafeTimerSeconds().ValueOr(kDefaultFailsafeTimeout);
request.breadcrumb = breadcrumb;
request.timeoutMs = kCommandTimeoutMs;
ChipLogProgress(Controller, "Arming failsafe (%u seconds)", request.expiryLengthSeconds);
SendCommand<GeneralCommissioningCluster>(proxy, request, OnArmFailSafe, OnBasicFailure, endpoint, timeout);
}
break;
case CommissioningStage::kReadCommissioningInfo: {
ChipLogProgress(Controller, "Sending request for commissioning information");
app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
app::ReadPrepareParams readParams(proxy->GetSecureSession().Value());
app::AttributePathParams readPaths[8];
// 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::Basic::Id, app::Clusters::Basic::Attributes::VendorID::Id);
readPaths[6] =
app::AttributePathParams(endpoint, app::Clusters::Basic::Id, app::Clusters::Basic::Attributes::ProductID::Id);
readPaths[7] =
app::AttributePathParams(endpoint, app::Clusters::Basic::Id, app::Clusters::Basic::Attributes::SoftwareVersion::Id);
readParams.mpAttributePathParamsList = readPaths;
readParams.mAttributePathParamsListSize = 8;
if (timeout.HasValue())
{
readParams.mTimeout = timeout.Value();
}
auto attributeCache = Platform::MakeUnique<app::AttributeCache>(*this);
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 for networking clusters");
CommissioningStageComplete(err);
return;
}
mAttributeCache = std::move(attributeCache);
mReadClient = std::move(readClient);
}
break;
case CommissioningStage::kConfigRegulatory: {
// To set during config phase:
// UTC time
// time zone
// dst offset
// Regulatory config
// TODO(cecille): Set time as well once the time cluster is implemented
// 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::RegulatoryLocationType::kOutdoor);
app::Clusters::GeneralCommissioning::RegulatoryLocationType regulatoryLocation;
// Value is only switchable on the devices with indoor/outdoor capability
if (capability == app::Clusters::GeneralCommissioning::RegulatoryLocationType::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())
{
regulatoryLocation = params.GetDeviceRegulatoryLocation().Value();
ChipLogProgress(Controller, "Setting regulatory location to %u from commissioner override",
static_cast<uint8_t>(regulatoryLocation));
}
else if (params.GetDefaultRegulatoryLocation().HasValue())
{
regulatoryLocation = params.GetDefaultRegulatoryLocation().Value();
ChipLogProgress(Controller, "No regulatory location supplied by controller, leaving as device default (%u)",
static_cast<uint8_t>(regulatoryLocation));
}
else
{
regulatoryLocation = app::Clusters::GeneralCommissioning::RegulatoryLocationType::kOutdoor;
ChipLogProgress(Controller, "No overrride or device regulatory location supplied, setting to outdoor");
}
}
else
{
ChipLogProgress(Controller, "Device does not support configurable regulatory location");
regulatoryLocation = capability;
}
static constexpr size_t kMaxCountryCodeSize = 3;
char countryCodeStr[kMaxCountryCodeSize] = "XX";
size_t actualCountryCodeSize = 2;
#if CONFIG_DEVICE_LAYER
CHIP_ERROR status =
DeviceLayer::ConfigurationMgr().GetCountryCode(countryCodeStr, kMaxCountryCodeSize, actualCountryCodeSize);
#else
CHIP_ERROR status = CHIP_ERROR_NOT_IMPLEMENTED;
#endif
if (status != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Unable to find country code, defaulting to XX");
}
chip::CharSpan countryCode(countryCodeStr, actualCountryCodeSize);
GeneralCommissioning::Commands::SetRegulatoryConfig::Type request;
request.location = regulatoryLocation;
request.countryCode = countryCode;
request.breadcrumb = breadcrumb;
request.timeoutMs = kCommandTimeoutMs;
SendCommand<GeneralCommissioningCluster>(proxy, request, OnSetRegulatoryConfigResponse, OnBasicFailure, endpoint, timeout);
}
break;
case CommissioningStage::kSendPAICertificateRequest:
ChipLogProgress(Controller, "Sending request for PAI certificate");
SendCertificateChainRequestCommand(proxy, CertificateType::kPAI);
break;
case CommissioningStage::kSendDACCertificateRequest:
ChipLogProgress(Controller, "Sending request for DAC certificate");
SendCertificateChainRequestCommand(proxy, CertificateType::kDAC);
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;
}
SendAttestationRequestCommand(proxy, params.GetAttestationNonce().Value());
break;
case CommissioningStage::kAttestationVerification: {
ChipLogProgress(Controller, "Verifying attestation");
if (!params.GetAttestationElements().HasValue() || !params.GetAttestationSignature().HasValue() ||
!params.GetAttestationNonce().HasValue() || !params.GetDAC().HasValue() || !params.GetPAI().HasValue() ||
!params.GetRemoteVendorId().HasValue() || !params.GetRemoteProductId().HasValue())
{
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_INVALID_ARGUMENT);
return;
}
}
break;
case CommissioningStage::kSendOpCertSigningRequest:
if (!params.GetCSRNonce().HasValue())
{
ChipLogError(Controller, "No CSR nonce found");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
SendOperationalCertificateSigningRequestCommand(proxy, params.GetCSRNonce().Value());
break;
case CommissioningStage::kGenerateNOCChain: {
if (!params.GetNOCChainGenerationParameters().HasValue() || !params.GetDAC().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.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());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Error sending trusted root certificate: %s", err.AsString());
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.GetIcac().HasValue() || !params.GetIpk().HasValue() ||
!params.GetAdminSubject().HasValue())
{
ChipLogError(Controller, "AddNOC contents not specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
ChipLogProgress(Controller, "Sending operational certificate chain to the device");
SendOperationalCertificate(proxy, params.GetNoc().Value(), params.GetIcac().Value(), params.GetIpk().Value(),
params.GetAdminSubject().Value());
break;
case CommissioningStage::kConfigACL:
// TODO: Implement
break;
case CommissioningStage::kWiFiNetworkSetup: {
if (!params.GetWiFiCredentials().HasValue())
{
ChipLogError(Controller, "No wifi credentials specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
ChipLogProgress(Controller, "Adding wifi network");
NetworkCommissioning::Commands::AddOrUpdateWiFiNetwork::Type request;
request.ssid = params.GetWiFiCredentials().Value().ssid;
request.credentials = params.GetWiFiCredentials().Value().credentials;
request.breadcrumb = breadcrumb;
SendCommand<NetworkCommissioningCluster>(proxy, request, OnNetworkConfigResponse, OnBasicFailure, endpoint, timeout);
}
break;
case CommissioningStage::kThreadNetworkSetup: {
if (!params.GetThreadOperationalDataset().HasValue())
{
ChipLogError(Controller, "No thread credentials specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
ChipLogProgress(Controller, "Adding thread network");
NetworkCommissioning::Commands::AddOrUpdateThreadNetwork::Type request;
request.operationalDataset = params.GetThreadOperationalDataset().Value();
request.breadcrumb = breadcrumb;
SendCommand<NetworkCommissioningCluster>(proxy, request, OnNetworkConfigResponse, OnBasicFailure, endpoint, timeout);
}
break;
case CommissioningStage::kWiFiNetworkEnable: {
if (!params.GetWiFiCredentials().HasValue())
{
ChipLogError(Controller, "No wifi credentials specified");
CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT);
return;
}
ChipLogProgress(Controller, "Enabling wifi network");
NetworkCommissioning::Commands::ConnectNetwork::Type request;
request.networkID = params.GetWiFiCredentials().Value().ssid;
request.breadcrumb = breadcrumb;
SendCommand<NetworkCommissioningCluster>(proxy, request, OnConnectNetworkResponse, OnBasicFailure, endpoint, timeout);
}
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;
}
ChipLogProgress(Controller, "Enabling thread network");
NetworkCommissioning::Commands::ConnectNetwork::Type request;
request.networkID = extendedPanId;
request.breadcrumb = breadcrumb;
SendCommand<NetworkCommissioningCluster>(proxy, request, OnConnectNetworkResponse, OnBasicFailure, endpoint, timeout);
}
break;
case CommissioningStage::kFindOperational: {
CHIP_ERROR err = UpdateDevice(proxy->GetDeviceId());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Unable to proceed to operational discovery\n");
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kSendComplete: {
ChipLogProgress(Controller, "Calling commissioning complete");
GeneralCommissioning::Commands::CommissioningComplete::Type request;
SendCommand<NetworkCommissioningCluster>(proxy, request, OnCommissioningCompleteResponse, OnBasicFailure, endpoint,
timeout);
}
break;
case CommissioningStage::kCleanup:
ChipLogProgress(Controller, "Rendezvous cleanup");
if (mPairingDelegate != nullptr)
{
mPairingDelegate->OnCommissioningComplete(proxy->GetDeviceId(), params.GetCompletionStatus());
}
CommissioningStageComplete(CHIP_NO_ERROR);
mCommissioningStage = CommissioningStage::kSecurePairing;
break;
case CommissioningStage::kError:
mCommissioningStage = CommissioningStage::kSecurePairing;
break;
case CommissioningStage::kSecurePairing:
break;
}
} // namespace Controller
} // namespace Controller
} // namespace chip