// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/bluetooth/bluetooth_rfcomm_channel_mac.h"
#include <memory>
#include "base/check_op.h"
#include "base/memory/raw_ptr.h"
#include "device/bluetooth/bluetooth_classic_device_mac.h"
#include "device/bluetooth/bluetooth_socket_mac.h"
// A simple delegate class for an open RFCOMM channel that forwards methods to
// its wrapped `_channel`.
@interface BluetoothRfcommChannelDelegate
: NSObject <IOBluetoothRFCOMMChannelDelegate> {
@private
raw_ptr<device::BluetoothRfcommChannelMac> _channel; // weak
IOBluetoothRFCOMMChannel* __strong _rfcommChannel;
// While `_rfcommChannel` is open, the delegate holds a strong reference to
// itself to ensure it is not destroyed before rfcommChannelClosed is
// received. This is a workaround for a macOS bug, see Apple Feedback report
// FB13705522.
BluetoothRfcommChannelDelegate* __strong _strongSelf;
}
- (instancetype)initWithChannel:(device::BluetoothRfcommChannelMac*)channel
rfcommChannel:(IOBluetoothRFCOMMChannel*)rfcommChannel;
- (void)setRfcommChannel:(IOBluetoothRFCOMMChannel*)rfcommChannel;
@end
@implementation BluetoothRfcommChannelDelegate
- (instancetype)initWithChannel:(device::BluetoothRfcommChannelMac*)channel
rfcommChannel:(IOBluetoothRFCOMMChannel*)rfcommChannel {
if ((self = [super init])) {
_channel = channel;
_rfcommChannel = rfcommChannel;
}
return self;
}
- (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
status:(IOReturn)error {
CHECK(_rfcommChannel);
if (error == kIOReturnSuccess) {
// Keep the delegate alive until rfcommChannelClosed.
_strongSelf = self;
}
if (_channel) {
_channel->OnChannelOpenComplete(rfcommChannel, error);
}
}
- (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
refcon:(void*)refcon
status:(IOReturn)error {
if (_channel) {
_channel->OnChannelWriteComplete(rfcommChannel, refcon, error);
}
}
- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel
data:(void*)dataPointer
length:(size_t)dataLength {
if (_channel) {
_channel->OnChannelDataReceived(rfcommChannel, dataPointer, dataLength);
}
}
- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel {
[_rfcommChannel setDelegate:nil];
// If `_channel` still exists, notify it that the channel was closed so it
// can release its strong references to `rfcommChannel` and the channel
// delegate (this object). In the typical case we expect `_channel` has
// already been destroyed.
if (_channel) {
_channel->OnChannelClosed(rfcommChannel);
}
// Remove the last owning references to the channel and delegate. After
// releasing `_strongSelf` this object may be destroyed, so the only safe
// thing to do is return.
_rfcommChannel = nil;
_strongSelf = nil;
}
- (void)resetOwner {
_channel = nullptr;
}
- (void)setRfcommChannel:(IOBluetoothRFCOMMChannel*)rfcommChannel {
CHECK(!_rfcommChannel);
_rfcommChannel = rfcommChannel;
}
@end
namespace device {
BluetoothRfcommChannelMac::BluetoothRfcommChannelMac(
BluetoothSocketMac* socket,
IOBluetoothRFCOMMChannel* channel)
: channel_(channel),
delegate_(nil) {
SetSocket(socket);
}
BluetoothRfcommChannelMac::~BluetoothRfcommChannelMac() {
// If `channel_` is opened, `delegate_` and `channel_` are allowed to persist
// until the delegate is notified that the channel has been closed. Reset the
// delegate's reference to this object so the delegate will not notify us
// for events that occur after our destruction.
[delegate_ resetOwner];
[channel_ closeChannel];
}
// static
std::unique_ptr<BluetoothRfcommChannelMac> BluetoothRfcommChannelMac::OpenAsync(
BluetoothSocketMac* socket,
IOBluetoothDevice* device,
BluetoothRFCOMMChannelID channel_id,
IOReturn* status) {
DCHECK(socket);
std::unique_ptr<BluetoothRfcommChannelMac> channel(
new BluetoothRfcommChannelMac(socket, /*channel=*/nil));
DCHECK(channel->delegate_);
IOBluetoothRFCOMMChannel* rfcomm_channel;
*status = [device openRFCOMMChannelAsync:&rfcomm_channel
withChannelID:channel_id
delegate:channel->delegate_];
if (*status == kIOReturnSuccess) {
channel->channel_ = rfcomm_channel;
[channel->delegate_ setRfcommChannel:rfcomm_channel];
} else {
channel.reset();
}
return channel;
}
void BluetoothRfcommChannelMac::SetSocket(BluetoothSocketMac* socket) {
BluetoothChannelMac::SetSocket(socket);
if (!this->socket())
return;
// Now that the socket is set, it's safe to associate a delegate, which can
// call back to the socket.
DCHECK(!delegate_);
delegate_ = [[BluetoothRfcommChannelDelegate alloc] initWithChannel:this
rfcommChannel:channel_];
[channel_ setDelegate:delegate_];
}
IOBluetoothDevice* BluetoothRfcommChannelMac::GetDevice() {
return [channel_ getDevice];
}
uint16_t BluetoothRfcommChannelMac::GetOutgoingMTU() {
return [channel_ getMTU];
}
IOReturn BluetoothRfcommChannelMac::WriteAsync(void* data,
uint16_t length,
void* refcon) {
DCHECK_LE(length, GetOutgoingMTU());
return [channel_ writeAsync:data length:length refcon:refcon];
}
void BluetoothRfcommChannelMac::OnChannelOpenComplete(
IOBluetoothRFCOMMChannel* channel,
IOReturn status) {
if (channel_) {
DCHECK_EQ(channel_, channel);
} else {
// The (potentially) asynchronous connection occurred synchronously.
// Should only be reachable from OpenAsync().
DCHECK_EQ(status, kIOReturnSuccess);
}
socket()->OnChannelOpenComplete(
BluetoothClassicDeviceMac::GetDeviceAddress([channel getDevice]), status);
}
void BluetoothRfcommChannelMac::OnChannelClosed(
IOBluetoothRFCOMMChannel* channel) {
DCHECK_EQ(channel_, channel);
channel_ = nil;
[delegate_ resetOwner];
delegate_ = nil;
socket()->OnChannelClosed();
}
void BluetoothRfcommChannelMac::OnChannelDataReceived(
IOBluetoothRFCOMMChannel* channel,
void* data,
size_t length) {
DCHECK_EQ(channel_, channel);
socket()->OnChannelDataReceived(data, length);
}
void BluetoothRfcommChannelMac::OnChannelWriteComplete(
IOBluetoothRFCOMMChannel* channel,
void* refcon,
IOReturn status) {
// Note: We use "CHECK" below to ensure we never run into unforeseen
// occurrences of asynchronous callbacks, which could lead to data
// corruption.
CHECK_EQ(channel_, channel);
socket()->OnChannelWriteComplete(refcon, status);
}
} // namespace device