root/dbus/test_service.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. EmptyCallback
  2. num_exported_methods_
  3. StartService
  4. WaitUntilServiceIsStarted
  5. ShutdownAndBlock
  6. HasDBusThread
  7. ShutdownAndBlockInternal
  8. SendTestSignal
  9. SendTestSignalFromRoot
  10. SendTestSignalInternal
  11. SendTestSignalFromRootInternal
  12. RequestOwnership
  13. RequestOwnershipInternal
  14. OnOwnership
  15. ReleaseOwnership
  16. ReleaseOwnershipInternal
  17. OnExported
  18. Run
  19. Echo
  20. SlowEcho
  21. AsyncEcho
  22. BrokenMethod
  23. GetAllProperties
  24. GetProperty
  25. SetProperty
  26. PerformAction
  27. PerformActionResponse
  28. OwnershipReleased
  29. OwnershipRegained
  30. GetManagedObjects
  31. AddPropertiesToWriter
  32. AddObject
  33. AddObjectInternal
  34. RemoveObject
  35. RemoveObjectInternal
  36. SendPropertyChangedSignal
  37. SendPropertyChangedSignalInternal

// Copyright (c) 2012 The Chromium 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 "dbus/test_service.h"

#include "base/bind.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "dbus/bus.h"
#include "dbus/exported_object.h"
#include "dbus/message.h"
#include "dbus/object_manager.h"
#include "dbus/object_path.h"
#include "dbus/property.h"

namespace {

void EmptyCallback(bool /* success */) {
}

}  // namespace

namespace dbus {

// Echo, SlowEcho, AsyncEcho, BrokenMethod, GetAll, Get, Set, PerformAction,
// GetManagedObjects.
const int TestService::kNumMethodsToExport = 9;

TestService::Options::Options()
    : request_ownership_options(Bus::REQUIRE_PRIMARY) {
}

TestService::Options::~Options() {
}

TestService::TestService(const Options& options)
    : base::Thread("TestService"),
      request_ownership_options_(options.request_ownership_options),
      dbus_task_runner_(options.dbus_task_runner),
      on_name_obtained_(false, false),
      num_exported_methods_(0) {
}

TestService::~TestService() {
  Stop();
}

bool TestService::StartService() {
  base::Thread::Options thread_options;
  thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
  return StartWithOptions(thread_options);
}

bool TestService::WaitUntilServiceIsStarted() {
  const base::TimeDelta timeout(TestTimeouts::action_max_timeout());
  // Wait until the ownership of the service name is obtained.
  return on_name_obtained_.TimedWait(timeout);
}

void TestService::ShutdownAndBlock() {
  message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&TestService::ShutdownAndBlockInternal,
                 base::Unretained(this)));
}

bool TestService::HasDBusThread() {
  return bus_->HasDBusThread();
}

void TestService::ShutdownAndBlockInternal() {
  if (HasDBusThread())
    bus_->ShutdownOnDBusThreadAndBlock();
  else
    bus_->ShutdownAndBlock();
}

void TestService::SendTestSignal(const std::string& message) {
  message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&TestService::SendTestSignalInternal,
                 base::Unretained(this),
                 message));
}

void TestService::SendTestSignalFromRoot(const std::string& message) {
  message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&TestService::SendTestSignalFromRootInternal,
                 base::Unretained(this),
                 message));
}

void TestService::SendTestSignalInternal(const std::string& message) {
  Signal signal("org.chromium.TestInterface", "Test");
  MessageWriter writer(&signal);
  writer.AppendString(message);
  exported_object_->SendSignal(&signal);
}

void TestService::SendTestSignalFromRootInternal(const std::string& message) {
  Signal signal("org.chromium.TestInterface", "Test");
  MessageWriter writer(&signal);
  writer.AppendString(message);

  bus_->RequestOwnership("org.chromium.TestService",
                         request_ownership_options_,
                         base::Bind(&TestService::OnOwnership,
                                    base::Unretained(this),
                                    base::Bind(&EmptyCallback)));

  // Use "/" just like dbus-send does.
  ExportedObject* root_object = bus_->GetExportedObject(ObjectPath("/"));
  root_object->SendSignal(&signal);
}

void TestService::RequestOwnership(base::Callback<void(bool)> callback) {
  message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&TestService::RequestOwnershipInternal,
                 base::Unretained(this),
                 callback));
}

void TestService::RequestOwnershipInternal(
    base::Callback<void(bool)> callback) {
  bus_->RequestOwnership("org.chromium.TestService",
                         request_ownership_options_,
                         base::Bind(&TestService::OnOwnership,
                                    base::Unretained(this),
                                    callback));
}

