blob: b8982c5698cd85982cb3fdf0e378240910424065 [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 <string>
#include <string_view>
#include <utility>
#include "build/build_config.h"
#include "ipcz/ipcz.h"
#include "test/multinode_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/strings/str_cat.h"
namespace ipcz {
namespace {
using RemotePortalTestNode = test::TestNode;
using RemotePortalTest = test::MultinodeTest<RemotePortalTestNode>;
static constexpr std::string_view kTestMessage1 = "hello world";
static constexpr std::string_view kTestMessage2 = "hola mundo";
MULTINODE_TEST_NODE(RemotePortalTestNode, BasicConnectionClient) {
IpczHandle b = ConnectToBroker();
EXPECT_EQ(IPCZ_RESULT_OK, Put(b, kTestMessage1));
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message));
EXPECT_EQ(kTestMessage2, message);
Close(b);
}
MULTINODE_TEST(RemotePortalTest, BasicConnection) {
IpczHandle c = SpawnTestNode<BasicConnectionClient>();
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c, &message));
EXPECT_EQ(kTestMessage1, message);
EXPECT_EQ(IPCZ_RESULT_OK, Put(c, kTestMessage2));
Close(c);
}
MULTINODE_TEST_NODE(RemotePortalTestNode, PortalTransferClient) {
IpczHandle b = ConnectToBroker();
IpczHandle p = IPCZ_INVALID_HANDLE;
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, {&p, 1}));
EXPECT_EQ(kTestMessage1, message);
EXPECT_NE(IPCZ_INVALID_HANDLE, p);
VerifyEndToEnd(p);
WaitForDirectRemoteLink(p);
CloseAll({p, b});
}
MULTINODE_TEST(RemotePortalTest, PortalTransfer) {
IpczHandle c = SpawnTestNode<PortalTransferClient>();
auto [q, p] = OpenPortals();
EXPECT_EQ(IPCZ_RESULT_OK, Put(c, kTestMessage1, {&p, 1}));
VerifyEndToEnd(q);
WaitForDirectRemoteLink(q);
CloseAll({q, c});
}
constexpr size_t kMultipleHopsNumIterations = 100;
MULTINODE_TEST_NODE(RemotePortalTestNode, MultipleHopsClient1) {
IpczHandle b = ConnectToBroker();
auto [q, p] = OpenPortals();
EXPECT_EQ(IPCZ_RESULT_OK, Put(b, "", {&p, 1}));
for (size_t i = 0; i < kMultipleHopsNumIterations; ++i) {
EXPECT_EQ(IPCZ_RESULT_OK, Put(q, kTestMessage2));
}
for (size_t i = 0; i < kMultipleHopsNumIterations; ++i) {
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(q, &message));
EXPECT_EQ(kTestMessage1, message);
}
WaitForDirectRemoteLink(q);
PingPong(b);
CloseAll({q, b});
}
MULTINODE_TEST_NODE(RemotePortalTestNode, MultipleHopsClient2) {
IpczHandle b = ConnectToBroker();
IpczHandle p;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&p, 1}));
for (size_t i = 0; i < kMultipleHopsNumIterations; ++i) {
EXPECT_EQ(IPCZ_RESULT_OK, Put(p, kTestMessage1));
}
for (size_t i = 0; i < kMultipleHopsNumIterations; ++i) {
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(p, &message));
EXPECT_EQ(kTestMessage2, message);
}
WaitForDirectRemoteLink(p);
PingPong(b);
CloseAll({p, b});
}
MULTINODE_TEST(RemotePortalTest, MultipleHops) {
IpczHandle c1 = SpawnTestNode<MultipleHopsClient1>();
IpczHandle c2 = SpawnTestNode<MultipleHopsClient2>();
IpczHandle p;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c1, nullptr, {&p, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(c2, "", {&p, 1}));
WaitForPingAndReply(c1);
WaitForPingAndReply(c2);
CloseAll({c1, c2});
}
constexpr size_t kTransferBackAndForthNumIterations = 100;
MULTINODE_TEST_NODE(RemotePortalTestNode, TransferBackAndForthClient) {
IpczHandle b = ConnectToBroker();
for (size_t i = 0; i < kTransferBackAndForthNumIterations; ++i) {
IpczHandle p;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&p, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(b, "", {&p, 1}));
}
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(b, IPCZ_TRAP_PEER_CLOSED));
Close(b);
}
MULTINODE_TEST(RemotePortalTest, TransferBackAndForth) {
IpczHandle c = SpawnTestNode<TransferBackAndForthClient>();
constexpr std::string_view kMessage = "hihihihi";
auto [q, p] = OpenPortals();
std::string message;
for (size_t i = 0; i < kTransferBackAndForthNumIterations; ++i) {
EXPECT_EQ(IPCZ_RESULT_OK, Put(q, kMessage));
EXPECT_EQ(IPCZ_RESULT_OK, Put(c, "", {&p, 1}));
p = IPCZ_INVALID_HANDLE;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c, nullptr, {&p, 1}));
EXPECT_NE(IPCZ_INVALID_HANDLE, p);
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(p, &message));
EXPECT_EQ(kMessage, message);
}
VerifyEndToEndLocal(q, p);
WaitForDirectLocalLink(q, p);
CloseAll({q, p, c});
}
constexpr size_t kHugeNumberOfPortalsCount = 1000;
MULTINODE_TEST_NODE(RemotePortalTestNode, HugeNumberOfPortalsClient) {
IpczHandle b = ConnectToBroker();
IpczHandle other_client;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&other_client, 1}));
std::vector<IpczHandle> portals(kHugeNumberOfPortalsCount);
for (auto& portal : portals) {
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&portal, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(other_client, "", {&portal, 1}));
}
for (size_t i = 0; i < kHugeNumberOfPortalsCount; ++i) {
EXPECT_EQ(IPCZ_RESULT_OK,
WaitToGet(other_client, nullptr, {&portals[i], 1}));
IpczHandle box = BoxBlob(absl::StrCat(absl::Dec(i)));
EXPECT_EQ(IPCZ_RESULT_OK, Put(portals[i], "", {&box, 1}));
box = IPCZ_INVALID_HANDLE;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(portals[i], nullptr, {&box, 1}));
EXPECT_EQ(absl::StrCat(absl::Dec(i)), UnboxBlob(box));
}
EXPECT_EQ(IPCZ_RESULT_OK, Put(b, "", {&other_client, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(b, IPCZ_TRAP_PEER_CLOSED));
CloseAll(portals);
Close(b);
}
MULTINODE_TEST(RemotePortalTest, HugeNumberOfPortals) {
// Opens a very large number of portals, and sends them all to client nodes.
// The client nodes exchange these portals with each other and transmit
// parcels over them, with and without driver objects. This exercises
// NodeLinkMemory allocations and related synchronization around e.g.
// delegated allocation and driver object relaying.
IpczHandle c1 = SpawnTestNode<HugeNumberOfPortalsClient>();
IpczHandle c2 = SpawnTestNode<HugeNumberOfPortalsClient>();
auto [q, p] = OpenPortals();
EXPECT_EQ(IPCZ_RESULT_OK, Put(c1, "", {&q, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(c2, "", {&p, 1}));
for (size_t i = 0; i < kHugeNumberOfPortalsCount; ++i) {
auto [a, b] = OpenPortals();
EXPECT_EQ(IPCZ_RESULT_OK, Put(c1, "", {&a, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(c2, "", {&b, 1}));
}
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c1, nullptr, {&q, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c2, nullptr, {&p, 1}));
WaitForDirectLocalLink(q, p);
CloseAll({c1, c2, q, p});
}
constexpr size_t kRouteExpansionStressTestNumIterations = 100;
MULTINODE_TEST_NODE(RemotePortalTestNode, RoutingStressTestClient) {
// Each client only expects portals from the broker and bounces them back.
IpczHandle b = ConnectToBroker();
for (size_t i = 0; i < kRouteExpansionStressTestNumIterations; ++i) {
IpczHandle p;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&p, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(b, "", {&p, 1}));
}
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(b, IPCZ_TRAP_PEER_CLOSED));
Close(b);
}
MULTINODE_TEST(RemotePortalTest, RoutingStressTest) {
// This test spawns a bunch of nodes and bounces two portals back and forth
// among them over a large number of iterations, then waits for all the
// intermediate routers to be removed. Every iteration also sends a message
// from each end of the pipe to exercise parcel routing, including with
// driver objects attached.
constexpr size_t kNumClientPairs = 3;
std::pair<IpczHandle, IpczHandle> client_pairs[kNumClientPairs];
for (auto& pair : client_pairs) {
pair = std::make_pair(SpawnTestNode<RoutingStressTestClient>(),
SpawnTestNode<RoutingStressTestClient>());
}
auto [a, b] = OpenPortals();
for (size_t j = 0; j < kRouteExpansionStressTestNumIterations; ++j) {
IpczHandle box = BoxBlob(absl::StrCat(absl::Dec(j)));
EXPECT_EQ(IPCZ_RESULT_OK,
Put(a, absl::StrCat("a", absl::Dec(j)), {&box, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(b, absl::StrCat("b", absl::Dec(j))));
for (auto& pair : client_pairs) {
EXPECT_EQ(IPCZ_RESULT_OK, Put(pair.first, "", {&a, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(pair.second, "", {&b, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(pair.first, nullptr, {&a, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(pair.second, nullptr, {&b, 1}));
}
}
WaitForDirectLocalLink(a, b);
for (size_t i = 0; i < kRouteExpansionStressTestNumIterations; ++i) {
std::string message;
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(a, &message));
EXPECT_EQ(absl::StrCat("b", absl::Dec(i)), message);
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, {&box, 1}));
EXPECT_EQ(absl::StrCat("a", absl::Dec(i)), message);
EXPECT_EQ(absl::StrCat(absl::Dec(i)), UnboxBlob(box));
}
for (auto& pair : client_pairs) {
CloseAll({pair.first, pair.second});
}
CloseAll({a, b});
}
MULTINODE_TEST_NODE(RemotePortalTestNode, DisconnectThroughProxyClient1) {
IpczHandle b = ConnectToBroker();
IpczHandle q;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&q, 1}));
// Should eventually be observed by virtue of the forced disconnection of
// client 3 in the main test body.
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(q, IPCZ_TRAP_PEER_CLOSED));
CloseAll({q, b});
}
MULTINODE_TEST_NODE(RemotePortalTestNode, DisconnectThroughProxyClient2) {
IpczHandle b = ConnectToBroker();
// Receive a portal p from the broker and bounce it back.
IpczHandle p;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&p, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(b, "", {&p, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(b, IPCZ_TRAP_PEER_CLOSED));
Close(b);
}
MULTINODE_TEST_NODE(RemotePortalTestNode, DisconnectThroughProxyClient3) {
IpczHandle b = ConnectToBroker();
// Receive a portal p from the broker and then immediately terminate this
// node.
IpczHandle p;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&p, 1}));
// Forcibly shut this node down, severing all connections to other nodes. We
// do this *before* closing `p` to ensure that we are't just exercising normal
// portal closure, which other tests already cover. From the perspective of
// other nodes, we are effectively simulating a crash of this node.
CloseThisNode();
// It's still necessary to explicitly close local portals after the node
// itself has been shut down. Otherwise they'd leak.
CloseAll({p, b});
}
#if BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER)
#define MAYBE_DisconnectThroughProxy DISABLED_DisconnectThroughProxy
#else
#define MAYBE_DisconnectThroughProxy DisconnectThroughProxy
#endif
MULTINODE_TEST(RemotePortalTest, MAYBE_DisconnectThroughProxy) {
// Exercises node disconnection. Namely if portals on nodes 1 and 3 are
// connected via proxy on node 2, and node 3 disappears, node 1's portal
// should observe peer closure.
IpczHandle c1, c3;
auto c1_control = SpawnTestNode<DisconnectThroughProxyClient1>({&c1, 1});
IpczHandle c2 = SpawnTestNode<DisconnectThroughProxyClient2>();
auto c3_control = SpawnTestNode<DisconnectThroughProxyClient3>({&c3, 1});
auto [q, p] = OpenPortals();
// We send q to client 1, and p to client 2.
EXPECT_EQ(IPCZ_RESULT_OK, Put(c1, "", {&q, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(c2, "", {&p, 1}));
// Client 2 forwards p back to us, and we forward it now to client 3. This
// process ensures that client 2 will for some time serve as a proxy between
// client 1 and client 3.
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c2, nullptr, {&p, 1}));
EXPECT_EQ(IPCZ_RESULT_OK, Put(c3, "", {&p, 1}));
// Client 3 will terminate on its own. Though not determinstic, this will
// race with proxy reduction such that client 2 may still be proxying for the
// q-p portal pair when client 3 (who owns p) goes away.
EXPECT_TRUE(c3_control->WaitForShutdown());
// Client 1 waits on q observing peer closure before terminating. This will
// block until that happens.
EXPECT_TRUE(c1_control->WaitForShutdown());
CloseAll({c1, c2, c3});
}
} // namespace
} // namespace ipcz