#include "secsgem/gem/communication_state.hpp" namespace secsgem::gem { const char* comm_state_name(CommState s) { switch (s) { case CommState::Disabled: return "DISABLED"; case CommState::WaitCRA: return "WAIT-CRA"; case CommState::WaitDelay: return "WAIT-DELAY"; case CommState::Communicating: return "COMMUNICATING"; } return "?"; } CommunicationStateMachine::CommunicationStateMachine(CommTimers timers, Initiator initiator) : timers_(timers), initiator_(initiator) {} void CommunicationStateMachine::transition(CommState next, const std::string& reason) { if (state_ == next) return; const CommState prev = state_; state_ = next; if (on_change_) on_change_(prev, next, reason); } void CommunicationStateMachine::enable() { if (state_ != CommState::Disabled) return; // Cancel any stale timers from a previous lifetime. if (env_.cancel_timers) env_.cancel_timers(); if (initiator_ == Initiator::Equipment) { // Equipment-initiated: send S1F13 immediately and wait for S1F14. transition(CommState::WaitCRA, "enabled; equipment-initiated S1F13"); if (env_.arm_t_cra) env_.arm_t_cra(timers_.t_cra); if (env_.send_s1f13) env_.send_s1f13(); } else { // Host-initiated: we stay non-communicating until the host sends // S1F13; we model the wait as WAIT-CRA (no T_CRA armed since the // wait is indefinite from our side). transition(CommState::WaitCRA, "enabled; awaiting host S1F13"); } } void CommunicationStateMachine::disable() { if (state_ == CommState::Disabled) return; if (env_.cancel_timers) env_.cancel_timers(); transition(CommState::Disabled, "disabled by operator"); } void CommunicationStateMachine::on_s1f14_received(uint8_t commack) { if (state_ != CommState::WaitCRA) return; // unexpected S1F14; ignore if (env_.cancel_timers) env_.cancel_timers(); if (commack == 0) { transition(CommState::Communicating, "S1F14 COMMACK=Accept"); } else { transition(CommState::WaitDelay, "S1F14 COMMACK=" + std::to_string(commack) + " (denied)"); if (env_.arm_t_delay) env_.arm_t_delay(timers_.t_delay); } } void CommunicationStateMachine::on_s1f13_received() { // Inbound establishment from the host. Spec allows this in any // ENABLED substate (the host can re-establish). Disabled equipment // would reply with COMMACK=Denied; that's the embedder's call, not // ours — we just record the transition if the embedder accepts. if (state_ == CommState::Disabled) return; if (env_.cancel_timers) env_.cancel_timers(); transition(CommState::Communicating, "host S1F13 received"); } void CommunicationStateMachine::on_connection_lost() { if (state_ == CommState::Disabled) return; if (env_.cancel_timers) env_.cancel_timers(); // Per E30: a transport drop returns us to NOT-COMMUNICATING. We // model that as WAIT-DELAY (so we retry after T_DELAY) when we're // an equipment-initiator, and as WAIT-CRA (awaiting host S1F13) // otherwise. if (initiator_ == Initiator::Equipment) { transition(CommState::WaitDelay, "transport dropped"); if (env_.arm_t_delay) env_.arm_t_delay(timers_.t_delay); } else { transition(CommState::WaitCRA, "transport dropped; awaiting host S1F13"); } } void CommunicationStateMachine::on_cra_timeout() { if (state_ != CommState::WaitCRA) return; transition(CommState::WaitDelay, "T_CRA timeout"); if (env_.arm_t_delay) env_.arm_t_delay(timers_.t_delay); } void CommunicationStateMachine::on_delay_elapsed() { if (state_ != CommState::WaitDelay) return; // T_DELAY elapsed; re-attempt S1F13 if equipment-initiated. if (initiator_ == Initiator::Equipment) { transition(CommState::WaitCRA, "T_DELAY elapsed; re-attempting S1F13"); if (env_.arm_t_cra) env_.arm_t_cra(timers_.t_cra); if (env_.send_s1f13) env_.send_s1f13(); } else { transition(CommState::WaitCRA, "T_DELAY elapsed; awaiting host S1F13"); } } } // namespace secsgem::gem