| /* |
| * |
| * 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/server/Dnssd.h> |
| |
| #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/QRCodeSetupPayloadParser.h> |
| #include <trace/trace.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); |
| |
| ReturnErrorOnFailure(mDNSResolver.Init(params.systemState->UDPEndPointManager())); |
| mDNSResolver.SetCommissioningDelegate(this); |
| RegisterDeviceDiscoveryDelegate(params.deviceDiscoveryDelegate); |
| |
| 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)); |
| |
| if (params.enableServerInteractions) |
| { |
| // |
| // Advertise our operational identity on the network to facilitate discovery by clients that look to |
| // establish CASE with a controller that is also offering server-side capabilities (e.g an OTA provider). |
| // |
| app::DnssdServer::Instance().AdvertiseOperational(); |
| } |
| } |
| |
| 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; |
| |
| if (mFabricInfo != nullptr) |
| { |
| // 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(GetCompressedFabricId()); |
| |
| // 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()->ExpireAllPairingsForFabric(mFabricInfo->GetFabricIndex()); |
| } |
| |
| mStorageDelegate = nullptr; |
| |
| if (mFabricInfo != nullptr) |
| { |
| mFabricInfo->Reset(); |
| } |
| mSystemState->Release(); |
| mSystemState = nullptr; |
| |
| mDNSResolver.Shutdown(); |
| mDeviceDiscoveryDelegate = 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")); |
| mSystemState->CASESessionMgr()->ReleaseSession(mFabricInfo->GetPeerIdForNode(remoteDeviceId)); |
| } |
| |
| CHIP_ERROR DeviceController::DisconnectDevice(NodeId nodeId) |
| { |
| ChipLogProgress(Controller, "Force close session for node 0x%" PRIx64, nodeId); |
| |
| OperationalDeviceProxy * proxy = mSystemState->CASESessionMgr()->FindExistingSession(mFabricInfo->GetPeerIdForNode(nodeId)); |
| if (proxy == nullptr) |
| { |
| ChipLogProgress(Controller, "Attempted to close a session that does not exist."); |
| return CHIP_NO_ERROR; |
| } |
| |
| if (proxy->IsConnected()) |
| { |
| return proxy->Disconnect(); |
| } |
| |
| if (proxy->IsConnecting()) |
| { |
| ChipLogError(Controller, "Attempting to disconnect while connection in progress"); |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| // TODO: logic here is unclear. Possible states are "uninitialized, needs address, initialized" |
| // and disconnecting in those states is unclear (especially for needds-address). |
| ChipLogProgress(Controller, "Disconnect attempt while not in connected/connecting state"); |
| return CHIP_NO_ERROR; |
| } |
| |
| void DeviceController::OnFirstMessageDeliveryFailed(const SessionHandle & session) |
| { |
| if (session->GetSessionType() != Session::SessionType::kSecure) |
| { |
| // Definitely not a CASE session. |
| return; |
| } |
| |
| auto * secureSession = session->AsSecureSession(); |
| if (secureSession->GetSecureSessionType() != SecureSession::Type::kCASE) |
| { |
| // Still not CASE. |
| return; |
| } |
| |
| FabricIndex ourIndex = kUndefinedFabricIndex; |
| CHIP_ERROR err = GetFabricIndex(&ourIndex); |
| if (err != CHIP_NO_ERROR) |
| { |
| // We can't really do CASE, now can we? |
| return; |
| } |
| |
| if (ourIndex != session->GetFabricIndex()) |
| { |
| // Not one of our sessions. |
| return; |
| } |
| |
| err = UpdateDevice(secureSession->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(mSystemState->CASESessionMgr()->GetPeerAddress(peerId, peerAddr)); |
| addr = peerAddr.GetIPAddress(); |
| port = peerAddr.GetPort(); |
| 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{ |
| .transportMgr = mSystemState->TransportMgr(), |
| .sessionManager = mSystemState->SessionMgr(), |
| .exchangeMgr = mSystemState->ExchangeMgr(), |
| .udpEndPointManager = mSystemState->UDPEndPointManager(), |
| .storageDelegate = mStorageDelegate, |
| .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; |
| mDeviceInPASEEstablishment = 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(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->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 = mDeviceInPASEEstablishment; |
| if (device != nullptr && device->IsSessionSetupInProgress()) |
| { |
| ChipLogDetail(Controller, "Setup in progress, stopping setup before shutting down"); |
| OnSessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED); |
| } |
| // TODO: If we have a commissioning step in progress, is there a way to cancel that callback? |
| |
| 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 |
| |
| // Release everything from the commissionee device pool here. DeviceController::Shutdown releases operational. |
| mCommissioneeDevicePool.ReleaseAll(); |
| |
| DeviceController::Shutdown(); |
| return CHIP_NO_ERROR; |
| } |
| |
| CommissioneeDeviceProxy * DeviceCommissioner::FindCommissioneeDevice(const SessionHandle & session) |
| { |
| MATTER_TRACE_EVENT_SCOPE("FindCommissioneeDevice", "DeviceCommissioner"); |
| 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) |
| { |
| MATTER_TRACE_EVENT_SCOPE("FindCommissioneeDevice", "DeviceCommissioner"); |
| 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); |
| // Make sure that there will be no dangling pointer |
| if (mDeviceInPASEEstablishment == device) |
| { |
| mDeviceInPASEEstablishment = nullptr; |
| } |
| } |
| |
| 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) |
| { |
| MATTER_TRACE_EVENT_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); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, const char * setUpCode) |
| { |
| MATTER_TRACE_EVENT_SCOPE("PairDevice", "DeviceCommissioner"); |
| return mSetUpCodePairer.PairDevice(remoteDeviceId, setUpCode, SetupCodePairerBehaviour::kCommission); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & params) |
| { |
| MATTER_TRACE_EVENT_SCOPE("PairDevice", "DeviceCommissioner"); |
| ReturnErrorOnFailure(EstablishPASEConnection(remoteDeviceId, params)); |
| return Commission(remoteDeviceId); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & rendezvousParams, |
| CommissioningParameters & commissioningParams) |
| { |
| MATTER_TRACE_EVENT_SCOPE("PairDevice", "DeviceCommissioner"); |
| ReturnErrorOnFailure(EstablishPASEConnection(remoteDeviceId, rendezvousParams)); |
| return Commission(remoteDeviceId, commissioningParams); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, const char * setUpCode) |
| { |
| MATTER_TRACE_EVENT_SCOPE("EstablishPASEConnection", "DeviceCommissioner"); |
| return mSetUpCodePairer.PairDevice(remoteDeviceId, setUpCode, SetupCodePairerBehaviour::kPaseOnly); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, RendezvousParameters & params) |
| { |
| MATTER_TRACE_EVENT_SCOPE("EstablishPASEConnection", "DeviceCommissioner"); |
| 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; |
| SessionHolder secureSessionHolder; |
| |
| VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrExit(mDeviceInPASEEstablishment == nullptr, err = CHIP_ERROR_INCORRECT_STATE); |
| |
| // This will initialize the commissionee device pool if it has not already been initialized. |
| 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 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()); |
| } |
| |
| device = mCommissioneeDevicePool.CreateObject(); |
| VerifyOrExit(device != nullptr, err = CHIP_ERROR_NO_MEMORY); |
| |
| mDeviceInPASEEstablishment = device; |
| |
| { |
| 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); |
| |
| // 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); |
| |
| err = device->GetPairing().Pair(*mSystemState->SessionMgr(), params.GetPeerAddress(), params.GetSetupPINCode(), |
| Optional<ReliableMessageProtocolConfig>::Value(GetLocalMRPConfig()), exchangeCtxt, this); |
| SuccessOrExit(err); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| if (device != nullptr) |
| { |
| ReleaseCommissioneeDevice(device); |
| } |
| } |
| |
| return err; |
| } |
| |
| CHIP_ERROR DeviceCommissioner::Commission(NodeId remoteDeviceId, CommissioningParameters & params) |
| { |
| if (mDefaultCommissioner == nullptr) |
| { |
| ChipLogError(Controller, "No default commissioner is specified"); |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| ReturnErrorOnFailure(mDefaultCommissioner->SetCommissioningParameters(params)); |
| return Commission(remoteDeviceId); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::Commission(NodeId remoteDeviceId) |
| { |
| MATTER_TRACE_EVENT_SCOPE("Commission", "DeviceCommissioner"); |
| 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 - not restarting"); |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| if (mDefaultCommissioner == nullptr) |
| { |
| ChipLogError(Controller, "No default commissioner is specified"); |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| ChipLogProgress(Controller, "Commission called for node ID 0x" ChipLogFormatX64, ChipLogValueX64(remoteDeviceId)); |
| |
| mSystemState->SystemLayer()->StartTimer(chip::System::Clock::Milliseconds32(kSessionEstablishmentTimeout), |
| OnSessionEstablishmentTimeoutCallback, this); |
| |
| mDefaultCommissioner->SetOperationalCredentialsDelegate(mOperationalCredentialsDelegate); |
| 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 (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) |
| { |
| // PASE session establishment failure. |
| mSystemState->SystemLayer()->CancelTimer(OnSessionEstablishmentTimeoutCallback, this); |
| |
| if (mPairingDelegate != nullptr) |
| { |
| mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingFailed); |
| } |
| |
| RendezvousCleanup(err); |
| } |
| |
| void DeviceCommissioner::OnSessionEstablished() |
| { |
| // 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)); |
| |
| PASESession * pairing = &device->GetPairing(); |
| |
| // TODO: the session should know which peer we are trying to connect to when started |
| pairing->SetPeerNodeId(device->GetDeviceId()); |
| |
| CHIP_ERROR err = device->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"); |
| |
| mPairingDelegate->OnPairingComplete(CHIP_NO_ERROR); |
| |
| if (mRunCommissioningAfterConnection) |
| { |
| mRunCommissioningAfterConnection = false; |
| mDefaultCommissioner->StartCommissioning(this, device); |
| } |
| } |
| |
| CHIP_ERROR DeviceCommissioner::SendCertificateChainRequestCommand(DeviceProxy * device, |
| Credentials::CertificateType certificateType) |
| { |
| MATTER_TRACE_EVENT_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 = certificateType; |
| return SendCommand<OperationalCredentialsCluster>(device, request, OnCertificateChainResponse, |
| OnCertificateChainFailureResponse); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void DeviceCommissioner::OnCertificateChainFailureResponse(void * context, CHIP_ERROR error) |
| { |
| MATTER_TRACE_EVENT_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_EVENT_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) |
| { |
| MATTER_TRACE_EVENT_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( |
| 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) |
| { |
| MATTER_TRACE_EVENT_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_EVENT_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.signature)); |
| commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report); |
| } |
| |
| void DeviceCommissioner::OnDeviceAttestationInformationVerification(void * context, AttestationVerificationResult result) |
| { |
| MATTER_TRACE_EVENT_SCOPE("OnDeviceAttestationInformationVerification", "DeviceCommissioner"); |
| 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; |
| } |
| |
| 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) |
| { |
| MATTER_TRACE_EVENT_SCOPE("ValidateAttestationInfo", "DeviceCommissioner"); |
| 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::ValidateCSR(DeviceProxy * proxy, const ByteSpan & NOCSRElements, |
| const ByteSpan & AttestationSignature, const ByteSpan & dac, const ByteSpan & csrNonce) |
| { |
| MATTER_TRACE_EVENT_SCOPE("ValidateCSR", "DeviceCommissioner"); |
| VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| 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. |
| return dacVerifier->VerifyNodeOperationalCSRInformation(NOCSRElements, attestationChallenge, AttestationSignature, dacPubkey, |
| csrNonce); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::SendOperationalCertificateSigningRequestCommand(DeviceProxy * device, const ByteSpan & csrNonce) |
| { |
| MATTER_TRACE_EVENT_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( |
| 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) |
| { |
| MATTER_TRACE_EVENT_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_EVENT_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<AesCcm128KeySpan> ipk, |
| Optional<NodeId> adminSubject) |
| { |
| MATTER_TRACE_EVENT_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 (!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 (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, const ByteSpan & dac, const ByteSpan & pai, |
| const ByteSpan & csrNonce) |
| { |
| MATTER_TRACE_EVENT_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 (mFabricInfo != nullptr) |
| { |
| mOperationalCredentialsDelegate->SetFabricIdForNextNOCRequest(mFabricInfo->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 AesCcm128KeySpan ipk, |
| const NodeId adminSubject) |
| { |
| MATTER_TRACE_EVENT_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.caseAdminNode = adminSubject; |
| request.adminVendorId = mVendorId; |
| |
| ReturnErrorOnFailure( |
| SendCommand<OperationalCredentialsCluster>(device, 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::kFabricConflict: |
| return CHIP_ERROR_FABRIC_EXISTS; |
| case OperationalCertStatus::kInsufficientPrivilege: |
| 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) |
| { |
| MATTER_TRACE_EVENT_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_EVENT_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) |
| { |
| MATTER_TRACE_EVENT_SCOPE("SendTrustedRootCertificate", "DeviceCommissioner"); |
| 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 &) |
| { |
| MATTER_TRACE_EVENT_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_EVENT_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_EVENT_SCOPE("OnOperationalCredentialsProvisioningCompletion", "DeviceCommissioner"); |
| 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 |
| #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 |
| |
| 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() |
| { |
| // This is called from the session establishment timer. Please see |
| // https://github.com/project-chip/connectedhomeip/issues/14650 |
| VerifyOrReturn(mState == State::Initialized); |
| VerifyOrReturn(mDeviceBeingCommissioned != nullptr); |
| |
| StopPairing(mDeviceBeingCommissioned->GetDeviceId()); |
| |
| if (mPairingDelegate != nullptr) |
| { |
| mPairingDelegate->OnPairingComplete(CHIP_ERROR_TIMEOUT); |
| } |
| } |
| |
| void DeviceCommissioner::OnSessionEstablishmentTimeoutCallback(System::Layer * aLayer, void * aAppState) |
| { |
| static_cast<DeviceCommissioner *>(aAppState)->OnSessionEstablishmentTimeout(); |
| } |
| |
| CHIP_ERROR DeviceCommissioner::DiscoverCommissionableNodes(Dnssd::DiscoveryFilter filter) |
| { |
| ReturnErrorOnFailure(SetUpNodeDiscovery()); |
| return mDNSResolver.FindCommissionableNodes(filter); |
| } |
| |
| const Dnssd::DiscoveredNodeData * 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 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) |
| { |
| // Once this stage is complete, reset mDeviceBeingCommissioned - this will be reset when the delegate calls the next step. |
| MATTER_TRACE_EVENT_SCOPE("CommissioningStageComplete", "DeviceCommissioner"); |
| NodeId nodeId = mDeviceBeingCommissioned->GetDeviceId(); |
| mDeviceBeingCommissioned = nullptr; |
| |
| 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 && mDeviceBeingCommissioned != nullptr) |
| { |
| mPairingDelegate->OnCommissioningComplete(nodeId, status); |
| } |
| } |
| } |
| |
| 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() && |
| commissioner->mCommissioningDelegate != nullptr) |
| { |
| CommissioningDelegate::CommissioningReport report; |
| report.Set<OperationalNodeFoundData>(OperationalNodeFoundData(device)); |
| commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report); |
| } |
| } |
| else if (commissioner->mPairingDelegate != nullptr) |
| { |
| commissioner->mPairingDelegate->OnPairingComplete(CHIP_NO_ERROR); |
| } |
| |
| // Release any CommissioneeDeviceProxies we have here as we now have an OperationalDeviceProxy. |
| CommissioneeDeviceProxy * commissionee = commissioner->FindCommissioneeDevice(device->GetDeviceId()); |
| if (commissionee != nullptr) |
| { |
| commissioner->ReleaseCommissioneeDevice(commissionee); |
| } |
| } |
| |
| 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")); |
| |
| // Ensure that commissioning stage advancement is done based on seeing an error. |
| if (error == CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Device connection failed without a valid error code. Making one up."); |
| error = CHIP_ERROR_INTERNAL; |
| } |
| // TODO: Determine if we really want the PASE session removed here. See #16089. |
| CommissioneeDeviceProxy * commissionee = commissioner->FindCommissioneeDevice(peerId.GetNodeId()); |
| if (commissionee != nullptr) |
| { |
| commissioner->ReleaseCommissioneeDevice(commissionee); |
| } |
| |
| commissioner->mSystemState->CASESessionMgr()->ReleaseSession(peerId); |
| if (commissioner->mCommissioningStage == CommissioningStage::kFindOperational && |
| commissioner->mCommissioningDelegate != nullptr) |
| { |
| commissioner->CommissioningStageComplete(error); |
| } |
| else |
| { |
| commissioner->mPairingDelegate->OnPairingComplete(error); |
| } |
| } |
| |
| // 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) |
| { |
| // 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); |
| 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; |
| |
| // Set the network cluster endpoints first so we can match up the connection times. |
| 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.endpoint = path.mEndpointId; |
| } |
| else if (features.Has( |
| app::Clusters::NetworkCommissioning::NetworkCommissioningFeature::kThreadNetworkInterface)) |
| { |
| info.network.thread.endpoint = path.mEndpointId; |
| } |
| else if (features.Has( |
| app::Clusters::NetworkCommissioning::NetworkCommissioningFeature::kEthernetNetworkInterface)) |
| { |
| info.network.eth.endpoint = path.mEndpointId; |
| } |
| else |
| { |
| // 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) { |
| if (path.mAttributeId != app::Clusters::NetworkCommissioning::Attributes::ConnectMaxTimeSeconds::Id) |
| { |
| return CHIP_NO_ERROR; |
| } |
| app::Clusters::NetworkCommissioning::Attributes::ConnectMaxTimeSeconds::TypeInfo::DecodableArgType time; |
| ReturnErrorOnFailure( |
| this->mAttributeCache->Get<app::Clusters::NetworkCommissioning::Attributes::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; |
| |
| 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) |
| { |
| ChipLogProgress(Controller, "Performing next commissioning step '%s' with completion status = '%s'", StageToString(step), |
| params.GetCompletionStatus().AsString()); |
| |
| // 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; |
| 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: { |
| GeneralCommissioning::Commands::ArmFailSafe::Type request; |
| request.expiryLengthSeconds = params.GetFailsafeTimerSeconds().ValueOr(kDefaultFailsafeTimeout); |
| request.breadcrumb = breadcrumb; |
| 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); |
| // 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); |
| |
| 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 regulatoryConfig; |
| // 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()) |
| { |
| 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::RegulatoryLocationType::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; |
| } |
| 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) |
| { |
| actualCountryCodeSize = 2; |
| memset(countryCodeStr, 'X', actualCountryCodeSize); |
| ChipLogError(Controller, "Unable to find country code, defaulting to %s", countryCodeStr); |
| } |
| chip::CharSpan countryCode(countryCodeStr, actualCountryCodeSize); |
| |
| GeneralCommissioning::Commands::SetRegulatoryConfig::Type request; |
| request.newRegulatoryConfig = regulatoryConfig; |
| request.countryCode = countryCode; |
| request.breadcrumb = breadcrumb; |
| 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::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); |
| } |
| 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()); |
| 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.GetIpk().HasValue() || !params.GetAdminSubject().HasValue()) |
| { |
| ChipLogError(Controller, "AddNOC contents not specified"); |
| CommissioningStageComplete(CHIP_ERROR_INVALID_ARGUMENT); |
| return; |
| } |
| SendOperationalCertificate(proxy, params.GetNoc().Value(), params.GetIcac(), params.GetIpk().Value(), |
| params.GetAdminSubject().Value()); |
| 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); |
| 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; |
| } |
| NetworkCommissioning::Commands::AddOrUpdateThreadNetwork::Type request; |
| request.operationalDataset = params.GetThreadOperationalDataset().Value(); |
| request.breadcrumb.Emplace(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; |
| } |
| NetworkCommissioning::Commands::ConnectNetwork::Type request; |
| request.networkID = params.GetWiFiCredentials().Value().ssid; |
| request.breadcrumb.Emplace(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; |
| } |
| NetworkCommissioning::Commands::ConnectNetwork::Type request; |
| request.networkID = extendedPanId; |
| request.breadcrumb.Emplace(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: { |
| GeneralCommissioning::Commands::CommissioningComplete::Type request; |
| SendCommand<NetworkCommissioningCluster>(proxy, request, OnCommissioningCompleteResponse, OnBasicFailure, endpoint, |
| timeout); |
| } |
| break; |
| case CommissioningStage::kCleanup: |
| 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; |
| } |
| } |
| |
| CHIP_ERROR DeviceController::UpdateDevice(NodeId deviceId) |
| { |
| VerifyOrReturnError(mState == State::Initialized && mFabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| OperationalDeviceProxy * proxy = GetDeviceSession(mFabricInfo->GetPeerIdForNode(deviceId)); |
| VerifyOrReturnError(proxy != nullptr, CHIP_ERROR_NOT_FOUND); |
| |
| return proxy->LookupPeerAddress(); |
| } |
| |
| OperationalDeviceProxy * DeviceController::GetDeviceSession(const PeerId & peerId) |
| { |
| return mSystemState->CASESessionMgr()->FindExistingSession(peerId); |
| } |
| |
| OperationalDeviceProxy * DeviceCommissioner::GetDeviceSession(const PeerId & peerId) |
| { |
| CHIP_ERROR err = mSystemState->CASESessionMgr()->FindOrEstablishSession(peerId, &mOnDeviceConnectedCallback, |
| &mOnDeviceConnectionFailureCallback); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Failed to establish new session: %" CHIP_ERROR_FORMAT, err.Format()); |
| return nullptr; |
| } |
| |
| // session should have been created now, expect this to return non-null |
| return DeviceController::GetDeviceSession(peerId); |
| } |
| |
| } // namespace Controller |
| } // namespace chip |