Implement AvahiMdnsClient

Implement the AvahiMdnsClient interface.  This is based on code
from peerd/avahi_client.cc and avahi_service_publisher.cc.

Bug: 23288773
Change-Id: If58df0bf3d71c665c764ca8994b43dcff237549e
diff --git a/Android.mk b/Android.mk
index e9b1c3d..1b94a46 100644
--- a/Android.mk
+++ b/Android.mk
@@ -38,6 +38,7 @@
 	external/gtest/include \
 
 buffetSharedLibraries := \
+	libavahi-common \
 	libchrome \
 	libchrome-dbus \
 	libchromeos \
@@ -101,6 +102,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := weaved
 LOCAL_REQUIRED_MODULES := \
+	avahi-daemon \
 	base_state.defaults.json \
 	base_state.schema.json \
 	buffet.json \
diff --git a/buffet/avahi_mdns_client.cc b/buffet/avahi_mdns_client.cc
index fde28d5..3216546 100644
--- a/buffet/avahi_mdns_client.cc
+++ b/buffet/avahi_mdns_client.cc
@@ -14,31 +14,331 @@
  * limitations under the License.
  */
 
-#include "buffet/avahi_mdns_client.h"
+#include <vector>
 
+#include "buffet/avahi_mdns_client.h"
+#include "buffet/dbus_constants.h"
+
+#include <avahi-common/defs.h>
+#include <avahi-common/address.h>
 #include <base/guid.h>
