diff options
author | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-09-10 12:54:18 +0200 |
---|---|---|
committer | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-09-26 15:41:09 +0200 |
commit | d1d77c8210ecf7a89a81dad69d21b91e06bf129e (patch) | |
tree | 94d7b91c64478720eee9c9afd82acfc496c383e1 /src/bluetooth/osx/osxbtl2capchannel.mm | |
parent | c1aabdba2e839ba525a4918eebfbf6fbbe96d34d (diff) |
Port QBluetoothSocket to OS X.
Implement QBluetoothSocket using IOBluetooth framework on OS X
(will implement Qt's API as close as possible with a given Apple's API).
Update 0: add (empty for now) delegate classes (L2CAP/RFCOMM).
Update 1: add service discovery (called doDeviceDiscovery though).
Update 2: implement the public class' logic (QBluetoothSocket, connectToService).
Update 3: more public logic implemented (since it's easy :) )
Update 4: L2CAP delegate - initial logic.
Update 5: connectToService - L2CAP "socket".
Update 6: fix pivate header files.
Update 7: fix dependency after the previous patch was merged.
Update 8: writeData - initial version for L2CAP.
Update 9: since RFCOM/L2CAP delegates have the same interface,
no need in duplicating the same class - add a "generic"
ChannelDelegate instead.
Update 10: more RFCOMM logic.
Update 11: function to build a service description from
QBluetoothServiceInfo (to be registered on SDP server).
Update 12: QBluetoothSocket::close/abort.
Update 13: Create a dictioinary out of QBluetoothServiceInfo to register a service.
Update 14: Add service registration.
Update 15: Convert attributes (sequences and 'scalars') from QBluetoothServiceInfor
into NSDictionary.
Update 16: Update QBluetoothServiceInfo with a real PSM/ChannelID
after a service was registered.
Update 17: Move a private class (bluetooth socket) into the separate private
header file (to make it visible for bluetooth_server_osx)
Update 18: Add an interface to create a bluetooth socket (private class)
from a channel, reported by a notification (found by a listening
server).
Update 19: Fix an invalid assert - any state (Inactive/ServiceDiscovery/DeviceDiscovery)
is possible, not only Inactive.
Implement the missing 'readData' and 'writeData' for RFCOMM.
Set SDP query as non-active after query finished.
Temporary (!) workaround - can not invokeMethod on a private socket (d_ptr).
Update 20: When creating a socket wrapper from an incoming notification/channel, set:
socket type + channel's delegate.
Change-Id: Idd6d5478597206ed759f49e282baed948d105ddf
Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
Diffstat (limited to 'src/bluetooth/osx/osxbtl2capchannel.mm')
-rw-r--r-- | src/bluetooth/osx/osxbtl2capchannel.mm | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/src/bluetooth/osx/osxbtl2capchannel.mm b/src/bluetooth/osx/osxbtl2capchannel.mm new file mode 100644 index 00000000..44cc2460 --- /dev/null +++ b/src/bluetooth/osx/osxbtl2capchannel.mm @@ -0,0 +1,263 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "osxbtchanneldelegate_p.h" +#include "osxbtl2capchannel_p.h" +#include "qbluetoothaddress.h" +#include "osxbtutility_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qdebug.h> + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +@implementation QT_MANGLE_NAMESPACE(OSXBTL2CAPChannel) + +- (id)initWithDelegate:(OSXBluetooth::ChannelDelegate *)aDelegate +{ + Q_ASSERT_X(aDelegate, "-initWithDelegate:", "invalid delegate (null)"); + + if (self = [super init]) { + delegate = aDelegate; + device = nil; + channel = nil; + connected = false; + } + + return self; +} + +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::ChannelDelegate) *)aDelegate + channel:(IOBluetoothL2CAPChannel *)aChannel +{ + // This type of channel does not require connect, it's created with + // already open channel. + Q_ASSERT_X(aDelegate, "-initWithDelegate:channel:", "invalid delegate (null)"); + Q_ASSERT_X(channel, "-initWithDelegate:channel:", "invalid channel (nil)"); + + if (self = [super init]) { + delegate = aDelegate; + channel = [aChannel retain]; + [channel setDelegate:self]; + device = [channel.device retain]; + connected = true; + } + + return self; +} + +- (void)dealloc +{ + // TODO: test if this implementation works at all! + if (channel) { + [channel setDelegate:nil]; + // From Apple's docs: + // "This method may only be called by the client that opened the channel + // in the first place. In the future asynchronous and synchronous versions + // will be provided that let the client know when the close process has been finished." + [channel closeChannel]; + [channel release]; + } + + [device release]; + + [super dealloc]; +} + +- (IOReturn)connectAsyncToDevice:(const QBluetoothAddress &)address + withPSM:(BluetoothL2CAPChannelID)psm +{ + if (address.isNull()) { + qCCritical(QT_BT_OSX) << "-connectAsyncToDevice:withPSM:, " + "invalid peer address"; + return kIOReturnNoDevice; + } + + // Can never be called twice. + if (connected || device || channel) { + qCCritical(QT_BT_OSX) << "-connectAsyncToDevice:withPSM:, " + "connection is already active"; + return kIOReturnStillOpen; + } + + QT_BT_MAC_AUTORELEASEPOOL; + + const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(address); + device = [IOBluetoothDevice deviceWithAddress:&iobtAddress]; + if (!device) { + qCCritical(QT_BT_OSX) << "-connectAsyncToDevice:withPSM:, " + "failed to create a device"; + return kIOReturnNoDevice; + } + + const IOReturn status = [device openL2CAPChannelAsync:&channel withPSM:psm delegate:self]; + if (status != kIOReturnSuccess) { + qCCritical(QT_BT_OSX) << "-connectAsyncToDevice:withPSM:, " + "failed to open L2CAP channel"; + // device is still autoreleased. + device = nil; + return status; + } + + [channel retain];// What if we're closed already? + [device retain]; + + return kIOReturnSuccess; +} + +// IOBluetoothL2CAPChannelDelegate: + +- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel + data:(void *)dataPointer length:(size_t)dataLength +{ + Q_UNUSED(l2capChannel) + + Q_ASSERT_X(delegate, "-l2capChannelData:data:length", + "invalid delegate (null)"); + + if (dataPointer && dataLength) + delegate->readChannelData(dataPointer, dataLength); +} + +- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*) + l2capChannel status:(IOReturn)error +{ + Q_UNUSED(l2capChannel) + + Q_ASSERT_X(delegate, "-l2capChannelOpenComplete:status:", + "invalid delegate (null)"); + + if (error != kIOReturnSuccess) { + delegate->setChannelError(error); + } else { + connected = true; + delegate->channelOpenComplete(); + } +} + +- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel +{ + Q_UNUSED(l2capChannel) + + connected = false; +} + +- (void)l2capChannelReconfigured:(IOBluetoothL2CAPChannel*)l2capChannel +{ + Q_UNUSED(l2capChannel) +} + +- (void)l2capChannelWriteComplete:(IOBluetoothL2CAPChannel*)l2capChannel + refcon:(void*)refcon status:(IOReturn)error +{ + Q_UNUSED(l2capChannel) + Q_UNUSED(refcon) + + Q_ASSERT_X(delegate, "-l2capChannelWriteComplete:refcon:status", + "invalid delegate (null)"); + + if (error != kIOReturnSuccess) + delegate->setChannelError(error); + else + delegate->writeComplete(); +} + +- (void)l2capChannelQueueSpaceAvailable:(IOBluetoothL2CAPChannel*)l2capChannel +{ + Q_UNUSED(l2capChannel) +} + +// Aux. methods. +- (BluetoothL2CAPPSM)getPSM +{ + if (channel) + return channel.PSM; + + return 0; +} + +- (BluetoothDeviceAddress)peerAddress +{ + const BluetoothDeviceAddress *const addr = device ? [device getAddress] + : Q_NULLPTR; + if (addr) + return *addr; + + return BluetoothDeviceAddress(); +} + +- (NSString *)peerName +{ + if (device) + return device.name; + + return nil; +} + +- (bool)isConnected +{ + return connected; +} + +- (IOReturn) writeSync:(void*)data length:(UInt16)length +{ + Q_ASSERT_X(data, "-writeSync:length:", "invalid data (null)"); + Q_ASSERT_X(length, "-writeSync:length:", "invalid data size"); + Q_ASSERT_X(connected && channel, "-writeSync:", + "invalid L2CAP channel"); + + return [channel writeSync:data length:length]; +} + +- (IOReturn) writeAsync:(void*)data length:(UInt16)length +{ + Q_ASSERT_X(data, "-writeAsync:length:", "invalid data (null)"); + Q_ASSERT_X(length, "-writeAync:length:", "invalid data size"); + Q_ASSERT_X(connected && channel, "-writeAsync:length:", + "invalid L2CAP channel"); + + return [channel writeAsync:data length:length refcon:Q_NULLPTR]; +} + + +@end |