blob: 0191968d6a9b0e8888861af9c247b8c18737d7fd [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <unistd.h>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "components/dbus/utils/read_value.h"
#include "components/dbus/utils/signature.h"
#include "components/dbus/utils/types.h"
#include "components/dbus/utils/write_value.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace dbus_utils {
namespace {
using TestTuple1 = std::tuple<int32_t, std::string, std::vector<uint8_t>>;
using TestTuple2 = std::tuple<int32_t, std::string, std::vector<double>>;
using TestTupleInner = std::tuple<int32_t, std::string>;
using TestTupleOuter = std::tuple<std::string, TestTupleInner, uint64_t>;
using TestTupleWithVector = std::tuple<std::string, std::vector<int32_t>>;
using TestTupleWithMap = std::tuple<int32_t, std::map<std::string, bool>>;
using TestMapWithTupleKey = std::map<std::tuple<int32_t, std::string>, bool>;
// Helper for round-trip serialization/deserialization testing.
// Writes the given value to a D-Bus message, then reads it back
// and checks for equality.
template <typename T>
void TestRoundTrip(const T& original_value) {
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
internal::WriteValue(writer, original_value);
dbus::MessageReader reader(response.get());
auto value_read_back = internal::ReadValue<T>(reader);
ASSERT_TRUE(value_read_back) << "Failed to read back type with signature: "
<< internal::GetDBusTypeSignature<T>();
EXPECT_FALSE(reader.HasMoreData())
<< "Reader has more data after reading type with signature: "
<< internal::GetDBusTypeSignature<T>();
EXPECT_EQ(original_value, value_read_back);
}
// Helper to get a signature as a std::string.
template <typename T>
std::string GetSignature() {
return internal::GetDBusTypeSignature<T>();
}
TEST(DBusTypesTest, RoundTripUint8) {
TestRoundTrip<uint8_t>(123);
}
TEST(DBusTypesTest, RoundTripBool) {
TestRoundTrip<bool>(true);
TestRoundTrip<bool>(false);
}
TEST(DBusTypesTest, RoundTripInt16) {
TestRoundTrip<int16_t>(-12345);
}
TEST(DBusTypesTest, RoundTripUint16) {
TestRoundTrip<uint16_t>(54321);
}
TEST(DBusTypesTest, RoundTripInt32) {
TestRoundTrip<int32_t>(-123456789);
}
TEST(DBusTypesTest, RoundTripUint32) {
TestRoundTrip<uint32_t>(3234567890u);
}
TEST(DBusTypesTest, RoundTripInt64) {
TestRoundTrip<int64_t>(-123456789012345LL);
}
TEST(DBusTypesTest, RoundTripUint64) {
TestRoundTrip<uint64_t>(9876543210987654321ULL);
}
TEST(DBusTypesTest, RoundTripDouble) {
TestRoundTrip<double>(3.1415926535);
}
TEST(DBusTypesTest, RoundTripString) {
TestRoundTrip<std::string>("Hello, D-Bus types!");
TestRoundTrip<std::string>("");
}
TEST(DBusTypesTest, RoundTripObjectPath) {
TestRoundTrip<dbus::ObjectPath>(dbus::ObjectPath("/org/chromium/TestObject"));
TestRoundTrip<dbus::ObjectPath>(dbus::ObjectPath("/"));
}
TEST(DBusTypesTest, RoundTripVectorOfInts) {
TestRoundTrip<std::vector<int32_t>>({10, 20, -30, 0, 42});
}
TEST(DBusTypesTest, RoundTripVectorOfStrings) {
TestRoundTrip<std::vector<std::string>>({"alpha", "beta", "", "gamma"});
}
TEST(DBusTypesTest, RoundTripVectorOfBools) {
// std::vector<bool>'s implementation is odd, so ensure it specifically.
TestRoundTrip<std::vector<bool>>({true, false, true, true, false});
}
TEST(DBusTypesTest, RoundTripEmptyVector) {
TestRoundTrip<std::vector<std::string>>({});
TestRoundTrip<std::vector<int32_t>>({});
}
TEST(DBusTypesTest, RoundTripVectorOfEmptyString) {
TestRoundTrip<std::vector<std::string>>({"", "", ""});
}
TEST(DBusTypesTest, RoundTripVectorVectorInts) {
TestRoundTrip<std::vector<std::vector<int32_t>>>(
{{1, 2}, {}, {3, 4, 5}, {-1}});
}
TEST(DBusTypesTest, RoundTripVectorOfVectorsOfStrings) {
TestRoundTrip<std::vector<std::vector<std::string>>>(
{{"a", "b"}, {}, {"c"}, {"d", "e", "f"}});
}
TEST(DBusTypesTest, RoundTripMapStringInt) {
TestRoundTrip<std::map<std::string, int32_t>>(
{{"one", 1}, {"two", 2}, {"three", 3}});
}
TEST(DBusTypesTest, RoundTripEmptyMap) {
TestRoundTrip<std::map<std::string, int32_t>>({});
TestRoundTrip<std::map<int32_t, double>>({});
}
TEST(DBusTypesTest, RoundTripMapStringVectorInt) {
TestRoundTrip<std::map<std::string, std::vector<int32_t>>>(
{{"a", {1, 2}}, {"b", {}}, {"c", {3, 4, 5}}});
}
TEST(DBusTypesTest, RoundTripMapStringMapStringInt) {
using InnerMapType = std::map<std::string, int32_t>;
using OuterMapType = std::map<std::string, InnerMapType>;
TestRoundTrip<OuterMapType>(
{{"user1", {{"score", 100}, {"level", 5}}},
{"user2", {{"score", 150}, {"level", 7}, {"rank", 1}}},
{"user3", {}},
{"user4", {{"items", 42}}}});
TestRoundTrip<OuterMapType>({});
TestRoundTrip<OuterMapType>({{"empty_inner_test", {}}});
}
TEST(DBusTypesTest, RoundTripTestStruct1) {
TestRoundTrip<TestTuple1>({123, "TestStruct", {0xDE, 0xAD, 0xBE, 0xEF}});
TestRoundTrip<TestTuple1>({-1, "", {}});
}
TEST(DBusTypesTest, RoundTripTestStruct2) {
TestRoundTrip<TestTuple2>({101, "complex data", {1.5, 2.5, -3.5}});
TestRoundTrip<TestTuple2>({0, "", {}});
}
TEST(DBusTypesTest, RoundTripVectorOfStructs) {
TestRoundTrip<std::vector<TestTuple1>>(
{{1, "one", {1}}, {2, "two", {2, 2}}, {3, "three", {}}});
}
TEST(DBusTypesTest, RoundTripMapStringToStruct) {
TestRoundTrip<std::map<std::string, TestTuple1>>(
{{"first", {1, "one", {1}}}, {"second", {2, "two", {}}}});
}
TEST(DBusTypesTest, RoundTripStructOfStruct) {
TestRoundTrip<TestTupleOuter>(
{"OuterLayer1", {101, "InnerPayload1"}, 1234567890ULL});
TestRoundTrip<TestTupleOuter>({"OuterEmptyInner", {0, ""}, 0ULL});
}
TEST(DBusTypesTest, RoundTripVectorOfStructOfStruct) {
TestRoundTrip<std::vector<TestTupleOuter>>(
{{"Outer1", {1, "InnerA"}, 1000ULL},
{"Outer2", {2, "InnerB"}, 2000ULL},
{"Outer3", {3, ""}, 3000ULL}});
TestRoundTrip<std::vector<TestTupleOuter>>({});
}
TEST(DBusTypesTest, RoundTripTupleWithVector) {
TestRoundTrip<TestTupleWithVector>({"hello", {1, 2, 3}});
TestRoundTrip<TestTupleWithVector>({"world", {}});
}
TEST(DBusTypesTest, RoundTripTupleWithMap) {
TestRoundTrip<TestTupleWithMap>({42, {{"a", true}, {"b", false}}});
TestRoundTrip<TestTupleWithMap>({-1, {}});
}
TEST(DBusTypesTest, RoundTripMapWithTupleKey) {
TestRoundTrip<TestMapWithTupleKey>({{{1, "a"}, true}, {{2, "b"}, false}});
TestRoundTrip<TestMapWithTupleKey>({});
}
TEST(DBusTypesTest, ReadStructNotEnoughDataInDBusMessage) {
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
dbus::MessageWriter struct_writer(nullptr);
writer.OpenStruct(&struct_writer);
struct_writer.AppendInt32(123);
writer.CloseContainer(&struct_writer);
dbus::MessageReader reader(response.get());
EXPECT_FALSE(internal::ReadValue<TestTuple1>(reader));
}
TEST(DBusTypesTest, ReadStructTooMuchDataInDBusMessage) {
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
dbus::MessageWriter struct_writer(nullptr);
writer.OpenStruct(&struct_writer);
struct_writer.AppendInt32(123);
struct_writer.AppendString("Test");
dbus::MessageWriter array_writer(nullptr);
struct_writer.OpenArray("y", &array_writer);
array_writer.AppendByte(0x01);
array_writer.AppendByte(0x02);
struct_writer.CloseContainer(&array_writer);
// Add extra data not in TestStruct1
struct_writer.AppendDouble(3.14);
writer.CloseContainer(&struct_writer);
dbus::MessageReader reader(response.get());
EXPECT_FALSE(internal::ReadValue<TestTuple1>(reader));
}
TEST(DBusTypesTest, WriteReadMultipleTypesSequentially) {
std::unique_ptr<dbus::MethodCall> call =
std::make_unique<dbus::MethodCall>("dummy.interface", "DummyMethod");
dbus::MessageWriter writer(call.get());
const std::string kArgString = "ArgumentValue";
const uint32_t kArgUint = 99;
const bool kArgBool = true;
const TestTuple1 kArgStruct = {1, "s", {1, 2}};
internal::WriteValue(writer, kArgString);
internal::WriteValue(writer, kArgUint);
internal::WriteValue(writer, kArgBool);
internal::WriteValue(writer, kArgStruct);
dbus::MessageReader reader(call.get());
EXPECT_EQ(internal::ReadValue<std::string>(reader), kArgString);
EXPECT_EQ(internal::ReadValue<uint32_t>(reader), kArgUint);
EXPECT_EQ(internal::ReadValue<bool>(reader), kArgBool);
EXPECT_EQ(internal::ReadValue<TestTuple1>(reader), kArgStruct);
}
TEST(DBusTypesTest, ReadEmptyMessageFails) {
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
dbus::MessageReader reader(response.get());
EXPECT_FALSE(reader.HasMoreData());
EXPECT_FALSE(internal::ReadValue<int32_t>(reader));
EXPECT_FALSE(internal::ReadValue<std::string>(reader));
EXPECT_FALSE(internal::ReadValue<TestTuple1>(reader));
}
TEST(DBusTypesTest, RoundTripScopedFdReadablePipe) {
int fds[2];
ASSERT_EQ(pipe(fds), 0);
base::ScopedFD read_fd(fds[0]);
base::ScopedFD write_fd(fds[1]);
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
internal::WriteValue(writer, read_fd);
dbus::MessageReader reader(response.get());
auto read_fd_back = internal::ReadValue<base::ScopedFD>(reader);
ASSERT_TRUE(read_fd_back);
EXPECT_TRUE(read_fd_back->is_valid());
const char kPayload = 'X';
ASSERT_EQ(write(write_fd.get(), &kPayload, 1), 1);
char buf = '\0';
ASSERT_EQ(read(read_fd_back->get(), &buf, 1), 1);
EXPECT_EQ(buf, kPayload);
}
TEST(DBusTypesTest, RoundTripVectorOfScopedFDs) {
constexpr int kFdCount = 3;
std::vector<base::ScopedFD> original_fds;
for (int i = 0; i < kFdCount; ++i) {
int fd = open("/dev/null", O_RDONLY);
ASSERT_GT(fd, -1);
original_fds.emplace_back(fd);
}
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
internal::WriteValue(writer, original_fds);
dbus::MessageReader reader(response.get());
auto fds_read_back = internal::ReadValue<std::vector<base::ScopedFD>>(reader);
ASSERT_TRUE(fds_read_back);
EXPECT_EQ(fds_read_back->size(), original_fds.size());
for (auto& fd : *fds_read_back) {
EXPECT_TRUE(fd.is_valid());
EXPECT_NE(lseek(fd.get(), 0, SEEK_CUR), -1);
}
}
} // namespace
} // namespace dbus_utils