3 Commits

Author SHA1 Message Date
raphael d4d1a411d7 secsi: T3 / T4 enforcement moved into the FSM
The SECS-I Protocol FSM now enforces T3 (reply timeout) and T4
(inter-block timeout) directly, instead of leaving them as
upper-layer hooks.

T3: on complete_send, if the block we just acked had W=1, record its
system_bytes in awaiting_reply_sys_ and emit ActionStartTimer{T3}.
deliver_recv cancels T3 when a block arrives whose system_bytes
match the outstanding request.  EventTimeout{T3} aborts the FSM with
"T3 reply timeout".

T4: deliver_recv emits ActionStartTimer{T4} whenever the delivered
block has end_block=false.  The next block's deliver_recv cancels
the timer; EventTimeout{T4} aborts with "T4 inter-block timeout".

abort() now also cancels T3/T4 and clears the tracking state.

Test changes:
  - Old "T3/T4 are FSM-level no-ops" test → REPLACED by four new
    tests: T3 arm+expire, T3 arm+matching-reply cancels, T4
    arm+expire, T4 arm+next-block cancels.
  - Two new observer accessors on Protocol (awaiting_reply,
    awaiting_next_block) so the tests can assert tracking state
    without poking internals.

COMPLIANCE.md §1a: T3 + T4 rows go .

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 11:52:43 +02:00
raphael 72fa73fee0 A5: SECS-I-over-TCP convenience layer
Wires the SECS-I Protocol FSM behind an asio TCP socket so the block
protocol can run over loopback without serial hardware.  Mirrors
secsgem-py's `secsitcp/` adapter — useful for back-to-back simulators
and CI without a serial device.

Adds:
  include/secsgem/secsi/tcp_transport.hpp
  src/secsi/tcp_transport.cpp
  tests/test_secsi_tcp.cpp

The transport:
- Splits outgoing SECS-II messages into blocks (transparent multi-block).
- Accumulates incoming blocks until end_block=true, then assembles and
  delivers as a single SECS-II message — same surface as the HSMS
  Connection's MessageHandler.
- Drives T1 / T2 timers from asio steady_timer; T3/T4 stay upper-layer
  per the FSM contract.
- Auto-allocates monotonic system bytes per send.

Tests cover single-block delivery, multi-block reassembly (700-byte
ASCII body spanning multiple SECS-I blocks), and bidirectional exchange.

This closes Tranche A (catch-up to secsgem-py wire/transport surface).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 21:36:17 +02:00
raphael a400ef3160 A4: SECS-I transport (block protocol + E4 retry FSM)
Adds a complete IO-free SECS-I implementation:

  include/secsgem/secsi/header.hpp   10-byte block header (R/W/E bits)
  include/secsgem/secsi/block.hpp    length + header + body + checksum
  include/secsgem/secsi/protocol.hpp half-duplex FSM (ENQ/EOT/ACK/NAK)
  src/secsi/*                         implementations
  tests/test_secsi.cpp                header, block, multi-block split,
                                      back-to-back FSM drive, RTY,
                                      contention, T2 timeout

The protocol is event-driven (`Event` → `Action` queue), so wiring it
to an asio serial_port is a thin adapter — that lands in the next
commit so this one stays reviewable.

Key design points:
- Master/slave contention: slave yields on simultaneous ENQ (E4 §7.1.4).
- RTY exhaustion raises ActionRaiseError, clears the send queue, resets
  to Idle (no zombie state).
- Multi-block assembler validates contiguous 1..N numbering and exclusive
  E-bit-on-last invariants — rejects malformed sequences with nullopt.
- Block::checksum is exposed publicly for the receive path's verification.

Tests cover the happy path (back-to-back delivery), error paths
(checksum mismatch, short input, oversize body), retries (NAK chain to
exhaustion), and protocol corner cases (contention, T2 timeout).

secsgem-py implements SECS-I block framing but lacks the explicit RTY
state machine; this commit puts the C++ port ahead on transport
correctness.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 21:34:09 +02:00