Skip to content

Commit

Permalink
Add storage to the example ACL implementation (#14253)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
harimau-qirex authored and pull[bot] committed Feb 8, 2024
1 parent 127fdad commit 3731651
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 3 deletions.
27 changes: 27 additions & 0 deletions examples/platform/linux/AppMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#include <credentials/examples/DefaultDeviceAttestationVerifier.h>
#include <credentials/examples/DeviceAttestationCredsExample.h>

#include <access/examples/ExampleAccessControlDelegate.h>

#include <lib/support/CHIPMem.h>
#include <lib/support/ScopedBuffer.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.");
Expand Down
206 changes: 203 additions & 3 deletions src/access/examples/ExampleAccessControlDelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
#include "ExampleAccessControlDelegate.h"

#include <lib/core/CHIPConfig.h>
#include <lib/core/CHIPTLV.h>
#include <lib/support/DefaultStorageKeyAllocator.h>

#include <algorithm>
#include <cstdint>
#include <string>
#include <type_traits>

namespace {
Expand Down Expand Up @@ -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; }

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<uint16_t>(writer.GetLengthWritten()));
}

CHIP_ERROR Deserialize(chip::PersistentStorageDelegate * storage, const char * key)
{
uint8_t buffer[kStorageBufferSize] = { 0 };
uint16_t bufferSize = static_cast<uint16_t>(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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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<uint16_t>(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<uint16_t>(writer.GetLengthWritten()));
}
};

static_assert(std::is_pod<SubjectStorage>(), "Storage type must be POD");
Expand All @@ -1122,6 +1315,13 @@ AccessControl::Delegate & GetAccessControlDelegate()
return accessControlDelegate;
}

void SetAccessControlDelegateStorage(chip::PersistentStorageDelegate * storageDelegate)
{
ChipLogDetail(DataManagement, "Examples::SetAccessControlDelegateStorage");
AccessControlDelegate & accessControlDelegate = static_cast<AccessControlDelegate &>(GetAccessControlDelegate());
accessControlDelegate.SetStorageDelegate(storageDelegate);
}

} // namespace Examples
} // namespace Access
} // namespace chip
3 changes: 3 additions & 0 deletions src/access/examples/ExampleAccessControlDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
#pragma once

#include "access/AccessControl.h"
#include <lib/core/CHIPPersistentStorageDelegate.h>

namespace chip {
namespace Access {
namespace Examples {

AccessControl::Delegate & GetAccessControlDelegate();

void SetAccessControlDelegateStorage(chip::PersistentStorageDelegate * storageDelegate);

} // namespace Examples
} // namespace Access
} // namespace chip
5 changes: 5 additions & 0 deletions src/lib/support/DefaultStorageKeyAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"); }
Expand Down

0 comments on commit 3731651

Please sign in to comment.