blob: c755b033ded63c3ee340e66fc85971ace4d4bd56 [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 <string_view>
#include "ipcz/ipcz.h"
#include "test/multinode_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "util/ref_counted.h"
namespace ipcz {
namespace {
class ParcelTestNode : public test::TestNode {
public:
IpczResult WaitForParcel(IpczHandle portal) {
const IpczTrapConditions conditions = {
.size = sizeof(conditions),
.flags = IPCZ_TRAP_ABOVE_MIN_LOCAL_PARCELS,
.min_local_parcels = 0,
};
return WaitForConditions(portal, conditions);
}
};
using ParcelTest = test::MultinodeTest<ParcelTestNode>;
constexpr std::string_view kMessage = "here's that box of hornets you wanted";
constexpr std::string_view kHornets = "bzzzzz";
MULTINODE_TEST_NODE(ParcelTestNode, GetClient) {
IpczHandle b = ConnectToBroker();
EXPECT_EQ(IPCZ_RESULT_OK, WaitForParcel(b));
// Retrieving a parcel object removes it from its portal's queue.
IpczHandle parcel = 0;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Get(b, IPCZ_GET_PARCEL_ONLY, nullptr, nullptr, nullptr,
nullptr, nullptr, &parcel));
EXPECT_EQ(IPCZ_RESULT_UNAVAILABLE,
ipcz().Get(b, IPCZ_GET_PARCEL_ONLY, nullptr, nullptr, nullptr,
nullptr, nullptr, &parcel));
// Short reads behave as with portals, providing parcel dimensions on output.
size_t num_bytes = 0;
size_t num_handles = 0;
EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
ipcz().Get(parcel, IPCZ_NO_FLAGS, nullptr, nullptr, &num_bytes,
nullptr, &num_handles, nullptr));
EXPECT_EQ(kMessage.size(), num_bytes);
EXPECT_EQ(1u, num_handles);
// Invalid arguments: null data or handles with non-zero capacity.
EXPECT_EQ(IPCZ_RESULT_INVALID_ARGUMENT,
ipcz().Get(parcel, IPCZ_NO_FLAGS, nullptr, nullptr, &num_bytes,
nullptr, nullptr, nullptr));
EXPECT_EQ(IPCZ_RESULT_INVALID_ARGUMENT,
ipcz().Get(parcel, IPCZ_NO_FLAGS, nullptr, nullptr, nullptr,
nullptr, &num_handles, nullptr));
// Verify the contents.
char buffer[kMessage.size()];
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Get(parcel, IPCZ_NO_FLAGS, nullptr, buffer, &num_bytes, &box,
&num_handles, nullptr));
EXPECT_EQ(kMessage.size(), num_bytes);
EXPECT_EQ(1u, num_handles);
EXPECT_EQ(kMessage, std::string_view(buffer, num_bytes));
// Contents of the parcel are consumed by Get(), so now there's nothing left.
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Get(parcel, IPCZ_NO_FLAGS, nullptr, buffer, &num_bytes, &box,
&num_handles, nullptr));
EXPECT_EQ(0u, num_bytes);
EXPECT_EQ(0u, num_handles);
// Send the contents of the box back as another parcel.
Put(b, UnboxBlob(box));
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(b, IPCZ_TRAP_DEAD));
CloseAll({b, parcel});
}
MULTINODE_TEST(ParcelTest, Get) {
IpczHandle c = SpawnTestNode<GetClient>();
IpczHandle blob = BoxBlob(kHornets);
Put(c, kMessage, {&blob, 1});
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c, &message));
EXPECT_EQ(kHornets, message);
Close(c);
}
MULTINODE_TEST_NODE(ParcelTestNode, TwoPhaseGetClient) {
IpczHandle b = ConnectToBroker();
EXPECT_EQ(IPCZ_RESULT_OK, WaitForParcel(b));
IpczHandle parcel = 0;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Get(b, IPCZ_GET_PARCEL_ONLY, nullptr, nullptr, nullptr,
nullptr, nullptr, &parcel));
// Various combinations of missing args return to indicate size requirements.
const void* data;
size_t num_bytes;
size_t num_handles;
EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr, nullptr, nullptr,
nullptr));
EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr, &data, &num_bytes,
nullptr));
EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr, nullptr, &num_bytes,
&num_handles));
EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr, nullptr, nullptr,
&num_handles));
EXPECT_EQ(IPCZ_RESULT_OK, ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr,
&data, &num_bytes, &num_handles));
EXPECT_EQ(kMessage.size(), num_bytes);
EXPECT_EQ(1u, num_handles);
// We can't start a new get of any kind during the two-phase get.
EXPECT_EQ(IPCZ_RESULT_ALREADY_EXISTS,
ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr, &data, &num_bytes,
&num_handles));
EXPECT_EQ(IPCZ_RESULT_ALREADY_EXISTS,
ipcz().Get(parcel, IPCZ_NO_FLAGS, nullptr, &data, &num_bytes,
nullptr, &num_handles, nullptr));
// Two-phase gets on parcels can be aborted.
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().EndGet(parcel, 0, 0, IPCZ_END_GET_ABORT, nullptr, nullptr));
EXPECT_EQ(IPCZ_RESULT_OK, ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr,
&data, &num_bytes, &num_handles));
EXPECT_EQ(kMessage.size(), num_bytes);
EXPECT_EQ(1u, num_handles);
// Verify the contents, and partially consume the parcel.
IpczHandle box;
EXPECT_EQ(kMessage,
std::string_view(static_cast<const char*>(data), num_bytes));
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().EndGet(parcel, 1, 1, IPCZ_NO_FLAGS, nullptr, &box));
// A new two-phase read should see the first byte gone as well as the box.
EXPECT_EQ(IPCZ_RESULT_OK, ipcz().BeginGet(parcel, IPCZ_NO_FLAGS, nullptr,
&data, &num_bytes, &num_handles));
EXPECT_EQ(kMessage.size() - 1, num_bytes);
EXPECT_EQ(kMessage.substr(1),
std::string_view(static_cast<const char*>(data), num_bytes));
EXPECT_EQ(0u, num_handles);
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().EndGet(parcel, 0, 0, IPCZ_END_GET_ABORT, nullptr, nullptr));
// Send the contents of the box back as another parcel.
Put(b, UnboxBlob(box));
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(b, IPCZ_TRAP_DEAD));
CloseAll({b, parcel});
}
MULTINODE_TEST(ParcelTest, TwoPhaseGet) {
IpczHandle c = SpawnTestNode<TwoPhaseGetClient>();
IpczHandle blob = BoxBlob(kHornets);
Put(c, kMessage, {&blob, 1});
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(c, &message));
EXPECT_EQ(kHornets, message);
Close(c);
}
MULTINODE_TEST_NODE(ParcelTestNode, CloseClient) {
IpczHandle b = ConnectToBroker();
IpczHandle parcel;
EXPECT_EQ(IPCZ_RESULT_OK, WaitForParcel(b));
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Get(b, IPCZ_GET_PARCEL_ONLY, nullptr, nullptr, nullptr,
nullptr, nullptr, &parcel));
size_t num_bytes = 0;
size_t num_handles = 0;
EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
ipcz().Get(parcel, IPCZ_NO_FLAGS, nullptr, nullptr, &num_bytes,
nullptr, &num_handles, nullptr));
EXPECT_EQ(kMessage.size(), num_bytes);
EXPECT_EQ(1u, num_handles);
// Closing the parcel should close any attached objects. The broker should
// observer its `q` portal dying, because that portal's peer was attached to
// this parcel.
Close(parcel);
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(b, IPCZ_TRAP_DEAD));
Close(b);
}
MULTINODE_TEST(ParcelTest, Close) {
IpczHandle c = SpawnTestNode<CloseClient>();
auto [q, p] = OpenPortals();
Put(c, kMessage, {&p, 1});
EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(q, IPCZ_TRAP_DEAD));
CloseAll({c, q});
}
} // namespace
} // namespace ipcz