blob: 95acfec85fb934c68d177f10d6e3c5afa6948d55 [file]
/**
*
* 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>
extern "C" {
#include "chip-zcl/chip-zcl-buffer.h"
#include "chip-zcl/chip-zcl.h"
#include "gen/gen-command-id.h"
} // extern "C"
#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_WORK_QUEUE = "com.zigbee.chip.work";
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;
// queue used to signal callbacks to the application
@property (readwrite) dispatch_queue_t appCallbackQueue;
@property (readwrite) ControllerOnMessageBlock onMessageHandler;
@property (readwrite) ControllerOnErrorBlock onErrorHandler;
@property (readonly) chip::DeviceController::ChipDeviceController * cppController;
@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 onMessageReceived(chip::DeviceController::ChipDeviceController * deviceController, void * appReqState,
chip::System::PacketBuffer * buffer, const chip::IPPacketInfo * packet_info)
{
CHIPDeviceController * controller = (__bridge CHIPDeviceController *) appReqState;
char src_addr[INET_ADDRSTRLEN];
size_t data_len = buffer->DataLength();
packet_info->SrcAddress.ToString(src_addr, sizeof(src_addr));
NSString * ipAddress = [[NSString alloc] initWithUTF8String:src_addr];
// 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 ipAddress:ipAddress port:packet_info->SrcPort];
// 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();
// to avoid retaining "self"
ControllerOnErrorBlock onErrorHandler = self.onErrorHandler;
dispatch_async(_appCallbackQueue, ^() {
onErrorHandler(error);
});
}
- (void)_dispatchAsyncMessageBlock:(NSData *)data ipAddress:(NSString *)ipAddress port:(UInt16)port
{
CHIP_LOG_METHOD_ENTRY();
// to avoid retaining "self"
ControllerOnMessageBlock onMessageHandler = self.onMessageHandler;
dispatch_async(_appCallbackQueue, ^() {
onMessageHandler(data, ipAddress, port);
});
}
- (BOOL)connect:(NSString *)ipAddress
local_key:(NSData *)local_key
peer_key:(NSData *)peer_key
error:(NSError * __autoreleasing *)error
{
CHIP_ERROR err = CHIP_NO_ERROR;
// TODO maybe refactor
// the work queue is being used for atomic access to chip's cpp controller
// this could be done async but the error we care about is sync. However, I think this could be restructured such that
// the request is fired async and that Block can then return an error to the caller. This function would then never error out.
// the only drawback is that it complicates the api where the user must handle async errors on every function
[self.lock lock];
chip::Inet::IPAddress addr;
chip::Inet::IPAddress::FromString([ipAddress UTF8String], addr);
err = self.cppController->ConnectDevice(0, addr, NULL, 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;
}
[self.lock lock];
const unsigned char * local_key_bytes = (const unsigned char *) [local_key bytes];
const unsigned char * peer_key_bytes = (const unsigned char *) [peer_key bytes];
err = self.cppController->ManualKeyExchange(peer_key_bytes, peer_key.length, local_key_bytes, local_key.length);
[self.lock unlock];
if (err != CHIP_NO_ERROR) {
CHIP_LOG_ERROR("Error(%d): %@, key exchange failed", err, [CHIPError errorForCHIPErrorCode:err]);
if (error) {
*error = [CHIPError errorForCHIPErrorCode:err];
}
return NO;
}
// Start the IO pump
[self _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:(ChipZclClusterId_t)cluster command:(ChipZclCommandId_t)command
{
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);
ChipZclBuffer_t * zcl_buffer = (ChipZclBuffer_t *) buffer;
ChipZclCommandContext_t ctx = {
1, // endpointId
cluster, // clusterId
true, // clusterSpecific
false, // mfgSpecific
0, // mfgCode
command, // commandId
ZCL_DIRECTION_CLIENT_TO_SERVER, // direction
0, // payloadStartIndex
nullptr, // request
nullptr // response
};
chipZclEncodeZclHeader(zcl_buffer, &ctx);
const size_t data_len = chipZclBufferDataLength(zcl_buffer);
buffer->SetDataLength(data_len);
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)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;
}
// TODO kill this with fire (NW might implicitly replace this?)
- (void)_serviceEvents
{
__weak typeof(self) weakSelf = self;
dispatch_async(self.chipSelectQueue, ^() {
typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
__block fd_set readFDs, writeFDs, exceptFDs;
struct timeval aSleepTime;
int numFDs = 0;
aSleepTime.tv_sec = 5;
FD_ZERO(&readFDs);
FD_ZERO(&writeFDs);
FD_ZERO(&exceptFDs);
chip::System::Layer * systemLayer = NULL;
chip::Inet::InetLayer * inetLayer = NULL;
// ask for the system and inet layers
[self.lock lock];
self.cppController->GetLayers(&systemLayer, &inetLayer);
if (systemLayer != NULL && systemLayer->State() == chip::System::kLayerState_Initialized) {
systemLayer->PrepareSelect(numFDs, &readFDs, &writeFDs, &exceptFDs, aSleepTime);
}
if (inetLayer != NULL && inetLayer->State == chip::Inet::InetLayer::kState_Initialized) {
inetLayer->PrepareSelect(numFDs, &readFDs, &writeFDs, &exceptFDs, aSleepTime);
}
[self.lock unlock];
int selectRes = select(numFDs, &readFDs, &writeFDs, &exceptFDs, const_cast<struct timeval *>(&aSleepTime));
[self.lock lock];
if (!self.cppController->IsConnected()) {
[self.lock unlock];
// cancel the loop, it'll restart the next time a connection is established
return;
}
systemLayer = NULL;
inetLayer = NULL;
self.cppController->GetLayers(&systemLayer, &inetLayer);
if (systemLayer != NULL && systemLayer->State() == chip::System::kLayerState_Initialized) {
systemLayer->HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
}
if (inetLayer != NULL && inetLayer->State == chip::Inet::InetLayer::kState_Initialized) {
inetLayer->HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
}
[self.lock unlock];
[self _serviceEvents];
});
}
- (void)registerCallbacks:appCallbackQueue onMessage:(ControllerOnMessageBlock)onMessage onError:(ControllerOnErrorBlock)onError
{
self.appCallbackQueue = appCallbackQueue;
self.onMessageHandler = onMessage;
self.onErrorHandler = onError;
}
@end
extern "C" {
// We have to have this empty callback, because the ZCL code links against it.
void chipZclPostAttributeChangeCallback(uint8_t endpoint, ChipZclClusterId clusterId, ChipZclAttributeId attributeId, uint8_t mask,
uint16_t manufacturerCode, uint8_t type, uint8_t size, uint8_t * value)
{
}
} // extern "C"