// 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 #include #include #include #include #include #include #include namespace freepdfeditor::spike::e { namespace { bool read_exact(int fd, void* buf, std::size_t n) { auto* p = static_cast(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(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 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(i); std::uint64_t payload_len = static_cast(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(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