blob: d59610eaf7a731def2df75a86969e1f89f71e79f [file] [log] [blame]
/*
*
* Copyright (c) 2021 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
* Platform-specific key value storage implementation for Darwin
*/
#if !__has_feature(objc_arc)
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
#include <platform/KeyValueStoreManager.h>
#include <algorithm>
#include <lib/support/CodeUtils.h>
#import <CoreData/CoreData.h>
#import <CoreFoundation/CoreFoundation.h>
#ifndef CHIP_CONFIG_DARWIN_STORAGE_VERBOSE_LOGGING
#define CHIP_CONFIG_DARWIN_STORAGE_VERBOSE_LOGGING 0
#endif // CHIP_CONFIG_DARWIN_STORAGE_VERBOSE_LOGGING
@interface KeyValueItem : NSManagedObject
@property (nonatomic, retain) NSString * key;
@property (nonatomic, retain) NSData * value;
@end
@implementation KeyValueItem
@dynamic key;
@dynamic value;
- (instancetype)initWithContext:(nonnull NSManagedObjectContext *)context key:(nonnull NSString *)key value:(nonnull NSData *)value
{
if (self = [super initWithContext:context]) {
self.key = key;
self.value = value;
}
return self;
}
@end
namespace chip {
namespace DeviceLayer {
namespace PersistedStorage {
namespace {
NSManagedObjectContext * gContext = nullptr;
NSManagedObjectModel * CreateManagedObjectModel()
{
NSManagedObjectModel * model = [[NSManagedObjectModel alloc] init];
// create the entity
NSEntityDescription * entity = [[NSEntityDescription alloc] init];
[entity setName:@"KeyValue"];
[entity setManagedObjectClassName:@"KeyValueItem"];
// create the attributes
NSMutableArray * properties = [NSMutableArray array];
NSAttributeDescription * keyAttribute = [[NSAttributeDescription alloc] init];
[keyAttribute setName:@"key"];
[keyAttribute setAttributeType:NSStringAttributeType];
[keyAttribute setOptional:NO];
[properties addObject:keyAttribute];
NSAttributeDescription * valueAttribute = [[NSAttributeDescription alloc] init];
[valueAttribute setName:@"value"];
[valueAttribute setAttributeType:NSBinaryDataAttributeType];
[valueAttribute setOptional:NO];
[properties addObject:valueAttribute];
NSFetchIndexElementDescription * elementIndex =
[[NSFetchIndexElementDescription alloc] initWithProperty:keyAttribute
collationType:NSFetchIndexElementTypeBinary];
elementIndex.ascending = true;
NSFetchIndexDescription * keyIndexDescription =
[[NSFetchIndexDescription alloc] initWithName:@"kv_item_key"
elements:[[NSArray alloc] initWithObjects:elementIndex, nil]];
// add attributes to entity
[entity setProperties:properties];
[entity setIndexes:[[NSArray alloc] initWithObjects:keyIndexDescription, nil]];
// add entity to model
[model setEntities:[NSArray arrayWithObject:entity]];
return model;
}
KeyValueItem * FindItemForKey(NSString * key, NSError ** error, BOOL returnsData)
{
NSFetchRequest * request = [[NSFetchRequest alloc] initWithEntityName:@"KeyValue"];
if (returnsData) {
[request setReturnsObjectsAsFaults:NO];
}
request.predicate = [NSPredicate predicateWithFormat:@"key = %@", key];
__block NSError * fetchError = nil;
__block NSArray * result;
[gContext performBlockAndWait:^{
result = [gContext executeFetchRequest:request error:&fetchError];
}];
if (error != nil) {
*error = fetchError;
}
if (result == nil) {
return nullptr;
}
if (result.count == 0) {
return nullptr;
}
return (KeyValueItem *) [result objectAtIndex:0];
}
KeyValueItem * FindItemForKey(NSString * key, NSError ** error) { return FindItemForKey(key, error, false); }
} // namespace
KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance;
CHIP_ERROR KeyValueStoreManagerImpl::Init(const char * fileName)
{
@autoreleasepool {
if (mInitialized) {
return CHIP_NO_ERROR;
}
VerifyOrReturnError(gContext == nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(fileName != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(fileName[0] != '\0', CHIP_ERROR_INVALID_ARGUMENT);
NSURL * url = nullptr;
NSString * filepath = [NSString stringWithUTF8String:fileName];
VerifyOrReturnError(filepath != nil, CHIP_ERROR_INVALID_ARGUMENT);
// relative paths are relative to Documents folder
if (![filepath hasPrefix:@"/"]) {
NSURL * documentsDirectory = [NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil];
if (documentsDirectory == nullptr) {
ChipLogError(DeviceLayer, "Failed to get documents directory.");
return CHIP_ERROR_INTERNAL;
}
ChipLogProgress(
DeviceLayer, "Found user documents directory: %s", [[documentsDirectory absoluteString] UTF8String]);
url = [NSURL URLWithString:filepath relativeToURL:documentsDirectory];
} else {
url = [NSURL fileURLWithPath:filepath];
}
VerifyOrReturnError(url != nullptr, CHIP_ERROR_NO_MEMORY);
ChipLogProgress(DeviceLayer, "KVS will be written to: %s", [[url absoluteString] UTF8String]);
NSManagedObjectModel * model = CreateManagedObjectModel();
VerifyOrReturnError(model != nullptr, CHIP_ERROR_NO_MEMORY);
// setup persistent store coordinator
NSPersistentStoreCoordinator * coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSError * error = nil;
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) {
ChipLogError(DeviceLayer, "Invalid store. Attempting to clear: %s", error.localizedDescription.UTF8String);
if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) {
ChipLogError(DeviceLayer, "Failed to delete item: %s", error.localizedDescription.UTF8String);
}
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:nil
error:&error]) {
ChipLogError(DeviceLayer, "Failed to initialize clear KVS storage: %s", error.localizedDescription.UTF8String);
chipDie();
}
}
// create Managed Object context
gContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[gContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[gContext setPersistentStoreCoordinator:coordinator];
mInitialized = true;
return CHIP_NO_ERROR;
} // @autoreleasepool
}
CHIP_ERROR KeyValueStoreManagerImpl::_Get(
const char * key, void * value, size_t value_size, size_t * read_bytes_size, size_t offset)
{
@autoreleasepool {
VerifyOrReturnError(key != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(offset == 0, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(gContext != nullptr, CHIP_ERROR_UNINITIALIZED);
KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil, true);
if (!item) {
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
__block NSData * itemValue = nil;
// can only access this object on the managed queue
[gContext performBlockAndWait:^{
itemValue = item.value;
}];
if (read_bytes_size != nullptr) {
*read_bytes_size = itemValue.length;
}
if (value != nullptr) {
memcpy(value, itemValue.bytes, std::min<size_t>((itemValue.length), value_size));
#if CHIP_CONFIG_DARWIN_STORAGE_VERBOSE_LOGGING
fprintf(stderr, "GETTING VALUE FOR: '%s': ", key);
for (size_t i = 0; i < std::min<size_t>((itemValue.length), value_size); ++i) {
fprintf(stderr, "%02x ", static_cast<uint8_t *>(value)[i]);
}
fprintf(stderr, "\n");
#endif
}
if (itemValue.length > value_size) {
return CHIP_ERROR_BUFFER_TOO_SMALL;
}
return CHIP_NO_ERROR;
} // @autoreleasepool
}
CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key)
{
@autoreleasepool {
VerifyOrReturnError(key != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(gContext != nullptr, CHIP_ERROR_UNINITIALIZED);
KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil);
if (!item) {
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
__block BOOL success = NO;
__block NSError * error = nil;
[gContext performBlockAndWait:^{
[gContext deleteObject:item];
success = [gContext save:&error];
}];
if (!success) {
ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String);
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
return CHIP_NO_ERROR;
} // @autoreleasepool
}
CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size)
{
@autoreleasepool {
VerifyOrReturnError(key != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(gContext != nullptr, CHIP_ERROR_UNINITIALIZED);
NSData * data = [[NSData alloc] initWithBytes:value length:value_size];
NSString * itemKey = [[NSString alloc] initWithUTF8String:key];
VerifyOrReturnError(itemKey != nil, CHIP_ERROR_INVALID_ARGUMENT);
KeyValueItem * item = FindItemForKey(itemKey, nil);
if (!item) {
[gContext performBlockAndWait:^{
[gContext insertObject:[[KeyValueItem alloc] initWithContext:gContext key:itemKey value:data]];
}];
} else {
[gContext performBlockAndWait:^{
item.value = data;
}];
}
__block BOOL success = NO;
__block NSError * error = nil;
[gContext performBlockAndWait:^{
success = [gContext save:&error];
}];
if (!success) {
ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String);
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
#if CHIP_CONFIG_DARWIN_STORAGE_VERBOSE_LOGGING
fprintf(stderr, "PUT VALUE FOR: '%s': ", key);
for (size_t i = 0; i < value_size; ++i) {
fprintf(stderr, "%02x ", static_cast<const uint8_t *>(value)[i]);
}
fprintf(stderr, "\n");
#endif
return CHIP_NO_ERROR;
} // @autoreleasepool
}
} // namespace PersistedStorage
} // namespace DeviceLayer
} // namespace chip