From 276f76371c2b78a203adad86ddb711bc2b418316 Mon Sep 17 00:00:00 2001 From: Andy Salisbury Date: Wed, 26 Jan 2022 22:34:27 -0500 Subject: [PATCH] Add storage to the example ACL implementation (#14253) * Add a simple implementation of storage for ACLs. It uses a persistent storage delegate to store the data as a TLV-formatted blob. Platforms or applications that want content-aware storage can either implement a persistent storage delegate that reads the TLV-formatted blob or implement the AccessControl::Delegate interface. * Use tags correctly and fix a format string. * Applied improvements suggested by bzbarsky. * Don't specify buffer sizes for writers. * Use form of Next that includes both tag and type. * Don't wrap subjects and targets in an unnecessary structure layer. * Save the ACL entries to flash whenever an entry is created, updated, or deleted. * Add simple storage for ACL. * Style fixes. * Remove unneeded and potentially misleading comments. * Missed one of the comments. * Explain choice of buffer sizes. * Style fixes. --- examples/platform/linux/AppMain.cpp | 27 +++ .../examples/ExampleAccessControlDelegate.cpp | 206 +++++++++++++++++- .../examples/ExampleAccessControlDelegate.h | 3 + src/lib/support/DefaultStorageKeyAllocator.h | 5 + 4 files changed, 238 insertions(+), 3 deletions(-) diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index fafa5afa0c8ab9..312daf85836310 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -33,6 +33,8 @@ #include #include +#include + #include #include #include @@ -64,6 +66,27 @@ using namespace chip::DeviceLayer; using namespace chip::Inet; using namespace chip::Transport; +class GeneralStorageDelegate : public PersistentStorageDelegate +{ + CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override + { + ChipLogProgress(NotSpecified, "Retrieved value from general storage."); + return PersistedStorage::KeyValueStoreMgr().Get(key, buffer, size); + } + + CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override + { + ChipLogProgress(NotSpecified, "Stored value in general storage"); + return PersistedStorage::KeyValueStoreMgr().Put(key, value, size); + } + + CHIP_ERROR SyncDeleteKeyValue(const char * key) override + { + ChipLogProgress(NotSpecified, "Delete value in general storage"); + return PersistedStorage::KeyValueStoreMgr().Delete(key); + } +}; + #if defined(ENABLE_CHIP_SHELL) using chip::Shell::Engine; #endif @@ -88,6 +111,8 @@ void EventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg ChipLogProgress(DeviceLayer, "Receive kCHIPoBLEConnectionEstablished"); } } + +GeneralStorageDelegate gAclStorageDelegate; } // namespace #if CHIP_DEVICE_CONFIG_ENABLE_WPA @@ -136,6 +161,8 @@ int ChipLinuxAppInit(int argc, char ** argv) PrintOnboardingCodes(LinuxDeviceOptions::GetInstance().payload); + Access::Examples::SetAccessControlDelegateStorage(&gAclStorageDelegate); + #if defined(PW_RPC_ENABLED) chip::rpc::Init(); ChipLogProgress(NotSpecified, "PW_RPC initialized."); diff --git a/src/access/examples/ExampleAccessControlDelegate.cpp b/src/access/examples/ExampleAccessControlDelegate.cpp index 893849080e97ea..dbff97122a514c 100644 --- a/src/access/examples/ExampleAccessControlDelegate.cpp +++ b/src/access/examples/ExampleAccessControlDelegate.cpp @@ -19,9 +19,12 @@ #include "ExampleAccessControlDelegate.h" #include +#include +#include #include #include +#include #include namespace { @@ -125,6 +128,15 @@ class SubjectStorage return CHIP_ERROR_INVALID_ARGUMENT; } +public: + CHIP_ERROR Serialize(chip::TLV::TLVWriter & writer) { return writer.Put(chip::TLV::AnonymousTag(), mNode); } + + CHIP_ERROR Deserialize(chip::TLV::TLVReader & reader) + { + ReturnErrorOnFailure(reader.Next(chip::TLV::AnonymousTag())); + return reader.Get(mNode); + } + private: static bool IsValid(NodeId node) { return node != kUndefinedNodeId; } @@ -180,6 +192,21 @@ class TargetStorage return CHIP_ERROR_INVALID_ARGUMENT; } +public: + CHIP_ERROR Serialize(chip::TLV::TLVWriter & writer) + { + ReturnErrorOnFailure(writer.Put(chip::TLV::AnonymousTag(), mCluster)); + return writer.Put(chip::TLV::AnonymousTag(), mDeviceType); + } + + CHIP_ERROR Deserialize(chip::TLV::TLVReader & reader) + { + ReturnErrorOnFailure(reader.Next(chip::TLV::AnonymousTag())); + ReturnErrorOnFailure(reader.Get(mCluster)); + ReturnErrorOnFailure(reader.Next(chip::TLV::AnonymousTag())); + return reader.Get(mDeviceType); + } + private: // TODO: eventually this functionality should live where the type itself is defined static bool IsValidCluster(ClusterId cluster) @@ -483,6 +510,94 @@ class EntryStorage index = found ? toIndex : ArraySize(acl); } +public: + static constexpr uint8_t kTagInUse = 1; + static constexpr uint8_t kTagFabricIndex = 2; + static constexpr uint8_t kTagAuthMode = 3; + static constexpr uint8_t kTagPrivilege = 4; + static constexpr uint8_t kTagSubjects = 5; + static constexpr uint8_t kTagTargets = 6; + // This value was chosen to be large enough to contain the data, but has not been fine-tuned. + static const size_t kStorageBufferSize = 192; + + CHIP_ERROR Serialize(chip::PersistentStorageDelegate * storage, const char * key) + { + uint8_t buffer[kStorageBufferSize] = { 0 }; + chip::TLV::TLVWriter writer; + writer.Init(buffer); + chip::TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::TLVType::kTLVType_Structure, container)); + + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagInUse), mInUse)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagFabricIndex), mFabricIndex)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagAuthMode), mAuthMode)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagPrivilege), mPrivilege)); + + chip::TLV::TLVType internalContainer; + ReturnErrorOnFailure( + writer.StartContainer(chip::TLV::ContextTag(kTagSubjects), chip::TLV::TLVType::kTLVType_Array, internalContainer)); + for (size_t i = 0; i < kMaxSubjects; ++i) + { + ReturnErrorOnFailure(mSubjects[i].Serialize(writer)); + } + ReturnErrorOnFailure(writer.EndContainer(internalContainer)); + + ReturnErrorOnFailure( + writer.StartContainer(chip::TLV::ContextTag(kTagTargets), chip::TLV::TLVType::kTLVType_Array, internalContainer)); + for (size_t i = 0; i < kMaxTargets; ++i) + { + ReturnErrorOnFailure(mTargets[i].Serialize(writer)); + } + ReturnErrorOnFailure(writer.EndContainer(internalContainer)); + + ReturnErrorOnFailure(writer.EndContainer(container)); + ReturnErrorOnFailure(writer.Finalize()); + + return storage->SyncSetKeyValue(key, buffer, static_cast(writer.GetLengthWritten())); + } + + CHIP_ERROR Deserialize(chip::PersistentStorageDelegate * storage, const char * key) + { + uint8_t buffer[kStorageBufferSize] = { 0 }; + uint16_t bufferSize = static_cast(sizeof(buffer)); + ReturnErrorOnFailure(storage->SyncGetKeyValue(key, buffer, bufferSize)); + chip::TLV::TLVReader reader; + reader.Init(buffer, bufferSize); + + ReturnErrorOnFailure(reader.Next(chip::TLV::TLVType::kTLVType_Structure, chip::TLV::AnonymousTag())); + + chip::TLV::TLVType container; + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagInUse))); + ReturnErrorOnFailure(reader.Get(mInUse)); + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagFabricIndex))); + ReturnErrorOnFailure(reader.Get(mFabricIndex)); + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagAuthMode))); + ReturnErrorOnFailure(reader.Get(mAuthMode)); + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagPrivilege))); + ReturnErrorOnFailure(reader.Get(mPrivilege)); + + chip::TLV::TLVType innerContainer; + ReturnErrorOnFailure(reader.Next(chip::TLV::TLVType::kTLVType_Array, chip::TLV::ContextTag(kTagSubjects))); + ReturnErrorOnFailure(reader.EnterContainer(innerContainer)); + for (size_t i = 0; i < kMaxSubjects; ++i) + { + ReturnErrorOnFailure(mSubjects[i].Deserialize(reader)); + } + ReturnErrorOnFailure(reader.ExitContainer(innerContainer)); + + ReturnErrorOnFailure(reader.Next(chip::TLV::TLVType::kTLVType_Array, chip::TLV::ContextTag(kTagTargets))); + ReturnErrorOnFailure(reader.EnterContainer(innerContainer)); + for (size_t i = 0; i < kMaxTargets; ++i) + { + ReturnErrorOnFailure(mTargets[i].Deserialize(reader)); + } + ReturnErrorOnFailure(reader.ExitContainer(innerContainer)); + + return reader.ExitContainer(container); + } + public: static constexpr size_t kMaxSubjects = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_SUBJECTS_PER_ENTRY; static constexpr size_t kMaxTargets = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_TARGETS_PER_ENTRY; @@ -1020,6 +1135,12 @@ class AccessControlDelegate : public AccessControl::Delegate EntryStorage::ConvertIndex(*index, *fabricIndex, EntryStorage::ConvertDirection::kAbsoluteToRelative); } } + + CHIP_ERROR saveError = SaveToFlash(); + if (saveError != CHIP_NO_ERROR && saveError != CHIP_ERROR_INCORRECT_STATE) + { + ChipLogDetail(DataManagement, "CreateEntry failed to save to flash"); + } } return err; } @@ -1044,7 +1165,16 @@ class AccessControlDelegate : public AccessControl::Delegate { if (auto * storage = EntryStorage::FindUsedInAcl(index, fabricIndex)) { - return Copy(entry, *storage); + CHIP_ERROR err = Copy(entry, *storage); + if (err == CHIP_NO_ERROR) + { + CHIP_ERROR saveError = SaveToFlash(); + if (saveError != CHIP_NO_ERROR && saveError != CHIP_ERROR_INCORRECT_STATE) + { + ChipLogDetail(DataManagement, "UpdateEntry failed to save to flash"); + } + } + return err; } return CHIP_ERROR_SENTINEL; } @@ -1080,6 +1210,12 @@ class AccessControlDelegate : public AccessControl::Delegate { delegate.FixAfterDelete(*storage); } + + CHIP_ERROR saveError = SaveToFlash(); + if (saveError != CHIP_NO_ERROR && saveError != CHIP_ERROR_INCORRECT_STATE) + { + ChipLogDetail(DataManagement, "DeleteEntry failed to save to flash"); + } return CHIP_NO_ERROR; } return CHIP_ERROR_SENTINEL; @@ -1095,10 +1231,67 @@ class AccessControlDelegate : public AccessControl::Delegate return CHIP_ERROR_BUFFER_TOO_SMALL; } +public: + void SetStorageDelegate(chip::PersistentStorageDelegate * storageDelegate) { mStorageDelegate = storageDelegate; } + private: - CHIP_ERROR LoadFromFlash() { return CHIP_NO_ERROR; } + chip::PersistentStorageDelegate * mStorageDelegate = nullptr; + + // The version of the storage data format. Increment this key when the format of the data model changes. + static const uint32_t kExampleAclStorageVersion = 1; + // This value was chosen to be large enough to contain the data, but has not been fine-tuned. + static const size_t kStorageBufferSize = 32; + static constexpr uint8_t kTagVersion = 1; + + CHIP_ERROR LoadFromFlash() + { + VerifyOrReturnError(mStorageDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE); + + uint8_t buffer[kStorageBufferSize] = { 0 }; + uint16_t size = static_cast(sizeof(buffer)); + chip::DefaultStorageKeyAllocator key; + ReturnErrorOnFailure(mStorageDelegate->SyncGetKeyValue(key.AccessControlList(), buffer, size)); - CHIP_ERROR SaveToFlash() { return CHIP_NO_ERROR; } + chip::TLV::TLVReader reader; + reader.Init(buffer, size); + + ReturnErrorOnFailure(reader.Next(chip::TLV::kTLVType_Structure, chip::TLV::AnonymousTag())); + + chip::TLV::TLVType container; + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagVersion))); + uint32_t version; + ReturnErrorOnFailure(reader.Get(version)); + VerifyOrReturnError(version == kExampleAclStorageVersion, CHIP_ERROR_VERSION_MISMATCH); + + for (size_t i = 0; i < EntryStorage::kNumberOfFabrics * EntryStorage::kEntriesPerFabric; ++i) + { + ReturnErrorOnFailure(EntryStorage::acl[i].Deserialize(mStorageDelegate, key.AccessControlEntry(i))); + } + return reader.ExitContainer(container); + } + + CHIP_ERROR SaveToFlash() + { + VerifyOrReturnError(mStorageDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE); + + uint8_t buffer[kStorageBufferSize] = { 0 }; + chip::TLV::TLVWriter writer; + writer.Init(buffer); + chip::DefaultStorageKeyAllocator key; + chip::TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::TLVType::kTLVType_Structure, container)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagVersion), kExampleAclStorageVersion)); + for (size_t i = 0; i < EntryStorage::kNumberOfFabrics * EntryStorage::kEntriesPerFabric; ++i) + { + ReturnErrorOnFailure(EntryStorage::acl[i].Serialize(mStorageDelegate, key.AccessControlEntry(i))); + } + ReturnErrorOnFailure(writer.EndContainer(container)); + ReturnErrorOnFailure(writer.Finalize()); + + return mStorageDelegate->SyncSetKeyValue(key.AccessControlList(), buffer, static_cast(writer.GetLengthWritten())); + } }; static_assert(std::is_pod(), "Storage type must be POD"); @@ -1122,6 +1315,13 @@ AccessControl::Delegate & GetAccessControlDelegate() return accessControlDelegate; } +void SetAccessControlDelegateStorage(chip::PersistentStorageDelegate * storageDelegate) +{ + ChipLogDetail(DataManagement, "Examples::SetAccessControlDelegateStorage"); + AccessControlDelegate & accessControlDelegate = static_cast(GetAccessControlDelegate()); + accessControlDelegate.SetStorageDelegate(storageDelegate); +} + } // namespace Examples } // namespace Access } // namespace chip diff --git a/src/access/examples/ExampleAccessControlDelegate.h b/src/access/examples/ExampleAccessControlDelegate.h index f411de1339df3c..e2a3bd04d913e3 100644 --- a/src/access/examples/ExampleAccessControlDelegate.h +++ b/src/access/examples/ExampleAccessControlDelegate.h @@ -17,6 +17,7 @@ #pragma once #include "access/AccessControl.h" +#include namespace chip { namespace Access { @@ -24,6 +25,8 @@ namespace Examples { AccessControl::Delegate & GetAccessControlDelegate(); +void SetAccessControlDelegateStorage(chip::PersistentStorageDelegate * storageDelegate); + } // namespace Examples } // namespace Access } // namespace chip diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index 886f0807f797c0..082e440a0d5d14 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -37,6 +37,11 @@ class DefaultStorageKeyAllocator const char * FabricTable(chip::FabricIndex fabric) { return Format("f/%x/t", fabric); } + // Access Control List + + const char * AccessControlList() { return Format("acl"); } + const char * AccessControlEntry(size_t index) { return Format("acl/%zx", index); } + // Group Data Provider const char * FabricTable() { return Format("f/t"); }