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

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: Rebase 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
« 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 // 0 is a safe way 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.
Ilya Sherman 2014/04/19 05:11:25 Is this appropriate, or is the channel provided in
keybuk 2014/04/19 07:54:05 When specified, it should be equivalent to request
Ilya Sherman 2014/04/22 00:27:20 Sounds good. I've added a TODO for now, as there
156 BluetoothRFCOMMChannelID rfcomm_channel_id;
157 result = [service_record getRFCOMMChannelID:&rfcomm_channel_id];
158 if (result != kIOReturnSuccess || rfcomm_channel_id != options.channel) {
159 IOBluetoothRemoveServiceWithRecordHandle(service_record_handle);
160 return kInvalidServiceRecordHandle;
161 }
162
163 return service_record_handle;
164 }
165
87 } // namespace 166 } // namespace
167 } // namespace device
168
169 // A simple helper class that forwards system notifications to its wrapped
170 // |profile_|.
171 @interface BluetoothProfileMacHelper : NSObject {
172 @private
173 // The profile that owns |self|.
174 device::BluetoothProfileMac* profile_; // weak
175
176 // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel
177 // creation notifications.
178 IOBluetoothUserNotification* rfcommNewChannelNotification_; // weak
179 }
180
181 - (id)initWithProfile:(device::BluetoothProfileMac*)profile
182 channelID:(BluetoothRFCOMMChannelID)channelID;
183 - (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification
184 channel:(IOBluetoothRFCOMMChannel*)rfcommChannel;
185
186 @end
187
188 @implementation BluetoothProfileMacHelper
189
190 - (id)initWithProfile:(device::BluetoothProfileMac*)profile
191 channelID:(BluetoothRFCOMMChannelID)channelID {
192 if ((self = [super init])) {
193 profile_ = profile;
194
195 SEL selector = @selector(rfcommChannelOpened:channel:);
196 const auto kIncomingDirection =
197 kIOBluetoothUserNotificationChannelDirectionIncoming;
198 rfcommNewChannelNotification_ =
199 [IOBluetoothRFCOMMChannel
200 registerForChannelOpenNotifications:self
201 selector:selector
202 withChannelID:channelID
203 direction:kIncomingDirection];
204 }
205
206 return self;
207 }
208
209 - (void)dealloc {
210 [rfcommNewChannelNotification_ unregister];
211 [super dealloc];
212 }
213
214 - (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification
215 channel:(IOBluetoothRFCOMMChannel*)rfcommChannel {
216 if (notification != rfcommNewChannelNotification_) {
217 // This case is reachable if there are pre-existing RFCOMM channels open at
218 // the time that the helper is created. In that case, each existing channel
219 // calls into this method with a different notification than the one this
220 // class registered with. Ignore those; this class is only interested in
221 // channels that have opened since it registered for notifications.
222 return;
223 }
224
225 profile_->OnRFCOMMChannelOpened(rfcommChannel);
226 }
227
228 @end
88 229
89 namespace device { 230 namespace device {
90 231
232 BluetoothProfile* CreateBluetoothProfileMac(
233 const BluetoothUUID& uuid,
234 const BluetoothProfile::Options& options) {
235 return new BluetoothProfileMac(uuid, options);
236 }
237
91 BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid, 238 BluetoothProfileMac::BluetoothProfileMac(const BluetoothUUID& uuid,
92 const std::string& name) 239 const Options& options)
93 : BluetoothProfile(), uuid_(uuid), name_(name) { 240 : uuid_(uuid),
241 rfcomm_channel_id_(options.channel),
242 helper_([[BluetoothProfileMacHelper alloc]
243 initWithProfile:this
244 channelID:rfcomm_channel_id_]),
245 service_record_handle_(RegisterService(uuid, options)) {
246 // TODO(isherman): What should happen if there was an error registering the
247 // service, i.e. if |service_record_handle_| is |kInvalidServiceRecordHandle|?
Ilya Sherman 2014/04/19 05:11:25 ^^^ thoughts?
94 } 248 }
95 249
96 BluetoothProfileMac::~BluetoothProfileMac() { 250 BluetoothProfileMac::~BluetoothProfileMac() {
251 if (service_record_handle_ != kInvalidServiceRecordHandle)
252 IOBluetoothRemoveServiceWithRecordHandle(service_record_handle_);
97 } 253 }
98 254
99 void BluetoothProfileMac::Unregister() { 255 void BluetoothProfileMac::Unregister() {
100 delete this; 256 delete this;
101 } 257 }
102 258
103 void BluetoothProfileMac::SetConnectionCallback( 259 void BluetoothProfileMac::SetConnectionCallback(
104 const ConnectionCallback& callback) { 260 const ConnectionCallback& callback) {
105 connection_callback_ = callback; 261 connection_callback_ = callback;
106 } 262 }
107 263
108 void BluetoothProfileMac::Connect( 264 void BluetoothProfileMac::Connect(
109 IOBluetoothDevice* device, 265 IOBluetoothDevice* device,
110 const base::Closure& success_callback, 266 const base::Closure& success_callback,
111 const BluetoothSocket::ErrorCompletionCallback& error_callback) { 267 const BluetoothSocket::ErrorCompletionCallback& error_callback) {
112 if (connection_callback_.is_null()) { 268 if (connection_callback_.is_null()) {
113 error_callback.Run(kNoConnectionCallback); 269 error_callback.Run(kNoConnectionCallback);
114 return; 270 return;
115 } 271 }
116 272
117 IOBluetoothSDPServiceRecord* record = [device 273 IOBluetoothSDPServiceRecord* record = [device
118 getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_.canonical_value())]; 274 getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_.canonical_value())];
119 if (record == nil) { 275 if (record == nil) {
120 error_callback.Run(kProfileNotFound); 276 error_callback.Run(kProfileNotFound);
121 return; 277 return;
122 } 278 }
123 279
124 std::string device_address = base::SysNSStringToUTF8([device addressString]); 280 std::string device_address = base::SysNSStringToUTF8([device addressString]);
125 scoped_refptr<BluetoothSocketMac> socket( 281 BluetoothSocketMac::Connect(
126 BluetoothSocketMac::CreateBluetoothSocket(record)); 282 record,
127 socket->Connect(
128 base::Bind(OnConnectSuccess, 283 base::Bind(OnConnectSuccess,
129 success_callback, 284 success_callback,
130 connection_callback_, 285 connection_callback_,
131 device_address, 286 device_address),
132 socket),
133 error_callback); 287 error_callback);
134 } 288 }
135 289
290 void BluetoothProfileMac::OnRFCOMMChannelOpened(
291 IOBluetoothRFCOMMChannel* rfcomm_channel) {
292 DCHECK_EQ([rfcomm_channel getChannelID], rfcomm_channel_id_);
293 std::string device_address =
294 base::SysNSStringToUTF8([[rfcomm_channel getDevice] addressString]);
295 BluetoothSocketMac::AcceptConnection(
296 rfcomm_channel,
297 base::Bind(OnConnectSuccess,
298 base::Closure(),
299 connection_callback_,
300 device_address),
301 BluetoothSocket::ErrorCompletionCallback());
302
303 // TODO(isherman): Currently, both the profile and the socket remain alive
304 // even after the app that requested them is closed. That's not great, as a
305 // misbehaving app could saturate all of the system's RFCOMM channels, and
306 // then they would not be freed until the user restarts Chrome.
307 // TODO(isherman): Likewise, the socket currently remains alive even if the
308 // underlying rfcomm_channel is closed, e.g. via the client disconnecting, or
309 // the user closing the Bluetooth connection via the system menu. This
310 // functions essentially as a minor memory leak.
Ilya Sherman 2014/04/19 05:11:25 ^^^ Thoughts?
311 }
312
136 } // namespace device 313 } // 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