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

Side by Side Diff: components/wifi/wifi_service_mac.mm

Issue 64683014: Mac OS X-specific implementation of Networking Private API. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address Roberts's comments. Created 6 years, 11 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 | « components/wifi/wifi_service.cc ('k') | components/wifi/wifi_service_win.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/wifi/wifi_service.h"
6
7 #import <netinet/in.h>
8 #import <CoreWLAN/CoreWLAN.h>
9 #import <SystemConfiguration/SystemConfiguration.h>
10
11 #include "base/bind.h"
12 #include "base/mac/scoped_cftyperef.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "components/onc/onc_constants.h"
17
18 #if !defined(MAC_OS_X_VERSION_10_7) || \
19 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
20
21 // Local definitions of API added in Mac OS X 10.7
22
23 @interface CWInterface (LionAPI)
24 - (BOOL)associateToNetwork:(CWNetwork*)network
25 password:(NSString*)password
26 error:(NSError**)error;
27 - (NSSet*)scanForNetworksWithName:(NSString*)networkName
28 error:(NSError**)error;
29 @end
30
31 enum CWChannelBand {
32 kCWChannelBandUnknown = 0,
33 kCWChannelBand2GHz = 1,
34 kCWChannelBand5GHz = 2,
35 };
36
37 @interface CWChannel : NSObject
38 @property(readonly) CWChannelBand channelBand;
39 @end
40
41 @interface CWNetwork (LionAPI)
42 @property(readonly) CWChannel* wlanChannel;
43 @end
44
45 #endif // 10.7
46
47 namespace wifi {
48
49 const char kErrorAssociateToNetwork[] = "Error.AssociateToNetwork";
50 const char kErrorInvalidData[] = "Error.InvalidData";
51 const char kErrorNotConnected[] = "Error.NotConnected";
52 const char kErrorNotFound[] = "Error.NotFound";
53 const char kErrorNotImplemented[] = "Error.NotImplemented";
54 const char kErrorScanForNetworksWithName[] = "Error.ScanForNetworksWithName";
55
56 // Implementation of WiFiService for Mac OS X.
57 class WiFiServiceMac : public WiFiService {
58 public:
59 WiFiServiceMac();
60 virtual ~WiFiServiceMac();
61
62 // WiFiService interface implementation.
63 virtual void Initialize(
64 scoped_refptr<base::SequencedTaskRunner> task_runner) OVERRIDE;
65
66 virtual void UnInitialize() OVERRIDE;
67
68 virtual void GetProperties(const std::string& network_guid,
69 base::DictionaryValue* properties,
70 std::string* error) OVERRIDE;
71
72 virtual void GetManagedProperties(const std::string& network_guid,
73 base::DictionaryValue* managed_properties,
74 std::string* error) OVERRIDE;
75
76 virtual void GetState(const std::string& network_guid,
77 base::DictionaryValue* properties,
78 std::string* error) OVERRIDE;
79
80 virtual void SetProperties(const std::string& network_guid,
81 scoped_ptr<base::DictionaryValue> properties,
82 std::string* error) OVERRIDE;
83
84 virtual void CreateNetwork(bool shared,
85 scoped_ptr<base::DictionaryValue> properties,
86 std::string* network_guid,
87 std::string* error) OVERRIDE;
88
89 virtual void GetVisibleNetworks(const std::string& network_type,
90 base::ListValue* network_list) OVERRIDE;
91
92 virtual void RequestNetworkScan() OVERRIDE;
93
94 virtual void StartConnect(const std::string& network_guid,
95 std::string* error) OVERRIDE;
96
97 virtual void StartDisconnect(const std::string& network_guid,
98 std::string* error) OVERRIDE;
99
100 virtual void SetEventObservers(
101 scoped_refptr<base::MessageLoopProxy> message_loop_proxy,
102 const NetworkGuidListCallback& networks_changed_observer,
103 const NetworkGuidListCallback& network_list_changed_observer) OVERRIDE;
104
105 private:
106 // Checks |ns_error| and if is not |nil|, then stores |error_name|
107 // into |error|.
108 bool CheckError(NSError* ns_error,
109 const char* error_name,
110 std::string* error) const;
111
112 // Gets |ssid| from unique |network_guid|.
113 NSString* SSIDFromGUID(const std::string& network_guid) const {
114 return base::SysUTF8ToNSString(network_guid);
115 }
116
117 // Gets unique |network_guid| string based on |ssid|.
118 std::string GUIDFromSSID(NSString* ssid) const {
119 return base::SysNSStringToUTF8(ssid);
120 }
121
122 // Populates |properties| from |network|.
123 void NetworkPropertiesFromCWNetwork(const CWNetwork* network,
124 NetworkProperties* properties) const;
125
126 // Convert s|CWSecurityMode| into onc::wifi::k{WPA|WEP}* security constant.
127 std::string SecurityFromCWSecurityMode(CWSecurityMode security) const;
128
129 // Waits up to |kMaxAttempts| with |kAttemptDelayMs| delay for connection to
130 // network with |network_guid|. Notifies that |NetworkChanged| upon success.
131 void WaitForNetworkConnect(const std::string& network_guid, int attempt);
132
133 // Gets current |onc::connection_state| for given |network_guid|.
134 std::string GetNetworkConnectionState(const std::string& network_guid) const;
135
136 // Updates |networks_| with the list of visible wireless networks.
137 void UpdateNetworks();
138
139 // Find network by |network_guid| and return iterator to its entry in
140 // |networks_|.
141 NetworkList::iterator FindNetwork(const std::string& network_guid);
142
143 // Handles notification from |wlan_observer_|.
144 void OnWlanObserverNotification();
145
146 // Notifies |network_list_changed_observer_| that list of visible networks has
147 // changed to |networks|.
148 void NotifyNetworkListChanged(const NetworkList& networks);
149
150 // Notifies |networks_changed_observer_| that network |network_guid|
151 // connection state has changed.
152 void NotifyNetworkChanged(const std::string& network_guid);
153
154 // Default interface.
155 base::scoped_nsobject<CWInterface> interface_;
156 // WLAN Notifications observer. |this| doesn't own this reference.
157 id wlan_observer_;
158
159 // Observer to get notified when network(s) have changed (e.g. connect).
160 NetworkGuidListCallback networks_changed_observer_;
161 // Observer to get notified when network list has changed (scan complete).
162 NetworkGuidListCallback network_list_changed_observer_;
163 // MessageLoopProxy to post events on UI thread.
164 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
165 // Task runner for worker tasks.
166 scoped_refptr<base::SequencedTaskRunner> task_runner_;
167 // Cache of network list collected by GetVisibleNetworks.
168 NetworkList networks_;
169 // Temporary storage of network properties indexed by |network_guid|.
170 base::DictionaryValue network_properties_;
171 // Number of attempts to check that network has connected successfully.
172 static const int kConnectionCheckMaxAttempts = 200;
173 // Delay between attempts to check that network has connected successfully.
174 static const int kConnectionCheckAttemptDelayMs = 100;
175 };
176
177 WiFiServiceMac::WiFiServiceMac() : wlan_observer_(nil) {
178 }
179
180 WiFiServiceMac::~WiFiServiceMac() {
181 }
182
183 void WiFiServiceMac::Initialize(
184 scoped_refptr<base::SequencedTaskRunner> task_runner) {
185 task_runner_.swap(task_runner);
186 interface_.reset([[CWInterface interface] retain]);
187 if (!interface_) {
188 DVLOG(1) << "Failed to initialize default interface.";
189 return;
190 }
191
192 if (![interface_
193 respondsToSelector:@selector(associateToNetwork:password:error:)]) {
194 DVLOG(1) << "CWInterface does not support associateToNetwork.";
195 interface_.reset();
196 return;
197 }
198 }
199
200 void WiFiServiceMac::UnInitialize() {
201 if (wlan_observer_)
202 [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
203 interface_.reset();
204 }
205
206 void WiFiServiceMac::GetProperties(const std::string& network_guid,
207 base::DictionaryValue* properties,
208 std::string* error) {
209 NetworkList::iterator it = FindNetwork(network_guid);
210 if (it == networks_.end()) {
211 *error = kErrorNotFound;
212 return;
213 }
214
215 it->connection_state = GetNetworkConnectionState(network_guid);
216 scoped_ptr<base::DictionaryValue> network(it->ToValue(false));
217 properties->Swap(network.get());
218 DVLOG(1) << *properties;
219 }
220
221 void WiFiServiceMac::GetManagedProperties(
222 const std::string& network_guid,
223 base::DictionaryValue* managed_properties,
224 std::string* error) {
225 *error = kErrorNotImplemented;
226 }
227
228 void WiFiServiceMac::GetState(const std::string& network_guid,
229 base::DictionaryValue* properties,
230 std::string* error) {
231 *error = kErrorNotImplemented;
232 }
233
234 void WiFiServiceMac::SetProperties(
235 const std::string& network_guid,
236 scoped_ptr<base::DictionaryValue> properties,
237 std::string* error) {
238 network_properties_.SetWithoutPathExpansion(network_guid,
239 properties.release());
240 }
241
242 void WiFiServiceMac::CreateNetwork(
243 bool shared,
244 scoped_ptr<base::DictionaryValue> properties,
245 std::string* network_guid,
246 std::string* error) {
247 WiFiService::NetworkProperties network_properties;
248 if (!network_properties.UpdateFromValue(*properties)) {
249 *error = kErrorInvalidData;
250 return;
251 }
252
253 std::string guid = network_properties.ssid;
254 if (FindNetwork(guid) != networks_.end()) {
255 *error = kErrorInvalidData;
256 return;
257 }
258 network_properties_.SetWithoutPathExpansion(guid,
259 properties.release());
260 *network_guid = guid;
261 }
262
263 void WiFiServiceMac::GetVisibleNetworks(const std::string& network_type,
264 base::ListValue* network_list) {
265 if (!network_type.empty() &&
266 network_type != onc::network_type::kAllTypes &&
267 network_type != onc::network_type::kWiFi) {
268 return;
269 }
270
271 if (networks_.empty())
272 UpdateNetworks();
273
274 for (WiFiService::NetworkList::const_iterator it = networks_.begin();
275 it != networks_.end();
276 ++it) {
277 scoped_ptr<base::DictionaryValue> network(it->ToValue(true));
278 network_list->Append(network.release());
279 }
280 }
281
282 void WiFiServiceMac::RequestNetworkScan() {
283 UpdateNetworks();
284 }
285
286 void WiFiServiceMac::StartConnect(const std::string& network_guid,
287 std::string* error) {
288 NSError* ns_error = nil;
289
290 DVLOG(1) << "*** StartConnect: " << network_guid;
291 // Remember previously connected network.
292 std::string connected_network_guid = GUIDFromSSID([interface_ ssid]);
293 // Check whether desired network is already connected.
294 if (network_guid == connected_network_guid) {
295 NotifyNetworkChanged(connected_network_guid);
296 return;
297 }
298
299 NSSet* networks = [interface_
300 scanForNetworksWithName:SSIDFromGUID(network_guid)
301 error:&ns_error];
302
303 if (CheckError(ns_error, kErrorScanForNetworksWithName, error))
304 return;
305
306 CWNetwork* network = [networks anyObject];
307 if (network == nil) {
308 // System can't find the network, remove it from the |networks_| and notify
309 // observers.
310 NetworkList::iterator it = FindNetwork(connected_network_guid);
311 if (it != networks_.end()) {
312 networks_.erase(it);
313 // Notify observers that list has changed.
314 NotifyNetworkListChanged(networks_);
315 }
316
317 *error = kErrorNotFound;
318 return;
319 }
320
321 // Check whether WiFi Password is set in |network_properties_|.
322 base::DictionaryValue* properties;
323 base::DictionaryValue* wifi;
324 std::string passphrase;
325 NSString* ns_password = nil;
326 if (network_properties_.GetDictionaryWithoutPathExpansion(network_guid,
327 &properties) &&
328 properties->GetDictionary(onc::network_type::kWiFi, &wifi) &&
329 wifi->GetString(onc::wifi::kPassphrase, &passphrase)) {
330 ns_password = base::SysUTF8ToNSString(passphrase);
331 }
332
333 // Number of attempts to associate to network.
334 static const int kMaxAssociationAttempts = 3;
335 // Try to associate to network several times if timeout or PMK error occurs.
336 for (int i = 0; i < kMaxAssociationAttempts; ++i) {
337 // Nil out the PMK to prevent stale data from causing invalid PMK error
338 // (CoreWLANTypes -3924).
339 [interface_ setPairwiseMasterKey:nil error:&ns_error];
340 if (![interface_ associateToNetwork:network
341 password:ns_password
342 error:&ns_error]) {
343 NSInteger error_code = [ns_error code];
344 if (error_code != kCWTimeoutErr && error_code != kCWInvalidPMKErr) {
345 break;
346 }
347 }
348 }
349 CheckError(ns_error, kErrorAssociateToNetwork, error);
350 }
351
352 void WiFiServiceMac::StartDisconnect(const std::string& network_guid,
353 std::string* error) {
354 DVLOG(1) << "*** StartDisconnect: " << network_guid;
355
356 if (network_guid == GUIDFromSSID([interface_ ssid])) {
357 // Power-cycle the interface to disconnect from current network and connect
358 // to default network.
359 NSError* ns_error = nil;
360 [interface_ setPower:NO error:&ns_error];
361 CheckError(ns_error, kErrorAssociateToNetwork, error);
362 [interface_ setPower:YES error:&ns_error];
363 CheckError(ns_error, kErrorAssociateToNetwork, error);
364 } else {
365 *error = kErrorNotConnected;
366 }
367 }
368
369 void WiFiServiceMac::SetEventObservers(
370 scoped_refptr<base::MessageLoopProxy> message_loop_proxy,
371 const NetworkGuidListCallback& networks_changed_observer,
372 const NetworkGuidListCallback& network_list_changed_observer) {
373 message_loop_proxy_.swap(message_loop_proxy);
374 networks_changed_observer_ = networks_changed_observer;
375 network_list_changed_observer_ = network_list_changed_observer;
376
377 // Remove previous OS notifications observer.
378 if (wlan_observer_) {
379 [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
380 wlan_observer_ = nil;
381 }
382
383 // Subscribe to OS notifications.
384 if (!networks_changed_observer_.is_null()) {
385 void (^ns_observer) (NSNotification* notification) =
386 ^(NSNotification* notification) {
387 task_runner_->PostTask(
388 FROM_HERE,
389 base::Bind(&WiFiServiceMac::OnWlanObserverNotification,
390 base::Unretained(this)));
391 };
392
393 wlan_observer_ = [[NSNotificationCenter defaultCenter]
394 addObserverForName:kCWSSIDDidChangeNotification
395 object:nil
396 queue:nil
397 usingBlock:ns_observer];
398 }
399 }
400
401 void WiFiServiceMac::WaitForNetworkConnect(const std::string& network_guid,
402 int attempt) {
403 // If network didn't get connected in |kMaxAttempts|, then stop waiting.
404 if (attempt > kConnectionCheckMaxAttempts) {
405 DLOG(ERROR) << kConnectionCheckMaxAttempts
406 << " attempts exceeded waiting for connect to "
407 << network_guid;
408 return;
409 }
410
411 if (GetNetworkConnectionState(network_guid) ==
412 onc::connection_state::kConnected) {
413 DVLOG(1) << "WiFi Connected, Reachable: " << network_guid;
414 NotifyNetworkChanged(network_guid);
415 } else {
416 // Continue waiting for network connection state change.
417 task_runner_->PostDelayedTask(
418 FROM_HERE,
419 base::Bind(&WiFiServiceMac::WaitForNetworkConnect,
420 base::Unretained(this),
421 network_guid,
422 ++attempt),
423 base::TimeDelta::FromMilliseconds(kConnectionCheckAttemptDelayMs));
424 }
425 }
426
427 std::string WiFiServiceMac::GetNetworkConnectionState(
428 const std::string& network_guid) const {
429 if (network_guid != GUIDFromSSID([interface_ ssid]))
430 return onc::connection_state::kNotConnected;
431
432 // Check whether WiFi network is reachable.
433 struct sockaddr_in local_wifi_address;
434 bzero(&local_wifi_address, sizeof(local_wifi_address));
435 local_wifi_address.sin_len = sizeof(local_wifi_address);
436 local_wifi_address.sin_family = AF_INET;
437 local_wifi_address.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
438 base::ScopedCFTypeRef<SCNetworkReachabilityRef> reachability(
439 SCNetworkReachabilityCreateWithAddress(
440 kCFAllocatorDefault,
441 reinterpret_cast<const struct sockaddr*>(&local_wifi_address)));
442 SCNetworkReachabilityFlags flags = 0u;
443 if (SCNetworkReachabilityGetFlags(reachability, &flags) &&
444 (flags & kSCNetworkReachabilityFlagsReachable) &&
445 (flags & kSCNetworkReachabilityFlagsIsDirect)) {
446 // Network is reachable, report is as |kConnected|.
447 return onc::connection_state::kConnected;
448 }
449 // Network is not reachable yet, so it must be |kConnecting|.
450 return onc::connection_state::kConnecting;
451 }
452
453 void WiFiServiceMac::UpdateNetworks() {
454 NSError* ns_error = nil;
455 NSSet* cw_networks = [interface_ scanForNetworksWithName:nil
456 error:&ns_error];
457
458 if (ns_error != nil)
459 return;
460
461 std::map<std::string, NetworkProperties*> network_properties_map;
462 networks_.clear();
463
464 // There is one |cw_network| per BSS in |cw_networks|, so go through the set
465 // and combine them, paying attention to supported frequencies.
466 for (CWNetwork* cw_network in cw_networks) {
467 NetworkProperties network_properties;
468 NetworkPropertiesFromCWNetwork(cw_network, &network_properties);
469
470 if (network_properties_map.find(network_properties.guid) ==
471 network_properties_map.end()) {
472 networks_.push_back(network_properties);
473 network_properties_map[network_properties.guid] = &networks_.back();
474 } else {
475 NetworkProperties* existing = network_properties_map.at(
476 network_properties.guid);
477 existing->frequency_set.insert(*network_properties.frequency_set.begin());
478 }
479 }
480 // Sort networks, so connected/connecting is up front.
481 networks_.sort(NetworkProperties::OrderByType);
482 // Notify observers that list has changed.
483 NotifyNetworkListChanged(networks_);
484 }
485
486 bool WiFiServiceMac::CheckError(NSError* ns_error,
487 const char* error_name,
488 std::string* error) const {
489 if (ns_error != nil) {
490 DLOG(ERROR) << "*** Error:" << error_name << ":" << [ns_error code];
491 *error = error_name;
492 return true;
493 }
494 return false;
495 }
496
497 void WiFiServiceMac::NetworkPropertiesFromCWNetwork(
498 const CWNetwork* network,
499 NetworkProperties* properties) const {
500 std::string network_guid = GUIDFromSSID([network ssid]);
501
502 properties->connection_state = GetNetworkConnectionState(network_guid);
503 properties->ssid = base::SysNSStringToUTF8([network ssid]);
504 properties->name = properties->ssid;
505 properties->guid = network_guid;
506 properties->type = onc::network_type::kWiFi;
507
508 properties->bssid = base::SysNSStringToUTF8([network bssid]);
509 if ([[network wlanChannel] channelBand] == kCWChannelBand2GHz)
510 properties->frequency = kFrequency2400;
511 else
512 properties->frequency = kFrequency5000;
513 properties->frequency_set.insert(properties->frequency);
514 properties->security = SecurityFromCWSecurityMode(
515 static_cast<CWSecurityMode>([[network securityMode] intValue]));
516
517 properties->signal_strength = [[network rssi] intValue];
518 }
519
520 std::string WiFiServiceMac::SecurityFromCWSecurityMode(
521 CWSecurityMode security) const {
522 switch (security) {
523 case kCWSecurityModeWPA_Enterprise:
524 case kCWSecurityModeWPA2_Enterprise:
525 return onc::wifi::kWPA_EAP;
526 case kCWSecurityModeWPA_PSK:
527 case kCWSecurityModeWPA2_PSK:
528 return onc::wifi::kWPA_PSK;
529 case kCWSecurityModeWEP:
530 return onc::wifi::kWEP_PSK;
531 case kCWSecurityModeOpen:
532 return onc::wifi::kNone;
533 // TODO(mef): Figure out correct mapping.
534 case kCWSecurityModeWPS:
535 case kCWSecurityModeDynamicWEP:
536 return onc::wifi::kWPA_EAP;
537 }
538 return onc::wifi::kWPA_EAP;
539 }
540
541 WiFiService::NetworkList::iterator WiFiServiceMac::FindNetwork(
542 const std::string& network_guid) {
543 for (NetworkList::iterator it = networks_.begin();
544 it != networks_.end();
545 ++it) {
546 if (it->guid == network_guid)
547 return it;
548 }
549 return networks_.end();
550 }
551
552 void WiFiServiceMac::OnWlanObserverNotification() {
553 std::string connected_network_guid = GUIDFromSSID([interface_ ssid]);
554 DVLOG(1) << " *** Got Notification: " << connected_network_guid;
555 // There are 2 notifications for every network switch:
556 // 1. When old WLAN is disconnected, |connected_network_guid| is empty.
557 // 2. When new WLAN is connected, |connected_network_guid| is new network
558 // guid.
559 if (connected_network_guid.empty()) {
560 // Find previously connected network and notify that it is disconnected.
561 for (WiFiService::NetworkList::iterator it = networks_.begin();
562 it != networks_.end();
563 ++it) {
564 if (it->connection_state == onc::connection_state::kConnected) {
565 it->connection_state = onc::connection_state::kNotConnected;
566 NotifyNetworkChanged(it->guid);
567 }
568 }
569 } else {
570 // Update connection_state of newly connected network.
571 NetworkList::iterator it = FindNetwork(connected_network_guid);
572 if (it != networks_.end()) {
573 it->connection_state = GetNetworkConnectionState(connected_network_guid);
574 } else {
575 // Can't find |connected_network_guid| in |networks_|, try to update it.
576 UpdateNetworks();
tbarzic 2014/01/16 19:41:49 will this find a connected hidden network? Should
mef 2014/01/16 23:42:48 UpdateNetworks() will not see the hidden network,
577 }
578 // Notify that network is connecting.
579 NotifyNetworkChanged(connected_network_guid);
580 // Start waiting for network connection to complete.
581 if (!networks_changed_observer_.is_null())
582 WaitForNetworkConnect(connected_network_guid, 0);
583 }
584 }
585
586 void WiFiServiceMac::NotifyNetworkListChanged(const NetworkList& networks) {
587 if (network_list_changed_observer_.is_null())
588 return;
589
590 NetworkGuidList current_networks;
591 for (NetworkList::const_iterator it = networks.begin();
592 it != networks.end();
593 ++it) {
594 current_networks.push_back(it->guid);
595 }
596
597 message_loop_proxy_->PostTask(
598 FROM_HERE,
599 base::Bind(network_list_changed_observer_, current_networks));
600 }
601
602 void WiFiServiceMac::NotifyNetworkChanged(const std::string& network_guid) {
603 if (networks_changed_observer_.is_null())
604 return;
605
606 DVLOG(1) << "NotifyNetworkChanged: " << network_guid;
607 NetworkGuidList changed_networks(1, network_guid);
608 message_loop_proxy_->PostTask(
609 FROM_HERE,
610 base::Bind(networks_changed_observer_, changed_networks));
611 }
612
613 // static
614 WiFiService* WiFiService::Create() { return new WiFiServiceMac(); }
615
616 } // namespace wifi
OLDNEW
« no previous file with comments | « components/wifi/wifi_service.cc ('k') | components/wifi/wifi_service_win.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698