summaryrefslogtreecommitdiffstats
path: root/chromium/device/bluetooth/bluetooth_socket_mac.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/device/bluetooth/bluetooth_socket_mac.mm')
-rw-r--r--chromium/device/bluetooth/bluetooth_socket_mac.mm968
1 files changed, 862 insertions, 106 deletions
diff --git a/chromium/device/bluetooth/bluetooth_socket_mac.mm b/chromium/device/bluetooth/bluetooth_socket_mac.mm
index 3751441f521..e428ba5a7f0 100644
--- a/chromium/device/bluetooth/bluetooth_socket_mac.mm
+++ b/chromium/device/bluetooth/bluetooth_socket_mac.mm
@@ -4,179 +4,935 @@
#include "device/bluetooth/bluetooth_socket_mac.h"
-#import <IOBluetooth/objc/IOBluetoothDevice.h>
-#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
-#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h>
+#import <IOBluetooth/IOBluetooth.h>
#include <limits>
+#include <sstream>
#include <string>
#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/mac/scoped_cftyperef.h"
#include "base/memory/ref_counted.h"
+#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
-#include "device/bluetooth/bluetooth_service_record.h"
-#include "device/bluetooth/bluetooth_service_record_mac.h"
+#include "base/threading/thread_restrictions.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_mac.h"
+#include "device/bluetooth/bluetooth_channel_mac.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_device_mac.h"
+#include "device/bluetooth/bluetooth_l2cap_channel_mac.h"
+#include "device/bluetooth/bluetooth_rfcomm_channel_mac.h"
#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
// Replicate specific 10.7 SDK declarations for building with prior SDKs.
#if !defined(MAC_OS_X_VERSION_10_7) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
@interface IOBluetoothDevice (LionSDKDeclarations)
-- (NSString*)addressString;
+- (IOReturn)performSDPQuery:(id)target uuids:(NSArray*)uuids;
@end
#endif // MAC_OS_X_VERSION_10_7
-@interface BluetoothRFCOMMChannelDelegate
- : NSObject <IOBluetoothRFCOMMChannelDelegate> {
+using device::BluetoothSocket;
+
+// A simple helper class that forwards SDP query completed notifications to its
+// wrapped |socket_|.
+@interface SDPQueryListener : NSObject {
+ @private
+ // The socket that registered for notifications.
+ scoped_refptr<device::BluetoothSocketMac> socket_;
+
+ // Callbacks associated with the request that triggered this SDP query.
+ base::Closure success_callback_;
+ BluetoothSocket::ErrorCompletionCallback error_callback_;
+
+ // The device being queried.
+ IOBluetoothDevice* device_; // weak
+}
+
+- (id)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket
+ device:(IOBluetoothDevice*)device
+ success_callback:(base::Closure)success_callback
+ error_callback:(BluetoothSocket::ErrorCompletionCallback)error_callback;
+- (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status;
+
+@end
+
+@implementation SDPQueryListener
+
+- (id)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket
+ device:(IOBluetoothDevice*)device
+ success_callback:(base::Closure)success_callback
+ error_callback:(BluetoothSocket::ErrorCompletionCallback)error_callback {
+ if ((self = [super init])) {
+ socket_ = socket;
+ device_ = device;
+ success_callback_ = success_callback;
+ error_callback_ = error_callback;
+ }
+
+ return self;
+}
+
+- (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status {
+ DCHECK_EQ(device, device_);
+ socket_->OnSDPQueryComplete(
+ status, device, success_callback_, error_callback_);
+}
+
+@end
+
+// A simple helper class that forwards RFCOMM channel opened notifications to
+// its wrapped |socket_|.
+@interface BluetoothRfcommConnectionListener : NSObject {
+ @private
+ // The socket that owns |self|.
+ device::BluetoothSocketMac* socket_; // weak
+
+ // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel
+ // creation notifications.
+ IOBluetoothUserNotification* rfcommNewChannelNotification_; // weak
+}
+
+- (id)initWithSocket:(device::BluetoothSocketMac*)socket
+ channelID:(BluetoothRFCOMMChannelID)channelID;
+- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification
+ channel:(IOBluetoothRFCOMMChannel*)rfcommChannel;
+
+@end
+
+@implementation BluetoothRfcommConnectionListener
+
+- (id)initWithSocket:(device::BluetoothSocketMac*)socket
+ channelID:(BluetoothRFCOMMChannelID)channelID {
+ if ((self = [super init])) {
+ socket_ = socket;
+
+ SEL selector = @selector(rfcommChannelOpened:channel:);
+ const auto kIncomingDirection =
+ kIOBluetoothUserNotificationChannelDirectionIncoming;
+ rfcommNewChannelNotification_ =
+ [IOBluetoothRFCOMMChannel
+ registerForChannelOpenNotifications:self
+ selector:selector
+ withChannelID:channelID
+ direction:kIncomingDirection];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [rfcommNewChannelNotification_ unregister];
+ [super dealloc];
+}
+
+- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification
+ channel:(IOBluetoothRFCOMMChannel*)rfcommChannel {
+ if (notification != rfcommNewChannelNotification_) {
+ // This case is reachable if there are pre-existing RFCOMM channels open at
+ // the time that the listener is created. In that case, each existing
+ // channel calls into this method with a different notification than the one
+ // this class registered with. Ignore those; this class is only interested
+ // in channels that have opened since it registered for notifications.
+ return;
+ }
+
+ socket_->OnChannelOpened(scoped_ptr<device::BluetoothChannelMac>(
+ new device::BluetoothRfcommChannelMac(NULL, [rfcommChannel retain])));
+}
+
+@end
+
+// A simple helper class that forwards L2CAP channel opened notifications to
+// its wrapped |socket_|.
+@interface BluetoothL2capConnectionListener : NSObject {
@private
+ // The socket that owns |self|.
device::BluetoothSocketMac* socket_; // weak
+
+ // The OS mechanism used to subscribe to and unsubscribe from L2CAP channel
+ // creation notifications.
+ IOBluetoothUserNotification* l2capNewChannelNotification_; // weak
}
-- (id)initWithSocket:(device::BluetoothSocketMac*)socket;
+- (id)initWithSocket:(device::BluetoothSocketMac*)socket
+ psm:(BluetoothL2CAPPSM)psm;
+- (void)l2capChannelOpened:(IOBluetoothUserNotification*)notification
+ channel:(IOBluetoothL2CAPChannel*)l2capChannel;
@end
-@implementation BluetoothRFCOMMChannelDelegate
+@implementation BluetoothL2capConnectionListener
-- (id)initWithSocket:(device::BluetoothSocketMac*)socket {
- if ((self = [super init]))
+- (id)initWithSocket:(device::BluetoothSocketMac*)socket
+ psm:(BluetoothL2CAPPSM)psm {
+ if ((self = [super init])) {
socket_ = socket;
+ SEL selector = @selector(l2capChannelOpened:channel:);
+ const auto kIncomingDirection =
+ kIOBluetoothUserNotificationChannelDirectionIncoming;
+ l2capNewChannelNotification_ =
+ [IOBluetoothL2CAPChannel
+ registerForChannelOpenNotifications:self
+ selector:selector
+ withPSM:psm
+ direction:kIncomingDirection];
+ }
+
return self;
}
-- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel
- data:(void*)dataPointer
- length:(size_t)dataLength {
- socket_->OnDataReceived(rfcommChannel, dataPointer, dataLength);
+- (void)dealloc {
+ [l2capNewChannelNotification_ unregister];
+ [super dealloc];
+}
+
+- (void)l2capChannelOpened:(IOBluetoothUserNotification*)notification
+ channel:(IOBluetoothL2CAPChannel*)l2capChannel {
+ if (notification != l2capNewChannelNotification_) {
+ // This case is reachable if there are pre-existing L2CAP channels open at
+ // the time that the listener is created. In that case, each existing
+ // channel calls into this method with a different notification than the one
+ // this class registered with. Ignore those; this class is only interested
+ // in channels that have opened since it registered for notifications.
+ return;
+ }
+
+ socket_->OnChannelOpened(scoped_ptr<device::BluetoothChannelMac>(
+ new device::BluetoothL2capChannelMac(NULL, [l2capChannel retain])));
}
@end
namespace device {
+namespace {
-BluetoothSocketMac::BluetoothSocketMac(IOBluetoothRFCOMMChannel* rfcomm_channel)
- : rfcomm_channel_(rfcomm_channel),
- delegate_([[BluetoothRFCOMMChannelDelegate alloc] initWithSocket:this]) {
- [rfcomm_channel_ setDelegate:delegate_];
- ResetIncomingDataBuffer();
+// It's safe to use 0 to represent an unregistered service, as implied by the
+// documentation at [ http://goo.gl/YRtCkF ].
+const BluetoothSDPServiceRecordHandle kInvalidServiceRecordHandle = 0;
+
+const char kInvalidOrUsedChannel[] = "Invalid channel or already in use";
+const char kInvalidOrUsedPsm[] = "Invalid PSM or already in use";
+const char kProfileNotFound[] = "Profile not found";
+const char kSDPQueryFailed[] = "SDP query failed";
+const char kSocketConnecting[] = "The socket is currently connecting";
+const char kSocketAlreadyConnected[] = "The socket is already connected";
+const char kSocketNotConnected[] = "The socket is not connected";
+const char kReceivePending[] = "A Receive operation is pending";
+
+template <class T>
+void empty_queue(std::queue<T>& queue) {
+ std::queue<T> empty;
+ std::swap(queue, empty);
}
-BluetoothSocketMac::~BluetoothSocketMac() {
- [rfcomm_channel_ setDelegate:nil];
- [rfcomm_channel_ closeChannel];
- [rfcomm_channel_ release];
- [delegate_ release];
+// Converts |uuid| to a IOBluetoothSDPUUID instance.
+IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const BluetoothUUID& uuid) {
+ // The canonical UUID format is XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.
+ const std::string uuid_str = uuid.canonical_value();
+ DCHECK_EQ(uuid_str.size(), 36U);
+ DCHECK_EQ(uuid_str[8], '-');
+ DCHECK_EQ(uuid_str[13], '-');
+ DCHECK_EQ(uuid_str[18], '-');
+ DCHECK_EQ(uuid_str[23], '-');
+ std::string numbers_only = uuid_str;
+ numbers_only.erase(23, 1);
+ numbers_only.erase(18, 1);
+ numbers_only.erase(13, 1);
+ numbers_only.erase(8, 1);
+ std::vector<uint8> uuid_bytes_vector;
+ base::HexStringToBytes(numbers_only, &uuid_bytes_vector);
+ DCHECK_EQ(uuid_bytes_vector.size(), 16U);
+
+ return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector.front()
+ length:uuid_bytes_vector.size()];
}
-// static
-scoped_refptr<BluetoothSocket> BluetoothSocketMac::CreateBluetoothSocket(
- const BluetoothServiceRecord& service_record) {
- BluetoothSocketMac* bluetooth_socket = NULL;
- if (service_record.SupportsRfcomm()) {
- const BluetoothServiceRecordMac* service_record_mac =
- static_cast<const BluetoothServiceRecordMac*>(&service_record);
- IOBluetoothDevice* device = service_record_mac->GetIOBluetoothDevice();
- IOBluetoothRFCOMMChannel* rfcomm_channel;
- IOReturn status =
- [device openRFCOMMChannelAsync:&rfcomm_channel
- withChannelID:service_record.rfcomm_channel()
- delegate:nil];
- if (status == kIOReturnSuccess) {
- bluetooth_socket = new BluetoothSocketMac(rfcomm_channel);
- } else {
- LOG(ERROR) << "Failed to connect bluetooth socket ("
- << service_record.address() << "): (" << status << ")";
- }
+// Converts the given |integer| to a string.
+NSString* IntToNSString(int integer) {
+ return [[NSNumber numberWithInt:integer] stringValue];
+}
+
+// Returns a dictionary containing the Bluetooth service definition
+// corresponding to the provided |uuid| and |protocol_definition|.
+NSDictionary* BuildServiceDefinition(const BluetoothUUID& uuid,
+ NSArray* protocol_definition) {
+ NSMutableDictionary* service_definition = [NSMutableDictionary dictionary];
+
+ // TODO(isherman): The service's language is currently hardcoded to English.
+ // The language should ideally be specified in the chrome.bluetooth API
+ // instead.
+ // TODO(isherman): Pass in the service name to this function.
+ const int kEnglishLanguageBase = 100;
+ const int kServiceNameKey =
+ kEnglishLanguageBase + kBluetoothSDPAttributeIdentifierServiceName;
+ NSString* service_name = base::SysUTF8ToNSString(uuid.canonical_value());
+ [service_definition setObject:service_name
+ forKey:IntToNSString(kServiceNameKey)];
+
+ const int kUUIDsKey = kBluetoothSDPAttributeIdentifierServiceClassIDList;
+ NSArray* uuids = @[GetIOBluetoothSDPUUID(uuid)];
+ [service_definition setObject:uuids forKey:IntToNSString(kUUIDsKey)];
+
+ const int kProtocolDefinitionsKey =
+ kBluetoothSDPAttributeIdentifierProtocolDescriptorList;
+ [service_definition setObject:protocol_definition
+ forKey:IntToNSString(kProtocolDefinitionsKey)];
+
+ return service_definition;
+}
+
+// Returns a dictionary containing the Bluetooth RFCOMM service definition
+// corresponding to the provided |uuid| and |channel_id|.
+NSDictionary* BuildRfcommServiceDefinition(const BluetoothUUID& uuid,
+ int channel_id) {
+ NSArray* rfcomm_protocol_definition =
+ @[
+ @[
+ [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP]
+ ],
+ @[
+ [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM],
+ @{
+ @"DataElementType": @1, // Unsigned integer.
+ @"DataElementSize": @1, // 1 byte.
+ @"DataElementValue": [NSNumber numberWithInt:channel_id]
+ }
+ ]
+ ];
+ return BuildServiceDefinition(uuid, rfcomm_protocol_definition);
+}
+
+// Returns a dictionary containing the Bluetooth L2CAP service definition
+// corresponding to the provided |uuid| and |psm|.
+NSDictionary* BuildL2capServiceDefinition(const BluetoothUUID& uuid,
+ int psm) {
+ NSArray* l2cap_protocol_definition =
+ @[
+ @[
+ [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP],
+ @{
+ @"DataElementType": @1, // Unsigned integer.
+ @"DataElementSize": @2, // 2 bytes.
+ @"DataElementValue": [NSNumber numberWithInt:psm]
+ }
+ ]
+ ];
+ return BuildServiceDefinition(uuid, l2cap_protocol_definition);
+}
+
+// Registers a Bluetooth service with the specified |service_definition| in the
+// system SDP server. Returns a handle to the registered service on success. If
+// the service could not be registered, or if |verify_service_callback|
+// indicates that the to-be-registered service is not configured correctly,
+// returns |kInvalidServiceRecordHandle|.
+BluetoothSDPServiceRecordHandle RegisterService(
+ NSDictionary* service_definition,
+ const base::Callback<bool(IOBluetoothSDPServiceRecord*)>&
+ verify_service_callback) {
+ // Attempt to register the service.
+ IOBluetoothSDPServiceRecordRef service_record_ref;
+ IOReturn result =
+ IOBluetoothAddServiceDict((CFDictionaryRef)service_definition,
+ &service_record_ref);
+ if (result != kIOReturnSuccess)
+ return kInvalidServiceRecordHandle;
+ // Transfer ownership to a scoped object, to simplify memory management.
+ base::ScopedCFTypeRef<IOBluetoothSDPServiceRecordRef>
+ scoped_service_record_ref(service_record_ref);
+
+ // Extract the service record handle.
+ BluetoothSDPServiceRecordHandle service_record_handle;
+ IOBluetoothSDPServiceRecord* service_record =
+ [IOBluetoothSDPServiceRecord withSDPServiceRecordRef:service_record_ref];
+ result = [service_record getServiceRecordHandle:&service_record_handle];
+ if (result != kIOReturnSuccess)
+ return kInvalidServiceRecordHandle;
+
+ // Verify that the registered service was configured correctly. If not,
+ // withdraw the service.
+ if (!verify_service_callback.Run(service_record)) {
+ IOBluetoothRemoveServiceWithRecordHandle(service_record_handle);
+ return kInvalidServiceRecordHandle;
}
- // TODO(youngki): add support for L2CAP sockets as well.
- return scoped_refptr<BluetoothSocketMac>(bluetooth_socket);
+ return service_record_handle;
}
+// Returns true iff the |requested_channel_id| was registered in the RFCOMM
+// |service_record|. If it was, also updates |registered_channel_id| with the
+// registered value, as the requested id may have been left unspecified.
+bool VerifyRfcommService(int requested_channel_id,
+ BluetoothRFCOMMChannelID* registered_channel_id,
+ IOBluetoothSDPServiceRecord* service_record) {
+ // Test whether the requested channel id was available.
+ // TODO(isherman): The OS doesn't seem to actually pick a random channel if we
+ // pass in |kChannelAuto|.
+ BluetoothRFCOMMChannelID rfcomm_channel_id;
+ IOReturn result = [service_record getRFCOMMChannelID:&rfcomm_channel_id];
+ if (result != kIOReturnSuccess ||
+ (requested_channel_id != BluetoothAdapter::kChannelAuto &&
+ rfcomm_channel_id != requested_channel_id)) {
+ return false;
+ }
+
+ *registered_channel_id = rfcomm_channel_id;
+ return true;
+}
+
+// Registers an RFCOMM service with the specified |uuid| and |channel_id| in the
+// system SDP server. Returns a handle to the registered service and updates
+// |registered_channel_id| to the actual channel id, or returns
+// |kInvalidServiceRecordHandle| if the service could not be registered.
+BluetoothSDPServiceRecordHandle RegisterRfcommService(
+ const BluetoothUUID& uuid,
+ int channel_id,
+ BluetoothRFCOMMChannelID* registered_channel_id) {
+ return RegisterService(
+ BuildRfcommServiceDefinition(uuid, channel_id),
+ base::Bind(&VerifyRfcommService, channel_id, registered_channel_id));
+}
+
+// Returns true iff the |requested_psm| was registered in the L2CAP
+// |service_record|. If it was, also updates |registered_psm| with the
+// registered value, as the requested PSM may have been left unspecified.
+bool VerifyL2capService(int requested_psm,
+ BluetoothL2CAPPSM* registered_psm,
+ IOBluetoothSDPServiceRecord* service_record) {
+ // Test whether the requested PSM was available.
+ // TODO(isherman): The OS doesn't seem to actually pick a random PSM if we
+ // pass in |kPsmAuto|.
+ BluetoothL2CAPPSM l2cap_psm;
+ IOReturn result = [service_record getL2CAPPSM:&l2cap_psm];
+ if (result != kIOReturnSuccess ||
+ (requested_psm != BluetoothAdapter::kPsmAuto &&
+ l2cap_psm != requested_psm)) {
+ return false;
+ }
+
+ *registered_psm = l2cap_psm;
+ return true;
+}
+
+// Registers an L2CAP service with the specified |uuid| and |psm| in the system
+// SDP server. Returns a handle to the registered service and updates
+// |registered_psm| to the actual PSM, or returns |kInvalidServiceRecordHandle|
+// if the service could not be registered.
+BluetoothSDPServiceRecordHandle RegisterL2capService(
+ const BluetoothUUID& uuid,
+ int psm,
+ BluetoothL2CAPPSM* registered_psm) {
+ return RegisterService(BuildL2capServiceDefinition(uuid, psm),
+ base::Bind(&VerifyL2capService, psm, registered_psm));
+}
+
+} // namespace
+
// static
-scoped_refptr<BluetoothSocket> BluetoothSocketMac::CreateBluetoothSocket(
- IOBluetoothSDPServiceRecord* record) {
- BluetoothSocketMac* bluetooth_socket = NULL;
- uint8 rfcomm_channel_id;
- if ([record getRFCOMMChannelID:&rfcomm_channel_id] == kIOReturnSuccess) {
- IOBluetoothDevice* device = [record device];
- IOBluetoothRFCOMMChannel* rfcomm_channel;
- IOReturn status =
- [device openRFCOMMChannelAsync:&rfcomm_channel
- withChannelID:rfcomm_channel_id
- delegate:nil];
- if (status == kIOReturnSuccess) {
- bluetooth_socket = new BluetoothSocketMac(rfcomm_channel);
- } else {
- LOG(ERROR) << "Failed to connect bluetooth socket ("
- << base::SysNSStringToUTF8([device addressString]) << "): (" << status
- << ")";
+scoped_refptr<BluetoothSocketMac> BluetoothSocketMac::CreateSocket() {
+ return make_scoped_refptr(new BluetoothSocketMac());
+}
+
+void BluetoothSocketMac::Connect(
+ IOBluetoothDevice* device,
+ const BluetoothUUID& uuid,
+ const base::Closure& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ uuid_ = uuid;
+
+ // Perform an SDP query on the |device| to refresh the cache, in case the
+ // services that the |device| advertises have changed since the previous
+ // query.
+ DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " "
+ << uuid_.canonical_value() << ": Sending SDP query.";
+ SDPQueryListener* listener =
+ [[SDPQueryListener alloc] initWithSocket:this
+ device:device
+ success_callback:success_callback
+ error_callback:error_callback];
+ [device performSDPQuery:[listener autorelease]
+ uuids:@[GetIOBluetoothSDPUUID(uuid_)]];
+}
+
+void BluetoothSocketMac::ListenUsingRfcomm(
+ scoped_refptr<BluetoothAdapterMac> adapter,
+ const BluetoothUUID& uuid,
+ int channel_id,
+ const base::Closure& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ adapter_ = adapter;
+ uuid_ = uuid;
+
+ DVLOG(1) << uuid_.canonical_value() << ": Registering RFCOMM service.";
+ BluetoothRFCOMMChannelID registered_channel_id;
+ service_record_handle_ =
+ RegisterRfcommService(uuid, channel_id, &registered_channel_id);
+ if (service_record_handle_ == kInvalidServiceRecordHandle) {
+ error_callback.Run(kInvalidOrUsedChannel);
+ return;
+ }
+
+ rfcomm_connection_listener_.reset(
+ [[BluetoothRfcommConnectionListener alloc]
+ initWithSocket:this
+ channelID:registered_channel_id]);
+
+ success_callback.Run();
+}
+
+void BluetoothSocketMac::ListenUsingL2cap(
+ scoped_refptr<BluetoothAdapterMac> adapter,
+ const BluetoothUUID& uuid,
+ int psm,
+ const base::Closure& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ adapter_ = adapter;
+ uuid_ = uuid;
+
+ DVLOG(1) << uuid_.canonical_value() << ": Registering L2CAP service.";
+ BluetoothL2CAPPSM registered_psm;
+ service_record_handle_ = RegisterL2capService(uuid, psm, &registered_psm);
+ if (service_record_handle_ == kInvalidServiceRecordHandle) {
+ error_callback.Run(kInvalidOrUsedPsm);
+ return;
+ }
+
+ l2cap_connection_listener_.reset(
+ [[BluetoothL2capConnectionListener alloc] initWithSocket:this
+ psm:registered_psm]);
+
+ success_callback.Run();
+}
+
+void BluetoothSocketMac::OnSDPQueryComplete(
+ IOReturn status,
+ IOBluetoothDevice* device,
+ const base::Closure& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " "
+ << uuid_.canonical_value() << ": SDP query complete.";
+
+ if (status != kIOReturnSuccess) {
+ error_callback.Run(kSDPQueryFailed);
+ return;
+ }
+
+ IOBluetoothSDPServiceRecord* record = [device
+ getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_)];
+ if (record == nil) {
+ error_callback.Run(kProfileNotFound);
+ return;
+ }
+
+ if (is_connecting()) {
+ error_callback.Run(kSocketConnecting);
+ return;
+ }
+
+ if (channel_) {
+ error_callback.Run(kSocketAlreadyConnected);
+ return;
+ }
+
+ // Since RFCOMM is built on top of L2CAP, a service record with both should
+ // always be treated as RFCOMM.
+ BluetoothRFCOMMChannelID rfcomm_channel_id = BluetoothAdapter::kChannelAuto;
+ BluetoothL2CAPPSM l2cap_psm = BluetoothAdapter::kPsmAuto;
+ status = [record getRFCOMMChannelID:&rfcomm_channel_id];
+ if (status != kIOReturnSuccess) {
+ status = [record getL2CAPPSM:&l2cap_psm];
+ if (status != kIOReturnSuccess) {
+ error_callback.Run(kProfileNotFound);
+ return;
}
}
- // TODO(youngki): Add support for L2CAP sockets as well.
+ if (rfcomm_channel_id != BluetoothAdapter::kChannelAuto) {
+ DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " "
+ << uuid_.canonical_value() << ": Opening RFCOMM channel: "
+ << rfcomm_channel_id;
+ } else {
+ DCHECK_NE(l2cap_psm, BluetoothAdapter::kPsmAuto);
+ DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " "
+ << uuid_.canonical_value() << ": Opening L2CAP channel: "
+ << l2cap_psm;
+ }
+
+ // Note: It's important to set the connect callbacks *prior* to opening the
+ // channel, as opening the channel can synchronously call into
+ // OnChannelOpenComplete().
+ connect_callbacks_.reset(new ConnectCallbacks());
+ connect_callbacks_->success_callback = success_callback;
+ connect_callbacks_->error_callback = error_callback;
+
+ if (rfcomm_channel_id != BluetoothAdapter::kChannelAuto) {
+ channel_ = BluetoothRfcommChannelMac::OpenAsync(
+ this, device, rfcomm_channel_id, &status);
+ } else {
+ DCHECK_NE(l2cap_psm, BluetoothAdapter::kPsmAuto);
+ channel_ =
+ BluetoothL2capChannelMac::OpenAsync(this, device, l2cap_psm, &status);
+ }
+ if (status != kIOReturnSuccess) {
+ ReleaseChannel();
+ std::stringstream error;
+ error << "Failed to connect bluetooth socket ("
+ << BluetoothDeviceMac::GetDeviceAddress(device) << "): (" << status
+ << ")";
+ error_callback.Run(error.str());
+ return;
+ }
+
+ DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " "
+ << uuid_.canonical_value()
+ << ": channel opening in background.";
+}
+
+void BluetoothSocketMac::OnChannelOpened(
+ scoped_ptr<BluetoothChannelMac> channel) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << uuid_.canonical_value() << ": Incoming channel pending.";
+
+ accept_queue_.push(linked_ptr<BluetoothChannelMac>(channel.release()));
+ if (accept_request_)
+ AcceptConnectionRequest();
+
+ // TODO(isherman): Test whether these TODOs are still relevant.
+ // TODO(isherman): Currently, both the profile and the socket remain alive
+ // even after the app that requested them is closed. That's not great, as a
+ // misbehaving app could saturate all of the system's RFCOMM channels, and
+ // then they would not be freed until the user restarts Chrome.
+ // http://crbug.com/367316
+ // TODO(isherman): Likewise, the socket currently remains alive even if the
+ // underlying rfcomm_channel is closed, e.g. via the client disconnecting, or
+ // the user closing the Bluetooth connection via the system menu. This
+ // functions essentially as a minor memory leak.
+ // http://crbug.com/367319
+}
+
+void BluetoothSocketMac::OnChannelOpenComplete(
+ const std::string& device_address,
+ IOReturn status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(is_connecting());
+
+ DVLOG(1) << device_address << " " << uuid_.canonical_value()
+ << ": channel open complete.";
+
+ scoped_ptr<ConnectCallbacks> temp = connect_callbacks_.Pass();
+ if (status != kIOReturnSuccess) {
+ ReleaseChannel();
+ std::stringstream error;
+ error << "Failed to connect bluetooth socket (" << device_address << "): ("
+ << status << ")";
+ temp->error_callback.Run(error.str());
+ return;
+ }
+
+ temp->success_callback.Run();
+}
+
+void BluetoothSocketMac::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
- return scoped_refptr<BluetoothSocketMac>(bluetooth_socket);
+ if (channel_)
+ ReleaseChannel();
+ else if (service_record_handle_ != kInvalidServiceRecordHandle)
+ ReleaseListener();
}
-bool BluetoothSocketMac::Receive(net::GrowableIOBuffer* buffer) {
- CHECK(buffer->offset() == 0);
- int length = incoming_data_buffer_->offset();
- if (length > 0) {
- buffer->SetCapacity(length);
- memcpy(buffer->data(), incoming_data_buffer_->StartOfBuffer(), length);
- buffer->set_offset(length);
+void BluetoothSocketMac::Disconnect(const base::Closure& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ Close();
+ callback.Run();
+}
- ResetIncomingDataBuffer();
+void BluetoothSocketMac::Receive(
+ int /* buffer_size */,
+ const ReceiveCompletionCallback& success_callback,
+ const ReceiveErrorCompletionCallback& error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (is_connecting()) {
+ error_callback.Run(BluetoothSocket::kSystemError, kSocketConnecting);
+ return;
}
- return true;
+
+ if (!channel_) {
+ error_callback.Run(BluetoothSocket::kDisconnected, kSocketNotConnected);
+ return;
+ }
+
+ // Only one pending read at a time
+ if (receive_callbacks_) {
+ error_callback.Run(BluetoothSocket::kIOPending, kReceivePending);
+ return;
+ }
+
+ // If there is at least one packet, consume it and succeed right away.
+ if (!receive_queue_.empty()) {
+ scoped_refptr<net::IOBufferWithSize> buffer = receive_queue_.front();
+ receive_queue_.pop();
+ success_callback.Run(buffer->size(), buffer);
+ return;
+ }
+
+ // Set the receive callback to use when data is received.
+ receive_callbacks_.reset(new ReceiveCallbacks());
+ receive_callbacks_->success_callback = success_callback;
+ receive_callbacks_->error_callback = error_callback;
}
-bool BluetoothSocketMac::Send(net::DrainableIOBuffer* buffer) {
- int bytes_written = buffer->BytesRemaining();
- IOReturn status = [rfcomm_channel_ writeAsync:buffer->data()
- length:bytes_written
- refcon:nil];
+void BluetoothSocketMac::OnChannelDataReceived(void* data, size_t length) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!is_connecting());
+
+ int data_size = base::checked_cast<int>(length);
+ scoped_refptr<net::IOBufferWithSize> buffer(
+ new net::IOBufferWithSize(data_size));
+ memcpy(buffer->data(), data, buffer->size());
+
+ // If there is a pending read callback, call it now.
+ if (receive_callbacks_) {
+ scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass();
+ temp->success_callback.Run(buffer->size(), buffer);
+ return;
+ }
+
+ // Otherwise, enqueue the buffer for later use
+ receive_queue_.push(buffer);
+}
+
+void BluetoothSocketMac::Send(scoped_refptr<net::IOBuffer> buffer,
+ int buffer_size,
+ const SendCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (is_connecting()) {
+ error_callback.Run(kSocketConnecting);
+ return;
+ }
+
+ if (!channel_) {
+ error_callback.Run(kSocketNotConnected);
+ return;
+ }
+
+ // Create and enqueue request in preparation of async writes.
+ linked_ptr<SendRequest> request(new SendRequest());
+ request->buffer_size = buffer_size;
+ request->success_callback = success_callback;
+ request->error_callback = error_callback;
+ send_queue_.push(request);
+
+ // |writeAsync| accepts buffers of max. mtu bytes per call, so we need to emit
+ // multiple write operations if buffer_size > mtu.
+ uint16_t mtu = channel_->GetOutgoingMTU();
+ scoped_refptr<net::DrainableIOBuffer> send_buffer(
+ new net::DrainableIOBuffer(buffer, buffer_size));
+ while (send_buffer->BytesRemaining() > 0) {
+ int byte_count = send_buffer->BytesRemaining();
+ if (byte_count > mtu)
+ byte_count = mtu;
+ IOReturn status =
+ channel_->WriteAsync(send_buffer->data(), byte_count, request.get());
+
+ if (status != kIOReturnSuccess) {
+ std::stringstream error;
+ error << "Failed to connect bluetooth socket ("
+ << channel_->GetDeviceAddress() << "): (" << status << ")";
+ // Remember the first error only
+ if (request->status == kIOReturnSuccess)
+ request->status = status;
+ request->error_signaled = true;
+ request->error_callback.Run(error.str());
+ // We may have failed to issue any write operation. In that case, there
+ // will be no corresponding completion callback for this particular
+ // request, so we must forget about it now.
+ if (request->active_async_writes == 0) {
+ send_queue_.pop();
+ }
+ return;
+ }
+
+ request->active_async_writes++;
+ send_buffer->DidConsume(byte_count);
+ }
+}
+
+void BluetoothSocketMac::OnChannelWriteComplete(void* refcon, IOReturn status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Note: We use "CHECK" below to ensure we never run into unforeseen
+ // occurrences of asynchronous callbacks, which could lead to data
+ // corruption.
+ CHECK_EQ(static_cast<SendRequest*>(refcon), send_queue_.front().get());
+
+ // Keep a local linked_ptr to avoid releasing the request too early if we end
+ // up removing it from the queue.
+ linked_ptr<SendRequest> request = send_queue_.front();
+
+ // Remember the first error only
if (status != kIOReturnSuccess) {
- error_message_ = base::StringPrintf(
- "Failed to send data. IOReturn code: %u", status);
- return false;
+ if (request->status == kIOReturnSuccess)
+ request->status = status;
}
- buffer->DidConsume(bytes_written);
- return true;
+ // Figure out if we are done with this async request
+ request->active_async_writes--;
+ if (request->active_async_writes > 0)
+ return;
+
+ // If this was the last active async write for this request, remove it from
+ // the queue and call the appropriate callback associated to the request.
+ send_queue_.pop();
+ if (request->status != kIOReturnSuccess) {
+ if (!request->error_signaled) {
+ std::stringstream error;
+ error << "Failed to connect bluetooth socket ("
+ << channel_->GetDeviceAddress() << "): (" << status << ")";
+ request->error_signaled = true;
+ request->error_callback.Run(error.str());
+ }
+ } else {
+ request->success_callback.Run(request->buffer_size);
+ }
}
-std::string BluetoothSocketMac::GetLastErrorMessage() const {
- return error_message_;
+void BluetoothSocketMac::OnChannelClosed() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (receive_callbacks_) {
+ scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass();
+ temp->error_callback.Run(BluetoothSocket::kDisconnected,
+ kSocketNotConnected);
+ }
+
+ ReleaseChannel();
}
-void BluetoothSocketMac::OnDataReceived(
- IOBluetoothRFCOMMChannel* rfcomm_channel, void* data, size_t length) {
- DCHECK(rfcomm_channel_ == rfcomm_channel);
- CHECK_LT(length, static_cast<size_t>(std::numeric_limits<int>::max()));
- int data_size = static_cast<int>(length);
- if (incoming_data_buffer_->RemainingCapacity() < data_size) {
- int additional_capacity =
- std::max(data_size, incoming_data_buffer_->capacity());
- CHECK_LT(
- additional_capacity,
- std::numeric_limits<int>::max() - incoming_data_buffer_->capacity());
- incoming_data_buffer_->SetCapacity(
- incoming_data_buffer_->capacity() + additional_capacity);
+void BluetoothSocketMac::Accept(
+ const AcceptCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Allow only one pending accept at a time.
+ if (accept_request_) {
+ error_callback.Run(net::ErrorToString(net::ERR_IO_PENDING));
+ return;
}
- memcpy(incoming_data_buffer_->data(), data, data_size);
- incoming_data_buffer_->set_offset(
- incoming_data_buffer_->offset() + data_size);
+
+ accept_request_.reset(new AcceptRequest);
+ accept_request_->success_callback = success_callback;
+ accept_request_->error_callback = error_callback;
+
+ if (accept_queue_.size() >= 1)
+ AcceptConnectionRequest();
}
-void BluetoothSocketMac::ResetIncomingDataBuffer() {
- incoming_data_buffer_ = new net::GrowableIOBuffer();
- incoming_data_buffer_->SetCapacity(1024);
+void BluetoothSocketMac::AcceptConnectionRequest() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << uuid_.canonical_value() << ": Accepting pending connection.";
+
+ linked_ptr<BluetoothChannelMac> channel = accept_queue_.front();
+ accept_queue_.pop();
+
+ adapter_->DeviceConnected(channel->GetDevice());
+ BluetoothDevice* device = adapter_->GetDevice(channel->GetDeviceAddress());
+ DCHECK(device);
+
+ scoped_refptr<BluetoothSocketMac> client_socket =
+ BluetoothSocketMac::CreateSocket();
+
+ client_socket->uuid_ = uuid_;
+ client_socket->channel_.reset(channel.release());
+
+ // Associating the socket can synchronously call into OnChannelOpenComplete().
+ // Make sure to first set the new socket to be connecting and hook it up to
+ // run the accept callback with the device object.
+ client_socket->connect_callbacks_.reset(new ConnectCallbacks());
+ client_socket->connect_callbacks_->success_callback =
+ base::Bind(accept_request_->success_callback, device, client_socket);
+ client_socket->connect_callbacks_->error_callback =
+ accept_request_->error_callback;
+ accept_request_.reset();
+
+ // Now it's safe to associate the socket with the channel.
+ client_socket->channel_->SetSocket(client_socket.get());
+
+ DVLOG(1) << uuid_.canonical_value() << ": Accept complete.";
+}
+
+BluetoothSocketMac::AcceptRequest::AcceptRequest() {}
+
+BluetoothSocketMac::AcceptRequest::~AcceptRequest() {}
+
+BluetoothSocketMac::SendRequest::SendRequest()
+ : status(kIOReturnSuccess), active_async_writes(0), error_signaled(false) {}
+
+BluetoothSocketMac::SendRequest::~SendRequest() {}
+
+BluetoothSocketMac::ReceiveCallbacks::ReceiveCallbacks() {}
+
+BluetoothSocketMac::ReceiveCallbacks::~ReceiveCallbacks() {}
+
+BluetoothSocketMac::ConnectCallbacks::ConnectCallbacks() {}
+
+BluetoothSocketMac::ConnectCallbacks::~ConnectCallbacks() {}
+
+BluetoothSocketMac::BluetoothSocketMac()
+ : service_record_handle_(kInvalidServiceRecordHandle) {
+}
+
+BluetoothSocketMac::~BluetoothSocketMac() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!channel_);
+ DCHECK(!rfcomm_connection_listener_);
+}
+
+void BluetoothSocketMac::ReleaseChannel() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ channel_.reset();
+
+ // Closing the channel above prevents the callback delegate from being called
+ // so it is now safe to release all callback state.
+ connect_callbacks_.reset();
+ receive_callbacks_.reset();
+ empty_queue(receive_queue_);
+ empty_queue(send_queue_);
+}
+
+void BluetoothSocketMac::ReleaseListener() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(service_record_handle_, kInvalidServiceRecordHandle);
+
+ IOBluetoothRemoveServiceWithRecordHandle(service_record_handle_);
+ rfcomm_connection_listener_.reset();
+ l2cap_connection_listener_.reset();
+
+ // Destroying the listener above prevents the callback delegate from being
+ // called so it is now safe to release all callback state.
+ accept_request_.reset();
+ empty_queue(accept_queue_);
}
} // namespace device