// 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