From 1c5d231d6b29535d8b196d382585ec24b1c932bd Mon Sep 17 00:00:00 2001
From: Fabrizio Gennari <fabrizio.ge@tiscali.it>
Date: Tue, 2 Feb 2016 22:50:15 +0100
Subject: [PATCH] Rewrite MacOS X Bluetooth implementation, since the old one
 used outdated APIs that have not compiled since OS X 10.7 Lion

---
 ChangeLog                      |   3 +
 common/devices/Makefile.am     |   2 +-
 common/devices/osxbluetooth.c  | 205 -----------------------------------------
 common/devices/osxbluetooth.m  | 183 ++++++++++++++++++++++++++++++++++++
 configure.ac                   |  31 +++----
 include/devices/osxbluetooth.h |  49 ----------
 6 files changed, 200 insertions(+), 273 deletions(-)
 delete mode 100644 common/devices/osxbluetooth.c
 create mode 100644 common/devices/osxbluetooth.m
 delete mode 100644 include/devices/osxbluetooth.h

diff --git a/ChangeLog b/ChangeLog
index cf6bb03..800b454 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -337,6 +337,9 @@
       from/to vcard files                               (Paweł Kot)
     o support recurrence in ical files                  (Paweł Kot)
     o support alarms in ical files                  (Daniele Forsi)
+    o Rewrite MacOS X Bluetooth implementation, since the old one
+        used outdated APIs that have not compiled since OS X 10.7
+        Lion                                     (Fabrizio Gennari)
  * build system updates
     o remove GNOKII_API from the definition of asprinf
                                                       (Jari Turkia)
diff --git a/common/devices/Makefile.am b/common/devices/Makefile.am
index 8d389f0..ccaa52b 100644
--- a/common/devices/Makefile.am
+++ b/common/devices/Makefile.am
@@ -10,7 +10,7 @@ WIN32_FILES = \
 UNIX_FILES = \
 	unixserial.c \
 	unixbluetooth.c \
-	osxbluetooth.c \
+	osxbluetooth.m \
 	unixirda.c \
 	tcp.c \
 	socketphonet.c
