Closes the S9 stream. Every documented protocol-error condition is now
auto-emitted by Connection (with the assist of one Router predicate),
without involving the application.
Router (include/secsgem/gem/router.hpp)
Adds two predicates: has_handler(stream, function) and
has_handler_for_stream(stream). Lets the wrapping message handler
decide whether an unhandled message is "unknown stream" (S9F3) or
"unknown function in a known stream" (S9F5).
Connection (include/secsgem/hsms/{connection.hpp, connection.cpp})
- emit_s9() goes public so the message_handler can call it.
- New current_header() accessor returns the HSMS header of the
primary currently being dispatched. Non-null only inside the
on_message_ call; cleared on the way out.
- handle_data sets current_header_ before invoking on_message_.
- on_length on oversized frame: synthesizes a 10-byte MHEAD whose
first 4 bytes are the offending length prefix, emits S9F11, and
sets close_after_flush so the S9F11 goes out before the socket
closes.
Server (apps/secs_server.cpp)
The conn->set_message_handler lambda now wraps router.dispatch. For
any inbound primary without a registered handler, it captures the
MHEAD via current_header() and emits either S9F3 (stream unknown) or
S9F5 (function unknown). The wrapper still returns the Router's
reply (SxF0 for primaries with W) so transactional semantics are
preserved.
COMPLIANCE.md
Error Messages row flips from 🟡 to ✅. S9F3/F5/F11 rows in the
coverage matrix flip from 🟡 to ✅. Each row in the matrix now
states its trigger condition explicitly. Drops the
"Finish S9 wiring" bullet from the "what would 100% take" list.
Verified
- Tests: 78 cases / 454 assertions still pass (no behavioural change
on the happy path; new emission paths fire only on protocol errors
that the demo doesn't induce).
- Build clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+5
-7
@@ -69,7 +69,7 @@ Legend:
|
||||
| Host-Initiated S1F13/F14 scenario | ✅ | E30 §6.5 | S1F13/F14 | |
|
||||
| Event Notification | ✅ | E30 §6.6 | S6F11/F12 | Equipment-initiated, host-acknowledged. |
|
||||
| On-Line Identification | ✅ | E30 §6.7 | S1F1/F2 | MDLN + SOFTREV. |
|
||||
| Error Messages | 🟡 | E30 §6.9 | S9F* | HSMS Reject.req covers the transport-level case. S9F1/F3/F5/F7/F9/F11/F13 are now in the message catalog and round-trip-tested. `Connection` automatically emits **S9F7** when a peer's primary or reply body fails SECS-II decode (connection stays up; the caller of `send_request` sees `Error::IllegalData`), and **S9F9** when its outgoing request times out at T3 (alongside the existing `Error::Timeout`). **Not wired**: S9F3 (unknown stream) and S9F5 (unknown function) — these belong in the `Router`'s fallback path; today the fallback returns SxF0 only. |
|
||||
| Error Messages | ✅ | E30 §6.9 | S9F* | HSMS Reject.req at the transport level; S9F1/F3/F5/F7/F9/F11/F13 in the SECS-II catalog. Auto-emitted by Connection on every documented condition: **S9F3** (unknown stream) and **S9F5** (unknown function) via the server's `Router`-aware wrapper using the new `Connection::current_header()` accessor; **S9F7** (illegal data) when a peer body fails SECS-II decode; **S9F9** (T3 timeout) reconstructing the original outgoing MHEAD; **S9F11** (data too long) with a synthesized MHEAD whose first 4 bytes are the offending length prefix. S9F1 / S9F13 are catalog-only — no documented condition currently triggers them in our codebase. |
|
||||
| Documentation | ✅ | E30 §6.10| S1F19/F20, S1F21/F22 | Equipment self-reports its GEM-compliance level via S1F20 (SOFTREV, EQPTYP, list of (CCODE, CDESC) per E30 Appendix A) and its DVID namelist via S1F22. Both populated from `data/equipment.yaml`. |
|
||||
| Control (Operator-Initiated) | ✅ | E30 §6.2 | — | `ControlStateMachine::operator_online/offline/local/remote`. |
|
||||
|
||||
@@ -137,11 +137,11 @@ Legend:
|
||||
| S7F19 / S7F20 | H→E | ✅ | `messages.hpp` | ✅ round-trip + demo |
|
||||
| S7F23–F26 | H↔E | ⬜ | — | enhanced PP |
|
||||
| S9F1 | E↔H | ✅ | catalog | ✅ round-trip |
|
||||
| S9F3 | E↔H | 🟡 | catalog | ✅ round-trip; emission not yet wired |
|
||||
| S9F5 | E↔H | 🟡 | catalog | ✅ round-trip; emission not yet wired |
|
||||
| S9F3 | E↔H | ✅ | catalog + Router wrapper | ✅ round-trip + auto-emitted on unknown stream |
|
||||
| S9F5 | E↔H | ✅ | catalog + Router wrapper | ✅ round-trip + auto-emitted on unknown function |
|
||||
| S9F7 | E↔H | ✅ | catalog + Connection | ✅ round-trip + auto-emitted on body decode |
|
||||
| S9F9 | E↔H | ✅ | catalog + Connection | ✅ round-trip + auto-emitted on T3 timeout |
|
||||
| S9F11 | E↔H | 🟡 | catalog | ✅ round-trip; emission not yet wired |
|
||||
| S9F11 | E↔H | ✅ | catalog + Connection | ✅ round-trip + auto-emitted on oversized frame |
|
||||
| S9F13 | E↔H | ✅ | catalog | ✅ round-trip |
|
||||
| S10F1 / S10F2 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||||
| S10F3 / S10F4 | E→H | ✅ | `messages.hpp` | ✅ round-trip + demo |
|
||||
@@ -193,9 +193,7 @@ The honest list, in priority order:
|
||||
notification on re-SELECT) so the host doesn't have to manually flip
|
||||
the test-only `force_spool` flag. Optional: persistent on-disk spool
|
||||
so equipment restarts don't lose queued events.
|
||||
2. **Finish S9 wiring**: route Router-level "unknown stream/function" through
|
||||
S9F3/F5, and emit S9F11 (Data Too Long) with the 4-byte length prefix
|
||||
in place of MHEAD when the incoming frame is oversized.
|
||||
2. *(done in this revision)*: S9F3/F5/F11 — all S9 conditions now auto-emit.
|
||||
3. *(done in this revision)*: S1F19/F20 + S1F21/F22 — Documentation.
|
||||
4. **Implement EC range validation** in `set_equipment_constant_value`
|
||||
so out-of-range sets return EAC=4 instead of being silently accepted.
|
||||
|
||||
Reference in New Issue
Block a user