// 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_path.h" #include "dbus/property.h" namespace dbus { // Echo, SlowEcho, AsyncEcho, BrokenMethod, GetAll, Get, Set. const int TestService::kNumMethodsToExport = 7; TestService::Options::Options() { } TestService::Options::~Options() { } TestService::TestService(const Options& options) : base::Thread("TestService"), dbus_thread_message_loop_proxy_(options.dbus_thread_message_loop_proxy), on_all_methods_exported_(false, false), num_exported_methods_(0) { } TestService::~TestService() { Stop(); } bool TestService::StartService() { base::Thread::Options thread_options; thread_options.message_loop_type = MessageLoop::TYPE_IO; return StartWithOptions(thread_options); } bool TestService::WaitUntilServiceIsStarted() { const base::TimeDelta timeout( base::TimeDelta::FromMilliseconds( TestTimeouts::action_max_timeout_ms())); // Wait until all methods are exported. return on_all_methods_exported_.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) { dbus::Signal signal("org.chromium.TestInterface", "Test"); dbus::MessageWriter writer(&signal); writer.AppendString(message); exported_object_->SendSignal(&signal); } void TestService::SendTestSignalFromRootInternal(const std::string& message) { dbus::Signal signal("org.chromium.TestInterface", "Test"); dbus::MessageWriter writer(&signal); writer.AppendString(message); bus_->RequestOwnership("org.chromium.TestService", base::Bind(&TestService::OnOwnership, base::Unretained(this))); // Use "/" just like dbus-send does. ExportedObject* root_object = bus_->GetExportedObject(dbus::ObjectPath("/")); root_object->SendSignal(&signal); } void TestService::OnOwnership(const std::string& service_name, bool success) { LOG_IF(ERROR, !success) << "Failed to own: " << service_name; } 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) on_all_methods_exported_.Signal(); } void TestService::Run(MessageLoop* message_loop) { Bus::Options bus_options; bus_options.bus_type = Bus::SESSION; bus_options.connection_type = Bus::PRIVATE; bus_options.dbus_thread_message_loop_proxy = dbus_thread_message_loop_proxy_; bus_ = new Bus(bus_options); bus_->RequestOwnership("org.chromium.TestService", base::Bind(&TestService::OnOwnership, base::Unretained(this))); exported_object_ = bus_->GetExportedObject( dbus::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( 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; // 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, dbus::ExportedObject::ResponseSender response_sender) { MessageReader reader(method_call); std::string text_message; if (!reader.PopString(&text_message)) { response_sender.Run(NULL); return; } Response* response = Response::FromMethodCall(method_call); MessageWriter writer(response); writer.AppendString(text_message); response_sender.Run(response); } void TestService::SlowEcho( MethodCall* method_call, dbus::ExportedObject::ResponseSender response_sender) { base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); Echo(method_call, response_sender); } void TestService::AsyncEcho( MethodCall* method_call, dbus::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, dbus::ExportedObject::ResponseSender response_sender) { response_sender.Run(NULL); } void TestService::GetAllProperties( MethodCall* method_call, dbus::ExportedObject::ResponseSender response_sender) { MessageReader reader(method_call); std::string interface; if (!reader.PopString(&interface)) { response_sender.Run(NULL); return; } // 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"]> // ] Response* response = Response::FromMethodCall(method_call); MessageWriter writer(response); 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(dbus::ObjectPath("/TestObjectPath")); variant_writer.CloseContainer(&variant_array_writer); dict_entry_writer.CloseContainer(&variant_writer); array_writer.CloseContainer(&dict_entry_writer); writer.CloseContainer(&array_writer); response_sender.Run(response); } void TestService::GetProperty( MethodCall* method_call, dbus::ExportedObject::ResponseSender response_sender) { MessageReader reader(method_call); std::string interface; if (!reader.PopString(&interface)) { response_sender.Run(NULL); return; } std::string name; if (!reader.PopString(&name)) { response_sender.Run(NULL); return; } if (name == "Name") { // Return the previous value for the "Name" property: // Variant<"TestService"> Response* response = Response::FromMethodCall(method_call); MessageWriter writer(response); writer.AppendVariantOfString("TestService"); response_sender.Run(response); } else if (name == "Version") { // Return a new value for the "Version" property: // Variant<20> Response* response = Response::FromMethodCall(method_call); MessageWriter writer(response); writer.AppendVariantOfInt16(20); response_sender.Run(response); } else if (name == "Methods") { // Return the previous value for the "Methods" property: // Variant<["Echo", "SlowEcho", "AsyncEcho", "BrokenMethod"]> Response* response = Response::FromMethodCall(method_call); MessageWriter writer(response); 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); } else if (name == "Objects") { // Return the previous value for the "Objects" property: // Variant<[objectpath:"/TestObjectPath"]> Response* response = Response::FromMethodCall(method_call); MessageWriter writer(response); 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(dbus::ObjectPath("/TestObjectPath")); variant_writer.CloseContainer(&variant_array_writer); writer.CloseContainer(&variant_writer); response_sender.Run(response); } else { // Return error. response_sender.Run(NULL); return; } } void TestService::SetProperty( MethodCall* method_call, dbus::ExportedObject::ResponseSender response_sender) { MessageReader reader(method_call); std::string interface; if (!reader.PopString(&interface)) { response_sender.Run(NULL); return; } std::string name; if (!reader.PopString(&name)) { response_sender.Run(NULL); return; } if (name != "Name") { response_sender.Run(NULL); return; } std::string value; if (!reader.PopVariantOfString(&value)) { response_sender.Run(NULL); return; } SendPropertyChangedSignal(value); Response* response = Response::FromMethodCall(method_call); response_sender.Run(response); } 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) { dbus::Signal signal(kPropertiesInterface, kPropertiesChanged); dbus::MessageWriter writer(&signal); writer.AppendString("org.chromium.TestService"); 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