| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "device/bluetooth/bluetooth_socket_mac.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 "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) |
| - (IOReturn)performSDPQuery:(id)target uuids:(NSArray*)uuids; |
| @end |
| |
| #endif // MAC_OS_X_VERSION_10_7 |
| |
| 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 |
| psm:(BluetoothL2CAPPSM)psm; |
| - (void)l2capChannelOpened:(IOBluetoothUserNotification*)notification |
| channel:(IOBluetoothL2CAPChannel*)l2capChannel; |
| |
| @end |
| |
| @implementation BluetoothL2capConnectionListener |
| |
| - (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)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 { |
| |
| // 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); |
| } |
| |
| // 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()]; |
| } |
| |
| // 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; |
| } |
| |
| 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<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, ®istered_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, ®istered_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; |
| } |
| } |
| |
| 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()); |
| |
| if (channel_) |
| ReleaseChannel(); |
| else if (service_record_handle_ != kInvalidServiceRecordHandle) |
| ReleaseListener(); |
| } |
| |
| void BluetoothSocketMac::Disconnect(const base::Closure& callback) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| Close(); |
| callback.Run(); |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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) { |
| if (request->status == kIOReturnSuccess) |
| request->status = status; |
| } |
| |
| // 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); |
| } |
| } |
| |
| 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::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; |
| } |
| |
| 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::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 |