blob: 33194ffb8ca3b8e059840bd4cac1c8e85c4a6474 [file] [log] [blame]
// Copyright 2019 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/fidl-async/cpp/bind.h>
#include <lib/service/llcpp/service.h>
#include <lib/sync/completion.h>
#include <lib/zx/channel.h>
#include <lib/zx/time.h>
#include <fidl/service/test/llcpp/fidl.h>
#include <zxtest/zxtest.h>
#include "src/lib/storage/vfs/cpp/pseudo_dir.h"
#include "src/lib/storage/vfs/cpp/service.h"
#include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
namespace {
using Echo = ::fidl_service_test::Echo;
using EchoService = ::fidl_service_test::EchoService;
class EchoCommon : public fidl::WireInterface<Echo> {
public:
explicit EchoCommon(const char* prefix) : prefix_(prefix) {}
zx_status_t Connect(async_dispatcher_t* dispatcher, zx::channel request) {
return fidl::BindSingleInFlightOnly(dispatcher, std::move(request), this);
}
void EchoString(fidl::StringView input, EchoStringCompleter::Sync& completer) override {
std::string reply = prefix_ + ": " + std::string(input.data(), input.size());
completer.Reply(fidl::StringView::FromExternal(reply));
}
private:
std::string prefix_;
};
class MockEchoService {
public:
static constexpr const char* kName = EchoService::Name;
// Build a VFS that looks like:
//
// fidl.service.test.EchoService/
// default/
// foo (Echo)
// bar (Echo)
// other/
// foo (Echo)
// bar (Echo)
//
explicit MockEchoService(async_dispatcher_t* dispatcher)
: dispatcher_(dispatcher), vfs_(dispatcher) {
auto default_member_foo = fbl::MakeRefCounted<fs::Service>([this](zx::channel request) {
return default_foo_.Connect(dispatcher_, std::move(request));
});
auto default_member_bar = fbl::MakeRefCounted<fs::Service>([this](zx::channel request) {
return default_bar_.Connect(dispatcher_, std::move(request));
});
auto default_instance = fbl::MakeRefCounted<fs::PseudoDir>();
default_instance->AddEntry("foo", std::move(default_member_foo));
default_instance->AddEntry("bar", std::move(default_member_bar));
auto other_member_foo = fbl::MakeRefCounted<fs::Service>([this](zx::channel request) {
return other_foo_.Connect(dispatcher_, std::move(request));
});
auto other_member_bar = fbl::MakeRefCounted<fs::Service>([this](zx::channel request) {
return other_bar_.Connect(dispatcher_, std::move(request));
});
auto other_instance = fbl::MakeRefCounted<fs::PseudoDir>();
other_instance->AddEntry("foo", std::move(other_member_foo));
other_instance->AddEntry("bar", std::move(other_member_bar));
auto service = fbl::MakeRefCounted<fs::PseudoDir>();
service->AddEntry("default", std::move(default_instance));
service->AddEntry("other", std::move(other_instance));
auto root_dir = fbl::MakeRefCounted<fs::PseudoDir>();
root_dir->AddEntry(kName, std::move(service));
auto svc_remote = fidl::CreateEndpoints(&svc_local_);
ASSERT_OK(svc_remote.status_value());
vfs_.ServeDirectory(root_dir, std::move(*svc_remote));
}
fidl::UnownedClientEnd<fuchsia_io::Directory> svc() { return svc_local_; }
private:
async_dispatcher_t* dispatcher_;
fs::SynchronousVfs vfs_;
EchoCommon default_foo_{"default-foo"};
EchoCommon default_bar_{"default-bar"};
EchoCommon other_foo_{"other-foo"};
EchoCommon other_bar_{"other-bar"};
fidl::ClientEnd<fuchsia_io::Directory> svc_local_;
};
} // namespace
class ClientTest : public zxtest::Test {
protected:
ClientTest()
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
echo_service_(loop_.dispatcher()),
svc_(echo_service_.svc()) {}
void SetUp() override { loop_.StartThread("client-test-loop"); }
void TearDown() override { loop_.Shutdown(); }
async::Loop loop_;
MockEchoService echo_service_;
fidl::UnownedClientEnd<fuchsia_io::Directory> svc_;
};
TEST_F(ClientTest, ConnectsToDefault) {
zx::status<EchoService::ServiceClient> open_result = llcpp::sys::OpenServiceAt<EchoService>(svc_);
ASSERT_TRUE(open_result.is_ok());
EchoService::ServiceClient service = std::move(open_result.value());
// Connect to the member 'foo'.
zx::status<fidl::ClientEnd<Echo>> connect_result = service.connect_foo();
ASSERT_TRUE(connect_result.is_ok());
fidl::WireSyncClient<Echo> client = fidl::BindSyncClient(std::move(connect_result.value()));
fidl::WireResult<Echo::EchoString> echo_result = client.EchoString(fidl::StringView("hello"));
ASSERT_TRUE(echo_result.ok());
auto response = echo_result.Unwrap();
std::string result_string(response->response.data(), response->response.size());
ASSERT_EQ(result_string, "default-foo: hello");
}
TEST_F(ClientTest, ConnectsToOther) {
zx::status<EchoService::ServiceClient> open_result =
llcpp::sys::OpenServiceAt<EchoService>(svc_, "other");
ASSERT_TRUE(open_result.is_ok());
EchoService::ServiceClient service = std::move(open_result.value());
// Connect to the member 'bar'.
zx::status<fidl::ClientEnd<Echo>> connect_result = service.connect_bar();
ASSERT_TRUE(connect_result.is_ok());
fidl::WireSyncClient<Echo> client = fidl::BindSyncClient(std::move(connect_result.value()));
fidl::WireResult<Echo::EchoString> echo_result = client.EchoString(fidl::StringView("hello"));
ASSERT_TRUE(echo_result.ok());
auto response = echo_result.Unwrap();
std::string result_string(response->response.data(), response->response.size());
ASSERT_EQ(result_string, "other-bar: hello");
}
TEST_F(ClientTest, FilePathTooLong) {
std::string illegal_path;
illegal_path.assign(256, 'a');
// Use an instance name that is too long.
zx::status<EchoService::ServiceClient> open_result =
llcpp::sys::OpenServiceAt<EchoService>(svc_, illegal_path);
ASSERT_TRUE(open_result.is_error());
ASSERT_EQ(open_result.status_value(), ZX_ERR_INVALID_ARGS);
// Use a service name that is too long.
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
ASSERT_EQ(llcpp::sys::OpenNamedServiceAt(svc_, illegal_path, "default", std::move(remote))
.status_value(),
ZX_ERR_INVALID_ARGS);
}
//
// Tests for connecting to singleton FIDL services (`/svc/MyProtocolName` style).
//
struct MockProtocol {};
template <>
struct ::fidl::internal::ProtocolDetails<MockProtocol> {
static constexpr char DiscoverableName[] = "mock";
};
// Test compile time path concatenation.
TEST(SingletonService, DefaultPath) {
constexpr auto path = ::fidl::DiscoverableProtocolDefaultPath<MockProtocol>;
ASSERT_STR_EQ(path, "/svc/mock", "protocol path should be /svc/mock");
}
// Using a local filesystem, test that |service::ConnectAt| successfully sends
// an open request using the path |fidl::DiscoverableProtocolName<MockProtocol>|,
// when connecting to the |MockProtocol| service.
TEST(SingletonService, ConnectAt) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
fs::SynchronousVfs vfs(loop.dispatcher());
// Set up the service directory with one fake protocol.
sync_completion_t connected;
auto protocol = fbl::MakeRefCounted<fs::Service>([&connected](zx::channel request) {
sync_completion_signal(&connected);
// Implicitly drop |request|.
return ZX_OK;
});
auto root_dir = fbl::MakeRefCounted<fs::PseudoDir>();
root_dir->AddEntry(fidl::DiscoverableProtocolName<MockProtocol>, std::move(protocol));
auto directory = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(directory.status_value());
ASSERT_OK(vfs.ServeDirectory(root_dir, std::move(directory->server)));
loop.StartThread("SingletonService/ConnectAt");
// Test connecting to that protocol.
auto client_end = service::ConnectAt<MockProtocol>(directory->client);
ASSERT_OK(client_end.status_value());
ASSERT_OK(sync_completion_wait(&connected, zx::duration::infinite().get()));
// Test that the request is dropped by the server.
ASSERT_OK(client_end->channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), nullptr));
loop.Shutdown();
}
//
// Tests for cloning |fuchsia.io/Node|-like services.
//
TEST_F(ClientTest, CloneServiceDirectory) {
auto svc_clone = service::Clone(svc_);
ASSERT_OK(svc_clone.status_value());
static_assert(
std::is_same_v<decltype(svc_clone.value()), fidl::ClientEnd<fuchsia_io::Directory>&>);
ASSERT_NE(svc_.channel(), svc_clone->channel().get());
// Test that we can connect to services in the |svc_clone| directory.
// Refer to |MockEchoService| for the directory layout.
auto client_end = service::ConnectAt<Echo>(
*svc_clone, (std::string(EchoService::Name) + "/default/foo").c_str());
ASSERT_OK(client_end.status_value());
auto echo = fidl::BindSyncClient(std::move(*client_end));
auto result = echo.EchoString("foo");
ASSERT_OK(result.status());
ASSERT_STR_EQ(std::string(result->response.data(), result->response.size()).c_str(),
"default-foo: foo");
}
TEST(CloneService, Error) {
auto bad_endpoint = fidl::CreateEndpoints<fuchsia_io::Directory>();
bad_endpoint->server.reset();
auto failure = service::Clone(bad_endpoint->client);
ASSERT_EQ(ZX_ERR_PEER_CLOSED, failure.status_value());
auto invalid = service::MaybeClone(bad_endpoint->client);
ASSERT_FALSE(invalid.is_valid());
}