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 |