diff options
Diffstat (limited to 'chromium/device/bluetooth/bluetooth_socket_mac.mm')
-rw-r--r-- | chromium/device/bluetooth/bluetooth_socket_mac.mm | 968 |
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, ®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; } } - // 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 |