From 29f646c7ca79bce2f28d5fda564ec1e080f76f3d Mon Sep 17 00:00:00 2001 From: Raphael Maenle Date: Tue, 9 Jun 2026 01:20:23 +0200 Subject: [PATCH] HostHandler: senders for the AA tranche messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A host couldn't drive the new messages through the HostHandler class — only the server side knew how to dispatch them. Adds six new senders plus a unit test that walks each through a real loopback connection: * send_legacy_remote_command -> S2F21 * send_event_report_request -> S6F15 * send_individual_report_request -> S6F19 * send_annotated_report_request -> S6F21 * send_pp_load_inquire -> S7F1 * send_delete_pp -> S7F17 Suite: 296 cases / 1571 assertions. Co-Authored-By: Claude Opus 4.7 --- include/secsgem/gem/host_handler.hpp | 22 +++++++++++ src/gem/host_handler.cpp | 29 +++++++++++++++ tests/test_host_handler.cpp | 55 ++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/include/secsgem/gem/host_handler.hpp b/include/secsgem/gem/host_handler.hpp index 7b9e9c7..d1dcb55 100644 --- a/include/secsgem/gem/host_handler.hpp +++ b/include/secsgem/gem/host_handler.hpp @@ -124,6 +124,20 @@ class HostHandler : public std::enable_shared_from_this { const std::vector& params, ReplyHandler cb); + // S2F21/F22 legacy Remote Command (no parameter list, single ack byte). + void send_legacy_remote_command(const std::string& rcmd, ReplyHandler cb); + + // ---- Host-initiated event/report queries (B3) ------------------------- + + // S6F15/F16: pull the current event-report payload for a given CEID. + void send_event_report_request(uint32_t ceid, ReplyHandler cb); + + // S6F19/F20: pull a single RPTID's current values (no annotation). + void send_individual_report_request(uint32_t rptid, ReplyHandler cb); + + // S6F21/F22: pull a single RPTID's values with (VID, V) annotation. + void send_annotated_report_request(uint32_t rptid, ReplyHandler cb); + // ---- Alarm management (B3) -------------------------------------------- // S5F3/F4 Enable/Disable Alarm. Spec uses ALED bit-7 to encode enable. @@ -144,6 +158,14 @@ class HostHandler : public std::enable_shared_from_this { ReplyHandler cb); // S7F3/F4 void send_request_pp(const std::string& ppid, ReplyHandler cb); // S7F5/F6 + // S7F1/F2 Process Program Load Inquire: announce a forthcoming S7F3 + // of `length` bytes; equipment grants or denies. + void send_pp_load_inquire(const std::string& ppid, uint32_t length, + ReplyHandler cb); + + // S7F17/F18 Delete Process Program: empty list = delete all. + void send_delete_pp(const std::vector& ppids, ReplyHandler cb); + // ---- Spool (B3) ------------------------------------------------------- void send_request_spool(SpoolRequestCode code, ReplyHandler cb); // S6F23/F24 diff --git a/src/gem/host_handler.cpp b/src/gem/host_handler.cpp index 29605aa..d39bd14 100644 --- a/src/gem/host_handler.cpp +++ b/src/gem/host_handler.cpp @@ -153,6 +153,25 @@ void HostHandler::send_enhanced_remote_command( std::move(cb)); } +void HostHandler::send_legacy_remote_command(const std::string& rcmd, + ReplyHandler cb) { + send_request(s2f21_remote_command(rcmd), std::move(cb)); +} + +// ---- Host-initiated event/report queries -------------------------------- + +void HostHandler::send_event_report_request(uint32_t ceid, ReplyHandler cb) { + send_request(s6f15_event_report_request(ceid), std::move(cb)); +} + +void HostHandler::send_individual_report_request(uint32_t rptid, ReplyHandler cb) { + send_request(s6f19_individual_report_request(rptid), std::move(cb)); +} + +void HostHandler::send_annotated_report_request(uint32_t rptid, ReplyHandler cb) { + send_request(s6f21_annotated_report_request(rptid), std::move(cb)); +} + // ---- Alarm management (B3) ---------------------------------------------- void HostHandler::send_enable_alarm(uint32_t alid, bool enable, ReplyHandler cb) { @@ -194,6 +213,16 @@ void HostHandler::send_request_pp(const std::string& ppid, ReplyHandler cb) { send_request(s7f5_process_program_request(ppid), std::move(cb)); } +void HostHandler::send_pp_load_inquire(const std::string& ppid, uint32_t length, + ReplyHandler cb) { + send_request(s7f1_pp_load_inquire(ppid, length), std::move(cb)); +} + +void HostHandler::send_delete_pp(const std::vector& ppids, + ReplyHandler cb) { + send_request(s7f17_delete_pp_send(ppids), std::move(cb)); +} + // ---- Spool (B3) --------------------------------------------------------- void HostHandler::send_request_spool(SpoolRequestCode code, ReplyHandler cb) { diff --git a/tests/test_host_handler.cpp b/tests/test_host_handler.cpp index 6a7fae3..aba73a2 100644 --- a/tests/test_host_handler.cpp +++ b/tests/test_host_handler.cpp @@ -100,6 +100,20 @@ struct Recorder { return gem::s16f6_pr_job_command_ack(gem::HostCmdAck::Accept); if (msg.stream == 14 && msg.function == 9) return gem::s14f10_create_control_job_ack("CJ-1", gem::ObjectAck::Success); + // New senders (Task 18): provide canned replies so the host's + // send_*_request transactions complete instead of timing out. + if (msg.stream == 2 && msg.function == 21) + return gem::s2f22_remote_command_ack(gem::HostCmdAck::Accept); + if (msg.stream == 6 && msg.function == 15) + return gem::s6f16_event_report_data({0, 300, {}}); + if (msg.stream == 6 && msg.function == 19) + return gem::s6f20_individual_report_data({}); + if (msg.stream == 6 && msg.function == 21) + return gem::s6f22_annotated_report_data({}); + if (msg.stream == 7 && msg.function == 1) + return gem::s7f2_pp_load_grant(gem::ProcessProgramAck::Accept); + if (msg.stream == 7 && msg.function == 17) + return gem::s7f18_delete_pp_ack(gem::ProcessProgramAck::Accept); // Unhandled — leave the host to time out on T3. return std::nullopt; }; @@ -254,6 +268,47 @@ TEST_CASE("HostHandler: E40/E94 job creation senders produce correct wire") { pump_until(w.io, [&] { return cmd_ack; }); } +TEST_CASE("HostHandler: new senders produce correct wire shapes") { + // Wires up the host + equipment, installs the handler, fires each + // new sender, and verifies the (stream, function) of the request + // the equipment received. The equipment auto-acks each via Recorder. + Wired w; + Recorder rec; + w.equipment->set_message_handler(rec.handler()); + w.handler->install(); + w.host->start(); + w.equipment->start(); + bool selected = false; + w.host->set_selected_handler([&] { selected = true; }); + pump_until(w.io, [&] { return selected; }); + + auto run_one = [&](auto fire, uint8_t want_stream, uint8_t want_fn) { + bool done = false; + fire([&](std::error_code ec, const s2::Message&) { + REQUIRE_FALSE(ec); + done = true; + }); + pump_until(w.io, [&] { return done; }); + REQUIRE_FALSE(rec.last.empty()); + CHECK(rec.last.back().stream == want_stream); + CHECK(rec.last.back().function == want_fn); + }; + + run_one([&](auto cb) { w.handler->send_legacy_remote_command("PAUSE", cb); }, + 2, 21); + run_one([&](auto cb) { w.handler->send_event_report_request(300, cb); }, 6, 15); + run_one([&](auto cb) { w.handler->send_individual_report_request(1, cb); }, + 6, 19); + run_one([&](auto cb) { w.handler->send_annotated_report_request(1, cb); }, + 6, 21); + run_one([&](auto cb) { + w.handler->send_pp_load_inquire("RECIPE-X", 4096, cb); + }, 7, 1); + run_one([&](auto cb) { + w.handler->send_delete_pp({"RECIPE-X", "RECIPE-Y"}, cb); + }, 7, 17); +} + TEST_CASE("HostHandler: inbound S5F1 alarm auto-acks with S5F2") { Wired w; // Equipment side stays silent on inbound; we only need the host to