summaryrefslogtreecommitdiffstats
path: root/src/bluetooth
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-10-03 17:05:00 +0200
committerAlex Blasche <alexander.blasche@digia.com>2014-10-16 16:59:15 +0200
commit038e0e38f96246848a6b7976690376cedb48140a (patch)
tree3f05f7bb2b506bffaea4b7214405ab08b27d53d4 /src/bluetooth
parente417b7036e35744daa1978cd236798e3ee1693c0 (diff)
Port QBluetoothTransferReply to OS X.
Implement file transfer using IOBluetoothOBEXSession. - Add OBEX session class and session delegate. - Implement OBEX connect operation. - Aux. function to convert OBEX data into Qt's data types. - Start implementing OBEX put. - Extract a connection ID from response headers (if any?). - OBEX Put (without response/event handler yet). - OBEX Put completed - send the remaining chunks of data (if any). Change the unicode string encoding - byte order. - OBEX change error handling - there can be real errors (OBEX event type == error_type) but also response code can be not something good - handle them both. - Emit all signals and make Qt' btfiletransfer really working now. - Protect a service discovery against the early stop (can be a result of emit serviceDiscovered). - After a file transfer finished (either success or a failure) - disconnect (if connected) OBEX session - to make it possible to send more files. - Implement OBEXPutError + isFinished/isRunning and signals on error. - Add proper 'abort' and 'start' slots. Also emit finished on errors. - If we do not have a file, but just an input stream, generate a _really_ random and unique name, not 'XXXXX' template. Change-Id: Ib6fb35d8e0eb07d71ccd2d7b565bba226bef119c Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
Diffstat (limited to 'src/bluetooth')
-rw-r--r--src/bluetooth/bluetooth.pro6
-rw-r--r--src/bluetooth/osx/osxbt.pri6
-rw-r--r--src/bluetooth/osx/osxbtobexsession.mm840
-rw-r--r--src/bluetooth/osx/osxbtobexsession_p.h147
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h7
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_osx.mm4
-rw-r--r--src/bluetooth/qbluetoothtransfermanager.cpp7
-rw-r--r--src/bluetooth/qbluetoothtransferreply_osx.mm473
-rw-r--r--src/bluetooth/qbluetoothtransferreply_osx_p.h84
9 files changed, 1569 insertions, 5 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index 16ae481f..e71adeae 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -153,13 +153,15 @@ config_bluez:qtHaveModule(dbus) {
qbluetoothserviceinfo_osx.mm \
qbluetoothservicediscoveryagent_osx.mm \
qbluetoothsocket_osx.mm \
- qbluetoothserver_osx.mm
+ qbluetoothserver_osx.mm \
+ qbluetoothtransferreply_osx.mm
SOURCES += \
qlowenergycontroller_p.cpp
PRIVATE_HEADERS += qbluetoothsocket_osx_p.h \
- qbluetoothserver_osx_p.h
+ qbluetoothserver_osx_p.h \
+ qbluetoothtransferreply_osx_p.h
SOURCES -= qbluetoothdevicediscoveryagent.cpp
SOURCES -= qbluetoothserviceinfo.cpp
diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri
index c1382484..cdcf0659 100644
--- a/src/bluetooth/osx/osxbt.pri
+++ b/src/bluetooth/osx/osxbt.pri
@@ -7,7 +7,8 @@ PRIVATE_HEADERS += osx/osxbtutility_p.h \
osx/osxbtl2capchannel_p.h \
osx/osxbtchanneldelegate_p.h \
osx/osxbtservicerecord_p.h \
- osx/osxbtsocketlistener_p.h
+ osx/osxbtsocketlistener_p.h \
+ osx/osxbtobexsession_p.h
OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtdevicepair.mm \
@@ -18,4 +19,5 @@ OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtl2capchannel.mm \
osx/osxbtchanneldelegate.mm \
osx/osxbtservicerecord.mm \
- osx/osxbtsocketlistener.mm
+ osx/osxbtsocketlistener.mm \
+ osx/osxbtobexsession.mm
diff --git a/src/bluetooth/osx/osxbtobexsession.mm b/src/bluetooth/osx/osxbtobexsession.mm
new file mode 100644
index 00000000..8a5feb32
--- /dev/null
+++ b/src/bluetooth/osx/osxbtobexsession.mm
@@ -0,0 +1,840 @@
+/****************************************************************************
+**
+** 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 "osxbtobexsession_p.h"
+#include "qbluetoothaddress.h"
+#include "osxbtutility_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+
+// Import, since it's Obj-C headers:
+#import <IOBluetooth/objc/IOBluetoothOBEXSession.h>
+#import <IOBluetooth/objc/IOBluetoothDevice.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+namespace OSXBluetooth
+{
+
+OBEXSessionDelegate::~OBEXSessionDelegate()
+{
+}
+
+namespace {
+
+struct OBEXHeader
+{
+ OBEXHeader() : headerID(0)
+ {
+ }
+
+ quint8 headerID;
+ QVariant value;
+};
+
+enum {
+ // Bits 7 and 8 == header's format.
+ OBEXHeaderFormatMask = 0xc0,
+ //
+ OBEXHeaderFormatUnicode = 0, // 87
+ OBEXHeaderFormatByteSequence = 0x40, // 0100 0000
+ OBEXHeaderFormat1Byte = 0x80, // 1000 0000
+ OBEXHeaderFormat4Byte = 0xc0, // 1100 0000
+
+};
+
+quint32 extract_uint32(const uint8_t *bytes)
+{
+ // Four byte value, high byte first.
+ Q_ASSERT_X(bytes, "extract_uint32", "invalid input data (null)");
+
+ uint32_t value = uint32_t();
+ std::copy(bytes, bytes + sizeof value, reinterpret_cast<uint8_t *>(&value));
+
+ return NSSwapBigIntToHost(value);
+}
+
+quint16 extract_uint16(const uint8_t *bytes)
+{
+ // Two byte value, high byte first.
+ Q_ASSERT_X(bytes, "extract_uint16", "invalid input data (null)");
+
+ uint16_t value = uint16_t();
+ std::copy(bytes, bytes + sizeof value, reinterpret_cast<uint8_t *>(&value));
+
+ return NSSwapBigShortToHost(value);
+}
+
+QString extract_qstring(const uint8_t *bytes, quint16 stringLength)
+{
+ if (bytes && stringLength) {
+ NSString * const nsString = [[NSString alloc] initWithBytes:bytes
+ length:stringLength
+ encoding:NSUnicodeStringEncoding];
+ if (nsString)
+ return QString::fromNSString(nsString);
+ }
+
+ // Empty string is an error, "valid" empty strings are
+ // handled separately.
+ return QString();
+}
+
+QList<OBEXHeader> qt_bluetooth_headers(const uint8_t *data, std::size_t length)
+{
+ // Convert a data from IOBluetooth into something, Qt understands.
+ // Possible formats (bits 7 and 8):
+ // 1. 00 Two bytes of length folowed by a null-terminated
+ // Unicode text string (length is unsigned integer;
+ // it covers the header ID and the whole of the header
+ // value, including the length bytes and the two bytes
+ // of null terminator.
+ // 2. 01 Two bytes of length followed by a byte sequence (length
+ // is an unsigned integer sent high byte first; it covers
+ // the header ID and the whole of the header value).
+ // 3. 10 A single byte value.
+ // 4. 11 A four byte value, sent high byte first.
+
+ Q_ASSERT_X(data, "qt_bluetooth_headers", "invalid data (null)");
+ Q_ASSERT_X(length >= 2, "qt_bluetooth_headers", "invalid data length");
+
+ Q_UNUSED(data)
+ Q_UNUSED(length)
+
+ QList<OBEXHeader> empty;
+ QList<OBEXHeader> qtHeaders;
+
+ for (std::size_t i = 0; i < length;) {
+ std::size_t headerLength = 0;
+ OBEXHeader header;
+ header.headerID = data[i];
+
+ switch (data[i] & OBEXHeaderFormatMask) {
+ case OBEXHeaderFormatUnicode:
+ {
+ if (i + 3 > length)
+ return empty;
+ headerLength = extract_uint16(data + i + 1);
+ // Invalid length or input data:
+ if (headerLength < 3 || i + headerLength > length)
+ return empty;
+ if (headerLength == 3 || headerLength == 5) { // Can 5 ever happen?
+ header.value.fromValue<QString>(QString());
+ } else if (headerLength > 5) {// TODO: We do not check now, that the string actually valid.
+ const QString value(extract_qstring(data + i + 3, headerLength - 5));
+ if (!value.length()) // Some error?
+ return empty;
+ header.value.setValue<QString>(value);
+ } else // Still something weird.
+ return empty;
+ break;
+ }
+ case OBEXHeaderFormatByteSequence:
+ {
+ if (i + 3 > length)
+ return empty;
+ headerLength = extract_uint16(data + i + 1);
+ // Something is wrong:
+ if (headerLength < 3 || i + headerLength > length)
+ return empty;
+ QVector<unsigned char> value;
+ if (headerLength > 3) {
+ value.resize(headerLength - 3);
+ std::copy(data, data + headerLength, value.begin());
+ }
+ header.value.setValue<QVector<unsigned char> >(value);
+ break;
+ }
+ case OBEXHeaderFormat1Byte:
+ {
+ // 1 byte integer + 1 byte headerID == 2
+ if (i + 2 > length)
+ return empty;
+ headerLength = 2;
+ header.value.setValue<quint8>(data[i + 1]);
+ break;
+ }
+ case OBEXHeaderFormat4Byte:
+ {
+ // 4 byte integer + 1 byte headerID == 5
+ if (i + 5 > length)
+ return empty;
+ headerLength = 5;
+ header.value.setValue<quint32>(extract_uint32(data + i + 1));
+ break;
+ }
+ default:
+ qCWarning(QT_BT_OSX) << "qt_bluetooth_headers(), invalid header format";
+ return empty;
+ }
+
+ i += headerLength;
+ qtHeaders.push_back(header);
+ }
+
+ return qtHeaders;
+}
+
+bool append_uint16(ObjCStrongReference<NSMutableData> headers, uint16_t value)
+{
+ if (!headers)
+ return false;
+
+ const NSUInteger length = [headers length];
+ const uint16_t valueSwapped = NSSwapHostShortToBig(value);
+ [headers appendBytes:&valueSwapped length:sizeof valueSwapped];
+
+ return [headers length] - length == 2;
+}
+
+
+bool append_four_byte_header(ObjCStrongReference<NSMutableData> headers, uint8_t headerID,
+ uint32_t headerValue)
+{
+ if (!headers)
+ return false;
+
+ const NSUInteger length = [headers length];
+ // Header ID (1 byte)
+ [headers appendBytes:&headerID length:1];
+ // Header value (4 bytes)
+ const uint32_t valueSwapped(NSSwapHostIntToBig(headerValue));
+ [headers appendBytes:&valueSwapped length:sizeof valueSwapped];
+
+ return [headers length] - length == 5;
+}
+
+bool append_unicode_header(ObjCStrongReference<NSMutableData> headers, uint8_t headerID,
+ const QString &string)
+{
+ // Two bytes of length followed by a null-terminated
+ // Unicode text string. Length is unsigned integer,
+ // it covers the header ID and the whole of the header
+ // value, including the length bytes and the two bytes
+ // of null terminator.
+ // All Obj-C objects are autoreleased.
+
+ if (!headers)
+ return false;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const NSUInteger initialLength = [headers length];
+ [headers appendBytes:&headerID length:1];
+
+ if (!string.length()) {
+ // Empty string. The length is 3
+ // (header ID + length value itself).
+ return append_uint16(headers, 3);
+ }
+
+ NSString *const nsString = string.toNSString();
+ if (!nsString)
+ return false;
+
+ // TODO: check if the encodings is right. It was NSUnicodeStringEncoding but
+ // byte order was wrong. Also, I do not need BOM check anymore?
+ NSData *const data = [nsString dataUsingEncoding:NSUTF16BigEndianStringEncoding];
+ if (!data)
+ return false;
+
+ // This data can include byte-order marker (BOM) and does not include
+ // a null terminator. Anyway, the length must be >= 2.
+ NSUInteger length = [data length];
+ if (length < 2)
+ return false;
+
+ const uint8_t *dataPtr = static_cast<const uint8_t *>([data bytes]);
+ if ((dataPtr[0] == 0xff && dataPtr[1] == 0xfe)
+ || (dataPtr[0] == 0xfe && dataPtr[1] == 0xff)) {
+ if (length == 2) //Something weird?
+ return false;
+ // Skip a BOM.
+ dataPtr += 2;
+ length -= 2;
+ }
+
+ // headerID + length == 3, string's length + 2
+ // bytes for a null terminator.
+ if (!append_uint16(headers, length + 3 + 2))
+ return false;
+
+ [headers appendBytes:dataPtr length:length];
+ const uint8_t nullTerminator[2] = {};
+ [headers appendBytes:nullTerminator length:2];
+
+ return [headers length] - initialLength == length + 3 + 2;
+}
+
+ObjCStrongReference<NSMutableData> next_data_chunk(QIODevice &inputStream, IOBluetoothOBEXSession *session,
+ NSUInteger headersLength, bool &isLast)
+{
+ // Work only for OBEX put (we request a specific payload length).
+ Q_ASSERT_X(session, "next_data_chunk", "invalid OBEX session (nil)");
+
+ const OBEXMaxPacketLength packetSize = [session getAvailableCommandPayloadLength:kOBEXOpCodePut];
+ if (!packetSize || headersLength >= packetSize)
+ return ObjCStrongReference<NSMutableData>();
+
+ const OBEXMaxPacketLength maxBodySize = packetSize - headersLength;
+
+ QVector<char> block(maxBodySize);
+ const int realSize = inputStream.read(block.data(), block.size());
+ if (realSize <= 0) {
+ // Well, either the last or an error.
+ isLast = true;
+ return ObjCStrongReference<NSMutableData>();
+ }
+
+ ObjCStrongReference<NSMutableData> chunk([NSMutableData dataWithBytes:block.data()
+ length:realSize], true);
+ if (chunk && [chunk length]) {
+ // If it actually was the last chunk
+ // of a length == maxBodySize, we'll
+ // send one more packet (empty though)?
+ isLast = [chunk length] < maxBodySize;
+ }
+
+ return chunk;
+}
+
+bool check_connect_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
+{
+ Q_ASSERT_X(e, "check_connect_event", "invalid event (null)");
+
+ // This function tries to extract either an error code or a
+ // server response code. "Good" event has type connect command respond
+ // and reponse code 0XA0. Everything else is a "bad" event and
+ // means connect failed.
+
+ // If it's an error event - return the error.
+ // If it's connect response - extract the response code.
+ // If it's something else (is it possible?) - set general error.
+
+ if (e->type == kOBEXSessionEventTypeError) {
+ error = e->u.errorData.error;
+ return false;
+ } if (e->type == kOBEXSessionEventTypeConnectCommandResponseReceived) {
+ // We can read response code only for such an event.
+ response = e->u.connectCommandResponseData.serverResponseOpCode;
+ return response == kOBEXResponseCodeSuccessWithFinalBit;
+ } else {
+ qCWarning(QT_BT_OSX) << "check_connect_event, unexpected event type";
+ error = kOBEXGeneralError;
+ return false;
+ }
+}
+
+bool check_put_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
+{
+ Q_ASSERT_X(e, "check_put_event", "invalid event (null)");
+
+ // See the comments above.
+
+ if (e->type == kOBEXSessionEventTypeError) {
+ error = e->u.errorData.error;
+ return false;
+ } else if (e->type == kOBEXSessionEventTypePutCommandResponseReceived) {
+ response = e->u.putCommandResponseData.serverResponseOpCode;
+ return response == kOBEXResponseCodeContinueWithFinalBit ||
+ response == kOBEXResponseCodeSuccessWithFinalBit;
+ } else {
+ qCWarning(QT_BT_OSX) << "check_put_event, unexpected event type";
+ error = kOBEXGeneralError;
+ return false;
+ }
+}
+
+bool check_abort_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
+{
+ Q_ASSERT_X(e, "check_abort_event", "invalid event (null)");
+
+ if (e->type == kOBEXSessionEventTypeError) {
+ error = e->u.errorData.error;
+ return false;
+ } else if (e->type == kOBEXSessionEventTypeAbortCommandResponseReceived) {
+ response = e->u.abortCommandResponseData.serverResponseOpCode;
+ return response == kOBEXResponseCodeSuccessWithFinalBit;
+ } else {
+ qCWarning(QT_BT_OSX) << "check_abort_event, unexpected event type";
+ return false;
+ }
+}
+
+} // Unnamed namespace.
+} // OSXBluetooth.
+
+QT_END_NAMESPACE
+
+#ifdef QT_NAMESPACE
+
+using namespace QT_NAMESPACE;
+
+#endif
+
+@interface QT_MANGLE_NAMESPACE(OSXBTOBEXSession) (PrivateAPI)
+
+// OBEXDisconnect returns void - it's considered to be always
+// successful. These methods are "private API" - no need to expose them,
+// for internal use only.
+- (void)OBEXDisconnect;
+- (void)OBEXDisconnectHandler:(const OBEXSessionEvent*)event;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(OSXBTOBEXSession)
+
++ (OBEXMaxPacketLength) maxPacketLength
+{
+ // Some arbitrary number, we'll adjust it as soon as
+ // we connected, asking a session about packet size for
+ // a particular command.
+ return 0x1000;
+}
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::OBEXSessionDelegate) *)aDelegate
+ remoteDevice:(const QBluetoothAddress &)deviceAddress channelID:(quint16)port
+{
+ Q_ASSERT_X(aDelegate, "-initWithDelegate:remoteDevice:channelID:",
+ "invalid delegate (null)");
+ Q_ASSERT_X(!deviceAddress.isNull(), "-initWithDelegate:remoteDevice:channelID:",
+ "invalid remote device address");
+ Q_ASSERT_X(port, "-initWithDelegate:remoteDevice:channelID:",
+ "invalid port (0)");
+
+ if (self = [super init]) {
+ connected = false;
+ currentRequest = OSXBluetooth::OBEXNoop;
+ connectionID = 0;
+ connectionIDFound = false;
+
+ const BluetoothDeviceAddress addr(OSXBluetooth::iobluetooth_address(deviceAddress));
+ device = [[IOBluetoothDevice deviceWithAddress:&addr] retain];
+ if (!device) {
+ qCWarning(QT_BT_OSX) << "-initWithDelegate:remoteDevice:channelID:, "
+ "failed to create an IOBluetoothDevice";
+ return self;
+ }
+
+ session = [[IOBluetoothOBEXSession alloc] initWithDevice:device channelID:port];
+ if (!session) {
+ qCWarning(QT_BT_OSX) << "-initWithDelegate:remoteDevice:channelID:, "
+ "failed to create an OBEX session";
+ return self;
+ }
+
+ delegate = aDelegate;
+ channelID = port;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [device release];
+ [session release];
+
+ [headersData release];
+ [bodyData release];
+
+ [super dealloc];
+}
+
+- (OBEXError)OBEXConnect
+{
+ if (!session) {
+ qCWarning(QT_BT_OSX) << "-OBEXConnect, invalid session (nil)";
+ return kOBEXGeneralError;
+ }
+
+ // That's a "single-shot" operation:
+ Q_ASSERT_X(currentRequest == OSXBluetooth::OBEXNoop, "-connect",
+ "can not connect in this state (another request is active)");
+
+ connected = false;
+ connectionIDFound = false;
+ connectionID = 0;
+ currentRequest = OSXBluetooth::OBEXConnect;
+
+ const OBEXError status = [session OBEXConnect:kOBEXConnectFlagNone
+ maxPacketLength:[QT_MANGLE_NAMESPACE(OSXBTOBEXSession) maxPacketLength]
+ optionalHeaders:Q_NULLPTR
+ optionalHeadersLength:0
+ eventSelector:@selector(OBEXConnectHandler:)
+ selectorTarget:self
+ refCon:Q_NULLPTR];
+
+ if (status != kOBEXSuccess) {
+ currentRequest = OSXBluetooth::OBEXNoop;
+ // Already connected is still ok for us?
+ connected = status == kOBEXSessionAlreadyConnectedError;
+ }
+
+ return status;
+}
+
+- (void)OBEXConnectHandler:(const OBEXSessionEvent*)event
+{
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(session, "-OBEXConnectHandler:", "invalid session (nil)");
+
+ if (pendingAbort) {
+ currentRequest = OBEXNoop;
+ [self OBEXAbort];
+ return;
+ }
+
+ if (currentRequest != OBEXConnect) {
+ qCWarning(QT_BT_OSX) << "-OBEXConnectHandler:, called while there is no"
+ "active connect request";
+ return;
+ }
+
+ currentRequest = OBEXNoop;
+
+ OBEXError errorCode = kOBEXSuccess;
+ OBEXOpCode responseCode = kOBEXResponseCodeSuccessWithFinalBit;
+
+ if (!check_connect_event(event, errorCode, responseCode)) {
+ // OBEX connect failed.
+ if (delegate)
+ delegate->OBEXConnectError(errorCode, responseCode);
+ return;
+ }
+
+ const OBEXConnectCommandResponseData *const response = &event->u.connectCommandResponseData;
+ if (response->headerDataPtr && response->headerDataLength >= 2) {
+ // 2 == 1 byte headerID + at least 1 byte headerValue ...
+ QList<OBEXHeader> headers(qt_bluetooth_headers(static_cast<const uint8_t *>(response->headerDataPtr),
+ response->headerDataLength));
+ // ConnectionID is used when multiplexing OBEX connections
+ // to identify which particular connection this object is
+ // being sent on. When used, this _must_ be the first
+ // header sent.
+
+ foreach (const OBEXHeader &header, headers) {
+ if (header.headerID == kOBEXHeaderIDConnectionID) {
+ connectionID = header.value.value<quint32>();
+ connectionIDFound = true;
+ break;
+ }
+ }
+ }
+
+ connected = true;
+
+ if (delegate)
+ delegate->OBEXConnectSuccess();
+}
+
+- (OBEXError)OBEXAbort
+{
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(session, "-OBEXAbort", "invalid OBEX session (nil)");
+
+ if (currentRequest == OBEXNoop) {
+ pendingAbort = false;
+
+ if (![self isConnected])
+ return kOBEXSessionNotConnectedError;
+
+ currentRequest = OBEXAbort;
+ const OBEXError status = [session OBEXAbort:Q_NULLPTR
+ optionalHeadersLength:0
+ eventSelector:@selector(OBEXAbortHandler:)
+ selectorTarget:self
+ refCon:Q_NULLPTR];
+ if (status != kOBEXSuccess)
+ currentRequest = OBEXNoop;
+
+ return status;
+ } else {
+ // We're in the middle of some request, wait
+ // for any handler to be called first.
+ pendingAbort = true;
+ return kOBEXSuccess;
+ }
+}
+
+- (void)OBEXAbortHandler:(const OBEXSessionEvent*)event
+{
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(session, "-OBEXAbortHandler:", "invalid OBEX session (nil)");
+
+ if (currentRequest != OBEXAbort) {
+ qCWarning(QT_BT_OSX) << "-OBEXAbortHandler:, called while there "
+ "is no ABORT request";
+ return;
+ }
+
+ pendingAbort = false;
+ currentRequest = OBEXNoop;
+
+ if (delegate) {
+ OBEXError error = kOBEXSuccess;
+ OBEXOpCode response = kOBEXResponseCodeSuccessWithFinalBit;
+ if (check_abort_event(event, error, response))
+ delegate->OBEXAbortSuccess();
+ }
+}
+
+- (OBEXError)OBEXPutFile:(QT_PREPEND_NAMESPACE(QIODevice) *)input withName:(const QString &)name
+{
+ using namespace OSXBluetooth;
+
+ if (!session || ![self isConnected])
+ return kOBEXSessionNotConnectedError;
+
+ Q_ASSERT_X(currentRequest == OBEXNoop, "-OBEXPutFile:withName:",
+ "the current session has an active request already");
+ Q_ASSERT_X(input, "-OBEXPutFile:withName:", "invalid input stream (null)");
+ Q_ASSERT_X(input->isReadable(), "-OBEXPutFile:withName:",
+ "invalid input stream (not readable)");
+
+ // We send a put request with a couple of headers (size/file name/may be connection ID) +
+ // a payload.
+ const qint64 fileSize = input->size();
+ if (fileSize <= 0 || fileSize >= std::numeric_limits<uint32_t>::max()) {
+ qCWarning(QT_BT_OSX) << "-OBEXPutFile:withName:, invalid input file size";
+ return kOBEXBadArgumentError;
+ }
+
+ ObjCStrongReference<NSMutableData> headers([[NSMutableData alloc] init], false);
+ if (!headers) {
+ qCWarning(QT_BT_OSX) << "-OBEXPutFile:withName:, failed to allocate headers";
+ return kOBEXNoResourcesError;
+ }
+
+ // Now we append headers with: Connection ID (if any),
+ // file name, file size, the first (and probably the only) chunk of data
+ // from the input stream and send a put request.
+
+ if (connectionIDFound) {
+ if (!append_four_byte_header(headers, kOBEXHeaderIDConnectionID, connectionID)) {
+ qCWarning(QT_BT_OSX) << "-OBEXPutFile:withName:, "
+ "failed to append connection ID header";
+ return kOBEXNoResourcesError;
+ }
+ }
+
+ if (name.length()) {
+ if (!append_unicode_header(headers, kOBEXHeaderIDName, name)) {
+ qCWarning(QT_BT_OSX) << "-OBEXPutFile:withName:, "
+ "failed to append a unicode string";
+ return kOBEXNoResourcesError;
+ }
+ }
+
+ if (fileSize && !input->isSequential())
+ append_four_byte_header(headers, kOBEXHeaderIDLength, uint32_t(fileSize));
+
+ bool lastChunk = false;
+ ObjCStrongReference<NSMutableData> chunk(next_data_chunk(*input, session, [headers length], lastChunk));
+ if (!chunk || ![chunk length]) {
+ // We do not support PUT-DELETE (?)
+ // At least the first chunk is expected to be non-empty.
+ qCWarning(QT_BT_OSX) << "-OBEXPutFile:withName:, invalid input stream";
+ return kOBEXBadArgumentError;
+ }
+
+ currentRequest = OBEXPut;
+
+ const OBEXError status = [session OBEXPut:lastChunk
+ headersData:[headers mutableBytes]
+ headersDataLength:[headers length]
+ bodyData:[chunk mutableBytes]
+ bodyDataLength:[chunk length]
+ eventSelector:@selector(OBEXPutHandler:)
+ selectorTarget:self
+ refCon:Q_NULLPTR];
+
+ if (status == kOBEXSuccess) {
+ if (delegate && fileSize && !input->isSequential())
+ delegate->OBEXPutDataSent([chunk length], fileSize);
+
+ bytesSent = [chunk length];
+ headersData = headers.take();
+ bodyData = chunk.take();
+ inputStream = input;
+ } else {
+ // PUT request failed and we now
+ // want to close a connection/session.
+ currentRequest = OBEXNoop;
+ // Try to cleanup (disconnect).
+ [self OBEXDisconnect];
+ }
+
+ return status;
+}
+
+- (void)OBEXPutHandler:(const OBEXSessionEvent*)event
+{
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(session, "-OBEXPutHandler:", "invalid OBEX session (nil)");
+
+ if (pendingAbort) {
+ currentRequest = OBEXNoop;
+ [self OBEXAbort];
+ return;
+ }
+
+ if (currentRequest != OBEXPut) {
+ qCWarning(QT_BT_OSX) << "-OBEXPutHandler:, called while the current "
+ "request is not a put request";
+ return;
+ }
+
+ OBEXError error = kOBEXSuccess;
+ OBEXOpCode responseCode = kOBEXResponseCodeSuccessWithFinalBit;
+ if (!check_put_event(event, error, responseCode)) {
+ currentRequest = OBEXNoop;
+ if (delegate)
+ delegate->OBEXPutError(error, responseCode);
+ [self OBEXDisconnect];
+ return;
+ }
+
+ // Now try to send more data if we have any.
+ if (responseCode == kOBEXResponseCodeContinueWithFinalBit) {
+ // Send more data.
+ bool lastChunk = false;
+ // 0 for the headers length, no more headers.
+ ObjCStrongReference<NSMutableData> chunk(next_data_chunk(*inputStream, session, 0, lastChunk));
+ if (!chunk && !lastChunk) {
+ qCWarning(QT_BT_OSX) << "-OBEXPutHandler:, failed to "
+ "allocate the next memory chunk";
+ return;
+ }
+
+ void *dataPtr = chunk ? [chunk mutableBytes] : Q_NULLPTR;
+ const NSUInteger dataSize = chunk ? [chunk length] : 0;
+ const OBEXError status = [session OBEXPut:lastChunk
+ headersData:Q_NULLPTR
+ headersDataLength:0
+ bodyData:dataPtr
+ bodyDataLength:dataSize
+ eventSelector:@selector(OBEXPutHandler:)
+ selectorTarget:self
+ refCon:Q_NULLPTR];
+
+ if (status != kOBEXSuccess) {
+ qCWarning(QT_BT_OSX) << "-OBEXPutHandler:, failed to "
+ "send the next memory chunk";
+ currentRequest = OBEXNoop;
+ if (delegate) // Response code is not important here.
+ delegate->OBEXPutError(kOBEXNoResourcesError, 0);
+
+ [self OBEXDisconnect];
+ } else {
+ [bodyData release];
+ bytesSent += [chunk length];
+ bodyData = chunk.take();//retained already.
+
+ if (delegate && !inputStream->isSequential())
+ delegate->OBEXPutDataSent(bytesSent, inputStream->size());
+ }
+ } else if (responseCode == kOBEXResponseCodeSuccessWithFinalBit) {
+ currentRequest = OBEXNoop;
+ if (delegate)
+ delegate->OBEXPutSuccess();
+
+ [self OBEXDisconnect];
+ }
+}
+
+- (void)OBEXDisconnect
+{
+ Q_ASSERT_X(session, "-OBEXDisconnect", "invalid session (nil)");
+
+ currentRequest = OSXBluetooth::OBEXDisconnect;
+
+ [session OBEXDisconnect:Q_NULLPTR
+ optionalHeadersLength:0
+ eventSelector:@selector(OBEXDisconnectHandler:)
+ selectorTarget:self
+ refCon:Q_NULLPTR];
+}
+
+- (void)OBEXDisconnectHandler:(const OBEXSessionEvent*)event
+{
+ Q_UNUSED(event)
+
+ Q_ASSERT_X(session, "-OBEXDisconnectHandler:", "invalid session (nil)");
+
+ // Event can have an error type, but there's nothing
+ // we can do - even "cleanup" failed.
+ connected = false;
+}
+
+- (bool)isConnected
+{
+ return device && session && connected;
+}
+
+- (void)closeSession
+{
+ // Clear the delegate and reset the request,
+ // do not try any of OBEX commands - the session will be deleted
+ // immediately.
+ delegate = Q_NULLPTR;
+ // This will stop any handler (callback) preventing
+ // any read/write to potentially deleted objects.
+ currentRequest = OSXBluetooth::OBEXNoop;
+}
+
+- (bool)hasActiveRequest
+{
+ return currentRequest != OSXBluetooth::OBEXNoop && !pendingAbort;
+}
+
+@end
diff --git a/src/bluetooth/osx/osxbtobexsession_p.h b/src/bluetooth/osx/osxbtobexsession_p.h
new file mode 100644
index 00000000..aa142b5a
--- /dev/null
+++ b/src/bluetooth/osx/osxbtobexsession_p.h
@@ -0,0 +1,147 @@
+/****************************************************************************
+**
+** 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 <QtCore/qvariant.h>
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+// Import - Obj-C headers do not have inclusion guards:
+#import <IOBluetooth/objc/OBEXSession.h>
+
+@class IOBluetoothOBEXSession;
+@class IOBluetoothDevice;
+
+@class QT_MANGLE_NAMESPACE(OSXBTOBEXSession);
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothAddress;
+class QIODevice;
+class QString;
+
+namespace OSXBluetooth
+{
+
+class OBEXSessionDelegate
+{
+public:
+ typedef QT_MANGLE_NAMESPACE(OSXBTOBEXSession) ObjCOBEXSession;
+
+ virtual ~OBEXSessionDelegate();
+
+ virtual void OBEXConnectError(OBEXError error, OBEXOpCode responseCode) = 0;
+ virtual void OBEXConnectSuccess() = 0;
+
+ virtual void OBEXAbortSuccess() = 0;
+
+ virtual void OBEXPutDataSent(quint32 current, quint32 total) = 0;
+ virtual void OBEXPutSuccess() = 0;
+ virtual void OBEXPutError(OBEXError error, OBEXOpCode responseCode) = 0;
+};
+
+enum OBEXRequest {
+ OBEXNoop,
+ OBEXConnect,
+ OBEXDisconnect,
+ OBEXPut,
+ OBEXGet,
+ OBEXSetPath,
+ OBEXAbort
+};
+
+}
+
+QT_END_NAMESPACE
+
+// OBEX Session, it's a "single-shot" operation as our QBluetoothTransferReply is
+// (it does not have an interface to re-send data or re-use the same transfer reply).
+// It either succeeds or fails and tries to cleanup in any case.
+@interface QT_MANGLE_NAMESPACE(OSXBTOBEXSession) : NSObject
+{
+ QT_PREPEND_NAMESPACE(OSXBluetooth::OBEXSessionDelegate) *delegate;
+ IOBluetoothDevice *device;
+ quint16 channelID;
+ IOBluetoothOBEXSession *session;
+
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::OBEXRequest currentRequest;
+
+ bool connected;
+ bool connectionIDFound;
+ quint32 connectionID;
+
+ QT_PREPEND_NAMESPACE(QIODevice) *inputStream;
+
+ // TODO: switch to scoped pointers or strong reference objects instead.
+ NSMutableData *headersData;
+ NSMutableData *bodyData;
+
+ quint32 bytesSent;
+ bool pendingAbort;
+}
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::OBEXSessionDelegate) *)aDelegate
+ remoteDevice:(const QBluetoothAddress &)deviceAddress channelID:(quint16)port;
+
+- (void)dealloc;
+
+// Below I have pairs: OBEX operation and its callback method.
+- (OBEXError)OBEXConnect;
+- (void)OBEXConnectHandler:(const OBEXSessionEvent*)event;
+
+- (OBEXError)OBEXAbort;
+- (void)OBEXAbortHandler:(const OBEXSessionEvent*)event;
+
+- (OBEXError)OBEXPutFile:(QT_PREPEND_NAMESPACE(QIODevice) *)inputStream withName:(const QString &)name;
+- (void)OBEXPutHandler:(const OBEXSessionEvent*)event;
+
+// Aux. methods.
+- (bool)isConnected;
+
+// To be called from C++ destructors. OBEXSession is not
+// valid anymore after this call (no more OBEX operations
+// can be executed). It's an ABORT/DISCONNECT sequence.
+// It also resets a delegate to null.
+- (void)closeSession;
+//
+- (bool)hasActiveRequest;
+
+@end
diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index 1c20c91a..cad84b7e 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -159,6 +159,13 @@ public:
{
return m_ptr;
}
+
+ T *take()
+ {
+ T * p = m_ptr;
+ m_ptr = nil;
+ return p;
+ }
private:
T *m_ptr;
};
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
index 9924f9ae..21744f0c 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
@@ -309,6 +309,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(IOBluetoothDevic
if (!isDuplicatedService(serviceInfo)) {
discoveredServices.append(serviceInfo);
emit q_ptr->serviceDiscovered(serviceInfo);
+ // Here a user code can ... interrupt us by calling
+ // stop. Check this.
+ if (state == Inactive)
+ break;
}
}
diff --git a/src/bluetooth/qbluetoothtransfermanager.cpp b/src/bluetooth/qbluetoothtransfermanager.cpp
index 0754fb9a..0897465d 100644
--- a/src/bluetooth/qbluetoothtransfermanager.cpp
+++ b/src/bluetooth/qbluetoothtransfermanager.cpp
@@ -38,7 +38,8 @@
#include "qbluetoothtransferreply_bluez_p.h"
#elif QT_QNX_BLUETOOTH
#include "qbluetoothtransferreply_qnx_p.h"
-#else
+#elif QT_OSX_BLUETOOTH
+#include "qbluetoothtransferreply_osx_p.h"
#endif
QT_BEGIN_NAMESPACE
@@ -99,6 +100,10 @@ QBluetoothTransferReply *QBluetoothTransferManager::put(const QBluetoothTransfer
QBluetoothTransferReplyQnx *reply = new QBluetoothTransferReplyQnx(data, request, this);
connect(reply, SIGNAL(finished(QBluetoothTransferReply*)), this, SIGNAL(finished(QBluetoothTransferReply*)));
return reply;
+#elif QT_OSX_BLUETOOTH
+ QBluetoothTransferReply *reply = new QBluetoothTransferReplyOSX(data, request, this);
+ connect(reply, SIGNAL(finished(QBluetoothTransferReply*)), this, SIGNAL(finished(QBluetoothTransferReply*)));
+ return reply;
#else
Q_UNUSED(request);
Q_UNUSED(data);
diff --git a/src/bluetooth/qbluetoothtransferreply_osx.mm b/src/bluetooth/qbluetoothtransferreply_osx.mm
new file mode 100644
index 00000000..3204e7b6
--- /dev/null
+++ b/src/bluetooth/qbluetoothtransferreply_osx.mm
@@ -0,0 +1,473 @@
+/****************************************************************************
+**
+** 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:LGPL21$
+** 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 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothservicediscoveryagent.h"
+#include "qbluetoothtransferreply_osx_p.h"
+#include "osx/osxbtobexsession_p.h"
+#include "qbluetoothserviceinfo.h"
+#include "osx/osxbtutility_p.h"
+#include "qbluetoothuuid.h"
+
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdir.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothTransferReplyOSXPrivate : OSXBluetooth::OBEXSessionDelegate
+{
+ friend class QBluetoothTransferReplyOSX;
+public:
+ QBluetoothTransferReplyOSXPrivate(QBluetoothTransferReplyOSX *q, QIODevice *inputStream);
+
+ ~QBluetoothTransferReplyOSXPrivate();
+
+ bool isActive() const;
+ bool startOPP(const QBluetoothAddress &device);
+
+ //
+ void sendConnect(const QBluetoothAddress &device, quint16 channelID);
+ void sendPut();
+
+private:
+ // OBEX session delegate:
+ void OBEXConnectError(OBEXError errorCode, OBEXOpCode response) Q_DECL_OVERRIDE;
+ void OBEXConnectSuccess() Q_DECL_OVERRIDE;
+
+ void OBEXAbortSuccess() Q_DECL_OVERRIDE;
+
+ void OBEXPutDataSent(quint32 current, quint32 total) Q_DECL_OVERRIDE;
+ void OBEXPutSuccess() Q_DECL_OVERRIDE;
+ void OBEXPutError(OBEXError error, OBEXOpCode response) Q_DECL_OVERRIDE;
+
+ QBluetoothTransferReplyOSX *q_ptr;
+
+ QIODevice *inputStream;
+
+ QBluetoothTransferReply::TransferError error;
+ QString errorString;
+
+ // Set requestComplete, error, description, emit error, emit finished.
+ // Too many things in one, but not to repeat this code everywhere.
+ void setReplyError(QBluetoothTransferReply::TransferError errorCode,
+ const QString &errorMessage);
+
+ // With a given API, we have to discover a service first
+ // since we need a channel ID to work with OBEX session.
+ // Also, service discovery agent does not have an interface
+ // to test discovery mode, that's why we have this bool here.
+ bool minimalScan;
+ QScopedPointer<QBluetoothServiceDiscoveryAgent> agent;
+
+ // The next step is to create an OBEX session:
+ typedef OSXBluetooth::ObjCScopedPointer<ObjCOBEXSession> OBEXSession;
+ OBEXSession session;
+
+ // Both success and failure to send - transfer is complete.
+ bool requestComplete;
+
+ // We need a temporary file to generate an unique name
+ // in case inputStream is not a file. QTemporaryFile not
+ // only creates a random name, it also guarantees this name
+ // is unique. The amount of code to generate such a name
+ // is amaizingly huge (and will require global variables)
+ // - so a temporary file can help.
+ QScopedPointer<QTemporaryFile> temporaryFile;
+};
+
+QBluetoothTransferReplyOSXPrivate::QBluetoothTransferReplyOSXPrivate(QBluetoothTransferReplyOSX *q,
+ QIODevice *input)
+ : q_ptr(q),
+ inputStream(input),
+ error(QBluetoothTransferReply::NoError),
+ minimalScan(true),
+ requestComplete(false)
+{
+ Q_ASSERT_X(q, "QBluetoothTransferReplyOSXPrivate", "invalid q_ptr (null)");
+}
+
+QBluetoothTransferReplyOSXPrivate::~QBluetoothTransferReplyOSXPrivate()
+{
+ // closeSession will set a delegate to null.
+ // The OBEX session will be closed then. If
+ // somehow IOBluetooth/OBEX still has a reference to our
+ // session, it will not call any of delegate's callbacks.
+ if (session)
+ [session closeSession];
+}
+
+bool QBluetoothTransferReplyOSXPrivate::isActive() const
+{
+ return agent || (session && [session hasActiveRequest]);
+}
+
+bool QBluetoothTransferReplyOSXPrivate::startOPP(const QBluetoothAddress &device)
+{
+ Q_ASSERT_X(!isActive(), "startOPP", "already started");
+ Q_ASSERT_X(!device.isNull(), "startOPP", "invalid device address");
+
+ errorString.clear();
+ error = QBluetoothTransferReply::NoError;
+
+ agent.reset(new QBluetoothServiceDiscoveryAgent);
+
+ agent->setRemoteAddress(device);
+ agent->setUuidFilter(QBluetoothUuid(QBluetoothUuid::ObexObjectPush));
+
+ QObject::connect(agent.data(), SIGNAL(finished()), q_ptr, SLOT(serviceDiscoveryFinished()));
+ QObject::connect(agent.data(), SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error)),
+ q_ptr, SLOT(serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error)));
+
+ minimalScan = true;
+ agent->start(QBluetoothServiceDiscoveryAgent::MinimalDiscovery);
+
+ // We probably failed already.
+ return error == QBluetoothTransferReply::NoError;
+}
+
+void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &device, quint16 channelID)
+{
+ Q_ASSERT_X(!session, "sendConnect", "session is already active");
+
+ error = QBluetoothTransferReply::NoError;
+ errorString.clear();
+
+ if (device.isNull() || !channelID) {
+ qCWarning(QT_BT_OSX) << "QBluetoothTransferReplyOSXPrivate::sendConnect(), "
+ "invalid device address or port";
+
+ setReplyError(QBluetoothTransferReply::HostNotFoundError,
+ QObject::tr("Invalid target address"));
+ return;
+ }
+
+ OBEXSession newSession([[ObjCOBEXSession alloc] initWithDelegate:this
+ remoteDevice:device channelID:channelID]);
+ if (!newSession) {
+ qCWarning(QT_BT_OSX) << "QBluetoothTransferReplyOSXPrivate::sendConnect(), "
+ "failed to allocate OSXBTOBEXSession object";
+
+ setReplyError(QBluetoothTransferReply::UnknownError,
+ QObject::tr("Failed to create an OBEX session"));
+ return;
+ }
+
+ const OBEXError status = [newSession OBEXConnect];
+
+ if ((status == kOBEXSuccess || status == kOBEXSessionAlreadyConnectedError)
+ && error == QBluetoothTransferReply::NoError) {
+ session.reset(newSession.take());
+ if ([session isConnected])
+ sendPut();// Connected, send a PUT request.
+ } else {
+ qCWarning(QT_BT_OSX) << "QBluetoothTransferReplyOSXPrivate::sendConnect(), "
+ "OBEXConnect failed";
+
+ if (error == QBluetoothTransferReply::NoError) {
+ // The error is not set yet.
+ error = QBluetoothTransferReply::SessionError;
+ errorString = QObject::tr("OBEXConnect failed");
+ }
+
+ requestComplete = true;
+ emit q_ptr->error(error);
+ emit q_ptr->finished(q_ptr);
+ }
+}
+
+void QBluetoothTransferReplyOSXPrivate::sendPut()
+{
+ Q_ASSERT_X(inputStream, "sendPut", "invalid input stream (null)");
+ Q_ASSERT_X(session, "sendPut", "invalid OBEX session (nil)");
+ Q_ASSERT_X([session isConnected], "sendPut", "not connected");
+ Q_ASSERT_X(![session hasActiveRequest], "sendPut",
+ "session already has an active request");
+
+ QString fileName;
+ QFile *const file = qobject_cast<QFile *>(inputStream);
+ if (file) {
+ if (!file->exists()) {
+ setReplyError(QBluetoothTransferReply::FileNotFoundError,
+ QObject::tr("Source file does not exist"));
+ return;
+ } else if (!file->isReadable()) {
+ file->open(QIODevice::ReadOnly);
+ if (!file->isReadable()) {
+ setReplyError(QBluetoothTransferReply::IODeviceNotReadableError,
+ QObject::tr("File cannot be read. "
+ "Make sure it is open for reading."));
+ return;
+ }
+ }
+
+ fileName = file->fileName();
+ } else {
+ if (!inputStream->isReadable()) {
+ setReplyError(QBluetoothTransferReply::IODeviceNotReadableError,
+ QObject::tr("QIODevice cannot be read. "
+ "Make sure it is open for reading."));
+ return;
+ }
+
+ temporaryFile.reset(new QTemporaryFile);
+ temporaryFile->open();
+ fileName = temporaryFile->fileName();
+ }
+
+ const QFileInfo fileInfo(fileName);
+ fileName = fileInfo.fileName();
+
+ if ([session OBEXPutFile:inputStream withName:fileName] != kOBEXSuccess) {
+ // TODO: convert OBEXError into something reasonable?
+ setReplyError(QBluetoothTransferReply::SessionError,
+ QObject::tr("OBEX put failed"));
+ }
+}
+
+
+void QBluetoothTransferReplyOSXPrivate::OBEXConnectError(OBEXError errorCode, OBEXOpCode response)
+{
+ Q_UNUSED(errorCode)
+ Q_UNUSED(response)
+
+ if (session) {
+ setReplyError(QBluetoothTransferReply::SessionError,
+ QObject::tr("OBEX connect failed"));
+ } else {
+ // Else we're still in OBEXConnect, in a call-back
+ // and do not want to emit yet (will be done a bit later).
+ error = QBluetoothTransferReply::SessionError;
+ errorString = QObject::tr("OBEX connect failed");
+ requestComplete = true;
+ }
+}
+
+void QBluetoothTransferReplyOSXPrivate::OBEXConnectSuccess()
+{
+ // Now that OBEX connect succeeded, we can send an OBEX put request.
+ if (!session) {
+ // We're still in OBEXConnect(), it'll take care of next steps.
+ return;
+ }
+
+ sendPut();
+}
+
+void QBluetoothTransferReplyOSXPrivate::OBEXAbortSuccess()
+{
+ // TODO:
+}
+
+void QBluetoothTransferReplyOSXPrivate::OBEXPutDataSent(quint32 current, quint32 total)
+{
+ emit q_ptr->transferProgress(current, total);
+}
+
+void QBluetoothTransferReplyOSXPrivate::OBEXPutSuccess()
+{
+ requestComplete = true;
+ emit q_ptr->finished(q_ptr);
+}
+
+void QBluetoothTransferReplyOSXPrivate::OBEXPutError(OBEXError errorCode, OBEXOpCode responseCode)
+{
+ // Error can be reported by errorCode or responseCode
+ // (that's how errors are reported in OBEXSession events).
+ // errorCode and responseCode are "mutually exclusive".
+
+ Q_UNUSED(responseCode)
+
+ if (errorCode != kOBEXSuccess) {
+ // TODO: errorCode -> TransferError.
+ } else {
+ // TODO: a response code can give some interesting information,
+ // like "forbidden" etc. - convert this into more reasonable error.
+ }
+
+ setReplyError(QBluetoothTransferReply::SessionError,
+ QObject::tr("OBEX PUT command failed"));
+}
+
+void QBluetoothTransferReplyOSXPrivate::setReplyError(QBluetoothTransferReply::TransferError errorCode,
+ const QString &description)
+{
+ // Not to be used to clear an error!
+
+ error = errorCode;
+ errorString = description;
+ requestComplete = true;
+ emit q_ptr->error(error);
+ emit q_ptr->finished(q_ptr);
+}
+
+
+QBluetoothTransferReplyOSX::QBluetoothTransferReplyOSX(QIODevice *input,
+ const QBluetoothTransferRequest &request,
+ QBluetoothTransferManager *manager)
+ : QBluetoothTransferReply(manager)
+
+{
+ Q_UNUSED(input)
+
+ setManager(manager);
+ setRequest(request);
+
+ osx_d_ptr.reset(new QBluetoothTransferReplyOSXPrivate(this, input));
+
+ if (input) {
+ QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
+ } else {
+ qCWarning(QT_BT_OSX) << "QBluetoothTransferReplyOSX::QBluetoothTransferReplyOSX(), "
+ "invalid input stream (null)";
+
+ osx_d_ptr->requestComplete = true;
+ osx_d_ptr->errorString = tr("Invalid input file (null)");
+ osx_d_ptr->error = FileNotFoundError;
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QBluetoothTransferReply::TransferError, FileNotFoundError));
+ }
+}
+
+QBluetoothTransferReplyOSX::~QBluetoothTransferReplyOSX()
+{
+ // A dtor to make a scoped pointer with incomplete type happy.
+}
+
+QBluetoothTransferReply::TransferError QBluetoothTransferReplyOSX::error() const
+{
+ return osx_d_ptr->error;
+}
+
+QString QBluetoothTransferReplyOSX::errorString() const
+{
+ return osx_d_ptr->errorString;
+}
+
+bool QBluetoothTransferReplyOSX::isFinished() const
+{
+ return osx_d_ptr->requestComplete;
+}
+
+bool QBluetoothTransferReplyOSX::isRunning() const
+{
+ return osx_d_ptr->isActive();
+}
+
+bool QBluetoothTransferReplyOSX::abort()
+{
+ // Reset a delegate.
+ [osx_d_ptr->session closeSession];
+ // Should never be called from an OBEX callback!
+ osx_d_ptr->session.reset(Q_NULLPTR);
+
+ // Not setReplyError, we emit finished only!
+ osx_d_ptr->requestComplete = true;
+ osx_d_ptr->errorString = tr("Operation canceled");
+ osx_d_ptr->error = UserCanceledTransferError;
+
+ emit finished(this);
+
+ return true;
+}
+
+bool QBluetoothTransferReplyOSX::start()
+{
+ // OBEXSession requires a channel ID and we have to find it first,
+ // using QBluetoothServiceDiscoveryAgent (singleDevice, OBEX uuid filter, start
+ // from MinimalDiscovery mode and continue with FullDiscovery if
+ // MinimalDiscovery fails.
+
+ if (!osx_d_ptr->isActive()) {
+ // Step 0: find a channelID.
+ if (request().address().isNull()) {
+ qCWarning(QT_BT_OSX) << "QBluetoothTransferReplyOSX::start(), "
+ "invalid device address";
+ osx_d_ptr->setReplyError(HostNotFoundError, tr("Invalid target address"));
+ return false;
+ }
+
+ return osx_d_ptr->startOPP(request().address());
+ } else {
+ osx_d_ptr->setReplyError(UnknownError, tr("Transfer already started"));
+ return false;
+ }
+}
+
+void QBluetoothTransferReplyOSX::serviceDiscoveryFinished()
+{
+ Q_ASSERT_X(osx_d_ptr->agent.data(), "serviceDiscoveryFinished",
+ "invalid service discovery agent (null)");
+
+ const QList<QBluetoothServiceInfo> services = osx_d_ptr->agent->discoveredServices();
+ if (services.size()) {
+ // TODO: what if we have several?
+ const QBluetoothServiceInfo &foundOBEX = services.front();
+ osx_d_ptr->sendConnect(request().address(), foundOBEX.serverChannel());
+ } else {
+ if (osx_d_ptr->minimalScan) {
+ // Try full discovery now.
+ osx_d_ptr->minimalScan = false;
+ osx_d_ptr->agent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
+ } else {
+ // No service record, no channel ID, no OBEX session.
+ osx_d_ptr->setReplyError(HostNotFoundError, tr("OBEX/OPP serice not found"));
+ }
+ }
+}
+
+void QBluetoothTransferReplyOSX::serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error errorCode)
+{
+ Q_ASSERT_X(osx_d_ptr->agent.data(), "serviceDiscoveryError", "invalid service discovery agent (null)");
+
+ if (errorCode == QBluetoothServiceDiscoveryAgent::PoweredOffError) {
+ // There's nothing else we can do.
+ osx_d_ptr->setReplyError(UnknownError, tr("Bluetooth adapter is powered off"));
+ return;
+ }
+
+ if (osx_d_ptr->minimalScan) {// Try again, this time in FullDiscovery mode.
+ osx_d_ptr->minimalScan = false;
+ osx_d_ptr->agent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
+ } else {
+ osx_d_ptr->setReplyError(HostNotFoundError, tr("Invalid target address"));
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothtransferreply_osx_p.h b/src/bluetooth/qbluetoothtransferreply_osx_p.h
new file mode 100644
index 00000000..cc973bfa
--- /dev/null
+++ b/src/bluetooth/qbluetoothtransferreply_osx_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** 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:LGPL21$
+** 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 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBLUETOOTHTRANSFERREPLY_OSX_P_H
+#define QBLUETOOTHTRANSFERREPLY_OSX_P_H
+
+#include "qbluetoothservicediscoveryagent.h"
+#include "qbluetoothtransferreply.h"
+
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothTransferReplyOSXPrivate;
+class QBluetoothServiceInfo;
+class QBluetoothAddress;
+class QIODevice;
+
+class Q_BLUETOOTH_EXPORT QBluetoothTransferReplyOSX : public QBluetoothTransferReply
+{
+ Q_OBJECT
+
+public:
+ QBluetoothTransferReplyOSX(QIODevice *input, const QBluetoothTransferRequest &request,
+ QBluetoothTransferManager *parent);
+ ~QBluetoothTransferReplyOSX();
+
+ TransferError error() const Q_DECL_OVERRIDE;
+ QString errorString() const Q_DECL_OVERRIDE;
+
+ bool isFinished() const Q_DECL_OVERRIDE;
+ bool isRunning() const Q_DECL_OVERRIDE;
+
+Q_SIGNALS:
+ void error(QBluetoothTransferReply::TransferError lastError);
+
+public slots:
+ bool abort();
+
+private slots:
+ bool start();
+
+ void serviceDiscoveryFinished();
+ void serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error error);
+
+private:
+ // OS X private data (not to be seen by moc).
+ QScopedPointer<QBluetoothTransferReplyOSXPrivate> osx_d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif