diff options
author | rpaquay@chromium.org <rpaquay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-10 17:09:55 +0000 |
---|---|---|
committer | rpaquay@chromium.org <rpaquay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-10 17:09:55 +0000 |
commit | 3b0175620040e14c736c31488098afbb2933f93c (patch) | |
tree | d0256e72bf8caf657899e1e0570dffcc81382bb5 /device | |
parent | 0e343edaec4e71ce240e72229d38f362277f97e1 (diff) | |
download | chromium_src-3b0175620040e14c736c31488098afbb2933f93c.zip chromium_src-3b0175620040e14c736c31488098afbb2933f93c.tar.gz chromium_src-3b0175620040e14c736c31488098afbb2933f93c.tar.bz2 |
MacOS implementation of BluetoothSocket.
BUG=343725,343651
Review URL: https://codereview.chromium.org/229463003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@263018 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'device')
-rw-r--r-- | device/bluetooth/bluetooth_adapter_mac.mm | 5 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_device_mac.h | 10 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_device_mac.mm | 22 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_profile_mac.h | 17 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_profile_mac.mm | 112 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_socket_mac.h | 97 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_socket_mac.mm | 413 |
7 files changed, 527 insertions, 149 deletions
diff --git a/device/bluetooth/bluetooth_adapter_mac.mm b/device/bluetooth/bluetooth_adapter_mac.mm index 9ac7caa..f8eba23 100644 --- a/device/bluetooth/bluetooth_adapter_mac.mm +++ b/device/bluetooth/bluetooth_adapter_mac.mm @@ -265,7 +265,7 @@ void BluetoothAdapterMac::UpdateDevices(NSArray* devices) { for (IOBluetoothDevice* device in devices) { std::string device_address = base::SysNSStringToUTF8([device addressString]); - devices_[device_address] = new BluetoothDeviceMac(device); + devices_[device_address] = new BluetoothDeviceMac(ui_task_runner_, device); } } @@ -290,7 +290,8 @@ void BluetoothAdapterMac::DeviceFound(IOBluetoothDeviceInquiry* inquiry, DCHECK(device_inquiry_ == inquiry); std::string device_address = base::SysNSStringToUTF8([device addressString]); if (discovered_devices_.find(device_address) == discovered_devices_.end()) { - scoped_ptr<BluetoothDeviceMac> device_mac(new BluetoothDeviceMac(device)); + scoped_ptr<BluetoothDeviceMac> device_mac( + new BluetoothDeviceMac(ui_task_runner_, device)); FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, DeviceAdded(this, device_mac.get())); discovered_devices_.insert(device_address); diff --git a/device/bluetooth/bluetooth_device_mac.h b/device/bluetooth/bluetooth_device_mac.h index e0ed024..e7bec55 100644 --- a/device/bluetooth/bluetooth_device_mac.h +++ b/device/bluetooth/bluetooth_device_mac.h @@ -17,11 +17,17 @@ class IOBluetoothDevice; #endif +namespace base { +class SequencedTaskRunner; +} // namespace base + namespace device { class BluetoothDeviceMac : public BluetoothDevice { public: - explicit BluetoothDeviceMac(IOBluetoothDevice* device); + explicit BluetoothDeviceMac( + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, + IOBluetoothDevice* device); virtual ~BluetoothDeviceMac(); // BluetoothDevice override @@ -81,6 +87,8 @@ class BluetoothDeviceMac : public BluetoothDevice { // List of observers interested in event notifications from us. ObserverList<Observer> observers_; + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + // (retained) IOBluetoothDevice* device_; DISALLOW_COPY_AND_ASSIGN(BluetoothDeviceMac); diff --git a/device/bluetooth/bluetooth_device_mac.mm b/device/bluetooth/bluetooth_device_mac.mm index bd25449..bd54495 100644 --- a/device/bluetooth/bluetooth_device_mac.mm +++ b/device/bluetooth/bluetooth_device_mac.mm @@ -13,6 +13,7 @@ #include "base/basictypes.h" #include "base/hash.h" +#include "base/sequenced_task_runner.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" @@ -36,8 +37,6 @@ namespace { -const char kFailedToConnect[] = "Connection failed"; - // Converts |uuid| to a IOBluetoothSDPUUID instance. // // |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. @@ -64,9 +63,12 @@ IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) { namespace device { -BluetoothDeviceMac::BluetoothDeviceMac(IOBluetoothDevice* device) - : BluetoothDevice(), device_([device retain]) { -} +BluetoothDeviceMac::BluetoothDeviceMac( + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, + IOBluetoothDevice* device) + : BluetoothDevice(), + ui_task_runner_(ui_task_runner), + device_([device retain]) {} BluetoothDeviceMac::~BluetoothDeviceMac() { [device_ release]; @@ -199,9 +201,8 @@ void BluetoothDeviceMac::ConnectToService( [device_ getServiceRecordForUUID:GetIOBluetoothSDPUUID( service_uuid.canonical_value())]; if (record != nil) { - BluetoothServiceRecordMac service_record(record); scoped_refptr<BluetoothSocket> socket( - BluetoothSocketMac::CreateBluetoothSocket(service_record)); + BluetoothSocketMac::CreateBluetoothSocket(ui_task_runner_, record)); if (socket.get() != NULL) callback.Run(socket); } @@ -211,10 +212,9 @@ void BluetoothDeviceMac::ConnectToProfile( BluetoothProfile* profile, const base::Closure& callback, const ConnectToProfileErrorCallback& error_callback) { - if (static_cast<BluetoothProfileMac*>(profile)->Connect(device_)) - callback.Run(); - else - error_callback.Run(kFailedToConnect); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + static_cast<BluetoothProfileMac*>(profile) + ->Connect(ui_task_runner_, device_, callback, error_callback); } void BluetoothDeviceMac::SetOutOfBandPairingData( diff --git a/device/bluetooth/bluetooth_profile_mac.h b/device/bluetooth/bluetooth_profile_mac.h index bad10ce..673abeb 100644 --- a/device/bluetooth/bluetooth_profile_mac.h +++ b/device/bluetooth/bluetooth_profile_mac.h @@ -18,6 +18,10 @@ class IOBluetoothDevice; #endif +namespace base { +class SequencedTaskRunner; +} // namespace base + namespace device { class BluetoothProfileMac : public BluetoothProfile { @@ -27,10 +31,15 @@ class BluetoothProfileMac : public BluetoothProfile { virtual void SetConnectionCallback( const ConnectionCallback& callback) OVERRIDE; - // Makes an outgoing connection to |device|. - // This method runs |socket_callback_| with the socket and returns true if the - // connection is made successfully. - bool Connect(IOBluetoothDevice* device); + typedef base::Callback<void(const std::string&)> ErrorCallback; + + // Makes an outgoing connection to |device|, calling |callback| on succes or + // |error_callback| on error. If successful, this method also calls + // |connection_callback_| before calling |callback|. + void Connect(const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, + IOBluetoothDevice* device, + const base::Closure& callback, + const ErrorCallback& error_callback); private: friend BluetoothProfile; diff --git a/device/bluetooth/bluetooth_profile_mac.mm b/device/bluetooth/bluetooth_profile_mac.mm index 54781e4..b666877 100644 --- a/device/bluetooth/bluetooth_profile_mac.mm +++ b/device/bluetooth/bluetooth_profile_mac.mm @@ -12,14 +12,32 @@ #include <vector> #include "base/basictypes.h" +#include "base/bind.h" +#include "base/location.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/sys_string_conversions.h" +#include "base/thread_task_runner_handle.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_device_mac.h" #include "device/bluetooth/bluetooth_socket_mac.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; +@end + +#endif // MAC_OS_X_VERSION_10_7 + namespace { +const char kNoConnectionCallback[] = "Connection callback not set"; +const char kProfileNotFound[] = "Profile not found"; + // Converts |uuid| to a IOBluetoothSDPUUID instance. // // |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. @@ -42,6 +60,54 @@ IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) { length:uuid_bytes_vector.size()]; } +void OnSocketConnectUI( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<device::BluetoothSocketMac> socket, + const base::Closure& success_callback, + const device::BluetoothProfileMac::ErrorCallback& error_callback) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + socket->Connect(success_callback, error_callback); +} + +void OnConnectSuccessUIWithAdapter( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + const base::Closure& callback, + const device::BluetoothProfileMac::ConnectionCallback& connection_callback, + const std::string& device_address, + scoped_refptr<device::BluetoothSocketMac> socket, + scoped_refptr<device::BluetoothAdapter> adapter) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + const device::BluetoothDevice* device = adapter->GetDevice(device_address); + if (device) { + connection_callback.Run(device, socket); + callback.Run(); + } +} + +void OnConnectSuccessUI( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + const base::Closure& callback, + const device::BluetoothProfileMac::ConnectionCallback& connection_callback, + const std::string& device_address, + scoped_refptr<device::BluetoothSocketMac> socket) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + device::BluetoothAdapterFactory::GetAdapter( + base::Bind(&OnConnectSuccessUIWithAdapter, + ui_task_runner, + callback, + connection_callback, + device_address, + socket)); +} + +void OnConnectErrorUI( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + const device::BluetoothProfileMac::ErrorCallback& error_callback, + const std::string& error) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + error_callback.Run(error); +} + } // namespace namespace device { @@ -63,23 +129,37 @@ void BluetoothProfileMac::SetConnectionCallback( connection_callback_ = callback; } -bool BluetoothProfileMac::Connect(IOBluetoothDevice* device) { - if (connection_callback_.is_null()) - return false; - - IOBluetoothSDPServiceRecord* record = - [device getServiceRecordForUUID:GetIOBluetoothSDPUUID( - uuid_.canonical_value())]; - if (record != nil) { - scoped_refptr<BluetoothSocket> socket( - BluetoothSocketMac::CreateBluetoothSocket(record)); - if (socket.get() != NULL) { - BluetoothDeviceMac device_mac(device); - connection_callback_.Run(&device_mac, socket); - return true; - } +void BluetoothProfileMac::Connect( + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, + IOBluetoothDevice* device, + const base::Closure& success_callback, + const ErrorCallback& error_callback) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + if (connection_callback_.is_null()) { + error_callback.Run(kNoConnectionCallback); + return; + } + + IOBluetoothSDPServiceRecord* record = [device + getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_.canonical_value())]; + if (record == nil) { + error_callback.Run(kProfileNotFound); + return; } - return false; + + std::string device_address = base::SysNSStringToUTF8([device addressString]); + scoped_refptr<BluetoothSocketMac> socket( + BluetoothSocketMac::CreateBluetoothSocket(ui_task_runner, record)); + OnSocketConnectUI( + ui_task_runner, + socket, + base::Bind(OnConnectSuccessUI, + ui_task_runner, + success_callback, + connection_callback_, + device_address, + socket), + base::Bind(OnConnectErrorUI, ui_task_runner, error_callback)); } } // namespace device diff --git a/device/bluetooth/bluetooth_socket_mac.h b/device/bluetooth/bluetooth_socket_mac.h index 806eceb..76e197d 100644 --- a/device/bluetooth/bluetooth_socket_mac.h +++ b/device/bluetooth/bluetooth_socket_mac.h @@ -5,9 +5,15 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_MAC_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_MAC_H_ +#include <queue> #include <string> +#include <IOKit/IOreturn.h> + +#include "base/memory/linked_ptr.h" #include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread_checker.h" #include "device/bluetooth/bluetooth_socket.h" #ifdef __OBJC__ @@ -21,8 +27,8 @@ class IOBluetoothSDPServiceRecord; #endif namespace net { -class GrowableIOBuffer; class IOBuffer; +class IOBufferWithSize; } // namespace net namespace device { @@ -30,16 +36,20 @@ namespace device { class BluetoothServiceRecord; // This class is an implementation of BluetoothSocket class for OSX platform. +// All methods of this class must all be called on the UI thread, as per Chrome +// guidelines on performing Async IO on UI thread on MacOS. class BluetoothSocketMac : public BluetoothSocket { public: - // TODO(youngki): This method is deprecated; remove this method when - // BluetoothServiceRecord is removed. - static scoped_refptr<BluetoothSocket> CreateBluetoothSocket( - const BluetoothServiceRecord& service_record); - - static scoped_refptr<BluetoothSocket> CreateBluetoothSocket( + static scoped_refptr<BluetoothSocketMac> CreateBluetoothSocket( + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, IOBluetoothSDPServiceRecord* record); + // Connect to the peer device and calls |success_callback| when the + // connection has been established successfully. If an error occurs, calls + // |error_callback| with a system error message. + void Connect(const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + // Overriden from BluetoothSocket: virtual void Close() OVERRIDE; virtual void Disconnect(const base::Closure& callback) OVERRIDE; @@ -53,22 +63,77 @@ class BluetoothSocketMac : public BluetoothSocket { const ErrorCompletionCallback& error_callback) OVERRIDE; // called by BluetoothRFCOMMChannelDelegate. - void OnDataReceived(IOBluetoothRFCOMMChannel* rfcomm_channel, - void* data, - size_t length); + void OnChannelOpened(IOBluetoothRFCOMMChannel* rfcomm_channel, + IOReturn status); + + // called by BluetoothRFCOMMChannelDelegate. + void OnChannelClosed(IOBluetoothRFCOMMChannel* rfcomm_channel); + + // called by BluetoothRFCOMMChannelDelegate. + void OnChannelDataReceived(IOBluetoothRFCOMMChannel* rfcomm_channel, + void* data, + size_t length); + + // called by BluetoothRFCOMMChannelDelegate. + void OnChannelWriteComplete(IOBluetoothRFCOMMChannel* rfcomm_channel, + void* refcon, + IOReturn status); protected: virtual ~BluetoothSocketMac(); private: - explicit BluetoothSocketMac(IOBluetoothRFCOMMChannel* rfcomm_channel); - - void ResetIncomingDataBuffer(); + BluetoothSocketMac( + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, + IOBluetoothSDPServiceRecord* record); - IOBluetoothRFCOMMChannel* rfcomm_channel_; + struct SendRequest { + SendRequest(); + ~SendRequest(); + int buffer_size; + SendCompletionCallback success_callback; + ErrorCompletionCallback error_callback; + IOReturn status; + int active_async_writes; + bool error_signaled; + }; + + struct ReceiveCallbacks { + ReceiveCallbacks(); + ~ReceiveCallbacks(); + ReceiveCompletionCallback success_callback; + ReceiveErrorCompletionCallback error_callback; + }; + + struct ConnectCallbacks { + ConnectCallbacks(); + ~ConnectCallbacks(); + base::Closure success_callback; + ErrorCompletionCallback error_callback; + }; + + void ReleaseChannel(); + + bool connecting() const { return connect_callbacks_; } + + // Used to verify all methods are called on the same thread. + base::ThreadChecker thread_checker_; + // Task Runner for the UI thread, used to post tasks. + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + // (retained) The Bluetooth Service definition. + IOBluetoothSDPServiceRecord* record_; + // (weak) The RFCOMM channel delegate. Released when the channel is closed. BluetoothRFCOMMChannelDelegate* delegate_; - scoped_refptr<net::GrowableIOBuffer> incoming_data_buffer_; - std::string error_message_; + // (retained) The IOBluetooth RFCOMM channel used to issue commands. + IOBluetoothRFCOMMChannel* rfcomm_channel_; + // Connection callbacks -- when a pending async connection is active. + scoped_ptr<ConnectCallbacks> connect_callbacks_; + // Packets received while there is no pending "receive" callback. + std::queue<scoped_refptr<net::IOBufferWithSize> > receive_queue_; + // Receive callbacks -- when a receive call is active. + scoped_ptr<ReceiveCallbacks> receive_callbacks_; + // Send queue -- one entry per pending send operation. + std::queue<linked_ptr<SendRequest> > send_queue_; DISALLOW_COPY_AND_ASSIGN(BluetoothSocketMac); }; diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm index b4a88c0..cee05a5 100644 --- a/device/bluetooth/bluetooth_socket_mac.mm +++ b/device/bluetooth/bluetooth_socket_mac.mm @@ -9,12 +9,15 @@ #import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> #include <limits> +#include <sstream> #include <string> #include "base/basictypes.h" +#include "base/callback_helpers.h" #include "base/memory/ref_counted.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" +#include "base/threading/thread_restrictions.h" #include "device/bluetooth/bluetooth_service_record.h" #include "device/bluetooth/bluetooth_service_record_mac.h" #include "net/base/io_buffer.h" @@ -48,156 +51,368 @@ return self; } +- (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel + status:(IOReturn)error { + socket_->OnChannelOpened(rfcommChannel, error); +} + +- (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel + refcon:(void*)refcon + status:(IOReturn)error { + socket_->OnChannelWriteComplete(rfcommChannel, refcon, error); +} + - (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel data:(void*)dataPointer length:(size_t)dataLength { - socket_->OnDataReceived(rfcommChannel, dataPointer, dataLength); + socket_->OnChannelDataReceived(rfcommChannel, dataPointer, dataLength); +} + +- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel { + socket_->OnChannelClosed(rfcommChannel); } @end +namespace { + +const char kL2CAPNotSupported[] = "Bluetooth L2CAP protocol is not supported"; +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); +} + +} // namespace + namespace device { -BluetoothSocketMac::BluetoothSocketMac(IOBluetoothRFCOMMChannel* rfcomm_channel) - : rfcomm_channel_(rfcomm_channel), - delegate_([[BluetoothRFCOMMChannelDelegate alloc] initWithSocket:this]) { - [rfcomm_channel_ setDelegate:delegate_]; - ResetIncomingDataBuffer(); +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() {} + +// static +scoped_refptr<BluetoothSocketMac> BluetoothSocketMac::CreateBluetoothSocket( + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, + IOBluetoothSDPServiceRecord* record) { + return new BluetoothSocketMac(ui_task_runner, record); +} + +BluetoothSocketMac::BluetoothSocketMac( + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner, + IOBluetoothSDPServiceRecord* record) + : ui_task_runner_(ui_task_runner), + record_(record), + delegate_([[BluetoothRFCOMMChannelDelegate alloc] initWithSocket:this]), + rfcomm_channel_(nil) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + [record_ retain]; } BluetoothSocketMac::~BluetoothSocketMac() { - [rfcomm_channel_ setDelegate:nil]; - [rfcomm_channel_ closeChannel]; - [rfcomm_channel_ release]; + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + ReleaseChannel(); [delegate_ release]; + [record_ release]; } -// 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 << ")"; - } +void BluetoothSocketMac::ReleaseChannel() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + if (rfcomm_channel_ != nil) { + [rfcomm_channel_ setDelegate:nil]; + [rfcomm_channel_ closeChannel]; + [rfcomm_channel_ release]; + rfcomm_channel_ = nil; } - // TODO(youngki): add support for L2CAP sockets as well. - return scoped_refptr<BluetoothSocketMac>(bluetooth_socket); + // Closing the channel above prevents the callback delegate from being called + // so it is now safe to release all callback state. + connect_callbacks_.reset(NULL); + receive_callbacks_.reset(NULL); + empty_queue(receive_queue_); + empty_queue(send_queue_); } -// static -scoped_refptr<BluetoothSocket> BluetoothSocketMac::CreateBluetoothSocket( - IOBluetoothSDPServiceRecord* record) { - BluetoothSocketMac* bluetooth_socket = NULL; +void BluetoothSocketMac::Connect( + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + if (connecting()) { + error_callback.Run(kSocketConnecting); + return; + } + + if (rfcomm_channel_ != nil) { + error_callback.Run(kSocketAlreadyConnected); + return; + } + 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 (" + IOReturn status = [record_ getRFCOMMChannelID:&rfcomm_channel_id]; + if (status != kIOReturnSuccess) { + // TODO(youngki) add support for L2CAP sockets as well. + error_callback.Run(kL2CAPNotSupported); + return; + } + + IOBluetoothDevice* device = [record_ device]; + IOBluetoothRFCOMMChannel* rfcomm_channel; + status = [device openRFCOMMChannelAsync:&rfcomm_channel + withChannelID:rfcomm_channel_id + delegate:delegate_]; + if (status != kIOReturnSuccess) { + std::stringstream error; + error << std::string("Failed to connect bluetooth socket (") << base::SysNSStringToUTF8([device addressString]) << "): (" << status - << ")"; - } + << std::string(")"); + error_callback.Run(error.str()); + return; } - // TODO(youngki): Add support for L2CAP sockets as well. + connect_callbacks_.reset(new ConnectCallbacks()); + connect_callbacks_->success_callback = success_callback; + connect_callbacks_->error_callback = error_callback; + rfcomm_channel_ = rfcomm_channel; + [rfcomm_channel_ setDelegate:delegate_]; +} + +void BluetoothSocketMac::OnChannelOpened( + IOBluetoothRFCOMMChannel* rfcomm_channel, + IOReturn status) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(rfcomm_channel_ == rfcomm_channel); + DCHECK(connecting()); + + scoped_ptr<ConnectCallbacks> temp = connect_callbacks_.Pass(); + if (status != kIOReturnSuccess) { + ReleaseChannel(); + std::stringstream error; + error << "Failed to connect bluetooth socket (" + << base::SysNSStringToUTF8([[record_ device] addressString]) << "): (" + << status << ")"; + temp->error_callback.Run(error.str()); + return; + } - return scoped_refptr<BluetoothSocketMac>(bluetooth_socket); + temp->success_callback.Run(); } -void BluetoothSocketMac::Close() { NOTIMPLEMENTED(); } +void BluetoothSocketMac::Close() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + ReleaseChannel(); +} void BluetoothSocketMac::Disconnect(const base::Closure& callback) { - NOTIMPLEMENTED(); + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + ReleaseChannel(); + callback.Run(); } void BluetoothSocketMac::Receive( int count, const ReceiveCompletionCallback& success_callback, const ReceiveErrorCompletionCallback& error_callback) { - NOTIMPLEMENTED(); + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + if (connecting()) { + error_callback.Run(BluetoothSocket::kSystemError, kSocketConnecting); + return; + } + + if (rfcomm_channel_ == nil) { + 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( + IOBluetoothRFCOMMChannel* rfcomm_channel, + void* data, + size_t length) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(rfcomm_channel_ == rfcomm_channel); + DCHECK(!connecting()); + CHECK_LT(length, static_cast<size_t>(std::numeric_limits<int>::max())); + + int data_size = static_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) { - NOTIMPLEMENTED(); -} + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + if (connecting()) { + error_callback.Run(kSocketConnecting); + return; + } -#if 0 -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); + if (rfcomm_channel_ == nil) { + error_callback.Run(kSocketNotConnected); + return; + } - ResetIncomingDataBuffer(); + // 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. + BluetoothRFCOMMMTU mtu = [rfcomm_channel_ getMTU]; + 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 = [rfcomm_channel_ writeAsync:send_buffer->data() + length:byte_count + refcon:request.get()]; + if (status != kIOReturnSuccess) { + std::stringstream error; + error << "Failed to connect bluetooth socket (" + << base::SysNSStringToUTF8([[record_ device] addressString]) + << "): (" << 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); } - return true; } -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::OnChannelWriteComplete( + IOBluetoothRFCOMMChannel* rfcomm_channel, + void* refcon, + IOReturn status) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + // Note: We use "CHECK" below to ensure we never run into unforeseen + // occurrences of asynchronous callbacks, which could lead to data + // corruption. + CHECK(rfcomm_channel_ == rfcomm_channel); + CHECK(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 (" + << base::SysNSStringToUTF8([[record_ device] addressString]) + << "): (" << 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_; -} -#endif -void BluetoothSocketMac::OnDataReceived( - IOBluetoothRFCOMMChannel* rfcomm_channel, void* data, size_t length) { +void BluetoothSocketMac::OnChannelClosed( + IOBluetoothRFCOMMChannel* rfcomm_channel) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); 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); - } - memcpy(incoming_data_buffer_->data(), data, data_size); - incoming_data_buffer_->set_offset( - incoming_data_buffer_->offset() + data_size); -} - -void BluetoothSocketMac::ResetIncomingDataBuffer() { - incoming_data_buffer_ = new net::GrowableIOBuffer(); - incoming_data_buffer_->SetCapacity(1024); + + if (receive_callbacks_) { + scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass(); + temp->error_callback.Run(BluetoothSocket::kDisconnected, + kSocketNotConnected); + } + + ReleaseChannel(); } } // namespace device |