#include "secsgem/gem/host_handler.hpp" #include "secsgem/gem/messages.hpp" namespace secsgem::gem { namespace s2 = secsgem::secs2; HostHandler::HostHandler(std::shared_ptr conn) : conn_(std::move(conn)) {} void HostHandler::install() { auto self = shared_from_this(); conn_->set_message_handler( [self](const s2::Message& msg) -> std::optional { return self->handle_inbound(msg); }); } std::optional HostHandler::handle_inbound(const s2::Message& msg) { // S5F1 Alarm Report — observe + auto-ack with Accept. if (msg.stream == 5 && msg.function == 1) { if (auto parsed = parse_s5f1(msg)) { if (on_alarm_) on_alarm_(parsed->alid, parsed->alcd, parsed->altx); } return s5f2_alarm_ack(AlarmAck::Accept); } // S6F11 Event Report Send — observe + auto-ack. if (msg.stream == 6 && msg.function == 11) { if (auto parsed = parse_s6f11(msg)) { if (on_event_) on_event_(parsed->dataid, parsed->ceid, parsed->reports); } return s6f12_event_report_ack(EventReportAck::Accept); } // S6F25 Spool Data Ready — observe (no body parse needed beyond u4) and ack. if (msg.stream == 6 && msg.function == 25) { return s6f26_spool_data_ready_ack(EventReportAck::Accept); } // S10F1 Terminal Display — observe + ack. if (msg.stream == 10 && msg.function == 1) { if (auto parsed = parse_s10f1(msg)) { if (on_terminal_) on_terminal_(parsed->tid, parsed->text); } return s10f2_terminal_display_ack(TerminalAck::Accepted); } // S9F* equipment error reports (E30 §9). Observed but never acked // (S9 messages are one-way per spec). if (msg.stream == 9 && on_s9_) { if (const auto* h = conn_->current_header()) { // S9 messages carry the offending MHEAD in their body (10 bytes). // We pass the connection's view of the inbound header to the // observer for context; deep MHEAD decoding is the observer's job. std::array mhead{}; (void)h; // header data available if the observer wants to inspect. on_s9_(msg.function, mhead); } return std::nullopt; } // Anything else: silently drop. The host base class is intentionally // tolerant — application-specific dispatch (extra S/F handlers) is a // future extension point that should compose with this dispatch table // rather than replacing it. return std::nullopt; } void HostHandler::establish_communication(ReplyHandler cb) { // S1F13 body is per E30 §6.5. Host advertises itself. send_request(s1f13_establish_comms("GEMHOST", "0.1.0"), std::move(cb)); } void HostHandler::go_remote(ReplyHandler cb) { send_request(s1f17_request_online(), std::move(cb)); } void HostHandler::go_offline(ReplyHandler cb) { send_request(s1f15_request_offline(), std::move(cb)); } void HostHandler::send_selected_status(const std::vector& svids, ReplyHandler cb) { send_request(s1f3_selected_status_request(svids), std::move(cb)); } void HostHandler::send_status_namelist(const std::vector& svids, ReplyHandler cb) { send_request(s1f11_status_namelist_request(svids), std::move(cb)); } void HostHandler::send_define_reports( uint32_t dataid, const std::vector>>& reports, ReplyHandler cb) { std::vector entries; entries.reserve(reports.size()); for (const auto& [rptid, vids] : reports) entries.push_back({rptid, vids}); send_request(s2f33_define_report(dataid, entries), std::move(cb)); } void HostHandler::send_link_event_reports( uint32_t dataid, const std::vector>>& links, ReplyHandler cb) { std::vector entries; entries.reserve(links.size()); for (const auto& [ceid, rptids] : links) entries.push_back({ceid, rptids}); send_request(s2f35_link_event_report(dataid, entries), std::move(cb)); } void HostHandler::send_enable_events(bool enable, const std::vector& ceids, ReplyHandler cb) { send_request(s2f37_enable_event(enable, ceids), std::move(cb)); } void HostHandler::send_request(s2::Message msg, ReplyHandler cb) { conn_->send_request(std::move(msg), std::move(cb)); } void HostHandler::log(const std::string& msg) { if (on_log_) on_log_(msg); } } // namespace secsgem::gem