blob: 3a5643ad0d505e1a2af1b992e4a9f096651a73de [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstring>
#include <memory>
#include <string_view>
#include "ipcz/ipcz.h"
#include "test/multinode_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
#include "third_party/abseil-cpp/absl/types/span.h"
#include "util/ref_counted.h"
namespace ipcz {
namespace {
using BoxTestNode = test::TestNode;
using BoxTest = test::MultinodeTest<BoxTestNode>;
MULTINODE_TEST(BoxTest, BoxAndUnbox) {
constexpr const char kMessage[] = "Hello, world?";
EXPECT_EQ(kMessage, UnboxBlob(BoxBlob(kMessage)));
}
MULTINODE_TEST(BoxTest, CloseBox) {
// Verifies that box closure releases its underlying driver object. This test
// does not explicitly observe side effects of that release, but LSan will
// fail if something's off.
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Close(BoxBlob("meh"), IPCZ_NO_FLAGS, nullptr));
}
MULTINODE_TEST(BoxTest, Peek) {
constexpr std::string_view kMessage = "Hello, world?";
IpczHandle box = BoxBlob(kMessage);
IpczBoxContents box_contents = {.size = sizeof(box_contents)};
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &box_contents));
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &box_contents));
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &box_contents));
EXPECT_EQ(IPCZ_BOX_TYPE_DRIVER_OBJECT, box_contents.type);
EXPECT_NE(IPCZ_INVALID_DRIVER_HANDLE, box_contents.object.driver_object);
const IpczDriverHandle memory = box_contents.object.driver_object;
IpczDriverHandle mapping;
void* base;
EXPECT_EQ(IPCZ_RESULT_OK,
GetDriver().MapSharedMemory(memory, IPCZ_NO_FLAGS, nullptr, &base,
&mapping));
std::string contents(static_cast<const char*>(base), kMessage.size());
EXPECT_EQ(kMessage, contents);
EXPECT_EQ(IPCZ_RESULT_OK, GetDriver().Close(mapping, IPCZ_NO_FLAGS, nullptr));
EXPECT_EQ(kMessage, UnboxBlob(box));
}
constexpr const char kMessage1[] = "Hello, world?";
constexpr const char kMessage2[] = "Hello, world!";
constexpr const char kMessage3[] = "Hello. World.";
MULTINODE_TEST_NODE(BoxTestNode, TransferBoxClient) {
IpczHandle b = ConnectToBroker();
std::string message;
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, {&box, 1}));
EXPECT_EQ(kMessage2, message);
EXPECT_EQ(kMessage1, UnboxBlob(box));
Close(b);
}
MULTINODE_TEST(BoxTest, TransferBox) {
IpczHandle c = SpawnTestNode<TransferBoxClient>();
IpczHandle box = BoxBlob(kMessage1);
EXPECT_EQ(IPCZ_RESULT_OK, Put(c, kMessage2, {&box, 1}));
Close(c);
}
MULTINODE_TEST_NODE(BoxTestNode, TransferBoxAndPortalClient) {
IpczHandle b = ConnectToBroker();
IpczHandle handles[2];
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, handles));
EXPECT_EQ(kMessage2, message);
EXPECT_EQ(IPCZ_RESULT_OK, Put(handles[1], kMessage3));
EXPECT_EQ(kMessage1, UnboxBlob(handles[0]));
CloseAll({b, handles[1]});
}
MULTINODE_TEST(BoxTest, TransferBoxAndPortal) {
IpczHandle c = SpawnTestNode<TransferBoxAndPortalClient>();
auto [q, p] = OpenPortals();
IpczHandle box = BoxBlob(kMessage1);
IpczHandle handles[] = {box, p};
EXPECT_EQ(IPCZ_RESULT_OK, Put(c, kMessage2, absl::MakeSpan(handles)));
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(q, &message));
EXPECT_EQ(kMessage3, message);
CloseAll({c, q});
}
constexpr size_t TransferBoxBetweenNonBrokersNumIterations = 50;
MULTINODE_TEST_NODE(BoxTestNode, TransferBoxBetweenNonBrokersClient1) {
IpczHandle q;
IpczHandle b = ConnectToBroker();
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&q, 1}));
for (size_t i = 0; i < TransferBoxBetweenNonBrokersNumIterations; ++i) {
IpczHandle box = BoxBlob(kMessage1);
EXPECT_EQ(IPCZ_RESULT_OK, Put(q, kMessage2, {&box, 1}));
box = IPCZ_INVALID_DRIVER_HANDLE;
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(q, &message, {&box, 1}));
EXPECT_EQ(kMessage1, message);
EXPECT_EQ(kMessage2, UnboxBlob(box));
}
WaitForDirectRemoteLink(q);
CloseAll({q, b});
}
MULTINODE_TEST_NODE(BoxTestNode, TransferBoxBetweenNonBrokersClient2) {
IpczHandle p;
IpczHandle b = ConnectToBroker();
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&p, 1}));
for (size_t i = 0; i < TransferBoxBetweenNonBrokersNumIterations; ++i) {
IpczHandle box;
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(p, &message, {&box, 1}));
EXPECT_EQ(kMessage2, message);
EXPECT_EQ(kMessage1, UnboxBlob(box));
box = BoxBlob(kMessage2);
EXPECT_EQ(IPCZ_RESULT_OK, Put(p, kMessage1, {&box, 1}));
}
WaitForDirectRemoteLink(p);
CloseAll({p, b});
}
MULTINODE_TEST(BoxTest, TransferBoxBetweenNonBrokers) {
IpczHandle c1 = SpawnTestNode<TransferBoxBetweenNonBrokersClient1>();
IpczHandle c2 = SpawnTestNode<TransferBoxBetweenNonBrokersClient2>();
// Create a new portal pair and send each end to one of the two non-brokers so
// they'll establish a direct link.
auto [q, p] = OpenPortals();
EXPECT_EQ(IPCZ_RESULT_OK, Put(c1, "", {&q, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(c2, "", {&p, 1}));
// Wait for the clients to finish their business and go away.
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(c1, IPCZ_TRAP_PEER_CLOSED));
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(c2, IPCZ_TRAP_PEER_CLOSED));
CloseAll({c1, c2});
}
// A simple object which we can boxed and transmitted to other nodes, by
// providing ipcz with a custom serialization function.
class NamedPortal {
public:
struct Header {
uint32_t name_length;
};
NamedPortal(const IpczAPI& ipcz, std::string_view name, IpczHandle portal)
: ipcz_(ipcz), name_(name.begin(), name.end()), portal_(portal) {}
~NamedPortal() { reset(); }
static uintptr_t Release(std::unique_ptr<NamedPortal> named_portal) {
return reinterpret_cast<uintptr_t>(named_portal.release());
}
const std::string& name() const { return name_; }
IpczHandle portal() const { return portal_; }
void reset() {
if (portal_ != IPCZ_INVALID_HANDLE) {
ipcz_.Close(std::exchange(portal_, IPCZ_INVALID_HANDLE), IPCZ_NO_FLAGS,
nullptr);
}
}
// Functions to interface with the Box() API.
static IpczResult Serialize(uintptr_t object,
uint32_t,
const void*,
void* data,
size_t* num_bytes,
IpczHandle* handles,
size_t* num_handles) {
auto& portal = *reinterpret_cast<NamedPortal*>(object);
const size_t required_byte_capacity = sizeof(Header) + portal.name_.size();
const size_t required_handle_capacity = 1;
const size_t byte_capacity = num_bytes ? *num_bytes : 0;
const size_t handle_capacity = num_handles ? *num_handles : 0;
if (num_bytes) {
*num_bytes = required_byte_capacity;
}
if (num_handles) {
*num_handles = required_handle_capacity;
}
if (byte_capacity < required_byte_capacity ||
handle_capacity < required_handle_capacity) {
return IPCZ_RESULT_RESOURCE_EXHAUSTED;
}
auto* header = static_cast<Header*>(data);
header->name_length = static_cast<size_t>(portal.name_.size());
memcpy(header + 1, portal.name_.data(), portal.name_.size());
handles[0] = std::exchange(portal.portal_, IPCZ_INVALID_HANDLE);
return IPCZ_RESULT_OK;
}
static void Destroy(uintptr_t object, uint32_t, const void*) {
delete reinterpret_cast<NamedPortal*>(object);
}
static std::unique_ptr<NamedPortal> Deserialize(
const IpczAPI& ipcz,
absl::Span<const uint8_t> bytes,
absl::Span<const IpczHandle> handles) {
ABSL_HARDENING_ASSERT(bytes.size() >= sizeof(Header));
const auto& header = *reinterpret_cast<const Header*>(bytes.data());
ABSL_HARDENING_ASSERT(bytes.size() == sizeof(Header) + header.name_length);
auto name_bytes = bytes.subspan(sizeof(Header), header.name_length);
const std::string name(name_bytes.begin(), name_bytes.end());
ABSL_HARDENING_ASSERT(handles.size() == 1);
return std::make_unique<NamedPortal>(ipcz, name, handles[0]);
}
private:
const IpczAPI& ipcz_;
const std::string name_;
IpczHandle portal_;
};
constexpr std::string_view kPortalName = "yahoo?";
MULTINODE_TEST_NODE(BoxTestNode, SerializedApplicationObjectClient) {
IpczHandle b = ConnectToBroker();
IpczHandle box;
std::string message;
ASSERT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, {&box, 1}));
EXPECT_EQ("hey", message);
IpczBoxContents contents = {.size = sizeof(contents)};
ASSERT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &contents));
EXPECT_EQ(IPCZ_BOX_TYPE_SUBPARCEL, contents.type);
EXPECT_NE(IPCZ_INVALID_HANDLE, contents.object.subparcel);
const IpczHandle subparcel = contents.object.subparcel;
constexpr size_t kDataSize = sizeof(NamedPortal::Header) + kPortalName.size();
uint8_t data[kDataSize];
size_t num_bytes = kDataSize;
IpczHandle p;
size_t num_handles = 1;
EXPECT_EQ(IPCZ_RESULT_OK, ipcz().Get(subparcel, IPCZ_NO_FLAGS, nullptr, data,
&num_bytes, &p, &num_handles, nullptr));
auto portal = NamedPortal::Deserialize(ipcz(), absl::MakeSpan(data), {&p, 1});
EXPECT_EQ(kPortalName, portal->name());
VerifyEndToEnd(portal->portal());
CloseAll({b, subparcel});
}
MULTINODE_TEST(BoxTest, SerializedApplicationObject) {
IpczHandle c = SpawnTestNode<SerializedApplicationObjectClient>();
auto [q, p] = OpenPortals();
auto portal = std::make_unique<NamedPortal>(ipcz(), kPortalName, p);
const IpczBoxContents contents = {
.size = sizeof(contents),
.type = IPCZ_BOX_TYPE_APPLICATION_OBJECT,
.object = {.application_object = NamedPortal::Release(std::move(portal))},
.serializer = &NamedPortal::Serialize,
.destructor = &NamedPortal::Destroy,
};
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Box(node(), &contents, IPCZ_NO_FLAGS, nullptr, &box));
Put(c, "hey", {&box, 1});
VerifyEndToEnd(q);
CloseAll({c, q});
}
MULTINODE_TEST(BoxTest, UnserializedApplicationObject) {
auto [a, b] = OpenPortals();
auto [q, p] = OpenPortals();
auto portal = std::make_unique<NamedPortal>(ipcz(), kPortalName, p);
// We can box an object without a serializer and transfer it between local
// portals.
const IpczBoxContents in_contents = {
.size = sizeof(in_contents),
.type = IPCZ_BOX_TYPE_APPLICATION_OBJECT,
.object = {.application_object = NamedPortal::Release(std::move(portal))},
.destructor = &NamedPortal::Destroy,
};
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Box(node(), &in_contents, IPCZ_NO_FLAGS, nullptr, &box));
Put(a, "", {&box, 1});
box = IPCZ_INVALID_HANDLE;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&box, 1}));
IpczBoxContents out_contents = {.size = sizeof(out_contents)};
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &out_contents));
EXPECT_EQ(IPCZ_BOX_TYPE_APPLICATION_OBJECT, out_contents.type);
portal = std::unique_ptr<NamedPortal>(
reinterpret_cast<NamedPortal*>(out_contents.object.application_object));
VerifyEndToEndLocal(q, portal->portal());
CloseAll({a, b, q});
}
} // namespace
} // namespace ipcz