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 | |
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. | |
156 // TODO(isherman): Once the chrome.bluetooth API is updated, it should be | |
157 // possible to register an RFCOMM service without explicitly specifying a | |
158 // channel. In that case, we should be willing to bind to any channel, and not | |
159 // try to require any specific channel as we currently do here. | |
160 BluetoothRFCOMMChannelID rfcomm_channel_id; | |
161 result = [service_record getRFCOMMChannelID:&rfcomm_channel_id]; | |
162 if (result != kIOReturnSuccess || rfcomm_channel_id != options.channel) { | |
163 IOBluetoothRemoveServiceWithRecordHandle(service_record_handle); | |
164 return kInvalidServiceRecordHandle; | |
165 } | |
166 | |
167 return service_record_handle; | |
168 } | |
169 | |
87 } // namespace | 170 } // namespace |
171 } // namespace device | |
172 | |
173 // A simple helper class that forwards system notifications to its wrapped | |
174 // |profile_|. | |
175 @interface BluetoothProfileMacHelper : NSObject { | |
176 @private | |
177 // The profile that owns |self|. | |
178 device::BluetoothProfileMac* profile_; // weak | |
179 | |
180 // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel | |
181 // creation notifications. | |
182 IOBluetoothUserNotification* rfcommNewChannelNotification_; // weak | |
183 } | |
184 | |
185 - (id)initWithProfile:(device::BluetoothProfileMac*)profile | |
186 channelID:(BluetoothRFCOMMChannelID)channelID; | |
187 - (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification | |
188 channel:(IOBluetoothRFCOMMChannel*)rfcommChannel; | |
189 | |
190 @end | |
191 | |
192 @implementation BluetoothProfileMacHelper | |
193 | |
194 - (id)initWithProfile:(device::BluetoothProfileMac*)profile | |
195 channelID:(BluetoothRFCOMMChannelID)channelID { | |
196 if ((self = [super init])) { | |
197 profile_ = profile; | |
198 | |
199 SEL selector = @selector(rfcommChannelOpened:channel:); | |
200 const auto kIncomingDirection = | |
201 kIOBluetoothUserNotificationChannelDirectionIncoming; | |
202 rfcommNewChannelNotification_ = | |
203 [IOBluetoothRFCOMMChannel | |
204 registerForChannelOpenNotifications:self | |
205 selector:selector | |
206 withChannelID:channelID | |
207 direction:kIncomingDirection]; | |
208 } | |
209 | |
210 return self; | |
211 } | |
212 | |
213 - (void)dealloc { | |
214 [rfcommNewChannelNotification_ unregister]; | |
215 [super dealloc]; | |
216 } | |
217 | |
218 - (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification | |
219 channel:(IOBluetoothRFCOMMChannel*)rfcommChannel { | |
220 if (notification != rfcommNewChannelNotification_) { | |
221 // This case is reachable if there are pre-existing RFCOMM channels open at | |
222 // the time that the helper is created. In that case, each existing channel | |
223 // calls into this method with a different notification than the one this | |
224 // class registered with. Ignore those; this class is only interested in | |
225 // channels that have opened since it registered for notifications. | |
226 return; | |
227 } | |
228 | |
229 profile_->OnRFCOMMChannelOpened(rfcommChannel); | |
230 } | |
231 | |
232 @end | |
88 | 233 |
89 namespace device { | 234 namespace device { |
90 | 235 |
236 BluetoothProfile* CreateBluetoothProfileMac( | |
237 const BluetoothUUID& uuid, | |
238 const BluetoothProfile::Options& options) { | |
239 return new BluetoothProfileMac(uuid, options); | |
240 } | |
241 | |
91 BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid, | 242 BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid, |
92 const std::string& name) | 243 const Options& options) |
93 : BluetoothProfile(), uuid_(uuid), name_(name) { | 244 : uuid_(uuid), |
245 rfcomm_channel_id_(options.channel), | |
246 helper_([[BluetoothProfileMacHelper alloc] | |
247 initWithProfile:this | |
248 channelID:rfcomm_channel_id_]), | |
249 service_record_handle_(RegisterService(uuid, options)) { | |
250 // TODO(isherman): What should happen if there was an error registering the | |
251 // service, i.e. if |service_record_handle_| is |kInvalidServiceRecordHandle|? | |
rpaquay
2014/04/23 22:15:55
Ideally, we would have a way to report the error t
Ilya Sherman
2014/04/25 22:44:44
Filed a bug and referenced it here.
| |
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 // TODO(isherman): Likewise, the socket currently remains alive even if the | |
312 // underlying rfcomm_channel is closed, e.g. via the client disconnecting, or | |
313 // the user closing the Bluetooth connection via the system menu. This | |
314 // functions essentially as a minor memory leak. | |
rpaquay
2014/04/23 22:15:55
These issues are serious imho. Do we have a bug tr
Ilya Sherman
2014/04/25 22:44:44
Filed bugs and referenced them here.
| |
315 } | |
316 | |
136 } // namespace device | 317 } // namespace device |
OLD | NEW |