Index: device/bluetooth/bluetooth_socket_mac.mm |
diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm |
index b4a88c02c4004319cbc0991229731cc4d9f93085..cee05a53f48a8b38067f7d7a7d27e632422dca57 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 |