void TestService::OnOwnership(base::Callback<void(bool)> callback,
                              const std::string& service_name,
                              bool success) {
  has_ownership_ = success;
  LOG_IF(ERROR, !success) << "Failed to own: " << service_name;
  callback.Run(success);

  on_name_obtained_.Signal();
}

void TestService::ReleaseOwnership(base::Closure callback) {
  bus_->GetDBusTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(&TestService::ReleaseOwnershipInternal,
                 base::Unretained(this),
                 callback));
}

void TestService::ReleaseOwnershipInternal(
    base::Closure callback) {
  bus_->ReleaseOwnership("org.chromium.TestService");
  has_ownership_ = false;

  bus_->GetOriginTaskRunner()->PostTask(
      FROM_HERE,
      callback);
}

void TestService::OnExported(const std::string& interface_name,
                             const std::string& method_name,
                             bool success) {
  if (!success) {
    LOG(ERROR) << "Failed to export: " << interface_name << "."
               << method_name;
    // Returning here will make WaitUntilServiceIsStarted() to time out
    // and return false.
    return;
  }

  ++num_exported_methods_;
  if (num_exported_methods_ == kNumMethodsToExport) {
    // As documented in exported_object.h, the service name should be
    // requested after all methods are exposed.
    bus_->RequestOwnership("org.chromium.TestService",
                           request_ownership_options_,
                           base::Bind(&TestService::OnOwnership,
                                      base::Unretained(this),
                                      base::Bind(&EmptyCallback)));
  }
}

void TestService::Run(base::MessageLoop* message_loop) {
  Bus::Options bus_options;
  bus_options.bus_type = Bus::SESSION;
  bus_options.connection_type = Bus::PRIVATE;
  bus_options.dbus_task_runner = dbus_task_runner_;
  bus_ = new Bus(bus_options);

  exported_object_ = bus_->GetExportedObject(
      ObjectPath("/org/chromium/TestObject"));

  int num_methods = 0;
  exported_object_->ExportMethod(
      "org.chromium.TestInterface",
      "Echo",
      base::Bind(&TestService::Echo,
                 base::Unretained(this)),
      base::Bind(&TestService::OnExported,
                 base::Unretained(this)));
  ++num_methods;

  exported_object_->ExportMethod(
      "org.chromium.TestInterface",
      "SlowEcho",
      base::Bind(&TestService::SlowEcho,
                 base::Unretained(this)),
      base::Bind(&TestService::OnExported,
                 base::Unretained(this)));
  ++num_methods;

  exported_object_->ExportMethod(
      "org.chromium.TestInterface",
      "AsyncEcho",
      base::Bind(&TestService::AsyncEcho,
                 base::Unretained(this)),
      base::Bind(&TestService::OnExported,
                 base::Unretained(this)));
  ++num_methods;

  exported_object_->ExportMethod(
      "org.chromium.TestInterface",
      "BrokenMethod",
      base::Bind(&TestService::BrokenMethod,
                 base::Unretained(this)),
      base::Bind(&TestService::OnExported,
                 base::Unretained(this)));
  ++num_methods;

  exported_object_->ExportMethod(
      "org.chromium.TestInterface",
      "PerformAction",
      base::Bind(&TestService::PerformAction,
                 base::Unretained(this)),
      base::Bind(&TestService::OnExported,
                 base::Unretained(this)));
  ++num_methods;

  exported_object_->ExportMethod(
       kPropertiesInterface,
       kPropertiesGetAll,
       base::Bind(&TestService::GetAllProperties,
                  base::Unretained(this)),
       base::Bind(&TestService::OnExported,
                  base::Unretained(this)));
  ++num_methods;

  exported_object_->ExportMethod(
       kPropertiesInterface,
       kPropertiesGet,
       base::Bind(&TestService::GetProperty,
                  base::Unretained(this)),
       base::Bind(&TestService::OnExported,
                  base::Unretained(this)));
  ++num_methods;

  exported_object_->ExportMethod(
       kPropertiesInterface,
       kPropertiesSet,
       base::Bind(&TestService::SetProperty,
                  base::Unretained(this)),
       base::Bind(&TestService::OnExported,
                  base::Unretained(this)));
  ++num_methods;

  exported_object_manager_ = bus_->GetExportedObject(
      ObjectPath("/org/chromium/TestService"));

  exported_object_manager_->ExportMethod(
       kObjectManagerInterface,
       kObjectManagerGetManagedObjects,
       base::Bind(&TestService::GetManagedObjects,
                  base::Unretained(this)),
       base::Bind(&TestService::OnExported,
                  base::Unretained(this)));
  ++num_methods;

  // Just print an error message as we don't want to crash tests.
  // Tests will fail at a call to WaitUntilServiceIsStarted().
  if (num_methods != kNumMethodsToExport) {
    LOG(ERROR) << "The number of methods does not match";
  }
  message_loop->Run();
}

