1 // Copyright 2015 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "buffet/shill_client.h"
16 
17 #include <set>
18 
19 #include <base/message_loop/message_loop.h>
20 #include <base/stl_util.h>
21 #include <brillo/any.h>
22 #include <brillo/errors/error.h>
23 #include <brillo/variant_dictionary.h>
24 #include <dbus/shill/dbus-constants.h>
25 #include <weave/enum_to_string.h>
26 
27 #include "buffet/ap_manager_client.h"
28 #include "buffet/socket_stream.h"
29 #include "buffet/weave_error_conversion.h"
30 
31 using brillo::Any;
32 using brillo::VariantDictionary;
33 using dbus::ObjectPath;
34 using org::chromium::flimflam::DeviceProxy;
35 using org::chromium::flimflam::ServiceProxy;
36 using std::map;
37 using std::set;
38 using std::string;
39 using std::vector;
40 using weave::EnumToString;
41 using weave::provider::Network;
42 
43 namespace buffet {
44 
45 namespace {
46 
IgnoreDetachEvent()47 void IgnoreDetachEvent() {}
48 
GetStateForService(ServiceProxy * service,string * state)49 bool GetStateForService(ServiceProxy* service, string* state) {
50   CHECK(service) << "|service| was nullptr in GetStateForService()";
51   VariantDictionary properties;
52   if (!service->GetProperties(&properties, nullptr)) {
53     LOG(WARNING) << "Failed to read properties from service.";
54     return false;
55   }
56   auto property_it = properties.find(shill::kStateProperty);
57   if (property_it == properties.end()) {
58     LOG(WARNING) << "No state found in service properties.";
59     return false;
60   }
61   string new_state = property_it->second.TryGet<string>();
62   if (new_state.empty()) {
63     LOG(WARNING) << "Invalid state value.";
64     return false;
65   }
66   *state = new_state;
67   return true;
68 }
69 
ShillServiceStateToNetworkState(const string & state)70 Network::State ShillServiceStateToNetworkState(const string& state) {
71   // TODO(wiley) What does "unconfigured" mean in a world with multiple sets
72   //             of WiFi credentials?
73   // TODO(wiley) Detect disabled devices, update state appropriately.
74   if ((state.compare(shill::kStateReady) == 0) ||
75       (state.compare(shill::kStatePortal) == 0) ||
76       (state.compare(shill::kStateOnline) == 0)) {
77     return Network::State::kOnline;
78   }
79   if ((state.compare(shill::kStateAssociation) == 0) ||
80       (state.compare(shill::kStateConfiguration) == 0)) {
81     return Network::State::kConnecting;
82   }
83   if ((state.compare(shill::kStateFailure) == 0) ||
84       (state.compare(shill::kStateActivationFailure) == 0)) {
85     // TODO(wiley) Get error information off the service object.
86     return Network::State::kError;
87   }
88   if ((state.compare(shill::kStateIdle) == 0) ||
89       (state.compare(shill::kStateOffline) == 0) ||
90       (state.compare(shill::kStateDisconnect) == 0)) {
91     return Network::State::kOffline;
92   }
93   LOG(WARNING) << "Unknown state found: '" << state << "'";
94   return Network::State::kOffline;
95 }
96 
97 }  // namespace
98 
ShillClient(const scoped_refptr<dbus::Bus> & bus,const set<string> & device_whitelist,bool disable_xmpp)99 ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus,
100                          const set<string>& device_whitelist,
101                          bool disable_xmpp)
102     : bus_{bus},
103       manager_proxy_{bus_},
104       device_whitelist_{device_whitelist},
105       disable_xmpp_{disable_xmpp},
106       ap_manager_client_{new ApManagerClient(bus)} {
107   manager_proxy_.RegisterPropertyChangedSignalHandler(
108       base::Bind(&ShillClient::OnManagerPropertyChange,
109                  weak_factory_.GetWeakPtr()),
110       base::Bind(&ShillClient::OnManagerPropertyChangeRegistration,
111                  weak_factory_.GetWeakPtr()));
112   auto owner_changed_cb = base::Bind(&ShillClient::OnShillServiceOwnerChange,
113                                      weak_factory_.GetWeakPtr());
114   bus_->GetObjectProxy(shill::kFlimflamServiceName, ObjectPath{"/"})
115       ->SetNameOwnerChangedCallback(owner_changed_cb);
116 
117   Init();
118 }
119 
~ShillClient()120 ShillClient::~ShillClient() {}
121 
Init()122 void ShillClient::Init() {
123   VLOG(2) << "ShillClient::Init();";
124   CleanupConnectingService();
125   devices_.clear();
126   connectivity_state_ = Network::State::kOffline;
127   VariantDictionary properties;
128   if (!manager_proxy_.GetProperties(&properties, nullptr)) {
129     LOG(ERROR) << "Unable to get properties from Manager, waiting for "
130                   "Manager to come back online.";
131     return;
132   }
133   auto it = properties.find(shill::kDevicesProperty);
134   CHECK(it != properties.end()) << "shill should always publish a device list.";
135   OnManagerPropertyChange(shill::kDevicesProperty, it->second);
136 }
137 
Connect(const string & ssid,const string & passphrase,const weave::DoneCallback & callback)138 void ShillClient::Connect(const string& ssid,
139                           const string& passphrase,
140                           const weave::DoneCallback& callback) {
141   LOG(INFO) << "Connecting to WiFi network: " << ssid;
142   if (connecting_service_) {
143     weave::ErrorPtr error;
144     weave::Error::AddTo(&error, FROM_HERE, "busy",
145                         "Already connecting to WiFi network");
146     base::MessageLoop::current()->PostTask(
147         FROM_HERE, base::Bind(callback, base::Passed(&error)));
148     return;
149   }
150   CleanupConnectingService();
151   VariantDictionary service_properties;
152   service_properties[shill::kTypeProperty] = Any{string{shill::kTypeWifi}};
153   service_properties[shill::kSSIDProperty] = Any{ssid};
154   if (passphrase.empty()) {
155     service_properties[shill::kSecurityProperty] = Any{shill::kSecurityNone};
156   } else {
157     service_properties[shill::kPassphraseProperty] = Any{passphrase};
158     service_properties[shill::kSecurityProperty] = Any{shill::kSecurityPsk};
159   }
160   service_properties[shill::kSaveCredentialsProperty] = Any{true};
161   service_properties[shill::kAutoConnectProperty] = Any{true};
162   ObjectPath service_path;
163   brillo::ErrorPtr brillo_error;
164   if (!manager_proxy_.ConfigureService(service_properties, &service_path,
165                                        &brillo_error) ||
166       !manager_proxy_.RequestScan(shill::kTypeWifi, &brillo_error)) {
167     weave::ErrorPtr weave_error;
168     ConvertError(*brillo_error, &weave_error);
169     base::MessageLoop::current()->PostTask(
170         FROM_HERE, base::Bind(callback, base::Passed(&weave_error)));
171     return;
172   }
173   connecting_service_.reset(new ServiceProxy{bus_, service_path});
174   connecting_service_->Connect(nullptr);
175   connect_done_callback_ = callback;
176   connecting_service_->RegisterPropertyChangedSignalHandler(
177       base::Bind(&ShillClient::OnServicePropertyChange,
178                  weak_factory_.GetWeakPtr(), service_path),
179       base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
180                  weak_factory_.GetWeakPtr(), service_path));
181   base::MessageLoop::current()->PostDelayedTask(
182       FROM_HERE, base::Bind(&ShillClient::ConnectToServiceError,
183                             weak_factory_.GetWeakPtr(), connecting_service_),
184       base::TimeDelta::FromMinutes(1));
185 }
186 
ConnectToServiceError(std::shared_ptr<org::chromium::flimflam::ServiceProxy> connecting_service)187 void ShillClient::ConnectToServiceError(
188     std::shared_ptr<org::chromium::flimflam::ServiceProxy> connecting_service) {
189   if (connecting_service != connecting_service_ ||
190       connect_done_callback_.is_null()) {
191     return;
192   }
193   std::string error = have_called_connect_ ? connecting_service_error_
194                                            : shill::kErrorOutOfRange;
195   if (error.empty())
196     error = shill::kErrorInternal;
197   OnErrorChangeForConnectingService(error);
198 }
199 
GetConnectionState() const200 Network::State ShillClient::GetConnectionState() const {
201   return connectivity_state_;
202 }
203 
StartAccessPoint(const std::string & ssid)204 void ShillClient::StartAccessPoint(const std::string& ssid) {
205   LOG(INFO) << "Starting Soft AP: " << ssid;
206   ap_manager_client_->Start(ssid);
207 }
208 
StopAccessPoint()209 void ShillClient::StopAccessPoint() {
210   LOG(INFO) << "Stopping Soft AP";
211   ap_manager_client_->Stop();
212 }
213 
AddConnectionChangedCallback(const ConnectionChangedCallback & listener)214 void ShillClient::AddConnectionChangedCallback(
215     const ConnectionChangedCallback& listener) {
216   connectivity_listeners_.push_back(listener);
217 }
218 
IsMonitoredDevice(DeviceProxy * device)219 bool ShillClient::IsMonitoredDevice(DeviceProxy* device) {
220   if (device_whitelist_.empty()) {
221     return true;
222   }
223   VariantDictionary device_properties;
224   if (!device->GetProperties(&device_properties, nullptr)) {
225     LOG(ERROR) << "Devices without properties aren't whitelisted.";
226     return false;
227   }
228   auto it = device_properties.find(shill::kInterfaceProperty);
229   if (it == device_properties.end()) {
230     LOG(ERROR) << "Failed to find interface property in device properties.";
231     return false;
232   }
233   return ContainsKey(device_whitelist_, it->second.TryGet<string>());
234 }
235 
OnShillServiceOwnerChange(const string & old_owner,const string & new_owner)236 void ShillClient::OnShillServiceOwnerChange(const string& old_owner,
237                                             const string& new_owner) {
238   VLOG(1) << "Shill service owner name changed to '" << new_owner << "'";
239   if (new_owner.empty()) {
240     CleanupConnectingService();
241     devices_.clear();
242     connectivity_state_ = Network::State::kOffline;
243   } else {
244     Init();  // New service owner means shill reset!
245   }
246 }
247 
OnManagerPropertyChangeRegistration(const string & interface,const string & signal_name,bool success)248 void ShillClient::OnManagerPropertyChangeRegistration(const string& interface,
249                                                       const string& signal_name,
250                                                       bool success) {
251   VLOG(3) << "Registered ManagerPropertyChange handler.";
252   CHECK(success) << "privetd requires Manager signals.";
253   VariantDictionary properties;
254   if (!manager_proxy_.GetProperties(&properties, nullptr)) {
255     LOG(ERROR) << "Unable to get properties from Manager, waiting for "
256                   "Manager to come back online.";
257     return;
258   }
259   auto it = properties.find(shill::kDevicesProperty);
260   CHECK(it != properties.end()) << "Shill should always publish a device list.";
261   OnManagerPropertyChange(shill::kDevicesProperty, it->second);
262 }
263 
OnManagerPropertyChange(const string & property_name,const Any & property_value)264 void ShillClient::OnManagerPropertyChange(const string& property_name,
265                                           const Any& property_value) {
266   if (property_name != shill::kDevicesProperty) {
267     return;
268   }
269   bool update_connectivity = false;
270   VLOG(3) << "Manager's device list has changed.";
271   // We're going to remove every device we haven't seen in the update.
272   set<ObjectPath> device_paths_to_remove;
273   for (const auto& kv : devices_) {
274     device_paths_to_remove.insert(kv.first);
275   }
276   for (const auto& device_path : property_value.TryGet<vector<ObjectPath>>()) {
277     if (!device_path.IsValid()) {
278       LOG(ERROR) << "Ignoring invalid device path in Manager's device list.";
279       return;
280     }
281     auto it = devices_.find(device_path);
282     if (it != devices_.end()) {
283       // Found an existing proxy.  Since the whitelist never changes,
284       // this still a valid device.
285       device_paths_to_remove.erase(device_path);
286       continue;
287     }
288     std::unique_ptr<DeviceProxy> device{new DeviceProxy{bus_, device_path}};
289     if (!IsMonitoredDevice(device.get())) {
290       continue;
291     }
292     VLOG(3) << "Creating device proxy at " << device_path.value();
293     devices_[device_path].device = std::move(device);
294     update_connectivity = true;
295     devices_[device_path].device->RegisterPropertyChangedSignalHandler(
296         base::Bind(&ShillClient::OnDevicePropertyChange,
297                    weak_factory_.GetWeakPtr(), device_path),
298         base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
299                    weak_factory_.GetWeakPtr(), device_path));
300   }
301   // Clean up devices/services related to removed devices.
302   for (const ObjectPath& device_path : device_paths_to_remove) {
303     devices_.erase(device_path);
304     update_connectivity = true;
305   }
306 
307   if (update_connectivity)
308     UpdateConnectivityState();
309 }
310 
OnDevicePropertyChangeRegistration(const ObjectPath & device_path,const string & interface,const string & signal_name,bool success)311 void ShillClient::OnDevicePropertyChangeRegistration(
312     const ObjectPath& device_path,
313     const string& interface,
314     const string& signal_name,
315     bool success) {
316   VLOG(3) << "Registered DevicePropertyChange handler.";
317   auto it = devices_.find(device_path);
318   if (it == devices_.end()) {
319     return;
320   }
321   CHECK(success) << "Failed to subscribe to Device property changes.";
322   DeviceProxy* device = it->second.device.get();
323   VariantDictionary properties;
324   if (!device->GetProperties(&properties, nullptr)) {
325     LOG(WARNING) << "Failed to get device properties?";
326     return;
327   }
328   auto prop_it = properties.find(shill::kSelectedServiceProperty);
329   if (prop_it == properties.end()) {
330     LOG(WARNING) << "Failed to get device's selected service?";
331     return;
332   }
333   OnDevicePropertyChange(device_path, shill::kSelectedServiceProperty,
334                          prop_it->second);
335 }
336 
OnDevicePropertyChange(const ObjectPath & device_path,const string & property_name,const Any & property_value)337 void ShillClient::OnDevicePropertyChange(const ObjectPath& device_path,
338                                          const string& property_name,
339                                          const Any& property_value) {
340   // We only care about selected services anyway.
341   if (property_name != shill::kSelectedServiceProperty) {
342     return;
343   }
344   // If the device isn't our list of whitelisted devices, ignore it.
345   auto it = devices_.find(device_path);
346   if (it == devices_.end()) {
347     return;
348   }
349   DeviceState& device_state = it->second;
350   ObjectPath service_path{property_value.TryGet<ObjectPath>()};
351   if (!service_path.IsValid()) {
352     LOG(ERROR) << "Device at " << device_path.value()
353                << " selected invalid service path.";
354     return;
355   }
356   VLOG(3) << "Device at " << it->first.value() << " has selected service at "
357           << service_path.value();
358   bool removed_old_service{false};
359   if (device_state.selected_service) {
360     if (device_state.selected_service->GetObjectPath() == service_path) {
361       return;  // Spurious update?
362     }
363     device_state.selected_service.reset();
364     device_state.service_state = Network::State::kOffline;
365     removed_old_service = true;
366   }
367   const bool reuse_connecting_service =
368       service_path.value() != "/" && connecting_service_ &&
369       connecting_service_->GetObjectPath() == service_path;
370   if (reuse_connecting_service) {
371     device_state.selected_service = connecting_service_;
372     // When we reuse the connecting service, we need to make sure that our
373     // cached state is correct.  Normally, we do this by relying reading the
374     // state when our signal handlers finish registering, but this may have
375     // happened long in the past for the connecting service.
376     string state;
377     if (GetStateForService(connecting_service_.get(), &state)) {
378       device_state.service_state = ShillServiceStateToNetworkState(state);
379     } else {
380       LOG(WARNING) << "Failed to read properties from existing service "
381                       "on selection.";
382     }
383   } else if (service_path.value() != "/") {
384     // The device has selected a new service we haven't see before.
385     device_state.selected_service =
386         std::make_shared<ServiceProxy>(bus_, service_path);
387     device_state.selected_service->RegisterPropertyChangedSignalHandler(
388         base::Bind(&ShillClient::OnServicePropertyChange,
389                    weak_factory_.GetWeakPtr(), service_path),
390         base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
391                    weak_factory_.GetWeakPtr(), service_path));
392   }
393 
394   if (reuse_connecting_service || removed_old_service) {
395     UpdateConnectivityState();
396   }
397 }
398 
OnServicePropertyChangeRegistration(const ObjectPath & path,const string & interface,const string & signal_name,bool success)399 void ShillClient::OnServicePropertyChangeRegistration(const ObjectPath& path,
400                                                       const string& interface,
401                                                       const string& signal_name,
402                                                       bool success) {
403   VLOG(3) << "OnServicePropertyChangeRegistration(" << path.value() << ");";
404   ServiceProxy* service{nullptr};
405   if (connecting_service_ && connecting_service_->GetObjectPath() == path) {
406     // Note that the connecting service might also be a selected service.
407     service = connecting_service_.get();
408     if (!success)
409       CleanupConnectingService();
410   } else {
411     for (const auto& kv : devices_) {
412       if (kv.second.selected_service &&
413           kv.second.selected_service->GetObjectPath() == path) {
414         service = kv.second.selected_service.get();
415         break;
416       }
417     }
418   }
419   if (service == nullptr || !success) {
420     return;  // A failure or success for a proxy we no longer care about.
421   }
422   VariantDictionary properties;
423   if (!service->GetProperties(&properties, nullptr)) {
424     return;
425   }
426   // Give ourselves property changed signals for the initial property
427   // values.
428   for (auto name : {shill::kStateProperty, shill::kSignalStrengthProperty,
429                     shill::kErrorProperty}) {
430     auto it = properties.find(name);
431     if (it != properties.end())
432       OnServicePropertyChange(path, name, it->second);
433   }
434 }
435 
OnServicePropertyChange(const ObjectPath & service_path,const string & property_name,const Any & property_value)436 void ShillClient::OnServicePropertyChange(const ObjectPath& service_path,
437                                           const string& property_name,
438                                           const Any& property_value) {
439   VLOG(3) << "ServicePropertyChange(" << service_path.value() << ", "
440           << property_name << ", ...);";
441 
442   bool is_connecting_service =
443       connecting_service_ &&
444       connecting_service_->GetObjectPath() == service_path;
445   if (property_name == shill::kStateProperty) {
446     const string state{property_value.TryGet<string>()};
447     if (state.empty()) {
448       VLOG(3) << "Invalid service state update.";
449       return;
450     }
451     VLOG(3) << "New service state=" << state;
452     OnStateChangeForSelectedService(service_path, state);
453     if (is_connecting_service)
454       OnStateChangeForConnectingService(state);
455   } else if (property_name == shill::kSignalStrengthProperty) {
456     VLOG(3) << "Signal strength=" << property_value.TryGet<uint8_t>();
457     if (is_connecting_service)
458       OnStrengthChangeForConnectingService(property_value.TryGet<uint8_t>());
459   } else if (property_name == shill::kErrorProperty) {
460     VLOG(3) << "Error=" << property_value.TryGet<std::string>();
461     if (is_connecting_service)
462       connecting_service_error_ = property_value.TryGet<std::string>();
463   }
464 }
465 
OnStateChangeForConnectingService(const string & state)466 void ShillClient::OnStateChangeForConnectingService(const string& state) {
467   switch (ShillServiceStateToNetworkState(state)) {
468     case Network::State::kOnline: {
469       auto callback = connect_done_callback_;
470       connect_done_callback_.Reset();
471       CleanupConnectingService();
472 
473       if (!callback.is_null())
474         callback.Run(nullptr);
475       break;
476     }
477     case Network::State::kError: {
478       ConnectToServiceError(connecting_service_);
479       break;
480     }
481     case Network::State::kOffline:
482     case Network::State::kConnecting:
483       break;
484   }
485 }
486 
OnErrorChangeForConnectingService(const std::string & error)487 void ShillClient::OnErrorChangeForConnectingService(const std::string& error) {
488   if (error.empty())
489     return;
490 
491   auto callback = connect_done_callback_;
492   CleanupConnectingService();
493 
494   weave::ErrorPtr weave_error;
495   weave::Error::AddTo(&weave_error, FROM_HERE, error,
496                       "Failed to connect to WiFi network");
497 
498   if (!callback.is_null())
499     callback.Run(std::move(weave_error));
500 }
501 
OnStrengthChangeForConnectingService(uint8_t signal_strength)502 void ShillClient::OnStrengthChangeForConnectingService(
503     uint8_t signal_strength) {
504   if (signal_strength == 0 || have_called_connect_) {
505     return;
506   }
507   VLOG(1) << "Connecting service has signal. Calling Connect().";
508   have_called_connect_ = true;
509   // Failures here indicate that we've already connected,
510   // or are connecting, or some other very unexciting thing.
511   // Ignore all that, and rely on state changes to detect
512   // connectivity.
513   connecting_service_->Connect(nullptr);
514 }
515 
OnStateChangeForSelectedService(const ObjectPath & service_path,const string & state)516 void ShillClient::OnStateChangeForSelectedService(
517     const ObjectPath& service_path,
518     const string& state) {
519   // Find the device/service pair responsible for this update
520   VLOG(3) << "State for potentially selected service " << service_path.value()
521           << " have changed to " << state;
522   for (auto& kv : devices_) {
523     if (kv.second.selected_service &&
524         kv.second.selected_service->GetObjectPath() == service_path) {
525       VLOG(3) << "Updated cached connection state for selected service.";
526       kv.second.service_state = ShillServiceStateToNetworkState(state);
527       UpdateConnectivityState();
528       return;
529     }
530   }
531 }
532 
UpdateConnectivityState()533 void ShillClient::UpdateConnectivityState() {
534   // Update the connectivity state of the device by picking the
535   // state of the currently most connected selected service.
536   Network::State new_connectivity_state{Network::State::kOffline};
537   for (const auto& kv : devices_) {
538     if (kv.second.service_state > new_connectivity_state) {
539       new_connectivity_state = kv.second.service_state;
540     }
541   }
542   VLOG(1) << "Connectivity changed: " << EnumToString(connectivity_state_)
543           << " -> " << EnumToString(new_connectivity_state);
544   // Notify listeners even if state changed to the same value. Listeners may
545   // want to handle this event.
546   connectivity_state_ = new_connectivity_state;
547   // We may call UpdateConnectivityState whenever we mutate a data structure
548   // such that our connectivity status could change.  However, we don't want
549   // to allow people to call into ShillClient while some other operation is
550   // underway.  Therefore, call our callbacks later, when we're in a good
551   // state.
552   base::MessageLoop::current()->PostTask(
553       FROM_HERE, base::Bind(&ShillClient::NotifyConnectivityListeners,
554                             weak_factory_.GetWeakPtr(),
555                             GetConnectionState() == Network::State::kOnline));
556 }
557 
NotifyConnectivityListeners(bool am_online)558 void ShillClient::NotifyConnectivityListeners(bool am_online) {
559   VLOG(3) << "Notifying connectivity listeners that online=" << am_online;
560   for (const auto& listener : connectivity_listeners_)
561     listener.Run();
562 }
563 
CleanupConnectingService()564 void ShillClient::CleanupConnectingService() {
565   if (connecting_service_) {
566     connecting_service_->ReleaseObjectProxy(base::Bind(&IgnoreDetachEvent));
567     connecting_service_.reset();
568   }
569   connect_done_callback_.Reset();
570   have_called_connect_ = false;
571 }
572 
OpenSslSocket(const std::string & host,uint16_t port,const OpenSslSocketCallback & callback)573 void ShillClient::OpenSslSocket(const std::string& host,
574                                 uint16_t port,
575                                 const OpenSslSocketCallback& callback) {
576   if (disable_xmpp_)
577     return;
578   std::unique_ptr<weave::Stream> raw_stream{
579       SocketStream::ConnectBlocking(host, port)};
580   if (!raw_stream) {
581     brillo::ErrorPtr error;
582     brillo::errors::system::AddSystemError(&error, FROM_HERE, errno);
583     weave::ErrorPtr weave_error;
584     ConvertError(*error.get(), &weave_error);
585     base::MessageLoop::current()->PostTask(
586         FROM_HERE, base::Bind(callback, nullptr, base::Passed(&weave_error)));
587     return;
588   }
589 
590   SocketStream::TlsConnect(std::move(raw_stream), host, callback);
591 }
592 
593 }  // namespace buffet
594