blob: 4168ce1d1acc073ca968b4d78e15da2cd9def84a [file]
// Copyright 2026 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.driver.lab/cpp/wire.h>
#include <lib/driver_lab/driver_lab.h>
#include <lib/driver_lab/driver_lab_c.h>
#include <lib/zx/vmo.h>
#include <zircon/time.h>
#include <atomic>
#include <string>
#include <vector>
#include <gtest/gtest.h>
namespace driver_lab {
namespace {
namespace flab = fuchsia_driver_lab;
TEST(DriverLabCppTest, DescribeReadWriteStateVmoBankAndAuditOverFidl) {
// 1. Create a simulated MMIO VMO with live register values:
// 0x00 = DEVICE_ID (0xD1A60001)
// 0x04 = STATUS (0x00000007)
// 0x08 = CTRL (0x00000000, writable)
// 0x40 = FIFO_DATA (0xDEADBEEF, hard-denied clear-on-read register)
zx::vmo mmio_vmo;
ASSERT_EQ(zx::vmo::create(4096, 0, &mmio_vmo), ZX_OK);
const uint32_t kDeviceId = 0xD1A60001u;
const uint32_t kStatusVal = 0x00000007u;
const uint32_t kFifoVal = 0xDEADBEEFu;
ASSERT_EQ(mmio_vmo.write(&kDeviceId, 0x00, sizeof(kDeviceId)), ZX_OK);
ASSERT_EQ(mmio_vmo.write(&kStatusVal, 0x04, sizeof(kStatusVal)), ZX_OK);
ASSERT_EQ(mmio_vmo.write(&kFifoVal, 0x40, sizeof(kFifoVal)), ZX_OK);
// 2. Create a StateVmoBank registered as the global C state/knob bank:
// 0x00 = C++ telemetry state word (0x11223344)
// 0x04 = Host-writable fault-injection knob (initial 100)
// 0x08 = Legacy C telemetry state word (0x55667788)
auto bank_res = StateVmoBank::Create(4096, /*register_global=*/true);
ASSERT_TRUE(bank_res.is_ok()) << bank_res.status_string();
StateVmoBank bank = std::move(*bank_res);
bank.SetState32(0x00, 0x11223344u);
bank.SetKnob32(0x04, 100u);
driver_lab_global_set_state_u32(0x08, 0x55667788u);
EXPECT_EQ(bank.GetState32(0x00), 0x11223344u);
EXPECT_EQ(bank.GetState32(0x08), 0x55667788u);
EXPECT_EQ(driver_lab_global_get_knob_u32(0x04, 0u), 100u);
// 3. Configure Builder with MMIO VMO, StateVmoBank, interrupt tap, and quiesce hook.
std::atomic<bool> quiesce_callback_state{false};
std::atomic<int> quiesce_transitions{0};
Builder builder("cpp-test-node");
builder.SetEnabled(true);
auto regs_id_res = builder.AddMmioVmo("regs", mmio_vmo.borrow(), 0, 0x100);
ASSERT_TRUE(regs_id_res.is_ok()) << regs_id_res.status_string();
const uint32_t regs_id = *regs_id_res;
EXPECT_EQ(regs_id, 0u);
builder.SetWritableRegisters(regs_id, {0x08});
builder.SetHardDeniedRanges(regs_id, {{0x40, 0x44}});
auto state_id_res = builder.AddStateVmoBank("state", bank, {0x04});
ASSERT_TRUE(state_id_res.is_ok()) << state_id_res.status_string();
const uint32_t state_id = *state_id_res;
EXPECT_EQ(state_id, 1u);
const uint32_t irq_id = builder.AddInterrupt("irq0");
EXPECT_EQ(irq_id, 2u);
builder.SetQuiesceHook([&](bool paused) {
quiesce_callback_state.store(paused);
quiesce_transitions.fetch_add(1);
});
auto server_res = builder.Build();
ASSERT_TRUE(server_res.is_ok()) << server_res.status_string();
EmbeddedServer server = std::move(*server_res);
EXPECT_TRUE(server.is_enabled());
EXPECT_FALSE(server.is_quiesced());
// 4. Connect a synchronous FIDL Proxy client.
auto proxy_endpoints = fidl::Endpoints<flab::Proxy>::Create();
ASSERT_TRUE(server.ServeProxy(std::move(proxy_endpoints.server)).is_ok());
fidl::WireSyncClient<flab::Proxy> proxy_client(std::move(proxy_endpoints.client));
// 5. Verify Describe() returns all resources and valid SHA-256 digests.
auto desc_res = proxy_client->Describe();
ASSERT_TRUE(desc_res.ok()) << desc_res.FormatDescription();
const auto& desc = *desc_res;
ASSERT_TRUE(desc.has_protocol_major());
EXPECT_EQ(desc.protocol_major(), 1u);
ASSERT_TRUE(desc.has_boot_id());
EXPECT_FALSE(desc.boot_id().empty());
ASSERT_TRUE(desc.has_resource_digest());
EXPECT_TRUE(std::string_view(desc.resource_digest().get()).starts_with("sha256:"));
ASSERT_TRUE(desc.has_policy_digest());
EXPECT_TRUE(std::string_view(desc.policy_digest().get()).starts_with("sha256:"));
ASSERT_TRUE(desc.has_resources());
ASSERT_EQ(desc.resources().size(), 3u);
EXPECT_EQ(desc.resources()[0].name().get(), "regs");
EXPECT_EQ(desc.resources()[1].name().get(), "state");
EXPECT_EQ(desc.resources()[2].name().get(), "irq0");
// 6. Open a Mutating session authorizing reads on regs/state and writes on knob 0x04.
fidl::Arena arena;
auto context = flab::wire::RunContext::Builder(arena)
.run_id("run-cs35")
.case_id("case-cs35")
.plan_digest("sha256:test")
.host_tool_version("0.1.0")
.Build();
auto expectations = flab::wire::Expectations::Builder(arena)
.boot_id(desc.boot_id())
.proxy_generation(desc.proxy_generation())
.resource_digest(desc.resource_digest())
.policy_digest(desc.policy_digest())
.Build();
std::vector<flab::wire::AccessRule> rules = {
{
.resource = regs_id,
.offset = 0x00,
.width = 4,
.class_ = flab::wire::AccessClass::kReadOnce,
},
{
.resource = regs_id,
.offset = 0x08,
.width = 4,
.class_ = flab::wire::AccessClass::kWrite,
},
{
.resource = state_id,
.offset = 0x00,
.width = 4,
.class_ = flab::wire::AccessClass::kReadOnce,
},
{
.resource = state_id,
.offset = 0x04,
.width = 4,
.class_ = flab::wire::AccessClass::kWrite,
},
{
.resource = state_id,
.offset = 0x08,
.width = 4,
.class_ = flab::wire::AccessClass::kReadOnce,
},
{
.resource = irq_id,
.offset = 0,
.width = 0,
.class_ = flab::wire::AccessClass::kInterrupt,
},
};
auto session_endpoints = fidl::Endpoints<flab::Session>::Create();
auto open_res =
proxy_client->OpenSession(context, flab::wire::SessionMode::kMutating, expectations,
fidl::VectorView<flab::wire::AccessRule>::FromExternal(rules),
std::move(session_endpoints.server));
ASSERT_TRUE(open_res.ok()) << open_res.FormatDescription();
ASSERT_TRUE(open_res->is_ok());
// Verify the mutating session engaged the quiesce hook.
EXPECT_TRUE(server.is_quiesced());
EXPECT_TRUE(quiesce_callback_state.load());
EXPECT_EQ(quiesce_transitions.load(), 1);
fidl::WireSyncClient<flab::Session> session_client(std::move(session_endpoints.client));
// 7. Read32 on MMIO VMO and StateVmoBank.
{
auto read_res = session_client->Read32(regs_id, 0x00);
ASSERT_TRUE(read_res.ok()) << read_res.FormatDescription();
ASSERT_TRUE(read_res->is_ok());
EXPECT_EQ(read_res->value()->value, 0xD1A60001u);
}
{
auto read_res = session_client->Read32(state_id, 0x00);
ASSERT_TRUE(read_res.ok()) << read_res.FormatDescription();
ASSERT_TRUE(read_res->is_ok());
EXPECT_EQ(read_res->value()->value, 0x11223344u);
}
{
auto read_res = session_client->Read32(state_id, 0x08);
ASSERT_TRUE(read_res.ok()) << read_res.FormatDescription();
ASSERT_TRUE(read_res->is_ok());
EXPECT_EQ(read_res->value()->value, 0x55667788u);
}
// 8. Write32 on the writable knob at 0x04 in StateVmoBank and verify C++/C visibility.
{
auto write_res =
session_client->Write32(state_id, 0x04, 0xCAFEBABEu, 0xFFFFFFFFu, nullptr, false);
ASSERT_TRUE(write_res.ok()) << write_res.FormatDescription();
ASSERT_TRUE(write_res->is_ok());
EXPECT_EQ(bank.GetKnob32(0x04), 0xCAFEBABEu);
EXPECT_EQ(driver_lab_global_get_knob_u32(0x04, 0u), 0xCAFEBABEu);
}
// 9. Tap interrupt from C++ and verify WaitForInterrupt receives it.
server.NotifyInterrupt(irq_id);
{
auto irq_res = session_client->WaitForInterrupt(irq_id, 0, ZX_SEC(1));
ASSERT_TRUE(irq_res.ok()) << irq_res.FormatDescription();
ASSERT_TRUE(irq_res->is_ok());
EXPECT_EQ(irq_res->value()->sequence, 1u);
}
// 10. ReadAudit() and confirm allowed operations are recorded.
{
auto audit_res = session_client->ReadAudit(0, 64);
ASSERT_TRUE(audit_res.ok()) << audit_res.FormatDescription();
const auto& entries = audit_res->entries;
EXPECT_GE(entries.size(), 6u);
bool saw_write = false;
for (const auto& entry : entries) {
if (entry.has_operation() && entry.operation().get() == "write32") {
saw_write = true;
ASSERT_TRUE(entry.has_decision());
EXPECT_EQ(entry.decision(), flab::wire::AuditDecision::kAllowed);
}
}
EXPECT_TRUE(saw_write);
}
}
TEST(DriverLabCppTest, RejectsHardDeniedRangeInSessionAllowlist) {
zx::vmo mmio_vmo;
ASSERT_EQ(zx::vmo::create(4096, 0, &mmio_vmo), ZX_OK);
Builder builder("cpp-deny-test");
builder.SetEnabled(true);
auto regs_id_res = builder.AddMmioVmo("regs", mmio_vmo.borrow(), 0, 0x100);
ASSERT_TRUE(regs_id_res.is_ok());
builder.SetHardDeniedRanges(*regs_id_res, {{0x40, 0x44}});
auto server_res = builder.Build();
ASSERT_TRUE(server_res.is_ok());
EmbeddedServer server = std::move(*server_res);
auto proxy_endpoints = fidl::Endpoints<flab::Proxy>::Create();
ASSERT_TRUE(server.ServeProxy(std::move(proxy_endpoints.server)).is_ok());
fidl::WireSyncClient<flab::Proxy> proxy_client(std::move(proxy_endpoints.client));
auto desc_res = proxy_client->Describe();
ASSERT_TRUE(desc_res.ok());
const auto& desc = *desc_res;
fidl::Arena arena;
auto context = flab::wire::RunContext::Builder(arena)
.run_id("run-deny")
.case_id("case-deny")
.plan_digest("sha256:deny")
.host_tool_version("0.1.0")
.Build();
auto expectations = flab::wire::Expectations::Builder(arena)
.boot_id(desc.boot_id())
.proxy_generation(desc.proxy_generation())
.resource_digest(desc.resource_digest())
.policy_digest(desc.policy_digest())
.Build();
// Requesting ReadOnce on hard-denied offset 0x40 must fail with kRejectedAllowlist.
std::vector<flab::wire::AccessRule> rules = {
{
.resource = *regs_id_res,
.offset = 0x40,
.width = 4,
.class_ = flab::wire::AccessClass::kReadOnce,
},
};
auto session_endpoints = fidl::Endpoints<flab::Session>::Create();
auto open_res =
proxy_client->OpenSession(context, flab::wire::SessionMode::kReadOnly, expectations,
fidl::VectorView<flab::wire::AccessRule>::FromExternal(rules),
std::move(session_endpoints.server));
ASSERT_TRUE(open_res.ok()) << open_res.FormatDescription();
ASSERT_TRUE(open_res->is_error());
EXPECT_EQ(open_res->error_value(), flab::wire::OpenSessionError::kRejectedAllowlist);
}
TEST(DriverLabCppTest, MaskedWritableRegisterLimitsWriteMask) {
// CTRL at 0x10 holds a run/stop bit (31) next to a host-tunable field in
// bits [11:8].
constexpr uint64_t kCtrl = 0x10;
constexpr uint32_t kTunableField = 0x0000'0F00u;
constexpr uint32_t kCtrlInitial = 0x8000'0001u;
zx::vmo mmio_vmo;
ASSERT_EQ(zx::vmo::create(4096, 0, &mmio_vmo), ZX_OK);
ASSERT_EQ(mmio_vmo.write(&kCtrlInitial, kCtrl, sizeof(kCtrlInitial)), ZX_OK);
Builder builder("cpp-masked-test");
builder.SetEnabled(true);
auto regs_id_res = builder.AddMmioVmo("regs", mmio_vmo.borrow(), 0, 0x100);
ASSERT_TRUE(regs_id_res.is_ok());
const uint32_t regs_id = *regs_id_res;
builder.SetMaskedWritableRegisters(regs_id, {{.offset = kCtrl, .allow_mask = kTunableField}});
auto server_res = builder.Build();
ASSERT_TRUE(server_res.is_ok());
EmbeddedServer server = std::move(*server_res);
auto proxy_endpoints = fidl::Endpoints<flab::Proxy>::Create();
ASSERT_TRUE(server.ServeProxy(std::move(proxy_endpoints.server)).is_ok());
fidl::WireSyncClient<flab::Proxy> proxy_client(std::move(proxy_endpoints.client));
auto desc_res = proxy_client->Describe();
ASSERT_TRUE(desc_res.ok());
const auto& desc = *desc_res;
fidl::Arena arena;
auto context = flab::wire::RunContext::Builder(arena)
.run_id("run-masked")
.case_id("case-masked")
.plan_digest("sha256:masked")
.host_tool_version("0.1.0")
.Build();
auto expectations = flab::wire::Expectations::Builder(arena)
.boot_id(desc.boot_id())
.proxy_generation(desc.proxy_generation())
.resource_digest(desc.resource_digest())
.policy_digest(desc.policy_digest())
.Build();
std::vector<flab::wire::AccessRule> rules = {
{
.resource = regs_id,
.offset = kCtrl,
.width = 4,
.class_ = flab::wire::AccessClass::kWrite,
},
};
auto session_endpoints = fidl::Endpoints<flab::Session>::Create();
auto open_res =
proxy_client->OpenSession(context, flab::wire::SessionMode::kMutating, expectations,
fidl::VectorView<flab::wire::AccessRule>::FromExternal(rules),
std::move(session_endpoints.server));
ASSERT_TRUE(open_res.ok()) << open_res.FormatDescription();
ASSERT_TRUE(open_res->is_ok());
fidl::WireSyncClient<flab::Session> session_client(std::move(session_endpoints.client));
auto read_ctrl = [&]() {
uint32_t value = 0;
EXPECT_EQ(mmio_vmo.read(&value, kCtrl, sizeof(value)), ZX_OK);
return value;
};
// A write within the allowed field is applied as read-modify-write and
// preserves the other bits.
{
auto write_res =
session_client->Write32(regs_id, kCtrl, 0x0000'0500u, kTunableField, nullptr, false);
ASSERT_TRUE(write_res.ok()) << write_res.FormatDescription();
ASSERT_TRUE(write_res->is_ok());
EXPECT_EQ(read_ctrl(), 0x8000'0501u);
}
// Writes that touch bits outside the allowed field are denied, including a
// full-width write, and leave the register unchanged.
for (uint32_t write_mask : {0x8000'0000u, kTunableField | 0x1u, 0xFFFF'FFFFu}) {
auto write_res = session_client->Write32(regs_id, kCtrl, 0u, write_mask, nullptr, false);
ASSERT_TRUE(write_res.ok()) << write_res.FormatDescription();
ASSERT_TRUE(write_res->is_error()) << "write_mask=" << write_mask;
EXPECT_EQ(write_res->error_value(), flab::wire::OperationError::kWriteNotPermitted);
EXPECT_EQ(read_ctrl(), 0x8000'0501u);
}
}
} // namespace
} // namespace driver_lab