blob: fffdf8e5d228f95e4e1032e8e46dd4f1baad0d73 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Google LLC.
* 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
* This file implements NLWdmClient interface.
* This is WEAVE_CONFIG_DATA_MANAGEMENT_EXPERIMENTAL feature.
*
*/
#import <Foundation/Foundation.h>
#import "NLWeaveStack.h"
#import "NLLogging.h"
#include <Weave/Core/WeaveCore.h>
#include <Weave/Core/WeaveError.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/NLDLLUtil.h>
#include <Weave/Profiles/WeaveProfiles.h>
#include <Weave/Profiles/common/CommonProfile.h>
#include <Weave/Profiles/data-management/Current/WdmManagedNamespace.h>
#include <Weave/Profiles/data-management/DataManagement.h>
#include <Weave/Profiles/data-management/SubscriptionClient.h>
#include <WeaveDeviceManager.h>
#include <WeaveDataManagementClient.h>
#import "NLWeaveDeviceManager_Protected.h"
#import "NLProfileStatusError.h"
#import "NLWeaveError_Protected.h"
#import "NLWdmClient_Protected.h"
#import "NLGenericTraitUpdatableDataSink.h"
#import "NLGenericTraitUpdatableDataSink_Protected.h"
#import "NLResourceIdentifier.h"
#import "NLResourceIdentifier_Protected.h"
using namespace nl::Weave::Profiles;
using namespace nl::Weave::Profiles::DataManagement;
#if WEAVE_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL
static void bindingEventCallback(void * const apAppState, const nl::Weave::Binding::EventType aEvent,
const nl::Weave::Binding::InEventParam & aInParam, nl::Weave::Binding::OutEventParam & aOutParam);
void bindingEventCallback(void * const apAppState, const nl::Weave::Binding::EventType aEvent,
const nl::Weave::Binding::InEventParam & aInParam, nl::Weave::Binding::OutEventParam & aOutParam)
{
WDM_LOG_DEBUG(@"%s: Event(%d)", __func__, aEvent);
switch (aEvent) {
case nl::Weave::Binding::kEvent_PrepareRequested:
WDM_LOG_DEBUG(@"kEvent_PrepareRequested");
break;
case nl::Weave::Binding::kEvent_PrepareFailed:
WDM_LOG_DEBUG(@"kEvent_PrepareFailed: reason %s", ::nl::ErrorStr(aInParam.PrepareFailed.Reason));
break;
case nl::Weave::Binding::kEvent_BindingFailed:
WDM_LOG_DEBUG(@"kEvent_BindingFailed: reason %s", ::nl::ErrorStr(aInParam.PrepareFailed.Reason));
break;
case nl::Weave::Binding::kEvent_BindingReady:
WDM_LOG_DEBUG(@"kEvent_BindingReady");
break;
case nl::Weave::Binding::kEvent_DefaultCheck:
WDM_LOG_DEBUG(@"kEvent_DefaultCheck");
// fall through
default:
nl::Weave::Binding::DefaultEventHandler(apAppState, aEvent, aInParam, aOutParam);
}
}
@interface NLWdmClient () {
nl::Weave::DeviceManager::WdmClient * _mWeaveCppWdmClient;
dispatch_queue_t _mWeaveWorkQueue;
dispatch_queue_t _mAppCallbackQueue;
WdmClientCompletionBlock _mCompletionHandler;
WdmClientFailureBlock _mFailureHandler;
NSString * _mRequestName;
NSMutableDictionary * _mTraitMap;
}
- (NSString *)getCurrentRequest;
@end
@implementation NLWdmClient
@synthesize resultCallbackQueue = _mAppCallbackQueue;
/**
@note
This function can only be called by the ARC runtime
*/
- (void)dealloc
{
// This method can only be called by ARC
// Let's not rely on this unpredictable mechanism for de-initialization
// application shall call ShutdownStack if it want to cleanly destroy everything before application termination
WDM_LOG_METHOD_SIG();
[self markTransactionCompleted];
_mRequestName = @"dealloc-Shutdown";
// we need to force the queue to be Weave work queue, as dealloc could be
// called at random queues (most probably from UI, when the app de-reference this wdmClient)
dispatch_sync(_mWeaveWorkQueue, ^() {
[self shutdown_Internal];
});
}
- (instancetype)init:(NSString *)name
weaveWorkQueue:(dispatch_queue_t)weaveWorkQueue
appCallbackQueue:(dispatch_queue_t)appCallbackQueue
exchangeMgr:(nl::Weave::WeaveExchangeManager *)exchangeMgr
messageLayer:(nl::Weave::WeaveMessageLayer *)messageLayer
nlWeaveDeviceManager:(NLWeaveDeviceManager *)deviceMgr
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
long long deviceMgrCppPtr = 0;
nl::Weave::Binding * pBinding = NULL;
id result = nil;
WDM_LOG_METHOD_SIG();
self = [super init];
VerifyOrExit(nil != self, err = WEAVE_ERROR_NO_MEMORY);
_mWeaveWorkQueue = weaveWorkQueue;
_mAppCallbackQueue = appCallbackQueue;
_name = name;
WDM_LOG_DEBUG(@"NewWdmClient() called");
[deviceMgr GetDeviceMgrPtr:&deviceMgrCppPtr];
pBinding = exchangeMgr->NewBinding(bindingEventCallback, (nl::Weave::DeviceManager::WeaveDeviceManager *) deviceMgrCppPtr);
VerifyOrExit(NULL != pBinding, err = WEAVE_ERROR_NO_MEMORY);
err = ((nl::Weave::DeviceManager::WeaveDeviceManager *) deviceMgrCppPtr)->ConfigureBinding(pBinding);
SuccessOrExit(err);
_mWeaveCppWdmClient = new nl::Weave::DeviceManager::WdmClient();
VerifyOrExit(NULL != _mWeaveCppWdmClient, err = WEAVE_ERROR_NO_MEMORY);
err = _mWeaveCppWdmClient->Init(messageLayer, pBinding);
SuccessOrExit(err);
_mWeaveCppWdmClient->mpAppState = (__bridge void *) self;
_mTraitMap = [[NSMutableDictionary alloc] init];
[self markTransactionCompleted];
exit:
if (WEAVE_NO_ERROR == err) {
result = self;
} else {
WDM_LOG_ERROR(@"Error in init : (%d) %@\n", err, [NSString stringWithUTF8String:nl::ErrorStr(err)]);
[self shutdown_Internal];
if (NULL != pBinding) {
pBinding->Release();
}
}
return result;
}
static void handleWdmClientComplete(void * wdmClient, void * reqState)
{
WDM_LOG_DEBUG(@"handleWdmClientComplete");
NLWdmClient * client = (__bridge NLWdmClient *) reqState;
[client dispatchAsyncCompletionBlock:nil];
}
static void handleWdmClientError(
void * wdmClient, void * appReqState, WEAVE_ERROR code, nl::Weave::DeviceManager::DeviceStatus * devStatus)
{
WDM_LOG_DEBUG(@"handleWdmClientError");
NSError * error = nil;
NSDictionary * userInfo = nil;
NLWdmClient * client = (__bridge NLWdmClient *) appReqState;
WDM_LOG_DEBUG(@"%@: Received error response to request %@, wdmClientErr = %d, devStatus = %p\n", client.name,
[client getCurrentRequest], code, devStatus);
NLWeaveRequestError requestError;
if (devStatus != NULL && code == WEAVE_ERROR_STATUS_REPORT_RECEIVED) {
NLProfileStatusError * statusError = [[NLProfileStatusError alloc]
initWithProfileId:devStatus->StatusProfileId
statusCode:devStatus->StatusCode
errorCode:devStatus->SystemErrorCode
statusReport:[client statusReportToString:devStatus->StatusProfileId statusCode:devStatus->StatusCode]];
requestError = NLWeaveRequestError_ProfileStatusError;
userInfo = @{@"WeaveRequestErrorType" : @(requestError), @"errorInfo" : statusError};
WDM_LOG_DEBUG(@"%@: status error: %@", client.name, userInfo);
} else {
NLWeaveError * weaveError = [[NLWeaveError alloc] initWithWeaveError:code
report:[NSString stringWithUTF8String:nl::ErrorStr(code)]];
requestError = NLWeaveRequestError_WeaveError;
userInfo = @{@"WeaveRequestErrorType" : @(requestError), @"errorInfo" : weaveError};
}
error = [NSError errorWithDomain:@"com.nest.error" code:code userInfo:userInfo];
[client dispatchAsyncDefaultFailureBlock:error];
}
- (void)markTransactionCompleted
{
_mRequestName = nil;
_mCompletionHandler = nil;
_mFailureHandler = nil;
}
- (NSString *)getCurrentRequest
{
return _mRequestName;
}
- (void)dispatchAsyncFailureBlock:(WEAVE_ERROR)code taskName:(NSString *)taskName handler:(WdmClientFailureBlock)handler
{
NSError * error =
[NSError errorWithDomain:@"com.nest.error"
code:code
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:nl::ErrorStr(code)],
@"error", nil]];
[self dispatchAsyncFailureBlockWithError:error taskName:taskName handler:handler];
}
- (void)dispatchAsyncFailureBlockWithError:(NSError *)error taskName:(NSString *)taskName handler:(WdmClientFailureBlock)handler
{
if (NULL != handler) {
// we use async because we don't need to wait for completion of this final completion report
dispatch_async(_mAppCallbackQueue, ^() {
WDM_LOG_DEBUG(@"%@: Calling failure handler for %@", _name, taskName);
handler(_owner, error);
});
} else {
WDM_LOG_DEBUG(@"%@: Skipping failure handler for %@", _name, taskName);
}
}
- (void)dispatchAsyncDefaultFailureBlockWithCode:(WEAVE_ERROR)code
{
NSError * error = [NSError errorWithDomain:@"com.nest.error" code:code userInfo:nil];
[self dispatchAsyncDefaultFailureBlock:error];
}
- (void)dispatchAsyncDefaultFailureBlock:(NSError *)error
{
NSString * taskName = _mRequestName;
WdmClientFailureBlock failureHandler = _mFailureHandler;
[self markTransactionCompleted];
[self dispatchAsyncFailureBlockWithError:error taskName:taskName handler:failureHandler];
}
- (void)dispatchAsyncCompletionBlock:(id)data
{
WdmClientCompletionBlock completionHandler = _mCompletionHandler;
[self markTransactionCompleted];
if (nil != completionHandler) {
dispatch_async(_mAppCallbackQueue, ^() {
completionHandler(_owner, data);
});
}
}
- (void)dispatchAsyncResponseBlock:(id)data
{
WdmClientCompletionBlock completionHandler = _mCompletionHandler;
if (nil != completionHandler) {
dispatch_async(_mAppCallbackQueue, ^() {
completionHandler(_owner, data);
});
}
}
- (NSString *)toErrorString:(WEAVE_ERROR)err
{
WDM_LOG_METHOD_SIG();
__block NSString * msg = nil;
dispatch_sync(_mWeaveWorkQueue, ^() {
msg = [NSString stringWithUTF8String:nl::ErrorStr(err)];
});
return msg;
}
- (NSString *)statusReportToString:(NSUInteger)profileId statusCode:(NSInteger)statusCode
{
WDM_LOG_METHOD_SIG();
NSString * report = nil;
const char * result = nl::StatusReportStr((uint32_t) profileId, statusCode);
if (result) {
report = [NSString stringWithUTF8String:result];
}
return report;
}
- (void)shutdown_Internal
{
WDM_LOG_METHOD_SIG();
NSArray * keys = [_mTraitMap allKeys];
for (NSString * key in keys) {
NLGenericTraitUpdatableDataSink * pDataSink = [_mTraitMap objectForKey:key];
NSLog(@"key=%@, pDataSink=%@", key, pDataSink);
if ((NSNull *) pDataSink != [NSNull null]) {
[pDataSink close];
}
}
[_mTraitMap removeAllObjects];
_mTraitMap = nil;
// there is no need to release Objective C objects, as we have ARC for them
if (_mWeaveCppWdmClient) {
WDM_LOG_ERROR(@"Shutdown C++ Weave WdmClient");
_mWeaveCppWdmClient->Close();
delete _mWeaveCppWdmClient;
_mWeaveCppWdmClient = NULL;
}
[self dispatchAsyncCompletionBlock:nil];
}
- (void)close:(WdmClientCompletionBlock)completionHandler
{
WDM_LOG_METHOD_SIG();
dispatch_sync(_mWeaveWorkQueue, ^() {
if (nil != _mRequestName) {
WDM_LOG_ERROR(@"%@: Forcefully close while we're still executing %@, continue close", _name, _mRequestName);
}
[self markTransactionCompleted];
_mCompletionHandler = [completionHandler copy];
_mRequestName = @"close";
[self shutdown_Internal];
});
}
- (void)removeDataSinkRef:(long long)traitInstancePtr;
{
NSString * address = [NSString stringWithFormat:@"%lld", traitInstancePtr];
if ([_mTraitMap objectForKey:address] != nil) {
_mTraitMap[address] = [NSNull null];
}
}
- (void)setNodeId:(uint64_t)nodeId;
{
WDM_LOG_METHOD_SIG();
// VerifyOrExit(NULL != _mWeaveCppWdmClient, err = WEAVE_ERROR_INCORRECT_STATE);
dispatch_sync(_mWeaveWorkQueue, ^() {
_mWeaveCppWdmClient->SetNodeId(nodeId);
});
}
- (NLGenericTraitUpdatableDataSink *)newDataSink:(NLResourceIdentifier *)nlResourceIdentifier
profileId:(uint32_t)profileId
instanceId:(uint64_t)instanceId
path:(NSString *)path;
{
__block WEAVE_ERROR err = WEAVE_NO_ERROR;
__block nl::Weave::DeviceManager::GenericTraitUpdatableDataSink * pDataSink = NULL;
WDM_LOG_METHOD_SIG();
// VerifyOrExit(NULL != _mWeaveCppWdmClient, err = WEAVE_ERROR_INCORRECT_STATE);
dispatch_sync(_mWeaveWorkQueue, ^() {
ResourceIdentifier resId = [nlResourceIdentifier toResourceIdentifier];
err = _mWeaveCppWdmClient->NewDataSink(resId, profileId, instanceId, [path UTF8String], pDataSink);
});
if (err != WEAVE_NO_ERROR || pDataSink == NULL) {
WDM_LOG_ERROR(@"pDataSink is not ready");
return nil;
}
NSString * address = [NSString stringWithFormat:@"%lld", (long long) pDataSink];
if ([_mTraitMap objectForKey:address] == nil) {
WDM_LOG_DEBUG(@"creating new NLGenericTraitUpdatableDataSink with profild id %d", profileId);
NLGenericTraitUpdatableDataSink * pTrait = [[NLGenericTraitUpdatableDataSink alloc] init:_name
weaveWorkQueue:_mWeaveWorkQueue
appCallbackQueue:_mAppCallbackQueue
genericTraitUpdatableDataSinkPtr:pDataSink
nlWdmClient:self];
[_mTraitMap setObject:pTrait forKey:address];
}
return [_mTraitMap objectForKey:address];
}
- (void)flushUpdate:(WdmClientCompletionBlock)completionHandler failure:(WdmClientFailureBlock)failureHandler
{
WDM_LOG_METHOD_SIG();
NSString * taskName = @"FlushUpdate";
// we use async for the results are sent back to caller via async means also
dispatch_async(_mWeaveWorkQueue, ^() {
if (nil == _mRequestName) {
_mRequestName = taskName;
_mCompletionHandler = [completionHandler copy];
_mFailureHandler = [failureHandler copy];
WEAVE_ERROR err
= _mWeaveCppWdmClient->FlushUpdate((__bridge void *) self, handleWdmClientComplete, handleWdmClientError);
if (WEAVE_NO_ERROR != err) {
[self dispatchAsyncDefaultFailureBlockWithCode:err];
}
} else {
WDM_LOG_ERROR(@"%@: Attemp to %@ while we're still executing %@, ignore", _name, taskName, _mRequestName);
// do not change _mRequestName, as we're rejecting this request
[self dispatchAsyncFailureBlock:WEAVE_ERROR_INCORRECT_STATE taskName:taskName handler:failureHandler];
}
});
}
- (void)refreshData:(WdmClientCompletionBlock)completionHandler failure:(WdmClientFailureBlock)failureHandler
{
WDM_LOG_METHOD_SIG();
NSString * taskName = @"RefreshData";
// we use async for the results are sent back to caller via async means also
dispatch_async(_mWeaveWorkQueue, ^() {
if (nil == _mRequestName) {
_mRequestName = taskName;
_mCompletionHandler = [completionHandler copy];
_mFailureHandler = [failureHandler copy];
WEAVE_ERROR err
= _mWeaveCppWdmClient->RefreshData((__bridge void *) self, handleWdmClientComplete, handleWdmClientError, NULL);
if (WEAVE_NO_ERROR != err) {
[self dispatchAsyncDefaultFailureBlockWithCode:err];
}
} else {
WDM_LOG_ERROR(@"%@: Attemp to %@ while we're still executing %@, ignore", _name, taskName, _mRequestName);
// do not change _mRequestName, as we're rejecting this request
[self dispatchAsyncFailureBlock:WEAVE_ERROR_INCORRECT_STATE taskName:taskName handler:failureHandler];
}
});
}
@end
#endif // WEAVE_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL