// Copyright (c) 2012 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 <algorithm>
#include <sys/select.h>
#include <stdlib.h>
#include <unistd.h>
#include "dbus_test.h"
namespace {
const char kServerAddress[] = "unix:abstract=/org/chromium/DBusTest";
} // namespace
DBusMatch::DBusMatch()
: message_type_(DBUS_MESSAGE_TYPE_INVALID),
as_property_dictionary_(false),
send_reply_(false),
send_error_(false),
expect_serial_(false),
matched_(false) {
}
DBusMatch& DBusMatch::WithString(std::string value) {
Arg arg;
arg.type = DBUS_TYPE_STRING;
arg.array = false;
arg.string_value = value;
if (send_reply_)
reply_args_.push_back(arg);
else
args_.push_back(arg);
return *this;
}
DBusMatch& DBusMatch::WithUnixFd(int value) {
Arg arg;
arg.type = DBUS_TYPE_UNIX_FD;
arg.array = false;
arg.int_value = value;
if (send_reply_)
reply_args_.push_back(arg);
else
args_.push_back(arg);
return *this;
}
DBusMatch& DBusMatch::WithObjectPath(std::string value) {
Arg arg;
arg.type = DBUS_TYPE_OBJECT_PATH;
arg.array = false;
arg.string_value = value;
if (send_reply_)
reply_args_.push_back(arg);
else
args_.push_back(arg);
return *this;
}
DBusMatch& DBusMatch::WithArrayOfStrings(std::vector<std::string> values) {
Arg arg;
arg.type = DBUS_TYPE_STRING;
arg.array = true;
arg.string_values = values;
if (send_reply_)
reply_args_.push_back(arg);
else
args_.push_back(arg);
return *this;
}
DBusMatch& DBusMatch::WithArrayOfObjectPaths(std::vector<std::string> values) {
Arg arg;
arg.type = DBUS_TYPE_OBJECT_PATH;
arg.array = true;
arg.string_values = values;
if (send_reply_)
reply_args_.push_back(arg);
else
args_.push_back(arg);
return *this;
}
DBusMatch& DBusMatch::WithNoMoreArgs() {
Arg arg;
arg.type = DBUS_TYPE_INVALID;
args_.push_back(arg);
return *this;
}
DBusMatch& DBusMatch::AsPropertyDictionary() {
as_property_dictionary_ = true;
return *this;
}
DBusMatch& DBusMatch::SendReply() {
send_reply_ = true;
expect_serial_ = true;
return *this;
}
DBusMatch& DBusMatch::SendError(std::string error_name,
std::string error_message) {
send_error_ = true;
error_name_ = error_name;
error_message_ = error_message;
expect_serial_ = true;
return *this;
}
DBusMatch& DBusMatch::SendReplyNoWait() {
send_reply_ = true;
expect_serial_ = false;
return *this;
}
DBusMatch& DBusMatch::Send() {
DBusMessage *message;
if (message_type_ == DBUS_MESSAGE_TYPE_SIGNAL)
message = dbus_message_new_signal(path_.c_str(),
interface_.c_str(),
member_.c_str());
else if (message_type_ == DBUS_MESSAGE_TYPE_METHOD_CALL)
message = dbus_message_new_method_call(NULL,
path_.c_str(),
interface_.c_str(),
member_.c_str());
else
return *this;
AppendArgsToMessage(message, &args_);
SendMessage(conn_, message);
dbus_message_unref(message);
return *this;
}
void DBusMatch::ExpectMethodCall(std::string path,
std::string interface,
std::string method) {
message_type_ = DBUS_MESSAGE_TYPE_METHOD_CALL;
path_ = path;
interface_ = interface;
member_ = method;
}
void DBusMatch::CreateSignal(DBusConnection *conn,
std::string path,
std::string interface,
std::string signal_name) {
message_type_ = DBUS_MESSAGE_TYPE_SIGNAL;
path_ = path;
interface_ = interface;
member_ = signal_name;
conn_ = conn;
expect_serial_ = true;
matched_ = true;
}
void DBusMatch::CreateMessageCall(DBusConnection *conn,
std::string path,
std::string interface,
std::string method_name) {
message_type_ = DBUS_MESSAGE_TYPE_METHOD_CALL;
path_ = path;
interface_ = interface;
member_ = method_name;
conn_ = conn;
expect_serial_ = true;
matched_ = true;
}
bool DBusMatch::MatchMessageArgs(DBusMessage *message,
std::vector<Arg> *args)
{
DBusMessageIter iter;
dbus_message_iter_init(message, &iter);
for (std::vector<Arg>::iterator it = args->begin(); it != args->end(); ++it) {
Arg &arg = *it;
int type = dbus_message_iter_get_arg_type(&iter);
if (type != arg.type)
return false;
if (arg.type == DBUS_TYPE_STRING
|| arg.type == DBUS_TYPE_OBJECT_PATH) {
const char *str_value;
dbus_message_iter_get_basic(&iter, &str_value);
if (strcmp(str_value, arg.string_value.c_str()) != 0)
return false;
}
// TODO(keybuk): additional argument types
dbus_message_iter_next(&iter);
}
return true;
}
void DBusMatch::AppendArgsToMessage(DBusMessage *message,
std::vector<Arg> *args)
{
DBusMessageIter message_iter;
DBusMessageIter dict_array_iter;
DBusMessageIter struct_iter;
DBusMessageIter iter;
if (as_property_dictionary_) {
dbus_message_iter_init_append(message, &message_iter);
dbus_message_iter_open_container(&message_iter,
DBUS_TYPE_ARRAY, "{sv}",
&dict_array_iter);
} else {
dbus_message_iter_init_append(message, &iter);
}
for (std::vector<Arg>::iterator it = args->begin(); it != args->end(); ++it) {
Arg &arg = *it;
if (as_property_dictionary_) {
dbus_message_iter_open_container(&dict_array_iter,
DBUS_TYPE_DICT_ENTRY, NULL,
&struct_iter);
const char *str_value = arg.string_value.c_str();
dbus_message_iter_append_basic(&struct_iter, arg.type, &str_value);
arg = *(++it);
}
const char *array_type, *element_type;
switch (arg.type) {
case DBUS_TYPE_STRING:
array_type = DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING;
element_type = DBUS_TYPE_STRING_AS_STRING;
break;
case DBUS_TYPE_OBJECT_PATH:
array_type = DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING;
element_type = DBUS_TYPE_OBJECT_PATH_AS_STRING;
break;
case DBUS_TYPE_UNIX_FD:
array_type = DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_UNIX_FD_AS_STRING;
element_type = DBUS_TYPE_UNIX_FD_AS_STRING;
break;
default:
abort();
// TODO(keybuk): additional argument types
}
if (as_property_dictionary_) {
dbus_message_iter_open_container(&struct_iter,
DBUS_TYPE_VARIANT,
arg.array ? array_type : element_type,
&iter);
}
DBusMessageIter array_iter;
if (arg.array) {
dbus_message_iter_open_container(&iter,
DBUS_TYPE_ARRAY, element_type,
&array_iter);
if (arg.type == DBUS_TYPE_STRING
|| arg.type == DBUS_TYPE_OBJECT_PATH) {
for (std::vector<std::string>::const_iterator vit =
arg.string_values.begin(); vit != arg.string_values.end();
++vit) {
const char *str_value = vit->c_str();
dbus_message_iter_append_basic(&array_iter, arg.type, &str_value);
}
}
// TODO(keybuk): additional element types
dbus_message_iter_close_container(&iter, &array_iter);
} else {
if (arg.type == DBUS_TYPE_STRING
|| arg.type == DBUS_TYPE_OBJECT_PATH) {
const char *str_value = arg.string_value.c_str();
dbus_message_iter_append_basic(&iter, arg.type, &str_value);
} else if (arg.type == DBUS_TYPE_UNIX_FD) {
dbus_message_iter_append_basic(&iter, arg.type, &arg.int_value);
}
// TODO(keybuk): additional argument types
}
if (as_property_dictionary_) {
dbus_message_iter_close_container(&struct_iter, &iter);
dbus_message_iter_close_container(&dict_array_iter, &struct_iter);
}
}
if (as_property_dictionary_)
dbus_message_iter_close_container(&message_iter, &dict_array_iter);
}
void DBusMatch::SendMessage(DBusConnection *conn, DBusMessage *message) {
dbus_bool_t success;
dbus_uint32_t serial;
success = dbus_connection_send(conn, message, &serial);
if (success && expect_serial_)
expected_serials_.push_back(serial);
}
bool DBusMatch::HandleServerMessage(DBusConnection *conn,
DBusMessage *message) {
// Make sure we're expecting a method call or signal of this name
if (message_type_ == DBUS_MESSAGE_TYPE_METHOD_CALL &&
!dbus_message_is_method_call(message,
interface_.c_str(), member_.c_str()))
return false;
else if (message_type_ == DBUS_MESSAGE_TYPE_SIGNAL &&
!dbus_message_is_signal(message,
interface_.c_str(), member_.c_str()))
return false;
// Make sure the path is what we expected.
if (path_.length() &&
strcmp(path_.c_str(), dbus_message_get_path(message)) != 0)
return false;
// And the arguments.
if (!MatchMessageArgs(message, &args_))
return false;
// Handle any actions.
matched_ = true;
if (send_reply_ || send_error_) {
// Send out the reply
DBusMessage *reply = NULL;
if (send_reply_)
reply = dbus_message_new_method_return(message);
else if (send_error_)
reply = dbus_message_new_error(message,
error_name_.c_str(),
error_message_.c_str());
AppendArgsToMessage(reply, &reply_args_);
SendMessage(conn, reply);
dbus_message_unref(reply);
}
return true;
}
bool DBusMatch::HandleClientMessage(DBusConnection *conn,
DBusMessage *message) {
// From the client side we check whether the message has a serial number
// we generated on our server side, and if so, remove it from the list of
// those we're expecting to see.
for (std::vector<dbus_uint32_t>::iterator it = expected_serials_.begin();
it != expected_serials_.end(); ++it) {
if (*it == dbus_message_get_serial(message)) {
expected_serials_.erase(it);
return true;
}
}
return false;
}
bool DBusMatch::Complete() {
return matched_ && expected_serials_.size() == 0;
}
DBusTest::DBusTest()
: conn_(NULL),
server_(NULL),
server_conn_(NULL),
dispatch_(false) {
}
DBusTest::~DBusTest() {
}
DBusMatch& DBusTest::ExpectMethodCall(std::string path,
std::string interface,
std::string method) {
DBusMatch match;
match.ExpectMethodCall(path, interface, method);
pthread_mutex_lock(&mutex_);
matches_.push_back(match);
DBusMatch &ref = matches_.back();
pthread_mutex_unlock(&mutex_);
return ref;
}
DBusMatch& DBusTest::CreateSignal(std::string path,
std::string interface,
std::string signal_name) {
DBusMatch match;
match.CreateSignal(server_conn_, path, interface, signal_name);
pthread_mutex_lock(&mutex_);
matches_.push_back(match);
DBusMatch &ref = matches_.back();
pthread_mutex_unlock(&mutex_);
return ref;
}
DBusMatch& DBusTest::CreateMessageCall(std::string path,
std::string interface,
std::string signal_name) {
DBusMatch match;
match.CreateMessageCall(server_conn_, path, interface, signal_name);
pthread_mutex_lock(&mutex_);
matches_.push_back(match);
DBusMatch &ref = matches_.back();
pthread_mutex_unlock(&mutex_);
return ref;
}
void DBusTest::WaitForMatches() {
for (;;) {
pthread_mutex_lock(&mutex_);
size_t incomplete_matches = 0;
for (std::vector<DBusMatch>::iterator it = matches_.begin();
it != matches_.end(); ++it) {
DBusMatch &match = *it;
if (!match.Complete())
++incomplete_matches;
}
pthread_mutex_unlock(&mutex_);
if (!incomplete_matches)
break;
// Fish a message from the queue.
DBusMessage *message;
while ((message = dbus_connection_borrow_message(conn_)) == NULL)
dbus_connection_read_write(conn_, -1);
// Allow matches to verify the serial of the message.
pthread_mutex_lock(&mutex_);
for (std::vector<DBusMatch>::iterator it = matches_.begin();
it != matches_.end(); ++it) {
DBusMatch &match = *it;
if (match.HandleClientMessage(conn_, message))
break;
}
pthread_mutex_unlock(&mutex_);
// Throw it back and dispatch.
dbus_connection_return_message(conn_, message);
dbus_connection_dispatch(conn_);
}
pthread_mutex_lock(&mutex_);
matches_.erase(matches_.begin(), matches_.end());
pthread_mutex_unlock(&mutex_);
}
void DBusTest::SetUp() {
dbus_threads_init_default();
// Create the D-Bus server that will accept a connection for us, since
// there's no "just give me a socketpair" option in libdbus.
server_ = dbus_server_listen(kServerAddress, NULL);
ASSERT_TRUE(server_ != NULL);
dbus_server_set_new_connection_function(server_, NewConnectionThunk,
this, NULL);
dbus_bool_t success;
success = dbus_server_set_watch_functions(server_,
AddWatchThunk,
RemoveWatchThunk,
WatchToggledThunk,
this, NULL);
ASSERT_TRUE(success);
success = dbus_server_set_timeout_functions(server_,
AddTimeoutThunk,
RemoveTimeoutThunk,
TimeoutToggledThunk,
this, NULL);
ASSERT_TRUE(success);
// Open a connection to our server, this returns the "client" side of the
// connection.
conn_ = dbus_connection_open_private(kServerAddress, NULL);
ASSERT_TRUE(conn_ != NULL);
// The "server" side of the connection comes from the NewConnection method
// we set above. Dispatch until we have it.
while (!server_conn_)
DispatchOnce();
// Now we set off "main loop" in the background to dispatch until the
// client is disconnected by the TearDown method.
int r;
r = pthread_mutex_init(&mutex_, NULL);
ASSERT_EQ(0, r);
dispatch_ = true;
r = pthread_create(&thread_id_, NULL, DispatchLoopThunk, this);
ASSERT_EQ(0, r);
}
void DBusTest::TearDown() {
WaitForMatches();
// Close the client end of the connection, this will result in a signal
// within the dispatch loop of the server.
if (conn_) {
dbus_connection_flush(conn_);
dbus_connection_close(conn_);
dbus_connection_unref(conn_);
conn_ = NULL;
}
// Join the thread and wait for it to finish dispatch.
if (dispatch_)
pthread_join(thread_id_, NULL);
pthread_mutex_destroy(&mutex_);
// Clean up the server end of the connection and the server itself.
if (server_conn_) {
dbus_connection_flush(server_conn_);
dbus_connection_close(server_conn_);
dbus_connection_unref(server_conn_);
server_conn_ = NULL;
}
dbus_server_disconnect(server_);
dbus_server_unref(server_);
server_ = NULL;
dbus_shutdown();
}
void DBusTest::NewConnectionThunk(DBusServer *server,
DBusConnection *conn,
void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
test->NewConnection(server, conn);
}
void DBusTest::NewConnection(DBusServer *server, DBusConnection *conn) {
ASSERT_TRUE(server_conn_ == NULL);
dbus_bool_t success;
success = dbus_connection_set_watch_functions(conn,
AddWatchThunk,
RemoveWatchThunk,
WatchToggledThunk,
this, NULL);
ASSERT_TRUE(success);
success = dbus_connection_set_timeout_functions(conn,
AddTimeoutThunk,
RemoveTimeoutThunk,
TimeoutToggledThunk,
this, NULL);
ASSERT_TRUE(success);
success = dbus_connection_add_filter(conn,
HandleMessageThunk,
this, NULL);
ASSERT_TRUE(success);
server_conn_ = conn;
dbus_connection_ref(server_conn_);
}
dbus_bool_t DBusTest::AddWatchThunk(DBusWatch *watch, void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
return test->AddWatch(watch);
}
dbus_bool_t DBusTest::AddWatch(DBusWatch *watch) {
watches_.push_back(watch);
return TRUE;
}
void DBusTest::RemoveWatchThunk(DBusWatch *watch, void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
test->RemoveWatch(watch);
}
void DBusTest::RemoveWatch(DBusWatch *watch) {
std::vector<DBusWatch *>::iterator it =
find(watches_.begin(), watches_.end(), watch);
if (it != watches_.end())
watches_.erase(it);
}
void DBusTest::WatchToggledThunk(DBusWatch *watch, void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
test->WatchToggled(watch);
}
void DBusTest::WatchToggled(DBusWatch *watch) {
}
dbus_bool_t DBusTest::AddTimeoutThunk(DBusTimeout *timeout, void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
return test->AddTimeout(timeout);
}
dbus_bool_t DBusTest::AddTimeout(DBusTimeout *timeout) {
timeouts_.push_back(timeout);
return TRUE;
}
void DBusTest::RemoveTimeoutThunk(DBusTimeout *timeout, void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
test->RemoveTimeout(timeout);
}
void DBusTest::RemoveTimeout(DBusTimeout *timeout) {
std::vector<DBusTimeout *>::iterator it =
find(timeouts_.begin(), timeouts_.end(), timeout);
if (it != timeouts_.end())
timeouts_.erase(it);
}
void DBusTest::TimeoutToggledThunk(DBusTimeout *timeout, void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
test->TimeoutToggled(timeout);
}
void DBusTest::TimeoutToggled(DBusTimeout *timeout) {
}
DBusHandlerResult DBusTest::HandleMessageThunk(DBusConnection *conn,
DBusMessage *message,
void *data) {
DBusTest *test = static_cast<DBusTest *>(data);
return test->HandleMessage(conn, message);
}
DBusHandlerResult DBusTest::HandleMessage(DBusConnection *conn,
DBusMessage *message) {
if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
dispatch_ = false;
return DBUS_HANDLER_RESULT_HANDLED;
}
pthread_mutex_lock(&mutex_);
for (std::vector<DBusMatch>::iterator it = matches_.begin();
it != matches_.end(); ++it) {
DBusMatch &match = *it;
if (match.HandleServerMessage(conn, message)) {
pthread_mutex_unlock(&mutex_);
return DBUS_HANDLER_RESULT_HANDLED;
}
}
pthread_mutex_unlock(&mutex_);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
void *DBusTest::DispatchLoopThunk(void *ptr) {
DBusTest *test = static_cast<DBusTest *>(ptr);
return test->DispatchLoop();
}
void *DBusTest::DispatchLoop() {
while (dispatch_)
DispatchOnce();
return NULL;
}
void DBusTest::DispatchOnce() {
fd_set readfds, writefds;
int nfds = 0;
int r;
// Ideally we'd just use dbus_connection_read_write_dispatch() here, but
// we have to deal with both the server itself and its connection, so we
// have to do it all by hand.
FD_ZERO(&readfds);
FD_ZERO(&writefds);
for (std::vector<DBusWatch *>::iterator it = watches_.begin();
it != watches_.end(); ++it) {
DBusWatch *watch = *it;
if (!dbus_watch_get_enabled(watch))
continue;
int fd = dbus_watch_get_unix_fd(watch);
if (fd + 1 > nfds)
nfds = fd + 1;
unsigned int flags = dbus_watch_get_flags(watch);
if (flags & DBUS_WATCH_READABLE)
FD_SET(fd, &readfds);
if (flags & DBUS_WATCH_WRITABLE)
FD_SET(fd, &writefds);
}
// Only block in select for the interval of the smallest timeout; this
// isn't quite right according to the D-Bus spec, since the interval is
// supposed to be since the time the timeout was added or toggled, but
// it's good enough for the purposes of testing.
DBusTimeout *earliest_timeout = NULL;
struct timeval timeval;
for (std::vector<DBusTimeout *>::iterator it = timeouts_.begin();
it != timeouts_.end(); ++it) {
DBusTimeout *timeout = *it;
if (!dbus_timeout_get_enabled(timeout))
continue;
if (!earliest_timeout || (dbus_timeout_get_interval(timeout) <
dbus_timeout_get_interval(earliest_timeout)))
earliest_timeout = timeout;
}
if (earliest_timeout) {
int interval = dbus_timeout_get_interval(earliest_timeout);
timeval.tv_sec = interval / 1000;
timeval.tv_usec = (interval % 1000) * 1000;
r = select(nfds, &readfds, &writefds, NULL, &timeval);
} else {
r = select(nfds, &readfds, &writefds, NULL, NULL);
}
ASSERT_LE(0, r);
// Handle the timeout if we didn't poll for anything else.
if (r == 0 && earliest_timeout)
dbus_timeout_handle(earliest_timeout);
// Handle the watches, use a copy of the vector since a watch handler
// might remove other watches in the vector.
std::vector<DBusWatch *> immutable_watches = watches_;
for (std::vector<DBusWatch *>::iterator it = immutable_watches.begin();
it != immutable_watches.end(); ++it) {
DBusWatch *watch = *it;
int fd = dbus_watch_get_unix_fd(watch);
unsigned int flags = 0;
if (FD_ISSET(fd, &readfds))
flags |= DBUS_WATCH_READABLE;
if (FD_ISSET(fd, &writefds))
flags |= DBUS_WATCH_WRITABLE;
if (flags)
dbus_watch_handle(watch, flags);
}
// Dispatch data on the server-side of the connection.
while (server_conn_ &&
dbus_connection_dispatch(server_conn_) == DBUS_DISPATCH_DATA_REMAINS)
;
}