Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "device/bluetooth/bluetooth_profile_mac.h" | 5 #include "device/bluetooth/bluetooth_profile_mac.h" |
| 6 | 6 |
| 7 #import <IOBluetooth/objc/IOBluetoothDevice.h> | |
| 8 #import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> | |
| 9 #import <IOBluetooth/objc/IOBluetoothSDPUUID.h> | |
| 10 | |
| 11 #include <vector> | 7 #include <vector> |
| 12 | 8 |
| 13 #include "base/basictypes.h" | 9 #include "base/basictypes.h" |
| 14 #include "base/bind.h" | 10 #include "base/bind.h" |
| 15 #include "base/location.h" | 11 #include "base/location.h" |
| 16 #include "base/logging.h" | 12 #include "base/logging.h" |
| 13 #include "base/mac/scoped_cftyperef.h" | |
| 17 #include "base/memory/ref_counted.h" | 14 #include "base/memory/ref_counted.h" |
| 18 #include "base/strings/string_number_conversions.h" | 15 #include "base/strings/string_number_conversions.h" |
| 19 #include "base/strings/sys_string_conversions.h" | 16 #include "base/strings/sys_string_conversions.h" |
| 20 #include "device/bluetooth/bluetooth_adapter_factory.h" | 17 #include "device/bluetooth/bluetooth_adapter_factory.h" |
| 21 #include "device/bluetooth/bluetooth_device_mac.h" | 18 #include "device/bluetooth/bluetooth_device_mac.h" |
| 22 #include "device/bluetooth/bluetooth_socket_mac.h" | 19 #include "device/bluetooth/bluetooth_socket_mac.h" |
| 23 | 20 |
| 24 // Replicate specific 10.7 SDK declarations for building with prior SDKs. | 21 // Replicate specific 10.7 SDK declarations for building with prior SDKs. |
| 25 #if !defined(MAC_OS_X_VERSION_10_7) || \ | 22 #if !defined(MAC_OS_X_VERSION_10_7) || \ |
| 26 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 | 23 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 |
| 27 | 24 |
| 28 @interface IOBluetoothDevice (LionSDKDeclarations) | 25 @interface IOBluetoothDevice (LionSDKDeclarations) |
| 29 - (NSString*)addressString; | 26 - (NSString*)addressString; |
| 30 @end | 27 @end |
| 31 | 28 |
| 32 #endif // MAC_OS_X_VERSION_10_7 | 29 #endif // MAC_OS_X_VERSION_10_7 |
| 33 | 30 |
| 31 namespace device { | |
| 34 namespace { | 32 namespace { |
| 35 | 33 |
| 36 const char kNoConnectionCallback[] = "Connection callback not set"; | 34 const char kNoConnectionCallback[] = "Connection callback not set"; |
| 37 const char kProfileNotFound[] = "Profile not found"; | 35 const char kProfileNotFound[] = "Profile not found"; |
| 38 | 36 |
| 37 // 0 is a safe way to represent an unregistered service, as implied by the | |
| 38 // documentation at | |
| 39 // https://developer.apple.com/library/mac/documentation/devicedrivers/conceptua l/bluetooth/BT_Develop_BT_Apps/BT_Develop_BT_Apps.html | |
| 40 const BluetoothSDPServiceRecordHandle kInvalidServiceRecordHandle = 0; | |
| 41 | |
| 39 // Converts |uuid| to a IOBluetoothSDPUUID instance. | 42 // Converts |uuid| to a IOBluetoothSDPUUID instance. |
| 40 // | 43 // |
| 41 // |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. | 44 // |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. |
| 42 IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) { | 45 IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) { |
| 43 DCHECK(uuid.size() == 36); | 46 DCHECK(uuid.size() == 36); |
| 44 DCHECK(uuid[8] == '-'); | 47 DCHECK(uuid[8] == '-'); |
| 45 DCHECK(uuid[13] == '-'); | 48 DCHECK(uuid[13] == '-'); |
| 46 DCHECK(uuid[18] == '-'); | 49 DCHECK(uuid[18] == '-'); |
| 47 DCHECK(uuid[23] == '-'); | 50 DCHECK(uuid[23] == '-'); |
| 48 std::string numbers_only = uuid; | 51 std::string numbers_only = uuid; |
| 49 numbers_only.erase(23, 1); | 52 numbers_only.erase(23, 1); |
| 50 numbers_only.erase(18, 1); | 53 numbers_only.erase(18, 1); |
| 51 numbers_only.erase(13, 1); | 54 numbers_only.erase(13, 1); |
| 52 numbers_only.erase(8, 1); | 55 numbers_only.erase(8, 1); |
| 53 std::vector<uint8> uuid_bytes_vector; | 56 std::vector<uint8> uuid_bytes_vector; |
| 54 base::HexStringToBytes(numbers_only, &uuid_bytes_vector); | 57 base::HexStringToBytes(numbers_only, &uuid_bytes_vector); |
| 55 DCHECK(uuid_bytes_vector.size() == 16); | 58 DCHECK(uuid_bytes_vector.size() == 16); |
| 56 | 59 |
| 57 return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector[0] | 60 return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector.front() |
| 58 length:uuid_bytes_vector.size()]; | 61 length:uuid_bytes_vector.size()]; |
| 59 } | 62 } |
| 60 | 63 |
| 61 void OnConnectSuccessWithAdapter( | 64 void OnConnectSuccessWithAdapter( |
| 62 const base::Closure& callback, | 65 const base::Closure& callback, |
| 63 const device::BluetoothProfileMac::ConnectionCallback& connection_callback, | 66 const BluetoothProfileMac::ConnectionCallback& connection_callback, |
| 64 const std::string& device_address, | 67 const std::string& device_address, |
| 65 scoped_refptr<device::BluetoothSocketMac> socket, | 68 scoped_refptr<BluetoothSocket> socket, |
| 66 scoped_refptr<device::BluetoothAdapter> adapter) { | 69 scoped_refptr<BluetoothAdapter> adapter) { |
| 67 const device::BluetoothDevice* device = adapter->GetDevice(device_address); | 70 const BluetoothDevice* device = adapter->GetDevice(device_address); |
| 68 if (device) { | 71 if (device) { |
| 69 connection_callback.Run(device, socket); | 72 connection_callback.Run(device, socket); |
| 70 callback.Run(); | 73 if (!callback.is_null()) |
| 74 callback.Run(); | |
| 71 } | 75 } |
| 72 } | 76 } |
| 73 | 77 |
| 74 void OnConnectSuccess( | 78 void OnConnectSuccess( |
| 75 const base::Closure& callback, | 79 const base::Closure& callback, |
| 76 const device::BluetoothProfileMac::ConnectionCallback& connection_callback, | 80 const BluetoothProfileMac::ConnectionCallback& connection_callback, |
| 77 const std::string& device_address, | 81 const std::string& device_address, |
| 78 scoped_refptr<device::BluetoothSocketMac> socket) { | 82 scoped_refptr<BluetoothSocket> socket) { |
| 79 device::BluetoothAdapterFactory::GetAdapter( | 83 BluetoothAdapterFactory::GetAdapter( |
| 80 base::Bind(&OnConnectSuccessWithAdapter, | 84 base::Bind(&OnConnectSuccessWithAdapter, |
| 81 callback, | 85 callback, |
| 82 connection_callback, | 86 connection_callback, |
| 83 device_address, | 87 device_address, |
| 84 socket)); | 88 socket)); |
| 85 } | 89 } |
| 86 | 90 |
| 91 // Converts the given |integer| to a string. | |
| 92 NSString* IntToNSString(int integer) { | |
| 93 return [[NSNumber numberWithInt:integer] stringValue]; | |
| 94 } | |
| 95 | |
| 96 // Returns a dictionary containing the Bluetooth service definition | |
| 97 // corresponding to the provided |uuid| and |options|. | |
| 98 // TODO(isherman): Support more of the fields defined in |options|. | |
| 99 NSDictionary* BuildServiceDefinition(const BluetoothUUID& uuid, | |
| 100 const BluetoothProfile::Options& options) { | |
| 101 NSMutableDictionary* service_definition = [NSMutableDictionary dictionary]; | |
| 102 | |
| 103 // TODO(isherman): The service's language is currently hardcoded to English. | |
| 104 // The language should ideally be specified in the chrome.bluetooth API | |
| 105 // instead. | |
| 106 const int kEnglishLanguageBase = 100; | |
| 107 const int kServiceNameKey = | |
| 108 kEnglishLanguageBase + kBluetoothSDPAttributeIdentifierServiceName; | |
| 109 NSString* service_name = base::SysUTF8ToNSString(options.name); | |
| 110 [service_definition setObject:service_name | |
| 111 forKey:IntToNSString(kServiceNameKey)]; | |
| 112 | |
| 113 const int kUUIDsKey = kBluetoothSDPAttributeIdentifierServiceClassIDList; | |
| 114 NSArray* uuids = @[GetIOBluetoothSDPUUID(uuid.canonical_value())]; | |
| 115 [service_definition setObject:uuids forKey:IntToNSString(kUUIDsKey)]; | |
| 116 | |
| 117 const int kProtocolDefinitionsKey = | |
| 118 kBluetoothSDPAttributeIdentifierProtocolDescriptorList; | |
| 119 NSArray* rfcomm_protocol_definition = | |
| 120 @[[IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM], | |
| 121 [NSNumber numberWithInt:options.channel]]; | |
| 122 [service_definition setObject:@[rfcomm_protocol_definition] | |
| 123 forKey:IntToNSString(kProtocolDefinitionsKey)]; | |
| 124 | |
| 125 return service_definition; | |
| 126 } | |
| 127 | |
| 128 // Registers a Bluetooth service with the specifieid |uuid| and |options| in the | |
| 129 // system SDP server. Returns a handle to the registered service, or | |
| 130 // |kInvalidServcieRecordHandle| if the service could not be registered. | |
| 131 BluetoothSDPServiceRecordHandle RegisterService( | |
| 132 const BluetoothUUID& uuid, | |
| 133 const BluetoothProfile::Options& options) { | |
| 134 // Attempt to register the service. | |
| 135 NSDictionary* service_definition = BuildServiceDefinition(uuid, options); | |
| 136 IOBluetoothSDPServiceRecordRef service_record_ref; | |
| 137 IOReturn result = | |
| 138 IOBluetoothAddServiceDict((CFDictionaryRef)service_definition, | |
| 139 &service_record_ref); | |
| 140 if (result != kIOReturnSuccess) | |
| 141 return kInvalidServiceRecordHandle; | |
| 142 // Transfer ownership to a scoped object, to simplify memory management. | |
| 143 base::ScopedCFTypeRef<IOBluetoothSDPServiceRecordRef> | |
| 144 scoped_service_record_ref(service_record_ref); | |
| 145 | |
| 146 // Extract the service record handle. | |
| 147 BluetoothSDPServiceRecordHandle service_record_handle; | |
| 148 IOBluetoothSDPServiceRecord* service_record = | |
| 149 [IOBluetoothSDPServiceRecord withSDPServiceRecordRef:service_record_ref]; | |
| 150 result = [service_record getServiceRecordHandle:&service_record_handle]; | |
| 151 if (result != kIOReturnSuccess) | |
| 152 return kInvalidServiceRecordHandle; | |
| 153 | |
| 154 // Verify that the requested channel id was available. If not, withdraw the | |
| 155 // 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
| |
| 156 BluetoothRFCOMMChannelID rfcomm_channel_id; | |
| 157 result = [service_record getRFCOMMChannelID:&rfcomm_channel_id]; | |
| 158 if (result != kIOReturnSuccess || rfcomm_channel_id != options.channel) { | |
| 159 IOBluetoothRemoveServiceWithRecordHandle(service_record_handle); | |
| 160 return kInvalidServiceRecordHandle; | |
| 161 } | |
| 162 | |
| 163 return service_record_handle; | |
| 164 } | |
| 165 | |
| 87 } // namespace | 166 } // namespace |
| 167 } // namespace device | |
| 168 | |
| 169 // A simple helper class that forwards system notifications to its wrapped | |
| 170 // |profile_|. | |
| 171 @interface BluetoothProfileMacHelper : NSObject { | |
| 172 @private | |
| 173 // The profile that owns |self|. | |
| 174 device::BluetoothProfileMac* profile_; // weak | |
| 175 | |
| 176 // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel | |
| 177 // creation notifications. | |
| 178 IOBluetoothUserNotification* rfcommNewChannelNotification_; // weak | |
| 179 } | |
| 180 | |
| 181 - (id)initWithProfile:(device::BluetoothProfileMac*)profile | |
| 182 channelID:(BluetoothRFCOMMChannelID)channelID; | |
| 183 - (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification | |
| 184 channel:(IOBluetoothRFCOMMChannel*)rfcommChannel; | |
| 185 | |
| 186 @end | |
| 187 | |
| 188 @implementation BluetoothProfileMacHelper | |
| 189 | |
| 190 - (id)initWithProfile:(device::BluetoothProfileMac*)profile | |
| 191 channelID:(BluetoothRFCOMMChannelID)channelID { | |
| 192 if ((self = [super init])) { | |
| 193 profile_ = profile; | |
| 194 | |
| 195 SEL selector = @selector(rfcommChannelOpened:channel:); | |
| 196 const auto kIncomingDirection = | |
| 197 kIOBluetoothUserNotificationChannelDirectionIncoming; | |
| 198 rfcommNewChannelNotification_ = | |
| 199 [IOBluetoothRFCOMMChannel | |
| 200 registerForChannelOpenNotifications:self | |
| 201 selector:selector | |
| 202 withChannelID:channelID | |
| 203 direction:kIncomingDirection]; | |
| 204 } | |
| 205 | |
| 206 return self; | |
| 207 } | |
| 208 | |
| 209 - (void)dealloc { | |
| 210 [rfcommNewChannelNotification_ unregister]; | |
| 211 [super dealloc]; | |
| 212 } | |
| 213 | |
| 214 - (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification | |
| 215 channel:(IOBluetoothRFCOMMChannel*)rfcommChannel { | |
| 216 if (notification != rfcommNewChannelNotification_) { | |
| 217 // This case is reachable if there are pre-existing RFCOMM channels open at | |
| 218 // the time that the helper is created. In that case, each existing channel | |
| 219 // calls into this method with a different notification than the one this | |
| 220 // class registered with. Ignore those; this class is only interested in | |
| 221 // channels that have opened since it registered for notifications. | |
| 222 return; | |
| 223 } | |
| 224 | |
| 225 profile_->OnRFCOMMChannelOpened(rfcommChannel); | |
| 226 } | |
| 227 | |
| 228 @end | |
| 88 | 229 |
| 89 namespace device { | 230 namespace device { |
| 90 | 231 |
| 232 BluetoothProfile* CreateBluetoothProfileMac( | |
| 233 const BluetoothUUID& uuid, | |
| 234 const BluetoothProfile::Options& options) { | |
| 235 return new BluetoothProfileMac(uuid, options); | |
| 236 } | |
| 237 | |
| 91 BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid, | 238 BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid, |
| 92 const std::string& name) | 239 const Options& options) |
| 93 : BluetoothProfile(), uuid_(uuid), name_(name) { | 240 : uuid_(uuid), |
| 241 rfcomm_channel_id_(options.channel), | |
| 242 helper_([[BluetoothProfileMacHelper alloc] | |
| 243 initWithProfile:this | |
| 244 channelID:rfcomm_channel_id_]), | |
| 245 service_record_handle_(RegisterService(uuid, options)) { | |
| 246 // TODO(isherman): What should happen if there was an error registering the | |
| 247 // service, i.e. if |service_record_handle_| is |kInvalidServiceRecordHandle|? | |
|
Ilya Sherman
2014/04/19 05:11:25
^^^ thoughts?
| |
| 94 } | 248 } |
| 95 | 249 |
| 96 BluetoothProfileMac::~BluetoothProfileMac() { | 250 BluetoothProfileMac::~BluetoothProfileMac() { |
| 251 if (service_record_handle_ != kInvalidServiceRecordHandle) | |
| 252 IOBluetoothRemoveServiceWithRecordHandle(service_record_handle_); | |
| 97 } | 253 } |
| 98 | 254 |
| 99 void BluetoothProfileMac::Unregister() { | 255 void BluetoothProfileMac::Unregister() { |
| 100 delete this; | 256 delete this; |
| 101 } | 257 } |
| 102 | 258 |
| 103 void BluetoothProfileMac::SetConnectionCallback( | 259 void BluetoothProfileMac::SetConnectionCallback( |
| 104 const ConnectionCallback& callback) { | 260 const ConnectionCallback& callback) { |
| 105 connection_callback_ = callback; | 261 connection_callback_ = callback; |
| 106 } | 262 } |
| 107 | 263 |
| 108 void BluetoothProfileMac::Connect( | 264 void BluetoothProfileMac::Connect( |
| 109 IOBluetoothDevice* device, | 265 IOBluetoothDevice* device, |
| 110 const base::Closure& success_callback, | 266 const base::Closure& success_callback, |
| 111 const BluetoothSocket::ErrorCompletionCallback& error_callback) { | 267 const BluetoothSocket::ErrorCompletionCallback& error_callback) { |
| 112 if (connection_callback_.is_null()) { | 268 if (connection_callback_.is_null()) { |
| 113 error_callback.Run(kNoConnectionCallback); | 269 error_callback.Run(kNoConnectionCallback); |
| 114 return; | 270 return; |
| 115 } | 271 } |
| 116 | 272 |
| 117 IOBluetoothSDPServiceRecord* record = [device | 273 IOBluetoothSDPServiceRecord* record = [device |
| 118 getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_.canonical_value())]; | 274 getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_.canonical_value())]; |
| 119 if (record == nil) { | 275 if (record == nil) { |
| 120 error_callback.Run(kProfileNotFound); | 276 error_callback.Run(kProfileNotFound); |
| 121 return; | 277 return; |
| 122 } | 278 } |
| 123 | 279 |
| 124 std::string device_address = base::SysNSStringToUTF8([device addressString]); | 280 std::string device_address = base::SysNSStringToUTF8([device addressString]); |
| 125 scoped_refptr<BluetoothSocketMac> socket( | 281 BluetoothSocketMac::Connect( |
| 126 BluetoothSocketMac::CreateBluetoothSocket(record)); | 282 record, |
| 127 socket->Connect( | |
| 128 base::Bind(OnConnectSuccess, | 283 base::Bind(OnConnectSuccess, |
| 129 success_callback, | 284 success_callback, |
| 130 connection_callback_, | 285 connection_callback_, |
| 131 device_address, | 286 device_address), |
| 132 socket), | |
| 133 error_callback); | 287 error_callback); |
| 134 } | 288 } |
| 135 | 289 |
| 290 void BluetoothProfileMac::OnRFCOMMChannelOpened( | |
| 291 IOBluetoothRFCOMMChannel* rfcomm_channel) { | |
| 292 DCHECK_EQ([rfcomm_channel getChannelID], rfcomm_channel_id_); | |
| 293 std::string device_address = | |
| 294 base::SysNSStringToUTF8([[rfcomm_channel getDevice] addressString]); | |
| 295 BluetoothSocketMac::AcceptConnection( | |
| 296 rfcomm_channel, | |
| 297 base::Bind(OnConnectSuccess, | |
| 298 base::Closure(), | |
| 299 connection_callback_, | |
| 300 device_address), | |
| 301 BluetoothSocket::ErrorCompletionCallback()); | |
| 302 | |
| 303 // TODO(isherman): Currently, both the profile and the socket remain alive | |
| 304 // even after the app that requested them is closed. That's not great, as a | |
| 305 // misbehaving app could saturate all of the system's RFCOMM channels, and | |
| 306 // then they would not be freed until the user restarts Chrome. | |
| 307 // TODO(isherman): Likewise, the socket currently remains alive even if the | |
| 308 // underlying rfcomm_channel is closed, e.g. via the client disconnecting, or | |
| 309 // the user closing the Bluetooth connection via the system menu. This | |
| 310 // functions essentially as a minor memory leak. | |
|
Ilya Sherman
2014/04/19 05:11:25
^^^ Thoughts?
| |
| 311 } | |
| 312 | |
| 136 } // namespace device | 313 } // namespace device |
| OLD | NEW |