freepdfeditor/spike/E_sandbox/UIProcess.cpp

126 lines
4.1 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 length-prefixed binary IPC
// 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).
#include "UIProcess.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <unistd.h>
namespace freepdfeditor::spike::e {
namespace {
bool read_exact(int fd, void* buf, std::size_t n)
{
auto* p = static_cast<char*>(buf);
while (n > 0) {
ssize_t r = read(fd, p, n);
if (r <= 0) return false;
p += r; n -= std::size_t(r);
}
return true;
}
bool write_exact(int fd, const void* buf, std::size_t n)
{
const auto* p = static_cast<const char*>(buf);
while (n > 0) {
ssize_t w = write(fd, p, n);
if (w <= 0) return false;
p += w; n -= std::size_t(w);
}
return true;
}
} // namespace
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{};
std::vector<unsigned char> payload(payload_bytes, 0xAB);
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();
// Request: [u64 request_id][u64 payload_len][payload]
std::uint64_t request_id = static_cast<std::uint64_t>(i);
std::uint64_t payload_len = static_cast<std::uint64_t>(payload_bytes);
if (!write_exact(parent_fd, &request_id, sizeof(request_id))) {
r.error = "write request_id failed at " + std::to_string(i);
break;
}
if (!write_exact(parent_fd, &payload_len, sizeof(payload_len))) {
r.error = "write payload_len failed at " + std::to_string(i);
break;
}
if (!write_exact(parent_fd, payload.data(), payload_bytes)) {
r.error = "write payload failed at " + std::to_string(i);
break;
}
// Response: [u64 request_id][u64 byte_count][u8 ok]
std::uint64_t resp_id = 0, byte_count = 0;
std::uint8_t ok = 0;
if (!read_exact(parent_fd, &resp_id, sizeof(resp_id))) {
r.error = "read resp_id failed at " + std::to_string(i);
break;
}
if (!read_exact(parent_fd, &byte_count, sizeof(byte_count))) {
r.error = "read byte_count failed at " + std::to_string(i);
break;
}
if (!read_exact(parent_fd, &ok, sizeof(ok))) {
r.error = "read ok failed at " + std::to_string(i);
break;
}
auto t1 = std::chrono::steady_clock::now();
double us = std::chrono::duration<double, std::micro>(t1 - t0).count();
sum_us += us;
min_us = std::min(min_us, us);
max_us = std::max(max_us, us);
// 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 = "doc returned ok=false at " + std::to_string(i); break; }
if (resp_id != request_id) {
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;
}
++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