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

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: Short url Created 6 years, 7 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
« no previous file with comments | « device/bluetooth/bluetooth_profile_mac.h ('k') | device/bluetooth/bluetooth_socket_mac.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 [ 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
OLDNEW
« no previous file with comments | « device/bluetooth/bluetooth_profile_mac.h ('k') | device/bluetooth/bluetooth_socket_mac.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698