126 lines
4.1 KiB
C++
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
|