blob: 553db7a870fa9bead936848203b14351dda9aafd [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 <fidl/llcpptest.protocol.test/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl/cpp/wire/server.h>
#include <cstdint>
#include <src/lib/fidl/llcpp/tests/arena_checker.h>
#include <zxtest/zxtest.h>
namespace test = ::llcpptest_protocol_test;
//
// This file tests the behavior of caller-allocating flavors (i.e. the
// `.buffer()` syntax) of clients and server APIs end-to-end.
//
namespace {
class CallerAllocatingFixture : public ::zxtest::Test {
public:
class Frobinator : public fidl::WireServer<test::Frobinator> {
public:
virtual size_t frob_count() const = 0;
};
virtual std::shared_ptr<Frobinator> GetServer() = 0;
void SetUp() override {
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigAttachToCurrentThread);
zx::result server_end = fidl::CreateEndpoints(&client_end_);
ASSERT_OK(server_end.status_value());
server_ = GetServer();
binding_ref_ = fidl::BindServer(loop_->dispatcher(), std::move(*server_end), server_);
}
std::unique_ptr<async::Loop>& loop() { return loop_; }
fidl::ClientEnd<test::Frobinator>& client_end() { return client_end_; }
const fidl::ServerBindingRef<test::Frobinator>& binding_ref() const {
return binding_ref_.value();
}
size_t frob_count() const { return server_->frob_count(); }
private:
std::unique_ptr<async::Loop> loop_;
fidl::ClientEnd<test::Frobinator> client_end_;
std::shared_ptr<Frobinator> server_;
std::optional<fidl::ServerBindingRef<test::Frobinator>> binding_ref_;
};
class CallerAllocatingFixtureWithDefaultServer : public CallerAllocatingFixture {
public:
class DefaultFrobinator : public Frobinator {
public:
void Frob(FrobRequestView request, FrobCompleter::Sync& completer) override {
EXPECT_EQ(request->value.get(), "test");
frob_count_++;
}
void Grob(GrobRequestView request, GrobCompleter::Sync& completer) override {
completer.Reply(request->value);
}
void TwoWayEmptyArg(TwoWayEmptyArgCompleter::Sync& completer) override { completer.Reply(); }
size_t frob_count() const override { return frob_count_; }
private:
size_t frob_count_ = 0;
};
std::shared_ptr<Frobinator> GetServer() override { return std::make_shared<DefaultFrobinator>(); }
};
bool IsPointerInBufferSpan(void* pointer, fidl::BufferSpan buffer_span) {
auto* data = static_cast<uint8_t*>(pointer);
if (data > buffer_span.data) {
if (data - buffer_span.data < buffer_span.capacity) {
return true;
}
}
return false;
}
class WireCallTest : public CallerAllocatingFixtureWithDefaultServer {
void SetUp() override {
CallerAllocatingFixtureWithDefaultServer::SetUp();
ASSERT_OK(loop()->StartThread());
}
};
TEST_F(WireCallTest, CallerAllocateBufferSpan) {
fidl::SyncClientBuffer<test::Frobinator::Grob> buffer;
fidl::WireUnownedResult result = fidl::WireCall(client_end()).buffer(buffer.view())->Grob("test");
ASSERT_OK(result.status());
EXPECT_EQ(result.value().value.get(), "test");
EXPECT_TRUE(IsPointerInBufferSpan(result.Unwrap(), buffer.view()));
}
TEST_F(WireCallTest, CallerAllocateBufferSpanLeftValueVeneerObject) {
fidl::SyncClientBuffer<test::Frobinator::Grob> buffer;
auto buffered = fidl::WireCall(client_end()).buffer(buffer.view());
fidl::WireUnownedResult result = buffered->Grob("test");
ASSERT_OK(result.status());
EXPECT_EQ(result.value().value.get(), "test");
EXPECT_TRUE(IsPointerInBufferSpan(result.Unwrap(), buffer.view()));
}
TEST_F(WireCallTest, CallerAllocateArena) {
fidl::Arena arena;
fidl::WireUnownedResult result = fidl::WireCall(client_end()).buffer(arena)->Grob("test");
ASSERT_OK(result.status());
EXPECT_EQ(result.value().value.get(), "test");
EXPECT_TRUE(fidl_testing::ArenaChecker::IsPointerInArena(result.Unwrap(), arena));
}
TEST_F(WireCallTest, CallerAllocateArenaLeftValueVeneerObject) {
// Pre-allocate a 1 MiB arena.
constexpr size_t kArenaSize = 1024ul * 1024ul;
auto arena = std::make_unique<fidl::Arena<kArenaSize>>();
auto buffered = fidl::WireCall(client_end()).buffer(*arena);
// Using an arena, we can now afford to make multiple calls without extra heap
// allocation, while keeping all the responses simultaneously alive...
fidl::WireUnownedResult result_foo = buffered->Grob("foo");
fidl::WireUnownedResult result_bar = buffered->Grob("bar");
fidl::WireUnownedResult result_baz = buffered->Grob("baz");
ASSERT_OK(result_foo.status());
ASSERT_OK(result_bar.status());
ASSERT_OK(result_baz.status());
EXPECT_EQ(result_foo.value().value.get(), "foo");
EXPECT_TRUE(fidl_testing::ArenaChecker::IsPointerInArena(result_foo.Unwrap(), *arena));
EXPECT_EQ(result_bar.value().value.get(), "bar");
EXPECT_TRUE(fidl_testing::ArenaChecker::IsPointerInArena(result_bar.Unwrap(), *arena));
EXPECT_EQ(result_baz.value().value.get(), "baz");
EXPECT_TRUE(fidl_testing::ArenaChecker::IsPointerInArena(result_baz.Unwrap(), *arena));
}
TEST_F(WireCallTest, CallerAllocateInsufficientBufferSize) {
uint8_t small_buffer[8];
fidl::WireUnownedResult result = fidl::WireCall(client_end())
.buffer(fidl::BufferSpan(small_buffer, sizeof(small_buffer)))
->Grob("test");
EXPECT_STATUS(ZX_ERR_BUFFER_TOO_SMALL, result.status());
EXPECT_EQ(fidl::Reason::kEncodeError, result.reason());
}
class WireClientTest : public CallerAllocatingFixtureWithDefaultServer {};
class WireSharedClientTest : public CallerAllocatingFixtureWithDefaultServer {};
class GrobResponseContext : public fidl::WireResponseContext<test::Frobinator::Grob> {
public:
void OnResult(fidl::WireUnownedResult<test::Frobinator::Grob>& result) final {
ASSERT_OK(result.status());
EXPECT_EQ(result.value().value.get(), "test");
got_result = true;
}
bool got_result = false;
};
class TwoWayEmptyArgResponseContext
: public fidl::WireResponseContext<test::Frobinator::TwoWayEmptyArg> {
public:
void OnResult(fidl::WireUnownedResult<test::Frobinator::TwoWayEmptyArg>& result) final {
ASSERT_OK(result.status());
got_result = true;
}
bool got_result = false;
};
TEST_F(WireClientTest, TwoWayCallerAllocateBufferSpan) {
fidl::AsyncClientBuffer<test::Frobinator::Grob> buffer;
fidl::WireClient client(std::move(client_end()), loop()->dispatcher());
GrobResponseContext context;
client.buffer(buffer.view())->Grob("test").ThenExactlyOnce(&context);
loop()->RunUntilIdle();
EXPECT_TRUE(context.got_result);
}
TEST_F(WireClientTest, TwoWayCallerAllocateArena) {
fidl::Arena arena;
fidl::WireClient client(std::move(client_end()), loop()->dispatcher());
EXPECT_FALSE(fidl_testing::ArenaChecker::DidUse(arena));
GrobResponseContext context;
client.buffer(arena)->Grob("test").ThenExactlyOnce(&context);
loop()->RunUntilIdle();
EXPECT_TRUE(context.got_result);
EXPECT_TRUE(fidl_testing::ArenaChecker::DidUse(arena));
}
TEST_F(WireClientTest, OneWayCallerAllocate) {
fidl::AsyncClientBuffer<test::Frobinator::Grob> buffer;
fidl::WireClient client(std::move(client_end()), loop()->dispatcher());
fidl::Status result = client.buffer(buffer.view())->Frob("test");
loop()->RunUntilIdle();
EXPECT_OK(result.status());
EXPECT_EQ(1, frob_count());
// Test multi-request syntax.
fidl::Arena arena;
auto buffered = client.buffer(arena);
EXPECT_OK(buffered->Frob("test").status());
EXPECT_OK(buffered->Frob("test").status());
EXPECT_OK(buffered->Frob("test").status());
loop()->RunUntilIdle();
EXPECT_EQ(4, frob_count());
}
TEST_F(WireSharedClientTest, TwoWayCallerAllocateBufferSpan) {
fidl::AsyncClientBuffer<test::Frobinator::Grob> buffer;
fidl::WireSharedClient client(std::move(client_end()), loop()->dispatcher());
GrobResponseContext context;
client.buffer(buffer.view())->Grob("test").ThenExactlyOnce(&context);
loop()->RunUntilIdle();
EXPECT_TRUE(context.got_result);
}
TEST_F(WireSharedClientTest, TwoWayCallerAllocateArena) {
fidl::Arena arena;
fidl::WireSharedClient client(std::move(client_end()), loop()->dispatcher());
EXPECT_FALSE(fidl_testing::ArenaChecker::DidUse(arena));
GrobResponseContext context;
client.buffer(arena)->Grob("test").ThenExactlyOnce(&context);
loop()->RunUntilIdle();
EXPECT_TRUE(context.got_result);
EXPECT_TRUE(fidl_testing::ArenaChecker::DidUse(arena));
}
TEST_F(WireSharedClientTest, TwoWayEmptyArgCallerAllocateArena) {
fidl::Arena arena;
fidl::WireSharedClient client(std::move(client_end()), loop()->dispatcher());
EXPECT_FALSE(fidl_testing::ArenaChecker::DidUse(arena));
TwoWayEmptyArgResponseContext context;
client.buffer(arena)->TwoWayEmptyArg().ThenExactlyOnce(&context);
loop()->RunUntilIdle();
EXPECT_TRUE(context.got_result);
EXPECT_TRUE(fidl_testing::ArenaChecker::DidUse(arena));
}
TEST_F(WireSharedClientTest, OneWayCallerAllocate) {
fidl::AsyncClientBuffer<test::Frobinator::Grob> buffer;
fidl::WireSharedClient client(std::move(client_end()), loop()->dispatcher());
fidl::Status result = client.buffer(buffer.view())->Frob("test");
loop()->RunUntilIdle();
EXPECT_OK(result.status());
EXPECT_EQ(1, frob_count());
// Test multi-request syntax.
fidl::Arena arena;
auto buffered = client.buffer(arena);
EXPECT_OK(buffered->Frob("test").status());
EXPECT_OK(buffered->Frob("test").status());
EXPECT_OK(buffered->Frob("test").status());
loop()->RunUntilIdle();
EXPECT_EQ(4, frob_count());
}
class WireSendEventTest : public CallerAllocatingFixtureWithDefaultServer {};
class ExpectHrobEventHandler : public fidl::WireAsyncEventHandler<test::Frobinator> {
public:
explicit ExpectHrobEventHandler(std::string expected) : expected_(std::move(expected)) {}
void Hrob(fidl::WireEvent<test::Frobinator::Hrob>* event) final {
EXPECT_EQ(event->value.get(), expected_);
hrob_count_++;
}
void on_fidl_error(fidl::UnbindInfo info) final {
ADD_FAILURE("Unexpected error: %s", info.FormatDescription().c_str());
}
int hrob_count() const { return hrob_count_; }
private:
std::string expected_;
int hrob_count_ = 0;
};
class ExpectPeerClosedEventHandler : public fidl::WireAsyncEventHandler<test::Frobinator> {
public:
ExpectPeerClosedEventHandler() = default;
void Hrob(fidl::WireEvent<test::Frobinator::Hrob>* event) final {
ADD_FAILURE("Unexpected Hrob event: %s", std::string(event->value.get()).c_str());
}
void on_fidl_error(fidl::UnbindInfo info) final {
EXPECT_EQ(fidl::Reason::kPeerClosedWhileReading, info.reason());
peer_closed_ = true;
}
bool peer_closed() const { return peer_closed_; }
private:
bool peer_closed_ = false;
};
TEST_F(WireSendEventTest, ServerBindingRefCallerAllocate) {
fidl::Arena arena;
ExpectHrobEventHandler event_handler{"test"};
fidl::WireClient client(std::move(client_end()), loop()->dispatcher(), &event_handler);
fidl::Status result = fidl::WireSendEvent(binding_ref()).buffer(arena)->Hrob("test");
EXPECT_OK(result.status());
EXPECT_TRUE(fidl_testing::ArenaChecker::DidUse(arena));
loop()->RunUntilIdle();
EXPECT_EQ(1, event_handler.hrob_count());
}
TEST_F(WireSendEventTest, ServerBindingRefCallerAllocateInsufficientBufferSize) {
uint8_t small_buffer[8];
ExpectPeerClosedEventHandler event_handler;
fidl::WireClient client(std::move(client_end()), loop()->dispatcher(), &event_handler);
fidl::Status result = fidl::WireSendEvent(binding_ref())
.buffer(fidl::BufferSpan(small_buffer, sizeof(small_buffer)))
->Hrob("test");
EXPECT_STATUS(ZX_ERR_BUFFER_TOO_SMALL, result.status());
EXPECT_EQ(fidl::Reason::kEncodeError, result.reason());
// Server is unbound due to the error.
loop()->RunUntilIdle();
EXPECT_TRUE(event_handler.peer_closed());
fidl::Status error = fidl::WireSendEvent(binding_ref())->Hrob("test");
EXPECT_TRUE(error.is_canceled());
}
TEST(WireSendEventTest, ServerEndCallerAllocate) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
fidl::ServerEnd<test::Frobinator> server_end;
zx::result client_end = fidl::CreateEndpoints(&server_end);
ASSERT_OK(client_end.status_value());
ExpectHrobEventHandler event_handler{"test"};
fidl::WireClient client(std::move(*client_end), loop.dispatcher(), &event_handler);
fidl::Arena arena;
fidl::Status result = fidl::WireSendEvent(server_end).buffer(arena)->Hrob("test");
EXPECT_OK(result.status());
EXPECT_TRUE(fidl_testing::ArenaChecker::DidUse(arena));
loop.RunUntilIdle();
EXPECT_EQ(1, event_handler.hrob_count());
}
TEST(WireSendEventTest, ServerEndCallerAllocateInsufficientBufferSize) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
fidl::ServerEnd<test::Frobinator> server_end;
zx::result client_end = fidl::CreateEndpoints(&server_end);
ASSERT_OK(client_end.status_value());
ExpectHrobEventHandler event_handler{"test"};
fidl::WireClient client(std::move(*client_end), loop.dispatcher(), &event_handler);
uint8_t small_buffer[8];
fidl::Status result = fidl::WireSendEvent(server_end)
.buffer(fidl::BufferSpan(small_buffer, sizeof(small_buffer)))
->Hrob("test");
EXPECT_STATUS(ZX_ERR_BUFFER_TOO_SMALL, result.status());
EXPECT_EQ(fidl::Reason::kEncodeError, result.reason());
loop.RunUntilIdle();
EXPECT_EQ(0, event_handler.hrob_count());
}
class CallerAllocatingFixtureWithCallerAllocatingServer : public CallerAllocatingFixture {
public:
using GrobHandler =
fit::callback<void(Frobinator::GrobRequestView, Frobinator::GrobCompleter::Sync&)>;
class CallerAllocatingFrobinator : public Frobinator {
public:
void Frob(FrobRequestView request, FrobCompleter::Sync& completer) override {
ZX_PANIC("Unused");
}
void Grob(GrobRequestView request, GrobCompleter::Sync& completer) override {
grob_handler(request, completer);
}
void TwoWayEmptyArg(TwoWayEmptyArgCompleter::Sync& completer) override { ZX_PANIC("Unused"); }
size_t frob_count() const override { return 0; }
GrobHandler grob_handler;
};
std::shared_ptr<Frobinator> GetServer() override { return impl_; }
void set_grob_handler(GrobHandler handler) { impl_->grob_handler = std::move(handler); }
private:
std::shared_ptr<CallerAllocatingFrobinator> impl_ =
std::make_shared<CallerAllocatingFrobinator>();
};
class WireCompleterTest : public CallerAllocatingFixtureWithCallerAllocatingServer {};
TEST_F(WireCompleterTest, CallerAllocateBufferSpan) {
set_grob_handler(
[](Frobinator::GrobRequestView request, Frobinator::GrobCompleter::Sync& completer) {
fidl::ServerBuffer<test::Frobinator::Grob> buffer;
completer.buffer(buffer.view()).Reply(request->value);
});
fidl::WireClient client(std::move(client_end()), loop()->dispatcher());
bool called = false;
client->Grob("test").ThenExactlyOnce(
[&](fidl::WireUnownedResult<test::Frobinator::Grob>& result) {
called = true;
ASSERT_OK(result.status());
EXPECT_EQ("test", result.value().value.get());
});
EXPECT_OK(loop()->RunUntilIdle());
EXPECT_TRUE(called);
}
TEST_F(WireCompleterTest, CallerAllocateArena) {
set_grob_handler(
[](Frobinator::GrobRequestView request, Frobinator::GrobCompleter::Sync& completer) {
fidl::Arena arena;
completer.buffer(arena).Reply(request->value);
});
fidl::WireClient client(std::move(client_end()), loop()->dispatcher());
bool called = false;
client->Grob("test").ThenExactlyOnce(
[&](fidl::WireUnownedResult<test::Frobinator::Grob>& result) {
called = true;
ASSERT_OK(result.status());
EXPECT_EQ("test", result.value().value.get());
});
EXPECT_OK(loop()->RunUntilIdle());
EXPECT_TRUE(called);
}
TEST_F(WireCompleterTest, CallerAllocateInsufficientBufferSize) {
set_grob_handler(
[](Frobinator::GrobRequestView request, Frobinator::GrobCompleter::Sync& completer) {
FIDL_ALIGNDECL uint8_t small[8];
fidl::BufferSpan small_buf(small, sizeof(small));
completer.buffer(small_buf).Reply(request->value);
fidl::Status result = completer.result_of_reply();
EXPECT_STATUS(ZX_ERR_BUFFER_TOO_SMALL, result.status());
EXPECT_EQ(fidl::Reason::kEncodeError, result.reason());
});
fidl::WireClient client(std::move(client_end()), loop()->dispatcher());
bool called = false;
client->Grob("test").ThenExactlyOnce(
[&](fidl::WireUnownedResult<test::Frobinator::Grob>& result) {
called = true;
ASSERT_STATUS(ZX_ERR_PEER_CLOSED, result.status());
});
EXPECT_OK(loop()->RunUntilIdle());
EXPECT_TRUE(called);
}
} // namespace