Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(377)

Unified Diff: device/bluetooth/bluetooth_profile_mac.mm

Issue 243963002: Implement Bluetooth server socket support for RFCOMM on Mac. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « device/bluetooth/bluetooth_profile_mac.h ('k') | device/bluetooth/bluetooth_socket_mac.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: device/bluetooth/bluetooth_profile_mac.mm
diff --git a/device/bluetooth/bluetooth_profile_mac.mm b/device/bluetooth/bluetooth_profile_mac.mm
index 8d676ecc71df16f561df052010079ae9eb90a05e..c759502c46bf8c9a91d5baaced06cece2a11c85e 100644
--- a/device/bluetooth/bluetooth_profile_mac.mm
+++ b/device/bluetooth/bluetooth_profile_mac.mm
@@ -4,16 +4,13 @@
#include "device/bluetooth/bluetooth_profile_mac.h"
-#import <IOBluetooth/objc/IOBluetoothDevice.h>
-#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h>
-#import <IOBluetooth/objc/IOBluetoothSDPUUID.h>
-
#include <vector>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
@@ -31,11 +28,17 @@
#endif // MAC_OS_X_VERSION_10_7
+namespace device {
namespace {
const char kNoConnectionCallback[] = "Connection callback not set";
const char kProfileNotFound[] = "Profile not found";
+// 0 is a safe way to represent an unregistered service, as implied by the
+// documentation at
+// https://developer.apple.com/library/mac/documentation/devicedrivers/conceptual/bluetooth/BT_Develop_BT_Apps/BT_Develop_BT_Apps.html
+const BluetoothSDPServiceRecordHandle kInvalidServiceRecordHandle = 0;
+
// Converts |uuid| to a IOBluetoothSDPUUID instance.
//
// |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.
@@ -54,29 +57,30 @@ IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) {
base::HexStringToBytes(numbers_only, &uuid_bytes_vector);
DCHECK(uuid_bytes_vector.size() == 16);
- return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector[0]
+ return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector.front()
length:uuid_bytes_vector.size()];
}
void OnConnectSuccessWithAdapter(
const base::Closure& callback,
- const device::BluetoothProfileMac::ConnectionCallback& connection_callback,
+ const BluetoothProfileMac::ConnectionCallback& connection_callback,
const std::string& device_address,
- scoped_refptr<device::BluetoothSocketMac> socket,
- scoped_refptr<device::BluetoothAdapter> adapter) {
- const device::BluetoothDevice* device = adapter->GetDevice(device_address);
+ scoped_refptr<BluetoothSocket> socket,
+ scoped_refptr<BluetoothAdapter> adapter) {
+ const BluetoothDevice* device = adapter->GetDevice(device_address);
if (device) {
connection_callback.Run(device, socket);
- callback.Run();
+ if (!callback.is_null())
+ callback.Run();
}
}
void OnConnectSuccess(
const base::Closure& callback,
- const device::BluetoothProfileMac::ConnectionCallback& connection_callback,
+ const BluetoothProfileMac::ConnectionCallback& connection_callback,
const std::string& device_address,
- scoped_refptr<device::BluetoothSocketMac> socket) {
- device::BluetoothAdapterFactory::GetAdapter(
+ scoped_refptr<BluetoothSocket> socket) {
+ BluetoothAdapterFactory::GetAdapter(
base::Bind(&OnConnectSuccessWithAdapter,
callback,
connection_callback,
@@ -84,16 +88,168 @@ void OnConnectSuccess(
socket));
}
+// Converts the given |integer| to a string.
+NSString* IntToNSString(int integer) {
+ return [[NSNumber numberWithInt:integer] stringValue];
+}
+
+// Returns a dictionary containing the Bluetooth service definition
+// corresponding to the provided |uuid| and |options|.
+// TODO(isherman): Support more of the fields defined in |options|.
+NSDictionary* BuildServiceDefinition(const BluetoothUUID& uuid,
+ const BluetoothProfile::Options& options) {
+ NSMutableDictionary* service_definition = [NSMutableDictionary dictionary];
+
+ // TODO(isherman): The service's language is currently hardcoded to English.
+ // The language should ideally be specified in the chrome.bluetooth API
+ // instead.
+ const int kEnglishLanguageBase = 100;
+ const int kServiceNameKey =
+ kEnglishLanguageBase + kBluetoothSDPAttributeIdentifierServiceName;
+ NSString* service_name = base::SysUTF8ToNSString(options.name);
+ [service_definition setObject:service_name
+ forKey:IntToNSString(kServiceNameKey)];
+
+ const int kUUIDsKey = kBluetoothSDPAttributeIdentifierServiceClassIDList;
+ NSArray* uuids = @[GetIOBluetoothSDPUUID(uuid.canonical_value())];
+ [service_definition setObject:uuids forKey:IntToNSString(kUUIDsKey)];
+
+ const int kProtocolDefinitionsKey =
+ kBluetoothSDPAttributeIdentifierProtocolDescriptorList;
+ NSArray* rfcomm_protocol_definition =
+ @[[IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM],
+ [NSNumber numberWithInt:options.channel]];
+ [service_definition setObject:@[rfcomm_protocol_definition]
+ forKey:IntToNSString(kProtocolDefinitionsKey)];
+
+ return service_definition;
+}
+
+// Registers a Bluetooth service with the specifieid |uuid| and |options| in the
+// system SDP server. Returns a handle to the registered service, or
+// |kInvalidServcieRecordHandle| if the service could not be registered.
+BluetoothSDPServiceRecordHandle RegisterService(
+ const BluetoothUUID& uuid,
+ const BluetoothProfile::Options& options) {
+ // Attempt to register the service.
+ NSDictionary* service_definition = BuildServiceDefinition(uuid, options);
+ IOBluetoothSDPServiceRecordRef service_record_ref;
+ IOReturn result =
+ IOBluetoothAddServiceDict((CFDictionaryRef)service_definition,
+ &service_record_ref);
+ if (result != kIOReturnSuccess)
+ return kInvalidServiceRecordHandle;
+ // Transfer ownership to a scoped object, to simplify memory management.
+ base::ScopedCFTypeRef<IOBluetoothSDPServiceRecordRef>
+ scoped_service_record_ref(service_record_ref);
+
+ // Extract the service record handle.
+ BluetoothSDPServiceRecordHandle service_record_handle;
+ IOBluetoothSDPServiceRecord* service_record =
+ [IOBluetoothSDPServiceRecord withSDPServiceRecordRef:service_record_ref];
+ result = [service_record getServiceRecordHandle:&service_record_handle];
+ if (result != kIOReturnSuccess)
+ return kInvalidServiceRecordHandle;
+
+ // Verify that the requested channel id was available. If not, withdraw the
+ // service.
Ilya Sherman 2014/04/19 05:11:25 Is this appropriate, or is the channel provided in
keybuk 2014/04/19 07:54:05 When specified, it should be equivalent to request
Ilya Sherman 2014/04/22 00:27:20 Sounds good. I've added a TODO for now, as there
+ BluetoothRFCOMMChannelID rfcomm_channel_id;
+ result = [service_record getRFCOMMChannelID:&rfcomm_channel_id];
+ if (result != kIOReturnSuccess || rfcomm_channel_id != options.channel) {
+ IOBluetoothRemoveServiceWithRecordHandle(service_record_handle);
+ return kInvalidServiceRecordHandle;
+ }
+
+ return service_record_handle;
+}
+
} // namespace
+} // namespace device
+
+// A simple helper class that forwards system notifications to its wrapped
+// |profile_|.
+@interface BluetoothProfileMacHelper : NSObject {
+ @private
+ // The profile that owns |self|.
+ device::BluetoothProfileMac* profile_; // weak
+
+ // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel
+ // creation notifications.
+ IOBluetoothUserNotification* rfcommNewChannelNotification_; // weak
+}
+
+- (id)initWithProfile:(device::BluetoothProfileMac*)profile
+ channelID:(BluetoothRFCOMMChannelID)channelID;
+- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification
+ channel:(IOBluetoothRFCOMMChannel*)rfcommChannel;
+
+@end
+
+@implementation BluetoothProfileMacHelper
+
+- (id)initWithProfile:(device::BluetoothProfileMac*)profile
+ channelID:(BluetoothRFCOMMChannelID)channelID {
+ if ((self = [super init])) {
+ profile_ = profile;
+
+ SEL selector = @selector(rfcommChannelOpened:channel:);
+ const auto kIncomingDirection =
+ kIOBluetoothUserNotificationChannelDirectionIncoming;
+ rfcommNewChannelNotification_ =
+ [IOBluetoothRFCOMMChannel
+ registerForChannelOpenNotifications:self
+ selector:selector
+ withChannelID:channelID
+ direction:kIncomingDirection];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [rfcommNewChannelNotification_ unregister];
+ [super dealloc];
+}
+
+- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification
+ channel:(IOBluetoothRFCOMMChannel*)rfcommChannel {
+ if (notification != rfcommNewChannelNotification_) {
+ // This case is reachable if there are pre-existing RFCOMM channels open at
+ // the time that the helper is created. In that case, each existing channel
+ // calls into this method with a different notification than the one this
+ // class registered with. Ignore those; this class is only interested in
+ // channels that have opened since it registered for notifications.
+ return;
+ }
+
+ profile_->OnRFCOMMChannelOpened(rfcommChannel);
+}
+
+@end
namespace device {
+BluetoothProfile* CreateBluetoothProfileMac(
+ const BluetoothUUID& uuid,
+ const BluetoothProfile::Options& options) {
+ return new BluetoothProfileMac(uuid, options);
+}
+
BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid,
- const std::string& name)
- : BluetoothProfile(), uuid_(uuid), name_(name) {
+ const Options& options)
+ : uuid_(uuid),
+ rfcomm_channel_id_(options.channel),
+ helper_([[BluetoothProfileMacHelper alloc]
+ initWithProfile:this
+ channelID:rfcomm_channel_id_]),
+ service_record_handle_(RegisterService(uuid, options)) {
+ // TODO(isherman): What should happen if there was an error registering the
+ // service, i.e. if |service_record_handle_| is |kInvalidServiceRecordHandle|?
Ilya Sherman 2014/04/19 05:11:25 ^^^ thoughts?
}
BluetoothProfileMac::~BluetoothProfileMac() {
+ if (service_record_handle_ != kInvalidServiceRecordHandle)
+ IOBluetoothRemoveServiceWithRecordHandle(service_record_handle_);
}
void BluetoothProfileMac::Unregister() {
@@ -122,15 +278,36 @@ void BluetoothProfileMac::Connect(
}
std::string device_address = base::SysNSStringToUTF8([device addressString]);
- scoped_refptr<BluetoothSocketMac> socket(
- BluetoothSocketMac::CreateBluetoothSocket(record));
- socket->Connect(
+ BluetoothSocketMac::Connect(
+ record,
base::Bind(OnConnectSuccess,
success_callback,
connection_callback_,
- device_address,
- socket),
+ device_address),
error_callback);
}
+void BluetoothProfileMac::OnRFCOMMChannelOpened(
+ IOBluetoothRFCOMMChannel* rfcomm_channel) {
+ DCHECK_EQ([rfcomm_channel getChannelID], rfcomm_channel_id_);
+ std::string device_address =
+ base::SysNSStringToUTF8([[rfcomm_channel getDevice] addressString]);
+ BluetoothSocketMac::AcceptConnection(
+ rfcomm_channel,
+ base::Bind(OnConnectSuccess,
+ base::Closure(),
+ connection_callback_,
+ device_address),
+ BluetoothSocket::ErrorCompletionCallback());
+
+ // TODO(isherman): Currently, both the profile and the socket remain alive
+ // even after the app that requested them is closed. That's not great, as a
+ // misbehaving app could saturate all of the system's RFCOMM channels, and
+ // then they would not be freed until the user restarts Chrome.
+ // TODO(isherman): Likewise, the socket currently remains alive even if the
+ // underlying rfcomm_channel is closed, e.g. via the client disconnecting, or
+ // the user closing the Bluetooth connection via the system menu. This
+ // functions essentially as a minor memory leak.
Ilya Sherman 2014/04/19 05:11:25 ^^^ Thoughts?
+}
+
} // namespace device
« no previous file with comments | « device/bluetooth/bluetooth_profile_mac.h ('k') | device/bluetooth/bluetooth_socket_mac.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698