blob: 2e6334d8951cc47db4e6c4a8484efa99093ab0e1 [file] [log] [blame]
// Copyright 2023 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/fuchsia.hardware.radar/cpp/fidl.h>
#include <fidl/fuchsia.io/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/driver/runtime/testing/cpp/sync_helpers.h>
#include <lib/fdio/directory.h>
#include <lib/sync/completion.h>
#include <lib/zx/clock.h>
#include <optional>
#include <vector>
#include <zxtest/zxtest.h>
#include "../radar-proxy.h"
namespace radar {
class FakeRadarDriver : public fidl::Server<fuchsia_hardware_radar::RadarBurstReaderProvider>,
public fidl::Server<fuchsia_hardware_radar::RadarBurstReader> {
public:
explicit FakeRadarDriver(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
void Bind(fidl::ServerEnd<fuchsia_hardware_radar::RadarBurstReaderProvider> server) {
EXPECT_TRUE(fdf::RunOnDispatcherSync(dispatcher_, [&]() {
provider_binding_ = fidl::BindServer(dispatcher_, std::move(server), this);
}).is_ok());
}
void UnbindProvider() {
EXPECT_TRUE(fdf::RunOnDispatcherSync(dispatcher_, [&]() {
if (provider_binding_) {
provider_binding_->Close(ZX_ERR_PEER_CLOSED);
}
}).is_ok());
}
void UnbindReader() {
EXPECT_TRUE(fdf::RunOnDispatcherSync(dispatcher_, [&]() {
if (reader_binding_) {
reader_binding_->Close(ZX_ERR_PEER_CLOSED);
}
}).is_ok());
}
void SendBurst() {
async::PostTask(dispatcher_, [&]() {
ASSERT_TRUE(reader_binding_);
for (auto& vmo : registered_vmos_) {
if (!vmo.locked) {
vmo.locked = true;
vmo.vmo.write(&kRealRadarBurstMarker, 0, sizeof(kRealRadarBurstMarker));
const auto burst = fuchsia_hardware_radar::RadarBurstReaderOnBurstRequest::WithBurst(
{{vmo.vmo_id, zx::clock::get_monotonic().get()}});
EXPECT_TRUE(fidl::SendEvent(*reader_binding_)->OnBurst(burst).is_ok());
return;
}
}
const auto burst = fuchsia_hardware_radar::RadarBurstReaderOnBurstRequest::WithError(
fuchsia_hardware_radar::StatusCode::kOutOfVmos);
EXPECT_TRUE(fidl::SendEvent(*reader_binding_)->OnBurst(burst).is_ok());
});
}
void SendError(fuchsia_hardware_radar::StatusCode status) {
async::PostTask(dispatcher_, [&, error = status]() {
ASSERT_TRUE(reader_binding_);
const auto burst = fuchsia_hardware_radar::RadarBurstReaderOnBurstRequest::WithError(error);
EXPECT_TRUE(fidl::SendEvent(*reader_binding_)->OnBurst(burst).is_ok());
});
}
zx_status_t WaitForBurstsStopped() {
return sync_completion_wait(&bursts_stopped_, ZX_TIME_INFINITE);
}
void set_burst_size(uint32_t burst_size) {
async::PostTask(dispatcher_, [&, size = burst_size]() { burst_size_ = size; });
}
private:
// We write this marker to the first byte of each burst we send, while injection tests write any
// other value. This way clients can determine whether received bursts came from the driver or
// were injected.
static constexpr uint8_t kRealRadarBurstMarker = 0xff;
struct RegisteredVmo {
uint32_t vmo_id;
zx::vmo vmo;
bool locked = false;
};
void Connect(ConnectRequest& request, ConnectCompleter::Sync& completer) override {
reader_binding_ = fidl::BindServer(dispatcher_, std::move(request.server()), this);
completer.Reply(fit::ok());
}
void GetBurstProperties(GetBurstPropertiesCompleter::Sync& completer) override {
completer.Reply({burst_size_, zx::usec(1).to_nsecs()});
}
void RegisterVmos(RegisterVmosRequest& request, RegisterVmosCompleter::Sync& completer) override {
if (!registered_vmos_.empty()) {
// Inject an error on the second RegisterVmos() call to test radar-proxy error handling.
completer.Reply(fit::error(fuchsia_hardware_radar::StatusCode::kVmoAlreadyRegistered));
return;
}
if (request.vmo_ids().size() != request.vmos().size()) {
completer.Reply(fit::error(fuchsia_hardware_radar::StatusCode::kInvalidArgs));
return;
}
for (size_t i = 0; i < request.vmos().size(); i++) {
// Ignore normal registration errors, such as duplicate IDs.
registered_vmos_.push_back(RegisteredVmo{request.vmo_ids()[i], std::move(request.vmos()[i])});
}
completer.Reply(fit::success());
}
void UnregisterVmos(UnregisterVmosRequest& request,
UnregisterVmosCompleter::Sync& completer) override {}
void StartBursts(StartBurstsCompleter::Sync& completer) override {
sync_completion_reset(&bursts_stopped_);
}
void StopBursts(StopBurstsCompleter::Sync& completer) override {
completer.Reply();
sync_completion_signal(&bursts_stopped_);
}
void UnlockVmo(UnlockVmoRequest& request, UnlockVmoCompleter::Sync& completer) override {
for (auto& vmo : registered_vmos_) {
if (vmo.vmo_id == request.vmo_id()) {
vmo.locked = false;
return;
}
}
}
async_dispatcher_t* const dispatcher_;
std::optional<fidl::ServerBindingRef<fuchsia_hardware_radar::RadarBurstReaderProvider>>
provider_binding_;
std::optional<fidl::ServerBindingRef<fuchsia_hardware_radar::RadarBurstReader>> reader_binding_;
std::vector<RegisteredVmo> registered_vmos_;
sync_completion_t bursts_stopped_;
uint32_t burst_size_ = 12345;
};
template <typename T>
class RadarProxyTest : public zxtest::Test, public RadarDeviceConnector {
public:
RadarProxyTest()
: proxy_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
driver_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
fake_driver_(driver_loop_.dispatcher()),
dut_(proxy_loop_.dispatcher(), this) {}
void SetUp() override {
ASSERT_OK(proxy_loop_.StartThread("Radar proxy"));
ASSERT_OK(driver_loop_.StartThread("Radar driver"));
{
zx::result endpoints =
fidl::CreateEndpoints<fuchsia_hardware_radar::RadarBurstReaderProvider>();
ASSERT_TRUE(endpoints.is_ok());
dut_client_.Bind(std::move(endpoints->client));
EXPECT_TRUE(fdf::RunOnDispatcherSync(proxy_loop_.dispatcher(), [&]() {
fidl::BindServer(proxy_loop_.dispatcher(), std::move(endpoints->server), &dut_);
}).is_ok());
}
{
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_TRUE(endpoints.is_ok());
outgoing_client_ = std::move(endpoints->client);
EXPECT_TRUE(fdf::RunOnDispatcherSync(proxy_loop_.dispatcher(), [&]() {
outgoing_.emplace(proxy_loop_.dispatcher());
EXPECT_TRUE(dut_.AddProtocols(&(*outgoing_)).is_ok());
EXPECT_TRUE(outgoing_->Serve(std::move(endpoints->server)).is_ok());
}).is_ok());
}
}
void TearDown() override {
// Make sure the proxy doesn't try to reconnect after the loop has been stopped.
UnbindRadarDevice();
// Destroy the outgoing directory on the proxy thread.
EXPECT_TRUE(
fdf::RunOnDispatcherSync(proxy_loop_.dispatcher(), [&]() { outgoing_.reset(); }).is_ok());
// dut_ must outlive the loop in order to prevent FIDL callbacks from keeping dangling
// references to them.
proxy_loop_.Shutdown();
driver_loop_.Shutdown();
}
void ConnectToRadarDevice(fidl::UnownedClientEnd<fuchsia_io::Directory> dir,
const std::string& path,
ConnectDeviceCallback connect_device) override {
ConnectToFirstRadarDevice(std::move(connect_device));
}
void ConnectToFirstRadarDevice(ConnectDeviceCallback connect_device) override {
if (!provider_connect_fail_) {
zx::result endpoints =
fidl::CreateEndpoints<fuchsia_hardware_radar::RadarBurstReaderProvider>();
ASSERT_TRUE(endpoints.is_ok());
fake_driver_.Bind(std::move(endpoints->server));
// This may not succeed if we've told the radar driver to return errors for certain calls.
connect_device(std::move(endpoints->client));
}
sync_completion_signal(&device_connected_);
}
private:
async::Loop proxy_loop_;
async::Loop driver_loop_;
protected:
static fuchsia_hardware_radar::RadarBurstReaderGetBurstPropertiesResponse kInvalidProperties() {
return fuchsia_hardware_radar::RadarBurstReaderGetBurstPropertiesResponse(0, 0);
}
// Closes the RadarBurstProvider connection between radar-proxy and the radar driver, and waits
// for radar-proxy to process the epitaph. After this call, requests to radar-proxy should either
// succeed or fail depending on the value of provider_connect_fail_.
void UnbindProviderAndWaitForConnectionAttempt() {
sync_completion_reset(&device_connected_);
UnbindRadarDevice();
sync_completion_wait(&device_connected_, ZX_TIME_INFINITE);
}
// Same as above, but for RadarBurstReader.
void UnbindReaderAndWaitForConnectionAttempt() {
sync_completion_reset(&device_connected_);
fake_driver_.UnbindReader();
sync_completion_wait(&device_connected_, ZX_TIME_INFINITE);
}
void UnbindRadarDevice() {
EXPECT_TRUE(fdf::RunOnDispatcherSync(proxy_loop_.dispatcher(), [&]() {
provider_connect_fail_ = true;
}).is_ok());
fake_driver_.UnbindProvider();
}
void AddRadarDevice() {
EXPECT_TRUE(fdf::RunOnDispatcherSync(proxy_loop_.dispatcher(), [&]() {
provider_connect_fail_ = false;
dut_.DeviceAdded(kInvalidDir, "000");
}).is_ok());
}
void BindInjector(fidl::ServerEnd<fuchsia_hardware_radar::RadarBurstInjector> server_end) {
constexpr char kInjectorProtocolPath[] = "/svc/fuchsia.hardware.radar.RadarBurstInjector";
EXPECT_OK(fdio_service_connect_at(outgoing_client_.channel().get(), kInjectorProtocolPath,
server_end.TakeChannel().release()));
}
// Visible for testing.
FakeRadarDriver fake_driver_;
fidl::SyncClient<fuchsia_hardware_radar::RadarBurstReaderProvider> dut_client_;
private:
static constexpr fidl::UnownedClientEnd<fuchsia_io::Directory> kInvalidDir{FIDL_HANDLE_INVALID};
T dut_;
bool provider_connect_fail_ = false;
sync_completion_t device_connected_;
std::optional<component::OutgoingDirectory> outgoing_;
fidl::ClientEnd<fuchsia_io::Directory> outgoing_client_;
};
} // namespace radar