From c6d5692eaf75f944ee9d64e773643b81763af99f Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Wed, 29 Nov 2023 15:31:24 -0800 Subject: [PATCH] Add ICD Client Info management support with persistent storage (#29783) * Add ICD Client Info management support with persistent storage * Move to 1 entry per fabric for storage * adjust test * remove counter storage * rename ICDClientStorageDelegate to ICDClientStorage * remove global static for DefaultICDClientStorage * update doxygen * revert change for storagekeyname * add fabricList to record all fabric and move to one persistent storage with all clientInfos index by fabric * remove ICDFabricListCounter and use 255 to hold that large buffer instead * create two fields within tlv struct to hold FabricICDClientInfoCounter and FabricICDClientInfoMaxSize under the same key * address comments * address comments * Restyled by clang-format * fix ci * Restyled by clang-format * address comments * address comments * address comments --------- Co-authored-by: Restyled.io --- scripts/tools/check_includes_config.py | 5 +- src/app/icd/client/BUILD.gn | 32 ++ .../icd/client/DefaultICDClientStorage.cpp | 443 ++++++++++++++++++ src/app/icd/client/DefaultICDClientStorage.h | 131 ++++++ src/app/icd/client/ICDClientInfo.h | 54 +++ src/app/icd/client/ICDClientStorage.h | 95 ++++ src/app/tests/BUILD.gn | 2 + src/app/tests/TestDefaultICDClientStorage.cpp | 241 ++++++++++ src/controller/java/BUILD.gn | 1 + src/lib/core/CHIPConfig.h | 11 + src/lib/support/DefaultStorageKeyAllocator.h | 9 + 11 files changed, 1023 insertions(+), 1 deletion(-) create mode 100644 src/app/icd/client/BUILD.gn create mode 100644 src/app/icd/client/DefaultICDClientStorage.cpp create mode 100644 src/app/icd/client/DefaultICDClientStorage.h create mode 100644 src/app/icd/client/ICDClientInfo.h create mode 100644 src/app/icd/client/ICDClientStorage.h create mode 100644 src/app/tests/TestDefaultICDClientStorage.cpp diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index 1e06f933dfb63c..abe2c0ed42f5af 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -164,5 +164,8 @@ 'src/lib/support/jsontlv/JsonToTlv.cpp': {'sstream'}, 'src/lib/support/jsontlv/JsonToTlv.h': {'string'}, 'src/lib/support/jsontlv/TlvToJson.h': {'string'}, - 'src/lib/support/jsontlv/TextFormat.h': {'string'} + 'src/lib/support/jsontlv/TextFormat.h': {'string'}, + 'src/app/icd/client/DefaultICDClientStorage.cpp': {'vector'}, + 'src/app/icd/client/DefaultICDClientStorage.h': {'vector'}, + 'src/app/icd/client/DefaultICDStorageKey.h': {'vector'} } diff --git a/src/app/icd/client/BUILD.gn b/src/app/icd/client/BUILD.gn new file mode 100644 index 00000000000000..8e04d3b586140f --- /dev/null +++ b/src/app/icd/client/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# 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. + +import("//build_overrides/chip.gni") + +# ICD sources and configurations +source_set("manager") { + sources = [ + "DefaultICDClientStorage.cpp", + "DefaultICDClientStorage.h", + "ICDClientInfo.h", + "ICDClientStorage.h", + ] + + deps = [ "${chip_root}/src/lib/core" ] + public_deps = [ + "${chip_root}/src/app:app_config", + "${chip_root}/src/crypto", + "${chip_root}/src/lib/support", + ] +} diff --git a/src/app/icd/client/DefaultICDClientStorage.cpp b/src/app/icd/client/DefaultICDClientStorage.cpp new file mode 100644 index 00000000000000..3c6453ded8bbac --- /dev/null +++ b/src/app/icd/client/DefaultICDClientStorage.cpp @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2023 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 "DefaultICDClientStorage.h" +#include +#include +#include +#include +#include +#include +#include + +namespace { +// FabricIndex is uint8_t, the tlv size with anonymous tag is 1(control bytes) + 1(value) = 2 +constexpr size_t kFabricIndexTlvSize = 2; + +// The array itself has a control byte and an end-of-array marker. +constexpr size_t kArrayOverHead = 2; +constexpr size_t kFabricIndexMax = 255; +} // namespace + +namespace chip { +namespace app { +CHIP_ERROR DefaultICDClientStorage::UpdateFabricList(FabricIndex fabricIndex) +{ + for (auto & fabric_idx : mFabricList) + { + if (fabric_idx == fabricIndex) + { + return CHIP_NO_ERROR; + } + } + + mFabricList.push_back(fabricIndex); + + Platform::ScopedMemoryBuffer backingBuffer; + size_t counter = mFabricList.size(); + size_t total = kFabricIndexTlvSize * counter + kArrayOverHead; + ReturnErrorCodeIf(!backingBuffer.Calloc(total), CHIP_ERROR_NO_MEMORY); + TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), total); + + TLV::TLVType arrayType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayType)); + for (auto & fabric_idx : mFabricList) + { + ReturnErrorOnFailure(writer.Put(TLV::AnonymousTag(), fabric_idx)); + } + ReturnErrorOnFailure(writer.EndContainer(arrayType)); + + const auto len = writer.GetLengthWritten(); + VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); + + writer.Finalize(backingBuffer); + return mpClientInfoStore->SyncSetKeyValue(DefaultStorageKeyAllocator::ICDFabricList().KeyName(), backingBuffer.Get(), + static_cast(len)); +} + +CHIP_ERROR DefaultICDClientStorage::LoadFabricList() +{ + Platform::ScopedMemoryBuffer backingBuffer; + size_t len = kFabricIndexTlvSize * kFabricIndexMax + kArrayOverHead; + VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); + ReturnErrorCodeIf(!backingBuffer.Calloc(len), CHIP_ERROR_NO_MEMORY); + uint16_t length = static_cast(len); + ReturnErrorOnFailure( + mpClientInfoStore->SyncGetKeyValue(DefaultStorageKeyAllocator::ICDFabricList().KeyName(), backingBuffer.Get(), length)); + + TLV::ScopedBufferTLVReader reader(std::move(backingBuffer), len); + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag())); + TLV::TLVType arrayType; + ReturnErrorOnFailure(reader.EnterContainer(arrayType)); + + while ((reader.Next(TLV::kTLVType_UnsignedInteger, TLV::AnonymousTag())) == CHIP_NO_ERROR) + { + FabricIndex fabricIndex; + ReturnErrorOnFailure(reader.Get(fabricIndex)); + mFabricList.push_back(fabricIndex); + } + + ReturnErrorOnFailure(reader.ExitContainer(arrayType)); + return reader.VerifyEndOfContainer(); +} + +DefaultICDClientStorage::ICDClientInfoIteratorImpl::ICDClientInfoIteratorImpl(DefaultICDClientStorage & manager) : mManager(manager) +{ + mFabricListIndex = 0; + mClientInfoIndex = 0; + mClientInfoVector.clear(); +} + +size_t DefaultICDClientStorage::ICDClientInfoIteratorImpl::Count() +{ + size_t total = 0; + for (auto & fabric_idx : mManager.mFabricList) + { + size_t count = 0; + size_t clientInfoSize = 0; + if (mManager.LoadCounter(fabric_idx, count, clientInfoSize) != CHIP_NO_ERROR) + { + return 0; + }; + IgnoreUnusedVariable(clientInfoSize); + total += count; + } + + return total; +} + +bool DefaultICDClientStorage::ICDClientInfoIteratorImpl::Next(ICDClientInfo & item) +{ + for (; mFabricListIndex < mManager.mFabricList.size(); mFabricListIndex++) + { + if (mClientInfoVector.size() == 0) + { + size_t clientInfoSize = 0; + if (mManager.Load(mManager.mFabricList[mFabricListIndex], mClientInfoVector, clientInfoSize) != CHIP_NO_ERROR) + { + continue; + } + IgnoreUnusedVariable(clientInfoSize); + } + if (mClientInfoIndex < mClientInfoVector.size()) + { + item = mClientInfoVector[mClientInfoIndex]; + mClientInfoIndex++; + return true; + } + mClientInfoIndex = 0; + mClientInfoVector.clear(); + } + + return false; +} + +void DefaultICDClientStorage::ICDClientInfoIteratorImpl::Release() +{ + mManager.mICDClientInfoIterators.ReleaseObject(this); +} + +CHIP_ERROR DefaultICDClientStorage::Init(PersistentStorageDelegate * clientInfoStore, Crypto::SymmetricKeystore * keyStore) +{ + VerifyOrReturnError(clientInfoStore != nullptr && keyStore != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mpClientInfoStore == nullptr && mpKeyStore == nullptr, CHIP_ERROR_INCORRECT_STATE); + mpClientInfoStore = clientInfoStore; + mpKeyStore = keyStore; + CHIP_ERROR err = LoadFabricList(); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + err = CHIP_NO_ERROR; + } + return err; +} + +DefaultICDClientStorage::ICDClientInfoIterator * DefaultICDClientStorage::IterateICDClientInfo() +{ + return mICDClientInfoIterators.CreateObject(*this); +} + +CHIP_ERROR DefaultICDClientStorage::LoadCounter(FabricIndex fabricIndex, size_t & count, size_t & clientInfoSize) +{ + Platform::ScopedMemoryBuffer backingBuffer; + size_t len = MaxICDCounterSize(); + VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); + ReturnErrorCodeIf(!backingBuffer.Calloc(len), CHIP_ERROR_NO_MEMORY); + uint16_t length = static_cast(len); + + CHIP_ERROR err = mpClientInfoStore->SyncGetKeyValue( + DefaultStorageKeyAllocator::FabricICDClientInfoCounter(fabricIndex).KeyName(), backingBuffer.Get(), length); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + return CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + + TLV::ScopedBufferTLVReader reader(std::move(backingBuffer), len); + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); + TLV::TLVType structType; + ReturnErrorOnFailure(reader.EnterContainer(structType)); + uint32_t tempCount = 0; + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(CounterTag::kCount))); + ReturnErrorOnFailure(reader.Get(tempCount)); + count = static_cast(tempCount); + + uint32_t tempClientInfoSize = 0; + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(CounterTag::kSize))); + ReturnErrorOnFailure(reader.Get(tempClientInfoSize)); + clientInfoSize = static_cast(tempClientInfoSize); + + ReturnErrorOnFailure(reader.ExitContainer(structType)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR DefaultICDClientStorage::Load(FabricIndex fabricIndex, std::vector & clientInfoVector, + size_t & clientInfoSize) +{ + size_t count = 0; + ReturnErrorOnFailure(LoadCounter(fabricIndex, count, clientInfoSize)); + size_t len = clientInfoSize * count + kArrayOverHead; + Platform::ScopedMemoryBuffer backingBuffer; + VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); + ReturnErrorCodeIf(!backingBuffer.Calloc(len), CHIP_ERROR_NO_MEMORY); + uint16_t length = static_cast(len); + CHIP_ERROR err = mpClientInfoStore->SyncGetKeyValue(DefaultStorageKeyAllocator::ICDClientInfoKey(fabricIndex).KeyName(), + backingBuffer.Get(), length); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + return CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + + TLV::ScopedBufferTLVReader reader(std::move(backingBuffer), len); + + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag())); + TLV::TLVType arrayType; + ReturnErrorOnFailure(reader.EnterContainer(arrayType)); + + while ((err = reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())) == CHIP_NO_ERROR) + { + ICDClientInfo clientInfo; + TLV::TLVType ICDClientInfoType; + NodeId nodeId; + FabricIndex fabric; + ReturnErrorOnFailure(reader.EnterContainer(ICDClientInfoType)); + // Peer Node ID + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(ClientInfoTag::kPeerNodeId))); + ReturnErrorOnFailure(reader.Get(nodeId)); + + // Fabric Index + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(ClientInfoTag::kFabricIndex))); + ReturnErrorOnFailure(reader.Get(fabric)); + clientInfo.peer_node = ScopedNodeId(nodeId, fabric); + + // Start ICD Counter + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(ClientInfoTag::kStartICDCounter))); + ReturnErrorOnFailure(reader.Get(clientInfo.start_icd_counter)); + + // Offset + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(ClientInfoTag::kOffset))); + ReturnErrorOnFailure(reader.Get(clientInfo.offset)); + + // MonitoredSubject + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(ClientInfoTag::kMonitoredSubject))); + ReturnErrorOnFailure(reader.Get(clientInfo.monitored_subject)); + + // Shared key + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(ClientInfoTag::kSharedKey))); + ByteSpan buf; + ReturnErrorOnFailure(reader.Get(buf)); + VerifyOrReturnError(buf.size() == sizeof(Crypto::Aes128KeyByteArray), CHIP_ERROR_INTERNAL); + memcpy(clientInfo.sharedKey.AsMutable(), buf.data(), sizeof(Crypto::Aes128KeyByteArray)); + ReturnErrorOnFailure(reader.ExitContainer(ICDClientInfoType)); + clientInfoVector.push_back(clientInfo); + } + + if (err != CHIP_END_OF_TLV) + { + return err; + } + + return reader.ExitContainer(arrayType); +} + +CHIP_ERROR DefaultICDClientStorage::SetKey(ICDClientInfo & clientInfo, const ByteSpan keyData) +{ + VerifyOrReturnError(keyData.size() == sizeof(Crypto::Aes128KeyByteArray), CHIP_ERROR_INVALID_ARGUMENT); + + Crypto::Aes128KeyByteArray keyMaterial; + memcpy(keyMaterial, keyData.data(), sizeof(Crypto::Aes128KeyByteArray)); + + return mpKeyStore->CreateKey(keyMaterial, clientInfo.sharedKey); +} + +CHIP_ERROR DefaultICDClientStorage::SerializeToTlv(TLV::TLVWriter & writer, const std::vector & clientInfoVector) +{ + TLV::TLVType arrayType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayType)); + for (auto & clientInfo : clientInfoVector) + { + TLV::TLVType ICDClientInfoContainerType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, ICDClientInfoContainerType)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(ClientInfoTag::kPeerNodeId), clientInfo.peer_node.GetNodeId())); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(ClientInfoTag::kFabricIndex), clientInfo.peer_node.GetFabricIndex())); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(ClientInfoTag::kStartICDCounter), clientInfo.start_icd_counter)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(ClientInfoTag::kOffset), clientInfo.offset)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(ClientInfoTag::kMonitoredSubject), clientInfo.monitored_subject)); + ByteSpan buf(clientInfo.sharedKey.As()); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(ClientInfoTag::kSharedKey), buf)); + ReturnErrorOnFailure(writer.EndContainer(ICDClientInfoContainerType)); + } + return writer.EndContainer(arrayType); +} + +CHIP_ERROR DefaultICDClientStorage::StoreEntry(const ICDClientInfo & clientInfo) +{ + std::vector clientInfoVector; + size_t clientInfoSize = MaxICDClientInfoSize(); + ReturnErrorOnFailure(Load(clientInfo.peer_node.GetFabricIndex(), clientInfoVector, clientInfoSize)); + + for (auto it = clientInfoVector.begin(); it != clientInfoVector.end(); it++) + { + if (clientInfo.peer_node.GetNodeId() == it->peer_node.GetNodeId()) + { + ReturnErrorOnFailure(DecreaseEntryCountForFabric(clientInfo.peer_node.GetFabricIndex())); + clientInfoVector.erase(it); + break; + } + } + + clientInfoVector.push_back(clientInfo); + size_t total = clientInfoSize * clientInfoVector.size() + kArrayOverHead; + Platform::ScopedMemoryBuffer backingBuffer; + ReturnErrorCodeIf(!backingBuffer.Calloc(total), CHIP_ERROR_NO_MEMORY); + TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), total); + + ReturnErrorOnFailure(SerializeToTlv(writer, clientInfoVector)); + + const auto len = writer.GetLengthWritten(); + VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); + + writer.Finalize(backingBuffer); + ReturnErrorOnFailure(mpClientInfoStore->SyncSetKeyValue( + DefaultStorageKeyAllocator::ICDClientInfoKey(clientInfo.peer_node.GetFabricIndex()).KeyName(), backingBuffer.Get(), + static_cast(len))); + + return IncreaseEntryCountForFabric(clientInfo.peer_node.GetFabricIndex()); +} + +CHIP_ERROR DefaultICDClientStorage::IncreaseEntryCountForFabric(FabricIndex fabricIndex) +{ + return UpdateEntryCountForFabric(fabricIndex, /*increase*/ true); +} + +CHIP_ERROR DefaultICDClientStorage::DecreaseEntryCountForFabric(FabricIndex fabricIndex) +{ + return UpdateEntryCountForFabric(fabricIndex, /*increase*/ false); +} + +CHIP_ERROR DefaultICDClientStorage::UpdateEntryCountForFabric(FabricIndex fabricIndex, bool increase) +{ + size_t count = 0; + size_t clientInfoSize = MaxICDClientInfoSize(); + ReturnErrorOnFailure(LoadCounter(fabricIndex, count, clientInfoSize)); + if (increase) + { + count++; + } + else + { + count--; + } + + size_t total = MaxICDCounterSize(); + Platform::ScopedMemoryBuffer backingBuffer; + ReturnErrorCodeIf(!backingBuffer.Calloc(total), CHIP_ERROR_NO_MEMORY); + TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), total); + + TLV::TLVType structType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, structType)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(CounterTag::kCount), static_cast(count))); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(CounterTag::kSize), static_cast(clientInfoSize))); + ReturnErrorOnFailure(writer.EndContainer(structType)); + + const auto len = writer.GetLengthWritten(); + VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); + writer.Finalize(backingBuffer); + + return mpClientInfoStore->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricICDClientInfoCounter(fabricIndex).KeyName(), + backingBuffer.Get(), static_cast(len)); +} + +CHIP_ERROR DefaultICDClientStorage::DeleteEntry(const ScopedNodeId & peerNode) +{ + size_t clientInfoSize = 0; + std::vector clientInfoVector; + ReturnErrorOnFailure(Load(peerNode.GetFabricIndex(), clientInfoVector, clientInfoSize)); + + for (auto it = clientInfoVector.begin(); it != clientInfoVector.end(); it++) + { + if (peerNode.GetNodeId() == it->peer_node.GetNodeId()) + { + mpKeyStore->DestroyKey(it->sharedKey); + it = clientInfoVector.erase(it); + break; + } + } + + ReturnErrorOnFailure( + mpClientInfoStore->SyncDeleteKeyValue(DefaultStorageKeyAllocator::ICDClientInfoKey(peerNode.GetFabricIndex()).KeyName())); + + size_t total = clientInfoSize * clientInfoVector.size() + kArrayOverHead; + Platform::ScopedMemoryBuffer backingBuffer; + ReturnErrorCodeIf(!backingBuffer.Calloc(total), CHIP_ERROR_NO_MEMORY); + TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), total); + + ReturnErrorOnFailure(SerializeToTlv(writer, clientInfoVector)); + + const auto len = writer.GetLengthWritten(); + VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); + + writer.Finalize(backingBuffer); + ReturnErrorOnFailure( + mpClientInfoStore->SyncSetKeyValue(DefaultStorageKeyAllocator::ICDClientInfoKey(peerNode.GetFabricIndex()).KeyName(), + backingBuffer.Get(), static_cast(len))); + + return DecreaseEntryCountForFabric(peerNode.GetFabricIndex()); +} + +CHIP_ERROR DefaultICDClientStorage::DeleteAllEntries(FabricIndex fabricIndex) +{ + size_t clientInfoSize = 0; + std::vector clientInfoVector; + ReturnErrorOnFailure(Load(fabricIndex, clientInfoVector, clientInfoSize)); + IgnoreUnusedVariable(clientInfoSize); + for (auto & clientInfo : clientInfoVector) + { + mpKeyStore->DestroyKey(clientInfo.sharedKey); + } + ReturnErrorOnFailure( + mpClientInfoStore->SyncDeleteKeyValue(DefaultStorageKeyAllocator::ICDClientInfoKey(fabricIndex).KeyName())); + return mpClientInfoStore->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FabricICDClientInfoCounter(fabricIndex).KeyName()); +} + +CHIP_ERROR DefaultICDClientStorage::ProcessCheckInPayload(const ByteSpan & payload, ICDClientInfo & clientInfo) +{ + // TODO: Need to implement default decription code using CheckinMessage::ParseCheckinMessagePayload + return CHIP_NO_ERROR; +} +} // namespace app +} // namespace chip diff --git a/src/app/icd/client/DefaultICDClientStorage.h b/src/app/icd/client/DefaultICDClientStorage.h new file mode 100644 index 00000000000000..adc8c69113a700 --- /dev/null +++ b/src/app/icd/client/DefaultICDClientStorage.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023 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 "ICDClientStorage.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: SymmetricKeystore is an alias for SessionKeystore, replace the below when sdk supports SymmetricKeystore +namespace chip { +namespace Crypto { +using SymmetricKeystore = SessionKeystore; +} // namespace Crypto +} // namespace chip + +namespace chip { +namespace app { + +/** + * A DefaultICDClientStorage implementation of ICDClientStorage. + */ +class DefaultICDClientStorage : public ICDClientStorage +{ +public: + static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_ICD_CLIENTS_INFO_STORAGE_CONCURRENT_ITERATORS; + + CHIP_ERROR Init(PersistentStorageDelegate * clientInfoStore, Crypto::SymmetricKeystore * keyStore); + + ICDClientInfoIterator * IterateICDClientInfo() override; + + CHIP_ERROR UpdateFabricList(FabricIndex fabricIndex); + + CHIP_ERROR SetKey(ICDClientInfo & clientInfo, const ByteSpan keyData) override; + + CHIP_ERROR StoreEntry(const ICDClientInfo & clientInfo) override; + + CHIP_ERROR DeleteEntry(const ScopedNodeId & peerNodeId) override; + + CHIP_ERROR DeleteAllEntries(FabricIndex fabricIndex) override; + + CHIP_ERROR ProcessCheckInPayload(const ByteSpan & payload, ICDClientInfo & clientInfo) override; + +protected: + enum class ClientInfoTag : uint8_t + { + kPeerNodeId = 1, + kFabricIndex = 2, + kStartICDCounter = 3, + kOffset = 4, + kMonitoredSubject = 5, + kSharedKey = 6 + }; + + enum class CounterTag : uint8_t + { + kCount = 1, + kSize = 2, + }; + + class ICDClientInfoIteratorImpl : public ICDClientInfoIterator + { + public: + ICDClientInfoIteratorImpl(DefaultICDClientStorage & manager); + size_t Count() override; + bool Next(ICDClientInfo & info) override; + void Release() override; + + private: + DefaultICDClientStorage & mManager; + size_t mFabricListIndex = 0; + size_t mClientInfoIndex = 0; + std::vector mClientInfoVector; + }; + + static constexpr size_t MaxICDClientInfoSize() + { + // All the fields added together + return TLV::EstimateStructOverhead(sizeof(NodeId), sizeof(FabricIndex), sizeof(uint32_t), sizeof(uint32_t), + sizeof(uint64_t), sizeof(Crypto::Aes128KeyByteArray)); + } + + static constexpr size_t MaxICDCounterSize() + { + // All the fields added together + return TLV::EstimateStructOverhead(sizeof(size_t), sizeof(size_t)); + } + +private: + friend class ICDClientInfoIteratorImpl; + CHIP_ERROR LoadFabricList(); + CHIP_ERROR LoadCounter(FabricIndex fabricIndex, size_t & count, size_t & clientInfoSize); + + CHIP_ERROR IncreaseEntryCountForFabric(FabricIndex fabricIndex); + CHIP_ERROR DecreaseEntryCountForFabric(FabricIndex fabricIndex); + CHIP_ERROR UpdateEntryCountForFabric(FabricIndex fabricIndex, bool increase); + + CHIP_ERROR SerializeToTlv(TLV::TLVWriter & writer, const std::vector & clientInfoVector); + CHIP_ERROR Load(FabricIndex fabricIndex, std::vector & clientInfoVector, size_t & clientInfoSize); + + ObjectPool mICDClientInfoIterators; + + PersistentStorageDelegate * mpClientInfoStore = nullptr; + Crypto::SymmetricKeystore * mpKeyStore = nullptr; + std::vector mFabricList; +}; +} // namespace app +} // namespace chip diff --git a/src/app/icd/client/ICDClientInfo.h b/src/app/icd/client/ICDClientInfo.h new file mode 100644 index 00000000000000..109d71d81373f4 --- /dev/null +++ b/src/app/icd/client/ICDClientInfo.h @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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 +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +struct ICDClientInfo +{ + ScopedNodeId peer_node; + uint32_t start_icd_counter = 0; + uint32_t offset = 0; + uint64_t monitored_subject = static_cast(0); + Crypto::Aes128KeyHandle sharedKey = Crypto::Aes128KeyHandle(); + + ICDClientInfo() {} + ICDClientInfo(const ICDClientInfo & other) { *this = other; } + + ICDClientInfo & operator=(const ICDClientInfo & other) + { + peer_node = other.peer_node; + start_icd_counter = other.start_icd_counter; + offset = other.offset; + monitored_subject = other.monitored_subject; + ByteSpan buf(other.sharedKey.As()); + memcpy(sharedKey.AsMutable(), buf.data(), sizeof(Crypto::Aes128KeyByteArray)); + return *this; + } +}; + +} // namespace app +} // namespace chip diff --git a/src/app/icd/client/ICDClientStorage.h b/src/app/icd/client/ICDClientStorage.h new file mode 100644 index 00000000000000..4df2c961260104 --- /dev/null +++ b/src/app/icd/client/ICDClientStorage.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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 "ICDClientInfo.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +/** + * The ICDClientStorage class is an abstract interface that defines the operations + * for storing, retrieving and deleting ICD client information in persistent storage. + */ +class ICDClientStorage +{ +public: + using ICDClientInfoIterator = CommonIterator; + + virtual ~ICDClientStorage() = default; + + /** + * Iterate through persisted ICD Client Info + * + * @return A valid iterator on success. Use CommonIterator accessor to retrieve ICDClientInfo + */ + virtual ICDClientInfoIterator * IterateICDClientInfo() = 0; + + /** + * Called during ICD device registration in commissioning, commissioner/controller + * provides raw key data, the shared key handle in clientInfo is updated based upon raw key data + * + * @param[inout] aICDClientInfo the ICD Client information to be updated with keyData and be saved + * @param[in] aKeyData raw key data provided by application + */ + virtual CHIP_ERROR SetKey(ICDClientInfo & clientInfo, const ByteSpan keyData) = 0; + + /** + * Store updated ICD ClientInfo to storage when ICD registration completes or check-in message + * comes. + * + * @param[in] aICDClientInfo the updated ICD Client Info. + */ + virtual CHIP_ERROR StoreEntry(const ICDClientInfo & clientInfo) = 0; + + /** + * Delete ICD Client persistent information associated with the specified scoped node Id. + * when ICD device is unpaired/removed, the corresponding entry in ICD storage is removed. + * @param aPeerNodeId scoped node with peer node id and fabric index + */ + virtual CHIP_ERROR DeleteEntry(const ScopedNodeId & peerNodeId) = 0; + + /** + * Remove all ICDClient persistent information associated with the specified + * fabric index. If no entries for the fabric index exist, this is a no-op + * and is considered successful. + * When the whole fabric is removed, all entries from persistent storage in current fabric index are removed. + * + * @param[in] fabricIndex the index of the fabric for which to remove ICDClient persistent information + */ + virtual CHIP_ERROR DeleteAllEntries(FabricIndex fabricIndex) = 0; + + /** + * Process received ICD Check-in message payload. The implementation needs to parse the payload, + * look for a key that allows successfully decrypting the payload, verify that the counter in the payload is valid, + * and populate the clientInfo with the stored information corresponding to the key. + * @param[in] payload received checkIn Message payload + * @param[out] clientInfo retrieved matched clientInfo from storage + */ + virtual CHIP_ERROR ProcessCheckInPayload(const ByteSpan & payload, ICDClientInfo & clientInfo) = 0; +}; +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 78e3bdb229e49c..9506037aeea2ff 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -161,6 +161,7 @@ chip_test_suite_using_nltest("tests") { } test_sources += [ "TestAclAttribute.cpp" ] + test_sources += [ "TestDefaultICDClientStorage.cpp" ] # # On NRF platforms, the allocation of a large number of pbufs in this test @@ -200,6 +201,7 @@ chip_test_suite_using_nltest("tests") { "${chip_root}/src/app", "${chip_root}/src/app/common:cluster-objects", "${chip_root}/src/app/icd:manager", + "${chip_root}/src/app/icd/client:manager", "${chip_root}/src/app/tests:helpers", "${chip_root}/src/app/util/mock:mock_ember", "${chip_root}/src/lib/core", diff --git a/src/app/tests/TestDefaultICDClientStorage.cpp b/src/app/tests/TestDefaultICDClientStorage.cpp new file mode 100644 index 00000000000000..ffddf526d763f7 --- /dev/null +++ b/src/app/tests/TestDefaultICDClientStorage.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2023 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 +#include + +#include +#include +#include +#include + +using namespace chip; +using namespace app; +using namespace System; +using TestSessionKeystoreImpl = Crypto::DefaultSessionKeystore; + +constexpr uint8_t kKeyBuffer1[] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f +}; + +constexpr uint8_t kKeyBuffer2[] = { + 0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81, 0x71, 0x61, 0x51, 0x14, 0x31, 0x21, 0x11, 0x01 +}; +constexpr uint8_t kKeyBuffer3[] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f +}; + +struct TestClientInfo : public ICDClientInfo +{ + bool operator==(const ICDClientInfo & that) const + { + if ((peer_node != that.peer_node)) + { + return false; + } + return true; + } +}; + +void TestClientInfoCount(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + FabricIndex fabricId = 1; + NodeId nodeId1 = 6666; + NodeId nodeId2 = 6667; + DefaultICDClientStorage manager; + TestPersistentStorageDelegate clientInfoStorage; + TestSessionKeystoreImpl keystore; + err = manager.Init(&clientInfoStorage, &keystore); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.UpdateFabricList(fabricId); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + // Write some ClientInfos and see the counts are correct + ICDClientInfo clientInfo1; + clientInfo1.peer_node = ScopedNodeId(nodeId1, fabricId); + ICDClientInfo clientInfo2; + clientInfo2.peer_node = ScopedNodeId(nodeId2, fabricId); + ICDClientInfo clientInfo3; + clientInfo3.peer_node = ScopedNodeId(nodeId1, fabricId); + err = manager.SetKey(clientInfo1, ByteSpan(kKeyBuffer1)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.StoreEntry(clientInfo1); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = manager.SetKey(clientInfo2, ByteSpan(kKeyBuffer2)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.StoreEntry(clientInfo2); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = manager.SetKey(clientInfo3, ByteSpan(kKeyBuffer3)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.StoreEntry(clientInfo3); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + // Make sure iterator counts correctly + auto * iterator = manager.IterateICDClientInfo(); + // same nodeId for clientInfo2 and clientInfo3, so the new one replace old one + NL_TEST_ASSERT(apSuite, iterator->Count() == 2); + + ICDClientInfo clientInfo; + NL_TEST_ASSERT(apSuite, iterator->Next(clientInfo)); + NL_TEST_ASSERT(apSuite, clientInfo.peer_node.GetNodeId() == nodeId2); + NL_TEST_ASSERT(apSuite, iterator->Next(clientInfo)); + NL_TEST_ASSERT(apSuite, clientInfo.peer_node.GetNodeId() == nodeId1); + + iterator->Release(); + + // Delete all and verify iterator counts 0 + err = manager.DeleteAllEntries(fabricId); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + iterator = manager.IterateICDClientInfo(); + NL_TEST_ASSERT(apSuite, iterator->Count() == 0); + + // Verify ClientInfos manually count correctly + size_t count = 0; + while (iterator->Next(clientInfo)) + { + count++; + } + iterator->Release(); + NL_TEST_ASSERT(apSuite, count == 0); +} + +void TestClientInfoCountMultipleFabric(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + FabricIndex fabricId1 = 1; + FabricIndex fabricId2 = 2; + NodeId nodeId1 = 6666; + NodeId nodeId2 = 6667; + NodeId nodeId3 = 6668; + DefaultICDClientStorage manager; + TestPersistentStorageDelegate clientInfoStorage; + TestSessionKeystoreImpl keystore; + err = manager.Init(&clientInfoStorage, &keystore); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.UpdateFabricList(fabricId1); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.UpdateFabricList(fabricId2); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + // Write some ClientInfos and see the counts are correct + ICDClientInfo clientInfo1; + clientInfo1.peer_node = ScopedNodeId(nodeId1, fabricId1); + ICDClientInfo clientInfo2; + clientInfo2.peer_node = ScopedNodeId(nodeId2, fabricId1); + ICDClientInfo clientInfo3; + clientInfo3.peer_node = ScopedNodeId(nodeId3, fabricId2); + + err = manager.SetKey(clientInfo1, ByteSpan(kKeyBuffer1)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.StoreEntry(clientInfo1); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = manager.SetKey(clientInfo2, ByteSpan(kKeyBuffer2)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.StoreEntry(clientInfo2); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = manager.SetKey(clientInfo3, ByteSpan(kKeyBuffer3)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = manager.StoreEntry(clientInfo3); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + // Make sure iterator counts correctly + auto * iterator = manager.IterateICDClientInfo(); + NL_TEST_ASSERT(apSuite, iterator->Count() == 3); + iterator->Release(); + + // Delete all and verify iterator counts 0 + err = manager.DeleteEntry(ScopedNodeId(nodeId1, fabricId1)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + iterator = manager.IterateICDClientInfo(); + NL_TEST_ASSERT(apSuite, iterator->Count() == 2); + + err = manager.DeleteEntry(ScopedNodeId(nodeId2, fabricId1)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, iterator->Count() == 1); + + err = manager.DeleteEntry(ScopedNodeId(nodeId3, fabricId2)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, iterator->Count() == 0); + + // Verify ClientInfos manually count correctly + size_t count = 0; + ICDClientInfo clientInfo; + while (iterator->Next(clientInfo)) + { + count++; + } + iterator->Release(); + NL_TEST_ASSERT(apSuite, count == 0); +} + +/** + * Set up the test suite. + */ +int TestClientInfo_Setup(void * apContext) +{ + VerifyOrReturnError(CHIP_NO_ERROR == Platform::MemoryInit(), FAILURE); + + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +int TestClientInfo_Teardown(void * apContext) +{ + Platform::MemoryShutdown(); + return SUCCESS; +} + +// Test Suite + +/** + * Test Suite that lists all the test functions. + */ +// clang-format off +static const nlTest sTests[] = +{ + NL_TEST_DEF("TestClientInfoCount", TestClientInfoCount), + NL_TEST_DEF("TestClientInfoCountMultipleFabric", TestClientInfoCountMultipleFabric), + NL_TEST_SENTINEL() +}; +// clang-format on + +// clang-format off +static nlTestSuite sSuite = +{ + "TestDefaultICDClientStorage", + &sTests[0], + &TestClientInfo_Setup, &TestClientInfo_Teardown +}; +// clang-format on + +/** + * Main + */ +int TestDefaultICDClientStorage() +{ + // Run test suit against one context + nlTestRunner(&sSuite, nullptr); + + return (nlTestRunnerStats(&sSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestDefaultICDClientStorage) diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 4d09f611bc4267..31116c55a79d6c 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -63,6 +63,7 @@ shared_library("jni") { ] deps = [ + "${chip_root}/src/app/icd/client:manager", "${chip_root}/src/credentials:default_attestation_verifier", "${chip_root}/src/inet", "${chip_root}/src/lib", diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index afbe4206d9ed75..3fd5b497cb57b9 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1678,6 +1678,17 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED 0 #endif +/** + * @def CHIP_CONFIG_MAX_ICD_CLIENTS_INFO_STORAGE_CONCURRENT_ITERATORS + * + * @brief Defines the number of simultaneous ICD Clients info iterators that can be allocated + * + * Number of iterator instances that can be allocated at any one time + */ +#ifndef CHIP_CONFIG_MAX_ICD_CLIENTS_INFO_STORAGE_CONCURRENT_ITERATORS +#define CHIP_CONFIG_MAX_ICD_CLIENTS_INFO_STORAGE_CONCURRENT_ITERATORS 1 +#endif + /** * @def CHIP_CONFIG_MAX_PATHS_PER_INVOKE * diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index c33e1127015e39..b5193c089eda65 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -229,6 +229,15 @@ class DefaultStorageKeyAllocator static StorageKeyName TSDefaultNTP() { return StorageKeyName::FromConst("g/ts/dntp"); } static StorageKeyName TSTimeZone() { return StorageKeyName::FromConst("g/ts/tz"); } static StorageKeyName TSDSTOffset() { return StorageKeyName::FromConst("g/ts/dsto"); } + + static StorageKeyName FabricICDClientInfoCounter(FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/icdc", fabric); } + + static StorageKeyName ICDClientInfoKey(FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/icdk", fabric); } + + // ICDFabricList is only used by DefaultICDClientStorage + // when new fabric is created, this list needs to be updated, + // when client init DefaultICDClientStorage, this table needs to be loaded. + static StorageKeyName ICDFabricList() { return StorageKeyName::FromConst("g/icdfl"); } }; } // namespace chip