| // Copyright 2020 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 <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/time.h> |
| #include <lib/fidl/epitaph.h> |
| #include <lib/fidl/llcpp/server.h> |
| #include <lib/fit/defer.h> |
| #include <lib/stdcompat/type_traits.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/eventpair.h> |
| #include <string.h> |
| #include <zircon/types.h> |
| |
| #include <fidl/test/coding/fuchsia/llcpp/fidl.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| using ::fidl_test_coding_fuchsia::Example; |
| |
| class Server : public fidl::WireServer<Example> { |
| public: |
| explicit Server(const char* data, size_t size) : data_(data), size_(size) {} |
| |
| void TwoWay(TwoWayRequestView request, TwoWayCompleter::Sync& completer) override { |
| ASSERT_EQ(size_, request->in.size()); |
| EXPECT_EQ(0, strncmp(data_, request->in.data(), size_)); |
| completer.Reply(request->in); |
| } |
| |
| void OneWay(OneWayRequestView, OneWayCompleter::Sync&) override {} |
| |
| private: |
| const char* data_; |
| size_t size_; |
| }; |
| |
| TEST(GenAPITestCase, TwoWayAsyncManaged) { |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::Client<Example> client(std::move(local), loop.dispatcher()); |
| |
| static constexpr char data[] = "TwoWay() sync managed"; |
| auto server_binding = fidl::BindServer(loop.dispatcher(), std::move(remote), |
| std::make_unique<Server>(data, strlen(data))); |
| |
| sync_completion_t done; |
| auto result = client->TwoWay(fidl::StringView(data), |
| [&done](fidl::WireResponse<Example::TwoWay>* response) { |
| ASSERT_EQ(strlen(data), response->out.size()); |
| EXPECT_EQ(0, strncmp(response->out.data(), data, strlen(data))); |
| sync_completion_signal(&done); |
| }); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| |
| server_binding.Unbind(); |
| } |
| |
| TEST(GenAPITestCase, TwoWayAsyncCallerAllocated) { |
| class ResponseContext final : public fidl::WireResponseContext<Example::TwoWay> { |
| public: |
| ResponseContext(sync_completion_t* done, const char* data, size_t size) |
| : done_(done), data_(data), size_(size) {} |
| |
| void OnCanceled() override { |
| sync_completion_signal(done_); |
| FAIL(); |
| } |
| |
| void OnResult(fidl::WireUnownedResult<Example::TwoWay>&& message) override { |
| ASSERT_TRUE(message.ok()); |
| auto& out = message->out; |
| ASSERT_EQ(size_, out.size()); |
| EXPECT_EQ(0, strncmp(out.data(), data_, size_)); |
| sync_completion_signal(done_); |
| } |
| |
| private: |
| sync_completion_t* done_; |
| const char* data_; |
| size_t size_; |
| }; |
| |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::Client<Example> client(std::move(local), loop.dispatcher()); |
| |
| static constexpr char data[] = "TwoWay() sync caller-allocated"; |
| auto server_binding = fidl::BindServer(loop.dispatcher(), std::move(remote), |
| std::make_unique<Server>(data, strlen(data))); |
| |
| sync_completion_t done; |
| fidl::Buffer<fidl::WireRequest<Example::TwoWay>> buffer; |
| ResponseContext context(&done, data, strlen(data)); |
| auto result = client->TwoWay(buffer.view(), fidl::StringView(data), &context); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| |
| server_binding.Unbind(); |
| } |
| |
| TEST(GenAPITestCase, EventManaged) { |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| static constexpr char data[] = "OnEvent() managed"; |
| class EventHandler : public fidl::WireAsyncEventHandler<Example> { |
| public: |
| EventHandler() = default; |
| |
| sync_completion_t& done() { return done_; } |
| |
| void OnEvent(fidl::WireResponse<Example::OnEvent>* event) { |
| ASSERT_EQ(strlen(data), event->out.size()); |
| EXPECT_EQ(0, strncmp(event->out.data(), data, strlen(data))); |
| sync_completion_signal(&done_); |
| } |
| |
| private: |
| sync_completion_t done_; |
| }; |
| |
| auto event_handler = std::make_shared<EventHandler>(); |
| fidl::Client<Example> client(std::move(local), loop.dispatcher(), event_handler); |
| |
| auto server_binding = fidl::BindServer(loop.dispatcher(), std::move(remote), |
| std::make_unique<Server>(data, strlen(data))); |
| |
| // Wait for the event from the server. |
| ASSERT_OK(server_binding->OnEvent(fidl::StringView(data))); |
| ASSERT_OK(sync_completion_wait(&event_handler->done(), ZX_TIME_INFINITE)); |
| |
| server_binding.Unbind(); |
| } |
| |
| TEST(GenAPITestCase, ConsumeEventsWhenEventHandlerIsAbsent) { |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| fidl::Client<Example> client(std::move(local), loop.dispatcher()); |
| fidl::WireEventSender<Example> event_sender(std::move(remote)); |
| |
| // Send an unhandled event. The event should be silently discarded since |
| // the user did not provide an event handler. |
| zx::eventpair ep1, ep2; |
| ASSERT_OK(zx::eventpair::create(0, &ep1, &ep2)); |
| ASSERT_OK(event_sender.OnResourceEvent(std::move(ep1))); |
| zx_signals_t observed; |
| ASSERT_OK(zx_object_wait_one(ep2.get(), ZX_EVENTPAIR_PEER_CLOSED, ZX_TIME_INFINITE, &observed)); |
| ASSERT_EQ(ZX_EVENTPAIR_PEER_CLOSED, observed); |
| } |
| |
| TEST(GenAPITestCase, ConsumeEventsWhenEventHandlerMethodIsAbsent) { |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<Example> {}; |
| |
| fidl::Client<Example> client(std::move(local), loop.dispatcher(), |
| std::make_shared<EventHandler>()); |
| fidl::WireEventSender<Example> event_sender(std::move(remote)); |
| |
| // Send an unhandled event. The event should be silently discarded since |
| // the user did not provide a handler method for |OnResourceEvent|. |
| zx::eventpair ep1, ep2; |
| ASSERT_OK(zx::eventpair::create(0, &ep1, &ep2)); |
| ASSERT_OK(event_sender.OnResourceEvent(std::move(ep1))); |
| zx_signals_t observed; |
| ASSERT_OK(zx_object_wait_one(ep2.get(), ZX_EVENTPAIR_PEER_CLOSED, ZX_TIME_INFINITE, &observed)); |
| ASSERT_EQ(ZX_EVENTPAIR_PEER_CLOSED, observed); |
| } |
| |
| // This is test is almost identical to ClientBindingTestCase.Epitaph in llcpp_client_test.cc but |
| // validates the part of the flow that's handled in the generated code. |
| TEST(GenAPITestCase, Epitaph) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| sync_completion_t unbound; |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<Example> { |
| public: |
| explicit EventHandler(sync_completion_t& unbound) : unbound_(unbound) {} |
| |
| void Unbound(fidl::UnbindInfo info) override { |
| EXPECT_EQ(fidl::Reason::kPeerClosed, info.reason()); |
| EXPECT_EQ(ZX_ERR_BAD_STATE, info.status()); |
| sync_completion_signal(&unbound_); |
| }; |
| |
| private: |
| sync_completion_t& unbound_; |
| }; |
| |
| fidl::Client<Example> client(std::move(local), loop.dispatcher(), |
| std::make_shared<EventHandler>(unbound)); |
| |
| // Send an epitaph and wait for on_unbound to run. |
| ASSERT_OK(fidl_epitaph_write(remote.channel().get(), ZX_ERR_BAD_STATE)); |
| EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE)); |
| } |
| |
| TEST(GenAPITestCase, UnbindInfoEncodeError) { |
| class ErrorServer : public fidl::WireServer<Example> { |
| public: |
| explicit ErrorServer() {} |
| |
| void TwoWay(TwoWayRequestView request, TwoWayCompleter::Sync& completer) override { |
| // Fail to send the reply due to an encoding error (the buffer is too small). |
| // The buffer still needs to be properly aligned. |
| constexpr size_t kSmallSize = 8; |
| FIDL_ALIGNDECL uint8_t small_buffer[kSmallSize]; |
| static_assert(sizeof(fidl::WireResponse<Example::TwoWay>) > kSmallSize); |
| fidl::BufferSpan too_small(small_buffer, std::size(small_buffer)); |
| EXPECT_EQ(ZX_ERR_BUFFER_TOO_SMALL, completer.Reply(too_small, request->in).status()); |
| completer.Close(ZX_OK); // This should not panic. |
| } |
| |
| void OneWay(OneWayRequestView, OneWayCompleter::Sync&) override {} |
| }; |
| |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::Client<Example> client(std::move(local), loop.dispatcher()); |
| |
| sync_completion_t done; |
| fidl::OnUnboundFn<ErrorServer> on_unbound = |
| [&done](ErrorServer*, fidl::UnbindInfo info, |
| fidl::ServerEnd<fidl_test_coding_fuchsia::Example>) { |
| EXPECT_EQ(fidl::Reason::kEncodeError, info.reason()); |
| EXPECT_EQ(ZX_ERR_BUFFER_TOO_SMALL, info.status()); |
| sync_completion_signal(&done); |
| }; |
| auto server = std::make_unique<ErrorServer>(); |
| auto server_binding = |
| fidl::BindServer(loop.dispatcher(), std::move(remote), server.get(), std::move(on_unbound)); |
| |
| // Make a synchronous call which should fail as a result of the server end closing. |
| auto result = client->TwoWay_Sync(fidl::StringView("")); |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ(ZX_ERR_PEER_CLOSED, result.status()); |
| |
| // Wait for the unbound handler to run. |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| } |
| |
| TEST(GenAPITestCase, UnbindInfoDecodeError) { |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| sync_completion_t done; |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<Example> { |
| public: |
| EventHandler(sync_completion_t& done) : done_(done) {} |
| |
| void OnEvent(fidl::WireResponse<Example::OnEvent>* event) override { |
| FAIL(); |
| sync_completion_signal(&done_); |
| } |
| |
| void Unbound(fidl::UnbindInfo info) override { |
| EXPECT_EQ(fidl::Reason::kDecodeError, info.reason()); |
| sync_completion_signal(&done_); |
| }; |
| |
| private: |
| sync_completion_t& done_; |
| }; |
| |
| fidl::Client<Example> client(std::move(local), loop.dispatcher(), |
| std::make_shared<EventHandler>(done)); |
| |
| // Set up an Example.OnEvent() message but send it without the payload. This should trigger a |
| // decoding error. |
| fidl::WireResponse<Example::OnEvent> resp{fidl::StringView("")}; |
| fidl::OwnedEncodedMessage<fidl::WireResponse<Example::OnEvent>> encoded(&resp); |
| ASSERT_TRUE(encoded.ok()); |
| auto bytes = encoded.GetOutgoingMessage().CopyBytes(); |
| ASSERT_OK(remote.channel().write(0, bytes.data(), sizeof(fidl_message_header_t), nullptr, 0)); |
| |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| } |
| |
| // After a client is unbound, no more calls can be made on that client. |
| TEST(GenAPITestCase, UnbindPreventsSubsequentCalls) { |
| // Use a server to count the number of |OneWay| calls. |
| class Server : public fidl::WireServer<Example> { |
| public: |
| Server() = default; |
| |
| void TwoWay(TwoWayRequestView request, TwoWayCompleter::Sync& completer) override { |
| ZX_PANIC("Not used in this test"); |
| } |
| |
| void OneWay(OneWayRequestView, OneWayCompleter::Sync&) override { num_one_way_.fetch_add(1); } |
| |
| int num_one_way() const { return num_one_way_.load(); } |
| |
| private: |
| std::atomic<int> num_one_way_ = 0; |
| }; |
| |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::Client<Example> client(std::move(endpoints->client), loop.dispatcher()); |
| |
| auto server = std::make_unique<Server>(); |
| auto* server_ptr = server.get(); |
| auto server_binding = |
| fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), std::move(server)); |
| |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(0, server_ptr->num_one_way()); |
| |
| ASSERT_OK(client->OneWay("foo").status()); |
| |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(1, server_ptr->num_one_way()); |
| |
| client.Unbind(); |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(1, server_ptr->num_one_way()); |
| |
| ASSERT_EQ(ZX_ERR_CANCELED, client->OneWay("foo").status()); |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(1, server_ptr->num_one_way()); |
| } |
| |
| // If writing to the channel fails, the response context ownership should be |
| // released back to the user with a call to |OnError|. |
| TEST(GenAPITestCase, ResponseContextOwnershipReleasedOnError) { |
| zx::status endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [client_end, server_end] = std::move(*endpoints); |
| { |
| zx::channel client_channel_non_writable; |
| ASSERT_OK( |
| client_end.channel().replace(ZX_RIGHT_READ | ZX_RIGHT_WAIT, &client_channel_non_writable)); |
| client_end.channel() = std::move(client_channel_non_writable); |
| } |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::Client<Example> client(std::move(client_end), loop.dispatcher()); |
| loop.StartThread("client-test"); |
| |
| class TestResponseContext final : public fidl::WireResponseContext<Example::TwoWay> { |
| public: |
| explicit TestResponseContext(sync_completion_t* error) : error_(error) {} |
| |
| void OnCanceled() override { FAIL(); } |
| |
| void OnResult(fidl::WireUnownedResult<Example::TwoWay>&& result) override { |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ(fidl::Reason::kTransportError, result.reason()); |
| EXPECT_EQ(ZX_ERR_ACCESS_DENIED, result.status()); |
| sync_completion_signal(error_); |
| } |
| |
| private: |
| sync_completion_t* error_; |
| }; |
| |
| sync_completion_t error; |
| TestResponseContext context(&error); |
| |
| fidl::Buffer<fidl::WireRequest<Example::TwoWay>> buffer; |
| fidl::Result result = client->TwoWay(buffer.view(), "foo", &context); |
| ASSERT_STATUS(ZX_ERR_ACCESS_DENIED, result.status()); |
| ASSERT_OK(sync_completion_wait(&error, ZX_TIME_INFINITE)); |
| } |
| |
| // An integration-style test that verifies that user-supplied async callbacks |
| // are not invoked when the binding is torn down by the user (i.e. explicit |
| // cancellation) instead of due to errors. |
| TEST(GenAPITestCase, SkipCallingInFlightCallbacksDuringCancellation) { |
| auto do_test = [](auto&& client_instance_indicator) { |
| using ClientType = cpp20::remove_cvref_t<decltype(client_instance_indicator)>; |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ClientType client(std::move(local), loop.dispatcher()); |
| bool destroyed = false; |
| auto callback_destruction_observer = fit::defer([&] { destroyed = true; }); |
| |
| // TODO(fxbug.dev/75324): Test overload that takes a |
| // |void(fidl::WireUnownedResult<T>&)| callback. |
| fidl::Result result = |
| client->TwoWay("foo", [observer = std::move(callback_destruction_observer)]( |
| fidl::WireResponse<Example::TwoWay>* response) { |
| ADD_FATAL_FAILURE("Should not be invoked"); |
| }); |
| // Immediately start cancellation. |
| client = {}; |
| loop.RunUntilIdle(); |
| |
| // The callback should be destroyed without being called. |
| ASSERT_TRUE(destroyed); |
| }; |
| |
| do_test(fidl::WireClient<Example>{}); |
| do_test(fidl::WireSharedClient<Example>{}); |
| } |
| |
| // The client should not notify the user of teardown completion until all |
| // up-calls to user code have finished. This is essential for a two-phase |
| // shutdown pattern to prevent use-after-free. |
| TEST(WireSharedClient, TeardownCompletesAfterUserCallbackReturns) { |
| // This invariant should hold regardless of how many threads are on the |
| // dispatcher. |
| for (int num_threads = 1; num_threads < 4; num_threads++) { |
| auto endpoints = fidl::CreateEndpoints<Example>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| for (int i = 0; i < num_threads; i++) { |
| ASSERT_OK(loop.StartThread()); |
| } |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<Example> { |
| void OnResourceEvent(fidl::WireResponse<Example::OnResourceEvent>* event) override { |
| // Signal to the test that the dispatcher thread has entered into |
| // a user callback. |
| event_ = zx::eventpair(event->h.release()); |
| event_.signal_peer(ZX_SIGNAL_NONE, ZX_USER_SIGNAL_0); |
| |
| // Block the user callback until |ZX_USER_SIGNAL_1| is observed. |
| zx_signals_t observed; |
| ASSERT_OK(event_.wait_one(ZX_USER_SIGNAL_1, zx::time::infinite(), &observed)); |
| ASSERT_EQ(ZX_USER_SIGNAL_1, observed); |
| } |
| |
| private: |
| zx::eventpair event_; |
| }; |
| |
| fidl::WireSharedClient<Example> client(std::move(local), loop.dispatcher(), |
| std::make_unique<EventHandler>()); |
| fidl::WireEventSender<Example> event_sender(std::move(remote)); |
| |
| zx::eventpair ep1, ep2; |
| ASSERT_OK(zx::eventpair::create(0, &ep1, &ep2)); |
| ASSERT_OK(event_sender.OnResourceEvent(std::move(ep1))); |
| |
| zx_signals_t observed; |
| ASSERT_OK(zx_object_wait_one(ep2.get(), ZX_USER_SIGNAL_0, ZX_TIME_INFINITE, &observed)); |
| ASSERT_EQ(ZX_USER_SIGNAL_0, observed); |
| |
| // Initiate teardown. The |EventHandler| must not be destroyed until the |
| // |OnResourceEvent| callback returns. |
| client.AsyncTeardown(); |
| ASSERT_EQ(ZX_ERR_TIMED_OUT, |
| ep2.wait_one(ZX_EVENTPAIR_PEER_CLOSED, async::Now(loop.dispatcher()) + zx::msec(250), |
| &observed)); |
| |
| ep2.signal_peer(ZX_SIGNAL_NONE, ZX_USER_SIGNAL_1); |
| ASSERT_OK(ep2.wait_one(ZX_EVENTPAIR_PEER_CLOSED, zx::time::infinite(), &observed)); |
| } |
| } |
| |
| } // namespace |