From 086bdbb78542a4689e9320d2561cb89a724f3a06 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 6 Apr 2021 22:51:29 -0400 Subject: [PATCH] KVS implementation for darwin (#5668) * Added a darwin KVS implementation * Restyle fixes * Fix darwin build, fix relative path handling in darwin KVS * Add coredata dependency in CHIP project, remove obsolete logging style gn argument * Restyle fixes * Remove commented out code --- .../Framework/CHIP.xcodeproj/project.pbxproj | 2 + .../Framework/chip_xcode_build_connector.sh | 1 - src/platform/BUILD.gn | 8 +- .../Darwin/KeyValueStoreManagerImpl.cpp | 34 --- .../Darwin/KeyValueStoreManagerImpl.h | 14 +- .../Darwin/KeyValueStoreManagerImpl.mm | 256 ++++++++++++++++++ 6 files changed, 265 insertions(+), 50 deletions(-) delete mode 100644 src/platform/Darwin/KeyValueStoreManagerImpl.cpp create mode 100644 src/platform/Darwin/KeyValueStoreManagerImpl.mm diff --git a/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj b/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj index 67b1a476f4b7f4..ab74578903dc7c 100644 --- a/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj @@ -546,6 +546,8 @@ LIBRARY_SEARCH_PATHS = "$(TEMP_DIR)/out/lib"; OTHER_LDFLAGS = ""; "OTHER_LDFLAGS[sdk=*]" = ( + "-framework", + CoreData, "-framework", Foundation, "-framework", diff --git a/src/darwin/Framework/chip_xcode_build_connector.sh b/src/darwin/Framework/chip_xcode_build_connector.sh index eec34838990df0..4c263b9fe7a74b 100755 --- a/src/darwin/Framework/chip_xcode_build_connector.sh +++ b/src/darwin/Framework/chip_xcode_build_connector.sh @@ -87,7 +87,6 @@ done declare -a args=( 'default_configs_cosmetic=[]' # suppress colorization 'chip_crypto="mbedtls"' - 'chip_logging_style="darwin"' 'chip_build_tools=false' 'chip_build_tests=false' 'chip_ble_project_config_include=""' diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index 46d1dd88c9721c..2202ea470df74b 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -171,6 +171,7 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { config("platform_config") { if (chip_device_platform == "darwin") { frameworks = [ + "CoreData.framework", "CoreFoundation.framework", "CoreBluetooth.framework", "Foundation.framework", @@ -312,6 +313,8 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { "Darwin/ConnectivityManagerImpl.cpp", "Darwin/ConnectivityManagerImpl.h", "Darwin/InetPlatformConfig.h", + "Darwin/KeyValueStoreManagerImpl.h", + "Darwin/KeyValueStoreManagerImpl.mm", "Darwin/Logging.cpp", "Darwin/MdnsError.cpp", "Darwin/MdnsError.h", @@ -324,11 +327,6 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { "Darwin/SystemPlatformConfig.h", ] - sources += [ - "Darwin/KeyValueStoreManagerImpl.cpp", - "Darwin/KeyValueStoreManagerImpl.h", - ] - if (chip_enable_ble) { sources += [ "Darwin/BleApplicationDelegate.h", diff --git a/src/platform/Darwin/KeyValueStoreManagerImpl.cpp b/src/platform/Darwin/KeyValueStoreManagerImpl.cpp deleted file mode 100644 index 40c3fdeecb23f1..00000000000000 --- a/src/platform/Darwin/KeyValueStoreManagerImpl.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * - * 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 - */ - -#include - -namespace chip { -namespace DeviceLayer { -namespace PersistedStorage { - -KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance; - -} // namespace PersistedStorage -} // namespace DeviceLayer -} // namespace chip diff --git a/src/platform/Darwin/KeyValueStoreManagerImpl.h b/src/platform/Darwin/KeyValueStoreManagerImpl.h index c72b9c8bf51d08..80c75c17b386fb 100644 --- a/src/platform/Darwin/KeyValueStoreManagerImpl.h +++ b/src/platform/Darwin/KeyValueStoreManagerImpl.h @@ -34,16 +34,10 @@ class KeyValueStoreManagerImpl final : public KeyValueStoreManager friend class KeyValueStoreManager; public: - // NOTE: Currently this platform does not support partial and offset reads - // these will return CHIP_ERROR_NOT_IMPLEMENTED. - CHIP_ERROR _Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size = nullptr, size_t offset = 0) - { - return CHIP_ERROR_NOT_IMPLEMENTED; - } - - CHIP_ERROR _Delete(const char * key) { return CHIP_ERROR_NOT_IMPLEMENTED; } - - CHIP_ERROR _Put(const char * key, const void * value, size_t value_size) { return CHIP_ERROR_NOT_IMPLEMENTED; } + CHIP_ERROR Init(const char * fileName); + CHIP_ERROR _Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size = nullptr, size_t offset = 0); + CHIP_ERROR _Delete(const char * key); + CHIP_ERROR _Put(const char * key, const void * value, size_t value_size); private: // ===== Members for internal use by the following friends. diff --git a/src/platform/Darwin/KeyValueStoreManagerImpl.mm b/src/platform/Darwin/KeyValueStoreManagerImpl.mm new file mode 100644 index 00000000000000..67522628721dbb --- /dev/null +++ b/src/platform/Darwin/KeyValueStoreManagerImpl.mm @@ -0,0 +1,256 @@ +/* + * + * 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 + */ + +#include + +#include + +#include + +#import +#import + +@interface KeyValueItem : NSManagedObject + +@property (nonatomic, retain) NSString * key; +@property (nonatomic, retain) NSData * value; + +@end + +@implementation KeyValueItem + +@synthesize key; +@synthesize value; + +- (instancetype)initWithContext:(nonnull NSManagedObjectContext *)context + key:(nonnull NSString *)key_ + value:(nonnull NSData *)value_ +{ + if (self = [super initWithContext:context]) { + key = key_; + 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) + { + NSFetchRequest * request = [[NSFetchRequest alloc] initWithEntityName:@"KeyValue"]; + request.predicate = [NSPredicate predicateWithFormat:@"key = %@", key]; + + NSArray * result = [gContext executeFetchRequest:request error:error]; + if (result == nil) { + return nullptr; + } + + if (result.count == 0) { + return nullptr; + } + return (KeyValueItem *) [result objectAtIndex:0]; + } + + } + + KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance; + + CHIP_ERROR KeyValueStoreManagerImpl::Init(const char * fileName) + { + ReturnErrorCodeIf(gContext != nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(fileName == nullptr, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(fileName[0] == '\0', CHIP_ERROR_INVALID_ARGUMENT); + + NSURL * url = nullptr; + + // relative paths are relative to Documents folder + if (fileName[0] != '/') { + 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:[NSString stringWithUTF8String:fileName] relativeToURL:documentsDirectory]; + } else { + url = [NSURL URLWithString:[NSString stringWithUTF8String:fileName]]; + } + ReturnErrorCodeIf(url == nullptr, CHIP_ERROR_NO_MEMORY); + + ChipLogProgress(DeviceLayer, "KVS will be written to: %s", [[url absoluteString] UTF8String]); + + NSManagedObjectModel * model = CreateManagedObjectModel(); + ReturnErrorCodeIf(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:NSMainQueueConcurrencyType]; + [gContext setPersistentStoreCoordinator:coordinator]; + + return CHIP_NO_ERROR; + } + + CHIP_ERROR KeyValueStoreManagerImpl::_Get( + const char * key, void * value, size_t value_size, size_t * read_bytes_size, size_t offset) + { + ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(offset != 0, CHIP_ERROR_INVALID_ARGUMENT); + + KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil); + if (!item) { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + if (read_bytes_size != nullptr) { + *read_bytes_size = item.value.length; + } + + if (value != nullptr) { + memcpy(value, item.value.bytes, std::min(item.value.length, value_size)); + } + + return CHIP_NO_ERROR; + } + + CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key) + { + ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil); + if (!item) { + return CHIP_NO_ERROR; + } + + [gContext deleteObject:item]; + + NSError * error = nil; + if (![gContext save:&error]) { + ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String); + return CHIP_ERROR_INTERNAL; + } + + return CHIP_NO_ERROR; + } + + CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size) + { + ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + NSData * data = [[NSData alloc] initWithBytes:value length:value_size]; + + KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil); + if (!item) { + item = [[KeyValueItem alloc] initWithContext:gContext key:[[NSString alloc] initWithUTF8String:key] value:data]; + [gContext insertObject:item]; + } else { + item.value = data; + } + + NSError * error = nil; + if (![gContext save:&error]) { + ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String); + return CHIP_ERROR_INTERNAL; + } + + return CHIP_NO_ERROR; + } + + } // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip