diff options
Diffstat (limited to 'dbus')
-rw-r--r-- | dbus/dbus.gyp | 19 | ||||
-rw-r--r-- | dbus/message.cc | 664 | ||||
-rw-r--r-- | dbus/message.h | 333 | ||||
-rw-r--r-- | dbus/message_unittest.cc | 372 |
4 files changed, 1388 insertions, 0 deletions
diff --git a/dbus/dbus.gyp b/dbus/dbus.gyp index f2f842b..c1fd9fe 100644 --- a/dbus/dbus.gyp +++ b/dbus/dbus.gyp @@ -15,6 +15,25 @@ '../build/linux/system.gyp:dbus', ], 'sources': [ + 'message.cc', + 'message.h', + ], + }, + { + 'target_name': 'dbus_unittests', + 'type': 'executable', + 'dependencies': [ + 'dbus', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + '../build/linux/system.gyp:dbus', + ], + 'sources': [ + '../base/test/run_all_unittests.cc', + 'message_unittest.cc', + ], + 'include_dirs': [ + '..', ], }, ], diff --git a/dbus/message.cc b/dbus/message.cc new file mode 100644 index 0000000..28f8960 --- /dev/null +++ b/dbus/message.cc @@ -0,0 +1,664 @@ +// Copyright (c) 2011 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/message.h" + +#include "base/basictypes.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/stringprintf.h" + +namespace dbus { + +Message::Message() + : raw_message_(NULL) { +} + +Message::~Message() { + if (raw_message_) + dbus_message_unref(raw_message_); +} + +Message::MessageType Message::GetMessageType() { + if (!raw_message_) + return MESSAGE_INVALID; + const int type = dbus_message_get_type(raw_message_); + return static_cast<Message::MessageType>(type); +} + +void Message::reset_raw_message(DBusMessage* raw_message) { + if (raw_message_) + dbus_message_unref(raw_message_); + raw_message_ = raw_message; +} + +std::string Message::ToStringInternal(const std::string& indent, + MessageReader* reader) { + const char* kBrokenMessage = "[broken message]"; + std::string output; + while (reader->HasMoreData()) { + const DataType type = reader->GetDataType(); + switch (type) { + case BYTE: { + uint8 value = 0; + if (!reader->PopByte(&value)) + return kBrokenMessage; + output += indent + "byte " + base::StringPrintf("%d", value) + "\n"; + break; + } + case BOOL: { + bool value = false; + if (!reader->PopBool(&value)) + return kBrokenMessage; + output += indent + "bool " + (value ? "true" : "false") + "\n"; + break; + } + case INT16: { + int16 value = 0; + if (!reader->PopInt16(&value)) + return kBrokenMessage; + output += indent + "int16 " + base::StringPrintf("%d", value) + "\n"; + break; + } + case UINT16: { + uint16 value = 0; + if (!reader->PopUint16(&value)) + return kBrokenMessage; + output += indent + "uint16 " + base::StringPrintf("%d", value) + "\n"; + break; + } + case INT32: { + int32 value = 0; + if (!reader->PopInt32(&value)) + return kBrokenMessage; + output += indent + "int32 " + base::StringPrintf("%d", value) + "\n"; + break; + } + case UINT32: { + uint32 value = 0; + if (!reader->PopUint32(&value)) + return kBrokenMessage; + output += indent + "uint32 " + base::StringPrintf("%u", value) + "\n"; + break; + } + case INT64: { + int64 value = 0; + if (!reader->PopInt64(&value)) + return kBrokenMessage; + output += (indent + "int64 " + + base::StringPrintf("%" PRId64, value) + "\n"); + break; + } + case UINT64: { + uint64 value = 0; + if (!reader->PopUint64(&value)) + return kBrokenMessage; + output += (indent + "uint64 " + + base::StringPrintf("%" PRIu64, value) + "\n"); + break; + } + case DOUBLE: { + double value = 0; + if (!reader->PopDouble(&value)) + return kBrokenMessage; + output += indent + "double " + base::StringPrintf("%f", value) + "\n"; + break; + } + case STRING: { + std::string value; + if (!reader->PopString(&value)) + return kBrokenMessage; + output += indent + "string \"" + value + "\"\n"; + break; + } + case OBJECT_PATH: { + std::string value; + if (!reader->PopObjectPath(&value)) + return kBrokenMessage; + output += indent + "object_path \"" + value + "\"\n"; + break; + } + case ARRAY: { + MessageReader sub_reader(this); + if (!reader->PopArray(&sub_reader)) + return kBrokenMessage; + output += indent + "array [\n"; + output += ToStringInternal(indent + " ", &sub_reader); + output += indent + "]\n"; + break; + } + case STRUCT: { + MessageReader sub_reader(this); + if (!reader->PopStruct(&sub_reader)) + return kBrokenMessage; + output += indent + "struct {\n"; + output += ToStringInternal(indent + " ", &sub_reader); + output += indent + "}\n"; + break; + } + case DICT_ENTRY: { + MessageReader sub_reader(this); + if (!reader->PopDictEntry(&sub_reader)) + return kBrokenMessage; + output += indent + "dict entry {\n"; + output += ToStringInternal(indent + " ", &sub_reader); + output += indent + "}\n"; + break; + } + case VARIANT: { + MessageReader sub_reader(this); + if (!reader->PopVariant(&sub_reader)) + return kBrokenMessage; + output += indent + "variant "; + output += ToStringInternal(indent + " ", &sub_reader); + break; + } + default: + LOG(FATAL) << "Unknown type: " << type; + } + } + return output; +} + +// The returned string consists of message headers such as +// destination if any, followed by a blank line, and the message +// payload. For example, a MethodCall's ToString() will look like: +// +// destination: com.example.Service +// path: /com/example/Object +// interface: com.example.Interface +// member: SomeMethod +// +// string \"payload\" +// ... +std::string Message::ToString() { + if (!raw_message_) + return ""; + + // Generate headers first. + std::string headers; + const char* destination = dbus_message_get_destination(raw_message_); + if (destination) + headers += base::StringPrintf("destination: %s\n", destination); + const char* path = dbus_message_get_path(raw_message_); + if (path) + headers += base::StringPrintf("path: %s\n", path); + const char* interface = dbus_message_get_interface(raw_message_); + if (interface) + headers += base::StringPrintf("interface: %s\n", interface); + const char* member = dbus_message_get_member(raw_message_); + if (member) + headers += base::StringPrintf("member: %s\n", member); + const char* error_name = dbus_message_get_error_name(raw_message_); + if (error_name) + headers += base::StringPrintf("error_name: %s\n", error_name); + const char* sender = dbus_message_get_sender(raw_message_); + if (sender) + headers += base::StringPrintf("sender: %s\n", sender); + const char* signature = dbus_message_get_signature(raw_message_); + if (signature) + headers += base::StringPrintf("signature: %s\n", signature); + + // Generate the payload. + MessageReader reader(this); + return headers + "\n" + ToStringInternal("", &reader); +} + +// +// MethodCall implementation. +// + +MethodCall::MethodCall(const std::string& interface_name, + const std::string& method_name) + : Message(), + interface_name_(interface_name), + method_name_(method_name) { + reset_raw_message(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL)); + + bool success = dbus_message_set_interface(raw_message(), + interface_name.c_str()); + CHECK(success) << "Unable to allocate memory"; + + success = dbus_message_set_member(raw_message(), method_name.c_str()); + CHECK(success) << "Unable to allocate memory"; +} + +void MethodCall::SetServiceName(const std::string& service_name) { + const bool success = dbus_message_set_destination(raw_message(), + service_name.c_str()); + CHECK(success) << "Unable to allocate memory"; +} + +void MethodCall::SetObjectPath(const std::string& object_path) { + const bool success = dbus_message_set_path(raw_message(), + object_path.c_str()); + CHECK(success) << "Unable to allocate memory"; +} + +// +// Response implementation. +// + +Response::Response() : Message() { +} + +// +// MessageWriter implementation. +// + +MessageWriter::MessageWriter(Message* message) : + message_(message), + container_is_open_(false) { + dbus_message_iter_init_append(message_->raw_message(), &raw_message_iter_); +} + +MessageWriter::~MessageWriter() { +} + +void MessageWriter::AppendByte(uint8 value) { + AppendBasic(DBUS_TYPE_BYTE, &value); +} + +void MessageWriter::AppendBool(bool value) { + AppendBasic(DBUS_TYPE_BOOLEAN, &value); +} + +void MessageWriter::AppendInt16(int16 value) { + AppendBasic(DBUS_TYPE_INT16, &value); +} + +void MessageWriter::AppendUint16(uint16 value) { + AppendBasic(DBUS_TYPE_UINT16, &value); +} + +void MessageWriter::AppendInt32(int32 value) { + AppendBasic(DBUS_TYPE_INT32, &value); +} + +void MessageWriter::AppendUint32(uint32 value) { + AppendBasic(DBUS_TYPE_UINT32, &value); +} + +void MessageWriter::AppendInt64(int64 value) { + AppendBasic(DBUS_TYPE_INT64, &value); +} + +void MessageWriter::AppendUint64(uint64 value) { + AppendBasic(DBUS_TYPE_UINT64, &value); +} + +void MessageWriter::AppendDouble(double value) { + AppendBasic(DBUS_TYPE_DOUBLE, &value); +} + +void MessageWriter::AppendString(const std::string& value) { + const char* pointer = value.c_str(); + AppendBasic(DBUS_TYPE_STRING, &pointer); + // TODO(satorux): It may make sense to return an error here, as the + // input string can be large. If needed, we could add something like + // bool AppendStringWithErrorChecking(). +} + +void MessageWriter::AppendObjectPath(const std::string& value) { + const char* pointer = value.c_str(); + AppendBasic(DBUS_TYPE_OBJECT_PATH, &pointer); +} + +// Ideally, client shouldn't need to supply the signature string, but +// the underlying D-Bus library requires us to supply this before +// appending contents to array and variant. It's technically possible +// for us to design API that doesn't require the signature but it will +// complicate the implementation so we decided to have the signature +// parameter. Hopefully, variants are less used in request messages from +// client side than response message from server side, so this should +// not be a big issue. +void MessageWriter::OpenArray(const std::string& signature, + MessageWriter* writer) { + DCHECK(!container_is_open_); + + const bool success = dbus_message_iter_open_container( + &raw_message_iter_, + DBUS_TYPE_ARRAY, + signature.c_str(), + &writer->raw_message_iter_); + CHECK(success) << "Unable to allocate memory"; + container_is_open_ = true; +} + +void MessageWriter::OpenVariant(const std::string& signature, + MessageWriter* writer) { + DCHECK(!container_is_open_); + + const bool success = dbus_message_iter_open_container( + &raw_message_iter_, + DBUS_TYPE_VARIANT, + signature.c_str(), + &writer->raw_message_iter_); + CHECK(success) << "Unable to allocate memory"; + container_is_open_ = true; +} + +void MessageWriter::OpenStruct(MessageWriter* writer) { + DCHECK(!container_is_open_); + + const bool success = dbus_message_iter_open_container( + &raw_message_iter_, + DBUS_TYPE_STRUCT, + NULL, // Signature should be NULL. + &writer->raw_message_iter_); + CHECK(success) << "Unable to allocate memory"; + container_is_open_ = true; +} + +void MessageWriter::OpenDictEntry(MessageWriter* writer) { + DCHECK(!container_is_open_); + + const bool success = dbus_message_iter_open_container( + &raw_message_iter_, + DBUS_TYPE_DICT_ENTRY, + NULL, // Signature should be NULL. + &writer->raw_message_iter_); + CHECK(success) << "Unable to allocate memory"; + container_is_open_ = true; +} + +void MessageWriter::CloseContainer(MessageWriter* writer) { + DCHECK(container_is_open_); + + const bool success = dbus_message_iter_close_container( + &raw_message_iter_, &writer->raw_message_iter_); + CHECK(success) << "Unable to allocate memory"; + container_is_open_ = false; +} + +void MessageWriter::AppendArrayOfBytes(const uint8* values, size_t length) { + DCHECK(!container_is_open_); + MessageWriter array_writer(message_); + OpenArray("y", &array_writer); + const bool success = dbus_message_iter_append_fixed_array( + &(array_writer.raw_message_iter_), + DBUS_TYPE_BYTE, + &values, + static_cast<int>(length)); + CHECK(success) << "Unable to allocate memory"; + CloseContainer(&array_writer); +} + +void MessageWriter::AppendVariantOfByte(uint8 value) { + AppendVariantOfBasic(DBUS_TYPE_BYTE, &value); +} + +void MessageWriter::AppendVariantOfBool(bool value) { + AppendVariantOfBasic(DBUS_TYPE_BOOLEAN, &value); +} + +void MessageWriter::AppendVariantOfInt16(int16 value) { + AppendVariantOfBasic(DBUS_TYPE_INT16, &value); +} + +void MessageWriter::AppendVariantOfUint16(uint16 value) { + AppendVariantOfBasic(DBUS_TYPE_UINT16, &value); +} + +void MessageWriter::AppendVariantOfInt32(int32 value) { + AppendVariantOfBasic(DBUS_TYPE_INT32, &value); +} + +void MessageWriter::AppendVariantOfUint32(uint32 value) { + AppendVariantOfBasic(DBUS_TYPE_UINT32, &value); +} + +void MessageWriter::AppendVariantOfInt64(int64 value) { + AppendVariantOfBasic(DBUS_TYPE_INT64, &value); +} + +void MessageWriter::AppendVariantOfUint64(uint64 value) { + AppendVariantOfBasic(DBUS_TYPE_UINT64, &value); +} + +void MessageWriter::AppendVariantOfDouble(double value) { + AppendVariantOfBasic(DBUS_TYPE_DOUBLE, &value); +} + +void MessageWriter::AppendVariantOfString(const std::string& value) { + const char* pointer = value.c_str(); + AppendVariantOfBasic(DBUS_TYPE_STRING, &pointer); +} + +void MessageWriter::AppendVariantOfObjectPath(const std::string& value) { + const char* pointer = value.c_str(); + AppendVariantOfBasic(DBUS_TYPE_OBJECT_PATH, &pointer); +} + +void MessageWriter::AppendBasic(int dbus_type, const void* value) { + DCHECK(!container_is_open_); + + const bool success = dbus_message_iter_append_basic( + &raw_message_iter_, dbus_type, value); + // dbus_message_iter_append_basic() fails only when there is not enough + // memory. We don't return this error as there is nothing we can do when + // it fails to allocate memory for a byte etc. + CHECK(success) << "Unable to allocate memory"; +} + +void MessageWriter::AppendVariantOfBasic(int dbus_type, const void* value) { + const std::string signature = base::StringPrintf("%c", dbus_type); + MessageWriter variant_writer(message_); + OpenVariant(signature, &variant_writer); + variant_writer.AppendBasic(dbus_type, value); + CloseContainer(&variant_writer); +} + +// +// MessageReader implementation. +// + +MessageReader::MessageReader(Message* message) + : message_(message) { + dbus_message_iter_init(message_->raw_message(), &raw_message_iter_); +} + + +MessageReader::~MessageReader() { +} + +bool MessageReader::HasMoreData() { + const int dbus_type = dbus_message_iter_get_arg_type(&raw_message_iter_); + return dbus_type != DBUS_TYPE_INVALID; +} + +bool MessageReader::PopByte(uint8* value) { + return PopBasic(DBUS_TYPE_BYTE, value); +} + +bool MessageReader::PopBool(bool* value) { + return PopBasic(DBUS_TYPE_BOOLEAN, value); +} + +bool MessageReader::PopInt16(int16* value) { + return PopBasic(DBUS_TYPE_INT16, value); +} + +bool MessageReader::PopUint16(uint16* value) { + return PopBasic(DBUS_TYPE_UINT16, value); +} + +bool MessageReader::PopInt32(int32* value) { + return PopBasic(DBUS_TYPE_INT32, value); +} + +bool MessageReader::PopUint32(uint32* value) { + return PopBasic(DBUS_TYPE_UINT32, value); +} + +bool MessageReader::PopInt64(int64* value) { + return PopBasic(DBUS_TYPE_INT64, value); +} + +bool MessageReader::PopUint64(uint64* value) { + return PopBasic(DBUS_TYPE_UINT64, value); +} + +bool MessageReader::PopDouble(double* value) { + return PopBasic(DBUS_TYPE_DOUBLE, value); +} + +bool MessageReader::PopString(std::string* value) { + char* tmp_value = NULL; + const bool success = PopBasic(DBUS_TYPE_STRING, &tmp_value); + if (success) + value->assign(tmp_value); + return success; +} + +bool MessageReader::PopObjectPath(std::string* value) { + char* tmp_value = NULL; + const bool success = PopBasic(DBUS_TYPE_OBJECT_PATH, &tmp_value); + if (success) + value->assign(tmp_value); + return success; +} + +bool MessageReader::PopArray(MessageReader* sub_reader) { + return PopContainer(DBUS_TYPE_ARRAY, sub_reader); +} + +bool MessageReader::PopStruct(MessageReader* sub_reader) { + return PopContainer(DBUS_TYPE_STRUCT, sub_reader); +} + +bool MessageReader::PopDictEntry(MessageReader* sub_reader) { + return PopContainer(DBUS_TYPE_DICT_ENTRY, sub_reader); +} + +bool MessageReader::PopVariant(MessageReader* sub_reader) { + return PopContainer(DBUS_TYPE_VARIANT, sub_reader); +} + +bool MessageReader::PopArrayOfBytes(uint8** bytes, size_t* length) { + MessageReader array_reader(message_); + if (!PopArray(&array_reader)) + return false; + if (!array_reader.CheckDataType(DBUS_TYPE_BYTE)) + return false; + int int_length = 0; + dbus_message_iter_get_fixed_array(&array_reader.raw_message_iter_, + bytes, + &int_length); + *length = static_cast<int>(int_length); + return bytes != NULL; +} + +bool MessageReader::PopArrayOfObjectPaths( + std::vector<std::string> *object_paths) { + MessageReader array_reader(message_); + if (!PopArray(&array_reader)) + return false; + while (array_reader.HasMoreData()) { + std::string object_path; + if (!array_reader.PopObjectPath(&object_path)) + return false; + object_paths->push_back(object_path); + } + return true; +} + +bool MessageReader::PopVariantOfByte(uint8* value) { + return PopVariantOfBasic(DBUS_TYPE_BYTE, value); +} + +bool MessageReader::PopVariantOfBool(bool* value) { + return PopVariantOfBasic(DBUS_TYPE_BOOLEAN, value); +} + +bool MessageReader::PopVariantOfInt16(int16* value) { + return PopVariantOfBasic(DBUS_TYPE_INT16, value); +} + +bool MessageReader::PopVariantOfUint16(uint16* value) { + return PopVariantOfBasic(DBUS_TYPE_UINT16, value); +} + +bool MessageReader::PopVariantOfInt32(int32* value) { + return PopVariantOfBasic(DBUS_TYPE_INT32, value); +} + +bool MessageReader::PopVariantOfUint32(uint32* value) { + return PopVariantOfBasic(DBUS_TYPE_UINT32, value); +} + +bool MessageReader::PopVariantOfInt64(int64* value) { + return PopVariantOfBasic(DBUS_TYPE_INT64, value); +} + +bool MessageReader::PopVariantOfUint64(uint64* value) { + return PopVariantOfBasic(DBUS_TYPE_UINT64, value); +} + +bool MessageReader::PopVariantOfDouble(double* value) { + return PopVariantOfBasic(DBUS_TYPE_DOUBLE, value); +} + +bool MessageReader::PopVariantOfString(std::string* value) { + char* tmp_value = NULL; + const bool success = PopVariantOfBasic(DBUS_TYPE_STRING, &tmp_value); + if (success) + value->assign(tmp_value); + return success; +} + +bool MessageReader::PopVariantOfObjectPath(std::string* value) { + char* tmp_value = NULL; + const bool success = PopVariantOfBasic(DBUS_TYPE_OBJECT_PATH, &tmp_value); + if (success) + value->assign(tmp_value); + return success; +} + +Message::DataType MessageReader::GetDataType() { + const int dbus_type = dbus_message_iter_get_arg_type(&raw_message_iter_); + return static_cast<Message::DataType>(dbus_type); +} + +bool MessageReader::CheckDataType(int dbus_type) { + const int actual_type = dbus_message_iter_get_arg_type(&raw_message_iter_); + if (actual_type != dbus_type) { + VLOG(1) << "Type " << dbus_type << " is expected but got " + << actual_type; + return false; + } + return true; +} + +bool MessageReader::PopBasic(int dbus_type, void* value) { + if (!CheckDataType(dbus_type)) + return false; + // dbus_message_iter_get_basic() here should always work, as we have + // already checked the next item's data type in CheckDataType(). Note + // that dbus_message_iter_get_basic() is a void function. + dbus_message_iter_get_basic(&raw_message_iter_, value); + DCHECK(value); + dbus_message_iter_next(&raw_message_iter_); + return true; +} + +bool MessageReader::PopContainer(int dbus_type, MessageReader* sub_reader) { + DCHECK_NE(this, sub_reader); + + if (!CheckDataType(dbus_type)) + return false; + dbus_message_iter_recurse(&raw_message_iter_, + &sub_reader->raw_message_iter_); + dbus_message_iter_next(&raw_message_iter_); + return true; +} + +bool MessageReader::PopVariantOfBasic(int dbus_type, void* value) { + dbus::MessageReader variant_reader(message_); + if (!PopVariant(&variant_reader)) + return false; + return variant_reader.PopBasic(dbus_type, value); +} + +} // namespace dbus diff --git a/dbus/message.h b/dbus/message.h new file mode 100644 index 0000000..501f99a --- /dev/null +++ b/dbus/message.h @@ -0,0 +1,333 @@ +// Copyright (c) 2011 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. + +#ifndef DBUS_MESSAGE_H_ +#define DBUS_MESSAGE_H_ +#pragma once + +#include <string> +#include <vector> +#include <dbus/dbus.h> + +#include "base/basictypes.h" + +namespace dbus { + +class MessageWriter; +class MessageReader; + +// Message is the base class of D-Bus message types. Client code should +// usually use sub classes such as MethodCall and Response instead. +// +// The class name Message is very generic, but there should be no problem +// as the class is inside 'dbus' namespace. We chose to name this way, as +// libdbus defines lots of types starting with DBus, such as +// DBusMessage. We should avoid confusion and conflict with these types. +class Message { + public: + // The message type used in D-Bus. Redefined here so client code + // doesn't need to use raw D-Bus macros. DBUS_MESSAGE_TYPE_INVALID + // etc. are #define macros. Having an enum type here makes code a bit + // more type-safe. + enum MessageType { + MESSAGE_INVALID = DBUS_MESSAGE_TYPE_INVALID, + MESSAGE_METHOD_CALL = DBUS_MESSAGE_TYPE_METHOD_CALL, + MESSAGE_METHOD_RETURN = DBUS_MESSAGE_TYPE_METHOD_RETURN, + MESSAGE_SIGNAL = DBUS_MESSAGE_TYPE_SIGNAL, + MESSAGE_ERROR = DBUS_MESSAGE_TYPE_ERROR, + }; + + // The data type used in the D-Bus type system. See the comment at + // MessageType for why we are redefining data types here. + enum DataType { + INVALID_DATA = DBUS_TYPE_INVALID, + BYTE = DBUS_TYPE_BYTE, + BOOL = DBUS_TYPE_BOOLEAN, + INT16 = DBUS_TYPE_INT16, + UINT16 = DBUS_TYPE_UINT16, + INT32 = DBUS_TYPE_INT32, + UINT32 = DBUS_TYPE_UINT32, + INT64 = DBUS_TYPE_INT64, + UINT64 = DBUS_TYPE_UINT64, + DOUBLE = DBUS_TYPE_DOUBLE, + STRING = DBUS_TYPE_STRING, + OBJECT_PATH = DBUS_TYPE_OBJECT_PATH, + ARRAY = DBUS_TYPE_ARRAY, + STRUCT = DBUS_TYPE_STRUCT, + DICT_ENTRY = DBUS_TYPE_DICT_ENTRY, + VARIANT = DBUS_TYPE_VARIANT, + }; + + // Creates a Message. The internal raw message is NULL until it's set + // from outside by reset_raw_message(). + Message(); + virtual ~Message(); + + // Returns the type of the message. Returns MESSAGE_INVALID if + // raw_message_ is NULL. + MessageType GetMessageType(); + + DBusMessage* raw_message() { return raw_message_; } + + // Resets raw_message_ with the given raw message. Takes the ownership + // of raw_message. raw_message_ will be unref'ed in the destructor. + void reset_raw_message(DBusMessage* raw_message); + + // Returns the string representation of this message. Useful for + // debugging. + std::string ToString(); + + private: + // Helper function used in ToString(). + std::string ToStringInternal(const std::string& indent, + MessageReader* reader); + + DBusMessage* raw_message_; + DISALLOW_COPY_AND_ASSIGN(Message); +}; + +// MessageCall is a type of message used for calling a method via D-Bus. +class MethodCall : public Message { + public: + // Creates a method call message for the specified interface name and + // the method name. + // + // For instance, to call "Get" method of DBUS_INTERFACE_INTROSPECTABLE + // interface ("org.freedesktop.DBus.Introspectable"), create a method + // call like this: + // + // MethodCall method_call(DBUS_INTERFACE_INTROSPECTABLE, "Get"); + // + // The constructor creates the internal raw_message_, so the client + // doesn't need to set this with reset_raw_message(). + MethodCall(const std::string& interface_name, + const std::string& method_name); + + const std::string& interface_name() { return interface_name_; } + const std::string& method_name() { return method_name_; } + + // Sets the service name. This will be handled by the object proxy. + void SetServiceName(const std::string& service_name); + // Sets the object path. This will be handled by the object proxy. + void SetObjectPath(const std::string& object_path); + + std::string interface_name_; + std::string method_name_; + + DISALLOW_COPY_AND_ASSIGN(MethodCall); +}; + +// Response is a type of message used for receiving a response from a +// method via D-Bus. +class Response : public Message { + public: + // Creates a Response message. The internal raw message is NULL. + // Classes that implment method calls need to set the raw message once a + // response is received from the server. See object_proxy.h. + Response(); + + private: + DISALLOW_COPY_AND_ASSIGN(Response); +}; + +// MessageWriter is used to write outgoing messages for calling methods +// and sending signals. +// +// The main design goal of MessageReader and MessageWriter classes is to +// provide a type safe API. In the past, there was a Chrome OS blocker +// bug, that took days to fix, that would have been prevented if the API +// was type-safe. +// +// For instance, instead of doing something like: +// +// // We shouldn't add '&' to str here, but it compiles with '&' added. +// dbus_g_proxy_call(..., G_TYPE_STRING, str, G_TYPE_INVALID, ...) +// +// We want to do something like: +// +// writer.AppendString(str); +// +class MessageWriter { + public: + // Data added with Append* will be written to |message|. + MessageWriter(Message* message); + ~MessageWriter(); + + // Appends a byte to the message. + void AppendByte(uint8 value); + void AppendBool(bool value); + void AppendInt16(int16 value); + void AppendUint16(uint16 value); + void AppendInt32(int32 value); + void AppendUint32(uint32 value); + void AppendInt64(int64 value); + void AppendUint64(uint64 value); + void AppendDouble(double value); + void AppendString(const std::string& value); + void AppendObjectPath(const std::string& value); + + // Opens an array. The array contents can be added to the array with + // |sub_writer|. The client code must close the array with + // CloseContainer(), once all contents are added. + // + // |signature| parameter is used to supply the D-Bus type signature of + // the array contents. For instance, if you want an array of strings, + // then you pass "s" as the signature. + // + // See the spec for details about the type signatures. + // http://dbus.freedesktop.org/doc/dbus-specification.html + // #message-protocol-signatures + // + void OpenArray(const std::string& signature, MessageWriter* sub_writer); + // Do the same for a variant. + void OpenVariant(const std::string& signature, MessageWriter* sub_writer); + // Do the same for Struct and dict entry. They don't need the signature. + void OpenStruct(MessageWriter* sub_writer); + void OpenDictEntry(MessageWriter* sub_writer); + + // Close the container for a array/variant/struct/dict entry. + void CloseContainer(MessageWriter* sub_writer); + + // Appends the array of bytes. Arrays of bytes are often used for + // exchanging binary blobs hence it's worth having a specialized + // function. + void AppendArrayOfBytes(const uint8* values, size_t length); + + // Appends the byte wrapped in a variant data container. Variants are + // widely used in D-Bus services so it's worth having a specialized + // function. For instance, The third parameter of + // "org.freedesktop.DBus.Properties.Set" is a variant. + void AppendVariantOfByte(uint8 value); + void AppendVariantOfBool(bool value); + void AppendVariantOfInt16(int16 value); + void AppendVariantOfUint16(uint16 value); + void AppendVariantOfInt32(int32 value); + void AppendVariantOfUint32(uint32 value); + void AppendVariantOfInt64(int64 value); + void AppendVariantOfUint64(uint64 value); + void AppendVariantOfDouble(double value); + void AppendVariantOfString(const std::string& value); + void AppendVariantOfObjectPath(const std::string& value); + + private: + // Helper function used to implement AppendByte etc. + void AppendBasic(int dbus_type, const void* value); + + // Helper function used to implement AppendVariantOfByte() etc. + void AppendVariantOfBasic(int dbus_type, const void* value); + + Message* message_; + DBusMessageIter raw_message_iter_; + bool container_is_open_; + + DISALLOW_COPY_AND_ASSIGN(MessageWriter); +}; + +// MessageReader is used to read incoming messages such as responses for +// method calls. +// +// MessageReader manages an internal iterator to read data. All functions +// starting with Pop advance the iterator on success. +class MessageReader { + public: + // The data will be read from the given message. + MessageReader(Message* message); + ~MessageReader(); + + // Returns true if the reader has more data to read. The function is + // used to iterate contents in a container like: + // + // while (reader.HasMoreData()) + // reader.PopString(&value); + bool HasMoreData(); + + // Gets the byte at the current iterator position. + // Returns true and advances the iterator on success. + // Returns false if the data type is not a byte. + bool PopByte(uint8* value); + bool PopBool(bool* value); + bool PopInt16(int16* value); + bool PopUint16(uint16* value); + bool PopInt32(int32* value); + bool PopUint32(uint32* value); + bool PopInt64(int64* value); + bool PopUint64(uint64* value); + bool PopDouble(double* value); + bool PopString(std::string* value); + bool PopObjectPath(std::string* value); + + // Sets up the given message reader to read an array at the current + // iterator position. + // Returns true and advances the iterator on success. + // Returns false if the data type is not an array + bool PopArray(MessageReader* sub_reader); + bool PopStruct(MessageReader* sub_reader); + bool PopDictEntry(MessageReader* sub_reader); + bool PopVariant(MessageReader* sub_reader); + + // Gets the array of bytes at the current iterator position. + // Returns true and advances the iterator on success. + // + // Arrays of bytes are often used for exchanging binary blobs hence it's + // worth having a specialized function. + // + // |bytes| must be copied if the contents will be referenced after the + // |MessageReader is destroyed. + bool PopArrayOfBytes(uint8** bytes, size_t* length); + + // Gets the array of object paths at the current iterator position. + // Returns true and advances the iterator on success. + // + // Arrays of object paths are often used to communicate with D-Bus + // services like NetworkManager, hence it's worth having a specialized + // function. + bool PopArrayOfObjectPaths(std::vector<std::string>* object_paths); + + // Gets the byte from the variant data container at the current iterator + // position. + // Returns true and advances the iterator on success. + // + // Variants are widely used in D-Bus services so it's worth having a + // specialized function. For instance, The return value type of + // "org.freedesktop.DBus.Properties.Get" is a variant. + bool PopVariantOfByte(uint8* value); + bool PopVariantOfBool(bool* value); + bool PopVariantOfInt16(int16* value); + bool PopVariantOfUint16(uint16* value); + bool PopVariantOfInt32(int32* value); + bool PopVariantOfUint32(uint32* value); + bool PopVariantOfInt64(int64* value); + bool PopVariantOfUint64(uint64* value); + bool PopVariantOfDouble(double* value); + bool PopVariantOfString(std::string* value); + bool PopVariantOfObjectPath(std::string* value); + + // Get the data type of the value at the current iterator + // position. INVALID_DATA will be returned if the iterator points to the + // end of the message. + Message::DataType GetDataType(); + + private: + // Returns true if the data type at the current iterator position + // matches the given D-Bus type, such as DBUS_TYPE_BYTE. + bool CheckDataType(int dbus_type); + + // Helper function used to implement PopByte() etc. + bool PopBasic(int dbus_type, void *value); + + // Helper function used to implement PopArray() etc. + bool PopContainer(int dbus_type, MessageReader* sub_reader); + + // Helper function used to implement PopVariantOfByte() etc. + bool PopVariantOfBasic(int dbus_type, void* value); + + Message* message_; + DBusMessageIter raw_message_iter_; + + DISALLOW_COPY_AND_ASSIGN(MessageReader); +}; + +} // namespace dbus + +#endif // DBUS_MESSAGE_H_ diff --git a/dbus/message_unittest.cc b/dbus/message_unittest.cc new file mode 100644 index 0000000..e7aaba1 --- /dev/null +++ b/dbus/message_unittest.cc @@ -0,0 +1,372 @@ +// Copyright (c) 2011 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 "base/logging.h" +#include "dbus/message.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Test that a byte can be properly written and read. We only have this +// test for byte, as repeating this for other basic types is too redundant. +TEST(MessageTest, AppendAndPopByte) { + dbus::Message message; + message.reset_raw_message(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL)); + dbus::MessageWriter writer(&message); + writer.AppendByte(123); // The input is 123. + + dbus::MessageReader reader(&message); + ASSERT_TRUE(reader.HasMoreData()); // Should have data to read. + ASSERT_EQ(dbus::Message::BYTE, reader.GetDataType()); + + bool bool_value = false; + // Should fail as the type is not bool here. + ASSERT_FALSE(reader.PopBool(&bool_value)); + + uint8 byte_value = 0; + ASSERT_TRUE(reader.PopByte(&byte_value)); + EXPECT_EQ(123, byte_value); // Should match with the input. + ASSERT_FALSE(reader.HasMoreData()); // Should not have more data to read. + + // Try to get another byte. Should fail. + ASSERT_FALSE(reader.PopByte(&byte_value)); +} + +// Check all basic types can be properly written and read. +TEST(MessageTest, AppendAndPopBasicDataTypes) { + dbus::Message message; + message.reset_raw_message(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL)); + dbus::MessageWriter writer(&message); + + // Append 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path". + writer.AppendByte(0); + writer.AppendBool(true); + writer.AppendInt16(2); + writer.AppendUint16(3); + writer.AppendInt32(4); + writer.AppendUint32(5); + writer.AppendInt64(6); + writer.AppendUint64(7); + writer.AppendDouble(8.0); + writer.AppendString("string"); + writer.AppendObjectPath("/object/path"); + + uint8 byte_value = 0; + bool bool_value = false; + int16 int16_value = 0; + uint16 uint16_value = 0; + int32 int32_value = 0; + uint32 uint32_value = 0; + int64 int64_value = 0; + uint64 uint64_value = 0; + double double_value = 0; + std::string string_value; + std::string object_path_value; + + dbus::MessageReader reader(&message); + ASSERT_TRUE(reader.HasMoreData()); + ASSERT_TRUE(reader.PopByte(&byte_value)); + ASSERT_TRUE(reader.PopBool(&bool_value)); + ASSERT_TRUE(reader.PopInt16(&int16_value)); + ASSERT_TRUE(reader.PopUint16(&uint16_value)); + ASSERT_TRUE(reader.PopInt32(&int32_value)); + ASSERT_TRUE(reader.PopUint32(&uint32_value)); + ASSERT_TRUE(reader.PopInt64(&int64_value)); + ASSERT_TRUE(reader.PopUint64(&uint64_value)); + ASSERT_TRUE(reader.PopDouble(&double_value)); + ASSERT_TRUE(reader.PopString(&string_value)); + ASSERT_TRUE(reader.PopObjectPath(&object_path_value)); + ASSERT_FALSE(reader.HasMoreData()); + + // 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path" should be returned. + EXPECT_EQ(0, byte_value); + EXPECT_EQ(true, bool_value); + EXPECT_EQ(2, int16_value); + EXPECT_EQ(3U, uint16_value); + EXPECT_EQ(4, int32_value); + EXPECT_EQ(5U, uint32_value); + EXPECT_EQ(6, int64_value); + EXPECT_EQ(7U, uint64_value); + EXPECT_DOUBLE_EQ(8.0, double_value); + EXPECT_EQ("string", string_value); + EXPECT_EQ("/object/path", object_path_value); +} + +// Check all variant types can be properly written and read. +TEST(MessageTest, AppendAndPopVariantDataTypes) { + dbus::Message message; + message.reset_raw_message(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL)); + dbus::MessageWriter writer(&message); + + // Append 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path". + writer.AppendVariantOfByte(0); + writer.AppendVariantOfBool(true); + writer.AppendVariantOfInt16(2); + writer.AppendVariantOfUint16(3); + writer.AppendVariantOfInt32(4); + writer.AppendVariantOfUint32(5); + writer.AppendVariantOfInt64(6); + writer.AppendVariantOfUint64(7); + writer.AppendVariantOfDouble(8.0); + writer.AppendVariantOfString("string"); + writer.AppendVariantOfObjectPath("/object/path"); + + uint8 byte_value = 0; + bool bool_value = false; + int16 int16_value = 0; + uint16 uint16_value = 0; + int32 int32_value = 0; + uint32 uint32_value = 0; + int64 int64_value = 0; + uint64 uint64_value = 0; + double double_value = 0; + std::string string_value; + std::string object_path_value; + + dbus::MessageReader reader(&message); + ASSERT_TRUE(reader.HasMoreData()); + ASSERT_TRUE(reader.PopVariantOfByte(&byte_value)); + ASSERT_TRUE(reader.PopVariantOfBool(&bool_value)); + ASSERT_TRUE(reader.PopVariantOfInt16(&int16_value)); + ASSERT_TRUE(reader.PopVariantOfUint16(&uint16_value)); + ASSERT_TRUE(reader.PopVariantOfInt32(&int32_value)); + ASSERT_TRUE(reader.PopVariantOfUint32(&uint32_value)); + ASSERT_TRUE(reader.PopVariantOfInt64(&int64_value)); + ASSERT_TRUE(reader.PopVariantOfUint64(&uint64_value)); + ASSERT_TRUE(reader.PopVariantOfDouble(&double_value)); + ASSERT_TRUE(reader.PopVariantOfString(&string_value)); + ASSERT_TRUE(reader.PopVariantOfObjectPath(&object_path_value)); + ASSERT_FALSE(reader.HasMoreData()); + + // 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path" should be returned. + EXPECT_EQ(0, byte_value); + EXPECT_EQ(true, bool_value); + EXPECT_EQ(2, int16_value); + EXPECT_EQ(3U, uint16_value); + EXPECT_EQ(4, int32_value); + EXPECT_EQ(5U, uint32_value); + EXPECT_EQ(6, int64_value); + EXPECT_EQ(7U, uint64_value); + EXPECT_DOUBLE_EQ(8.0, double_value); + EXPECT_EQ("string", string_value); + EXPECT_EQ("/object/path", object_path_value); +} + +TEST(MessageTest, ArrayOfBytes) { + dbus::Message message; + message.reset_raw_message(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL)); + dbus::MessageWriter writer(&message); + std::vector<uint8> bytes; + bytes.push_back(1); + bytes.push_back(2); + bytes.push_back(3); + writer.AppendArrayOfBytes(bytes.data(), bytes.size()); + + dbus::MessageReader reader(&message); + uint8* output_bytes = NULL; + size_t length = 0; + ASSERT_TRUE(reader.PopArrayOfBytes(&output_bytes, &length)); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_EQ(3U, length); + ASSERT_EQ(1, output_bytes[0]); + ASSERT_EQ(2, output_bytes[1]); + ASSERT_EQ(3, output_bytes[2]); +} + +// Test that an array can be properly written and read. We only have this +// test for array, as repeating this for other container types is too +// redundant. +TEST(MessageTest, OpenArrayAndPopArray) { + dbus::Message message; + message.reset_raw_message(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL)); + dbus::MessageWriter writer(&message); + dbus::MessageWriter array_writer(&message); + writer.OpenArray("s", &array_writer); // Open an array of strings. + array_writer.AppendString("foo"); + array_writer.AppendString("bar"); + array_writer.AppendString("baz"); + writer.CloseContainer(&array_writer); + + dbus::MessageReader reader(&message); + ASSERT_EQ(dbus::Message::ARRAY, reader.GetDataType()); + dbus::MessageReader array_reader(&message); + ASSERT_TRUE(reader.PopArray(&array_reader)); + ASSERT_FALSE(reader.HasMoreData()); // Should not have more data to read. + + std::string string_value; + ASSERT_TRUE(array_reader.PopString(&string_value)); + EXPECT_EQ("foo", string_value); + ASSERT_TRUE(array_reader.PopString(&string_value)); + EXPECT_EQ("bar", string_value); + ASSERT_TRUE(array_reader.PopString(&string_value)); + EXPECT_EQ("baz", string_value); + // Should not have more data to read. + ASSERT_FALSE(array_reader.HasMoreData()); +} + +// Create a complex message using array, struct, variant, dict entry, and +// make sure it can be read properly. +TEST(MessageTest, CreateComplexMessageAndReadIt) { + dbus::Message message; + message.reset_raw_message(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL)); + dbus::MessageWriter writer(&message); + { + dbus::MessageWriter array_writer(&message); + // Open an array of variants. + writer.OpenArray("v", &array_writer); + { + // The first value in the array. + { + dbus::MessageWriter variant_writer(&message); + // Open a variant of a boolean. + array_writer.OpenVariant("b", &variant_writer); + variant_writer.AppendBool(true); + array_writer.CloseContainer(&variant_writer); + } + + // The second value in the array. + { + dbus::MessageWriter variant_writer(&message); + // Open a variant of a struct that contains a string and an int32. + array_writer.OpenVariant("(si)", &variant_writer); + { + dbus::MessageWriter struct_writer(&message); + variant_writer.OpenStruct(&struct_writer); + struct_writer.AppendString("string"); + struct_writer.AppendInt32(123); + variant_writer.CloseContainer(&struct_writer); + } + array_writer.CloseContainer(&variant_writer); + } + + // The third value in the array. + { + dbus::MessageWriter variant_writer(&message); + // Open a variant of an array of string-to-int64 dict entries. + array_writer.OpenVariant("a{sx}", &variant_writer); + { + // Opens an array of string-to-int64 dict entries. + dbus::MessageWriter dict_array_writer(&message); + variant_writer.OpenArray("{sx}", &dict_array_writer); + { + // Opens a string-to-int64 dict entries. + dbus::MessageWriter dict_entry_writer(&message); + dict_array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendString("foo"); + dict_entry_writer.AppendInt64(1234567890123456789); + dict_array_writer.CloseContainer(&dict_entry_writer); + } + variant_writer.CloseContainer(&dict_array_writer); + } + array_writer.CloseContainer(&variant_writer); + } + } + writer.CloseContainer(&array_writer); + } + // What we have created looks like this: + EXPECT_EQ("signature: av\n" + "\n" + "array [\n" + " variant bool true\n" + " variant struct {\n" + " string \"string\"\n" + " int32 123\n" + " }\n" + " variant array [\n" + " dict entry {\n" + " string \"foo\"\n" + " int64 1234567890123456789\n" + " }\n" + " ]\n" + "]\n", + message.ToString()); + + dbus::MessageReader reader(&message); + dbus::MessageReader array_reader(&message); + ASSERT_TRUE(reader.PopArray(&array_reader)); + + // The first value in the array. + bool bool_value = false; + ASSERT_TRUE(array_reader.PopVariantOfBool(&bool_value)); + EXPECT_EQ(true, bool_value); + + // The second value in the array. + { + dbus::MessageReader variant_reader(&message); + ASSERT_TRUE(array_reader.PopVariant(&variant_reader)); + { + dbus::MessageReader struct_reader(&message); + ASSERT_TRUE(variant_reader.PopStruct(&struct_reader)); + std::string string_value; + ASSERT_TRUE(struct_reader.PopString(&string_value)); + EXPECT_EQ("string", string_value); + int32 int32_value = 0; + ASSERT_TRUE(struct_reader.PopInt32(&int32_value)); + EXPECT_EQ(123, int32_value); + ASSERT_FALSE(struct_reader.HasMoreData()); + } + ASSERT_FALSE(variant_reader.HasMoreData()); + } + + // The third value in the array. + { + dbus::MessageReader variant_reader(&message); + ASSERT_TRUE(array_reader.PopVariant(&variant_reader)); + { + dbus::MessageReader dict_array_reader(&message); + ASSERT_TRUE(variant_reader.PopArray(&dict_array_reader)); + { + dbus::MessageReader dict_entry_reader(&message); + ASSERT_TRUE(dict_array_reader.PopDictEntry(&dict_entry_reader)); + std::string string_value; + ASSERT_TRUE(dict_entry_reader.PopString(&string_value)); + EXPECT_EQ("foo", string_value); + int64 int64_value = 0; + ASSERT_TRUE(dict_entry_reader.PopInt64(&int64_value)); + EXPECT_EQ(1234567890123456789, int64_value); + } + ASSERT_FALSE(dict_array_reader.HasMoreData()); + } + ASSERT_FALSE(variant_reader.HasMoreData()); + } + ASSERT_FALSE(array_reader.HasMoreData()); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST(MessageTest, Message) { + dbus::Message message; + EXPECT_TRUE(message.raw_message() == NULL); + EXPECT_EQ(dbus::Message::MESSAGE_INVALID, message.GetMessageType()); +} + +TEST(MessageTest, MethodCall) { + dbus::MethodCall method_call("com.example.Interface", "SomeMethod"); + EXPECT_TRUE(method_call.raw_message() != NULL); + EXPECT_EQ(dbus::Message::MESSAGE_METHOD_CALL, method_call.GetMessageType()); + method_call.SetServiceName("com.example.Service"); + method_call.SetObjectPath("/com/example/Object"); + + dbus::MessageWriter writer(&method_call); + writer.AppendString("payload"); + + EXPECT_EQ("destination: com.example.Service\n" + "path: /com/example/Object\n" + "interface: com.example.Interface\n" + "member: SomeMethod\n" + "signature: s\n" + "\n" + "string \"payload\"\n", + method_call.ToString()); +} + +TEST(MessageTest, Response) { + dbus::Response response; + EXPECT_TRUE(response.raw_message() == NULL); + response.reset_raw_message( + dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN)); + EXPECT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response.GetMessageType()); +} + +TEST(MessageTest, ToString_EmptyMessage) { + dbus::Message message; + EXPECT_EQ("", message.ToString()); +} |