blob: cdbc8dd4c69f3793e542c6491ca573f212b9ab1f [file] [log] [blame]
// 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_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "device/bluetooth/bluetooth_device_mac.h"
#include "device/bluetooth/bluetooth_service_record.h"
#include "device/bluetooth/bluetooth_service_record_mac.h"
#include "net/base/io_buffer.h"
@interface BluetoothRFCOMMChannelDelegate
: NSObject <IOBluetoothRFCOMMChannelDelegate> {
@private
device::BluetoothSocketMac* socket_; // weak
}
- (id)initWithSocket:(device::BluetoothSocketMac*)socket;
@end
@implementation BluetoothRFCOMMChannelDelegate
- (id)initWithSocket:(device::BluetoothSocketMac*)socket {
if ((self = [super init]))
socket_ = socket;
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_->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::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
void BluetoothSocketMac::Connect(
IOBluetoothSDPServiceRecord* record,
const ConnectSuccessCallback& success_callback,
const ErrorCompletionCallback& error_callback) {
scoped_refptr<BluetoothSocketMac> socket(new BluetoothSocketMac());
socket->ConnectImpl(record, success_callback, error_callback);
}
// static
void BluetoothSocketMac::AcceptConnection(
IOBluetoothRFCOMMChannel* rfcomm_channel,
const ConnectSuccessCallback& success_callback,
const ErrorCompletionCallback& error_callback) {
scoped_refptr<BluetoothSocketMac> socket(new BluetoothSocketMac());
socket->AcceptConnectionImpl(
rfcomm_channel, success_callback, error_callback);
}
BluetoothSocketMac::BluetoothSocketMac()
: delegate_([[BluetoothRFCOMMChannelDelegate alloc] initWithSocket:this]) {
}
BluetoothSocketMac::~BluetoothSocketMac() {
DCHECK(thread_checker_.CalledOnValidThread());
ReleaseChannel();
}
void BluetoothSocketMac::ReleaseChannel() {
DCHECK(thread_checker_.CalledOnValidThread());
if (rfcomm_channel_) {
[rfcomm_channel_ setDelegate:nil];
[rfcomm_channel_ closeChannel];
rfcomm_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::ConnectImpl(
IOBluetoothSDPServiceRecord* record,
const ConnectSuccessCallback& success_callback,
const ErrorCompletionCallback& error_callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (connecting()) {
error_callback.Run(kSocketConnecting);
return;
}
if (rfcomm_channel_) {
error_callback.Run(kSocketAlreadyConnected);
return;
}
uint8 rfcomm_channel_id;
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 << "Failed to connect bluetooth socket ("
<< BluetoothDeviceMac::GetDeviceAddress(device) << "): (" << status
<< ")";
error_callback.Run(error.str());
return;
}
AcceptConnectionImpl(rfcomm_channel, success_callback, error_callback);
}
void BluetoothSocketMac::AcceptConnectionImpl(
IOBluetoothRFCOMMChannel* rfcomm_channel,
const ConnectSuccessCallback& success_callback,
const ErrorCompletionCallback& error_callback) {
connect_callbacks_.reset(new ConnectCallbacks());
connect_callbacks_->success_callback =
base::Bind(success_callback, scoped_refptr<BluetoothSocketMac>(this));
connect_callbacks_->error_callback = error_callback;
// Note: It's important to set the connect callbacks *prior* to setting the
// delegate, as setting the delegate can synchronously call into
// OnChannelOpened().
rfcomm_channel_.reset([rfcomm_channel retain]);
[rfcomm_channel_ setDelegate:delegate_];
}
void BluetoothSocketMac::OnChannelOpened(
IOBluetoothRFCOMMChannel* rfcomm_channel,
IOReturn status) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(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 ("
<< BluetoothDeviceMac::GetDeviceAddress([rfcomm_channel_ getDevice])
<< "): (" << status << ")";
temp->error_callback.Run(error.str());
return;
}
temp->success_callback.Run();
}
void BluetoothSocketMac::Close() {
DCHECK(thread_checker_.CalledOnValidThread());
ReleaseChannel();
}
void BluetoothSocketMac::Disconnect(const base::Closure& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
ReleaseChannel();
callback.Run();
}
void BluetoothSocketMac::Receive(
int /* buffer_size */,
const ReceiveCompletionCallback& success_callback,
const ReceiveErrorCompletionCallback& error_callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (connecting()) {
error_callback.Run(BluetoothSocket::kSystemError, kSocketConnecting);
return;
}
if (!rfcomm_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(
IOBluetoothRFCOMMChannel* rfcomm_channel,
void* data,
size_t length) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(rfcomm_channel_, rfcomm_channel);
DCHECK(!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 (connecting()) {
error_callback.Run(kSocketConnecting);
return;
}
if (!rfcomm_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.
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 ("
<< BluetoothDeviceMac::GetDeviceAddress([rfcomm_channel_ getDevice])
<< "): (" << 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(
IOBluetoothRFCOMMChannel* rfcomm_channel,
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(rfcomm_channel_, rfcomm_channel);
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 ("
<< BluetoothDeviceMac::GetDeviceAddress([rfcomm_channel_ getDevice])
<< "): (" << status << ")";
request->error_signaled = true;
request->error_callback.Run(error.str());
}
} else {
request->success_callback.Run(request->buffer_size);
}
}
void BluetoothSocketMac::OnChannelClosed(
IOBluetoothRFCOMMChannel* rfcomm_channel) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(rfcomm_channel_, rfcomm_channel);
if (receive_callbacks_) {
scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass();
temp->error_callback.Run(BluetoothSocket::kDisconnected,
kSocketNotConnected);
}
ReleaseChannel();
}
} // namespace device