1//
2// Copyright (C) 2012 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "shill/wimax/wimax.h"
18
19#include <string>
20
21#include <base/bind.h>
22#include <base/strings/string_util.h>
23#include <base/strings/stringprintf.h>
24
25#include "shill/control_interface.h"
26#include "shill/key_value_store.h"
27#include "shill/logging.h"
28#include "shill/manager.h"
29#include "shill/wimax/wimax_device_proxy_interface.h"
30#include "shill/wimax/wimax_service.h"
31
32using base::Bind;
33using std::set;
34using std::string;
35
36namespace shill {
37
38namespace Logging {
39static auto kModuleLogScope = ScopeLogger::kWiMax;
40static string ObjectID(WiMax* w) { return w->GetRpcIdentifier(); }
41}
42
43namespace {
44
45const char* DeviceStatusToString(wimax_manager::DeviceStatus status) {
46  switch (status) {
47    case wimax_manager::kDeviceStatusUninitialized:
48      return "Uninitialized";
49    case wimax_manager::kDeviceStatusDisabled:
50      return "Disabled";
51    case wimax_manager::kDeviceStatusReady:
52      return "Ready";
53    case wimax_manager::kDeviceStatusScanning:
54      return "Scanning";
55    case wimax_manager::kDeviceStatusConnecting:
56      return "Connecting";
57    case wimax_manager::kDeviceStatusConnected:
58      return "Connected";
59    default:
60      return "Unknown";
61  }
62}
63
64}  // namespace
65
66const int WiMax::kDefaultConnectTimeoutSeconds = 60;
67const int WiMax::kDefaultRPCTimeoutSeconds = 30;
68
69WiMax::WiMax(ControlInterface* control,
70             EventDispatcher* dispatcher,
71             Metrics* metrics,
72             Manager* manager,
73             const string& link_name,
74             const string& address,
75             int interface_index,
76             const RpcIdentifier& path)
77    : Device(control, dispatcher, metrics, manager, link_name, address,
78             interface_index, Technology::kWiMax),
79      path_(path),
80      weak_ptr_factory_(this),
81      scanning_(false),
82      status_(wimax_manager::kDeviceStatusUninitialized),
83      connect_timeout_seconds_(kDefaultConnectTimeoutSeconds) {
84  LOG(INFO) << "WiMAX device created: " << link_name << " @ " << path;
85  PropertyStore* store = mutable_store();
86  store->RegisterConstBool(kScanningProperty, &scanning_);
87}
88
89WiMax::~WiMax() {
90  LOG(INFO) << "WiMAX device destroyed: " << link_name();
91}
92
93void WiMax::Start(Error* error, const EnabledStateChangedCallback& callback) {
94  SLOG(this, 2) << __func__;
95  scanning_ = false;
96  proxy_.reset(control_interface()->CreateWiMaxDeviceProxy(path_));
97  proxy_->set_networks_changed_callback(
98      Bind(&WiMax::OnNetworksChanged, Unretained(this)));
99  proxy_->set_status_changed_callback(
100      Bind(&WiMax::OnStatusChanged, Unretained(this)));
101  proxy_->Enable(
102      error, Bind(&WiMax::OnEnableComplete, this, callback),
103      kDefaultRPCTimeoutSeconds * 1000);
104}
105
106void WiMax::Stop(Error* error, const EnabledStateChangedCallback& callback) {
107  SLOG(this, 2) << __func__;
108  StopConnectTimeout();
109  if (pending_service_) {
110    pending_service_->SetState(Service::kStateIdle);
111    pending_service_ = nullptr;
112  }
113  if (selected_service()) {
114    Error error;
115    DisconnectFrom(selected_service(), &error);
116  }
117  scanning_ = false;
118  networks_.clear();
119  manager()->wimax_provider()->OnNetworksChanged();
120  if (proxy_.get()) {
121    proxy_->Disable(
122        error, Bind(&WiMax::OnDisableComplete, this, callback),
123        kDefaultRPCTimeoutSeconds * 1000);
124  } else {
125    OnDisableComplete(callback, Error());
126  }
127}
128
129void WiMax::Scan(ScanType /*scan_type*/, Error* error,
130                 const string& /*reason*/) {
131  SLOG(this, 2) << __func__;
132  if (scanning_) {
133    Error::PopulateAndLog(
134        FROM_HERE, error, Error::kInProgress, "Scan already in progress.");
135    return;
136  }
137  scanning_ = true;
138  proxy_->ScanNetworks(
139      error, Bind(&WiMax::OnScanNetworksComplete, this),
140      kDefaultRPCTimeoutSeconds * 1000);
141  if (error->IsFailure()) {
142    OnScanNetworksComplete(*error);
143  }
144}
145
146void WiMax::ConnectTo(const WiMaxServiceRefPtr& service, Error* error) {
147  SLOG(this, 2) << __func__ << "(" << service->GetStorageIdentifier() << ")";
148  if (pending_service_) {
149    Error::PopulateAndLog(
150        FROM_HERE, error, Error::kInProgress,
151        base::StringPrintf(
152            "Pending connect to service %s, ignoring connect request to %s.",
153            pending_service_->unique_name().c_str(),
154            service->GetStorageIdentifier().c_str()));
155    return;
156  }
157  service->SetState(Service::kStateAssociating);
158  pending_service_ = service;
159
160  // We use the RPC device status to determine the outcome of the connect
161  // operation by listening for status updates in OnStatusChanged. A transition
162  // to Connected means success. A transition to Connecting and then to a status
163  // different than Connected means failure. Also, schedule a connect timeout to
164  // guard against the RPC device never transitioning to a Connecting or a
165  // Connected state.
166  status_ = wimax_manager::kDeviceStatusUninitialized;
167  StartConnectTimeout();
168
169  KeyValueStore parameters;
170  service->GetConnectParameters(&parameters);
171  proxy_->Connect(
172      service->GetNetworkObjectPath(), parameters,
173      error, Bind(&WiMax::OnConnectComplete, this),
174      kDefaultRPCTimeoutSeconds * 1000);
175  if (error->IsFailure()) {
176    OnConnectComplete(*error);
177  }
178}
179
180void WiMax::DisconnectFrom(const ServiceRefPtr& service, Error* error) {
181  SLOG(this, 2) << __func__;
182  if (pending_service_) {
183    Error::PopulateAndLog(
184        FROM_HERE, error, Error::kInProgress,
185        base::StringPrintf(
186            "Pending connect to service %s, "
187            "ignoring disconnect request from %s.",
188            pending_service_->unique_name().c_str(),
189            service->GetStorageIdentifier().c_str()));
190    return;
191  }
192  if (selected_service() && service != selected_service()) {
193    Error::PopulateAndLog(
194        FROM_HERE, error, Error::kNotConnected,
195        base::StringPrintf(
196            "Current service is %s, ignoring disconnect request from %s.",
197            selected_service()->unique_name().c_str(),
198            service->GetStorageIdentifier().c_str()));
199    return;
200  }
201  DropConnection();
202  proxy_->Disconnect(
203      error, Bind(&WiMax::OnDisconnectComplete, this),
204      kDefaultRPCTimeoutSeconds * 1000);
205  if (error->IsFailure()) {
206    OnDisconnectComplete(*error);
207  }
208}
209
210bool WiMax::IsIdle() const {
211  return !pending_service_ && !selected_service();
212}
213
214void WiMax::OnServiceStopped(const WiMaxServiceRefPtr& service) {
215  SLOG(this, 2) << __func__;
216  if (service == selected_service()) {
217    DropConnection();
218  }
219  if (service == pending_service_) {
220    pending_service_ = nullptr;
221  }
222}
223
224void WiMax::OnDeviceVanished() {
225  LOG(INFO) << "WiMAX device vanished: " << link_name();
226  proxy_.reset();
227  DropService(Service::kStateIdle);
228  // Disable the device. This will also clear any relevant properties such as
229  // the live network set.
230  SetEnabled(false);
231}
232
233void WiMax::OnScanNetworksComplete(const Error& /*error*/) {
234  SLOG(this, 2) << __func__;
235  scanning_ = false;
236  // The networks are updated when the NetworksChanged signal is received.
237}
238
239void WiMax::OnConnectComplete(const Error& error) {
240  SLOG(this, 2) << __func__;
241  if (error.IsSuccess()) {
242    // Nothing to do -- the connection process is resumed on the StatusChanged
243    // signal.
244    return;
245  }
246  DropService(Service::kStateFailure);
247}
248
249void WiMax::OnDisconnectComplete(const Error& /*error*/) {
250  SLOG(this, 2) << __func__;
251}
252
253void WiMax::OnEnableComplete(const EnabledStateChangedCallback& callback,
254                             const Error& error) {
255  SLOG(this, 2) << __func__;
256  if (error.IsFailure()) {
257    proxy_.reset();
258  } else {
259    LOG(INFO) << "WiMAX device " << link_name() << " enabled.";
260    // Updates the live networks based on the current WiMaxManager.Device
261    // networks. The RPC device will signal when the network set changes.
262    Error e;
263    OnNetworksChanged(proxy_->Networks(&e));
264  }
265  callback.Run(error);
266}
267
268void WiMax::OnDisableComplete(const EnabledStateChangedCallback& callback,
269                              const Error& error) {
270  LOG(INFO) << "WiMAX device " << link_name() << " disabled.";
271  proxy_.reset();
272  callback.Run(error);
273}
274
275void WiMax::OnNetworksChanged(const RpcIdentifiers& networks) {
276  SLOG(this, 2) << __func__;
277  networks_.clear();
278  networks_.insert(networks.begin(), networks.end());
279  manager()->wimax_provider()->OnNetworksChanged();
280}
281
282void WiMax::OnStatusChanged(wimax_manager::DeviceStatus status) {
283  SLOG(this, 2) << "WiMAX device " << link_name()
284                << " status: " << DeviceStatusToString(status);
285  wimax_manager::DeviceStatus old_status = status_;
286  status_ = status;
287  switch (status) {
288    case wimax_manager::kDeviceStatusConnected:
289      if (!pending_service_) {
290        LOG(WARNING) << "Unexpected status change; ignored.";
291        return;
292      }
293      // Stops the connect timeout -- the DHCP provider has a separate timeout.
294      StopConnectTimeout();
295      if (AcquireIPConfig()) {
296        LOG(INFO) << "WiMAX device " << link_name() << " connected to "
297                  << pending_service_->GetStorageIdentifier();
298        SelectService(pending_service_);
299        pending_service_ = nullptr;
300        SetServiceState(Service::kStateConfiguring);
301      } else {
302        DropService(Service::kStateFailure);
303      }
304      break;
305    case wimax_manager::kDeviceStatusConnecting:
306      LOG(INFO) << "WiMAX device " << link_name() << " connecting...";
307      // Nothing to do.
308      break;
309    default:
310      // We may receive a queued up status update (e.g., to Scanning) before
311      // receiving the status update to Connecting, so be careful to fail the
312      // service only on the right status transition.
313      if (old_status == wimax_manager::kDeviceStatusConnecting ||
314          old_status == wimax_manager::kDeviceStatusConnected) {
315        LOG(INFO) << "WiMAX device " << link_name()
316                  << " status: " << DeviceStatusToString(old_status)
317                  << " -> " << DeviceStatusToString(status);
318        // TODO(benchan): Investigate a method to determine if the connection
319        // failure is due to incorrect EAP credentials and indicate that via
320        // Service::kFailureBadPassphrase (crosbug.com/p/16324).
321        DropService(Service::kStateFailure);
322      }
323      break;
324  }
325}
326
327void WiMax::DropService(Service::ConnectState state) {
328  SLOG(this, 2) << __func__
329                << "(" << Service::ConnectStateToString(state) << ")";
330  StopConnectTimeout();
331  if (pending_service_) {
332    LOG(WARNING) << "Unable to initiate connection to: "
333                 << pending_service_->GetStorageIdentifier();
334    pending_service_->SetState(state);
335    pending_service_ = nullptr;
336  }
337  if (selected_service()) {
338    LOG(WARNING) << "Service disconnected: "
339                 << selected_service()->GetStorageIdentifier();
340    selected_service()->SetState(state);
341    DropConnection();
342  }
343}
344
345void WiMax::StartConnectTimeout() {
346  SLOG(this, 2) << __func__;
347  if (IsConnectTimeoutStarted()) {
348    return;
349  }
350  connect_timeout_callback_.Reset(
351      Bind(&WiMax::OnConnectTimeout, weak_ptr_factory_.GetWeakPtr()));
352  dispatcher()->PostDelayedTask(
353      connect_timeout_callback_.callback(), connect_timeout_seconds_ * 1000);
354}
355
356void WiMax::StopConnectTimeout() {
357  SLOG(this, 2) << __func__;
358  connect_timeout_callback_.Cancel();
359}
360
361bool WiMax::IsConnectTimeoutStarted() const {
362  return !connect_timeout_callback_.IsCancelled();
363}
364
365void WiMax::OnConnectTimeout() {
366  LOG(ERROR) << "WiMAX device " << link_name() << ": connect timeout.";
367  StopConnectTimeout();
368  DropService(Service::kStateFailure);
369}
370
371}  // namespace shill
372