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