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