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/bootstrapping_device_lister.h"
6
7#include <algorithm>
8#include <iterator>
9
10#include "base/bind.h"
11#include "base/location.h"
12#include "base/message_loop/message_loop_proxy.h"
13#include "base/stl_util.h"
14#include "base/strings/string_util.h"
15
16namespace local_discovery {
17
18namespace wifi {
19
20namespace {
21
22const char kPrivetSuffix[] = "prv";
23// 3 for prv, 3 for type, one for connection status.
24const size_t kPrivetCharactersAfterSeparator = 7;
25
26const struct {
27  const char* const short_name;
28  const char* const long_name;
29} kPrivetShortNames[] = {{"cam", "camera"}, {"pri", "printer"}};
30
31const struct {
32  char signifier;
33  BootstrappingDeviceDescription::ConnectionStatus status;
34} kPrivetConnectionStatuses[] = {
35      {'C', BootstrappingDeviceDescription::CONNECTING},
36      {'F', BootstrappingDeviceDescription::OFFLINE},
37      {'L', BootstrappingDeviceDescription::LOCAL_ONLY},
38      {'N', BootstrappingDeviceDescription::NOT_CONFIGURED},
39      {'O', BootstrappingDeviceDescription::ONLINE},
40};
41
42const char kPrivetDeviceLongName[] = "device";
43
44std::string ExpandDeviceKind(const std::string& device_kind_short) {
45  for (size_t i = 0; i < arraysize(kPrivetShortNames); i++) {
46    if (device_kind_short == kPrivetShortNames[i].short_name) {
47      return kPrivetShortNames[i].long_name;
48    }
49  }
50  return kPrivetDeviceLongName;
51}
52
53BootstrappingDeviceDescription::ConnectionStatus GetConnectionStatus(
54    char signifier) {
55  for (size_t i = 0; i < arraysize(kPrivetConnectionStatuses); i++) {
56    if (signifier == kPrivetConnectionStatuses[i].signifier) {
57      return kPrivetConnectionStatuses[i].status;
58    }
59  }
60
61  LOG(WARNING) << "Unknown WiFi connection state signifier " << signifier;
62  return BootstrappingDeviceDescription::NOT_CONFIGURED;
63}
64
65// Return true if the SSID is a privet ssid and fills |description| with its
66// attributes.
67bool ParsePrivetSSID(const std::string& internal_id,
68                     const std::string& ssid,
69                     BootstrappingDeviceDescription* description) {
70  if (!EndsWith(ssid, kPrivetSuffix, true))
71    return false;
72
73  size_t at_pos = ssid.length() - kPrivetCharactersAfterSeparator - 1;
74
75  if (ssid[at_pos] != '@' || ssid.find('@', at_pos + 1) != std::string::npos)
76    return false;
77
78  description->device_network_id = internal_id;
79  description->device_ssid = ssid;
80
81  description->device_name = ssid.substr(0, at_pos);
82  description->device_kind = ExpandDeviceKind(ssid.substr(at_pos + 1, 3));
83  description->connection_status = GetConnectionStatus(ssid.at(at_pos + 4));
84
85  return true;
86}
87
88}  // namespace
89
90BootstrappingDeviceDescription::BootstrappingDeviceDescription()
91    : connection_status(NOT_CONFIGURED) {
92}
93
94BootstrappingDeviceDescription::~BootstrappingDeviceDescription() {
95}
96
97BootstrappingDeviceLister::BootstrappingDeviceLister(
98    WifiManager* wifi_manager,
99    const UpdateCallback& update_callback)
100    : wifi_manager_(wifi_manager),
101      update_callback_(update_callback),
102      started_(false),
103      weak_factory_(this) {
104}
105
106BootstrappingDeviceLister::~BootstrappingDeviceLister() {
107  if (started_)
108    wifi_manager_->RemoveNetworkListObserver(this);
109}
110
111void BootstrappingDeviceLister::Start() {
112  DCHECK(!started_);
113
114  started_ = true;
115
116  wifi_manager_->AddNetworkListObserver(this);
117
118  wifi_manager_->GetSSIDList(
119      base::Bind(&BootstrappingDeviceLister::OnNetworkListChanged,
120                 weak_factory_.GetWeakPtr()));
121}
122
123void BootstrappingDeviceLister::OnNetworkListChanged(
124    const std::vector<NetworkProperties>& ssids) {
125  ActiveDeviceList new_devices;
126
127  for (size_t i = 0; i < ssids.size(); i++) {
128    new_devices.push_back(make_pair(ssids[i].ssid, ssids[i].guid));
129  }
130
131  std::sort(new_devices.begin(), new_devices.end());
132
133  base::WeakPtr<BootstrappingDeviceLister> weak_this =
134      weak_factory_.GetWeakPtr();
135  // Find new or changed SSIDs
136  UpdateChangedSSIDs(true, new_devices, active_devices_);
137  if (!weak_this)
138    return;
139
140  // Find removed SSIDs
141  UpdateChangedSSIDs(false, active_devices_, new_devices);
142  if (!weak_this)
143    return;
144
145  active_devices_.swap(new_devices);
146}
147
148void BootstrappingDeviceLister::UpdateChangedSSIDs(
149    bool available,
150    const ActiveDeviceList& changed,
151    const ActiveDeviceList& original) {
152  base::WeakPtr<BootstrappingDeviceLister> weak_this =
153      weak_factory_.GetWeakPtr();
154
155  ActiveDeviceList changed_devices =
156    base::STLSetDifference<ActiveDeviceList>(changed, original);
157
158  for (ActiveDeviceList::reverse_iterator i = changed_devices.rbegin();
159       weak_this && i != changed_devices.rend();
160       i++) {
161    BootstrappingDeviceDescription description;
162    if (ParsePrivetSSID(i->second, i->first, &description)) {
163      update_callback_.Run(available, description);
164    }
165  }
166}
167
168}  // namespace wifi
169
170}  // namespace local_discovery
171