2251 lines
71 KiB
Objective-C
2251 lines
71 KiB
Objective-C
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
#import "OCTTox+Private.h"
|
|
#import "OCTToxOptions+Private.h"
|
|
#import "OCTLogging.h"
|
|
|
|
void (*_tox_self_get_public_key)(const Tox *tox, uint8_t *public_key);
|
|
|
|
@interface OCTTox ()
|
|
|
|
@property (assign, nonatomic) Tox *tox;
|
|
|
|
@property (strong, nonatomic) dispatch_source_t timer;
|
|
@property (assign, nonatomic) uint64_t previousIterate;
|
|
|
|
@end
|
|
|
|
@implementation OCTTox
|
|
|
|
static long long last_check_time = 0;
|
|
static long long TWO_MIN_IN_MILLIS = (2 * 60 * 1000); // 2 minutes in milliseconds
|
|
|
|
#pragma mark - Class methods
|
|
|
|
+ (NSString *)version
|
|
{
|
|
return [NSString stringWithFormat:@"%lu.%lu.%lu",
|
|
(unsigned long)[self versionMajor], (unsigned long)[self versionMinor], (unsigned long)[self versionPatch]];
|
|
}
|
|
|
|
+ (NSUInteger)versionMajor
|
|
{
|
|
return tox_version_major();
|
|
}
|
|
|
|
+ (NSUInteger)versionMinor
|
|
{
|
|
return tox_version_minor();
|
|
}
|
|
|
|
+ (NSUInteger)versionPatch
|
|
{
|
|
return tox_version_patch();
|
|
}
|
|
|
|
#pragma mark - Lifecycle
|
|
|
|
- (instancetype)initWithOptions:(OCTToxOptions *)options savedData:(NSData *)data error:(NSError **)error
|
|
{
|
|
NSParameterAssert(options);
|
|
|
|
self = [super init];
|
|
|
|
OCTLogVerbose(@"OCTTox: loading with options %@", options);
|
|
|
|
if (data) {
|
|
OCTLogVerbose(@"loading from data of length %lu", (unsigned long)data.length);
|
|
tox_options_set_savedata_type(options.options, TOX_SAVEDATA_TYPE_TOX_SAVE);
|
|
tox_options_set_savedata_data(options.options, data.bytes, data.length);
|
|
}
|
|
else {
|
|
tox_options_set_savedata_type(options.options, TOX_SAVEDATA_TYPE_NONE);
|
|
}
|
|
|
|
tox_options_set_log_callback(options.options, logCallback);
|
|
|
|
TOX_ERR_NEW cError;
|
|
|
|
_tox = tox_new(options.options, &cError);
|
|
|
|
[self fillError:error withCErrorInit:cError];
|
|
|
|
if (! _tox) {
|
|
return nil;
|
|
}
|
|
|
|
[self setupCFunctions];
|
|
[self setupCallbacks];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[self stop];
|
|
|
|
if (self.tox) {
|
|
tox_kill(self.tox);
|
|
}
|
|
|
|
OCTLogVerbose(@"dealloc called, tox killed");
|
|
}
|
|
|
|
- (NSData *)save
|
|
{
|
|
OCTLogVerbose(@"saving...");
|
|
|
|
size_t size = tox_get_savedata_size(self.tox);
|
|
uint8_t *cData = malloc(size);
|
|
|
|
tox_get_savedata(self.tox, cData);
|
|
|
|
NSData *data = [NSData dataWithBytes:cData length:size];
|
|
free(cData);
|
|
|
|
OCTLogInfo(@"saved to data with length %lu", (unsigned long)data.length);
|
|
|
|
return data;
|
|
}
|
|
|
|
- (void)start
|
|
{
|
|
OCTLogVerbose(@"start method called");
|
|
|
|
@synchronized(self) {
|
|
if (self.timer) {
|
|
OCTLogWarn(@"already started");
|
|
return;
|
|
}
|
|
|
|
dispatch_queue_t queue = dispatch_queue_create("me.dvor.objcTox.OCTToxQueue", NULL);
|
|
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
|
|
|
[self updateTimerIntervalIfNeeded];
|
|
|
|
// HINT: prevent bootstrapping here on startup. so add 2 minutes grace period
|
|
last_check_time = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
|
|
last_check_time = last_check_time + TWO_MIN_IN_MILLIS;
|
|
|
|
__weak OCTTox *weakSelf = self;
|
|
dispatch_source_set_event_handler(self.timer, ^{
|
|
OCTTox *strongSelf = weakSelf;
|
|
if (! strongSelf) {
|
|
return;
|
|
}
|
|
|
|
tox_iterate(strongSelf.tox, (__bridge void *)self);
|
|
|
|
long long current_time = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
|
|
|
|
if (current_time > (last_check_time + (TWO_MIN_IN_MILLIS))) {
|
|
last_check_time = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
|
|
int cstatus = tox_self_get_connection_status(strongSelf.tox);
|
|
|
|
OCTLogInfo(@"Tox checking online status: %d", cstatus);
|
|
|
|
if (cstatus == 0) {
|
|
OCTLogInfo(@"Tox offline for a long time, bootstrapping again ...");
|
|
|
|
uint8_t *key_bin = hex_to_bin("CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707",
|
|
(TOX_PUBLIC_KEY_SIZE * 2));
|
|
|
|
if (key_bin != NULL) {
|
|
// -------------------------------------------------------------
|
|
// HINT: fix me. how to access OCTSubmanagerBootstrapImpl here?
|
|
// add a hardcoded node to least make it come online
|
|
// after a long period of being offline.
|
|
// -------------------------------------------------------------
|
|
tox_add_tcp_relay(strongSelf.tox, "46.101.197.175", 33445, key_bin, NULL);
|
|
tox_bootstrap(strongSelf.tox, "46.101.197.175", 33445, key_bin, NULL);
|
|
|
|
tox_add_tcp_relay(strongSelf.tox, "2a03:b0c0:3:d0::ac:5001", 33445, key_bin, NULL);
|
|
tox_bootstrap(strongSelf.tox, "2a03:b0c0:3:d0::ac:5001", 33445, key_bin, NULL);
|
|
|
|
OCTLogInfo(@"Tox offline for a long time, bootstrapping DONE");
|
|
free(key_bin);
|
|
}
|
|
}
|
|
}
|
|
|
|
[strongSelf updateTimerIntervalIfNeeded];
|
|
});
|
|
|
|
dispatch_resume(self.timer);
|
|
}
|
|
|
|
OCTLogInfo(@"started");
|
|
}
|
|
|
|
- (void)stop
|
|
{
|
|
OCTLogVerbose(@"stop method called");
|
|
|
|
@synchronized(self) {
|
|
if (! self.timer) {
|
|
OCTLogWarn(@"tox isn't running, nothing to stop");
|
|
return;
|
|
}
|
|
|
|
dispatch_source_cancel(self.timer);
|
|
self.timer = nil;
|
|
}
|
|
|
|
OCTLogInfo(@"stopped");
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
- (OCTToxConnectionStatus)connectionStatus
|
|
{
|
|
return [self userConnectionStatusFromCUserStatus:tox_self_get_connection_status(self.tox)];
|
|
}
|
|
|
|
- (OCTToxCapabilities)capabilities
|
|
{
|
|
OCTLogVerbose(@"get capabilities");
|
|
|
|
return tox_self_get_capabilities();
|
|
}
|
|
|
|
- (NSString *)userAddress
|
|
{
|
|
OCTLogVerbose(@"get userAddress");
|
|
|
|
const NSUInteger length = TOX_ADDRESS_SIZE;
|
|
uint8_t *cAddress = malloc(length);
|
|
|
|
tox_self_get_address(self.tox, cAddress);
|
|
|
|
if (! cAddress) {
|
|
return nil;
|
|
}
|
|
|
|
NSString *address = [OCTTox binToHexString:cAddress length:length];
|
|
|
|
free(cAddress);
|
|
|
|
return address;
|
|
}
|
|
|
|
- (NSString *)publicKey
|
|
{
|
|
OCTLogVerbose(@"get publicKey");
|
|
|
|
uint8_t *cPublicKey = malloc(TOX_PUBLIC_KEY_SIZE);
|
|
|
|
_tox_self_get_public_key(self.tox, cPublicKey);
|
|
|
|
NSString *publicKey = [OCTTox binToHexString:cPublicKey length:TOX_PUBLIC_KEY_SIZE];
|
|
free(cPublicKey);
|
|
|
|
return publicKey;
|
|
}
|
|
|
|
- (NSString *)secretKey
|
|
{
|
|
OCTLogVerbose(@"get secretKey");
|
|
|
|
uint8_t *cSecretKey = malloc(TOX_SECRET_KEY_SIZE);
|
|
|
|
tox_self_get_secret_key(self.tox, cSecretKey);
|
|
|
|
NSString *secretKey = [OCTTox binToHexString:cSecretKey length:TOX_SECRET_KEY_SIZE];
|
|
free(cSecretKey);
|
|
|
|
return secretKey;
|
|
}
|
|
|
|
- (void)setNospam:(OCTToxNoSpam)nospam
|
|
{
|
|
OCTLogVerbose(@"set nospam");
|
|
tox_self_set_nospam(self.tox, nospam);
|
|
}
|
|
|
|
- (OCTToxNoSpam)nospam
|
|
{
|
|
OCTLogVerbose(@"get nospam");
|
|
return tox_self_get_nospam(self.tox);
|
|
}
|
|
|
|
- (void)setUserStatus:(OCTToxUserStatus)status
|
|
{
|
|
TOX_USER_STATUS cStatus = TOX_USER_STATUS_NONE;
|
|
|
|
switch (status) {
|
|
case OCTToxUserStatusNone:
|
|
cStatus = TOX_USER_STATUS_NONE;
|
|
break;
|
|
case OCTToxUserStatusAway:
|
|
cStatus = TOX_USER_STATUS_AWAY;
|
|
break;
|
|
case OCTToxUserStatusBusy:
|
|
cStatus = TOX_USER_STATUS_BUSY;
|
|
break;
|
|
}
|
|
|
|
tox_self_set_status(self.tox, cStatus);
|
|
|
|
OCTLogInfo(@"set user status to %lu", (unsigned long)status);
|
|
}
|
|
|
|
- (OCTToxUserStatus)userStatus
|
|
{
|
|
return [self userStatusFromCUserStatus:tox_self_get_status(self.tox)];
|
|
}
|
|
|
|
#pragma mark - Methods
|
|
|
|
/*
|
|
* Converts an ASCII character in hexadecimal (lower or upper case) into the corresponding decimal value.
|
|
*
|
|
* Returns decimal value on success.
|
|
* Returns -1 on failure.
|
|
*/
|
|
int char_to_int(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
{
|
|
return c - '0';
|
|
}
|
|
|
|
if (c >= 'A' && c <= 'F')
|
|
{
|
|
return 10 + c - 'A';
|
|
}
|
|
|
|
if (c >= 'a' && c <= 'f')
|
|
{
|
|
return 10 + c - 'a';
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Converts a hexidecimal string of length hex_string_len to binary format and puts the result in output.
|
|
* output_size must be exactly half of hex_string_len.
|
|
*
|
|
* Returns (uint8_t *) on success. the caller must free the buffer after use.
|
|
* Returns NULL on failure.
|
|
*/
|
|
uint8_t *hex_to_bin(const char *hex_string_buffer, size_t hex_string_len)
|
|
{
|
|
if ((!hex_string_buffer) || (hex_string_len < 2))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if ((hex_string_len % 2) != 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
size_t len_bin = (hex_string_len / 2);
|
|
uint8_t *val = calloc(1, len_bin);
|
|
|
|
for (size_t i = 0; i < len_bin; i++)
|
|
{
|
|
val[i] = (16 * char_to_int(hex_string_buffer[2 * i])) + (char_to_int(hex_string_buffer[2 * i + 1]));
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* Converts byte buffer into a hexidecimal string.
|
|
*
|
|
* Returns 0 on success. the caller must must provide a buffer with enough space to hold the hex string.
|
|
* Returns -1 on failure.
|
|
*/
|
|
int bin_to_hex(const char *bin_id, size_t bin_id_size, char *output)
|
|
{
|
|
if ((!output) || (!bin_id) || (bin_id_size < 1))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < bin_id_size; i++)
|
|
{
|
|
snprintf(&output[i * 2], ((bin_id_size * 2) + 1) - (i * 2), "%02X", bin_id[i] & 0xff);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t xnet_pack_u16(uint8_t *bytes, uint16_t v)
|
|
{
|
|
bytes[0] = (v >> 8) & 0xff;
|
|
bytes[1] = v & 0xff;
|
|
return sizeof(v);
|
|
}
|
|
|
|
size_t xnet_pack_u32(uint8_t *bytes, uint32_t v)
|
|
{
|
|
uint8_t *p = bytes;
|
|
p += xnet_pack_u16(p, (v >> 16) & 0xffff);
|
|
p += xnet_pack_u16(p, v & 0xffff);
|
|
return p - bytes;
|
|
}
|
|
|
|
size_t xnet_unpack_u16(const uint8_t *bytes, uint16_t *v)
|
|
{
|
|
uint8_t hi = bytes[0];
|
|
uint8_t lo = bytes[1];
|
|
*v = ((uint16_t)hi << 8) | lo;
|
|
return sizeof(*v);
|
|
}
|
|
|
|
size_t xnet_unpack_u32(const uint8_t *bytes, uint32_t *v)
|
|
{
|
|
const uint8_t *p = bytes;
|
|
uint16_t hi;
|
|
uint16_t lo;
|
|
p += xnet_unpack_u16(p, &hi);
|
|
p += xnet_unpack_u16(p, &lo);
|
|
*v = ((uint32_t)hi << 16) | lo;
|
|
return p - bytes;
|
|
}
|
|
|
|
- (BOOL)bootstrapFromHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey error:(NSError **)error
|
|
{
|
|
NSParameterAssert(host);
|
|
NSParameterAssert(publicKey);
|
|
|
|
OCTLogInfo(@"bootstrap with host %@ port %d publicKey %@", host, port, publicKey);
|
|
|
|
const char *cAddress = host.UTF8String;
|
|
uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey];
|
|
|
|
TOX_ERR_BOOTSTRAP cError;
|
|
|
|
bool result = tox_bootstrap(self.tox, cAddress, port, cPublicKey, &cError);
|
|
|
|
[self fillError:error withCErrorBootstrap:cError];
|
|
|
|
free(cPublicKey);
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (BOOL)addTCPRelayWithHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey error:(NSError **)error
|
|
{
|
|
NSParameterAssert(host);
|
|
NSParameterAssert(publicKey);
|
|
|
|
OCTLogInfo(@"add TCP relay with host %@ port %d publicKey %@", host, port, publicKey);
|
|
|
|
const char *cAddress = host.UTF8String;
|
|
uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey];
|
|
|
|
TOX_ERR_BOOTSTRAP cError;
|
|
|
|
bool result = tox_add_tcp_relay(self.tox, cAddress, port, cPublicKey, &cError);
|
|
|
|
[self fillError:error withCErrorBootstrap:cError];
|
|
|
|
free(cPublicKey);
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (OCTToxFriendNumber)addFriendWithAddress:(NSString *)address message:(NSString *)message error:(NSError **)error
|
|
{
|
|
NSParameterAssert(address);
|
|
NSParameterAssert(message);
|
|
NSAssert(address.length == kOCTToxAddressLength, @"Address must be kOCTToxAddressLength length");
|
|
|
|
OCTLogVerbose(@"add friend with address.length %lu, message.length %lu", (unsigned long)address.length, (unsigned long)message.length);
|
|
|
|
uint8_t *cAddress = [OCTTox hexStringToBin:address];
|
|
const char *cMessage = [message cStringUsingEncoding:NSUTF8StringEncoding];
|
|
size_t length = [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
TOX_ERR_FRIEND_ADD cError;
|
|
|
|
OCTToxFriendNumber result = tox_friend_add(self.tox, cAddress, (const uint8_t *)cMessage, length, &cError);
|
|
|
|
free(cAddress);
|
|
|
|
[self fillError:error withCErrorFriendAdd:cError];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (OCTToxFriendNumber)addFriendWithNoRequestWithPublicKey:(NSString *)publicKey error:(NSError **)error
|
|
{
|
|
NSParameterAssert(publicKey);
|
|
NSAssert(publicKey.length == kOCTToxPublicKeyLength, @"Public key must be kOCTToxPublicKeyLength length");
|
|
|
|
OCTLogVerbose(@"add friend with no request and publicKey.length %lu", (unsigned long)publicKey.length);
|
|
|
|
uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey];
|
|
|
|
TOX_ERR_FRIEND_ADD cError;
|
|
|
|
OCTToxFriendNumber result = tox_friend_add_norequest(self.tox, cPublicKey, &cError);
|
|
|
|
free(cPublicKey);
|
|
|
|
[self fillError:error withCErrorFriendAdd:cError];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)deleteFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_FRIEND_DELETE cError;
|
|
|
|
bool result = tox_friend_delete(self.tox, friendNumber, &cError);
|
|
|
|
[self fillError:error withCErrorFriendDelete:cError];
|
|
|
|
OCTLogVerbose(@"deleting friend with friendNumber %d, result %d", friendNumber, (result == 0));
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (OCTToxFriendNumber)friendNumberWithPublicKey:(NSString *)publicKey error:(NSError **)error
|
|
{
|
|
NSParameterAssert(publicKey);
|
|
NSAssert(publicKey.length == kOCTToxPublicKeyLength, @"Public key must be kOCTToxPublicKeyLength length");
|
|
|
|
OCTLogVerbose(@"get friend number with publicKey.length %lu", (unsigned long)publicKey.length);
|
|
|
|
uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey];
|
|
|
|
TOX_ERR_FRIEND_BY_PUBLIC_KEY cError;
|
|
|
|
OCTToxFriendNumber result = tox_friend_by_public_key(self.tox, cPublicKey, &cError);
|
|
|
|
free(cPublicKey);
|
|
|
|
[self fillError:error withCErrorFriendByPublicKey:cError];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSString *)publicKeyFromFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
OCTLogVerbose(@"get public key from friend number %d", friendNumber);
|
|
|
|
uint8_t *cPublicKey = malloc(TOX_PUBLIC_KEY_SIZE);
|
|
|
|
TOX_ERR_FRIEND_GET_PUBLIC_KEY cError;
|
|
|
|
bool result = tox_friend_get_public_key(self.tox, friendNumber, cPublicKey, &cError);
|
|
|
|
NSString *publicKey = nil;
|
|
|
|
if (result) {
|
|
publicKey = [OCTTox binToHexString:cPublicKey length:TOX_PUBLIC_KEY_SIZE];
|
|
}
|
|
|
|
if (cPublicKey) {
|
|
free(cPublicKey);
|
|
}
|
|
|
|
[self fillError:error withCErrorFriendGetPublicKey:cError];
|
|
|
|
return publicKey;
|
|
}
|
|
|
|
- (BOOL)friendExistsWithFriendNumber:(OCTToxFriendNumber)friendNumber
|
|
{
|
|
bool result = tox_friend_exists(self.tox, friendNumber);
|
|
|
|
OCTLogVerbose(@"friend exists with friendNumber %d, result %d", friendNumber, result);
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (NSDate *)friendGetLastOnlineWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_FRIEND_GET_LAST_ONLINE cError;
|
|
|
|
uint64_t timestamp = tox_friend_get_last_online(self.tox, friendNumber, &cError);
|
|
|
|
[self fillError:error withCErrorFriendGetLastOnline:cError];
|
|
|
|
if (timestamp == UINT64_MAX) {
|
|
return nil;
|
|
}
|
|
|
|
return [NSDate dateWithTimeIntervalSince1970:timestamp];
|
|
}
|
|
|
|
|
|
- (OCTToxCapabilities)friendGetCapabilitiesWithFriendNumber:(OCTToxFriendNumber)friendNumber
|
|
{
|
|
return tox_friend_get_capabilities(self.tox, friendNumber);
|
|
}
|
|
|
|
- (OCTToxUserStatus)friendStatusWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_FRIEND_QUERY cError;
|
|
|
|
TOX_USER_STATUS cStatus = tox_friend_get_status(self.tox, friendNumber, &cError);
|
|
|
|
[self fillError:error withCErrorFriendQuery:cError];
|
|
|
|
return [self userStatusFromCUserStatus:cStatus];
|
|
}
|
|
|
|
- (OCTToxConnectionStatus)friendConnectionStatusWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_FRIEND_QUERY cError;
|
|
|
|
TOX_CONNECTION cStatus = tox_friend_get_connection_status(self.tox, friendNumber, &cError);
|
|
|
|
[self fillError:error withCErrorFriendQuery:cError];
|
|
|
|
return [self userConnectionStatusFromCUserStatus:cStatus];
|
|
}
|
|
|
|
- (BOOL)sendLosslessPacketWithFriendNumber:(OCTToxFriendNumber)friendNumber
|
|
pktid:(uint8_t)pktid
|
|
data:(NSString *)data
|
|
error:(NSError **)error
|
|
{
|
|
// TODO: this now only works with UTF8 strings as data, make it work fully with byte arrays later
|
|
|
|
NSParameterAssert(data);
|
|
|
|
char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding];
|
|
size_t length = [data lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
cData[0] = pktid;
|
|
|
|
TOX_ERR_FRIEND_CUSTOM_PACKET cError;
|
|
|
|
bool result = tox_friend_send_lossless_packet(self.tox, friendNumber, (const uint8_t *)cData, length, &cError);
|
|
|
|
// TODO: fill cError with errorcode
|
|
// [self fillError:error xxxxxxxxxx:cError];
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (OCTToxMessageId)sendMessageWithFriendNumber:(OCTToxFriendNumber)friendNumber
|
|
type:(OCTToxMessageType)type
|
|
message:(NSString *)message
|
|
msgv3HashHex:(NSString *)msgv3HashHex
|
|
msgv3tssec:(UInt32)msgv3tssec
|
|
error:(NSError **)error
|
|
{
|
|
NSParameterAssert(message);
|
|
|
|
char *cMessage = [message cStringUsingEncoding:NSUTF8StringEncoding];
|
|
size_t length = [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
TOX_MESSAGE_TYPE cType;
|
|
switch (type) {
|
|
case OCTToxMessageTypeNormal:
|
|
cType = TOX_MESSAGE_TYPE_NORMAL;
|
|
break;
|
|
case OCTToxMessageTypeAction:
|
|
cType = TOX_MESSAGE_TYPE_ACTION;
|
|
break;
|
|
case OCTToxMessageTypeHighlevelack:
|
|
cType = TOX_MESSAGE_TYPE_HIGH_LEVEL_ACK;
|
|
break;
|
|
}
|
|
|
|
TOX_ERR_FRIEND_SEND_MESSAGE cError;
|
|
|
|
char *cMessage2 = cMessage;
|
|
size_t length2 = length;
|
|
char *cMessage2_alloc = NULL;
|
|
uint8_t *hash_buffer_c = NULL;
|
|
|
|
if (msgv3HashHex != nil)
|
|
{
|
|
char *msgv3HashHex_cstr = [msgv3HashHex cStringUsingEncoding:NSUTF8StringEncoding];
|
|
size_t msgv3HashHex_length = [msgv3HashHex lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
if (msgv3HashHex_length >= (TOX_MSGV3_MSGID_LENGTH * 2))
|
|
{
|
|
size_t length_orig_corrected = length;
|
|
if (length > TOX_MSGV3_MAX_MESSAGE_LENGTH)
|
|
{
|
|
length_orig_corrected = TOX_MSGV3_MAX_MESSAGE_LENGTH;
|
|
}
|
|
|
|
cMessage2_alloc = (char *)calloc(1, (size_t)(length_orig_corrected +
|
|
TOX_MSGV3_GUARD + TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH));
|
|
hash_buffer_c = hex_to_bin(msgv3HashHex_cstr, (TOX_MSGV3_MSGID_LENGTH * 2));
|
|
|
|
if ((cMessage2_alloc) && (hash_buffer_c))
|
|
{
|
|
uint32_t timestamp_unix = (uint32_t)msgv3tssec;
|
|
uint32_t timestamp_unix_buf = 0;
|
|
// NSLog(@"mmm:timestamp_unix %d", timestamp_unix);
|
|
xnet_pack_u32((uint8_t *)×tamp_unix_buf, timestamp_unix);
|
|
// NSLog(@"mmm:timestamp_unix_buf %d", timestamp_unix_buf);
|
|
|
|
uint8_t* position = cMessage2_alloc;
|
|
memcpy(position, cMessage, (size_t)(length_orig_corrected));
|
|
position = position + length_orig_corrected;
|
|
position = position + TOX_MSGV3_GUARD;
|
|
memcpy(position, hash_buffer_c, (size_t)(TOX_MSGV3_MSGID_LENGTH));
|
|
position = position + TOX_MSGV3_MSGID_LENGTH;
|
|
memcpy(position, ×tamp_unix_buf, (size_t)(TOX_MSGV3_TIMESTAMP_LENGTH));
|
|
|
|
length2 = length_orig_corrected + TOX_MSGV3_GUARD + TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH;
|
|
cMessage2 = cMessage2_alloc;
|
|
}
|
|
}
|
|
}
|
|
|
|
OCTToxMessageId result = tox_friend_send_message(self.tox, friendNumber, cType, (const uint8_t *)cMessage2, length2, &cError);
|
|
|
|
if (cMessage2_alloc)
|
|
{
|
|
free(cMessage2_alloc);
|
|
}
|
|
|
|
if (hash_buffer_c)
|
|
{
|
|
free(hash_buffer_c);
|
|
}
|
|
|
|
[self fillError:error withCErrorFriendSendMessage:cError];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)setNickname:(NSString *)name error:(NSError **)error
|
|
{
|
|
NSParameterAssert(name);
|
|
|
|
const char *cName = [name cStringUsingEncoding:NSUTF8StringEncoding];
|
|
size_t length = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
TOX_ERR_SET_INFO cError;
|
|
|
|
bool result = tox_self_set_name(self.tox, (const uint8_t *)cName, length, &cError);
|
|
|
|
[self fillError:error withCErrorSetInfo:cError];
|
|
|
|
OCTLogInfo(@"set userName to %@, result %d", name, result);
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (NSString *)userName
|
|
{
|
|
size_t length = tox_self_get_name_size(self.tox);
|
|
|
|
if (! length) {
|
|
return nil;
|
|
}
|
|
|
|
uint8_t *cName = malloc(length);
|
|
tox_self_get_name(self.tox, cName);
|
|
|
|
NSString *name = [[NSString alloc] initWithBytes:cName length:length encoding:NSUTF8StringEncoding];
|
|
|
|
free(cName);
|
|
|
|
return name;
|
|
}
|
|
|
|
- (NSString *)friendNameWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_FRIEND_QUERY cError;
|
|
size_t size = tox_friend_get_name_size(self.tox, friendNumber, &cError);
|
|
|
|
[self fillError:error withCErrorFriendQuery:cError];
|
|
|
|
if (cError != TOX_ERR_FRIEND_QUERY_OK) {
|
|
return nil;
|
|
}
|
|
|
|
uint8_t *cName = malloc(size);
|
|
bool result = tox_friend_get_name(self.tox, friendNumber, cName, &cError);
|
|
|
|
NSString *name = nil;
|
|
|
|
if (result) {
|
|
name = [[NSString alloc] initWithBytes:cName length:size encoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
if (cName) {
|
|
free(cName);
|
|
}
|
|
|
|
[self fillError:error withCErrorFriendQuery:cError];
|
|
|
|
return name;
|
|
}
|
|
|
|
- (BOOL)setUserStatusMessage:(NSString *)statusMessage error:(NSError **)error
|
|
{
|
|
NSParameterAssert(statusMessage);
|
|
|
|
const char *cStatusMessage = [statusMessage cStringUsingEncoding:NSUTF8StringEncoding];
|
|
size_t length = [statusMessage lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
TOX_ERR_SET_INFO cError;
|
|
|
|
bool result = tox_self_set_status_message(self.tox, (const uint8_t *)cStatusMessage, length, &cError);
|
|
|
|
[self fillError:error withCErrorSetInfo:cError];
|
|
|
|
OCTLogInfo(@"set user status message to %@, result %d", statusMessage, result);
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (NSString *)userStatusMessage
|
|
{
|
|
size_t length = tox_self_get_status_message_size(self.tox);
|
|
|
|
if (! length) {
|
|
return nil;
|
|
}
|
|
|
|
uint8_t *cBuffer = malloc(length);
|
|
|
|
tox_self_get_status_message(self.tox, cBuffer);
|
|
|
|
NSString *message = [[NSString alloc] initWithBytes:cBuffer length:length encoding:NSUTF8StringEncoding];
|
|
free(cBuffer);
|
|
|
|
return message;
|
|
}
|
|
|
|
- (NSString *)friendStatusMessageWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_FRIEND_QUERY cError;
|
|
|
|
size_t size = tox_friend_get_status_message_size(self.tox, friendNumber, &cError);
|
|
|
|
[self fillError:error withCErrorFriendQuery:cError];
|
|
|
|
if (cError != TOX_ERR_FRIEND_QUERY_OK) {
|
|
return nil;
|
|
}
|
|
|
|
uint8_t *cBuffer = malloc(size);
|
|
|
|
bool result = tox_friend_get_status_message(self.tox, friendNumber, cBuffer, &cError);
|
|
|
|
NSString *message = nil;
|
|
|
|
if (result) {
|
|
message = [[NSString alloc] initWithBytes:cBuffer length:size encoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
if (cBuffer) {
|
|
free(cBuffer);
|
|
}
|
|
|
|
[self fillError:error withCErrorFriendQuery:cError];
|
|
|
|
return message;
|
|
}
|
|
|
|
- (BOOL)setUserIsTyping:(BOOL)isTyping forFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_SET_TYPING cError;
|
|
|
|
bool result = tox_self_set_typing(self.tox, friendNumber, (bool)isTyping, &cError);
|
|
|
|
[self fillError:error withCErrorSetTyping:cError];
|
|
|
|
OCTLogInfo(@"set user isTyping to %d for friend number %d, result %d", isTyping, friendNumber, result);
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (BOOL)isFriendTypingWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
|
|
{
|
|
TOX_ERR_FRIEND_QUERY cError;
|
|
|
|
bool isTyping = tox_friend_get_typing(self.tox, friendNumber, &cError);
|
|
|
|
[self fillError:error withCErrorFriendQuery:cError];
|
|
|
|
return (BOOL)isTyping;
|
|
}
|
|
|
|
- (NSUInteger)friendsCount
|
|
{
|
|
return tox_self_get_friend_list_size(self.tox);
|
|
}
|
|
|
|
- (NSArray *)friendsArray
|
|
{
|
|
size_t count = tox_self_get_friend_list_size(self.tox);
|
|
|
|
if (! count) {
|
|
return @[];
|
|
}
|
|
|
|
size_t listSize = count * sizeof(uint32_t);
|
|
uint32_t *cList = malloc(listSize);
|
|
|
|
tox_self_get_friend_list(self.tox, cList);
|
|
|
|
NSMutableArray *list = [NSMutableArray new];
|
|
|
|
for (NSUInteger index = 0; index < count; index++) {
|
|
int32_t friendId = cList[index];
|
|
[list addObject:@(friendId)];
|
|
}
|
|
|
|
free(cList);
|
|
|
|
OCTLogVerbose(@"friend array %@", list);
|
|
|
|
return [list copy];
|
|
}
|
|
|
|
- (NSData *)hashData:(NSData *)data
|
|
{
|
|
uint8_t *cHash = malloc(TOX_HASH_LENGTH);
|
|
const uint8_t *cData = [data bytes];
|
|
|
|
bool result = tox_hash(cHash, cData, (uint32_t)data.length);
|
|
NSData *hash;
|
|
|
|
if (result) {
|
|
hash = [NSData dataWithBytes:cHash length:TOX_HASH_LENGTH];
|
|
}
|
|
|
|
if (cHash) {
|
|
free(cHash);
|
|
}
|
|
|
|
OCTLogInfo(@"hash data result %@", hash);
|
|
|
|
return hash;
|
|
}
|
|
|
|
- (BOOL)fileSendControlForFileNumber:(OCTToxFileNumber)fileNumber
|
|
friendNumber:(OCTToxFriendNumber)friendNumber
|
|
control:(OCTToxFileControl)control
|
|
error:(NSError **)error
|
|
{
|
|
TOX_FILE_CONTROL cControl;
|
|
|
|
switch (control) {
|
|
case OCTToxFileControlResume:
|
|
cControl = TOX_FILE_CONTROL_RESUME;
|
|
break;
|
|
case OCTToxFileControlPause:
|
|
cControl = TOX_FILE_CONTROL_PAUSE;
|
|
break;
|
|
case OCTToxFileControlCancel:
|
|
cControl = TOX_FILE_CONTROL_CANCEL;
|
|
break;
|
|
}
|
|
|
|
TOX_ERR_FILE_CONTROL cError;
|
|
|
|
bool result = tox_file_control(self.tox, friendNumber, fileNumber, cControl, &cError);
|
|
|
|
[self fillError:error withCErrorFileControl:cError];
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (BOOL)fileSeekForFileNumber:(OCTToxFileNumber)fileNumber
|
|
friendNumber:(OCTToxFriendNumber)friendNumber
|
|
position:(OCTToxFileSize)position
|
|
error:(NSError **)error
|
|
{
|
|
TOX_ERR_FILE_SEEK cError;
|
|
|
|
bool result = tox_file_seek(self.tox, friendNumber, fileNumber, position, &cError);
|
|
|
|
[self fillError:error withCErrorFileSeek:cError];
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
- (NSData *)fileGetFileIdForFileNumber:(OCTToxFileNumber)fileNumber
|
|
friendNumber:(OCTToxFriendNumber)friendNumber
|
|
error:(NSError **)error
|
|
{
|
|
uint8_t *cFileId = malloc(kOCTToxFileIdLength);
|
|
TOX_ERR_FILE_GET cError;
|
|
|
|
bool result = tox_file_get_file_id(self.tox, friendNumber, fileNumber, cFileId, &cError);
|
|
NSData *fileId;
|
|
|
|
[self fillError:error withCErrorFileGet:cError];
|
|
|
|
if (result) {
|
|
fileId = [NSData dataWithBytes:cFileId length:kOCTToxFileIdLength];
|
|
}
|
|
|
|
if (cFileId) {
|
|
free(cFileId);
|
|
}
|
|
|
|
return fileId;
|
|
}
|
|
|
|
- (OCTToxFileNumber)fileSendWithFriendNumber:(OCTToxFriendNumber)friendNumber
|
|
kind:(OCTToxFileKind)kind
|
|
fileSize:(OCTToxFileSize)fileSize
|
|
fileId:(NSData *)fileId
|
|
fileName:(NSString *)fileName
|
|
error:(NSError **)error
|
|
{
|
|
TOX_ERR_FILE_SEND cError;
|
|
TOX_FILE_KIND cKind;
|
|
const uint8_t *cFileId = NULL;
|
|
const uint8_t *cFileName = NULL;
|
|
|
|
switch (kind) {
|
|
case OCTToxFileKindData:
|
|
cKind = TOX_FILE_KIND_DATA;
|
|
break;
|
|
case OCTToxFileKindAvatar:
|
|
cKind = TOX_FILE_KIND_AVATAR;
|
|
break;
|
|
}
|
|
|
|
if (fileId.length) {
|
|
cFileId = [fileId bytes];
|
|
}
|
|
|
|
if (fileName.length) {
|
|
cFileName = (const uint8_t *)[fileName cStringUsingEncoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
OCTToxFileNumber result = tox_file_send(self.tox, friendNumber, cKind, fileSize, cFileId, cFileName, fileName.length, &cError);
|
|
|
|
[self fillError:error withCErrorFileSend:cError];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)fileSendChunkForFileNumber:(OCTToxFileNumber)fileNumber
|
|
friendNumber:(OCTToxFriendNumber)friendNumber
|
|
position:(OCTToxFileSize)position
|
|
data:(NSData *)data
|
|
error:(NSError **)error
|
|
{
|
|
TOX_ERR_FILE_SEND_CHUNK cError;
|
|
const uint8_t *cData = [data bytes];
|
|
|
|
bool result = tox_file_send_chunk(self.tox, friendNumber, fileNumber, position, cData, (uint32_t)data.length, &cError);
|
|
|
|
[self fillError:error withCErrorFileSendChunk:cError];
|
|
|
|
return (BOOL)result;
|
|
}
|
|
|
|
#pragma mark - Private methods
|
|
|
|
- (void)updateTimerIntervalIfNeeded
|
|
{
|
|
uint64_t nextIterate = tox_iteration_interval(self.tox) * USEC_PER_SEC;
|
|
|
|
if (self.previousIterate == nextIterate) {
|
|
return;
|
|
}
|
|
|
|
self.previousIterate = nextIterate;
|
|
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, nextIterate), nextIterate, nextIterate / 5);
|
|
}
|
|
|
|
- (void)setupCFunctions
|
|
{
|
|
_tox_self_get_public_key = tox_self_get_public_key;
|
|
}
|
|
|
|
- (void)setupCallbacks
|
|
{
|
|
tox_callback_self_connection_status(_tox, connectionStatusCallback);
|
|
tox_callback_friend_name(_tox, friendNameCallback);
|
|
tox_callback_friend_status_message(_tox, friendStatusMessageCallback);
|
|
tox_callback_friend_status(_tox, friendStatusCallback);
|
|
tox_callback_friend_connection_status(_tox, friendConnectionStatusCallback);
|
|
tox_callback_friend_typing(_tox, friendTypingCallback);
|
|
tox_callback_friend_read_receipt(_tox, friendReadReceiptCallback);
|
|
tox_callback_friend_request(_tox, friendRequestCallback);
|
|
tox_callback_friend_message(_tox, friendMessageCallback);
|
|
tox_callback_friend_lossless_packet(_tox, friendLosslessPacketCallback);
|
|
tox_callback_file_recv_control(_tox, fileReceiveControlCallback);
|
|
tox_callback_file_chunk_request(_tox, fileChunkRequestCallback);
|
|
tox_callback_file_recv(_tox, fileReceiveCallback);
|
|
tox_callback_file_recv_chunk(_tox, fileReceiveChunkCallback);
|
|
}
|
|
|
|
- (OCTToxUserStatus)userStatusFromCUserStatus:(TOX_USER_STATUS)cStatus
|
|
{
|
|
switch (cStatus) {
|
|
case TOX_USER_STATUS_NONE:
|
|
return OCTToxUserStatusNone;
|
|
case TOX_USER_STATUS_AWAY:
|
|
return OCTToxUserStatusAway;
|
|
case TOX_USER_STATUS_BUSY:
|
|
return OCTToxUserStatusBusy;
|
|
}
|
|
}
|
|
|
|
- (OCTToxConnectionStatus)userConnectionStatusFromCUserStatus:(TOX_CONNECTION)cStatus
|
|
{
|
|
switch (cStatus) {
|
|
case TOX_CONNECTION_NONE:
|
|
return OCTToxConnectionStatusNone;
|
|
case TOX_CONNECTION_TCP:
|
|
return OCTToxConnectionStatusTCP;
|
|
case TOX_CONNECTION_UDP:
|
|
return OCTToxConnectionStatusUDP;
|
|
}
|
|
}
|
|
|
|
- (OCTToxMessageType)messageTypeFromCMessageType:(TOX_MESSAGE_TYPE)cType
|
|
{
|
|
switch (cType) {
|
|
case TOX_MESSAGE_TYPE_NORMAL:
|
|
return OCTToxMessageTypeNormal;
|
|
case TOX_MESSAGE_TYPE_ACTION:
|
|
return OCTToxMessageTypeAction;
|
|
}
|
|
}
|
|
|
|
- (OCTToxFileControl)fileControlFromCFileControl:(TOX_FILE_CONTROL)cControl
|
|
{
|
|
switch (cControl) {
|
|
case TOX_FILE_CONTROL_RESUME:
|
|
return OCTToxFileControlResume;
|
|
case TOX_FILE_CONTROL_PAUSE:
|
|
return OCTToxFileControlPause;
|
|
case TOX_FILE_CONTROL_CANCEL:
|
|
return OCTToxFileControlCancel;
|
|
}
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorInit:(TOX_ERR_NEW)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_NEW_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorInitCode code = OCTToxErrorInitCodeUnknown;
|
|
NSString *description = @"Cannot initialize Tox";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_NEW_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_NEW_NULL:
|
|
code = OCTToxErrorInitCodeUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_NEW_MALLOC:
|
|
code = OCTToxErrorInitCodeMemoryError;
|
|
failureReason = @"Not enough memory";
|
|
break;
|
|
case TOX_ERR_NEW_PORT_ALLOC:
|
|
code = OCTToxErrorInitCodePortAlloc;
|
|
failureReason = @"Cannot bint to a port";
|
|
break;
|
|
case TOX_ERR_NEW_PROXY_BAD_TYPE:
|
|
code = OCTToxErrorInitCodeProxyBadType;
|
|
failureReason = @"Proxy type is invalid";
|
|
break;
|
|
case TOX_ERR_NEW_PROXY_BAD_HOST:
|
|
code = OCTToxErrorInitCodeProxyBadHost;
|
|
failureReason = @"Proxy host is invalid";
|
|
break;
|
|
case TOX_ERR_NEW_PROXY_BAD_PORT:
|
|
code = OCTToxErrorInitCodeProxyBadPort;
|
|
failureReason = @"Proxy port is invalid";
|
|
break;
|
|
case TOX_ERR_NEW_PROXY_NOT_FOUND:
|
|
code = OCTToxErrorInitCodeProxyNotFound;
|
|
failureReason = @"Proxy host could not be resolved";
|
|
break;
|
|
case TOX_ERR_NEW_LOAD_ENCRYPTED:
|
|
code = OCTToxErrorInitCodeEncrypted;
|
|
failureReason = @"Tox save is encrypted";
|
|
break;
|
|
case TOX_ERR_NEW_LOAD_BAD_FORMAT:
|
|
code = OCTToxErrorInitCodeLoadBadFormat;
|
|
failureReason = @"Tox save is corrupted";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorBootstrap:(TOX_ERR_BOOTSTRAP)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_BOOTSTRAP_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorBootstrapCode code = OCTToxErrorBootstrapCodeUnknown;
|
|
NSString *description = @"Cannot bootstrap with specified node";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_BOOTSTRAP_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_BOOTSTRAP_NULL:
|
|
code = OCTToxErrorBootstrapCodeUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_BOOTSTRAP_BAD_HOST:
|
|
code = OCTToxErrorBootstrapCodeBadHost;
|
|
failureReason = @"The host could not be resolved to an IP address, or the IP address passed was invalid";
|
|
break;
|
|
case TOX_ERR_BOOTSTRAP_BAD_PORT:
|
|
code = OCTToxErrorBootstrapCodeBadPort;
|
|
failureReason = @"The port passed was invalid";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFriendAdd:(TOX_ERR_FRIEND_ADD)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FRIEND_ADD_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFriendAdd code = OCTToxErrorFriendAddUnknown;
|
|
NSString *description = @"Cannot add friend";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FRIEND_ADD_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FRIEND_ADD_NULL:
|
|
code = OCTToxErrorFriendAddUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_FRIEND_ADD_TOO_LONG:
|
|
code = OCTToxErrorFriendAddTooLong;
|
|
failureReason = @"The message is too long";
|
|
break;
|
|
case TOX_ERR_FRIEND_ADD_NO_MESSAGE:
|
|
code = OCTToxErrorFriendAddNoMessage;
|
|
failureReason = @"No message specified";
|
|
break;
|
|
case TOX_ERR_FRIEND_ADD_OWN_KEY:
|
|
code = OCTToxErrorFriendAddOwnKey;
|
|
failureReason = @"Cannot add own address";
|
|
break;
|
|
case TOX_ERR_FRIEND_ADD_ALREADY_SENT:
|
|
code = OCTToxErrorFriendAddAlreadySent;
|
|
failureReason = @"The request was already sent";
|
|
break;
|
|
case TOX_ERR_FRIEND_ADD_BAD_CHECKSUM:
|
|
code = OCTToxErrorFriendAddBadChecksum;
|
|
failureReason = @"Bad checksum";
|
|
break;
|
|
case TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM:
|
|
code = OCTToxErrorFriendAddSetNewNospam;
|
|
failureReason = @"The no spam value is outdated";
|
|
break;
|
|
case TOX_ERR_FRIEND_ADD_MALLOC:
|
|
code = OCTToxErrorFriendAddMalloc;
|
|
failureReason = nil;
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFriendDelete:(TOX_ERR_FRIEND_DELETE)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FRIEND_DELETE_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFriendDelete code = OCTToxErrorFriendDeleteNotFound;
|
|
NSString *description = @"Cannot delete friend";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FRIEND_DELETE_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFriendDeleteNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFriendByPublicKey:(TOX_ERR_FRIEND_BY_PUBLIC_KEY)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFriendByPublicKey code = OCTToxErrorFriendByPublicKeyUnknown;
|
|
NSString *description = @"Cannot get friend by public key";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL:
|
|
code = OCTToxErrorFriendByPublicKeyUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND:
|
|
code = OCTToxErrorFriendByPublicKeyNotFound;
|
|
failureReason = @"No friend with the given Public Key exists on the friend list";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFriendGetPublicKey:(TOX_ERR_FRIEND_GET_PUBLIC_KEY)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFriendGetPublicKey code = OCTToxErrorFriendGetPublicKeyFriendNotFound;
|
|
NSString *description = @"Cannot get public key of a friend";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFriendGetPublicKeyFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorSetInfo:(TOX_ERR_SET_INFO)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_SET_INFO_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorSetInfoCode code = OCTToxErrorSetInfoCodeUnknow;
|
|
NSString *description = @"Cannot set user info";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_SET_INFO_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_SET_INFO_NULL:
|
|
code = OCTToxErrorSetInfoCodeUnknow;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_SET_INFO_TOO_LONG:
|
|
code = OCTToxErrorSetInfoCodeTooLong;
|
|
failureReason = @"Specified string is too long";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFriendGetLastOnline:(TOX_ERR_FRIEND_GET_LAST_ONLINE)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FRIEND_GET_LAST_ONLINE_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFriendGetLastOnline code;
|
|
NSString *description = @"Cannot get last online of a friend";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FRIEND_GET_LAST_ONLINE_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFriendGetLastOnlineFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFriendQuery:(TOX_ERR_FRIEND_QUERY)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FRIEND_QUERY_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFriendQuery code = OCTToxErrorFriendQueryUnknown;
|
|
NSString *description = @"Cannot perform friend query";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FRIEND_QUERY_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FRIEND_QUERY_NULL:
|
|
code = OCTToxErrorFriendQueryUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFriendQueryFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorSetTyping:(TOX_ERR_SET_TYPING)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_SET_TYPING_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorSetTyping code = OCTToxErrorSetTypingFriendNotFound;
|
|
NSString *description = @"Cannot set typing status for a friend";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_SET_TYPING_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorSetTypingFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFriendSendMessage:(TOX_ERR_FRIEND_SEND_MESSAGE)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FRIEND_SEND_MESSAGE_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFriendSendMessage code = OCTToxErrorFriendSendMessageUnknown;
|
|
NSString *description = @"Cannot send message to a friend";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FRIEND_SEND_MESSAGE_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FRIEND_SEND_MESSAGE_NULL:
|
|
code = OCTToxErrorFriendSendMessageUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFriendSendMessageFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED:
|
|
code = OCTToxErrorFriendSendMessageFriendNotConnected;
|
|
failureReason = @"Friend not connected";
|
|
break;
|
|
case TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ:
|
|
code = OCTToxErrorFriendSendMessageAlloc;
|
|
failureReason = @"Allocation error";
|
|
break;
|
|
case TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG:
|
|
code = OCTToxErrorFriendSendMessageTooLong;
|
|
failureReason = @"Message is too long";
|
|
break;
|
|
case TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY:
|
|
code = OCTToxErrorFriendSendMessageEmpty;
|
|
failureReason = @"Message is empty";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFileControl:(TOX_ERR_FILE_CONTROL)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FILE_CONTROL_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFileControl code;
|
|
NSString *description = @"Cannot send file control to a friend";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FILE_CONTROL_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFileControlFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
case TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED:
|
|
code = OCTToxErrorFileControlFriendNotConnected;
|
|
failureReason = @"Friend is not connected";
|
|
break;
|
|
case TOX_ERR_FILE_CONTROL_NOT_FOUND:
|
|
code = OCTToxErrorFileControlNotFound;
|
|
failureReason = @"No file transfer with given file number found";
|
|
break;
|
|
case TOX_ERR_FILE_CONTROL_NOT_PAUSED:
|
|
code = OCTToxErrorFileControlNotPaused;
|
|
failureReason = @"Resume was send, but file transfer if running normally";
|
|
break;
|
|
case TOX_ERR_FILE_CONTROL_DENIED:
|
|
code = OCTToxErrorFileControlDenied;
|
|
failureReason = @"Cannot resume, file transfer was paused by the other party.";
|
|
break;
|
|
case TOX_ERR_FILE_CONTROL_ALREADY_PAUSED:
|
|
code = OCTToxErrorFileControlAlreadyPaused;
|
|
failureReason = @"File is already paused";
|
|
break;
|
|
case TOX_ERR_FILE_CONTROL_SENDQ:
|
|
code = OCTToxErrorFileControlSendq;
|
|
failureReason = @"Packet queue is full";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFileSeek:(TOX_ERR_FILE_SEEK)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FILE_SEEK_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFileSeek code;
|
|
NSString *description = @"Cannot perform file seek";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FILE_SEEK_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFileSeekFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
case TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED:
|
|
code = OCTToxErrorFileSeekFriendNotConnected;
|
|
failureReason = @"Friend is not connected";
|
|
break;
|
|
case TOX_ERR_FILE_SEEK_NOT_FOUND:
|
|
code = OCTToxErrorFileSeekNotFound;
|
|
failureReason = @"No file transfer with given file number found";
|
|
break;
|
|
case TOX_ERR_FILE_SEEK_DENIED:
|
|
code = OCTToxErrorFileSeekDenied;
|
|
failureReason = @"File was not in a state where it could be seeked";
|
|
break;
|
|
case TOX_ERR_FILE_SEEK_INVALID_POSITION:
|
|
code = OCTToxErrorFileSeekInvalidPosition;
|
|
failureReason = @"Seek position was invalid";
|
|
break;
|
|
case TOX_ERR_FILE_SEEK_SENDQ:
|
|
code = OCTToxErrorFileSeekSendq;
|
|
failureReason = @"Packet queue is full";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFileGet:(TOX_ERR_FILE_GET)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FILE_GET_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFileGet code;
|
|
NSString *description = @"Cannot get file id";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FILE_GET_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FILE_GET_NULL:
|
|
code = OCTToxErrorFileGetInternal;
|
|
failureReason = @"Interval error";
|
|
break;
|
|
case TOX_ERR_FILE_GET_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFileGetFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
case TOX_ERR_FILE_GET_NOT_FOUND:
|
|
code = OCTToxErrorFileGetNotFound;
|
|
failureReason = @"No file transfer with given file number found";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFileSend:(TOX_ERR_FILE_SEND)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FILE_SEND_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFileSend code;
|
|
NSString *description = @"Cannot send file";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FILE_SEND_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FILE_SEND_NULL:
|
|
code = OCTToxErrorFileSendUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFileSendFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED:
|
|
code = OCTToxErrorFileSendFriendNotConnected;
|
|
failureReason = @"Friend not connected";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_NAME_TOO_LONG:
|
|
code = OCTToxErrorFileSendNameTooLong;
|
|
failureReason = @"File name is too long";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_TOO_MANY:
|
|
code = OCTToxErrorFileSendTooMany;
|
|
failureReason = @"Too many ongoing transfers with friend";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillError:(NSError **)error withCErrorFileSendChunk:(TOX_ERR_FILE_SEND_CHUNK)cError
|
|
{
|
|
if (! error || (cError == TOX_ERR_FILE_SEND_CHUNK_OK)) {
|
|
return NO;
|
|
}
|
|
|
|
OCTToxErrorFileSendChunk code;
|
|
NSString *description = @"Cannot send chunk of file";
|
|
NSString *failureReason = nil;
|
|
|
|
switch (cError) {
|
|
case TOX_ERR_FILE_SEND_CHUNK_OK:
|
|
NSAssert(NO, @"We shouldn't be here");
|
|
return NO;
|
|
case TOX_ERR_FILE_SEND_CHUNK_NULL:
|
|
code = OCTToxErrorFileSendChunkUnknown;
|
|
failureReason = @"Unknown error occured";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND:
|
|
code = OCTToxErrorFileSendChunkFriendNotFound;
|
|
failureReason = @"Friend not found";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED:
|
|
code = OCTToxErrorFileSendChunkFriendNotConnected;
|
|
failureReason = @"Friend not connected";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND:
|
|
code = OCTToxErrorFileSendChunkNotFound;
|
|
failureReason = @"No file transfer with given file number found";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING:
|
|
code = OCTToxErrorFileSendChunkNotTransferring;
|
|
failureReason = @"Wrong file transferring state";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH:
|
|
code = OCTToxErrorFileSendChunkInvalidLength;
|
|
failureReason = @"Invalid chunk length";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_CHUNK_SENDQ:
|
|
code = OCTToxErrorFileSendChunkSendq;
|
|
failureReason = @"Packet queue is full";
|
|
break;
|
|
case TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION:
|
|
code = OCTToxErrorFileSendChunkWrongPosition;
|
|
failureReason = @"Wrong position in file";
|
|
break;
|
|
}
|
|
|
|
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
|
|
|
|
return YES;
|
|
}
|
|
|
|
+ (NSError *)createErrorWithCode:(NSUInteger)code
|
|
description:(NSString *)description
|
|
failureReason:(NSString *)failureReason
|
|
{
|
|
NSMutableDictionary *userInfo = [NSMutableDictionary new];
|
|
|
|
if (description) {
|
|
userInfo[NSLocalizedDescriptionKey] = description;
|
|
}
|
|
|
|
if (failureReason) {
|
|
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
|
|
}
|
|
|
|
return [NSError errorWithDomain:kOCTToxErrorDomain code:code userInfo:userInfo];
|
|
}
|
|
|
|
+ (NSString *)binToHexString:(uint8_t *)bin length:(NSUInteger)length
|
|
{
|
|
NSMutableString *string = [NSMutableString stringWithCapacity:length];
|
|
|
|
for (NSUInteger idx = 0; idx < length; ++idx) {
|
|
[string appendFormat:@"%02X", bin[idx]];
|
|
}
|
|
|
|
return [string copy];
|
|
}
|
|
|
|
// You are responsible for freeing the return value!
|
|
+ (uint8_t *)hexStringToBin:(NSString *)string
|
|
{
|
|
// byte is represented by exactly 2 hex digits, so lenth of binary string
|
|
// is half of that of the hex one. only hex string with even length
|
|
// valid. the more proper implementation would be to check if strlen(hex_string)
|
|
// is odd and return error code if it is. we assume strlen is even. if it's not
|
|
// then the last byte just won't be written in 'ret'.
|
|
|
|
char *hex_string = (char *)string.UTF8String;
|
|
size_t i, len = strlen(hex_string) / 2;
|
|
uint8_t *ret = malloc(len);
|
|
char *pos = hex_string;
|
|
|
|
for (i = 0; i < len; ++i, pos += 2) {
|
|
sscanf(pos, "%2hhx", &ret[i]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - Callbacks
|
|
|
|
void logCallback(Tox *tox,
|
|
TOX_LOG_LEVEL level,
|
|
const char *file,
|
|
uint32_t line,
|
|
const char *func,
|
|
const char *message,
|
|
void *user_data)
|
|
{
|
|
switch (level) {
|
|
case TOX_LOG_LEVEL_TRACE:
|
|
OCTLogCCVerbose(@"TRACE: <toxcore: %s:%u, %s> %s", file, line, func, message);
|
|
break;
|
|
case TOX_LOG_LEVEL_DEBUG:
|
|
OCTLogCCDebug(@"DEBUG: <toxcore: %s:%u, %s> %s", file, line, func, message);
|
|
break;
|
|
case TOX_LOG_LEVEL_INFO:
|
|
OCTLogCCInfo(@"INFO: <toxcore: %s:%u, %s> %s", file, line, func, message);
|
|
break;
|
|
case TOX_LOG_LEVEL_WARNING:
|
|
OCTLogCCWarn(@"WARNING: <toxcore: %s:%u, %s> %s", file, line, func, message);
|
|
break;
|
|
case TOX_LOG_LEVEL_ERROR:
|
|
OCTLogCCError(@"ERROR: <toxcore: %s:%u, %s> %s", file, line, func, message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void connectionStatusCallback(Tox *cTox, TOX_CONNECTION cStatus, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
OCTToxConnectionStatus status = [tox userConnectionStatusFromCUserStatus:cStatus];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"connectionStatusCallback with status %lu", tox, (unsigned long)status);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:connectionStatus:)]) {
|
|
[tox.delegate tox:tox connectionStatus:status];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendNameCallback(Tox *cTox, uint32_t friendNumber, const uint8_t *cName, size_t length, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
NSString *name = [NSString stringWithCString:(const char *)cName encoding:NSUTF8StringEncoding];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"nameChangeCallback with name %@, friend number %d", tox, name, friendNumber);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendNameUpdate:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendNameUpdate:name friendNumber:friendNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendStatusMessageCallback(Tox *cTox, uint32_t friendNumber, const uint8_t *cMessage, size_t length, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
NSString *message = [NSString stringWithCString:(const char *)cMessage encoding:NSUTF8StringEncoding];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"statusMessageCallback with status message %@, friend number %d", tox, message, friendNumber);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendStatusMessageUpdate:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendStatusMessageUpdate:message friendNumber:friendNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendStatusCallback(Tox *cTox, uint32_t friendNumber, TOX_USER_STATUS cStatus, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
OCTToxUserStatus status = [tox userStatusFromCUserStatus:cStatus];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"userStatusCallback with status %lu, friend number %d", tox, (unsigned long)status, friendNumber);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendStatusUpdate:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendStatusUpdate:status friendNumber:friendNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendConnectionStatusCallback(Tox *cTox, uint32_t friendNumber, TOX_CONNECTION cStatus, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
OCTToxConnectionStatus status = [tox userConnectionStatusFromCUserStatus:cStatus];
|
|
|
|
OCTLogCInfo(@"connectionStatusCallback with status %lu, friendNumber %d", tox, (unsigned long)status, friendNumber);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendConnectionStatusChanged:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendConnectionStatusChanged:status friendNumber:friendNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendTypingCallback(Tox *cTox, uint32_t friendNumber, bool isTyping, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
OCTLogCInfo(@"typingChangeCallback with isTyping %d, friend number %d", tox, isTyping, friendNumber);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendIsTypingUpdate:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendIsTypingUpdate:(BOOL)isTyping friendNumber:friendNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendReadReceiptCallback(Tox *cTox, uint32_t friendNumber, uint32_t messageId, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
OCTLogCInfo(@"readReceiptCallback with message id %d, friendNumber %d", tox, messageId, friendNumber);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([tox.delegate respondsToSelector:@selector(tox:messageDelivered:friendNumber:)]) {
|
|
[tox.delegate tox:tox messageDelivered:messageId friendNumber:friendNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendRequestCallback(Tox *cTox, const uint8_t *cPublicKey, const uint8_t *cMessage, size_t length, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
NSString *publicKey = [OCTTox binToHexString:(uint8_t *)cPublicKey length:TOX_PUBLIC_KEY_SIZE];
|
|
NSString *message = [[NSString alloc] initWithBytes:cMessage length:length encoding:NSUTF8StringEncoding];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"friendRequestCallback with publicKey %@, message %@", tox, publicKey, message);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendRequestWithMessage:publicKey:)]) {
|
|
[tox.delegate tox:tox friendRequestWithMessage:message publicKey:publicKey];
|
|
}
|
|
});
|
|
}
|
|
|
|
void friendMessageCallback(
|
|
Tox *cTox,
|
|
uint32_t friendNumber,
|
|
TOX_MESSAGE_TYPE cType,
|
|
const uint8_t *cMessage,
|
|
size_t length,
|
|
void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
// HINT: invalid UTF-8 will make realm manager crash later, or if length is shorter than bytes in cMessage
|
|
// so check at least for NULL bytes and shorten length accordingly
|
|
|
|
uint8_t *newcMessage = calloc(1, length + 1);
|
|
if (!newcMessage)
|
|
{
|
|
// HINT: we cant allocate the new buffer, so we must ignore this incoming message
|
|
return;
|
|
}
|
|
|
|
size_t newLength = 0;
|
|
for (int i=0; i < length; i++)
|
|
{
|
|
if (*(cMessage + i) != 0)
|
|
{
|
|
newLength++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
memcpy(newcMessage, cMessage, (size_t)newLength);
|
|
|
|
// add 1 for the NULL byte at the end
|
|
newLength++;
|
|
|
|
if (newLength < 2)
|
|
{
|
|
// HINT: message seems to contain nothing before the first NULL byte, so discard it
|
|
free(newcMessage);
|
|
return;
|
|
}
|
|
|
|
NSString *message = [[NSString alloc] initWithBytes:newcMessage length:newLength encoding:NSUTF8StringEncoding];
|
|
free(newcMessage);
|
|
|
|
if (!message)
|
|
{
|
|
// HINT: message seems to contain invalid UTF-8
|
|
// instead use a dummy message "__"
|
|
message = @"__";
|
|
newLength = 2;
|
|
}
|
|
|
|
|
|
// HINT: msgV3 ------------------------------------------------
|
|
// HINT: msgV3 ------------------------------------------------
|
|
// HINT: msgV3 ------------------------------------------------
|
|
int need_free = 0;
|
|
uint32_t msgv3_timstamp_int = 0;
|
|
char *message_v3_hash_hexstr = NULL;
|
|
|
|
if ((cMessage) && (length > (TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH + TOX_MSGV3_GUARD)))
|
|
{
|
|
int pos = length - (TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH + TOX_MSGV3_GUARD);
|
|
|
|
// bytes at guard position
|
|
uint8_t g1 = *(cMessage + pos);
|
|
uint8_t g2 = *(cMessage + pos + 1);
|
|
|
|
// check for the msgv3 guard
|
|
if ((g1 == 0) && (g2 == 0))
|
|
{
|
|
uint8_t *message_v3_hash_bin = calloc(1, TOX_MSGV3_MSGID_LENGTH);
|
|
if (!message_v3_hash_bin)
|
|
{
|
|
OCTLogCInfo(@"friendMessageCallback:friend_message_cb:could not allocate buffer for hash: incoming message discarded", tox);
|
|
return;
|
|
}
|
|
|
|
uint8_t *message_v3_timestamp_bin = calloc(1, TOX_MSGV3_TIMESTAMP_LENGTH);
|
|
if (!message_v3_timestamp_bin)
|
|
{
|
|
OCTLogCInfo(@"friendMessageCallback:friend_message_cb:could not allocate buffer for timestamp: incoming message discarded", tox);
|
|
free(message_v3_hash_bin);
|
|
return;
|
|
}
|
|
|
|
need_free = 1;
|
|
memcpy(message_v3_hash_bin, (cMessage + pos + TOX_MSGV3_GUARD), TOX_MSGV3_MSGID_LENGTH);
|
|
memcpy(message_v3_timestamp_bin, (cMessage + pos + TOX_MSGV3_GUARD + TOX_MSGV3_MSGID_LENGTH), TOX_MSGV3_TIMESTAMP_LENGTH);
|
|
|
|
message_v3_hash_hexstr = calloc(1, (TOX_MSGV3_MSGID_LENGTH * 2) + 1);
|
|
if (message_v3_hash_hexstr)
|
|
{
|
|
bin_to_hex((const char *)message_v3_hash_bin, (size_t)TOX_MSGV3_MSGID_LENGTH, message_v3_hash_hexstr);
|
|
const uint8_t *p = (uint8_t *)(message_v3_timestamp_bin);
|
|
p += xnet_unpack_u32(p, &msgv3_timstamp_int);
|
|
// OCTLogCInfo(@"mmm:friendMessageCallback:friend_message_cb:hash=%s ts=%d", tox, message_v3_hash_hexstr, msgv3_timstamp_int);
|
|
}
|
|
|
|
if (need_free == 1)
|
|
{
|
|
free(message_v3_hash_bin);
|
|
free(message_v3_timestamp_bin);
|
|
}
|
|
}
|
|
}
|
|
// HINT: msgV3 ------------------------------------------------
|
|
// HINT: msgV3 ------------------------------------------------
|
|
// HINT: msgV3 ------------------------------------------------
|
|
|
|
NSString *msgv3HashHexStr = nil;
|
|
if (message_v3_hash_hexstr)
|
|
{
|
|
msgv3HashHexStr = [[NSString alloc] initWithBytes:message_v3_hash_hexstr length:(TOX_MSGV3_MSGID_LENGTH * 2) encoding:NSUTF8StringEncoding];
|
|
free(message_v3_hash_hexstr);
|
|
OCTLogCInfo(@"friendMessageCallback with friend message %@", tox, msgv3HashHexStr);
|
|
}
|
|
|
|
if (cType == TOX_MESSAGE_TYPE_HIGH_LEVEL_ACK)
|
|
{
|
|
// HINT: this message is not a normal message, but a msgV3 high level ACK.
|
|
// we do not save it in the database, nor show it in the chat window.
|
|
if (msgv3HashHexStr != nil)
|
|
{
|
|
// HINT: set isDelivered status to true for the message with this hex hash.
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"friendMessageCallback received level ACK %@", tox, msgv3HashHexStr);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendHighLevelACK:friendNumber:msgv3HashHex:sendTimestamp:)]) {
|
|
[tox.delegate tox:tox friendHighLevelACK:message friendNumber:friendNumber
|
|
msgv3HashHex:msgv3HashHexStr sendTimestamp:msgv3_timstamp_int];
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (msgv3HashHexStr != nil)
|
|
{
|
|
// HINT: msgV3 message reveived
|
|
// friend must have msgV3 capability, set it in the database
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendSetMsgv3Capability:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendSetMsgv3Capability:YES friendNumber:friendNumber];
|
|
}
|
|
OCTLogCInfo(@"friendMessageCallback msgV3 YES", tox);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// HINT: old msg version recevied
|
|
// friend does not msgV3 capability, clear it in the database
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendSetMsgv3Capability:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendSetMsgv3Capability:NO friendNumber:friendNumber];
|
|
}
|
|
OCTLogCInfo(@"friendMessageCallback msgV3 --NO--", tox);
|
|
});
|
|
}
|
|
|
|
OCTToxMessageType type = [tox messageTypeFromCMessageType:cType];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// OCTLogCInfo(@"friendMessageCallback with message %@, friend number %d", tox, message, friendNumber);
|
|
// OCTLogCInfo(@"friendMessageCallback with friend number %d len %d newlen %d", tox, friendNumber, length, newLength);
|
|
// OCTLogCInfo(@"friendMessageCallback with friend message %@", tox, msgv3HashHexStr);
|
|
|
|
// HINT: save message to database
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendMessage:type:friendNumber:msgv3HashHex:sendTimestamp:)]) {
|
|
[tox.delegate tox:tox friendMessage:message type:type friendNumber:friendNumber
|
|
msgv3HashHex:msgv3HashHexStr sendTimestamp:msgv3_timstamp_int];
|
|
}
|
|
|
|
// HINT: now send msgV3 high level ACK
|
|
if ([tox.delegate respondsToSelector:@selector(tox:sendFriendHighlevelACK:friendNumber:msgv3HashHex:sendTimestamp:)]) {
|
|
NSString *message = @"_";
|
|
[tox.delegate tox:tox sendFriendHighlevelACK:message friendNumber:friendNumber
|
|
msgv3HashHex:msgv3HashHexStr sendTimestamp:msgv3_timstamp_int];
|
|
}
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
void friendLosslessPacketCallback(
|
|
Tox *cTox,
|
|
uint32_t friendNumber,
|
|
const uint8_t *data,
|
|
size_t length,
|
|
void *userData)
|
|
{
|
|
if ((length <= 5) || (length >= 300)) {
|
|
return;
|
|
}
|
|
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
// TODO: catch errors and bad utf-8 here!
|
|
NSData *lossless_bytes = [NSData dataWithBytes:data length:length];
|
|
|
|
if (lossless_bytes == nil) {
|
|
return;
|
|
}
|
|
|
|
NSString *pushTokenString = nil;
|
|
|
|
if ((length > 5) && (length < 300)) {
|
|
if (data[0] == 181) {
|
|
pushTokenString = [[NSString alloc] initWithUTF8String:(data + 1)];
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// OCTLogCInfo(@"friendLosslessPacketCallback with lossless data %@, friend number %d",
|
|
// tox, [lossless_bytes description], friendNumber);
|
|
// OCTLogCInfo(@"friendLosslessPacketCallback with pushTokenString %@, friend number %d",
|
|
// tox, [pushTokenString description], friendNumber);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:friendPushTokenUpdate:friendNumber:)]) {
|
|
[tox.delegate tox:tox friendPushTokenUpdate:pushTokenString friendNumber:friendNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void fileReceiveControlCallback(Tox *cTox, uint32_t friendNumber, OCTToxFileNumber fileNumber, TOX_FILE_CONTROL cControl, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
OCTToxFileControl control = [tox fileControlFromCFileControl:cControl];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"fileReceiveControlCallback with friendNumber %d fileNumber %d controlType %lu",
|
|
tox, friendNumber, fileNumber, (unsigned long)control);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:fileReceiveControl:friendNumber:fileNumber:)]) {
|
|
[tox.delegate tox:tox fileReceiveControl:control friendNumber:friendNumber fileNumber:fileNumber];
|
|
}
|
|
});
|
|
}
|
|
|
|
void fileChunkRequestCallback(Tox *cTox, uint32_t friendNumber, OCTToxFileNumber fileNumber, uint64_t position, size_t length, void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([tox.delegate respondsToSelector:@selector(tox:fileChunkRequestForFileNumber:friendNumber:position:length:)]) {
|
|
[tox.delegate tox:tox fileChunkRequestForFileNumber:fileNumber
|
|
friendNumber:friendNumber
|
|
position:position
|
|
length:length];
|
|
}
|
|
});
|
|
}
|
|
|
|
void fileReceiveCallback(
|
|
Tox *cTox,
|
|
uint32_t friendNumber,
|
|
OCTToxFileNumber fileNumber,
|
|
TOX_FILE_KIND cKind,
|
|
uint64_t fileSize,
|
|
const uint8_t *cFileName,
|
|
size_t fileNameLength,
|
|
void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
OCTToxFileKind kind;
|
|
|
|
switch (cKind) {
|
|
case TOX_FILE_KIND_DATA:
|
|
kind = OCTToxFileKindData;
|
|
break;
|
|
case TOX_FILE_KIND_AVATAR:
|
|
kind = OCTToxFileKindAvatar;
|
|
break;
|
|
}
|
|
|
|
NSString *fileName = [[NSString alloc] initWithBytes:cFileName length:fileNameLength encoding:NSUTF8StringEncoding];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
OCTLogCInfo(@"fileReceiveCallback with friendNumber %d fileNumber %d kind %ld fileSize %llu fileName %@",
|
|
tox, friendNumber, fileNumber, (long)kind, fileSize, fileName);
|
|
|
|
if ([tox.delegate respondsToSelector:@selector(tox:fileReceiveForFileNumber:friendNumber:kind:fileSize:fileName:)]) {
|
|
[tox.delegate tox:tox fileReceiveForFileNumber:fileNumber
|
|
friendNumber:friendNumber
|
|
kind:kind
|
|
fileSize:fileSize
|
|
fileName:fileName];
|
|
}
|
|
});
|
|
}
|
|
|
|
void fileReceiveChunkCallback(
|
|
Tox *cTox,
|
|
uint32_t friendNumber,
|
|
OCTToxFileNumber fileNumber,
|
|
uint64_t position,
|
|
const uint8_t *cData,
|
|
size_t length,
|
|
void *userData)
|
|
{
|
|
OCTTox *tox = (__bridge OCTTox *)(userData);
|
|
|
|
NSData *chunk = nil;
|
|
|
|
if (length) {
|
|
chunk = [NSData dataWithBytes:cData length:length];
|
|
}
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([tox.delegate respondsToSelector:@selector(tox:fileReceiveChunk:fileNumber:friendNumber:position:)]) {
|
|
[tox.delegate tox:tox fileReceiveChunk:chunk fileNumber:fileNumber friendNumber:friendNumber position:position];
|
|
}
|
|
});
|
|
}
|