libweave: Added Ubuntu example of libweave integration

This code is not intended to be used in production. It is insecure and
optimal for the sake of simplicity.

BUG=brillo:1275
TEST=none

Change-Id: I0269160f6bb1b4c523640c2e0e6e2a0e2828dd3f
Reviewed-on: https://chromium-review.googlesource.com/296407
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Trybot-Ready: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/libweave/examples/ubuntu/avahi_client.cc b/libweave/examples/ubuntu/avahi_client.cc
new file mode 100644
index 0000000..b14d45c
--- /dev/null
+++ b/libweave/examples/ubuntu/avahi_client.cc
@@ -0,0 +1,103 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "libweave/examples/ubuntu/avahi_client.h"
+
+#include <cstdlib>
+#include <vector>
+
+#include <avahi-common/error.h>
+
+namespace weave {
+namespace examples {
+
+namespace {
+
+void GroupCallback(AvahiEntryGroup* g,
+                   AvahiEntryGroupState state,
+                   AVAHI_GCC_UNUSED void* userdata) {
+  CHECK_NE(state, AVAHI_ENTRY_GROUP_COLLISION);
+  CHECK_NE(state, AVAHI_ENTRY_GROUP_FAILURE);
+}
+
+}  // namespace
+
+MdnsImpl::MdnsImpl() {
+  CHECK_EQ(0, std::system("service avahi-daemon status | grep process || "
+                          "service avahi-daemon start"));
+  thread_pool_.reset(avahi_threaded_poll_new());
+  CHECK(thread_pool_);
+
+  int ret = 0;
+  client_.reset(avahi_client_new(avahi_threaded_poll_get(thread_pool_.get()),
+                                 {}, nullptr, this, &ret));
+  CHECK(client_) << avahi_strerror(ret);
+
+  avahi_threaded_poll_start(thread_pool_.get());
+
+  group_.reset(avahi_entry_group_new(client_.get(), GroupCallback, nullptr));
+
+  CHECK(group_);
+}
+
+MdnsImpl::~MdnsImpl() {
+  if (thread_pool_)
+    avahi_threaded_poll_stop(thread_pool_.get());
+}
+
+void MdnsImpl::PublishService(const std::string& service_name,
+                              uint16_t port,
+                              const std::map<std::string, std::string>& txt) {
+  LOG(INFO) << "Publishing service";
+  CHECK(group_);
+
+  // Create txt record.
+  std::unique_ptr<AvahiStringList, decltype(&avahi_string_list_free)> txt_list{
+      nullptr, &avahi_string_list_free};
+  if (!txt.empty()) {
+    std::vector<std::string> txt_vector;
+    for (const auto& i : txt)
+      txt_vector.push_back(i.first + "=" + i.second);
+    std::vector<const char*> txt_vector_ptr;
+    for (const auto& i : txt_vector)
+      txt_vector_ptr.push_back(i.c_str());
+    txt_list.reset(avahi_string_list_new_from_array(txt_vector_ptr.data(),
+                                                    txt_vector_ptr.size()));
+    CHECK(txt_list);
+  }
+
+  int ret = 0;
+  if (prev_port_ == port && prev_type_ == service_name) {
+    ret = avahi_entry_group_update_service_txt_strlst(
+        group_.get(), AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, {}, GetId().c_str(),
+        ("_" + service_name + "._tcp").c_str(), nullptr, txt_list.get());
+    CHECK_GE(ret, 0) << avahi_strerror(ret);
+  } else {
+    prev_port_ = port;
+    prev_type_ = service_name;
+
+    avahi_entry_group_reset(group_.get());
+    CHECK(avahi_entry_group_is_empty(group_.get()));
+
+    ret = avahi_entry_group_add_service_strlst(
+        group_.get(), AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, {}, GetId().c_str(),
+        ("_" + service_name + "._tcp").c_str(), nullptr, nullptr, port,
+        txt_list.get());
+    CHECK_GE(ret, 0) << avahi_strerror(ret);
+    ret = avahi_entry_group_commit(group_.get());
+    CHECK_GE(ret, 0) << avahi_strerror(ret);
+  }
+}
+
+void MdnsImpl::StopPublishing(const std::string& service_name) {
+  CHECK(group_);
+  avahi_entry_group_reset(group_.get());
+}
+
+std::string MdnsImpl::GetId() const {
+  return "Weave example " + std::to_string(gethostid());
+}
+
+}  // namespace examples
+}  // namespace weave
diff --git a/libweave/examples/ubuntu/avahi_client.h b/libweave/examples/ubuntu/avahi_client.h
new file mode 100644
index 0000000..6b5d1b0
--- /dev/null
+++ b/libweave/examples/ubuntu/avahi_client.h
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_EXAMPLES_UBUNTU_AVAHI_CLIENT_H_
+#define LIBWEAVE_EXAMPLES_UBUNTU_AVAHI_CLIENT_H_
+
+#include <map>
+#include <string>
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/thread-watch.h>
+
+#include <weave/mdns.h>
+
+namespace weave {
+namespace examples {
+
+// Example of weave::Mdns implemented with avahi.
+class MdnsImpl : public Mdns {
+ public:
+  MdnsImpl();
+
+  ~MdnsImpl() override;
+  void PublishService(const std::string& service_name,
+                      uint16_t port,
+                      const std::map<std::string, std::string>& txt) override;
+  void StopPublishing(const std::string& service_name) override;
+  std::string GetId() const override;
+
+  uint16_t prev_port_{0};
+  std::string prev_type_;
+
+  std::unique_ptr<AvahiThreadedPoll, decltype(&avahi_threaded_poll_free)>
+      thread_pool_{nullptr, &avahi_threaded_poll_free};
+
+  std::unique_ptr<AvahiClient, decltype(&avahi_client_free)> client_{
+      nullptr, &avahi_client_free};
+
+  std::unique_ptr<AvahiEntryGroup, decltype(&avahi_entry_group_free)> group_{
+      nullptr, &avahi_entry_group_free};
+};
+
+}  // namespace examples
+}  // namespace weave
+
+#endif  // LIBWEAVE_EXAMPLES_UBUNTU_AVAHI_CLIENT_H_
diff --git a/libweave/examples/ubuntu/curl_http_client.cc b/libweave/examples/ubuntu/curl_http_client.cc
new file mode 100644
index 0000000..3ab3497
--- /dev/null
+++ b/libweave/examples/ubuntu/curl_http_client.cc
@@ -0,0 +1,146 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "libweave/examples/ubuntu/curl_http_client.h"
+
+#include <base/bind.h>
+#include <curl/curl.h>
+#include <weave/task_runner.h>
+
+namespace weave {
+namespace examples {
+
+namespace {
+
+struct ResponseImpl : public HttpClient::Response {
+  int GetStatusCode() const { return status; }
+  std::string GetContentType() const { return content_type; }
+  const std::string& GetData() const { return data; }
+
+  int status;
+  std::string content_type;
+  std::string data;
+};
+
+size_t WriteFunction(void* contents, size_t size, size_t nmemb, void* userp) {
+  static_cast<std::string*>(userp)
+      ->append(static_cast<const char*>(contents), size * nmemb);
+  return size * nmemb;
+}
+
+}  // namespace
+
+CurlHttpClient::CurlHttpClient(TaskRunner* task_runner)
+    : task_runner_{task_runner} {}
+
+std::unique_ptr<HttpClient::Response> CurlHttpClient::SendRequestAndBlock(
+    const std::string& method,
+    const std::string& url,
+    const Headers& headers,
+    const std::string& data,
+    ErrorPtr* error) {
+  std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl{curl_easy_init(),
+                                                           &curl_easy_cleanup};
+  CHECK(curl);
+
+  if (method == "GET") {
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPGET, 1L));
+  } else if (method == "POST") {
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPPOST, 1L));
+  } else {
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_CUSTOMREQUEST,
+                                        method.c_str()));
+  }
+
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()));
+
+  curl_slist* chunk = nullptr;
+  for (const auto& h : headers)
+    chunk = curl_slist_append(chunk, (h.first + ": " + h.second).c_str());
+
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, chunk));
+
+  if (!data.empty() || method == "POST") {
+    CHECK_EQ(CURLE_OK,
+             curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, data.c_str()));
+  }
+
+  std::unique_ptr<ResponseImpl> response{new ResponseImpl};
+  CHECK_EQ(CURLE_OK,
+           curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, &WriteFunction));
+  CHECK_EQ(CURLE_OK,
+           curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response->data));
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION,
+                                      &WriteFunction));
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA,
+                                      &response->content_type));
+
+  CURLcode res = curl_easy_perform(curl.get());
+  if (chunk)
+    curl_slist_free_all(chunk);
+
+  if (res != CURLE_OK) {
+    Error::AddTo(error, FROM_HERE, "curl", "curl_easy_perform_error",
+                 curl_easy_strerror(res));
+    return nullptr;
+  }
+
+  const std::string kContentType = "\r\nContent-Type:";
+  auto pos = response->content_type.find(kContentType);
+  if (pos == std::string::npos) {
+    Error::AddTo(error, FROM_HERE, "curl", "no_content_header",
+                 "Content-Type header is missing");
+    return nullptr;
+  }
+  pos += kContentType.size();
+  auto pos_end = response->content_type.find("\r\n", pos);
+  if (pos_end == std::string::npos) {
+    pos_end = response->content_type.size();
+  }
+
+  response->content_type = response->content_type.substr(pos, pos_end);
+
+  CHECK_EQ(CURLE_OK, curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE,
+                                       &response->status));
+  return std::move(response);
+}
+
+int CurlHttpClient::SendRequest(const std::string& method,
+                                const std::string& url,
+                                const Headers& headers,
+                                const std::string& data,
+                                const SuccessCallback& success_callback,
+                                const ErrorCallback& error_callback) {
+  ++request_id_;
+  ErrorPtr error;
+  auto response = SendRequestAndBlock(method, url, headers, data, &error);
+  if (response) {
+    task_runner_->PostDelayedTask(
+        FROM_HERE, base::Bind(&CurlHttpClient::RunSuccessCallback,
+                              weak_ptr_factory_.GetWeakPtr(), success_callback,
+                              request_id_, base::Passed(&response)),
+        {});
+    return request_id_;
+  }
+  task_runner_->PostDelayedTask(
+      FROM_HERE, base::Bind(&CurlHttpClient::RunErrorCallback,
+                            weak_ptr_factory_.GetWeakPtr(), error_callback,
+                            request_id_, base::Passed(&error)),
+      {});
+}
+
+void CurlHttpClient::RunSuccessCallback(const SuccessCallback& success_callback,
+                                        int id,
+                                        std::unique_ptr<Response> response) {
+  success_callback.Run(id, *response);
+}
+
+void CurlHttpClient::RunErrorCallback(const ErrorCallback& error_callback,
+                                      int id,
+                                      ErrorPtr error) {
+  error_callback.Run(id, error.get());
+}
+
+}  // namespace examples
+}  // namespace weave
diff --git a/libweave/examples/ubuntu/curl_http_client.h b/libweave/examples/ubuntu/curl_http_client.h
new file mode 100644
index 0000000..45e13dc
--- /dev/null
+++ b/libweave/examples/ubuntu/curl_http_client.h
@@ -0,0 +1,54 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_EXAMPLES_UBUNTU_CURL_HTTP_CLIENT_H_
+#define LIBWEAVE_EXAMPLES_UBUNTU_CURL_HTTP_CLIENT_H_
+
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+#include <weave/http_client.h>
+
+namespace weave {
+
+class TaskRunner;
+
+namespace examples {
+
+// Basic implementation of weave::HttpClient using libcurl. Should be used in
+// production code as it's blocking and does not validate server certificates.
+class CurlHttpClient : public HttpClient {
+ public:
+  explicit CurlHttpClient(TaskRunner* task_runner);
+
+  std::unique_ptr<Response> SendRequestAndBlock(const std::string& method,
+                                                const std::string& url,
+                                                const Headers& headers,
+                                                const std::string& data,
+                                                ErrorPtr* error) override;
+  int SendRequest(const std::string& method,
+                  const std::string& url,
+                  const Headers& headers,
+                  const std::string& data,
+                  const SuccessCallback& success_callback,
+                  const ErrorCallback& error_callback) override;
+
+ private:
+  void RunSuccessCallback(const SuccessCallback& success_callback,
+                          int id,
+                          std::unique_ptr<Response> response);
+  void RunErrorCallback(const ErrorCallback& error_callback,
+                        int id,
+                        ErrorPtr error);
+
+  TaskRunner* task_runner_{nullptr};
+  int request_id_ = 0;
+
+  base::WeakPtrFactory<CurlHttpClient> weak_ptr_factory_{this};
+};
+
+}  // namespace examples
+}  // namespace weave
+
+#endif  // LIBWEAVE_EXAMPLES_UBUNTU_CURL_HTTP_CLIENT_H_
diff --git a/libweave/examples/ubuntu/event_http_server.cc b/libweave/examples/ubuntu/event_http_server.cc
new file mode 100644
index 0000000..a8e2139
--- /dev/null
+++ b/libweave/examples/ubuntu/event_http_server.cc
@@ -0,0 +1,185 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "libweave/examples/ubuntu/event_http_server.h"
+
+#include <vector>
+
+#include <base/bind.h>
+#include <base/time/time.h>
+#include <event2/bufferevent_ssl.h>
+#include <openssl/err.h>
+
+namespace weave {
+namespace examples {
+
+namespace {
+
+std::string GetSslError() {
+  char error[1000] = {};
+  ERR_error_string_n(ERR_get_error(), error, sizeof(error));
+  return error;
+}
+
+bufferevent* BuffetEventCallback(event_base* base, void* arg) {
+  SSL_CTX* ctx = static_cast<SSL_CTX*>(arg);
+  return bufferevent_openssl_socket_new(
+      base, -1, SSL_new(ctx), BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE);
+}
+
+}  // namespace
+
+class HttpServerImpl::RequestImpl : public Request {
+ public:
+  explicit RequestImpl(evhttp_request* req) : path_{evhttp_request_uri(req)} {
+    path_ = path_.substr(0, path_.find("?"));
+    path_ = path_.substr(0, path_.find("#"));
+    req_.reset(req);
+
+    data_.resize(evbuffer_get_length(req_->input_buffer));
+    evbuffer_remove(req_->input_buffer, data_.data(), data_.size());
+  }
+
+  ~RequestImpl() {}
+
+  const std::string& GetPath() const override { return path_; }
+  std::string GetFirstHeader(const std::string& name) const override {
+    const char* header = evhttp_find_header(req_->input_headers, name.c_str());
+    if (!header)
+      return {};
+    return header;
+  }
+  const std::vector<uint8_t>& GetData() const override { return data_; }
+
+  evhttp_request* ReleaseHandler() { return req_.release(); }
+
+ private:
+  std::vector<uint8_t> data_;
+  std::unique_ptr<evhttp_request, decltype(&evhttp_cancel_request)> req_{
+      nullptr, &evhttp_cancel_request};
+  std::string path_;
+};
+
+HttpServerImpl::HttpServerImpl(event_base* base) : base_{base} {
+  SSL_load_error_strings();
+  SSL_library_init();
+
+  ctx_.reset(SSL_CTX_new(TLSv1_2_server_method()));
+  SSL_CTX_set_options(ctx_.get(), SSL_OP_SINGLE_DH_USE |
+                                      SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_SSLv2);
+
+  ec_key_.reset(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  CHECK(ec_key_) << GetSslError();
+  CHECK_EQ(1, SSL_CTX_set_tmp_ecdh(ctx_.get(), ec_key_.get())) << GetSslError();
+
+  GenerateX509();
+  CHECK_EQ(1, SSL_CTX_use_PrivateKey(ctx_.get(), pkey_.get())) << GetSslError();
+  CHECK_EQ(1, SSL_CTX_use_certificate(ctx_.get(), x509_.get()))
+      << GetSslError();
+
+  CHECK_EQ(1, SSL_CTX_check_private_key(ctx_.get())) << GetSslError();
+
+  httpd_.reset(evhttp_new(base_));
+  CHECK(httpd_);
+  httpsd_.reset(evhttp_new(base_));
+  CHECK(httpsd_);
+
+  evhttp_set_bevcb(httpsd_.get(), BuffetEventCallback, ctx_.get());
+  evhttp_set_gencb(httpd_.get(), ProcessRequestCallback, this);
+  evhttp_set_gencb(httpsd_.get(), ProcessRequestCallback, this);
+
+  CHECK_EQ(0, evhttp_bind_socket(httpd_.get(), "0.0.0.0", GetHttpPort()));
+  CHECK_EQ(0, evhttp_bind_socket(httpsd_.get(), "0.0.0.0", GetHttpsPort()));
+}
+
+void HttpServerImpl::GenerateX509() {
+  x509_.reset(X509_new());
+  CHECK(x509_) << GetSslError();
+
+  X509_set_version(x509_.get(), 2);
+
+  X509_gmtime_adj(X509_get_notBefore(x509_.get()), 0);
+  X509_gmtime_adj(X509_get_notAfter(x509_.get()),
+                  base::TimeDelta::FromDays(365).InSeconds());
+
+  pkey_.reset(EVP_PKEY_new());
+  CHECK(pkey_) << GetSslError();
+  std::unique_ptr<BIGNUM, decltype(&BN_free)> big_num(BN_new(), &BN_free);
+  CHECK(BN_set_word(big_num.get(), 65537)) << GetSslError();
+  auto rsa = RSA_new();
+  RSA_generate_key_ex(rsa, 2048, big_num.get(), nullptr);
+  CHECK(EVP_PKEY_assign_RSA(pkey_.get(), rsa)) << GetSslError();
+
+  X509_set_pubkey(x509_.get(), pkey_.get());
+
+  CHECK(X509_sign(x509_.get(), pkey_.get(), EVP_sha256())) << GetSslError();
+
+  cert_fingerprint_.resize(EVP_MD_size(EVP_sha256()));
+  uint32_t len = 0;
+  CHECK(X509_digest(x509_.get(), EVP_sha256(), cert_fingerprint_.data(), &len));
+  CHECK_EQ(len, cert_fingerprint_.size());
+}
+
+void HttpServerImpl::ProcessRequestCallback(evhttp_request* req, void* arg) {
+  static_cast<HttpServerImpl*>(arg)->ProcessRequest(req);
+}
+
+void HttpServerImpl::NotFound(evhttp_request* req) {
+  std::unique_ptr<evbuffer, decltype(&evbuffer_free)> buf{evbuffer_new(),
+                                                          &evbuffer_free};
+  evbuffer_add_printf(buf.get(), "404 Not Found: %s\n",
+                      evhttp_request_uri(req));
+  evhttp_send_reply(req, 404, "Not Found", buf.get());
+}
+
+void HttpServerImpl::ProcessRequest(evhttp_request* req) {
+  std::string path = evhttp_request_uri(req);
+  for (auto i = handlers_.rbegin(); i != handlers_.rend(); ++i) {
+    if (path.compare(0, i->first.size(), i->first) == 0) {
+      auto request = std::make_shared<RequestImpl>(req);
+      i->second.Run(*request,
+                    base::Bind(&HttpServerImpl::ProcessReply,
+                               weak_ptr_factory_.GetWeakPtr(), request));
+      return;
+    }
+  }
+  NotFound(req);
+}
+void HttpServerImpl::ProcessReply(std::shared_ptr<RequestImpl> request,
+                                  int status_code,
+                                  const std::string& data,
+                                  const std::string& mime_type) {
+  std::unique_ptr<evbuffer, decltype(&evbuffer_free)> buf{evbuffer_new(),
+                                                          &evbuffer_free};
+  evbuffer_add(buf.get(), data.data(), data.size());
+  evhttp_request* req = request->ReleaseHandler();
+  evhttp_add_header(req->output_headers, "Content-Type", mime_type.c_str());
+  evhttp_send_reply(req, status_code, "None", buf.get());
+}
+
+void HttpServerImpl::AddOnStateChangedCallback(
+    const OnStateChangedCallback& callback) {
+  callback.Run(*this);
+}
+
+void HttpServerImpl::AddRequestHandler(const std::string& path_prefix,
+                                       const OnRequestCallback& callback) {
+  handlers_.emplace(path_prefix, callback);
+}
+
+uint16_t HttpServerImpl::GetHttpPort() const {
+  return 7780;
+}
+
+uint16_t HttpServerImpl::GetHttpsPort() const {
+  return 7781;
+}
+
+const std::vector<uint8_t>& HttpServerImpl::GetHttpsCertificateFingerprint()
+    const {
+  return cert_fingerprint_;
+}
+
+}  // namespace examples
+}  // namespace weave
diff --git a/libweave/examples/ubuntu/event_http_server.h b/libweave/examples/ubuntu/event_http_server.h
new file mode 100644
index 0000000..32d567e
--- /dev/null
+++ b/libweave/examples/ubuntu/event_http_server.h
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_EXAMPLES_UBUNTU_EVENT_HTTP_SERVER_H_
+#define LIBWEAVE_EXAMPLES_UBUNTU_EVENT_HTTP_SERVER_H_
+
+#include <event2/http.h>
+#include <evhttp.h>
+#include <openssl/ssl.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <weave/http_server.h>
+
+namespace weave {
+namespace examples {
+
+// HTTP/HTTPS server implemented with libevent.
+class HttpServerImpl : public HttpServer {
+ public:
+  class RequestImpl;
+
+  explicit HttpServerImpl(event_base* base);
+
+  void AddOnStateChangedCallback(
+      const OnStateChangedCallback& callback) override;
+  void AddRequestHandler(const std::string& path_prefix,
+                         const OnRequestCallback& callback) override;
+  uint16_t GetHttpPort() const override;
+  uint16_t GetHttpsPort() const override;
+  const std::vector<uint8_t>& GetHttpsCertificateFingerprint() const override;
+
+ private:
+  void GenerateX509();
+  static void ProcessRequestCallback(evhttp_request* req, void* arg);
+  void ProcessRequest(evhttp_request* req);
+  void ProcessReply(std::shared_ptr<RequestImpl> request,
+                    int status_code,
+                    const std::string& data,
+                    const std::string& mime_type);
+  void NotFound(evhttp_request* req);
+
+  std::map<std::string, OnRequestCallback> handlers_;
+
+  std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)> ec_key_{nullptr,
+                                                          &EC_KEY_free};
+
+  std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> pkey_{nullptr,
+                                                            &EVP_PKEY_free};
+
+  std::unique_ptr<X509, decltype(&X509_free)> x509_{nullptr, &X509_free};
+
+  std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)> ctx_{nullptr,
+                                                         &SSL_CTX_free};
+  std::vector<uint8_t> cert_fingerprint_;
+  event_base* base_{nullptr};
+  std::unique_ptr<evhttp, decltype(&evhttp_free)> httpd_{nullptr, &evhttp_free};
+  std::unique_ptr<evhttp, decltype(&evhttp_free)> httpsd_{nullptr,
+                                                          &evhttp_free};
+  base::WeakPtrFactory<HttpServerImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace examples
+}  // namespace weave
+
+#endif  // LIBWEAVE_EXAMPLES_UBUNTU_EVENT_HTTP_SERVER_H_
diff --git a/libweave/examples/ubuntu/event_task_runner.cc b/libweave/examples/ubuntu/event_task_runner.cc
new file mode 100644
index 0000000..e80d7a5
--- /dev/null
+++ b/libweave/examples/ubuntu/event_task_runner.cc
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "libweave/examples/ubuntu/event_task_runner.h"
+
+#include <signal.h>
+
+namespace weave {
+namespace examples {
+
+namespace {
+event_base* g_event_base = nullptr;
+}
+
+void EventTaskRunner::PostDelayedTask(
+    const tracked_objects::Location& from_here,
+    const base::Closure& task,
+    base::TimeDelta delay) {
+  base::Time new_time = base::Time::Now() + delay;
+  if (queue_.empty() || new_time < queue_.top().first.first) {
+    ReScheduleEvent(delay);
+  }
+  queue_.emplace(std::make_pair(new_time, ++counter_), task);
+}
+
+void EventTaskRunner::Run() {
+  g_event_base = base_.get();
+
+  struct sigaction sa = {};
+  sa.sa_handler = [](int signal) {
+    event_base_loopexit(g_event_base, nullptr);
+  };
+  sigfillset(&sa.sa_mask);
+  sigaction(SIGINT, &sa, nullptr);
+
+  event_base_loop(g_event_base, EVLOOP_NO_EXIT_ON_EMPTY);
+  g_event_base = nullptr;
+}
+
+void EventTaskRunner::ReScheduleEvent(base::TimeDelta delay) {
+  timespec ts = delay.ToTimeSpec();
+  timeval tv = {ts.tv_sec, ts.tv_nsec / 1000};
+  event_add(task_event_.get(), &tv);
+}
+
+void EventTaskRunner::EventHandler(int, int16_t, void* runner) {
+  static_cast<EventTaskRunner*>(runner)->Process();
+}
+
+void EventTaskRunner::FreeEvent(event* evnt) {
+  event_del(evnt);
+  event_free(evnt);
+}
+
+void EventTaskRunner::Process() {
+  while (!queue_.empty() && queue_.top().first.first <= base::Time::Now()) {
+    auto cb = queue_.top().second;
+    queue_.pop();
+    cb.Run();
+  }
+  if (!queue_.empty()) {
+    base::TimeDelta delta = std::max(
+        base::TimeDelta(), queue_.top().first.first - base::Time::Now());
+    ReScheduleEvent(delta);
+  }
+}
+
+}  // namespace examples
+}  // namespace weave
diff --git a/libweave/examples/ubuntu/event_task_runner.h b/libweave/examples/ubuntu/event_task_runner.h
new file mode 100644
index 0000000..726a621
--- /dev/null
+++ b/libweave/examples/ubuntu/event_task_runner.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_EXAMPLES_UBUNTU_EVENT_TASK_RUNNER_H_
+#define LIBWEAVE_EXAMPLES_UBUNTU_EVENT_TASK_RUNNER_H_
+
+#include <queue>
+#include <utility>
+#include <vector>
+
+#include <event2/event.h>
+
+#include <weave/task_runner.h>
+
+namespace weave {
+namespace examples {
+
+// Simple task runner implemented with libevent message loop.
+class EventTaskRunner : public TaskRunner {
+ public:
+  void PostDelayedTask(const tracked_objects::Location& from_here,
+                       const base::Closure& task,
+                       base::TimeDelta delay) override;
+
+  event_base* GetEventBase() const { return base_.get(); }
+
+  void Run();
+
+ private:
+  void ReScheduleEvent(base::TimeDelta delay);
+  static void EventHandler(int, int16_t, void* runner);
+  static void FreeEvent(event* evnt);
+  void Process();
+
+  using QueueItem = std::pair<std::pair<base::Time, size_t>, base::Closure>;
+
+  struct Greater {
+    bool operator()(const QueueItem& a, const QueueItem& b) const {
+      return a.first > b.first;
+    }
+  };
+
+  size_t counter_{0};  // Keeps order of tasks with the same time.
+
+  std::priority_queue<QueueItem,
+                      std::vector<QueueItem>,
+                      EventTaskRunner::Greater> queue_;
+
+  std::unique_ptr<event_base, decltype(&event_base_free)> base_{
+      event_base_new(), &event_base_free};
+  std::unique_ptr<event, decltype(&FreeEvent)> task_event_{
+      event_new(base_.get(), -1, EV_TIMEOUT, &EventHandler, this), &FreeEvent};
+};
+
+}  // namespace examples
+}  // namespace weave
+
+#endif  // LIBWEAVE_EXAMPLES_UBUNTU_EVENT_TASK_RUNNER_H_
diff --git a/libweave/examples/ubuntu/file_config_store.cc b/libweave/examples/ubuntu/file_config_store.cc
new file mode 100644
index 0000000..b2bfd0a
--- /dev/null
+++ b/libweave/examples/ubuntu/file_config_store.cc
@@ -0,0 +1,134 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "libweave/examples/ubuntu/file_config_store.h"
+
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <fstream>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace weave {
+namespace examples {
+
+const char kSettingsDir[] = "/var/lib/weave/";
+const char kSettingsPath[] = "/var/lib/weave/weave_settings.json";
+
+bool FileConfigStore::LoadDefaults(Settings* settings) {
+  char host_name[HOST_NAME_MAX] = {};
+  gethostname(host_name, HOST_NAME_MAX);
+
+  settings->name = host_name;
+  settings->description = "";
+
+  utsname uname_data;
+  uname(&uname_data);
+
+  settings->firmware_version = uname_data.sysname;
+  settings->oem_name = "Unknown";
+  settings->model_name = "Unknown";
+  settings->model_id = "AAAAA";
+  settings->pairing_modes = {PairingType::kEmbeddedCode};
+  settings->embedded_code = "0000";
+  return true;
+}
+
+std::string FileConfigStore::LoadSettings() {
+  LOG(INFO) << "Loading settings from " << kSettingsPath;
+  std::ifstream str(kSettingsPath);
+  return std::string(std::istreambuf_iterator<char>(str),
+                     std::istreambuf_iterator<char>());
+}
+
+void FileConfigStore::SaveSettings(const std::string& settings) {
+  CHECK(mkdir(kSettingsDir, S_IRWXU) == 0 || errno == EEXIST);
+  LOG(INFO) << "Saving settings to " << kSettingsPath;
+  std::ofstream str(kSettingsPath);
+  str << settings;
+}
+
+void FileConfigStore::OnSettingsChanged(const Settings& settings) {
+  LOG(INFO) << "OnSettingsChanged";
+}
+
+std::string FileConfigStore::LoadBaseCommandDefs() {
+  return R"({
+    "base": {
+      "updateBaseConfiguration": {
+        "minimalRole": "manager",
+        "parameters": {
+          "localDiscoveryEnabled": "boolean",
+          "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+          "localPairingEnabled": "boolean"
+        },
+        "results": {}
+      },
+      "identify": {
+        "minimalRole": "user",
+        "parameters": {},
+        "results": {}
+      },
+      "updateDeviceInfo": {
+        "minimalRole": "manager",
+        "parameters": {
+          "description": "string",
+          "name": "string",
+          "location": "string"
+        },
+        "results": {}
+      }
+    }
+  })";
+}
+
+std::map<std::string, std::string> FileConfigStore::LoadCommandDefs() {
+  return {{"example", R"({
+    "base": {
+      "updateBaseConfiguration": {},
+      "identify": {},
+      "updateDeviceInfo": {}
+    }
+  })"}};
+}
+
+std::string FileConfigStore::LoadBaseStateDefs() {
+  return R"({
+    "base": {
+      "firmwareVersion": "string",
+      "localDiscoveryEnabled": "boolean",
+      "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+      "localPairingEnabled": "boolean",
+      "network": {
+        "properties": {
+          "name": "string"
+        }
+      }
+    }
+  })";
+}
+
+std::string FileConfigStore::LoadBaseStateDefaults() {
+  return R"({
+    "base": {
+      "firmwareVersion": "unknown",
+      "localDiscoveryEnabled": false,
+      "localAnonymousAccessMaxRole": "none",
+      "localPairingEnabled": false
+    }
+  })";
+}
+
+std::map<std::string, std::string> FileConfigStore::LoadStateDefs() {
+  return {};
+}
+
+std::vector<std::string> FileConfigStore::LoadStateDefaults() {
+  return {};
+}
+
+}  // namespace examples
+}  // namespace weave
diff --git a/libweave/examples/ubuntu/file_config_store.h b/libweave/examples/ubuntu/file_config_store.h
new file mode 100644
index 0000000..f6e9b5f
--- /dev/null
+++ b/libweave/examples/ubuntu/file_config_store.h
@@ -0,0 +1,34 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_EXAMPLES_UBUNTU_FILE_CONFIG_STORE_H_
+#define LIBWEAVE_EXAMPLES_UBUNTU_FILE_CONFIG_STORE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <weave/config_store.h>
+
+namespace weave {
+namespace examples {
+
+class FileConfigStore : public ConfigStore {
+ public:
+  bool LoadDefaults(Settings* settings) override;
+  std::string LoadSettings() override;
+  void SaveSettings(const std::string& settings) override;
+  void OnSettingsChanged(const Settings& settings) override;
+  std::string LoadBaseCommandDefs() override;
+  std::map<std::string, std::string> LoadCommandDefs() override;
+  std::string LoadBaseStateDefs() override;
+  std::string LoadBaseStateDefaults() override;
+  std::map<std::string, std::string> LoadStateDefs() override;
+  std::vector<std::string> LoadStateDefaults() override;
+};
+
+}  // namespace examples
+}  // namespace weave
+
+#endif  // LIBWEAVE_EXAMPLES_UBUNTU_FILE_CONFIG_STORE_H_
diff --git a/libweave/examples/ubuntu/main.cc b/libweave/examples/ubuntu/main.cc
new file mode 100644
index 0000000..b42ab66
--- /dev/null
+++ b/libweave/examples/ubuntu/main.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <weave/device.h>
+
+#include "libweave/examples/ubuntu/avahi_client.h"
+#include "libweave/examples/ubuntu/curl_http_client.h"
+#include "libweave/examples/ubuntu/event_http_server.h"
+#include "libweave/examples/ubuntu/event_task_runner.h"
+#include "libweave/examples/ubuntu/file_config_store.h"
+#include "libweave/examples/ubuntu/network_manager.h"
+
+int main() {
+  weave::examples::FileConfigStore config_store;
+  weave::examples::EventTaskRunner task_runner;
+  weave::examples::CurlHttpClient http_client{&task_runner};
+  weave::examples::NetworkImpl network{&task_runner};
+  weave::examples::MdnsImpl mdns;
+  weave::examples::HttpServerImpl http_server{task_runner.GetEventBase()};
+
+  auto device = weave::Device::Create();
+  weave::Device::Options opts;
+  opts.xmpp_enabled = true;
+  opts.disable_privet = false;
+  opts.disable_security = false;
+  opts.enable_ping = true;
+  device->Start(opts, &config_store, &task_runner, &http_client, &network,
+                &mdns, &http_server);
+
+  task_runner.Run();
+
+  LOG(INFO) << "exit";
+  return 0;
+}
diff --git a/libweave/examples/ubuntu/network_manager.cc b/libweave/examples/ubuntu/network_manager.cc
new file mode 100644
index 0000000..48c817c
--- /dev/null
+++ b/libweave/examples/ubuntu/network_manager.cc
@@ -0,0 +1,534 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "libweave/examples/ubuntu/network_manager.h"
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <linux/wireless.h>
+#include <netdb.h>
+#include <openssl/ssl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <cstdlib>
+#include <fstream>
+
+#include <base/bind.h>
+#include <weave/task_runner.h>
+
+namespace weave {
+namespace examples {
+
+namespace {
+
+int ForkCmd(const std::string& path, const std::vector<std::string>& args) {
+  int pid = fork();
+  if (pid != 0)
+    return pid;
+
+  std::vector<const char*> args_vector;
+  args_vector.push_back(path.c_str());
+  for (auto& i : args)
+    args_vector.push_back(i.c_str());
+  args_vector.push_back(nullptr);
+
+  execvp(path.c_str(), const_cast<char**>(args_vector.data()));
+  NOTREACHED();
+}
+
+class SocketStream : public Stream {
+ public:
+  explicit SocketStream(TaskRunner* task_runner) : task_runner_{task_runner} {}
+
+  ~SocketStream() { CloseBlocking(nullptr); }
+
+  void RunDelayedTask(const base::Closure& success_callback) {
+    success_callback.Run();
+  }
+
+  bool ReadAsync(void* buffer,
+                 size_t size_to_read,
+                 const base::Callback<void(size_t)>& success_callback,
+                 const base::Callback<void(const Error*)>& error_callback,
+                 ErrorPtr* error) {
+    if (socket_fd_ < 0) {
+      Error::AddTo(error, FROM_HERE, "socket", "invalid_socket",
+                   strerror(errno));
+      return false;
+    }
+    int size_read = recv(socket_fd_, buffer, size_to_read, MSG_DONTWAIT);
+    if (size_read > 0) {
+      task_runner_->PostDelayedTask(
+          FROM_HERE, base::Bind(&SocketStream::RunDelayedTask,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                base::Bind(success_callback, size_read)),
+          {});
+      return true;
+    }
+    if (errno == EAGAIN || errno == EWOULDBLOCK) {
+      task_runner_->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(base::IgnoreResult(&SocketStream::ReadAsync),
+                     weak_ptr_factory_.GetWeakPtr(), buffer, size_to_read,
+                     success_callback, error_callback, nullptr),
+          base::TimeDelta::FromMilliseconds(200));
+      return true;
+    }
+
+    ErrorPtr recv_error;
+    Error::AddTo(&recv_error, FROM_HERE, "socket", "socket_recv_failed",
+                 strerror(errno));
+    task_runner_->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(error_callback, base::Owned(recv_error.release())), {});
+    return true;
+  }
+
+  bool WriteAllAsync(const void* buffer,
+                     size_t size_to_write,
+                     const base::Closure& success_callback,
+                     const base::Callback<void(const Error*)>& error_callback,
+                     ErrorPtr* error) {
+    if (socket_fd_ < 0) {
+      Error::AddTo(error, FROM_HERE, "socket", "invalid_socket",
+                   strerror(errno));
+      return false;
+    }
+    const char* buffer_ptr = static_cast<const char*>(buffer);
+    do {
+      int size_sent = send(socket_fd_, buffer_ptr, size_to_write, 0);
+      if (size_sent <= 0) {
+        ErrorPtr send_error;
+        Error::AddTo(&send_error, FROM_HERE, "socket", "socket_send_failed",
+                     strerror(errno));
+        task_runner_->PostDelayedTask(
+            FROM_HERE,
+            base::Bind(error_callback, base::Owned(send_error.release())), {});
+        // Still true as we return error with callback.
+        return true;
+      }
+      size_to_write -= size_sent;
+      buffer_ptr += size_sent;
+    } while (size_to_write > 0);
+
+    task_runner_->PostDelayedTask(FROM_HERE, success_callback, {});
+    return true;
+  }
+  bool FlushBlocking(ErrorPtr* error) { return true; }
+
+  bool CloseBlocking(ErrorPtr* error) {
+    weak_ptr_factory_.InvalidateWeakPtrs();
+    if (socket_fd_ >= 0) {
+      close(socket_fd_);
+      socket_fd_ = -1;
+    }
+  }
+
+  void CancelPendingAsyncOperations() {
+    weak_ptr_factory_.InvalidateWeakPtrs();
+  }
+
+  bool Connect(const std::string& host, uint16_t port) {
+    std::string service = std::to_string(port);
+    addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM};
+    addrinfo* result = nullptr;
+    if (getaddrinfo(host.c_str(), service.c_str(), &hints, &result)) {
+      LOG(ERROR) << "Failed to resolve host name: " << host;
+      return false;
+    }
+    std::unique_ptr<addrinfo, decltype(&freeaddrinfo)> result_deleter{
+        result, &freeaddrinfo};
+
+    for (const addrinfo* info = result; info != nullptr; info = info->ai_next) {
+      socket_fd_ =
+          socket(info->ai_family, info->ai_socktype, info->ai_protocol);
+      if (socket_fd_ < 0)
+        continue;
+
+      int flags = fcntl(socket_fd_, F_GETFL, 0);
+      if (flags == -1)
+        flags = 0;
+      fcntl(socket_fd_, F_SETFL, flags | O_NONBLOCK);
+
+      LOG(INFO) << "Connecting...";
+      if (connect(socket_fd_, info->ai_addr, info->ai_addrlen) == 0)
+        break;  // Success.
+
+      if (errno == EINPROGRESS) {
+        fd_set write_fds;
+        FD_ZERO(&write_fds);
+        FD_SET(socket_fd_, &write_fds);
+
+        struct timeval tv;
+        tv.tv_sec = 5;
+        tv.tv_usec = 0;
+
+        int select_ret = select(socket_fd_ + 1, NULL, &write_fds, NULL, &tv);
+        if (select_ret != -1 && select_ret != 0) {
+          break;
+        }
+      }
+
+      LOG(ERROR) << "Failed to connect";
+      CloseBlocking(nullptr);
+    }
+
+    return socket_fd_ >= 0;
+  }
+
+  int GetFd() const { return socket_fd_; }
+
+ private:
+  TaskRunner* task_runner_{nullptr};
+  int socket_fd_{-1};
+
+  base::WeakPtrFactory<SocketStream> weak_ptr_factory_{this};
+};
+
+class SSLStream : public Stream {
+ public:
+  explicit SSLStream(TaskRunner* task_runner) : task_runner_{task_runner} {}
+
+  ~SSLStream() { weak_ptr_factory_.InvalidateWeakPtrs(); }
+
+  void RunDelayedTask(const base::Closure& success_callback) {
+    success_callback.Run();
+  }
+
+  bool ReadAsync(void* buffer,
+                 size_t size_to_read,
+                 const base::Callback<void(size_t)>& success_callback,
+                 const base::Callback<void(const Error*)>& error_callback,
+                 ErrorPtr* error) {
+    int res = SSL_read(ssl_.get(), buffer, size_to_read);
+    if (res > 0) {
+      task_runner_->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(&SSLStream::RunDelayedTask, weak_ptr_factory_.GetWeakPtr(),
+                     base::Bind(success_callback, res)),
+          {});
+      return true;
+    }
+
+    int err = SSL_get_error(ssl_.get(), res);
+
+    if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+      task_runner_->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(base::IgnoreResult(&SSLStream::ReadAsync),
+                     weak_ptr_factory_.GetWeakPtr(), buffer, size_to_read,
+                     success_callback, error_callback, nullptr),
+          base::TimeDelta::FromSeconds(1));
+      return true;
+    }
+
+    ErrorPtr weave_error;
+    Error::AddTo(&weave_error, FROM_HERE, "ssl", "socket_read_failed",
+                 "SSL error");
+    task_runner_->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(
+            &SSLStream::RunDelayedTask, weak_ptr_factory_.GetWeakPtr(),
+            base::Bind(error_callback, base::Owned(weave_error.release()))),
+        {});
+    return true;
+  }
+
+  bool WriteAllAsync(const void* buffer,
+                     size_t size_to_write,
+                     const base::Closure& success_callback,
+                     const base::Callback<void(const Error*)>& error_callback,
+                     ErrorPtr* error) {
+    int res = SSL_write(ssl_.get(), buffer, size_to_write);
+    if (res > 0) {
+      buffer = static_cast<const char*>(buffer) + res;
+      size_to_write -= res;
+      if (size_to_write == 0) {
+        task_runner_->PostDelayedTask(
+            FROM_HERE,
+            base::Bind(&SSLStream::RunDelayedTask,
+                       weak_ptr_factory_.GetWeakPtr(), success_callback),
+            {});
+        return true;
+      }
+
+      task_runner_->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(base::IgnoreResult(&SSLStream::WriteAllAsync),
+                     weak_ptr_factory_.GetWeakPtr(), buffer, size_to_write,
+                     success_callback, error_callback, nullptr),
+          base::TimeDelta::FromSeconds(1));
+
+      return true;
+    }
+
+    int err = SSL_get_error(ssl_.get(), res);
+
+    if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+      task_runner_->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(base::IgnoreResult(&SSLStream::WriteAllAsync),
+                     weak_ptr_factory_.GetWeakPtr(), buffer, size_to_write,
+                     success_callback, error_callback, nullptr),
+          base::TimeDelta::FromSeconds(1));
+      return true;
+    }
+
+    ErrorPtr weave_error;
+    Error::AddTo(&weave_error, FROM_HERE, "ssl", "socket_write_failed",
+                 "SSL error");
+    task_runner_->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(
+            &SSLStream::RunDelayedTask, weak_ptr_factory_.GetWeakPtr(),
+            base::Bind(error_callback, base::Owned(weave_error.release()))),
+        {});
+    return true;
+  }
+
+  bool FlushBlocking(ErrorPtr* error) { return true; }
+
+  bool CloseBlocking(ErrorPtr* error) {
+    weak_ptr_factory_.InvalidateWeakPtrs();
+    return true;
+  }
+
+  void CancelPendingAsyncOperations() {
+    weak_ptr_factory_.InvalidateWeakPtrs();
+  }
+
+  bool Init() {
+    ctx_.reset(SSL_CTX_new(TLSv1_2_client_method()));
+    CHECK(ctx_);
+    ssl_.reset(SSL_new(ctx_.get()));
+
+    char endpoint[] = "talk.google.com:5223";
+    stream_bio_ = BIO_new_connect(endpoint);
+    CHECK(stream_bio_);
+    BIO_set_nbio(stream_bio_, 1);
+
+    while (BIO_do_connect(stream_bio_) != 1) {
+      CHECK(BIO_should_retry(stream_bio_));
+      sleep(1);
+    }
+
+    SSL_set_bio(ssl_.get(), stream_bio_, stream_bio_);
+    SSL_set_connect_state(ssl_.get());
+
+    for (;;) {
+      int res = SSL_do_handshake(ssl_.get());
+      if (res) {
+        return true;
+      }
+
+      res = SSL_get_error(ssl_.get(), res);
+
+      if (res != SSL_ERROR_WANT_READ || res != SSL_ERROR_WANT_WRITE) {
+        return false;
+      }
+
+      sleep(1);
+    }
+    return false;
+  }
+
+ private:
+  TaskRunner* task_runner_{nullptr};
+  std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)> ctx_{nullptr, SSL_CTX_free};
+  std::unique_ptr<SSL, decltype(&SSL_free)> ssl_{nullptr, SSL_free};
+  BIO* stream_bio_{nullptr};
+
+  base::WeakPtrFactory<SSLStream> weak_ptr_factory_{this};
+};
+
+}  // namespace
+
+NetworkImpl::NetworkImpl(TaskRunner* task_runner) : task_runner_{task_runner} {
+  SSL_load_error_strings();
+  SSL_library_init();
+
+  DisableAccessPoint();
+}
+NetworkImpl::~NetworkImpl() {
+  DisableAccessPoint();
+}
+
+void NetworkImpl::AddOnConnectionChangedCallback(
+    const OnConnectionChangedCallback& listener) {
+  callbacks_.push_back(listener);
+}
+
+void NetworkImpl::TryToConnect(const std::string& ssid,
+                               const std::string& passphrase,
+                               int pid,
+                               base::Time until,
+                               const base::Closure& on_success) {
+  if (pid) {
+    int status = 0;
+    if (pid == waitpid(pid, &status, WNOWAIT)) {
+      int sockf_d = socket(AF_INET, SOCK_DGRAM, 0);
+      CHECK_GE(sockf_d, 0) << strerror(errno);
+
+      iwreq wreq = {};
+      snprintf(wreq.ifr_name, sizeof(wreq.ifr_name), "wlan0");
+      std::string essid(' ', IW_ESSID_MAX_SIZE + 1);
+      wreq.u.essid.pointer = &essid[0];
+      wreq.u.essid.length = essid.size();
+      CHECK_GE(ioctl(sockf_d, SIOCGIWESSID, &wreq), 0) << strerror(errno);
+      essid.resize(wreq.u.essid.length);
+      close(sockf_d);
+
+      if (ssid == essid) {
+        task_runner_->PostDelayedTask(
+            FROM_HERE, base::Bind(&NetworkImpl::NotifyNetworkChanged,
+                                  weak_ptr_factory_.GetWeakPtr()),
+            {});
+        return task_runner_->PostDelayedTask(FROM_HERE, on_success, {});
+      }
+      pid = 0;  // Try again.
+    }
+  }
+
+  if (pid == 0) {
+    pid = ForkCmd("nmcli",
+                  {"dev", "wifi", "connect", ssid, "password", passphrase});
+  }
+
+  if (base::Time::Now() >= until) {
+    task_runner_->PostDelayedTask(FROM_HERE,
+                                  base::Bind(&NetworkImpl::NotifyNetworkChanged,
+                                             weak_ptr_factory_.GetWeakPtr()),
+                                  {});
+    return;
+  }
+
+  task_runner_->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&NetworkImpl::TryToConnect, weak_ptr_factory_.GetWeakPtr(),
+                 ssid, passphrase, pid, until, on_success),
+      base::TimeDelta::FromSeconds(1));
+}
+
+bool NetworkImpl::ConnectToService(const std::string& ssid,
+                                   const std::string& passphrase,
+                                   const base::Closure& on_success,
+                                   ErrorPtr* error) {
+  CHECK(!hostapd_started_);
+  if (hostapd_started_) {
+    Error::AddTo(error, FROM_HERE, "wifi", "busy", "Running Access Point.");
+    return false;
+  }
+
+  TryToConnect(ssid, passphrase, 0,
+               base::Time::Now() + base::TimeDelta::FromMinutes(1), on_success);
+}
+
+NetworkState NetworkImpl::GetConnectionState() const {
+  // Forced soft AP.
+  return NetworkState::kOffline;
+
+  if (std::system("ping talk.google.com -c 1") == 0)
+    return NetworkState::kConnected;
+
+  if (std::system("nmcli dev"))
+    return NetworkState::kFailure;
+
+  if (std::system("nmcli dev | grep connecting") == 0)
+    return NetworkState::kConnecting;
+
+  return NetworkState::kOffline;
+}
+
+void NetworkImpl::EnableAccessPoint(const std::string& ssid) {
+  if (hostapd_started_)
+    return;
+
+  // Release wlan0 interface.
+  CHECK_EQ(0, std::system("nmcli nm wifi off"));
+  CHECK_EQ(0, std::system("rfkill unblock wlan"));
+  sleep(1);
+
+  std::string hostapd_conf = "/tmp/weave_hostapd.conf";
+  {
+    std::ofstream ofs(hostapd_conf);
+    ofs << "interface=wlan0" << std::endl;
+    ofs << "channel=1" << std::endl;
+    ofs << "ssid=" << ssid << std::endl;
+  }
+
+  CHECK_EQ(0, std::system(("hostapd -B -K " + hostapd_conf).c_str()));
+  hostapd_started_ = true;
+
+  for (size_t i = 0; i < 10; ++i) {
+    if (0 == std::system("ifconfig wlan0 192.168.76.1/24"))
+      break;
+    sleep(1);
+  }
+
+  std::string dnsmasq_conf = "/tmp/weave_dnsmasq.conf";
+  {
+    std::ofstream ofs(dnsmasq_conf.c_str());
+    ofs << "port=0" << std::endl;
+    ofs << "bind-interfaces" << std::endl;
+    ofs << "log-dhcp" << std::endl;
+    ofs << "dhcp-range=192.168.76.10,192.168.76.100" << std::endl;
+    ofs << "interface=wlan0" << std::endl;
+    ofs << "dhcp-leasefile=" << dnsmasq_conf << ".leases" << std::endl;
+  }
+
+  CHECK_EQ(0, std::system(("dnsmasq --conf-file=" + dnsmasq_conf).c_str()));
+  task_runner_->PostDelayedTask(FROM_HERE,
+                                base::Bind(&NetworkImpl::NotifyNetworkChanged,
+                                           weak_ptr_factory_.GetWeakPtr()),
+                                {});
+}
+
+void NetworkImpl::DisableAccessPoint() {
+  int res = std::system("pkill -f dnsmasq.*/tmp/weave");
+  res = std::system("pkill -f hostapd.*/tmp/weave");
+  CHECK_EQ(0, std::system("nmcli nm wifi on"));
+  hostapd_started_ = false;
+
+  task_runner_->PostDelayedTask(FROM_HERE,
+                                base::Bind(&NetworkImpl::NotifyNetworkChanged,
+                                           weak_ptr_factory_.GetWeakPtr()),
+                                {});
+}
+
+void NetworkImpl::NotifyNetworkChanged() {
+  bool online = GetConnectionState() == NetworkState::kConnected;
+  for (const auto& i : callbacks_)
+    i.Run(online);
+}
+
+std::unique_ptr<Stream> NetworkImpl::OpenSocketBlocking(const std::string& host,
+                                                        uint16_t port) {
+  std::unique_ptr<SocketStream> stream{new SocketStream{task_runner_}};
+  if (!stream->Connect(host, port))
+    return nullptr;
+  return std::move(stream);
+}
+
+void NetworkImpl::CreateTlsStream(
+    std::unique_ptr<Stream> stream,
+    const std::string& host,
+    const base::Callback<void(std::unique_ptr<Stream>)>& success_callback,
+    const base::Callback<void(const Error*)>& error_callback) {
+  // Connect to SSL port instead of upgrading to TLS.
+  std::unique_ptr<SSLStream> tls_stream{new SSLStream{task_runner_}};
+
+  if (tls_stream->Init()) {
+    task_runner_->PostDelayedTask(
+        FROM_HERE, base::Bind(success_callback, base::Passed(&tls_stream)), {});
+  } else {
+    ErrorPtr error;
+    Error::AddTo(&error, FROM_HERE, "tls", "tls_init_failed",
+                 "Failed to initialize TLS stream.");
+  }
+}
+
+}  // namespace examples
+}  // namespace weave
diff --git a/libweave/examples/ubuntu/network_manager.h b/libweave/examples/ubuntu/network_manager.h
new file mode 100644
index 0000000..c58d220
--- /dev/null
+++ b/libweave/examples/ubuntu/network_manager.h
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_EXAMPLES_UBUNTU_NETWORK_MANAGER_H_
+#define LIBWEAVE_EXAMPLES_UBUNTU_NETWORK_MANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <base/time/time.h>
+#include <weave/network.h>
+
+namespace weave {
+
+class TaskRunner;
+
+namespace examples {
+
+// Basic weave::Network implementation.
+// Production version of SSL socket needs secure server certificate check.
+class NetworkImpl : public Network {
+ public:
+  explicit NetworkImpl(TaskRunner* task_runner);
+  ~NetworkImpl();
+
+  void AddOnConnectionChangedCallback(
+      const OnConnectionChangedCallback& listener) override;
+  bool ConnectToService(const std::string& ssid,
+                        const std::string& passphrase,
+                        const base::Closure& on_success,
+                        ErrorPtr* error) override;
+  NetworkState GetConnectionState() const override;
+  void EnableAccessPoint(const std::string& ssid) override;
+  void DisableAccessPoint() override;
+  std::unique_ptr<Stream> OpenSocketBlocking(const std::string& host,
+                                             uint16_t port) override;
+  void CreateTlsStream(
+      std::unique_ptr<Stream> stream,
+      const std::string& host,
+      const base::Callback<void(std::unique_ptr<Stream>)>& success_callback,
+      const base::Callback<void(const Error*)>& error_callback) override;
+
+ private:
+  void TryToConnect(const std::string& ssid,
+                    const std::string& passphrase,
+                    int pid,
+                    base::Time until,
+                    const base::Closure& on_success);
+  void NotifyNetworkChanged();
+
+  bool hostapd_started_{false};
+  TaskRunner* task_runner_{nullptr};
+  std::vector<OnConnectionChangedCallback> callbacks_;
+
+  base::WeakPtrFactory<NetworkImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace examples
+}  // namespace weave
+
+#endif  // LIBWEAVE_EXAMPLES_UBUNTU_NETWORK_MANAGER_H_