diff --git a/common/devices/osxbluetooth.c b/common/devices/osxbluetooth.c
deleted file mode 100644
index 254bf52..0000000
--- a/common/devices/osxbluetooth.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
-
-  G N O K I I
-
-  A Linux/Unix toolset and driver for the mobile phones.
-
-  This file is part of gnokii.
-
-  Copyright (C) 1999-2000  Hugh Blemings & Pavel Jan�k ml.
-  Copyright (C) 2003       Siegfried Schloissnig
-
-*/
-
-#include "config.h"
-
-#ifdef HAVE_BLUETOOTH_MACOSX
-
-#include "compat.h"
-#include "devices/osxbluetooth.h"
-#include "misc.h"
-#include "gnokii.h"
-#include "devices/unixbluetooth.h"
-
-/* ----- bluetooth io thread ----- */
-
-static void thread_rfcommDataListener(IOBluetoothRFCOMMChannelRef rfcommChannel,
-                               void* data, UInt16 length, void* refCon)
-{
-	threadContext *pContext = (threadContext *)refCon;
-	void *pBuffer = malloc(length);
-	dataBlock *pDataBlock = (dataBlock *)malloc(sizeof(dataBlock));
-
-	memcpy(pBuffer, data, length);
-
-	pDataBlock->pData = pBuffer;
-	pDataBlock->nSize = length;
-
-	pthread_mutex_lock(&(pContext->mutexWait));
-	CFArrayAppendValue(pContext->arrDataReceived, pDataBlock);
-	pthread_mutex_unlock(&(pContext->mutexWait));
-}
-
-static void *thread_main(void *pArg)
-{
-	threadContext* pContext = (threadContext *)pArg;
-	IOBluetoothDeviceRef device = IOBluetoothDeviceCreateWithAddress(&(pContext->deviceAddress));
-	IOBluetoothRFCOMMChannelRef rfcommChannel;
-
-	if (IOBluetoothDeviceOpenRFCOMMChannel(device, pContext->nChannel,
-				      &rfcommChannel) != kIOReturnSuccess) {
-		rfcommChannel = 0;
-	} else {
-		/* register an incoming data listener */
-		if (IOBluetoothRFCOMMChannelRegisterIncomingDataListener(rfcommChannel,
-			 thread_rfcommDataListener, pArg) != kIOReturnSuccess) {
-		    rfcommChannel = 0;
-		}
-	}
-
-	pContext->rfcommChannel = rfcommChannel;
-
-	pthread_mutex_unlock(&(pContext->mutexWait));
-
-	/* start the runloop */
-	CFRunLoopRun();
-}
-
-/* ---- bluetooth io thread ---- */
-
-int bluetooth_open(const char* addr, uint8_t channel, struct gn_statemachine* state)
-{
-	/* create the thread context and start the thread */
-	CFStringRef strDevice;
-	pthread_t threadID;
-	threadContext *pContext = (threadContext *)malloc(sizeof(threadContext));
-
-	strDevice = CFStringCreateWithCString(kCFAllocatorDefault, addr, kCFStringEncodingMacRomanLatin1);
-	IOBluetoothNSStringToDeviceAddress(strDevice, &pContext->deviceAddress);
-	CFRelease(strDevice);
-
-	pContext->arrDataReceived = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
-	pContext->rfcommChannel = 0;
-	pContext->nChannel = channel;
-
-	pthread_mutex_init(&(pContext->mutexWait), NULL);
-	pthread_mutex_lock(&(pContext->mutexWait));	/* lock */
-
-	pthread_create(&(pContext->threadID), NULL, thread_main, pContext);
-
-	/* wait until main finishes its initialization */
-	pthread_mutex_lock(&(pContext->mutexWait));
-	/* unblock the mutex */
-	pthread_mutex_unlock(&(pContext->mutexWait));
-
-	if (pContext->rfcommChannel == 0)
-		return -1;
-	else
-		/* return the thread context as the file descriptor */
-		return (int)pContext;
-}
-
-int bluetooth_close(int fd, struct gn_statemachine *state)
-{
-	threadContext *pContext = (threadContext *)fd;
-        IOBluetoothDeviceRef device;
-
-	sleep(2);
-
-	if (fd != -1 && pContext->rfcommChannel > 0) {
-		/* de-register the callback */
-		IOBluetoothRFCOMMChannelRegisterIncomingDataListener(pContext->rfcommChannel, NULL, NULL);
-
-		/* close channel and device connection */
-		IOBluetoothRFCOMMChannelCloseChannel(pContext->rfcommChannel);
-		device = IOBluetoothRFCOMMChannelGetDevice(pContext->rfcommChannel);
-		IOBluetoothDeviceCloseConnection(device);
-		/* IOBluetoothObjectRelease(pContext->rfcommChannel); */
-		IOBluetoothObjectRelease(device);
-	}
-
-	return 1;
-}
-
-int bluetooth_write(int fd, const __ptr_t bytes, int size, struct gn_statemachine *state)
-{
-	threadContext *pContext = (threadContext *)fd;
-
-	if (IOBluetoothRFCOMMChannelWrite(pContext->rfcommChannel, bytes, size, TRUE) != kIOReturnSuccess)
-		return -1;
-
-	return size;
-}
-
-int bluetooth_read(int fd, __ptr_t bytes, int size, struct gn_statemachine *state)
-{
-	threadContext *pContext = (threadContext *)fd;
-	int nOffset = 0;
-	int nBytes = 0;
-        dataBlock* pDataBlock;
-
-	/* no data received so far */
-	if (CFArrayGetCount(pContext->arrDataReceived) == 0)
-		return 0;
-
-	while (CFArrayGetCount(pContext->arrDataReceived) != 0) {
-		pthread_mutex_lock(&(pContext->mutexWait));
-		pDataBlock = (dataBlock*)CFArrayGetValueAtIndex(pContext->arrDataReceived, 0);
-		pthread_mutex_unlock(&(pContext->mutexWait));
-
-		if (pDataBlock->nSize == size) {
-			/* copy data and remove block */
-			memcpy(bytes + nOffset, pDataBlock->pData, size);
-
-			pthread_mutex_lock(&(pContext->mutexWait));
-			CFArrayRemoveValueAtIndex(pContext->arrDataReceived, 0);
-			pthread_mutex_unlock(&(pContext->mutexWait));
-
-			free(pDataBlock->pData);
-			free(pDataBlock);
-
-			return nBytes + size;
-		} else if (pDataBlock->nSize > size) {
-			/* copy data and update block contents */
-			memcpy(bytes + nOffset, pDataBlock->pData, size);
-			memmove(pDataBlock->pData, pDataBlock->pData + size, pDataBlock->nSize - size);
-			pDataBlock->nSize -= size;
-			return nBytes + size;
-		} else { /* pDataBlock->nSize < size */
-			/* copy data and remove block */
-			memcpy(bytes + nOffset, pDataBlock->pData, pDataBlock->nSize);
-
-			size -= pDataBlock->nSize;
-			nOffset += pDataBlock->nSize;
-			nBytes += pDataBlock->nSize;
-
-			pthread_mutex_lock(&(pContext->mutexWait));
-			CFArrayRemoveValueAtIndex(pContext->arrDataReceived, 0);
-			pthread_mutex_unlock(&(pContext->mutexWait));
-
-			free(pDataBlock->pData);
-			free(pDataBlock);
-		}
-	}
-
-	return nBytes;
-}
-
-int bluetooth_select(int fd, struct timeval *timeout, struct gn_statemachine *state)
-{
-	threadContext *pContext = (threadContext *)fd;
-	int nRetVal = 0;
-
-	usleep(timeout->tv_usec);
-
-	pthread_mutex_lock(&(pContext->mutexWait));
-
-	if (CFArrayGetCount(pContext->arrDataReceived) > 0)
-		nRetVal = 1;
-
-	pthread_mutex_unlock(&(pContext->mutexWait));
-
-	return nRetVal;
-}
-
-#endif	/* HAVE_BLUETOOTH_MACOSX */
diff --git a/common/devices/osxbluetooth.m b/common/devices/osxbluetooth.m
new file mode 100644
index 0000000..6216cc5
--- /dev/null
+++ b/common/devices/osxbluetooth.m
@@ -0,0 +1,183 @@
+/*
+
+  G N O K I I
+
+  A Linux/Unix toolset and driver for the mobile phones.
+
+  This file is part of gnokii.
+
+  Copyright (C) 2015       Fabrizio Gennari
+
+*/
+
+#include "config.h"
+
+#ifdef HAVE_BLUETOOTH_MACOSX
+
+#include <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
+#include <IOBluetooth/objc/IOBluetoothDevice.h>
+
+#include "devices/unixbluetooth.h"
+
+static NSMutableDictionary *queues;
+static int next_fd = 1;
+
+@interface GnokiiOSXBluetooth : NSObject <IOBluetoothRFCOMMChannelDelegate> {
+@private
+    NSMutableData *queue;
+    IOBluetoothRFCOMMChannel *channel;
+}
+- (BOOL)connect:(const char*)addr chid:(BluetoothRFCOMMChannelID)channelID;
+- (int)select:(CFAbsoluteTime)timeoutDate;
+- (int)read:(__ptr_t)bytes size:(int)size;
+- (BOOL)write:(const __ptr_t *)data length:(int)length;
+@end
+
+@implementation GnokiiOSXBluetooth
+- (id)init {
+    if ((self = [super init])) {
+        queue = [[NSMutableData alloc] init];
+    }
+    return self;
+}
+
+- (BOOL)connect:(const char*)addr chid:(BluetoothRFCOMMChannelID)channelID {
+    NSString *address = [[[NSString alloc] initWithCString:addr encoding:NSASCIIStringEncoding] autorelease];
+    IOBluetoothDevice* btDevice = [[IOBluetoothDevice deviceWithAddressString:address] autorelease];
+    return btDevice != nil &&
+    [btDevice openRFCOMMChannelSync:&channel withChannelID:channelID delegate:self] == kIOReturnSuccess; // after connection it is established.. the delegates methoed are triggered.
+}
+
+-(BOOL)write:(const __ptr_t *)data length:(int)length {
+    int i;\
+    IOReturn ret = [channel writeSync:(void*)data length:(UInt16)length];
+    return ret == kIOReturnSuccess;
+}
+
+- (void)dealloc {
+    if (channel != nil) {
+        [channel closeChannel];
+        [[channel getDevice] closeConnection];
+        [channel release];
+    }
+    [queue release];
+    [super dealloc];
+}
+
+- (int)waitForData:(NSTimeInterval)timeout {
+    int ret = -1;
+    if ([queue length] <= 0 && [channel isOpen]) {
+        NSTimer *timer = nil;
+        if (timeout > 0){
+            timer =     [NSTimer scheduledTimerWithTimeInterval:timeout
+             target:self
+             selector:@selector(timerElapsed:)
+             userInfo:nil
+             repeats:NO];
+        }
+        CFRunLoopRun();
+        if (timer != nil){
+            if ([timer isValid])
+                [timer invalidate];
+            else
+                ret = 0;
+        }
+    }
+    if ([queue length] > 0)
+        return 1;
+    return ret;
+}
+
+- (int)select:(NSTimeInterval)timeout {
+    return [self waitForData:timeout];
+}
+
+-(int)read:(__ptr_t)bytes size:(int)size {
+    if (![self waitForData:0])
+        return -1;
+
+    if (size > [queue length])
+        size = [queue length];
+    memcpy (bytes, [queue mutableBytes], size);
+    NSRange range = {0, size};
+    [queue replaceBytesInRange:range withBytes:nil length:0];
+    return size;
+}
+
+- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel data:(void *)dataPointer length:(size_t)dataLength {
+    int i;
+    [queue appendBytes:dataPointer length:dataLength];
+    if ([queue length] > 0)
+        CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel {
+    if (channel == rfcommChannel)
+        CFRunLoopStop(CFRunLoopGetCurrent());
+}
+- (void) timerElapsed:(NSTimer*) timer {
+    CFRunLoopStop(CFRunLoopGetCurrent());
+}
+@end
+
+int bluetooth_open(const char* addr, uint8_t channel_num, struct gn_statemachine* state)
+{
+    if (queues == nil)
+        queues = [NSMutableDictionary dictionaryWithCapacity:1];
+    GnokiiOSXBluetooth *q = [[GnokiiOSXBluetooth alloc] init];
+    if (![q connect:addr chid:(BluetoothRFCOMMChannelID)channel_num]) { // after connection it is established.. the delegates methoed are triggered.
+        [q release];
+        return -1;
+    }
+    int ret = next_fd++;
+    [queues setObject:q forKey:@(ret)];
+    return ret;
+}
+
+int bluetooth_write(int fd, const __ptr_t bytes, int size, struct gn_statemachine *state)
+{
+    if (queues == nil)
+        return -1;
+    GnokiiOSXBluetooth *q = [queues objectForKey:@(fd)];
+    sleep(2);
+    if (q == nil
+        || ![q write:bytes length:size])
+        return -1;
+
+    return size;
+}
+
+int bluetooth_read(int fd, __ptr_t bytes, int size, struct gn_statemachine *state)
+{
+    if (queues == nil)
+        return -1;
+    GnokiiOSXBluetooth *q = [queues objectForKey:@(fd)];
+    if (q == nil)
+        return -1;
+    return [q read:bytes size:size];
+}
+
+int bluetooth_select(int fd, struct timeval *timeout, struct gn_statemachine *state)
+{
+    if (queues == nil)
+        return -1;
+    GnokiiOSXBluetooth *q = [queues objectForKey:@(fd)];
+    if (q == nil)
+        return -1;
+
+    return [q select:(NSTimeInterval)(timeout->tv_sec + timeout->tv_usec / 1000000.0)];
+}
+
+int bluetooth_close(int fd, struct gn_statemachine *state)
+{
+    if (queues == nil)
+        return -1;
+    GnokiiOSXBluetooth *q = [queues objectForKey:@(fd)];
+    if (q == nil)
+        return -1;
+    [q release];
+    [queues removeObjectForKey:@(fd)];
+    return 1;
+}
+
+#endif
diff --git a/configure.ac b/configure.ac
index ecae200..8c65cc3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -54,6 +54,7 @@ AC_PROG_CC
 AM_PROG_CC_C_O
 AC_PROG_CPP
 AC_PROG_LEX
+AC_PROG_OBJC
 # If there's no flex/lex utility and there's no generated common/gnvcal.c file, we won't be able to compile
 if test "x$LEX" = "x:" && test ! -f common/gnvcal.c; then
 	echo "You need one of the following:"
@@ -545,26 +546,20 @@ fi
 dnl ======================== Checks for MacOSX Bluetooth support
 if test `uname -s` = "Darwin" ; then
 	if test "$enable_bluetooth" = "yes" -a "$USE_BLUETOOTH" = "no"; then
-		AC_MSG_NOTICE([checking for the MacOS X Bluetooth support])
-		AC_CHECK_HEADERS(CoreFoundation/CoreFoundation.h)
-		AC_CHECK_HEADERS(IOBluetooth/Bluetooth.h)
-		AC_CHECK_HEADERS([IOBluetooth/IOBluetoothUserLib.h], [], [],
-			[[#ifdef HAVE_COREFOUNDATION_COREFOUNDATION_H
-			  #  include <CoreFoundation/CoreFoundation.h>
-			  #endif
-			  #ifdef HAVE_IOBLUETOOTH_BLUETOOTH_H
-			  #  include <IOBluetooth/Bluetooth.h>
-			  #endif
-			]])
-		AC_TRY_COMPILE([#include <CoreFoundation/CoreFoundation.h>
-				#include <IOBluetooth/Bluetooth.h>
-				#include <IOBluetooth/IOBluetoothUserLib.h>],
-
-				[CFStringRef strDevice;
-				strDevice = CFStringCreateWithCString(NULL, NULL, NULL);
-				IOBluetoothNSStringToDeviceAddress(NULL, NULL);],
+		AC_LANG_PUSH([Objective C])
+		AC_CHECK_HEADERS(IOBluetooth/objc/IOBluetoothRFCOMMChannel.h)
+		AC_CHECK_HEADERS(IOBluetooth/objc/IOBluetoothDevice.h)
+		AC_MSG_CHECKING([for MacOS X Bluetooth support])
+		AC_TRY_COMPILE([#include <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
+		#include <IOBluetooth/objc/IOBluetoothDevice.h>],
+				[
+				IOBluetoothDevice* btDevice = [[IOBluetoothDevice deviceWithAddressString:@"00:00:00:00:00:00"] autorelease];
+			    ],
+			AC_MSG_RESULT(yes)
 			ac_cv_have_iobluetooth=yes,
+			AC_MSG_RESULT(no)
 			ac_cv_have_iobluetooth=no)
+		AC_LANG_POP([Objective C])
 
 		if test $ac_cv_have_iobluetooth = yes; then
 			AC_DEFINE(HAVE_BLUETOOTH, 1, [Whether Bluetooth is available])
diff --git a/include/devices/osxbluetooth.h b/include/devices/osxbluetooth.h
deleted file mode 100644
index 8de6bf1..0000000
--- a/include/devices/osxbluetooth.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-
-  G N O K I I
-
-  A Linux/Unix toolset and driver for the mobile phones.
-
-  This file is part of gnokii.
-
-  Copyright (C) 1999, 2000 Hugh Blemings & Pavel Jan�k ml.
-  Copyright (C) 2003       Siegfried Schloissnig
-
-*/
-
-#ifndef _gnokii_osx_bluetooth_h
-#define _gnokii_osx_bluetooth_h
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-#include <sys/time.h>
-#include <sys/socket.h>
-
-#include <CoreFoundation/CoreFoundation.h>
-#include <IOBluetooth/Bluetooth.h>
-#include <IOBluetooth/IOBluetoothUserLib.h>
-#include <pthread.h>
-
-typedef struct {
-	IOBluetoothRFCOMMChannelRef rfcommChannel;
-	IOReturn ioReturnValue;
-	pthread_t threadID;
-
-	BluetoothDeviceAddress deviceAddress;
-	uint8_t nChannel;
-
-	pthread_mutex_t mutexWait;
-
-	CFMutableArrayRef arrDataReceived;
-} threadContext;
-
-typedef struct {
-	void *pData;
-	unsigned int nSize;
-} dataBlock;
-
-#endif /* _gnokii_osx_bluetooth_h */
-- 
2.5.0