+#include <chromeos/dbus/async_event_sequencer.h>
+#include <chromeos/dbus/dbus_signal_handler.h>
+#include <chromeos/dbus/dbus_method_invoker.h>
+#include <chromeos/errors/error.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+using chromeos::ErrorPtr;
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::CallMethodAndBlock;
+using chromeos::dbus_utils::ExtractMethodCallResults;
+using CompletionAction =
+    chromeos::dbus_utils::AsyncEventSequencer::CompletionAction;
 
 namespace buffet {
 
-AvahiMdnsClient::AvahiMdnsClient() {
+AvahiMdnsClient::AvahiMdnsClient(const scoped_refptr<dbus::Bus> &bus):
+    bus_(bus) {
 }
 
 AvahiMdnsClient::~AvahiMdnsClient() {
 }
 
+// NB: This should be the one-and-only definition of this MdnsClient static
+// method.
+std::unique_ptr<MdnsClient> MdnsClient::CreateInstance(
+    const scoped_refptr<dbus::Bus> &bus) {
+  return std::unique_ptr<MdnsClient>{new AvahiMdnsClient(bus)};
+}
+
+// TODO(rginda): Report errors back to the caller.
+// TODO(rginda): Support publishing more than one service.
 void AvahiMdnsClient::PublishService(
-    const std::string& service_name,
+    const std::string& service_type,
     uint16_t port,
     const std::map<std::string, std::string>& txt) {
-  // TODO(avakulenko)
+
+  if (service_state_ == READY) {
+    // TODO(rginda): Instead of stop/start, we should update the existing
+    // service when only the txt record changes.
+    StopPublishing(service_type);
+    if (service_state_ != UNDEF) {
+      LOG(ERROR) << "Failed to disable existing service.";
+      return;
+    }
+  }
+
+  service_name_ = base::GenerateGUID();
+  service_type_ = service_type;
+  port_ = port;
+  txt_ = GetTxtRecord(txt);
+
+  if (avahi_state_ == UNDEF || avahi_state_ == ERROR) {
+    ConnectToAvahi();
+  } else if (avahi_state_ == READY) {
+    CreateService();
+  } else {
+    CHECK(avahi_state_ == PENDING);
+  }
 }
 
-void AvahiMdnsClient::StopPublishing(const std::string& service_name) {
-  // TODO(avakulenko)
+// TODO(rginda): If we support publishing more than one service then we
+// may need a less ambiguous way of unpublishing them.
+void AvahiMdnsClient::StopPublishing(const std::string& service_type) {
+  if (service_type_.compare(service_type) != 0) {
+    LOG(ERROR) << "Unknown service type: " << service_type;
+    return;
+  }
+
+  if (service_state_ != READY) {
+    LOG(ERROR) << "Service is not published.";
+  }
+
+  service_name_.clear();
+  service_type_.clear();
+  port_ = 0;
+
+  FreeEntryGroup();
 }
 
-std::unique_ptr<MdnsClient> MdnsClient::CreateInstance() {
-  return std::unique_ptr<MdnsClient>{new AvahiMdnsClient};
+// Transform a service_info to a mDNS compatible TXT record value.
+// Concretely, a TXT record consists of a list of strings in the format
+// "key=value".  Each string must be less than 256 bytes long, since they are
+// length/value encoded.  Keys may not contain '=' characters, but are
+// otherwise unconstrained.
+//
+// We need a DBus type of "aay", which is a vector<vector<uint8_t>> in our
+// bindings.
+AvahiMdnsClient::TxtRecord AvahiMdnsClient::GetTxtRecord(
+    const std::map<std::string, std::string>& txt) {
+  TxtRecord result;
+  result.reserve(txt.size());
+  for (const auto& kv : txt) {
+    result.emplace_back();
+    std::vector<uint8_t>& record = result.back();
+    record.reserve(kv.first.length() + kv.second.length() + 1);
+    record.insert(record.end(), kv.first.begin(), kv.first.end());
+    record.push_back('=');
+    record.insert(record.end(), kv.second.begin(), kv.second.end());
+  }
+  return result;
+}
+
+void AvahiMdnsClient::ConnectToAvahi() {
+  avahi_state_ = PENDING;
+
+  avahi_ = bus_->GetObjectProxy(
+      dbus_constants::avahi::kServiceName,
+      dbus::ObjectPath(dbus_constants::avahi::kServerPath));
+
+  // This callback lives for the lifetime of the ObjectProxy.
+  avahi_->SetNameOwnerChangedCallback(
+      base::Bind(&AvahiMdnsClient::OnAvahiOwnerChanged,
+                 weak_ptr_factory_.GetWeakPtr()));
+
+  // Reconnect to our signals on a new Avahi instance.
+  scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+  chromeos::dbus_utils::ConnectToSignal(
+      avahi_,
+      dbus_constants::avahi::kServerInterface,
+      dbus_constants::avahi::kServerSignalStateChanged,
+      base::Bind(&AvahiMdnsClient::OnAvahiStateChanged,
+                 weak_ptr_factory_.GetWeakPtr()),
+      sequencer->GetExportHandler(
+          dbus_constants::avahi::kServerInterface,
+          dbus_constants::avahi::kServerSignalStateChanged,
+          "Failed to subscribe to Avahi state change.",
+          true));
+  sequencer->OnAllTasksCompletedCall(
+      {// Get a onetime callback with the initial state of Avahi.
+       AsyncEventSequencer::WrapCompletionTask(
+            base::Bind(&dbus::ObjectProxy::WaitForServiceToBeAvailable,
+                       avahi_,
+                       base::Bind(&AvahiMdnsClient::OnAvahiAvailable,
+                                  weak_ptr_factory_.GetWeakPtr()))),
+      });
+}
+
+void AvahiMdnsClient::CreateEntryGroup() {
+  ErrorPtr error;
+
+  service_state_ = PENDING;
+
+  auto resp = CallMethodAndBlock(
+      avahi_, dbus_constants::avahi::kServerInterface,
+      dbus_constants::avahi::kServerMethodEntryGroupNew,
+      &error);
+
+  dbus::ObjectPath group_path;
+  if (!resp || !ExtractMethodCallResults(resp.get(), &error, &group_path)) {
+    service_state_ = ERROR;
+    LOG(ERROR) << "Error creating group.";
+    return;
+  }
+  entry_group_ = bus_->GetObjectProxy(dbus_constants::avahi::kServiceName,
+                                      group_path);
+
+  // If we fail to connect to the the StateChange signal for this group, just
+  // report that the whole thing has failed.
+  auto on_connect_cb = [](const std::string& interface_name,
+                          const std::string& signal_name,
+                          bool success) {
+    if (!success) {
+      LOG(ERROR) << "Failed to connect to StateChange signal "
+        "from EntryGroup.";
+      return;
+    }
+  };
+
+  chromeos::dbus_utils::ConnectToSignal(
+      entry_group_,
+      dbus_constants::avahi::kGroupInterface,
+      dbus_constants::avahi::kGroupSignalStateChanged,
+      base::Bind(&AvahiMdnsClient::HandleGroupStateChanged,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::Bind(on_connect_cb));
+
+  if (!service_name_.empty())
+    CreateService();
+}
+
+void AvahiMdnsClient::FreeEntryGroup() {
+  if (!entry_group_) {
+    LOG(ERROR) << "No group to free.";
+    return;
+  }
+
+  ErrorPtr error;
+  auto resp = CallMethodAndBlock(entry_group_,
+                                 dbus_constants::avahi::kGroupInterface,
+                                 dbus_constants::avahi::kGroupMethodFree,
+                                 &error);
+  // Extract and log relevant errors.
+  bool success = resp && ExtractMethodCallResults(resp.get(), &error);
+  if (!success) {
+    LOG(ERROR) << "Error freeing service group";
+  }
+
+  // Ignore any signals we may have registered for from this proxy.
+  entry_group_->Detach();
+  entry_group_ = nullptr;
+
+  service_state_ = UNDEF;
+}
+
+void AvahiMdnsClient::CreateService() {
+  ErrorPtr error;
+
+  CHECK_EQ(service_type_, "privet");
+
+  VLOG(1) << "CreateService: name " << service_name_ << ", type: " <<
+      service_type_ << ", port: " << port_;
+
+  auto resp = CallMethodAndBlock(
+      entry_group_,
+      dbus_constants::avahi::kGroupInterface,
+      dbus_constants::avahi::kGroupMethodAddService,
+      &error,
+      int32_t{AVAHI_IF_UNSPEC},
+      int32_t{AVAHI_PROTO_UNSPEC},
+      uint32_t{0},  // No flags.
+      service_name_,
+      // For historical purposes, we convert the string "privet" into a proper
+      // mdns service type.  Everyone else should just pass a proper service
+      // type.
+      service_type_.compare("privet") == 0 ? "_privet._tcp" : service_type_,
+      std::string{},  // domain.
+      std::string{},  // hostname
+      port_,
+      txt_);
+  if (!resp || !ExtractMethodCallResults(resp.get(), &error)) {
+    LOG(ERROR) << "Error creating service";
+    service_state_ = ERROR;
+    return;
+  }
+
+  resp = CallMethodAndBlock(entry_group_,
+                            dbus_constants::avahi::kGroupInterface,
+                            dbus_constants::avahi::kGroupMethodCommit,
+                            &error);
+  if (!resp || !ExtractMethodCallResults(resp.get(), &error)) {
+    LOG(ERROR) << "Error committing service.";
+    service_state_ = ERROR;
+    return;
+  }
+
+  service_state_ = READY;
+}
+
+void AvahiMdnsClient::OnAvahiOwnerChanged(const std::string& old_owner,
+                                          const std::string& new_owner) {
+  if (new_owner.empty()) {
+    OnAvahiAvailable(false);
+    return;
+  }
+  OnAvahiAvailable(true);
+}
+
+void AvahiMdnsClient::OnAvahiStateChanged(int32_t state,
+                                          const std::string& error) {
+  HandleAvahiStateChange(state);
+}
+
+void AvahiMdnsClient::OnAvahiAvailable(bool avahi_is_on_dbus) {
+  VLOG(1) << "Avahi is "  << (avahi_is_on_dbus ? "up." : "down.");
+  int32_t state = AVAHI_SERVER_FAILURE;
+  if (avahi_is_on_dbus) {
+    auto resp = CallMethodAndBlock(
+        avahi_, dbus_constants::avahi::kServerInterface,
+        dbus_constants::avahi::kServerMethodGetState,
+        nullptr);
+    if (!resp || !ExtractMethodCallResults(resp.get(), nullptr, &state)) {
+      LOG(WARNING) << "Failed to get avahi initial state.  Relying on signal.";
+    }
+  }
+  VLOG(1) << "Initial Avahi state=" << state << ".";
+  HandleAvahiStateChange(state);
+}
+
+void AvahiMdnsClient::HandleAvahiStateChange(int32_t state) {
+  switch (state) {
+    case AVAHI_SERVER_RUNNING: {
+      // All host RRs have been established.
+      VLOG(1) << "Avahi ready for action.";
+      if (avahi_state_ == READY) {
+        LOG(INFO) << "Ignoring redundant Avahi up event.";
+        return;
+      }
+      avahi_state_ = READY;
+      if (!service_name_.empty())
+        CreateEntryGroup();
+    } break;
+    case AVAHI_SERVER_INVALID:
+      // Invalid state (initial).
+    case AVAHI_SERVER_REGISTERING:
+      // Host RRs are being registered.
+    case AVAHI_SERVER_COLLISION:
+      // There is a collision with a host RR. All host RRs have been withdrawn,
+      // the user should set a new host name via avahi_server_set_host_name().
+    case AVAHI_SERVER_FAILURE:
+      // Some fatal failure happened, the server is unable to proceed.
+      avahi_state_ = ERROR;
+      LOG(ERROR) << "Avahi changed to error state: " << state;
+      break;
+    default:
+      LOG(ERROR) << "Unknown Avahi server state change to " << state;
+      break;
+  }
+}
+
+void AvahiMdnsClient::HandleGroupStateChanged(
+    int32_t state,
+    const std::string& error_message) {
+  if (state == AVAHI_ENTRY_GROUP_COLLISION ||
+      state == AVAHI_ENTRY_GROUP_FAILURE) {
+    LOG(ERROR) << "Avahi service group error: " << state;
+  }
 }
 
 }  // namespace buffet
diff --git a/buffet/avahi_mdns_client.h b/buffet/avahi_mdns_client.h
index a8fd86e..1028620 100644
--- a/buffet/avahi_mdns_client.h
+++ b/buffet/avahi_mdns_client.h
@@ -20,6 +20,8 @@
 #include <map>
 #include <string>
 
+#include <base/memory/weak_ptr.h>
+#include <dbus/bus.h>
 #include <weave/mdns.h>
 
 #include "buffet/mdns_client.h"
@@ -29,16 +31,61 @@
 // Publishes privet service on mDns using Avahi.
 class AvahiMdnsClient : public MdnsClient {
  public:
-  AvahiMdnsClient();
+  explicit AvahiMdnsClient(const scoped_refptr<dbus::Bus> &bus);
   ~AvahiMdnsClient() override;
 
   // weave::Mdns implementation.
-  void PublishService(const std::string& service_name,
+  void PublishService(const std::string& service_type,
                       uint16_t port,
                       const std::map<std::string, std::string>& txt) override;
-  void StopPublishing(const std::string& service_name) override;
+  void StopPublishing(const std::string& service_type) override;
 
  private:
+  using TxtRecord = std::vector<std::vector<uint8_t>>;
+
+  // States used to track progress of our asynchronous dbus operations.
+  typedef enum {
+    UNDEF,
+    PENDING,
+    READY,
+    ERROR
+  } AsyncState;
+
+  scoped_refptr<dbus::Bus> bus_;
+  dbus::ObjectProxy* avahi_{nullptr};
+  // The avahi interface we use to add/remove mdns services.
+  dbus::ObjectProxy* entry_group_{nullptr};
+
+  // State of our dbus connection to avahi.
+  AsyncState avahi_state_{UNDEF};
+  // State of the group/service publish operation.
+  AsyncState service_state_{UNDEF};
+
+  std::string service_name_;
+  std::string service_type_;
+  uint16_t port_{0};
+  TxtRecord txt_;
+
+  // Must be last member to invalidate pointers before actual destruction.
+  base::WeakPtrFactory<AvahiMdnsClient> weak_ptr_factory_{this};
+
+  // Convert a {string:string} text record into something we can send over
+  // dbus.
+  TxtRecord GetTxtRecord(const std::map<std::string, std::string>& txt);
+
+  void ConnectToAvahi();
+  void CreateEntryGroup();
+  void FreeEntryGroup();
+  void CreateService();
+
+  void OnAvahiOwnerChanged(const std::string& old_owner,
+                           const std::string& new_owner);
+  void OnAvahiStateChanged(int32_t state,
+                           const std::string& error);
+  void OnAvahiAvailable(bool avahi_is_on_dbus);
+  void HandleAvahiStateChange(int32_t state);
+  void HandleGroupStateChanged(int32_t state,
+                               const std::string& error_message);
   DISALLOW_COPY_AND_ASSIGN(AvahiMdnsClient);
 };
 
diff --git a/buffet/dbus_command_dispatcher.cc b/buffet/dbus_command_dispatcher.cc
index 1af0983..03f4789 100644
--- a/buffet/dbus_command_dispatcher.cc
+++ b/buffet/dbus_command_dispatcher.cc
@@ -28,7 +28,8 @@
     return;
   std::unique_ptr<DBusCommandProxy> proxy{new DBusCommandProxy(
       object_manager_.get(), object_manager_->GetBus(), command,
-      buffet::kCommandServicePathPrefix + std::to_string(++next_id_))};
+      buffet::dbus_constants::kCommandServicePathPrefix +
+      std::to_string(++next_id_))};
   proxy->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction());
   // DBusCommandProxy::DBusCommandProxy() subscribe itself to weave::Command
   // notifications. When weave::Command is being destroyed it sends
diff --git a/buffet/dbus_command_proxy_unittest.cc b/buffet/dbus_command_proxy_unittest.cc
index 1c7cc11..52bcf9f 100644
--- a/buffet/dbus_command_proxy_unittest.cc
+++ b/buffet/dbus_command_proxy_unittest.cc
@@ -82,7 +82,7 @@
         .WillOnce(ReturnRefOfCopy<std::string>("{}"));
 
     // Set up a mock ExportedObject to be used with the DBus command proxy.
-    std::string cmd_path = buffet::kCommandServicePathPrefix;
+    std::string cmd_path = buffet::dbus_constants::kCommandServicePathPrefix;
     cmd_path += kTestCommandId;
     const dbus::ObjectPath kCmdObjPath(cmd_path);
     // Use a mock exported object for the exported object manager.
diff --git a/buffet/dbus_constants.cc b/buffet/dbus_constants.cc
index a1521fb..584bbff 100644
--- a/buffet/dbus_constants.cc
+++ b/buffet/dbus_constants.cc
@@ -6,8 +6,47 @@
 
 namespace buffet {
 
+namespace dbus_constants {
+
 const char kServiceName[] = "com.android.Weave";
 const char kRootServicePath[] = "/com/android/Weave";
 const char kCommandServicePathPrefix[] = "/com/android/Weave/commands/";
 
+namespace avahi {
+
+const char kServiceName[] = "org.freedesktop.Avahi";
+
+const char kServerInterface[] = "org.freedesktop.Avahi.Server";
+const char kServerPath[] = "/";
+const char kServerMethodEntryGroupNew[] = "EntryGroupNew";
+const char kServerMethodServiceBrowserNew[] = "ServiceBrowserNew";
+const char kServerMethodServiceResolverNew[] = "ServiceResolverNew";
+const char kServerMethodGetHostName[] = "GetHostName";
+const char kServerMethodGetState[] = "GetState";
+const char kServerSignalStateChanged[] = "StateChanged";
+
+const char kGroupInterface[] = "org.freedesktop.Avahi.EntryGroup";
+const char kGroupMethodAddRecord[] = "AddRecord";
+const char kGroupMethodAddService[] = "AddService";
+const char kGroupMethodCommit[] = "Commit";
+const char kGroupMethodFree[] = "Free";
+const char kGroupMethodReset[]= "Reset";
+const char kGroupSignalStateChanged[] = "StateChanged";
+
+const char kServiceBrowserInterface[] = "org.freedesktop.Avahi.ServiceBrowser";
+const char kServiceBrowserMethodFree[] = "Free";
+const char kServiceBrowserSignalItemNew[] = "ItemNew";
+const char kServiceBrowserSignalItemRemove[] = "ItemRemove";
+const char kServiceBrowserSignalFailure[] = "Failure";
+
+const char kServiceResolverInterface[] =
+    "org.freedesktop.Avahi.ServiceResolver";
+const char kServiceResolverMethodFree[] = "Free";
+const char kServiceResolverSignalFound[] = "Found";
+const char kServiceResolverSignalFailure[] = "Failure";
+
+}  // namespace avahi
+
+}  // namespace dbus constants
+
 }  // namespace buffet
diff --git a/buffet/dbus_constants.h b/buffet/dbus_constants.h
index 0bb72dc..e994c06 100644
--- a/buffet/dbus_constants.h
+++ b/buffet/dbus_constants.h
@@ -7,6 +7,8 @@
 
 namespace buffet {
 
+namespace dbus_constants {
+
 // The service name claimed by the Buffet daemon.
 extern const char kServiceName[];
 
@@ -16,6 +18,42 @@
 // D-Bus object path prefix for Command objects.
 extern const char kCommandServicePathPrefix[];
 
+namespace avahi {
+
+extern const char kServiceName[];
+
+extern const char kServerInterface[];
+extern const char kServerPath[];
+extern const char kServerMethodEntryGroupNew[];
+extern const char kServerMethodServiceBrowserNew[];
+extern const char kServerMethodServiceResolverNew[];
+extern const char kServerMethodGetHostName[];
+extern const char kServerMethodGetState[];
+extern const char kServerSignalStateChanged[];
+
+extern const char kGroupInterface[];
+extern const char kGroupMethodAddRecord[];
+extern const char kGroupMethodAddService[];
+extern const char kGroupMethodCommit[];
+extern const char kGroupMethodFree[];
+extern const char kGroupMethodReset[];
+extern const char kGroupSignalStateChanged[];
+
+extern const char kServiceBrowserInterface[];
+extern const char kServiceBrowserMethodFree[];
+extern const char kServiceBrowserSignalItemNew[];
+extern const char kServiceBrowserSignalItemRemove[];
+extern const char kServiceBrowserSignalFailure[];
+
+extern const char kServiceResolverInterface[];
+extern const char kServiceResolverMethodFree[];
+extern const char kServiceResolverSignalFound[];
+extern const char kServiceResolverSignalFailure[];
+
+}  // namespace avahi
+
+}  // namespace dbus_constants
+
 }  // namespace buffet
 
 #endif  // BUFFET_DBUS_CONSTANTS_H_
diff --git a/buffet/main.cc b/buffet/main.cc
index 203492b..d2ca7e6 100644
--- a/buffet/main.cc
+++ b/buffet/main.cc
@@ -18,8 +18,8 @@
 
 using chromeos::dbus_utils::AsyncEventSequencer;
 using chromeos::DBusServiceDaemon;
-using buffet::kServiceName;
-using buffet::kRootServicePath;
+using buffet::dbus_constants::kServiceName;
+using buffet::dbus_constants::kRootServicePath;
 
 namespace buffet {
 
diff --git a/buffet/manager.cc b/buffet/manager.cc
index f8f1f70..f5874f4 100644
--- a/buffet/manager.cc
+++ b/buffet/manager.cc
@@ -77,7 +77,7 @@
   network_client_ = NetworkClient::CreateInstance(device_whitelist);
 #ifdef BUFFET_USE_WIFI_BOOTSTRAPPING
   if (!options.disable_privet) {
-    mdns_client_ = MdnsClient::CreateInstance();
+    mdns_client_ = MdnsClient::CreateInstance(dbus_object_.GetBus());
     web_serv_client_.reset(new WebServClient{dbus_object_.GetBus(), sequencer});
   }
 #endif  // BUFFET_USE_WIFI_BOOTSTRAPPING
diff --git a/buffet/mdns_client.h b/buffet/mdns_client.h
index f486d05..ccfb920 100644
--- a/buffet/mdns_client.h
+++ b/buffet/mdns_client.h
@@ -22,6 +22,8 @@
 #include <string>
 
 #include <base/guid.h>
+#include <base/memory/ref_counted.h>
+#include <dbus/bus.h>
 #include <weave/mdns.h>
 
 namespace buffet {
@@ -33,13 +35,15 @@
   ~MdnsClient() override = default;
 
   // weave::Mdns implementation.
-  void PublishService(const std::string& service_name,
+  void PublishService(const std::string& service_type,
                       uint16_t port,
-                      const std::map<std::string, std::string>& txt) override {}
-  void StopPublishing(const std::string& service_name) override {}
+                      const std::map<std::string, std::string>& txt) override {
+  };
+  void StopPublishing(const std::string& service_type) override {}
   std::string GetId() const override { return device_id_; }
 
-  static std::unique_ptr<MdnsClient> CreateInstance();
+  static std::unique_ptr<MdnsClient> CreateInstance(
+      const scoped_refptr<dbus::Bus> &bus);
 
  protected:
   // Cached value of the device ID that we got from peerd.
diff --git a/buffet/webserv_client.cc b/buffet/webserv_client.cc
index 2ae5910..37c9716 100644
--- a/buffet/webserv_client.cc
+++ b/buffet/webserv_client.cc
@@ -84,7 +84,7 @@
       base::Bind(&WebServClient::OnProtocolHandlerDisconnected,
                  weak_ptr_factory_.GetWeakPtr()));
 
-  web_server_->Connect(bus, buffet::kServiceName,
+  web_server_->Connect(bus, buffet::dbus_constants::kServiceName,
                        sequencer->GetHandler("Server::Connect failed.", true),
                        base::Bind(&base::DoNothing),
                        base::Bind(&base::DoNothing));