diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index 1e37cf61f923b3..05a9d5678c622a 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -76,6 +76,8 @@ shared_library("ChipDeviceCtrl") { "chip/crypto/p256keypair.cpp", "chip/crypto/p256keypair.h", "chip/discovery/NodeResolution.cpp", + "chip/icd/CheckInDelegate.cpp", + "chip/icd/CheckInDelegate.h", "chip/interaction_model/Delegate.cpp", "chip/interaction_model/Delegate.h", "chip/internal/ChipThreadWork.cpp", @@ -121,7 +123,7 @@ shared_library("ChipDeviceCtrl") { public_deps = [ "${chip_root}/src/app", - "${chip_root}/src/app/icd/client:manager", + "${chip_root}/src/app/icd/client:handler", "${chip_root}/src/app/server", "${chip_root}/src/credentials:default_attestation_verifier", "${chip_root}/src/lib", @@ -251,6 +253,7 @@ chip_python_wheel_action("chip-core") { "chip/discovery/library_handle.py", "chip/discovery/types.py", "chip/exceptions/__init__.py", + "chip/icd/__init__.py", "chip/interaction_model/__init__.py", "chip/interaction_model/delegate.py", "chip/internal/__init__.py", @@ -306,6 +309,7 @@ chip_python_wheel_action("chip-core") { "chip.credentials", "chip.crypto", "chip.utils", + "chip.icd", "chip.discovery", "chip.exceptions", "chip.internal", diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index 00d8c67dc2e20e..f946cdc0321ba4 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -42,9 +42,9 @@ #include #include -#include #include #include +#include #include #include #include @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -103,10 +104,9 @@ chip::Platform::ScopedMemoryBuffer sDefaultNTPBuf; app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type sDSTBuf; app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type sTimeZoneBuf; chip::Platform::ScopedMemoryBuffer sTimeZoneNameBuf; -chip::Controller::CommissioningParameters sCommissioningParameters; - } // namespace +chip::Controller::CommissioningParameters sCommissioningParameters; chip::app::DefaultICDClientStorage sICDClientStorage; chip::Controller::ScriptPairingDeviceDiscoveryDelegate sPairingDeviceDiscoveryDelegate; chip::Credentials::GroupDataProviderImpl sGroupDataProvider; @@ -118,6 +118,7 @@ chip::Crypto::RawKeySessionKeystore sSessionKeystore; // knowing its id, because the ID can be learned on the first response that is received. chip::NodeId kDefaultLocalDeviceId = chip::kTestControllerNodeId; chip::NodeId kRemoteDeviceId = chip::kTestDeviceNodeId; +uint8_t sICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length]; extern "C" { PyChipError pychip_DeviceController_StackInit(Controller::Python::StorageAdapter * storageAdapter, bool enableServerInteractions); @@ -147,6 +148,9 @@ PyChipError pychip_DeviceController_SetDSTOffset(int32_t offset, uint64_t validS PyChipError pychip_DeviceController_SetDefaultNtp(const char * defaultNTP); PyChipError pychip_DeviceController_SetTrustedTimeSource(chip::NodeId nodeId, chip::EndpointId endpoint); PyChipError pychip_DeviceController_SetCheckMatchingFabric(bool check); +PyChipError pychip_DeviceController_SetIcdRegistrationParameters(chip::Controller::DeviceCommissioner * devCtrl, bool enabled, + uint8_t * icdSymmetricKeyOrNull, uint64_t icdCheckInNodeIdOrZero, + uint64_t icdMonitoredSubjectOrZero, uint32_t icdStayActiveMsec); PyChipError pychip_DeviceController_ResetCommissioningParameters(); PyChipError pychip_DeviceController_CloseSession(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid); PyChipError pychip_DeviceController_EstablishPASESessionIP(chip::Controller::DeviceCommissioner * devCtrl, const char * peerAddrStr, @@ -266,6 +270,8 @@ PyChipError pychip_DeviceController_StackInit(Controller::Python::StorageAdapter sICDClientStorage.Init(storageAdapter, &sSessionKeystore); + PyChipCheckInDelegate::GetInstance().Init(&sICDClientStorage, chip::app::InteractionModelEngine::GetInstance()); + sGroupDataProvider.SetStorageDelegate(storageAdapter); sGroupDataProvider.SetSessionKeystore(factoryParams.sessionKeystore); PyReturnErrorOnFailure(ToPyChipError(sGroupDataProvider.Init())); @@ -560,6 +566,46 @@ PyChipError pychip_DeviceController_SetCheckMatchingFabric(bool check) return ToPyChipError(CHIP_NO_ERROR); } +PyChipError pychip_DeviceController_SetIcdRegistrationParameters(chip::Controller::DeviceCommissioner * devCtrl, bool enabled, + uint8_t * icdSymmetricKeyOrNull, uint64_t icdCheckInNodeIdOrZero, + uint64_t icdMonitoredSubjectOrZero, uint32_t icdStayActiveMsec) +{ + if (!enabled) + { + return ToPyChipError(CHIP_NO_ERROR); + } + + sCommissioningParameters.SetICDRegistrationStrategy(ICDRegistrationStrategy::kBeforeComplete); + + if (icdSymmetricKeyOrNull != nullptr) + { + memcpy(sICDSymmetricKey, icdSymmetricKeyOrNull, sizeof(sICDSymmetricKey)); + } + else + { + chip::Crypto::DRBG_get_bytes(sICDSymmetricKey, sizeof(sICDSymmetricKey)); + } + if (icdCheckInNodeIdOrZero == 0) + { + icdCheckInNodeIdOrZero = devCtrl->GetNodeId(); + } + if (icdMonitoredSubjectOrZero == 0) + { + icdMonitoredSubjectOrZero = icdCheckInNodeIdOrZero; + } + // These Optionals must have values now. + // The commissioner will verify these values. + sCommissioningParameters.SetICDSymmetricKey(ByteSpan(sICDSymmetricKey)); + if (icdStayActiveMsec != 0) + { + sCommissioningParameters.SetICDStayActiveDurationMsec(icdStayActiveMsec); + } + sCommissioningParameters.SetICDCheckInNodeId(icdCheckInNodeIdOrZero); + sCommissioningParameters.SetICDMonitoredSubject(icdMonitoredSubjectOrZero); + + return ToPyChipError(CHIP_NO_ERROR); +} + PyChipError pychip_DeviceController_ResetCommissioningParameters() { sCommissioningParameters = CommissioningParameters(); diff --git a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp index c1df8125793d02..ddf9b1de87eb10 100644 --- a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp +++ b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp @@ -19,12 +19,17 @@ #include "ChipDeviceController-ScriptDevicePairingDelegate.h" #include "lib/support/TypeTraits.h" +#include #include #include #include #include +extern chip::app::DefaultICDClientStorage sICDClientStorage; +extern chip::Controller::CommissioningParameters sCommissioningParameters; +extern uint8_t sICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length]; + namespace chip { namespace Controller { @@ -180,5 +185,40 @@ ScriptDevicePairingDelegate::GetOpenWindowCallback(Controller::CommissioningWind return &mOpenWindowCallback; } +void ScriptDevicePairingDelegate::OnICDRegistrationComplete(NodeId nodeId, uint32_t icdCounter) +{ + app::ICDClientInfo clientInfo; + clientInfo.peer_node = ScopedNodeId(nodeId, mFabricIndex); + clientInfo.monitored_subject = sCommissioningParameters.GetICDMonitoredSubject().Value(); + clientInfo.start_icd_counter = icdCounter; + + CHIP_ERROR err = sICDClientStorage.SetKey(clientInfo, ByteSpan(sICDSymmetricKey)); + if (err == CHIP_NO_ERROR) + { + err = sICDClientStorage.StoreEntry(clientInfo); + } + + if (err != CHIP_NO_ERROR) + { + sICDClientStorage.RemoveKey(clientInfo); + ChipLogError(chipTool, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId), + err.AsString()); + return; + } + + ChipLogProgress(Zcl, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId)); + ChipLogProgress(Zcl, + "ICD Registration Complete for device " ChipLogFormatX64 " / Check-In NodeID: " ChipLogFormatX64 + " / Monitored Subject: " ChipLogFormatX64 " / ICDCounter %u", + ChipLogValueX64(nodeId), ChipLogValueX64(sCommissioningParameters.GetICDCheckInNodeId().Value()), + ChipLogValueX64(clientInfo.monitored_subject), icdCounter); +} + +void ScriptDevicePairingDelegate::OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration) +{ + ChipLogProgress(Zcl, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u", + ChipLogValueX64(deviceId), promisedActiveDuration); +} + } // namespace Controller } // namespace chip diff --git a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h index 2740b6eb85e983..52c8498dd68f62 100644 --- a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h +++ b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h @@ -68,11 +68,14 @@ class ScriptDevicePairingDelegate final : public Controller::DevicePairingDelega void OnCommissioningFailure(PeerId peerId, CHIP_ERROR error, CommissioningStage stageFailed, Optional additionalErrorInfo) override; void OnCommissioningStatusUpdate(PeerId peerId, CommissioningStage stageCompleted, CHIP_ERROR error) override; + void OnICDRegistrationComplete(NodeId deviceId, uint32_t icdCounter) override; + void OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration) override; void OnFabricCheck(NodeId matchingNodeId) override; Callback::Callback * GetOpenWindowCallback(Controller::CommissioningWindowOpener * context); void OnOpenCommissioningWindow(NodeId deviceId, CHIP_ERROR status, SetupPayload payload); void SetExpectingPairingComplete(bool value) { expectingPairingComplete = value; } + void SetFabricIndex(FabricIndex fabricIndex) { mFabricIndex = fabricIndex; } private: DevicePairingDelegate_OnPairingCompleteFunct mOnPairingCompleteCallback = nullptr; @@ -85,6 +88,7 @@ class ScriptDevicePairingDelegate final : public Controller::DevicePairingDelega Callback::Callback mOpenWindowCallback; Controller::CommissioningWindowOpener * mWindowOpener = nullptr; bool expectingPairingComplete = false; + FabricIndex mFabricIndex; }; } // namespace Controller diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp index a78b7d51b6471d..427ee9be46ba3c 100644 --- a/src/controller/python/OpCredsBinding.cpp +++ b/src/controller/python/OpCredsBinding.cpp @@ -572,6 +572,7 @@ PyChipError pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Co VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err)); sICDClientStorage.UpdateFabricList(devCtrl->GetFabricIndex()); + pairingDelegate->SetFabricIndex(devCtrl->GetFabricIndex()); *outDevCtrl = devCtrl.release(); *outPairingDelegate = pairingDelegate.release(); diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index d63a3772e62dc6..d2bee1d75b2d74 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -1631,6 +1631,11 @@ def _InitLib(self): self._dmLib.pychip_DeviceController_SetCheckMatchingFabric.restype = PyChipError self._dmLib.pychip_DeviceController_SetCheckMatchingFabric.argtypes = [c_bool] + self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters.restype = PyChipError + self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters.argtypes = [ + c_void_p, c_bool, c_char_p, c_uint64, c_uint64, c_uint32 + ] + self._dmLib.pychip_DeviceController_ResetCommissioningParameters.restype = PyChipError self._dmLib.pychip_DeviceController_ResetCommissioningParameters.argtypes = [] @@ -1979,6 +1984,27 @@ def SetCheckMatchingFabric(self, check: bool): lambda: self._dmLib.pychip_DeviceController_SetCheckMatchingFabric(check) ).raise_on_error() + def SetIcdRegistrationParameters(self, symmetricKey: bytes = None, checkInNodeId: int = 0, monitoredSubject: int = 0, stayActiveMs: int = 0): + if symmetricKey is not None: + if len(symmetricKey) != 16: + raise ValueError("symmetricKey should be 16 bytes") + if not checkInNodeId: + checkInNodeId = self._nodeId + if not monitoredSubject: + monitoredSubject = checkInNodeId + + self.CheckIsActive() + self._ChipStack.Call( + lambda: self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters( + self.devCtrl, True, symmetricKey, checkInNodeId, monitoredSubject, stayActiveMs) + ).raise_on_error() + + def DisableIcdRegistration(self): + self.CheckIsActive() + self._ChipStack.Call( + lambda: self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters(self.devCtrl, False, None, 0, 0, 0) + ).raise_on_error() + def GetFabricCheckResult(self) -> int: ''' Returns the fabric check result if SetCheckMatchingFabric was used.''' return self.fabricCheckNodeId diff --git a/src/controller/python/chip/ChipReplStartup.py b/src/controller/python/chip/ChipReplStartup.py index a49638cb99e56d..8aebde46f4e8be 100644 --- a/src/controller/python/chip/ChipReplStartup.py +++ b/src/controller/python/chip/ChipReplStartup.py @@ -94,6 +94,8 @@ def main(): "-d", "--debug", help="Set default logging level to debug.", action="store_true") parser.add_argument( "-t", "--trust-store", help="Path to the PAA trust store.", action="store", default="./credentials/development/paa-root-certs") + parser.add_argument( + "-s", "--server-interactions", help="Enable server interactions.", action="store_true") args = parser.parse_args() if not os.path.exists(args.trust_store): @@ -137,7 +139,7 @@ def main(): ReplInit(args.debug) - chipStack = ChipStack(persistentStoragePath=args.storagepath, enableServerInteractions=False) + chipStack = ChipStack(persistentStoragePath=args.storagepath, enableServerInteractions=args.server_interactions) certificateAuthorityManager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack, chipStack.GetStorageManager()) certificateAuthorityManager.LoadAuthoritiesFromStorage() diff --git a/src/controller/python/chip/icd/CheckInDelegate.cpp b/src/controller/python/chip/icd/CheckInDelegate.cpp new file mode 100644 index 00000000000000..a6f8f71a78afb3 --- /dev/null +++ b/src/controller/python/chip/icd/CheckInDelegate.cpp @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CheckInDelegate.h" + +using namespace ::chip; +using namespace ::chip::app; + +PyChipCheckInDelegate PyChipCheckInDelegate::sInstance; + +void PyChipCheckInDelegate::OnCheckInComplete(const ICDClientInfo & clientInfo) +{ + DefaultCheckInDelegate::OnCheckInComplete(clientInfo); + + if (mCallback != nullptr) + { + mCallback(clientInfo.peer_node.GetFabricIndex(), clientInfo.peer_node.GetNodeId()); + } +} + +void pychip_CheckInDelegate_SetOnCheckInCompleteCallback(PyChipCheckInDelegate::OnCheckInCompleteCallback * callback) +{ + PyChipCheckInDelegate::GetInstance().SetOnCheckInCompleteCallback(callback); +} diff --git a/src/controller/python/chip/icd/CheckInDelegate.h b/src/controller/python/chip/icd/CheckInDelegate.h new file mode 100644 index 00000000000000..29b0ac8be8a6fb --- /dev/null +++ b/src/controller/python/chip/icd/CheckInDelegate.h @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class PyChipCheckInDelegate : public chip::app::DefaultCheckInDelegate +{ +public: + using OnCheckInCompleteCallback = void(uint8_t fabricIndex, uint64_t nodeId); + + virtual ~PyChipCheckInDelegate() = default; + + void OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo) override; + + void SetOnCheckInCompleteCallback(OnCheckInCompleteCallback * callback) { mCallback = callback; } + + static PyChipCheckInDelegate & GetInstance() { return sInstance; } + +private: + static PyChipCheckInDelegate sInstance; + + OnCheckInCompleteCallback * mCallback; +}; diff --git a/src/controller/python/chip/icd/__init__.py b/src/controller/python/chip/icd/__init__.py new file mode 100644 index 00000000000000..653461f5ab35d0 --- /dev/null +++ b/src/controller/python/chip/icd/__init__.py @@ -0,0 +1,74 @@ +# +# Copyright (c) 2024 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from ctypes import CFUNCTYPE, c_uint8, c_uint64 +from dataclasses import dataclass +from threading import Condition, Event, Lock +from typing import Callable + +from chip import native + +_OnCheckInCompleteFunct = CFUNCTYPE(None, c_uint8, c_uint64) + +_OnCheckInCompleteWaitListLock = Lock() +_OnCheckInCompleteWaitList = dict() + + +@dataclass +class OnCheckInCompleteParams: + fabricIndex: int + nodeId: int + + +@_OnCheckInCompleteFunct +def _OnCheckInComplete(fabricIndex: int, nodeId: int): + callbacks = list() + with _OnCheckInCompleteWaitListLock: + callbacks = list(_OnCheckInCompleteWaitList.get((fabricIndex, nodeId), set())) + + for callback in callbacks: + callback(OnCheckInCompleteParams(fabricIndex, nodeId)) + + +_initialized = False + + +def _ensureInit(): + global _initialized + + if _initialized: + return + + libraryHandle = native.GetLibraryHandle() + libraryHandle.pychip_CheckInDelegate_SetOnCheckInCompleteCallback.restype = None + libraryHandle.pychip_CheckInDelegate_SetOnCheckInCompleteCallback.argtypes = [_OnCheckInCompleteFunct] + + libraryHandle.pychip_CheckInDelegate_SetOnCheckInCompleteCallback(_OnCheckInComplete) + + _initialized = True + + +def AddOnActiveCallback(fabricIndex: int, nodeId: int, callback: Callable[None, [OnCheckInCompleteParams]]): + with _OnCheckInCompleteWaitListLock: + waitList = _OnCheckInCompleteWaitList.get((fabricIndex, nodeId), set()) + waitList.add(callback) + _OnCheckInCompleteWaitList[(fabricIndex, nodeId)] = waitList + + +def RemoveOnActiveCallback(fabricIndex: int, nodeId: int, callback: Callable[None, [OnCheckInCompleteParams]]): + with _OnCheckInCompleteWaitListLock: + _OnCheckInCompleteWaitList.get((fabricIndex, nodeId), set()).remove(callback)