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 "chrome/browser/local_discovery/wifi/wifi_manager_nonchromeos.h"
6
7#include "base/cancelable_callback.h"
8#include "base/threading/sequenced_worker_pool.h"
9#include "base/threading/thread.h"
10#include "components/onc/onc_constants.h"
11#include "components/wifi/wifi_service.h"
12#include "content/public/browser/browser_thread.h"
13#include "net/base/network_change_notifier.h"
14
15#if defined(OS_WIN)
16#include "chrome/browser/local_discovery/wifi/credential_getter_win.h"
17#endif  // OS_WIN
18
19using ::wifi::WiFiService;
20
21namespace local_discovery {
22
23namespace wifi {
24
25namespace {
26
27const int kConnectionTimeoutSeconds = 10;
28
29scoped_ptr<base::DictionaryValue> MakeProperties(const std::string& ssid,
30                                                 const std::string& password) {
31  scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue);
32
33  properties->SetString(onc::network_config::kType, onc::network_type::kWiFi);
34  base::DictionaryValue* wifi = new base::DictionaryValue;
35  properties->Set(onc::network_config::kWiFi, wifi);
36
37  wifi->SetString(onc::wifi::kSSID, ssid);
38  if (!password.empty()) {
39    wifi->SetString(onc::wifi::kPassphrase, password);
40    // TODO(noamsml): Allow choosing security mechanism in a more fine-grained
41    // manner.
42    wifi->SetString(onc::wifi::kSecurity, onc::wifi::kWPA2_PSK);
43  } else {
44    wifi->SetString(onc::wifi::kSecurity, onc::wifi::kSecurityNone);
45  }
46
47  return properties.Pass();
48}
49
50}  // namespace
51
52class WifiManagerNonChromeos::WifiServiceWrapper
53    : public net::NetworkChangeNotifier::NetworkChangeObserver {
54 public:
55  explicit WifiServiceWrapper(
56      base::WeakPtr<WifiManagerNonChromeos> wifi_manager);
57
58  virtual ~WifiServiceWrapper();
59
60  void Start();
61
62  void GetSSIDList(const WifiManager::SSIDListCallback& callback);
63
64  void ConfigureAndConnectPskNetwork(
65      const std::string& ssid,
66      const std::string& password,
67      const WifiManager::SuccessCallback& callback);
68
69  base::WeakPtr<WifiManagerNonChromeos::WifiServiceWrapper> AsWeakPtr();
70
71  void RequestScan();
72
73  void ConnectToNetworkByID(const std::string& network_guid,
74                            const WifiManager::SuccessCallback& callback);
75
76  void RequestNetworkCredentials(
77      const std::string& ssid,
78      const WifiManager::CredentialsCallback& callback);
79
80 private:
81  // net::NetworkChangeNotifier::NetworkChangeObserver implementation.
82  virtual void OnNetworkChanged(
83      net::NetworkChangeNotifier::ConnectionType type) OVERRIDE;
84
85  void GetSSIDListInternal(NetworkPropertiesList* ssid_list);
86
87  void OnNetworkListChangedEvent(const std::vector<std::string>& network_guids);
88
89  void OnNetworksChangedEvent(const std::vector<std::string>& network_guids);
90
91  std::string GetConnectedGUID();
92
93  bool IsConnected(const std::string& network_guid);
94
95  void OnConnectToNetworkTimeout();
96
97  void PostClosure(const base::Closure& closure);
98
99  bool FindAndConfigureNetwork(const std::string& ssid,
100                               const std::string& password,
101                               std::string* network_guid);
102
103#if defined(OS_WIN)
104  void PostCredentialsCallback(const WifiManager::CredentialsCallback& callback,
105                               const std::string& ssid,
106                               bool success,
107                               const std::string& password);
108#endif  // OS_WIN
109
110  scoped_ptr<WiFiService> wifi_service_;
111
112  base::WeakPtr<WifiManagerNonChromeos> wifi_manager_;
113
114  WifiManager::SuccessCallback connect_success_callback_;
115  base::CancelableClosure connect_failure_callback_;
116
117  // SSID of previously connected network.
118  std::string connected_network_guid_;
119
120  // SSID of network we are connecting to.
121  std::string connecting_network_guid_;
122
123  scoped_refptr<base::MessageLoopProxy> callback_runner_;
124
125  base::WeakPtrFactory<WifiServiceWrapper> weak_factory_;
126
127#if defined(OS_WIN)
128  scoped_refptr<CredentialGetterWin> credential_getter_;
129#endif  // OS_WIN
130
131  DISALLOW_COPY_AND_ASSIGN(WifiServiceWrapper);
132};
133
134WifiManagerNonChromeos::WifiServiceWrapper::WifiServiceWrapper(
135    base::WeakPtr<WifiManagerNonChromeos> wifi_manager)
136    : wifi_manager_(wifi_manager),
137      callback_runner_(base::MessageLoopProxy::current()),
138      weak_factory_(this) {
139}
140
141WifiManagerNonChromeos::WifiServiceWrapper::~WifiServiceWrapper() {
142  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
143}
144
145void WifiManagerNonChromeos::WifiServiceWrapper::Start() {
146  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
147  wifi_service_.reset(WiFiService::Create());
148
149  wifi_service_->Initialize(base::MessageLoopProxy::current());
150
151  wifi_service_->SetEventObservers(
152      base::MessageLoopProxy::current(),
153      base::Bind(&WifiServiceWrapper::OnNetworksChangedEvent, AsWeakPtr()),
154      base::Bind(&WifiServiceWrapper::OnNetworkListChangedEvent, AsWeakPtr()));
155
156  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
157}
158
159void WifiManagerNonChromeos::WifiServiceWrapper::GetSSIDList(
160    const WifiManager::SSIDListCallback& callback) {
161  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
162
163  scoped_ptr<NetworkPropertiesList> ssid_list(new NetworkPropertiesList);
164  GetSSIDListInternal(ssid_list.get());
165
166  callback_runner_->PostTask(
167      FROM_HERE,
168      base::Bind(&WifiManagerNonChromeos::PostSSIDListCallback,
169                 wifi_manager_,
170                 callback,
171                 base::Passed(&ssid_list)));
172}
173
174void WifiManagerNonChromeos::WifiServiceWrapper::ConfigureAndConnectPskNetwork(
175    const std::string& ssid,
176    const std::string& password,
177    const WifiManager::SuccessCallback& callback) {
178  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
179  scoped_ptr<base::DictionaryValue> properties = MakeProperties(ssid, password);
180
181  std::string network_guid;
182  std::string error_string;
183  // Will fail without changing system state if network already exists.
184  wifi_service_->CreateNetwork(
185      false, properties.Pass(), &network_guid, &error_string);
186
187  if (error_string.empty()) {
188    ConnectToNetworkByID(network_guid, callback);
189    return;
190  }
191
192  // If we cannot create the network, attempt to configure and connect to an
193  // existing network.
194  if (FindAndConfigureNetwork(ssid, password, &network_guid)) {
195    ConnectToNetworkByID(network_guid, callback);
196  } else {
197    if (!callback.is_null())
198      PostClosure(base::Bind(callback, false /* success */));
199  }
200}
201
202void WifiManagerNonChromeos::WifiServiceWrapper::OnNetworkListChangedEvent(
203    const std::vector<std::string>& network_guids) {
204  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
205  scoped_ptr<NetworkPropertiesList> ssid_list(new NetworkPropertiesList);
206  GetSSIDListInternal(ssid_list.get());
207  callback_runner_->PostTask(
208      FROM_HERE,
209      base::Bind(&WifiManagerNonChromeos::OnNetworkListChanged,
210                 wifi_manager_,
211                 base::Passed(&ssid_list)));
212}
213
214void WifiManagerNonChromeos::WifiServiceWrapper::OnNetworksChangedEvent(
215    const std::vector<std::string>& network_guids) {
216  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
217  if (connecting_network_guid_.empty() ||
218      !IsConnected(connecting_network_guid_)) {
219    return;
220  }
221
222  connecting_network_guid_.clear();
223  connect_failure_callback_.Cancel();
224
225  if (!connect_success_callback_.is_null())
226    PostClosure(base::Bind(connect_success_callback_, true));
227
228  connect_success_callback_.Reset();
229}
230
231base::WeakPtr<WifiManagerNonChromeos::WifiServiceWrapper>
232WifiManagerNonChromeos::WifiServiceWrapper::AsWeakPtr() {
233  return weak_factory_.GetWeakPtr();
234}
235
236void WifiManagerNonChromeos::WifiServiceWrapper::RequestScan() {
237  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
238  wifi_service_->RequestNetworkScan();
239}
240
241void WifiManagerNonChromeos::WifiServiceWrapper::ConnectToNetworkByID(
242    const std::string& network_guid,
243    const WifiManager::SuccessCallback& callback) {
244  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
245
246  std::string connected_network_id = GetConnectedGUID();
247  std::string error_string;
248  wifi_service_->StartConnect(network_guid, &error_string);
249
250  if (!error_string.empty()) {
251    LOG(ERROR) << "Could not connect to network by ID: " << error_string;
252    PostClosure(base::Bind(callback, false /* success */));
253    wifi_service_->StartConnect(connected_network_id, &error_string);
254    return;
255  }
256
257  if (IsConnected(network_guid)) {
258    if (!callback.is_null())
259      PostClosure(base::Bind(callback, true /* success */));
260    return;
261  }
262
263  connect_success_callback_ = callback;
264  connecting_network_guid_ = network_guid;
265  connected_network_guid_ = connected_network_id;
266
267  connect_failure_callback_.Reset(base::Bind(
268      &WifiServiceWrapper::OnConnectToNetworkTimeout, base::Unretained(this)));
269
270  base::MessageLoop::current()->PostDelayedTask(
271      FROM_HERE,
272      connect_failure_callback_.callback(),
273      base::TimeDelta::FromSeconds(kConnectionTimeoutSeconds));
274}
275
276void WifiManagerNonChromeos::WifiServiceWrapper::OnConnectToNetworkTimeout() {
277  bool connected = IsConnected(connecting_network_guid_);
278  std::string error_string;
279
280  WifiManager::SuccessCallback connect_success_callback =
281      connect_success_callback_;
282
283  connect_success_callback_.Reset();
284
285  connecting_network_guid_.clear();
286
287  // If we did not connect, return to the network the user was originally
288  // connected to.
289  if (!connected)
290    wifi_service_->StartConnect(connected_network_guid_, &error_string);
291
292  PostClosure(base::Bind(connect_success_callback, connected /* success */));
293}
294
295void WifiManagerNonChromeos::WifiServiceWrapper::RequestNetworkCredentials(
296    const std::string& ssid,
297    const WifiManager::CredentialsCallback& callback) {
298  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
299
300  bool success = true;
301  std::string guid;
302  std::string key;
303
304  NetworkPropertiesList network_list;
305
306  GetSSIDListInternal(&network_list);
307
308  for (NetworkPropertiesList::iterator i = network_list.begin();
309       i != network_list.end();
310       i++) {
311    if (i->ssid == ssid) {
312      guid = i->guid;
313      break;
314    }
315  }
316
317  if (guid.empty()) {
318    success = false;
319  }
320
321  if (!success) {
322    PostClosure(base::Bind(callback, success, "", ""));
323    return;
324  }
325
326#if defined(OS_WIN)
327  credential_getter_ = new CredentialGetterWin();
328  credential_getter_->StartGetCredentials(
329      guid,
330      base::Bind(&WifiServiceWrapper::PostCredentialsCallback,
331                 AsWeakPtr(),
332                 callback,
333                 ssid));
334#else
335  if (success) {
336    std::string error_string;
337    wifi_service_->GetKeyFromSystem(guid, &key, &error_string);
338
339    if (!error_string.empty()) {
340      LOG(ERROR) << "Could not get key from system: " << error_string;
341      success = false;
342    }
343
344    PostClosure(base::Bind(callback, success, ssid, key));
345  }
346#endif  // OS_WIN
347}
348
349void WifiManagerNonChromeos::WifiServiceWrapper::OnNetworkChanged(
350    net::NetworkChangeNotifier::ConnectionType type) {
351  wifi_service_->RequestConnectedNetworkUpdate();
352}
353
354void WifiManagerNonChromeos::WifiServiceWrapper::GetSSIDListInternal(
355    NetworkPropertiesList* ssid_list) {
356  base::ListValue visible_networks;
357
358  wifi_service_->GetVisibleNetworks(
359      onc::network_type::kWiFi, &visible_networks, true);
360
361  for (size_t i = 0; i < visible_networks.GetSize(); i++) {
362    const base::DictionaryValue* network_value = NULL;
363    NetworkProperties network_properties;
364
365    if (!visible_networks.GetDictionary(i, &network_value)) {
366      NOTREACHED();
367    }
368
369    network_properties.UpdateFromValue(*network_value);
370
371    ssid_list->push_back(network_properties);
372  }
373}
374
375std::string WifiManagerNonChromeos::WifiServiceWrapper::GetConnectedGUID() {
376  NetworkPropertiesList ssid_list;
377  GetSSIDListInternal(&ssid_list);
378
379  for (NetworkPropertiesList::const_iterator it = ssid_list.begin();
380       it != ssid_list.end();
381       ++it) {
382    if (it->connection_state == onc::connection_state::kConnected)
383      return it->guid;
384  }
385
386  return "";
387}
388
389bool WifiManagerNonChromeos::WifiServiceWrapper::IsConnected(
390    const std::string& network_guid) {
391  NetworkPropertiesList ssid_list;
392  GetSSIDListInternal(&ssid_list);
393
394  for (NetworkPropertiesList::const_iterator it = ssid_list.begin();
395       it != ssid_list.end();
396       ++it) {
397    if (it->connection_state == onc::connection_state::kConnected &&
398        it->guid == network_guid)
399      return true;
400  }
401
402  return false;
403}
404
405bool WifiManagerNonChromeos::WifiServiceWrapper::FindAndConfigureNetwork(
406    const std::string& ssid,
407    const std::string& password,
408    std::string* network_guid) {
409  std::string provisional_network_guid;
410  NetworkPropertiesList network_property_list;
411  GetSSIDListInternal(&network_property_list);
412
413  for (size_t i = 0; i < network_property_list.size(); i++) {
414    // TODO(noamsml): Handle case where there are multiple networks with the
415    // same SSID but different security.
416    if (network_property_list[i].ssid == ssid) {
417      provisional_network_guid = network_property_list[i].guid;
418      break;
419    }
420  }
421
422  if (provisional_network_guid.empty())
423    return false;
424
425  scoped_ptr<base::DictionaryValue> properties = MakeProperties(ssid, password);
426
427  std::string error_string;
428  wifi_service_->SetProperties(
429      provisional_network_guid, properties.Pass(), &error_string);
430
431  if (!error_string.empty()) {
432    LOG(ERROR) << "Could not set properties on network: " << error_string;
433    return false;
434  }
435
436  *network_guid = provisional_network_guid;
437  return true;
438}
439
440void WifiManagerNonChromeos::WifiServiceWrapper::PostClosure(
441    const base::Closure& closure) {
442  callback_runner_->PostTask(
443      FROM_HERE,
444      base::Bind(&WifiManagerNonChromeos::PostClosure, wifi_manager_, closure));
445}
446
447#if defined(OS_WIN)
448void WifiManagerNonChromeos::WifiServiceWrapper::PostCredentialsCallback(
449    const WifiManager::CredentialsCallback& callback,
450    const std::string& ssid,
451    bool success,
452    const std::string& password) {
453  PostClosure(base::Bind(callback, success, ssid, password));
454}
455
456#endif  // OS_WIN
457
458scoped_ptr<WifiManager> WifiManager::CreateDefault() {
459  return scoped_ptr<WifiManager>(new WifiManagerNonChromeos());
460}
461
462WifiManagerNonChromeos::WifiManagerNonChromeos()
463    : wifi_wrapper_(NULL), weak_factory_(this) {
464}
465
466WifiManagerNonChromeos::~WifiManagerNonChromeos() {
467  if (wifi_wrapper_) {
468    content::BrowserThread::DeleteSoon(
469        content::BrowserThread::FILE, FROM_HERE, wifi_wrapper_);
470  }
471}
472
473void WifiManagerNonChromeos::Start() {
474  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
475  task_runner_ = content::BrowserThread::GetMessageLoopProxyForThread(
476      content::BrowserThread::FILE);
477
478  // Allocated on UI thread, but all initialization is done on file
479  // thread. Destroyed on file thread, which should be safe since all of the
480  // thread-unsafe members are created on the file thread.
481  wifi_wrapper_ = new WifiServiceWrapper(weak_factory_.GetWeakPtr());
482
483  task_runner_->PostTask(
484      FROM_HERE,
485      base::Bind(&WifiServiceWrapper::Start, wifi_wrapper_->AsWeakPtr()));
486}
487
488void WifiManagerNonChromeos::GetSSIDList(const SSIDListCallback& callback) {
489  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
490  task_runner_->PostTask(FROM_HERE,
491                         base::Bind(&WifiServiceWrapper::GetSSIDList,
492                                    wifi_wrapper_->AsWeakPtr(),
493                                    callback));
494}
495
496void WifiManagerNonChromeos::RequestScan() {
497  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
498  task_runner_->PostTask(
499      FROM_HERE,
500      base::Bind(&WifiServiceWrapper::RequestScan, wifi_wrapper_->AsWeakPtr()));
501}
502
503void WifiManagerNonChromeos::OnNetworkListChanged(
504    scoped_ptr<NetworkPropertiesList> ssid_list) {
505  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
506  FOR_EACH_OBSERVER(NetworkListObserver,
507                    network_list_observers_,
508                    OnNetworkListChanged(*ssid_list));
509}
510
511void WifiManagerNonChromeos::PostClosure(const base::Closure& callback) {
512  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
513  callback.Run();
514}
515
516void WifiManagerNonChromeos::PostSSIDListCallback(
517    const SSIDListCallback& callback,
518    scoped_ptr<NetworkPropertiesList> ssid_list) {
519  callback.Run(*ssid_list);
520}
521
522void WifiManagerNonChromeos::ConfigureAndConnectNetwork(
523    const std::string& ssid,
524    const WifiCredentials& credentials,
525    const SuccessCallback& callback) {
526  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
527  task_runner_->PostTask(
528      FROM_HERE,
529      base::Bind(&WifiServiceWrapper::ConfigureAndConnectPskNetwork,
530                 wifi_wrapper_->AsWeakPtr(),
531                 ssid,
532                 credentials.psk,
533                 callback));
534}
535
536void WifiManagerNonChromeos::ConnectToNetworkByID(
537    const std::string& internal_id,
538    const SuccessCallback& callback) {
539  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
540  task_runner_->PostTask(FROM_HERE,
541                         base::Bind(&WifiServiceWrapper::ConnectToNetworkByID,
542                                    wifi_wrapper_->AsWeakPtr(),
543                                    internal_id,
544                                    callback));
545}
546
547void WifiManagerNonChromeos::RequestNetworkCredentials(
548    const std::string& ssid,
549    const CredentialsCallback& callback) {
550  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
551  task_runner_->PostTask(
552      FROM_HERE,
553      base::Bind(&WifiServiceWrapper::RequestNetworkCredentials,
554                 wifi_wrapper_->AsWeakPtr(),
555                 ssid,
556                 callback));
557}
558
559void WifiManagerNonChromeos::AddNetworkListObserver(
560    NetworkListObserver* observer) {
561  network_list_observers_.AddObserver(observer);
562}
563
564void WifiManagerNonChromeos::RemoveNetworkListObserver(
565    NetworkListObserver* observer) {
566  network_list_observers_.RemoveObserver(observer);
567}
568
569}  // namespace wifi
570
571}  // namespace local_discovery
572