blob: 1521a8c54f672617884c3406e5ff435bf5ecc901 [file] [log] [blame]
/**
* 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 "MTRDemuxingStorage.h"
#import "MTRDeviceControllerFactory_Internal.h"
#import "MTRDeviceController_Internal.h"
#import "MTRLogging_Internal.h"
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <platform/LockTracker.h>
using namespace chip;
static bool IsGlobalKey(NSString * key) { return [key hasPrefix:@"g/"]; }
/**
* Checks for a key that is scoped to a specific fabric index.
*/
static bool IsIndexSpecificKey(NSString * key) { return [key hasPrefix:@"f/"]; }
/**
* Extracts the fabric index from an index-specific key. Fails if the key
* is not index-specific or if a numeric FabricIndex could not be extracted
* from it.
*/
static CHIP_ERROR ExtractIndexFromKey(NSString * key, FabricIndex * index)
{
if (!IsIndexSpecificKey(key)) {
return CHIP_ERROR_INVALID_ARGUMENT;
}
auto * components = [key componentsSeparatedByString:@"/"];
if (components.count < 3) {
// Unexpected "f/something" without any actual data.
return CHIP_ERROR_INVALID_ARGUMENT;
}
auto * indexString = components[1];
auto * scanner = [NSScanner scannerWithString:indexString];
auto * charset = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
charset = [charset invertedSet];
if ([scanner scanCharactersFromSet:charset intoString:nil] == YES) {
// Leading non-hex chars.
return CHIP_ERROR_INVALID_ARGUMENT;
}
unsigned int value;
if ([scanner scanHexInt:&value] == NO) {
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (scanner.atEnd == NO) {
// Trailing garbage chars.
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (!CanCastTo<FabricIndex>(value)) {
return CHIP_ERROR_INVALID_ARGUMENT;
}
*index = static_cast<FabricIndex>(value);
return CHIP_NO_ERROR;
}
/**
* Extracts the "index-specific" part of an index-specific key (i.e. the
* part after "f/index/").
*/
static CHIP_ERROR ExtractIndexSpecificKey(NSString * key, NSString * __autoreleasing * extractedKey)
{
if (!IsIndexSpecificKey(key)) {
return CHIP_ERROR_INVALID_ARGUMENT;
}
auto * components = [key componentsSeparatedByString:@"/"];
if (components.count < 3) {
// Unexpected "f/something" without any actual data.
return CHIP_ERROR_INVALID_ARGUMENT;
}
components = [components subarrayWithRange:NSMakeRange(2, components.count - 2)];
*extractedKey = [components componentsJoinedByString:@"/"];
return CHIP_NO_ERROR;
}
/**
* Method to test whether a global key should be stored in memory only, as
* opposed to being passed on to the actual storage related to controllers.
*/
static bool IsMemoryOnlyGlobalKey(NSString * key)
{
if ([key isEqualToString:@"g/fidx"]) {
// Fabric index list only needs to be stored in-memory, not persisted,
// because we do not tie controllers to specific fabric indices.
return true;
}
if ([key isEqualToString:@"g/fs/c"] || [key isEqualToString:@"g/fs/n"]) {
// Just store the fail-safe markers in memory as well. We could
// plausibly not store them at all, since we never actually need to
// clean up anything by fabric index, but this is safer in case
// consumers try to read back right after writing.
return true;
}
if ([key isEqualToString:@"g/lkgt"]) {
// Store Last Known Good Time in memory only. We never need this in
// general, because we can always provide the wall-clock time.
return true;
}
// We do not expect to see the "g/sri" or "g/s/*" keys for session
// resumption, because we implement SessionResumptionStorage ourselves.
// For now, put group global counters in memory.
// TODO: we should inject a group counter manager that makes these counters
// per-controller, not globally handled via storage.
// See https://github.com/project-chip/connectedhomeip/issues/28510
if ([key isEqualToString:@"g/gdc"] || [key isEqualToString:@"g/gcc"]) {
return true;
}
// We do not expect to see "g/userlbl/*" User Label keys.
// We do not expect to see the "g/gfl" key for endpoint-to-group
// associations.
// We do not expect to see the "g/a/*" keys for attribute values.
// We do not expect to see the "g/bt" and "g/bt/*" keys for the binding
// table.
// We do not expect to see the "g/o/*" OTA Requestor keys.
// We do not expect to see the "g/im/ec" event number counter key.
// We do not expect to see the "g/su/*" and "g/sum" keys for server-side
// subscription resumption storage.
// We do not expect to see the "g/scc/*" scenes keys.
// We do not expect to see the "g/ts/tts", "g/ts/dntp", "g/ts/tz",
// "g/ts/dsto" Time Synchronization keys.
// We do not expect to see the "g/icd/cic" key; that's only used for an ICD
// that sends check-in messages.
return false;
}
/**
* Method to test whether an index-specific key should be stored in memory only, as
* opposed to being passed on to the actual storage related to controllers.
* The key string will have the "f/index/" bit already stripped off the
* front of the key.
*/
static bool IsMemoryOnlyIndexSpecificKey(NSString * key)
{
// Store all the fabric table bits in memory only. This is done because the
// fabric table expects none of these things to be stored in the case of a
// "new fabric addition", which is what we always do when using
// per-controller storage.
//
// TODO: Figure out which, if any, of these things we should also store for
// later recall when starting a controller with storage we have used before.
//
// For future reference:
//
// n == NOC
// i == ICAC
// r == RCAC
// m == Fabric metadata (TLV containing the vendor ID)
// o == operational key, only written if internally generated.
if ([key isEqualToString:@"n"] || [key isEqualToString:@"i"] || [key isEqualToString:@"r"] || [key isEqualToString:@"m"] ||
[key isEqualToString:@"o"]) {
return true;
}
// We do not expect to see the "s/*" keys for session resumption, because we
// implement SessionResumptionStorage ourselves.
// We do not expect to see the "ac/*" keys for ACL entries.
// We do not expect to see the "g" or "g/*" keys for which endpoints are in which
// group.
// For now, just store group keysets and group keys in memory.
// TODO: We want to start persisting these, per-controller, if we're going
// to support group keys. Or inject a GroupDataProvider of our own instead
// of using Credentials::GroupDataProviderImp and then
// not be tied to whatever storage format that uses.
// https://github.com/project-chip/connectedhomeip/issues/28511
if ([key hasPrefix:@"gk/"] || [key hasPrefix:@"k/"]) {
return true;
}
// We do not expect to see the "icd/*" keys for the ICD Management table.
// We do not expect to see the "e/*" scenes keys.
return false;
}
/**
* Method to convert an index-specific key into a fully qualified key.
*/
static NSString * FullyQualifiedKey(FabricIndex index, NSString * key)
{
return [NSString stringWithFormat:@"f/%x/%s", index, key.UTF8String];
}
MTRDemuxingStorage::MTRDemuxingStorage(MTRDeviceControllerFactory * factory)
: mFactory(factory)
{
mInMemoryStore = [[NSMutableDictionary alloc] init];
}
CHIP_ERROR MTRDemuxingStorage::SyncGetKeyValue(const char * key, void * buffer, uint16_t & size)
{
assertChipStackLockedByCurrentThread();
if (buffer == nullptr && size != 0) {
return CHIP_ERROR_INVALID_ARGUMENT;
}
NSString * keyString = [NSString stringWithUTF8String:key];
#if LOG_DEBUG_PERSISTENT_STORAGE_DELEGATE
MTR_LOG_DEBUG("MTRDemuxingStorage Sync Get Value for Key: %@", keyString);
#endif
NSData * value;
if (IsGlobalKey(keyString)) {
value = GetGlobalValue(keyString);
} else if (IsIndexSpecificKey(keyString)) {
FabricIndex index;
ReturnErrorOnFailure(ExtractIndexFromKey(keyString, &index));
ReturnErrorOnFailure(ExtractIndexSpecificKey(keyString, &keyString));
value = GetIndexSpecificValue(index, keyString);
} else {
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (value == nil) {
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
if (value.length > UINT16_MAX) {
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
uint16_t valueSize = static_cast<uint16_t>(value.length);
if (valueSize > size) {
return CHIP_ERROR_BUFFER_TOO_SMALL;
}
size = valueSize;
if (size != 0) {
// buffer is known to be non-null here.
memcpy(buffer, value.bytes, size);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR MTRDemuxingStorage::SyncSetKeyValue(const char * key, const void * value, uint16_t size)
{
if (value == nullptr && size != 0) {
return CHIP_ERROR_INVALID_ARGUMENT;
}
NSString * keyString = [NSString stringWithUTF8String:key];
NSData * valueData = (value == nullptr) ? [NSData data] : [NSData dataWithBytes:value length:size];
#if LOG_DEBUG_PERSISTENT_STORAGE_DELEGATE
MTR_LOG_DEBUG("MTRDemuxingStorage Set Key %@", keyString);
#endif
if (IsGlobalKey(keyString)) {
return SetGlobalValue(keyString, valueData);
}
if (IsIndexSpecificKey(keyString)) {
FabricIndex index;
ReturnErrorOnFailure(ExtractIndexFromKey(keyString, &index));
ReturnErrorOnFailure(ExtractIndexSpecificKey(keyString, &keyString));
return SetIndexSpecificValue(index, keyString, valueData);
}
return CHIP_ERROR_INVALID_ARGUMENT;
}
CHIP_ERROR MTRDemuxingStorage::SyncDeleteKeyValue(const char * key)
{
NSString * keyString = [NSString stringWithUTF8String:key];
#if LOG_DEBUG_PERSISTENT_STORAGE_DELEGATE
MTR_LOG_DEBUG("MTRDemuxingStorage Delete Key: %@", keyString);
#endif
if (IsGlobalKey(keyString)) {
return DeleteGlobalValue(keyString);
}
if (IsIndexSpecificKey(keyString)) {
FabricIndex index;
ReturnErrorOnFailure(ExtractIndexFromKey(keyString, &index));
ReturnErrorOnFailure(ExtractIndexSpecificKey(keyString, &keyString));
return DeleteIndexSpecificValue(index, keyString);
}
return CHIP_ERROR_INVALID_ARGUMENT;
}
NSData * _Nullable MTRDemuxingStorage::GetGlobalValue(NSString * key)
{
if (IsMemoryOnlyGlobalKey(key)) {
return GetInMemoryValue(key);
}
MTR_LOG_ERROR("MTRDemuxingStorage reading unknown global key: %@", key);
return nil;
}
NSData * _Nullable MTRDemuxingStorage::GetIndexSpecificValue(FabricIndex index, NSString * key)
{
if (IsMemoryOnlyIndexSpecificKey(key)) {
return GetInMemoryValue(FullyQualifiedKey(index, key));
}
return nil;
}
CHIP_ERROR MTRDemuxingStorage::SetGlobalValue(NSString * key, NSData * data)
{
if (IsMemoryOnlyGlobalKey(key)) {
// Fabric index list only needs to be stored in-memory, not persisted,
// because we do not tie controllers to specific fabric indices.
return SetInMemoryValue(key, data);
}
MTR_LOG_ERROR("MTRDemuxingStorage setting unknown global key: %@", key);
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
CHIP_ERROR MTRDemuxingStorage::SetIndexSpecificValue(FabricIndex index, NSString * key, NSData * data)
{
if ([key isEqualToString:@"n"]) {
// Index-scoped "n" is NOC.
auto * controller = [mFactory runningControllerForFabricIndex:index];
if (controller == nil) {
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
ReturnErrorOnFailure([controller.controllerDataStore storeLastLocallyUsedNOC:data]);
}
if (IsMemoryOnlyIndexSpecificKey(key)) {
return SetInMemoryValue(FullyQualifiedKey(index, key), data);
}
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
CHIP_ERROR MTRDemuxingStorage::DeleteGlobalValue(NSString * key)
{
if (IsMemoryOnlyGlobalKey(key)) {
return DeleteInMemoryValue(key);
}
MTR_LOG_ERROR("MTRDemuxingStorage deleting unknown global key: %@", key);
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
CHIP_ERROR MTRDemuxingStorage::DeleteIndexSpecificValue(FabricIndex index, NSString * key)
{
if (IsMemoryOnlyIndexSpecificKey(key)) {
return DeleteInMemoryValue(FullyQualifiedKey(index, key));
}
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
NSData * _Nullable MTRDemuxingStorage::GetInMemoryValue(NSString * key) { return mInMemoryStore[key]; }
CHIP_ERROR MTRDemuxingStorage::SetInMemoryValue(NSString * key, NSData * data)
{
mInMemoryStore[key] = data;
return CHIP_NO_ERROR;
}
CHIP_ERROR MTRDemuxingStorage::DeleteInMemoryValue(NSString * key)
{
BOOL present = (mInMemoryStore[key] != nil);
if (present) {
[mInMemoryStore removeObjectForKey:key];
}
return present ? CHIP_NO_ERROR : CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}