From 95657500fd0bcc9b8d4efc781a1dfa2e031b938a Mon Sep 17 00:00:00 2001 From: joonhaengHeo <85541460+joonhaengHeo@users.noreply.github.com> Date: Tue, 30 Apr 2024 01:33:51 +0900 Subject: [PATCH] [Android] Implement LIT ICD for subscription (#33152) * Implement android LIT ICD subscribe * Refactoring ChipICDClient * Fix fabric Index, remoteID issue * Restyled by whitespace * Restyled by google-java-format * Restyled by clang-format * Restyled by gn * remove Unused API * Add Kotlin ICD Client Info * Fix build error --------- Co-authored-by: Restyled.io --- .../generators/java/ChipClusters_java.jinja | 4 +- .../several_clusters/java/ChipClusters.java | 4 +- .../java/AndroidDeviceControllerWrapper.cpp | 17 ++-- .../java/AndroidDeviceControllerWrapper.h | 3 - src/controller/java/AndroidICDClient.cpp | 99 +++++++++++++++++++ src/controller/java/AndroidICDClient.h | 32 ++++++ .../java/AndroidInteractionClient.cpp | 29 +++++- .../java/AndroidInteractionClient.h | 5 +- src/controller/java/BUILD.gn | 10 +- .../java/CHIPDeviceController-JNI.cpp | 67 ------------- src/controller/java/CHIPICDClient-JNI.cpp | 29 ++++++ .../java/CHIPInteractionClient-JNI.cpp | 16 ++- src/controller/java/MatterICDClient-JNI.cpp | 29 ++++++ .../java/MatterInteractionClient-JNI.cpp | 16 ++- .../chip/devicecontroller/ChipClusters.java | 4 +- .../ChipDeviceController.java | 73 ++++++++++++-- .../chip/devicecontroller/ChipICDClient.java | 34 +++++++ .../ChipInteractionClient.java | 7 +- .../src/matter/controller/ICDClientInfo.kt | 31 ++++++ .../matter/controller/MatterControllerImpl.kt | 72 +++----------- .../matter/controller/MatterICDClientImpl.kt | 28 ++++++ 21 files changed, 449 insertions(+), 160 deletions(-) create mode 100644 src/controller/java/AndroidICDClient.cpp create mode 100644 src/controller/java/AndroidICDClient.h create mode 100644 src/controller/java/CHIPICDClient-JNI.cpp create mode 100644 src/controller/java/MatterICDClient-JNI.cpp create mode 100644 src/controller/java/src/chip/devicecontroller/ChipICDClient.java create mode 100644 src/controller/java/src/matter/controller/ICDClientInfo.kt create mode 100644 src/controller/java/src/matter/controller/MatterICDClientImpl.kt diff --git a/scripts/py_matter_idl/matter_idl/generators/java/ChipClusters_java.jinja b/scripts/py_matter_idl/matter_idl/generators/java/ChipClusters_java.jinja index b391d09389ac2b..954bd7de57dcbf 100644 --- a/scripts/py_matter_idl/matter_idl/generators/java/ChipClusters_java.jinja +++ b/scripts/py_matter_idl/matter_idl/generators/java/ChipClusters_java.jinja @@ -219,7 +219,9 @@ public class ChipClusters { int maxInterval) { ReportCallbackJni jniCallback = new ReportCallbackJni(callback, callback, callback); ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, attributeId); - ChipInteractionClient.subscribe(0, jniCallback.getCallbackHandle(), devicePtr, Arrays.asList(path), null, null, minInterval, maxInterval, false, true, timeoutMillis.orElse(0L).intValue(), null); + int fabricIndex = ChipInteractionClient.getFabricIndex(devicePtr); + long deviceId = ChipInteractionClient.getRemoteDeviceId(devicePtr); + ChipInteractionClient.subscribe(0, jniCallback.getCallbackHandle(), devicePtr, Arrays.asList(path), null, null, minInterval, maxInterval, false, true, timeoutMillis.orElse(0L).intValue(), null, ChipICDClient.isPeerICDClient(fabricIndex, deviceId)); } protected void invoke( diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/java/ChipClusters.java b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/java/ChipClusters.java index b574271a684521..037870ee206c5a 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/java/ChipClusters.java +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/java/ChipClusters.java @@ -142,7 +142,9 @@ protected void subscribeAttribute( int maxInterval) { ReportCallbackJni jniCallback = new ReportCallbackJni(callback, callback, callback); ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, attributeId); - ChipInteractionClient.subscribe(0, jniCallback.getCallbackHandle(), devicePtr, Arrays.asList(path), null, null, minInterval, maxInterval, false, true, timeoutMillis.orElse(0L).intValue(), null); + int fabricIndex = ChipInteractionClient.getFabricIndex(devicePtr); + long deviceId = ChipInteractionClient.getRemoteDeviceId(devicePtr); + ChipInteractionClient.subscribe(0, jniCallback.getCallbackHandle(), devicePtr, Arrays.asList(path), null, null, minInterval, maxInterval, false, true, timeoutMillis.orElse(0L).intValue(), null, ChipICDClient.isPeerICDClient(fabricIndex, deviceId)); } protected void invoke( diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index c5b3863524e153..1576e5883ca256 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -185,7 +186,7 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( chip::Credentials::SetDeviceAttestationVerifier(GetDefaultDACVerifier(testingRootStore)); } - *errInfoOnFailure = wrapper->mICDClientStorage.Init(wrapperStorage, &wrapper->mSessionKeystore); + *errInfoOnFailure = getICDClientStorage()->Init(wrapperStorage, &wrapper->mSessionKeystore); if (*errInfoOnFailure != CHIP_NO_ERROR) { ChipLogError(Controller, "ICD Client Storage failure"); @@ -401,12 +402,12 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( *errInfoOnFailure = chip::Credentials::SetSingleIpkEpochKey( &wrapper->mGroupDataProvider, wrapper->Controller()->GetFabricIndex(), ipkSpan, compressedFabricIdSpan); - wrapper->getICDClientStorage()->UpdateFabricList(wrapper->Controller()->GetFabricIndex()); + getICDClientStorage()->UpdateFabricList(wrapper->Controller()->GetFabricIndex()); auto engine = chip::app::InteractionModelEngine::GetInstance(); - *errInfoOnFailure = wrapper->mCheckInDelegate.Init(&wrapper->mICDClientStorage, engine); + *errInfoOnFailure = wrapper->mCheckInDelegate.Init(getICDClientStorage(), engine); *errInfoOnFailure = wrapper->mCheckInHandler.Init(DeviceControllerFactory::GetInstance().GetSystemState()->ExchangeMgr(), - &wrapper->mICDClientStorage, &wrapper->mCheckInDelegate, engine); + getICDClientStorage(), &wrapper->mCheckInDelegate, engine); memset(ipkBuffer.data(), 0, ipkBuffer.size()); @@ -725,7 +726,7 @@ void AndroidDeviceControllerWrapper::OnCommissioningComplete(NodeId deviceId, CH if (error != CHIP_NO_ERROR && mDeviceIsICD) { - CHIP_ERROR deleteEntryError = mICDClientStorage.DeleteEntry(ScopedNodeId(deviceId, Controller()->GetFabricIndex())); + CHIP_ERROR deleteEntryError = getICDClientStorage()->DeleteEntry(ScopedNodeId(deviceId, Controller()->GetFabricIndex())); if (deleteEntryError != CHIP_NO_ERROR) { ChipLogError(chipTool, "Failed to delete ICD entry: %" CHIP_ERROR_FORMAT, deleteEntryError.Format()); @@ -1003,10 +1004,10 @@ void AndroidDeviceControllerWrapper::OnICDRegistrationComplete(chip::NodeId icdN ByteSpan symmetricKey = mAutoCommissioner.GetCommissioningParameters().GetICDSymmetricKey().Value(); - err = mICDClientStorage.SetKey(clientInfo, symmetricKey); + err = getICDClientStorage()->SetKey(clientInfo, symmetricKey); if (err == CHIP_NO_ERROR) { - err = mICDClientStorage.StoreEntry(clientInfo); + err = getICDClientStorage()->StoreEntry(clientInfo); } if (err == CHIP_NO_ERROR) @@ -1015,7 +1016,7 @@ void AndroidDeviceControllerWrapper::OnICDRegistrationComplete(chip::NodeId icdN } else { - mICDClientStorage.RemoveKey(clientInfo); + getICDClientStorage()->RemoveKey(clientInfo); ChipLogError(Controller, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(icdNodeId), err.AsString()); } diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index 3acf16963525f5..51beae2d0eba48 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -212,8 +212,6 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel CHIP_ERROR FinishOTAProvider(); - chip::app::DefaultICDClientStorage * getICDClientStorage() { return &mICDClientStorage; } - CHIP_ERROR SetICDCheckInDelegate(jobject checkInDelegate); private: @@ -228,7 +226,6 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel // TODO: This may need to be injected as a SessionKeystore* chip::Crypto::RawKeySessionKeystore mSessionKeystore; - chip::app::DefaultICDClientStorage mICDClientStorage; chip::app::AndroidCheckInDelegate mCheckInDelegate; chip::app::CheckInHandler mCheckInHandler; diff --git a/src/controller/java/AndroidICDClient.cpp b/src/controller/java/AndroidICDClient.cpp new file mode 100644 index 00000000000000..83cb5222576049 --- /dev/null +++ b/src/controller/java/AndroidICDClient.cpp @@ -0,0 +1,99 @@ +/* + * 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. + * + */ + +/** + * @file + * Implementation of ICD Client API for Android Platform + * + */ + +#include "AndroidICDClient.h" + +#include + +chip::app::DefaultICDClientStorage sICDClientStorage; + +jobject getICDClientInfo(JNIEnv * env, const char * icdClientInfoSign, jint jFabricIndex) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + jobject jInfo = nullptr; + chip::app::ICDClientInfo info; + chip::FabricIndex fabricIndex = static_cast(jFabricIndex); + + ChipLogProgress(Controller, "getICDClientInfo(%u) called", fabricIndex); + + err = chip::JniReferences::GetInstance().CreateArrayList(jInfo); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, + ChipLogError(Controller, "CreateArrayList failed!: %" CHIP_ERROR_FORMAT, err.Format())); + + auto iter = getICDClientStorage()->IterateICDClientInfo(); + VerifyOrReturnValue(iter != nullptr, nullptr, ChipLogError(Controller, "IterateICDClientInfo failed!")); + chip::app::DefaultICDClientStorage::ICDClientInfoIteratorWrapper clientInfoIteratorWrapper(iter); + + jmethodID constructor; + jclass infoClass; + chip::JniLocalReferenceScope scope(env); + + err = chip::JniReferences::GetInstance().GetLocalClassRef(env, icdClientInfoSign, infoClass); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, + ChipLogError(Controller, "Find ICDClientInfo class: %" CHIP_ERROR_FORMAT, err.Format())); + + env->ExceptionClear(); + constructor = env->GetMethodID(infoClass, "", "(JJJJ[B[B)V"); + VerifyOrReturnValue(constructor != nullptr, nullptr, ChipLogError(Controller, "Find GetMethodID error!")); + + while (iter->Next(info)) + { + jbyteArray jIcdAesKey = nullptr; + jbyteArray jIcdHmacKey = nullptr; + jobject jICDClientInfo = nullptr; + + if (info.peer_node.GetFabricIndex() != fabricIndex) + { + continue; + } + + err = chip::JniReferences::GetInstance().N2J_ByteArray(env, + info.aes_key_handle.As(), + chip::Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES, jIcdAesKey); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, + ChipLogError(Controller, "ICD AES KEY N2J_ByteArray error!: %" CHIP_ERROR_FORMAT, err.Format())); + + err = chip::JniReferences::GetInstance().N2J_ByteArray( + env, info.hmac_key_handle.As(), + chip::Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES, jIcdHmacKey); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, + ChipLogError(Controller, "ICD HMAC KEY N2J_ByteArray error!: %" CHIP_ERROR_FORMAT, err.Format())); + + jICDClientInfo = (jobject) env->NewObject(infoClass, constructor, static_cast(info.peer_node.GetNodeId()), + static_cast(info.start_icd_counter), static_cast(info.offset), + static_cast(info.monitored_subject), jIcdAesKey, jIcdHmacKey); + + err = chip::JniReferences::GetInstance().AddToList(jInfo, jICDClientInfo); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, + ChipLogError(Controller, "AddToList error!: %" CHIP_ERROR_FORMAT, err.Format())); + } + + return jInfo; +} + +chip::app::DefaultICDClientStorage * getICDClientStorage() +{ + return &sICDClientStorage; +} diff --git a/src/controller/java/AndroidICDClient.h b/src/controller/java/AndroidICDClient.h new file mode 100644 index 00000000000000..e5b929e2a9c5f6 --- /dev/null +++ b/src/controller/java/AndroidICDClient.h @@ -0,0 +1,32 @@ +/* + * 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. + * + */ + +/** + * @file + * Implementation of ICD Client API for Android Platform + * + */ + +#pragma once + +#include +#include + +jobject getICDClientInfo(JNIEnv * env, const char * icdClientInfoSign, jint jFabricIndex); + +chip::app::DefaultICDClientStorage * getICDClientStorage(); diff --git a/src/controller/java/AndroidInteractionClient.cpp b/src/controller/java/AndroidInteractionClient.cpp index 6ab01bc01034fc..84506fddcf23d5 100644 --- a/src/controller/java/AndroidInteractionClient.cpp +++ b/src/controller/java/AndroidInteractionClient.cpp @@ -46,7 +46,7 @@ static CHIP_ERROR ParseDataVersionFilterList(jobject dataVersionFilterList, CHIP_ERROR subscribe(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, jobject dataVersionFilterList, jint minInterval, jint maxInterval, - jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin) + jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin, jboolean isPeerLIT) { chip::DeviceLayer::StackLock lock; CHIP_ERROR err = CHIP_NO_ERROR; @@ -128,6 +128,9 @@ CHIP_ERROR subscribe(JNIEnv * env, jlong handle, jlong callbackHandle, jlong dev params.mEventNumber.SetValue(static_cast(JniReferences::GetInstance().LongToPrimitive(eventMin))); } + params.mIsPeerLIT = (isPeerLIT == JNI_TRUE); + ChipLogProgress(Controller, "Peer ICD type is set to %s", params.mIsPeerLIT ? "LIT-ICD" : "non LIT-ICD"); + if (eventPathList != nullptr) { jint jNumEventPaths = 0; @@ -804,6 +807,30 @@ CHIP_ERROR invoke(JNIEnv * env, jlong handle, jlong callbackHandle, jlong device return err; } +jlong getRemoteDeviceId(jlong devicePtr) +{ + OperationalDeviceProxy * chipDevice = reinterpret_cast(devicePtr); + if (chipDevice == nullptr) + { + ChipLogProgress(Controller, "Could not cast device pointer to Device object"); + return static_cast(chip::kUndefinedNodeId); + } + + return static_cast(chipDevice->GetDeviceId()); +} + +jint getFabricIndex(jlong devicePtr) +{ + OperationalDeviceProxy * chipDevice = reinterpret_cast(devicePtr); + if (chipDevice == nullptr) + { + ChipLogProgress(Controller, "Could not cast device pointer to Device object"); + return static_cast(chip::kUndefinedFabricIndex); + } + + return static_cast(chipDevice->GetPeerScopedNodeId().GetFabricIndex()); +} + /** * Takes objects in attributePathList, converts them to app:AttributePathParams, and appends them to outAttributePathParamsList. */ diff --git a/src/controller/java/AndroidInteractionClient.h b/src/controller/java/AndroidInteractionClient.h index 32ee629856df1c..646ac141a42488 100644 --- a/src/controller/java/AndroidInteractionClient.h +++ b/src/controller/java/AndroidInteractionClient.h @@ -22,7 +22,7 @@ CHIP_ERROR subscribe(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, jobject dataVersionFilterList, jint minInterval, jint maxInterval, - jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin); + jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin, jboolean isPeerLIT); CHIP_ERROR read(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, jobject dataVersionFilterList, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin); CHIP_ERROR write(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributeList, @@ -32,3 +32,6 @@ CHIP_ERROR invoke(JNIEnv * env, jlong handle, jlong callbackHandle, jlong device CHIP_ERROR extendableInvoke(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject invokeElementList, jint timedRequestTimeoutMs, jint imTimeoutMs); CHIP_ERROR shutdownSubscriptions(JNIEnv * env, jlong handle, jobject fabricIndex, jobject peerNodeId, jobject subscriptionId); + +jlong getRemoteDeviceId(jlong devicePtr); +jint getFabricIndex(jlong devicePtr); diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 129ebe6e816339..e22c50cd023788 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -51,11 +51,14 @@ source_set("android_chip_im_jni") { "AndroidConnectionFailureExceptions.h", "AndroidControllerExceptions.cpp", "AndroidControllerExceptions.h", + "AndroidICDClient.cpp", + "AndroidICDClient.h", "AndroidInteractionClient.cpp", "AndroidInteractionClient.h", "BaseCHIPCluster-JNI.cpp", "CHIPAttributeTLVValueDecoder.h", "CHIPEventTLVValueDecoder.h", + "CHIPICDClient-JNI.cpp", "CHIPInteractionClient-JNI.cpp", "CHIPInteractionClient-JNI.h", ] @@ -70,6 +73,7 @@ source_set("android_chip_im_jni") { } deps = [ + "${chip_root}/src/app/icd/client:manager", "${chip_root}/src/lib", "${chip_root}/src/lib/support/jsontlv", "${chip_root}/src/platform", @@ -136,6 +140,7 @@ shared_library("jni") { "DeviceAttestationDelegateBridge.h", "GroupDeviceProxy.h", "MatterCallbacks-JNI.cpp", + "MatterICDClient-JNI.cpp", "MatterInteractionClient-JNI.cpp", ] @@ -431,12 +436,14 @@ kotlin_library("kotlin_matter_controller") { sources = [ "src/matter/controller/CompletionListenerAdapter.kt", "src/matter/controller/ControllerParams.kt", + "src/matter/controller/ICDClientInfo.kt", "src/matter/controller/InteractionClient.kt", "src/matter/controller/InvokeCallback.kt", "src/matter/controller/InvokeCallbackJni.kt", "src/matter/controller/MatterController.kt", "src/matter/controller/MatterControllerException.kt", "src/matter/controller/MatterControllerImpl.kt", + "src/matter/controller/MatterICDClientImpl.kt", "src/matter/controller/Messages.kt", "src/matter/controller/OperationalKeyConfig.kt", "src/matter/controller/ReportCallback.kt", @@ -499,10 +506,12 @@ android_library("android_chip_im") { sources = [ "src/chip/devicecontroller/ChipClusterException.java", "src/chip/devicecontroller/ChipDeviceControllerException.java", + "src/chip/devicecontroller/ChipICDClient.java", "src/chip/devicecontroller/ChipInteractionClient.java", "src/chip/devicecontroller/ExtendableInvokeCallback.java", "src/chip/devicecontroller/ExtendableInvokeCallbackJni.java", "src/chip/devicecontroller/GetConnectedDeviceCallbackJni.java", + "src/chip/devicecontroller/ICDClientInfo.java", "src/chip/devicecontroller/InvokeCallback.java", "src/chip/devicecontroller/InvokeCallbackJni.java", "src/chip/devicecontroller/ReportCallback.java", @@ -600,7 +609,6 @@ android_library("java") { "src/chip/devicecontroller/GroupKeySecurityPolicy.java", "src/chip/devicecontroller/ICDCheckInDelegate.java", "src/chip/devicecontroller/ICDCheckInDelegateWrapper.java", - "src/chip/devicecontroller/ICDClientInfo.java", "src/chip/devicecontroller/ICDDeviceInfo.java", "src/chip/devicecontroller/ICDRegistrationInfo.java", "src/chip/devicecontroller/KeypairDelegate.java", diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index dd034caf2007ba..9b3d10a2bf1d31 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -2154,73 +2154,6 @@ JNI_METHOD(jbyteArray, validateAndExtractCSR)(JNIEnv * env, jclass clazz, jbyteA return javaCsr; } -JNI_METHOD(jobject, getICDClientInfo)(JNIEnv * env, jobject self, jlong handle, jint jFabricIndex) -{ - chip::DeviceLayer::StackLock lock; - - CHIP_ERROR err = CHIP_NO_ERROR; - - jobject jInfo = nullptr; - chip::app::ICDClientInfo info; - chip::FabricIndex fabricIndex = static_cast(jFabricIndex); - - ChipLogProgress(Controller, "getICDClientInfo(%u) called", fabricIndex); - AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); - VerifyOrReturnValue(wrapper != nullptr, nullptr, ChipLogError(Controller, "wrapper is null")); - - err = JniReferences::GetInstance().CreateArrayList(jInfo); - VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, - ChipLogError(Controller, "CreateArrayList failed!: %" CHIP_ERROR_FORMAT, err.Format())); - - auto iter = wrapper->getICDClientStorage()->IterateICDClientInfo(); - VerifyOrReturnValue(iter != nullptr, nullptr, ChipLogError(Controller, "IterateICDClientInfo failed!")); - app::DefaultICDClientStorage::ICDClientInfoIteratorWrapper clientInfoIteratorWrapper(iter); - - jmethodID constructor; - jclass infoClass; - JniLocalReferenceScope scope(env); - - err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/ICDClientInfo", infoClass); - VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, - ChipLogError(Controller, "Find ICDClientInfo class: %" CHIP_ERROR_FORMAT, err.Format())); - - env->ExceptionClear(); - constructor = env->GetMethodID(infoClass, "", "(JJJJ[B[B)V"); - VerifyOrReturnValue(constructor != nullptr, nullptr, ChipLogError(Controller, "Find GetMethodID error!")); - - while (iter->Next(info)) - { - jbyteArray jIcdAesKey = nullptr; - jbyteArray jIcdHmacKey = nullptr; - jobject jICDClientInfo = nullptr; - - if (info.peer_node.GetFabricIndex() != fabricIndex) - { - continue; - } - - err = chip::JniReferences::GetInstance().N2J_ByteArray(env, info.aes_key_handle.As(), - CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES, jIcdAesKey); - VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, - ChipLogError(Controller, "ICD AES KEY N2J_ByteArray error!: %" CHIP_ERROR_FORMAT, err.Format())); - - err = chip::JniReferences::GetInstance().N2J_ByteArray(env, info.hmac_key_handle.As(), - CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES, jIcdHmacKey); - VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, - ChipLogError(Controller, "ICD HMAC KEY N2J_ByteArray error!: %" CHIP_ERROR_FORMAT, err.Format())); - - jICDClientInfo = (jobject) env->NewObject(infoClass, constructor, static_cast(info.peer_node.GetNodeId()), - static_cast(info.start_icd_counter), static_cast(info.offset), - static_cast(info.monitored_subject), jIcdAesKey, jIcdHmacKey); - - err = JniReferences::GetInstance().AddToList(jInfo, jICDClientInfo); - VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, - ChipLogError(Controller, "AddToList error!: %" CHIP_ERROR_FORMAT, err.Format())); - } - - return jInfo; -} - void * IOThreadMain(void * arg) { JNIEnv * env; diff --git a/src/controller/java/CHIPICDClient-JNI.cpp b/src/controller/java/CHIPICDClient-JNI.cpp new file mode 100644 index 00000000000000..b1fb2574c84465 --- /dev/null +++ b/src/controller/java/CHIPICDClient-JNI.cpp @@ -0,0 +1,29 @@ +/* + * 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 "AndroidICDClient.h" + +#include + +#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_chip_devicecontroller_ChipICDClient_##METHOD_NAME + +JNI_METHOD(jobject, getICDClientInfo)(JNIEnv * env, jobject self, jint jFabricIndex) +{ + chip::DeviceLayer::StackLock lock; + + return getICDClientInfo(env, "chip/devicecontroller/ICDClientInfo", jFabricIndex); +} diff --git a/src/controller/java/CHIPInteractionClient-JNI.cpp b/src/controller/java/CHIPInteractionClient-JNI.cpp index 62abf35ddca956..f6d8e0a0093d8f 100644 --- a/src/controller/java/CHIPInteractionClient-JNI.cpp +++ b/src/controller/java/CHIPInteractionClient-JNI.cpp @@ -73,10 +73,10 @@ void AndroidChipInteractionJNI_OnUnload(JavaVM * jvm, void * reserved) JNI_METHOD(void, subscribe) (JNIEnv * env, jobject self, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, jobject dataVersionFilterList, jint minInterval, jint maxInterval, jboolean keepSubscriptions, jboolean isFabricFiltered, - jint imTimeoutMs, jobject eventMin) + jint imTimeoutMs, jobject eventMin, jboolean isPeerLIT) { CHIP_ERROR err = subscribe(env, handle, callbackHandle, devicePtr, attributePathList, eventPathList, dataVersionFilterList, - minInterval, maxInterval, keepSubscriptions, isFabricFiltered, imTimeoutMs, eventMin); + minInterval, maxInterval, keepSubscriptions, isFabricFiltered, imTimeoutMs, eventMin, isPeerLIT); if (err != CHIP_NO_ERROR) { ChipLogError(Controller, "JNI IM Subscribe Error: %" CHIP_ERROR_FORMAT, err.Format()); @@ -138,3 +138,15 @@ JNI_METHOD(void, shutdownSubscriptions) ChipLogError(Controller, "Failed to shutdown subscriptions with Error: %" CHIP_ERROR_FORMAT, err.Format()); } } + +JNI_METHOD(jlong, getRemoteDeviceId) +(JNIEnv * env, jobject self, jlong devicePtr) +{ + return getRemoteDeviceId(devicePtr); +} + +JNI_METHOD(jint, getFabricIndex) +(JNIEnv * env, jobject self, jlong devicePtr) +{ + return getFabricIndex(devicePtr); +} diff --git a/src/controller/java/MatterICDClient-JNI.cpp b/src/controller/java/MatterICDClient-JNI.cpp new file mode 100644 index 00000000000000..45ed4d96c241cb --- /dev/null +++ b/src/controller/java/MatterICDClient-JNI.cpp @@ -0,0 +1,29 @@ +/* + * 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 "AndroidICDClient.h" + +#include + +#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_matter_controller_MatterICDClientImpl_##METHOD_NAME + +JNI_METHOD(jobject, getICDClientInfo)(JNIEnv * env, jobject self, jint jFabricIndex) +{ + chip::DeviceLayer::StackLock lock; + + return getICDClientInfo(env, "matter/controller/ICDClientInfo", jFabricIndex); +} diff --git a/src/controller/java/MatterInteractionClient-JNI.cpp b/src/controller/java/MatterInteractionClient-JNI.cpp index 152e52aedd65d7..5b526a39de68b7 100644 --- a/src/controller/java/MatterInteractionClient-JNI.cpp +++ b/src/controller/java/MatterInteractionClient-JNI.cpp @@ -22,10 +22,10 @@ JNI_METHOD(void, subscribe) (JNIEnv * env, jobject self, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, - jint minInterval, jint maxInterval, jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs) + jint minInterval, jint maxInterval, jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jboolean isPeerLIT) { CHIP_ERROR err = subscribe(env, handle, callbackHandle, devicePtr, attributePathList, eventPathList, nullptr, minInterval, - maxInterval, keepSubscriptions, isFabricFiltered, imTimeoutMs, nullptr); + maxInterval, keepSubscriptions, isFabricFiltered, imTimeoutMs, nullptr, isPeerLIT); if (err != CHIP_NO_ERROR) { ChipLogError(Controller, "JNI IM Subscribe Error: %" CHIP_ERROR_FORMAT, err.Format()); @@ -77,3 +77,15 @@ JNI_METHOD(void, extendableInvoke) ChipLogError(Controller, "JNI IM Batch Invoke Error: %" CHIP_ERROR_FORMAT, err.Format()); } } + +JNI_METHOD(jlong, getRemoteDeviceId) +(JNIEnv * env, jobject self, jlong devicePtr) +{ + return getRemoteDeviceId(devicePtr); +} + +JNI_METHOD(jint, getFabricIndex) +(JNIEnv * env, jobject self, jlong devicePtr) +{ + return getFabricIndex(devicePtr); +} diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java index 0844ffee7a1918..2e64d37cd62b11 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java @@ -142,7 +142,9 @@ protected void subscribeAttribute( int maxInterval) { ReportCallbackJni jniCallback = new ReportCallbackJni(callback, callback, callback); ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, attributeId); - ChipInteractionClient.subscribe(0, jniCallback.getCallbackHandle(), devicePtr, Arrays.asList(path), null, null, minInterval, maxInterval, false, true, timeoutMillis.orElse(0L).intValue(), null); + int fabricIndex = ChipInteractionClient.getFabricIndex(devicePtr); + long deviceId = ChipInteractionClient.getRemoteDeviceId(devicePtr); + ChipInteractionClient.subscribe(0, jniCallback.getCallbackHandle(), devicePtr, Arrays.asList(path), null, null, minInterval, maxInterval, false, true, timeoutMillis.orElse(0L).intValue(), null, ChipICDClient.isPeerICDClient(fabricIndex, deviceId)); } protected void invoke( diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 482bc2e917a425..653cebf89d1030 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -743,7 +743,7 @@ public int getFabricIndex() { } public List getICDClientInfo() { - return getICDClientInfo(deviceControllerPtr, getFabricIndex(deviceControllerPtr)); + return getICDClientInfo(getFabricIndex(deviceControllerPtr)); } /** @@ -752,7 +752,7 @@ public List getICDClientInfo() { * @param fabricIndex the fabric index to check */ public List getICDClientInfo(int fabricIndex) { - return getICDClientInfo(deviceControllerPtr, fabricIndex); + return ChipICDClient.getICDClientInfo(fabricIndex); } /* Shuts down all active subscriptions. */ @@ -838,7 +838,10 @@ public void subscribeToAttributePath( false, false, imTimeoutMs, - null); + null, + ChipICDClient.isPeerICDClient( + ChipInteractionClient.getFabricIndex(devicePtr), + ChipInteractionClient.getRemoteDeviceId(devicePtr))); } /** @@ -876,7 +879,10 @@ public void subscribeToEventPath( false, false, imTimeoutMs, - null); + null, + ChipICDClient.isPeerICDClient( + ChipInteractionClient.getFabricIndex(devicePtr), + ChipInteractionClient.getRemoteDeviceId(devicePtr))); } public void subscribeToEventPath( @@ -902,7 +908,10 @@ public void subscribeToEventPath( false, false, imTimeoutMs, - eventMin); + eventMin, + ChipICDClient.isPeerICDClient( + ChipInteractionClient.getFabricIndex(devicePtr), + ChipInteractionClient.getRemoteDeviceId(devicePtr))); } /** @@ -936,7 +945,10 @@ public void subscribeToPath( keepSubscriptions, isFabricFiltered, imTimeoutMs, - null); + null, + ChipICDClient.isPeerICDClient( + ChipInteractionClient.getFabricIndex(devicePtr), + ChipInteractionClient.getRemoteDeviceId(devicePtr))); } /** @@ -988,7 +1000,10 @@ public void subscribeToPath( keepSubscriptions, isFabricFiltered, imTimeoutMs, - null); + null, + ChipICDClient.isPeerICDClient( + ChipInteractionClient.getFabricIndex(devicePtr), + ChipInteractionClient.getRemoteDeviceId(devicePtr))); } public void subscribeToPath( @@ -1019,7 +1034,47 @@ public void subscribeToPath( keepSubscriptions, isFabricFiltered, imTimeoutMs, - eventMin); + eventMin, + ChipICDClient.isPeerICDClient( + ChipInteractionClient.getFabricIndex(devicePtr), + ChipInteractionClient.getRemoteDeviceId(devicePtr))); + } + + public void subscribeToPath( + SubscriptionEstablishedCallback subscriptionEstablishedCallback, + ResubscriptionAttemptCallback resubscriptionAttemptCallback, + ReportCallback reportCallback, + long devicePtr, + List attributePaths, + List eventPaths, + int minInterval, + int maxInterval, + boolean keepSubscriptions, + boolean isFabricFiltered, + int imTimeoutMs, + @Nullable Long eventMin, + @Nullable Boolean isPeerLIT) { + ReportCallbackJni jniCallback = + new ReportCallbackJni( + subscriptionEstablishedCallback, reportCallback, resubscriptionAttemptCallback); + ChipInteractionClient.subscribe( + deviceControllerPtr, + jniCallback.getCallbackHandle(), + devicePtr, + attributePaths, + eventPaths, + null, + minInterval, + maxInterval, + keepSubscriptions, + isFabricFiltered, + imTimeoutMs, + eventMin, + isPeerLIT != null + ? isPeerLIT + : ChipICDClient.isPeerICDClient( + ChipInteractionClient.getFabricIndex(devicePtr), + ChipInteractionClient.getRemoteDeviceId(devicePtr))); } /** @@ -1526,8 +1581,6 @@ private native void updateCommissioningNetworkCredentials( private native void updateCommissioningICDRegistrationInfo( long deviceControllerPtr, ICDRegistrationInfo icdRegistrationInfo); - private native List getICDClientInfo(long deviceControllerPtr, long fabricIndex); - private native long onNOCChainGeneration(long deviceControllerPtr, ControllerParams params); private native int getFabricIndex(long deviceControllerPtr); diff --git a/src/controller/java/src/chip/devicecontroller/ChipICDClient.java b/src/controller/java/src/chip/devicecontroller/ChipICDClient.java new file mode 100644 index 00000000000000..4d95a095f010f7 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/ChipICDClient.java @@ -0,0 +1,34 @@ +/* + * 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. + * + */ + +package chip.devicecontroller; + +import java.util.List; + +public class ChipICDClient { + public static boolean isPeerICDClient(int fabricIndex, long deviceId) { + List clientInfo = getICDClientInfo(fabricIndex); + if (clientInfo == null) { + return false; + } + + return clientInfo.stream().anyMatch(info -> info.getPeerNodeId() == deviceId); + } + + public static native List getICDClientInfo(int fabricIndex); +} diff --git a/src/controller/java/src/chip/devicecontroller/ChipInteractionClient.java b/src/controller/java/src/chip/devicecontroller/ChipInteractionClient.java index d06f1170efebcd..a20d1ceef33691 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipInteractionClient.java +++ b/src/controller/java/src/chip/devicecontroller/ChipInteractionClient.java @@ -37,7 +37,8 @@ static native void subscribe( boolean keepSubscriptions, boolean isFabricFiltered, int imTimeoutMs, - @Nullable Long eventMin); + @Nullable Long eventMin, + boolean isPeerICD); static native void read( long deviceControllerPtr, @@ -79,4 +80,8 @@ static native void shutdownSubscriptions( @Nullable Integer fabricIndex, @Nullable Long peerNodeId, @Nullable Long subscriptionId); + + static native long getRemoteDeviceId(long devicePtr); + + static native int getFabricIndex(long devicePtr); } diff --git a/src/controller/java/src/matter/controller/ICDClientInfo.kt b/src/controller/java/src/matter/controller/ICDClientInfo.kt new file mode 100644 index 00000000000000..3f0f30255a7dbc --- /dev/null +++ b/src/controller/java/src/matter/controller/ICDClientInfo.kt @@ -0,0 +1,31 @@ +/* + * 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. + * + */ + +package matter.controller + +/** Class for holding ICD Client information. */ +data class ICDClientInfo( + val peerNodeId: Long, + val startCounter: Long, + val offset: Long, + val monitoredSubject: Long, + val icdAesKey: ByteArray, + val icdHmacKey: ByteArray +) { + override fun toString(): String = "$peerNodeId/$startCounter/$offset/$monitoredSubject" +} diff --git a/src/controller/java/src/matter/controller/MatterControllerImpl.kt b/src/controller/java/src/matter/controller/MatterControllerImpl.kt index dde29eeb7c5354..1004b64815ddec 100644 --- a/src/controller/java/src/matter/controller/MatterControllerImpl.kt +++ b/src/controller/java/src/matter/controller/MatterControllerImpl.kt @@ -204,6 +204,9 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { reportHandler, resubscriptionAttemptHandler ) + + val fabricIndex = getFabricIndex(devicePtr) + val deviceId = getRemoteDeviceId(devicePtr) subscribe( deviceControllerPtr, reportCallbackJni.getJniHandle(), @@ -214,7 +217,8 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { request.maxInterval.seconds.toInt(), request.keepSubscriptions, request.fabricFiltered, - CHIP_IM_TIMEOUT_MS + CHIP_IM_TIMEOUT_MS, + MatterICDClientImpl.isPeerICDClient(fabricIndex, deviceId) ) awaitClose { logger.log(Level.FINE, "Closing flow") } @@ -232,7 +236,8 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { maxInterval: Int, keepSubscriptions: Boolean, isFabricFiltered: Boolean, - imTimeoutMs: Int + imTimeoutMs: Int, + isPeerLIT: Boolean ) override suspend fun read(request: ReadRequest): ReadResponse { @@ -450,6 +455,10 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { imTimeoutMs: Int ) + external fun getRemoteDeviceId(devicePtr: Long): Long + + external fun getFabricIndex(devicePtr: Long): Int + override fun close() { logger.log(Level.INFO, "MatterController is closed") deviceController.shutdownCommissioning() @@ -477,65 +486,6 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } } - // private fun ChipAttributePath.wrap(): AttributePath { - // return AttributePath( - // endpointId.getId().toUShort(), - // clusterId.getId().toUInt(), - // attributeId.getId().toUInt() - // ) - // } - - // private fun ChipEventPath.wrap(): EventPath { - // return EventPath( - // endpointId.getId().toUShort(), - // clusterId.getId().toUInt(), - // eventId.getId().toUInt() - // ) - // } - - // private fun chip.devicecontroller.model.NodeState.wrap(): NodeState { - // return NodeState( - // endpoints = endpointStates.mapValues { (id, value) -> value.wrap(id) }.toMutableMap(), - // ) - // } - - // private fun chip.devicecontroller.model.EndpointState.wrap(id: Int): EndpointState { - // return EndpointState( - // id, - // clusterStates.mapValues { (id, value) -> value.wrap(id) }.toMutableMap() - // ) - // } - - // private fun chip.devicecontroller.model.ClusterState.wrap(id: Long): ClusterState { - // return ClusterState( - // id, - // attributeStates.mapValues { (id, value) -> value.wrap(id) }.toMutableMap(), - // eventStates - // .mapValues { (id, value) -> - // value.map { eventState -> eventState.wrap(id) }.toMutableList() - // } - // .toMutableMap() - // ) - // } - - // private fun chip.devicecontroller.model.AttributeState.wrap(id: Long): AttributeState { - // return AttributeState(id, tlv, json.toString(), AttributePath(0U, 0U, id.toUInt()), value) - // } - - // private fun chip.devicecontroller.model.EventState.wrap(id: Long): EventState { - // return EventState( - // id, - // eventNumber, - // priorityLevel, - // timestampType, - // timestampValue, - // tlv, - // EventPath(0U, 0U, id.toUInt()), - // json.toString(), - // value - // ) - // } - init { val config: OperationalKeyConfig? = params.operationalKeyConfig val paramsBuilder = diff --git a/src/controller/java/src/matter/controller/MatterICDClientImpl.kt b/src/controller/java/src/matter/controller/MatterICDClientImpl.kt new file mode 100644 index 00000000000000..ae91e341904f08 --- /dev/null +++ b/src/controller/java/src/matter/controller/MatterICDClientImpl.kt @@ -0,0 +1,28 @@ +/* + * 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. + * + */ + +package matter.controller + +object MatterICDClientImpl { + fun isPeerICDClient(fabricIndex: Int, deviceId: Long): Boolean { + val clientInfo = getICDClientInfo(fabricIndex) ?: return false + return clientInfo.firstOrNull { it.peerNodeId == deviceId } != null + } + + external fun getICDClientInfo(fabricIndex: Int): List? +}