119 lines
4.2 KiB
C++
119 lines
4.2 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
|
//
|
|
// UIProcess.cpp — the UI process side. Drives the Cap'n Proto RPC channel,
|
|
// sends Parse requests, and measures round-trip latency. The UI does no
|
|
// untrusted parsing; it validates the document process's responses (ADR-0004:
|
|
// bidirectional trust boundary).
|
|
//
|
|
// The client uses `EzRpcClient(int fd)`, which is designed for an
|
|
// already-connected socket (unlike EzRpcServer, which expects a listening
|
|
// socket). See DocumentProcess.cpp for the server-side integration note.
|
|
|
|
#include "UIProcess.h"
|
|
#include "ipc.capnp.h"
|
|
|
|
#include <capnp/ez-rpc.h>
|
|
#include <capnp/message.h>
|
|
#include <kj/array.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unistd.h>
|
|
|
|
namespace proto = freepdfeditor::spike::e;
|
|
|
|
namespace freepdfeditor::spike::e {
|
|
|
|
UIResult run_ui_process(int parent_fd, int child_fd,
|
|
std::size_t n_requests, std::size_t payload_bytes)
|
|
{
|
|
(void)child_fd; // the child end is the document process's concern
|
|
UIResult r{};
|
|
|
|
// The payload the UI hands the document process. Sent as a Cap'n Proto
|
|
// Data field; the document process counts the bytes and returns the count.
|
|
std::vector<unsigned char> payload(payload_bytes, 0xAB);
|
|
|
|
// EzRpcClient(int fd) wraps an already-connected socket — exactly the
|
|
// socketpair-end shape. It sets up its own KJ EventLoop + WaitScope.
|
|
capnp::EzRpcClient client(parent_fd);
|
|
auto& waitScope = client.getWaitScope();
|
|
proto::DocumentProcess::Client doc =
|
|
client.getMain().castAs<proto::DocumentProcess>();
|
|
|
|
double min_us = 1e18, max_us = 0.0, sum_us = 0.0;
|
|
std::size_t acked = 0;
|
|
|
|
for (std::size_t i = 0; i < n_requests; ++i) {
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
|
|
auto req = doc.parseRequest();
|
|
req.getRequest().setRequestId(static_cast<std::uint64_t>(i));
|
|
req.getRequest().setData(kj::ArrayPtr<const kj::byte>(
|
|
payload.data(), payload.size()));
|
|
|
|
// The Response owns the message backing the result reader, so it must
|
|
// stay alive for as long as we read from `resp` (a dangling reader is
|
|
// a use-after-free — ASan catches it, validating §7.2). Validate the
|
|
// response inside the scope that owns the Response.
|
|
bool ok = false;
|
|
std::string err;
|
|
std::uint64_t resp_id = 0;
|
|
std::uint64_t byte_count = 0;
|
|
try {
|
|
auto response = req.send().wait(waitScope);
|
|
auto resp = response.getResult();
|
|
ok = resp.getOk();
|
|
if (!ok) {
|
|
err = "doc returned ok=false: " + std::string(resp.getError().cStr());
|
|
} else {
|
|
resp_id = resp.getRequestId();
|
|
byte_count = resp.getByteCount();
|
|
}
|
|
} catch (kj::Exception& e) {
|
|
err = "RPC failed: " + std::string(e.getDescription().cStr());
|
|
}
|
|
|
|
auto t1 = std::chrono::steady_clock::now();
|
|
double us = std::chrono::duration<double, std::micro>(t1 - t0).count();
|
|
|
|
// Validate the response (ADR-0004: the IPC is a trust boundary in both
|
|
// directions — the UI never trusts the document process blindly).
|
|
if (!ok) {
|
|
r.error = err + " at " + std::to_string(i);
|
|
break;
|
|
}
|
|
if (resp_id != static_cast<std::uint64_t>(i)) {
|
|
r.error = "request id mismatch at " + std::to_string(i);
|
|
break;
|
|
}
|
|
if (byte_count != payload_bytes) {
|
|
r.error = "byte count mismatch at " + std::to_string(i) +
|
|
": got " + std::to_string(byte_count) +
|
|
" expected " + std::to_string(payload_bytes);
|
|
break;
|
|
}
|
|
|
|
sum_us += us;
|
|
min_us = std::min(min_us, us);
|
|
max_us = std::max(max_us, us);
|
|
++acked;
|
|
}
|
|
|
|
r.requests_sent = n_requests;
|
|
r.requests_acked = acked;
|
|
r.min_latency_us = acked ? min_us : 0.0;
|
|
r.max_latency_us = acked ? max_us : 0.0;
|
|
r.avg_latency_us = acked ? sum_us / double(acked) : 0.0;
|
|
r.total_time_us = sum_us;
|
|
r.ok = (acked == n_requests);
|
|
return r;
|
|
}
|
|
|
|
} // namespace freepdfeditor::spike::e
|