blob: 9dbf650d4dad52cb0e14419cef526fd49b367535 [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 <fuchsia/testing/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/port.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include <thread>
namespace fake_clock = llcpp::fuchsia::testing;
namespace {
zx::unowned_channel GetService() {
static std::once_flag svc_connect_once;
static zx::channel fake_clock;
std::call_once(svc_connect_once, []() {
if (!fake_clock.is_valid()) {
zx::channel req;
if (zx::channel::create(0, &fake_clock, &req) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create channel to FakeClock service";
fake_clock.reset();
return;
}
if (fdio_service_connect("/svc/fuchsia.testing.FakeClock", req.release()) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to connect to fuchsia.testing.FakeClock service";
fake_clock.reset();
return;
}
}
});
return zx::unowned_channel(fake_clock);
}
zx::eventpair MakeEvent(zx_time_t deadline) {
zx::eventpair l, r;
ZX_ASSERT(zx::eventpair::create(0, &l, &r) == ZX_OK);
ZX_ASSERT(fake_clock::FakeClock::Call::RegisterEvent(GetService(), std::move(r), deadline).ok());
return l;
}
} // namespace
__EXPORT zx_status_t zx_futex_wait(const zx_futex_t* value_ptr, zx_futex_t current_value,
zx_handle_t new_futex_owner, zx_time_t deadline) {
ZX_ASSERT_MSG(deadline == ZX_TIME_INFINITE,
"zx_futex_wait with deadline is currently supported by FakeClock library");
return _zx_futex_wait(value_ptr, current_value, new_futex_owner, deadline);
}
__EXPORT zx_status_t zx_channel_call(zx_handle_t handle, uint32_t options, zx_time_t deadline,
const zx_channel_call_args_t* args, uint32_t* actual_bytes,
uint32_t* actual_handles) {
// TODO(brunodalbo) There may be a way to get channel_call working if we create a temporary
// channel and an auxiliary thread, but looks like most channel_call call sites don't define
// deadlines.
ZX_ASSERT_MSG(deadline == ZX_TIME_INFINITE,
"zx_channel_call with deadline is not yet supported by FakeClock library");
return _zx_channel_call(handle, options, deadline, args, actual_bytes, actual_handles);
}
__EXPORT zx_time_t zx_clock_get_monotonic() {
auto result = fake_clock::FakeClock::Call::Get(GetService());
ZX_ASSERT(result.ok());
return result.value().time;
}
__EXPORT zx_status_t zx_clock_get(zx_clock_t clock, zx_time_t* out) {
if (clock == ZX_CLOCK_MONOTONIC) {
*out = zx_clock_get_monotonic();
return ZX_OK;
} else {
return _zx_clock_get(clock, out);
}
}
__EXPORT zx_time_t zx_deadline_after(zx_duration_t duration) {
return zx_time_add_duration(zx_clock_get_monotonic(), duration);
}
__EXPORT zx_status_t zx_nanosleep(zx_time_t deadline) {
auto e = MakeEvent(deadline);
ZX_ASSERT(_zx_object_wait_one(e.get(), ZX_EVENTPAIR_SIGNALED, ZX_TIME_INFINITE, nullptr) ==
ZX_OK);
return ZX_OK;
}
// wait_one is implemented by making it a wait_many on an infinite deadline with two items: one is
// the original handle+signals, the other is the eventpair created from the fake-clock service.
__EXPORT zx_status_t zx_object_wait_one(zx_handle_t handle, zx_signals_t signals,
zx_time_t deadline, zx_signals_t* observed) {
if (deadline == ZX_TIME_INFINITE || deadline == 0) {
return _zx_object_wait_one(handle, signals, deadline, observed);
}
auto e = MakeEvent(deadline);
zx_wait_item_t items[] = {{.handle = e.get(), .waitfor = ZX_EVENTPAIR_SIGNALED, .pending = 0},
{.handle = handle, .waitfor = signals, .pending = 0}};
auto status = _zx_object_wait_many(items, 2, ZX_TIME_INFINITE);
if (observed) {
*observed = items[1].pending;
}
if (status != ZX_OK || (items[0].pending & ZX_EVENTPAIR_SIGNALED) == 0) {
return status;
} else {
return ZX_ERR_TIMED_OUT;
}
}
// wait_many is implemented by adding an extra eventpair handle extracted from fake-clock to the
// wait list, and changing the deadline to infinite. If the number of items on the wait is already
// ZX_WAIT_MANY_MAX_ITEMS (meaning we can't add an extra item), we create a port instead and
// register all the wait items to it.
__EXPORT zx_status_t zx_object_wait_many(zx_wait_item_t* items, size_t num_items,
zx_time_t deadline) {
if (deadline == ZX_TIME_INFINITE || deadline == 0 || num_items > ZX_WAIT_MANY_MAX_ITEMS) {
return _zx_object_wait_many(items, num_items, deadline);
} else if (num_items == ZX_WAIT_MANY_MAX_ITEMS) {
// can't add a new item, we need to build a port and wait on it.
zx::port port;
ZX_ASSERT(zx::port::create(0, &port) == ZX_OK);
zx_status_t status;
for (size_t i = 0; i < num_items; i++) {
if ((status = zx_object_wait_async(items[i].handle, port.get(), i, items[i].waitfor, 0)) !=
ZX_OK) {
return status;
}
}
auto event = MakeEvent(deadline);
ZX_ASSERT(zx_object_wait_async(event.get(), port.get(), num_items, ZX_EVENTPAIR_SIGNALED, 0) ==
ZX_OK);
auto update_item = [&items, num_items](const zx_port_packet& packet) {
if (packet.key == num_items) {
if (packet.signal.observed & ZX_EVENTPAIR_SIGNALED) {
return true;
}
} else {
auto* item = &items[packet.key];
item->pending = packet.signal.observed;
}
return false;
};
zx_port_packet_t packet;
if ((status = port.wait(zx::time::infinite(), &packet)) != ZX_OK) {
return status;
}
// update_item will return true if the first packet out of the port is a timeout.
if (update_item(packet)) {
return ZX_ERR_TIMED_OUT;
}
// many things may have happened at once, how we just keep polling the port with a zero deadline
// and updating the items
while (port.wait(zx::time(0), &packet) == ZX_OK) {
if (update_item(packet)) {
break;
}
}
return ZX_OK;
} else {
// we can just add an extra item, but we'll need to copy all the wait items
zx_wait_item_t tmp[ZX_WAIT_MANY_MAX_ITEMS];
memcpy(tmp, items, num_items * sizeof(zx_wait_item_t));
auto event = MakeEvent(deadline);
tmp[num_items].pending = 0;
tmp[num_items].waitfor = ZX_EVENTPAIR_SIGNALED;
tmp[num_items].handle = event.get();
auto status = _zx_object_wait_many(tmp, num_items + 1, ZX_TIME_INFINITE);
// copy everything back:
memcpy(items, tmp, num_items * sizeof(zx_wait_item_t));
if (status != ZX_OK || (tmp[num_items].pending & ZX_EVENTPAIR_SIGNALED) == 0) {
return status;
} else {
return ZX_ERR_TIMED_OUT;
}
}
}
// port_wait adds an extra fake-clock eventpair handle to the port and changes the deadline to
// ZX_TIME_INFINITE.
__EXPORT zx_status_t zx_port_wait(zx_handle_t handle, zx_time_t deadline,
zx_port_packet_t* packet) {
if (deadline == ZX_TIME_INFINITE) {
return _zx_port_wait(handle, deadline, packet);
}
auto event = MakeEvent(deadline);
uint64_t key = 0xFACEFACE00000000 | event.get();
ZX_ASSERT(zx_object_wait_async(event.get(), handle, key, ZX_EVENTPAIR_SIGNALED, 0) == ZX_OK);
zx_port_packet_t tmp;
auto status = _zx_port_wait(handle, ZX_TIME_INFINITE, &tmp);
// always cancel the wait in case it wasn't a timeout
zx_port_cancel(handle, event.get(), key);
if (status != ZX_OK) {
return status;
} else if (tmp.type == ZX_PKT_TYPE_SIGNAL_ONE && tmp.key == key &&
tmp.signal.observed == ZX_EVENTPAIR_SIGNALED) {
return ZX_ERR_TIMED_OUT;
} else {
*packet = tmp;
return ZX_OK;
}
}
// timer_create changes the type of returned handle from an actual timer to one side of an eventpair
// created by fake-clock. It relies on the fact that ZX_EVENTPAIR_SIGNALED is the same bit as
// ZX_TIMER_SIGNALED, meaning unless clients are inspecting the handle type, they shouldn't be able
// to tell the difference.
__EXPORT zx_status_t zx_timer_create(uint32_t options, zx_clock_t clock_id, zx_handle_t* out) {
// We're replacing a timer with an event, and shamelessly using the fact that
// ZX_EVENTPAIR_SIGNALED and ZX_TIMER_SIGNAL collide, this assertion protects that assumption more
// strongly.
static_assert(ZX_EVENTPAIR_SIGNALED == ZX_TIMER_SIGNALED);
if (clock_id != ZX_CLOCK_MONOTONIC) {
// NOTE: _zx_timer_create will just fail according to the docs.
return _zx_timer_create(options, clock_id, out);
}
// Create an event with infinite deadline and return that instead of a timer handle
*out = MakeEvent(ZX_TIME_INFINITE).release();
return ZX_OK;
}
__EXPORT zx_status_t zx_timer_set(zx_handle_t handle, zx_time_t deadline, zx_duration_t slack) {
zx::eventpair e;
zx_status_t status;
if ((status = zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, e.reset_and_get_address())) !=
ZX_OK) {
return status;
}
// reschedule the event with the fake clock service:
ZX_ASSERT(
fake_clock::FakeClock::Call::RescheduleEvent(GetService(), std::move(e), deadline).ok());
return ZX_OK;
}
__EXPORT zx_status_t zx_timer_cancel(zx_handle_t handle) {
zx::eventpair e;
zx_status_t status;
if ((status = zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, e.reset_and_get_address())) !=
ZX_OK) {
return status;
}
ZX_ASSERT(fake_clock::FakeClock::Call::CancelEvent(GetService(), std::move(e)).ok());
return ZX_OK;
}