#pragma once #include #include #include #include #include #include "secsgem/secs2/message.hpp" // E30 §6.22 Spooling. // // When the equipment can't deliver a primary message to the host (because // the link is down, the host hasn't selected, or — for the demo — a // test-force flag is set), it queues the message in the spool instead of // dropping it. When the host later sends S6F23 with RSDC=0, the equipment // transmits everything in the queue. RSDC=1 purges. // // The set of streams the equipment is allowed to spool is configured via // S2F43 (Reset Spooling). This implementation supports the simple form // (per-stream enable list, no per-message-type granularity) which covers // the common cases (stream 5 alarms + stream 6 event reports). namespace secsgem::gem { namespace s2 = secsgem::secs2; // S2F44 RSPACK — ack for S2F43. enum class ResetSpoolAck : uint8_t { Accept = 0, Denied_Busy = 1, Denied_NotAllowed = 2, Denied_NoSuchStream = 3, }; // S6F24 RSDA — ack for S6F23 (Request Spooled Data). enum class SpoolRequestAck : uint8_t { Accept = 0, Denied = 1, }; // S6F23 RSDC — host's request code in the Spool Request. enum class SpoolRequestCode : uint8_t { Transmit = 0, Purge = 1, }; class SpoolStore { public: enum class EnqueueResult { Queued, Dropped_NotSpoolable, // stream not in the configured spoolable set Dropped_Full, // queue at max_size, oldest-message overflow policy }; // Spool config (set by S2F43 + loader). void set_spoolable_streams(std::vector streams) { spoolable_.clear(); for (auto s : streams) spoolable_.insert(s); } bool is_stream_spoolable(uint8_t stream) const { return spoolable_.count(stream) > 0; } std::vector spoolable_streams() const { return {spoolable_.begin(), spoolable_.end()}; } void set_max_size(std::size_t n) { max_size_ = n; } std::size_t max_size() const { return max_size_; } // Equipment may force-enable spool mode (e.g. while the host is offline, // or via a test command in the demo). Independent of the spoolable // stream list — if `force_spool` is true *and* the stream is spoolable, // enqueue() returns Queued. If `force_spool` is false, enqueue() still // returns Dropped_NotSpoolable (so the caller can send live instead). void set_force_spool(bool on) { force_spool_ = on; } bool force_spool() const { return force_spool_; } EnqueueResult enqueue(s2::Message msg) { if (!is_stream_spoolable(msg.stream)) return EnqueueResult::Dropped_NotSpoolable; if (queue_.size() >= max_size_) { // Overflow policy: drop the oldest (FIFO eviction). queue_.pop_front(); } queue_.push_back(std::move(msg)); return EnqueueResult::Queued; } // Drain everything currently in the queue (FIFO order). std::vector drain() { std::vector out; out.reserve(queue_.size()); while (!queue_.empty()) { out.push_back(std::move(queue_.front())); queue_.pop_front(); } return out; } void clear() { queue_.clear(); } std::size_t size() const { return queue_.size(); } bool empty() const { return queue_.empty(); } private: std::set spoolable_; std::deque queue_; std::size_t max_size_ = 100; bool force_spool_ = false; }; } // namespace secsgem::gem