blob: 868b23194ae46ab43c399f32618ae7195fa4fef2 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.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>
#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, Q_FUNC_INFO, "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, Q_FUNC_INFO, "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, Q_FUNC_INFO, "invalid data (null)");
Q_ASSERT_X(length >= 2, Q_FUNC_INFO, "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) << "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, Q_FUNC_INFO, "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, Q_FUNC_INFO, "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) << "unexpected event type";
error = kOBEXGeneralError;
return false;
}
}
bool check_put_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
{
Q_ASSERT_X(e, Q_FUNC_INFO, "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) << "unexpected event type";
error = kOBEXGeneralError;
return false;
}
}
bool check_abort_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
{
Q_ASSERT_X(e, Q_FUNC_INFO, "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) << "unexpected event type";
return false;
}
}
} // Unnamed namespace.
} // OSXBluetooth.
QT_END_NAMESPACE
QT_USE_NAMESPACE
@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)
{
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;
}
+ (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, Q_FUNC_INFO, "invalid delegate (null)");
Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, "invalid remote device address");
Q_ASSERT_X(port, Q_FUNC_INFO, "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) << "failed to create an IOBluetoothDevice";
return self;
}
session = [[IOBluetoothOBEXSession alloc] initWithDevice:device channelID:port];
if (!session) {
qCWarning(QT_BT_OSX) << "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) << "invalid session (nil)";
return kOBEXGeneralError;
}
// That's a "single-shot" operation:
Q_ASSERT_X(currentRequest == OSXBluetooth::OBEXNoop, Q_FUNC_INFO,
"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:nullptr
optionalHeadersLength:0
eventSelector:@selector(OBEXConnectHandler:)
selectorTarget:self
refCon: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, Q_FUNC_INFO, "invalid session (nil)");
if (pendingAbort) {
currentRequest = OBEXNoop;
[self OBEXAbort];
return;
}
if (currentRequest != OBEXConnect) {
qCWarning(QT_BT_OSX) << "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 ...
const 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.
for (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, Q_FUNC_INFO, "invalid OBEX session (nil)");
if (currentRequest == OBEXNoop) {
pendingAbort = false;
if (![self isConnected])
return kOBEXSessionNotConnectedError;
currentRequest = OBEXAbort;
const OBEXError status = [session OBEXAbort:nullptr
optionalHeadersLength:0
eventSelector:@selector(OBEXAbortHandler:)
selectorTarget:self
refCon: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, Q_FUNC_INFO, "invalid OBEX session (nil)");
if (currentRequest != OBEXAbort) {
qCWarning(QT_BT_OSX) << "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, Q_FUNC_INFO,
"the current session has an active request already");
Q_ASSERT_X(input, Q_FUNC_INFO, "invalid input stream (null)");
Q_ASSERT_X(input->isReadable(), Q_FUNC_INFO, "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) << "invalid input file size";
return kOBEXBadArgumentError;
}
ObjCStrongReference<NSMutableData> headers([[NSMutableData alloc] init], false);
if (!headers) {
qCWarning(QT_BT_OSX) << "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) << "failed to append connection ID header";
return kOBEXNoResourcesError;
}
}
if (name.length()) {
if (!append_unicode_header(headers, kOBEXHeaderIDName, name)) {
qCWarning(QT_BT_OSX) << "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) << "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: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, Q_FUNC_INFO, "invalid OBEX session (nil)");
if (pendingAbort) {
currentRequest = OBEXNoop;
[self OBEXAbort];
return;
}
if (currentRequest != OBEXPut) {
qCWarning(QT_BT_OSX) << "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) << "failed to allocate the next memory chunk";
return;
}
void *dataPtr = chunk ? [chunk mutableBytes] : nullptr;
const NSUInteger dataSize = chunk ? [chunk length] : 0;
const OBEXError status = [session OBEXPut:lastChunk
headersData:nullptr
headersDataLength:0
bodyData:dataPtr
bodyDataLength:dataSize
eventSelector:@selector(OBEXPutHandler:)
selectorTarget:self
refCon:nullptr];
if (status != kOBEXSuccess) {
qCWarning(QT_BT_OSX) << "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, Q_FUNC_INFO, "invalid session (nil)");
currentRequest = OSXBluetooth::OBEXDisconnect;
[session OBEXDisconnect:nullptr
optionalHeadersLength:0
eventSelector:@selector(OBEXDisconnectHandler:)
selectorTarget:self
refCon:nullptr];
}
- (void)OBEXDisconnectHandler:(const OBEXSessionEvent*)event
{
Q_UNUSED(event)
Q_ASSERT_X(session, Q_FUNC_INFO, "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 = 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