blob: 0c84c14c7140380c58427edc949da89d4faacdc3 [file] [log] [blame]
/**
*
* Copyright (c) 2020 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 <Foundation/Foundation.h>
#include <app/chip-zcl-zpro-codec.h>
#import "CHIPDeviceController.h"
#import "CHIPError.h"
#import "CHIPLogging.h"
#include <controller/CHIPDeviceController.h>
#include <inet/IPAddress.h>
#include <system/SystemPacketBuffer.h>
static const char * const CHIP_SELECT_QUEUE = "com.zigbee.chip.select";
// NOTE: Remote device ID is in sync with the echo server device id
// At some point, we may want to add an option to connect to a device without
// knowing its id, because the ID can be learned on the first response that is received.
constexpr chip::NodeId kLocalDeviceId = 112233;
constexpr chip::NodeId kRemoteDeviceId = 12344321;
@implementation AddressInfo
- (instancetype)initWithIP:(NSString *)ip
{
if (self = [super init]) {
_ip = ip;
}
return self;
}
@end
@interface CHIPDeviceController ()
@property (nonatomic, readonly, strong, nonnull) NSRecursiveLock * lock;
// queue used to call select on the system and inet layer fds., remove this with NW Framework.
// primarily used to not block the work queue
@property (atomic, readonly) dispatch_queue_t chipSelectQueue;
/**
* The Controller delegate.
* Note: Getter is not thread safe.
*/
@property (readonly, weak, nonatomic) id<CHIPDeviceControllerDelegate> delegate;
/**
* The delegate queue where delegate callbacks will run
*/
@property (readonly, nonatomic) dispatch_queue_t delegateQueue;
@property (readonly) chip::DeviceController::ChipDeviceController * cppController;
@property (readwrite) NSData * localKey;
@property (readwrite) NSData * peerKey;
@end
@implementation CHIPDeviceController
+ (CHIPDeviceController *)sharedController
{
static CHIPDeviceController * controller = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// initialize the device controller
controller = [[CHIPDeviceController alloc] init];
});
return controller;
}
- (instancetype)init
{
if (self = [super init]) {
_lock = [[NSRecursiveLock alloc] init];
_chipSelectQueue = dispatch_queue_create(CHIP_SELECT_QUEUE, DISPATCH_QUEUE_SERIAL);
if (!_chipSelectQueue) {
return nil;
}
_cppController = new chip::DeviceController::ChipDeviceController();
if (!_cppController) {
CHIP_LOG_ERROR("Error: couldn't create c++ controller");
return nil;
}
if (CHIP_NO_ERROR != _cppController->Init(kLocalDeviceId)) {
CHIP_LOG_ERROR("Error: couldn't initialize c++ controller");
delete _cppController;
_cppController = NULL;
return nil;
}
}
return self;
}
static void onConnected(
chip::DeviceController::ChipDeviceController * cppController, chip::Transport::PeerConnectionState * state, void * appReqState)
{
CHIPDeviceController * controller = (__bridge CHIPDeviceController *) appReqState;
[controller _dispatchAsyncConnectBlock];
}
static void onMessageReceived(
chip::DeviceController::ChipDeviceController * deviceController, void * appReqState, chip::System::PacketBuffer * buffer)
{
CHIPDeviceController * controller = (__bridge CHIPDeviceController *) appReqState;
size_t data_len = buffer->DataLength();
// convert to NSData and pass back to the application
NSMutableData * dataBuffer = [[NSMutableData alloc] initWithBytes:buffer->Start() length:data_len];
buffer = buffer->Next();
while (buffer != NULL) {
data_len = buffer->DataLength();
[dataBuffer appendBytes:buffer->Start() length:data_len];
buffer = buffer->Next();
}
[controller _dispatchAsyncMessageBlock:dataBuffer];
// ignore unused variable
(void) deviceController;
chip::System::PacketBuffer::Free(buffer);
}
static void onInternalError(chip::DeviceController::ChipDeviceController * deviceController, void * appReqState, CHIP_ERROR error,
const chip::IPPacketInfo * pi)
{
CHIPDeviceController * controller = (__bridge CHIPDeviceController *) appReqState;
[controller _dispatchAsyncErrorBlock:[CHIPError errorForCHIPErrorCode:error]];
}
- (void)_dispatchAsyncErrorBlock:(NSError *)error
{
CHIP_LOG_METHOD_ENTRY();
id<CHIPDeviceControllerDelegate> strongDelegate = [self delegate];
if (strongDelegate && [self delegateQueue]) {
dispatch_async(self.delegateQueue, ^{
[strongDelegate deviceControllerOnError:error];
});
}
}
- (void)_dispatchAsyncMessageBlock:(NSData *)data
{
CHIP_LOG_METHOD_ENTRY();
id<CHIPDeviceControllerDelegate> strongDelegate = [self delegate];
if (strongDelegate && [self delegateQueue]) {
dispatch_async(self.delegateQueue, ^{
[strongDelegate deviceControllerOnMessage:data];
});
}
}
- (void)_dispatchAsyncConnectBlock
{
CHIP_LOG_METHOD_ENTRY();
id<CHIPDeviceControllerDelegate> strongDelegate = [self delegate];
if (strongDelegate && [self delegateQueue]) {
dispatch_async(self.delegateQueue, ^{
[strongDelegate deviceControllerOnConnected];
});
}
}
- (BOOL)connect:(NSString *)ipAddress
local_key:(NSData *)local_key
peer_key:(NSData *)peer_key
error:(NSError * __autoreleasing *)error
{
CHIP_ERROR err = CHIP_NO_ERROR;
// cache the keys before calling connect (because connect invokes the manual key exchange)
self.localKey = local_key.copy;
self.peerKey = peer_key.copy;
[self.lock lock];
chip::Inet::IPAddress addr;
chip::Inet::IPAddress::FromString([ipAddress UTF8String], addr);
err = self.cppController->ConnectDevice(
kRemoteDeviceId, addr, (__bridge void *) self, nil, onMessageReceived, onInternalError, CHIP_PORT);
[self.lock unlock];
if (err != CHIP_NO_ERROR) {
CHIP_LOG_ERROR("Error(%d): %@, connect failed", err, [CHIPError errorForCHIPErrorCode:err]);
if (error) {
*error = [CHIPError errorForCHIPErrorCode:err];
}
return NO;
}
// Start the IO pump
dispatch_async(_chipSelectQueue, ^() {
self.cppController->ServiceEvents();
});
return YES;
}
- (BOOL)connect:(uint16_t)discriminator setupPINCode:(uint32_t)setupPINCode error:(NSError * __autoreleasing *)error
{
CHIP_ERROR err = CHIP_NO_ERROR;
[self.lock lock];
err = self.cppController->ConnectDevice(
kRemoteDeviceId, discriminator, setupPINCode, (__bridge void *) self, onConnected, onMessageReceived, onInternalError);
[self.lock unlock];
if (err != CHIP_NO_ERROR) {
CHIP_LOG_ERROR("Error(%d): %@, connect failed", err, [CHIPError errorForCHIPErrorCode:err]);
if (error) {
*error = [CHIPError errorForCHIPErrorCode:err];
}
return NO;
}
// Start the IO pump
dispatch_async(_chipSelectQueue, ^() {
self.cppController->ServiceEvents();
});
return YES;
}
- (AddressInfo *)getAddressInfo
{
CHIP_ERROR err = CHIP_NO_ERROR;
chip::Transport::PeerAddress peerAddr = chip::Transport::PeerAddress::Uninitialized();
[self.lock lock];
err = self.cppController->PopulatePeerAddress(peerAddr);
[self.lock unlock];
chip::IPAddress ipAddr = peerAddr.GetIPAddress();
uint16_t port = peerAddr.GetPort();
if (err != CHIP_NO_ERROR) {
return nil;
}
// ignore the unused port
(void) port;
// A buffer big enough to hold ipv4 and ipv6 addresses
char ipAddrStr[64];
ipAddr.ToString(ipAddrStr, sizeof(ipAddrStr));
NSString * ipAddress = [NSString stringWithUTF8String:ipAddrStr];
return [[AddressInfo alloc] initWithIP:ipAddress];
}
- (BOOL)sendMessage:(NSData *)message error:(NSError * __autoreleasing *)error
{
CHIP_ERROR err = CHIP_NO_ERROR;
[self.lock lock];
size_t messageLen = [message length];
const void * messageChars = [message bytes];
chip::System::PacketBuffer * buffer = chip::System::PacketBuffer::NewWithAvailableSize(messageLen);
buffer->SetDataLength(messageLen);
memcpy(buffer->Start(), messageChars, messageLen);
err = self.cppController->SendMessage((__bridge void *) self, buffer);
[self.lock unlock];
if (err != CHIP_NO_ERROR) {
CHIP_LOG_ERROR("Error(%d): %@, send failed", err, [CHIPError errorForCHIPErrorCode:err]);
if (error) {
*error = [CHIPError errorForCHIPErrorCode:err];
}
return NO;
}
return YES;
}
- (BOOL)sendCHIPCommand:(uint32_t (^)(chip::System::PacketBuffer *, uint16_t))encodeCommandBlock
{
CHIP_ERROR err = CHIP_NO_ERROR;
[self.lock lock];
// FIXME: This needs a better buffersizing setup!
static const size_t bufferSize = 1024;
chip::System::PacketBuffer * buffer = chip::System::PacketBuffer::NewWithAvailableSize(bufferSize);
uint32_t dataLength = encodeCommandBlock(buffer, (uint16_t) bufferSize);
buffer->SetDataLength(dataLength);
err = self.cppController->SendMessage((__bridge void *) self, buffer);
[self.lock unlock];
if (err != CHIP_NO_ERROR) {
CHIP_LOG_ERROR("Error(%d): %@, send failed", err, [CHIPError errorForCHIPErrorCode:err]);
return NO;
}
return YES;
}
- (BOOL)sendOnCommand
{
return [self sendCHIPCommand:^(chip::System::PacketBuffer * buffer, uint16_t bufferSize) {
// Hardcode endpoint to 1 for now
return encodeOnCommand(buffer->Start(), bufferSize, 1);
}];
}
- (BOOL)sendOffCommand
{
return [self sendCHIPCommand:^(chip::System::PacketBuffer * buffer, uint16_t bufferSize) {
// Hardcode endpoint to 1 for now
return encodeOffCommand(buffer->Start(), bufferSize, 1);
}];
}
- (BOOL)sendToggleCommand
{
return [self sendCHIPCommand:^(chip::System::PacketBuffer * buffer, uint16_t bufferSize) {
// Hardcode endpoint to 1 for now
return encodeToggleCommand(buffer->Start(), bufferSize, 1);
}];
}
- (BOOL)disconnect:(NSError * __autoreleasing *)error
{
CHIP_ERROR err = CHIP_NO_ERROR;
[self.lock lock];
err = self.cppController->DisconnectDevice();
[self.lock unlock];
if (err != CHIP_NO_ERROR) {
CHIP_LOG_ERROR("Error(%d): %@, disconnect failed", err, [CHIPError errorForCHIPErrorCode:err]);
if (error) {
*error = [CHIPError errorForCHIPErrorCode:err];
}
return NO;
}
return YES;
}
- (BOOL)isConnected
{
bool isConnected = false;
[self.lock lock];
isConnected = self.cppController->IsConnected();
[self.lock unlock];
return isConnected ? YES : NO;
}
+ (BOOL)isDataModelCommand:(NSData * _Nonnull)message
{
if (message.length == 0) {
return NO;
}
UInt8 * bytes = (UInt8 *) message.bytes;
return bytes[0] < 0x04 ? YES : NO;
}
+ (NSString *)commandToString:(NSData * _Nonnull)response
{
if ([CHIPDeviceController isDataModelCommand:response] == NO) {
return @"Response is not a CHIP command";
}
uint8_t * bytes = (uint8_t *) response.bytes;
EmberApsFrame frame;
if (extractApsFrame(bytes, response.length, &frame) == 0) {
return @"Response is not an APS frame";
}
uint8_t * message;
uint16_t messageLen = extractMessage(bytes, response.length, &message);
if (messageLen != 5) {
// Not a Default Response command for sure.
return @"Unexpected response length";
}
if (message[0] != 8) {
// Unexpected control byte
return [NSString stringWithFormat:@"Control byte value '0x%02x' is not expected", message[0]];
}
// message[1] is the sequence counter; just ignore it for now.
if (message[2] != 0x0b) {
// Not a Default Response command id
return [NSString stringWithFormat:@"Command id '0x%02x' is not the Default Response command id (0x0b)", message[2]];
}
if (frame.clusterId != 0x06) {
// Not On/Off cluster
return [NSString stringWithFormat:@"Cluster id '0x%02x' is not the on/off cluster id (0x06)", frame.clusterId];
}
NSString * command;
if (message[3] == 0) {
command = @"off";
} else if (message[3] == 1) {
command = @"on";
} else if (message[3] == 2) {
command = @"toggle";
} else {
return [NSString stringWithFormat:@"Command '0x%02x' is unknown", message[3]];
}
NSString * status;
if (message[4] == 0) {
status = @"succeeded";
} else {
status = @"failed";
}
return [NSString stringWithFormat:@"Sending '%@' command %@", command, status];
}
- (void)setDelegate:(id<CHIPDeviceControllerDelegate>)delegate queue:(dispatch_queue_t)queue
{
[self.lock lock];
if (delegate && queue) {
self->_delegate = delegate;
self->_delegateQueue = queue;
} else {
self->_delegate = nil;
self->_delegateQueue = NULL;
}
[self.lock unlock];
}
@end