void TestService::Echo(MethodCall* method_call,
                       ExportedObject::ResponseSender response_sender) {
  MessageReader reader(method_call);
  std::string text_message;
  if (!reader.PopString(&text_message)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  scoped_ptr<Response> response = Response::FromMethodCall(method_call);
  MessageWriter writer(response.get());
  writer.AppendString(text_message);
  response_sender.Run(response.Pass());
}

void TestService::SlowEcho(MethodCall* method_call,
                           ExportedObject::ResponseSender response_sender) {
  base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
  Echo(method_call, response_sender);
}

void TestService::AsyncEcho(MethodCall* method_call,
                            ExportedObject::ResponseSender response_sender) {
  // Schedule a call to Echo() to send an asynchronous response after we return.
  message_loop()->PostDelayedTask(FROM_HERE,
                                  base::Bind(&TestService::Echo,
                                             base::Unretained(this),
                                             method_call,
                                             response_sender),
                                  TestTimeouts::tiny_timeout());
}

void TestService::BrokenMethod(MethodCall* method_call,
                               ExportedObject::ResponseSender response_sender) {
  response_sender.Run(scoped_ptr<Response>());
}


void TestService::GetAllProperties(
    MethodCall* method_call,
    ExportedObject::ResponseSender response_sender) {
  MessageReader reader(method_call);
  std::string interface;
  if (!reader.PopString(&interface)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  scoped_ptr<Response> response = Response::FromMethodCall(method_call);
  MessageWriter writer(response.get());

  AddPropertiesToWriter(&writer);

  response_sender.Run(response.Pass());
}

void TestService::GetProperty(MethodCall* method_call,
                              ExportedObject::ResponseSender response_sender) {
  MessageReader reader(method_call);
  std::string interface;
  if (!reader.PopString(&interface)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  std::string name;
  if (!reader.PopString(&name)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  if (name == "Name") {
    // Return the previous value for the "Name" property:
    // Variant<"TestService">
    scoped_ptr<Response> response = Response::FromMethodCall(method_call);
    MessageWriter writer(response.get());

    writer.AppendVariantOfString("TestService");

    response_sender.Run(response.Pass());
  } else if (name == "Version") {
    // Return a new value for the "Version" property:
    // Variant<20>
    scoped_ptr<Response> response = Response::FromMethodCall(method_call);
    MessageWriter writer(response.get());

    writer.AppendVariantOfInt16(20);

    response_sender.Run(response.Pass());
  } else if (name == "Methods") {
    // Return the previous value for the "Methods" property:
    // Variant<["Echo", "SlowEcho", "AsyncEcho", "BrokenMethod"]>
    scoped_ptr<Response> response = Response::FromMethodCall(method_call);
    MessageWriter writer(response.get());
    MessageWriter variant_writer(NULL);
    MessageWriter variant_array_writer(NULL);

    writer.OpenVariant("as", &variant_writer);
    variant_writer.OpenArray("s", &variant_array_writer);
    variant_array_writer.AppendString("Echo");
    variant_array_writer.AppendString("SlowEcho");
    variant_array_writer.AppendString("AsyncEcho");
    variant_array_writer.AppendString("BrokenMethod");
    variant_writer.CloseContainer(&variant_array_writer);
    writer.CloseContainer(&variant_writer);

    response_sender.Run(response.Pass());
  } else if (name == "Objects") {
    // Return the previous value for the "Objects" property:
    // Variant<[objectpath:"/TestObjectPath"]>
    scoped_ptr<Response> response = Response::FromMethodCall(method_call);
    MessageWriter writer(response.get());
    MessageWriter variant_writer(NULL);
    MessageWriter variant_array_writer(NULL);

    writer.OpenVariant("ao", &variant_writer);
    variant_writer.OpenArray("o", &variant_array_writer);
    variant_array_writer.AppendObjectPath(ObjectPath("/TestObjectPath"));
    variant_writer.CloseContainer(&variant_array_writer);
    writer.CloseContainer(&variant_writer);

    response_sender.Run(response.Pass());
  } else if (name == "Bytes") {
    // Return the previous value for the "Bytes" property:
    // Variant<[0x54, 0x65, 0x73, 0x74]>
    scoped_ptr<Response> response = Response::FromMethodCall(method_call);
    MessageWriter writer(response.get());
    MessageWriter variant_writer(NULL);
    MessageWriter variant_array_writer(NULL);

    writer.OpenVariant("ay", &variant_writer);
    const uint8 bytes[] = { 0x54, 0x65, 0x73, 0x74 };
    variant_writer.AppendArrayOfBytes(bytes, sizeof(bytes));
    writer.CloseContainer(&variant_writer);

    response_sender.Run(response.Pass());
  } else {
    // Return error.
    response_sender.Run(scoped_ptr<Response>());
    return;
  }
}

void TestService::SetProperty(MethodCall* method_call,
                              ExportedObject::ResponseSender response_sender) {
  MessageReader reader(method_call);
  std::string interface;
  if (!reader.PopString(&interface)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  std::string name;
  if (!reader.PopString(&name)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  if (name != "Name") {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  std::string value;
  if (!reader.PopVariantOfString(&value)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  SendPropertyChangedSignal(value);

  response_sender.Run(Response::FromMethodCall(method_call));
}

void TestService::PerformAction(
      MethodCall* method_call,
      ExportedObject::ResponseSender response_sender) {
  MessageReader reader(method_call);
  std::string action;
  ObjectPath object_path;
  if (!reader.PopString(&action) || !reader.PopObjectPath(&object_path)) {
    response_sender.Run(scoped_ptr<Response>());
    return;
  }

  if (action == "AddObject")
    AddObject(object_path);
  else if (action == "RemoveObject")
    RemoveObject(object_path);
  else if (action == "ReleaseOwnership") {
    ReleaseOwnership(base::Bind(&TestService::PerformActionResponse,
                                base::Unretained(this),
                                method_call, response_sender));
    return;
  } else if (action == "Ownership") {
    ReleaseOwnership(base::Bind(&TestService::OwnershipReleased,
                                base::Unretained(this),
                                method_call, response_sender));
    return;
  }

  scoped_ptr<Response> response = Response::FromMethodCall(method_call);
  response_sender.Run(response.Pass());
}

void TestService::PerformActionResponse(
    MethodCall* method_call,
    ExportedObject::ResponseSender response_sender) {
  scoped_ptr<Response> response = Response::FromMethodCall(method_call);
  response_sender.Run(response.Pass());
}

void TestService::OwnershipReleased(
    MethodCall* method_call,
    ExportedObject::ResponseSender response_sender) {
  RequestOwnership(base::Bind(&TestService::OwnershipRegained,
                              base::Unretained(this),
                              method_call, response_sender));
}


void TestService::OwnershipRegained(
    MethodCall* method_call,
    ExportedObject::ResponseSender response_sender,
    bool success) {
  PerformActionResponse(method_call, response_sender);
}


void TestService::GetManagedObjects(
    MethodCall* method_call,
    ExportedObject::ResponseSender response_sender) {
  scoped_ptr<Response> response = Response::FromMethodCall(method_call);
  MessageWriter writer(response.get());

  // The managed objects response is a dictionary of object paths identifying
  // the object(s) with a dictionary of strings identifying the interface(s)
  // they implement and then a dictionary of property values.
  //
  // Thus this looks something like:
  //
  // {
  //   "/org/chromium/TestObject": {
  //     "org.chromium.TestInterface": { /* Properties */ }
  //   }
  // }


  MessageWriter array_writer(NULL);
  MessageWriter dict_entry_writer(NULL);
  MessageWriter object_array_writer(NULL);
  MessageWriter object_dict_entry_writer(NULL);

  writer.OpenArray("{oa{sa{sv}}}", &array_writer);

  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendObjectPath(ObjectPath("/org/chromium/TestObject"));
  dict_entry_writer.OpenArray("{sa{sv}}", &object_array_writer);

  object_array_writer.OpenDictEntry(&object_dict_entry_writer);
  object_dict_entry_writer.AppendString("org.chromium.TestInterface");
  AddPropertiesToWriter(&object_dict_entry_writer);
  object_array_writer.CloseContainer(&object_dict_entry_writer);

  dict_entry_writer.CloseContainer(&object_array_writer);

  array_writer.CloseContainer(&dict_entry_writer);
  writer.CloseContainer(&array_writer);

  response_sender.Run(response.Pass());
}

void TestService::AddPropertiesToWriter(MessageWriter* writer) {
  // The properties response is a dictionary of strings identifying the
  // property and a variant containing the property value. We return all
  // of the properties, thus the response is:
  //
  // {
  //   "Name": Variant<"TestService">,
  //   "Version": Variant<10>,
  //   "Methods": Variant<["Echo", "SlowEcho", "AsyncEcho", "BrokenMethod"]>,
  //   "Objects": Variant<[objectpath:"/TestObjectPath"]>
  //   "Bytes": Variant<[0x54, 0x65, 0x73, 0x74]>
  // }

  MessageWriter array_writer(NULL);
  MessageWriter dict_entry_writer(NULL);
  MessageWriter variant_writer(NULL);
  MessageWriter variant_array_writer(NULL);

  writer->OpenArray("{sv}", &array_writer);

  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendString("Name");
  dict_entry_writer.AppendVariantOfString("TestService");
  array_writer.CloseContainer(&dict_entry_writer);

  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendString("Version");
  dict_entry_writer.AppendVariantOfInt16(10);
  array_writer.CloseContainer(&dict_entry_writer);

  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendString("Methods");
  dict_entry_writer.OpenVariant("as", &variant_writer);
  variant_writer.OpenArray("s", &variant_array_writer);
  variant_array_writer.AppendString("Echo");
  variant_array_writer.AppendString("SlowEcho");
  variant_array_writer.AppendString("AsyncEcho");
  variant_array_writer.AppendString("BrokenMethod");
  variant_writer.CloseContainer(&variant_array_writer);
  dict_entry_writer.CloseContainer(&variant_writer);
  array_writer.CloseContainer(&dict_entry_writer);

  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendString("Objects");
  dict_entry_writer.OpenVariant("ao", &variant_writer);
  variant_writer.OpenArray("o", &variant_array_writer);
  variant_array_writer.AppendObjectPath(ObjectPath("/TestObjectPath"));
  variant_writer.CloseContainer(&variant_array_writer);
  dict_entry_writer.CloseContainer(&variant_writer);
  array_writer.CloseContainer(&dict_entry_writer);

  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendString("Bytes");
  dict_entry_writer.OpenVariant("ay", &variant_writer);
  const uint8 bytes[] = { 0x54, 0x65, 0x73, 0x74 };
  variant_writer.AppendArrayOfBytes(bytes, sizeof(bytes));
  dict_entry_writer.CloseContainer(&variant_writer);
  array_writer.CloseContainer(&dict_entry_writer);

  writer->CloseContainer(&array_writer);
}

void TestService::AddObject(const ObjectPath& object_path) {
  message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&TestService::AddObjectInternal,
                 base::Unretained(this),
                 object_path));
}

void TestService::AddObjectInternal(const ObjectPath& object_path) {
  Signal signal(kObjectManagerInterface, kObjectManagerInterfacesAdded);
  MessageWriter writer(&signal);
  writer.AppendObjectPath(object_path);

  MessageWriter array_writer(NULL);
  MessageWriter dict_entry_writer(NULL);

  writer.OpenArray("{sa{sv}}", &array_writer);
  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendString("org.chromium.TestInterface");
  AddPropertiesToWriter(&dict_entry_writer);
  array_writer.CloseContainer(&dict_entry_writer);
  writer.CloseContainer(&array_writer);

  exported_object_manager_->SendSignal(&signal);
}

void TestService::RemoveObject(const ObjectPath& object_path) {
  message_loop()->PostTask(FROM_HERE,
                           base::Bind(&TestService::RemoveObjectInternal,
                                      base::Unretained(this),
                                      object_path));
}

void TestService::RemoveObjectInternal(const ObjectPath& object_path) {
  Signal signal(kObjectManagerInterface, kObjectManagerInterfacesRemoved);
  MessageWriter writer(&signal);

  writer.AppendObjectPath(object_path);

  std::vector<std::string> interfaces;
  interfaces.push_back("org.chromium.TestInterface");
  writer.AppendArrayOfStrings(interfaces);

  exported_object_manager_->SendSignal(&signal);
}

void TestService::SendPropertyChangedSignal(const std::string& name) {
  message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&TestService::SendPropertyChangedSignalInternal,
                 base::Unretained(this),
                 name));
}

void TestService::SendPropertyChangedSignalInternal(const std::string& name) {
  Signal signal(kPropertiesInterface, kPropertiesChanged);
  MessageWriter writer(&signal);
  writer.AppendString("org.chromium.TestInterface");

  MessageWriter array_writer(NULL);
  MessageWriter dict_entry_writer(NULL);

  writer.OpenArray("{sv}", &array_writer);
  array_writer.OpenDictEntry(&dict_entry_writer);
  dict_entry_writer.AppendString("Name");
  dict_entry_writer.AppendVariantOfString(name);
  array_writer.CloseContainer(&dict_entry_writer);
  writer.CloseContainer(&array_writer);

  exported_object_->SendSignal(&signal);
}

}  // namespace dbus

/* [<][>][^][v][top][bottom][index][help] */