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

Side by Side 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: Add a TODO around binding to a free channel if no channel is explicitly specified 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698