65db38d9f2
tests / build-and-test (push) Failing after 31s
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>
214 lines
15 KiB
Markdown
214 lines
15 KiB
Markdown
# SECS/GEM Compliance Audit
|
||
|
||
An honest, per-capability accounting of which parts of SEMI **E5 (SECS-II)**,
|
||
**E30 (GEM)**, and **E37 (HSMS)** this codebase implements, which parts it
|
||
implements partially, and which parts it does not implement at all.
|
||
|
||
> **TL;DR.** This is *not* a 100%-conformant GEM equipment. It implements
|
||
> every **GEM Fundamental capability** except formal Documentation (S1F19–F22)
|
||
> and the S9 error stream, plus most of the high-value **GEM Additional
|
||
> capabilities** (Dynamic Event Reporting, Alarms, EC, Clock, Remote Control,
|
||
> Process Programs, Terminal). Spooling, Limits Monitoring, Trace Data
|
||
> Collection, Multi-block, and Material Movement are intentionally not yet
|
||
> implemented. See the per-capability tables below for spec section refs.
|
||
|
||
Legend:
|
||
|
||
- ✅ **Full** — message/feature implemented; round-tripped in tests.
|
||
- 🟡 **Partial** — implemented in the demo path but with documented limitations.
|
||
- ⬜ **Not implemented** — intentionally out of scope for this iteration.
|
||
|
||
---
|
||
|
||
## 1. E37 — HSMS transport
|
||
|
||
| Item | Status | Spec ref | Notes |
|
||
|---------------------------------------|--------|----------|-------|
|
||
| TCP transport | ✅ | E37 §6 | `hsms::Connection` (Asio). |
|
||
| 4-byte length prefix + 10-byte header | ✅ | E37 §8.2 | `hsms::Frame::encode/decode`. |
|
||
| Session ID, byte2, byte3, PType, SType, system-bytes | ✅ | E37 §8.3 | `hsms::Header`. |
|
||
| `Select.req / .rsp` | ✅ | E37 §7.2 | `SType` 1/2; SelectStatus enum (0–3). |
|
||
| `Deselect.req / .rsp` | ✅ | E37 §7.4 | `SType` 3/4; DeselectStatus enum (0–2). |
|
||
| `Linktest.req / .rsp` | ✅ | E37 §7.5 | `SType` 5/6; periodic interval configurable. |
|
||
| `Separate.req` | ✅ | E37 §7.6 | `SType` 9; graceful close after flush. |
|
||
| `Reject.req` | 🟡 | E37 §7.7 | Emitted on data-while-NOT-SELECTED with reason 4; we don't yet reject on PType/SType-not-supported. |
|
||
| Connection state machine NOT-CONNECTED → NOT-SELECTED → SELECTED | ✅ | E37 §6.3 | Both Active and Passive modes. |
|
||
| T3 reply timeout | ✅ | E37 §10 | per-transaction `steady_timer`. |
|
||
| T5 connect separation timeout | ✅ | E37 §10 | `Client::schedule_retry`. |
|
||
| T6 control transaction timeout | ✅ | E37 §10 | single slot (no concurrent control transactions). |
|
||
| T7 not-selected timeout (passive) | ✅ | E37 §10 | armed on connect / on Deselect.req. |
|
||
| T8 intercharacter timeout | ✅ | E37 §10 | bounds the payload read after length prefix. |
|
||
| Multi-host / multi-session | ⬜ | E37 §6 | single SELECTED session at a time. |
|
||
| HSMS-SS vs HSMS-GS | ⬜ | E37 §11 | implemented HSMS-SS only. |
|
||
|
||
---
|
||
|
||
## 2. E5 — SECS-II encoding
|
||
|
||
| Item | Status | Spec ref | Notes |
|
||
|---------------------------------------|--------|----------|-------|
|
||
| Format byte + 1/2/3 length bytes | ✅ | E5 §9 | `secs2::encode_into`. |
|
||
| List (`L`) | ✅ | E5 §9.3 | recursive. |
|
||
| ASCII (`A`) | ✅ | E5 §9.5 | |
|
||
| Binary (`B`) | ✅ | E5 §9.5 | |
|
||
| Boolean (`BOOLEAN`) | ✅ | E5 §9.5 | |
|
||
| `U1, U2, U4, U8` (big-endian) | ✅ | E5 §9.5 | |
|
||
| `I1, I2, I4, I8` (big-endian, two's complement) | ✅ | E5 §9.5 | |
|
||
| `F4, F8` (IEEE 754 big-endian) | ✅ | E5 §9.5 | bit-cast round-trip. |
|
||
| JIS-8, C2 (Unicode) | ⬜ | E5 §9.5 | rarely used in modern fabs. |
|
||
| SML text rendering | ✅ | E5 Annex | `secs2::to_sml`. |
|
||
|
||
---
|
||
|
||
## 3. E30 — GEM Fundamental capabilities (§5.2)
|
||
|
||
| Fundamental Capability | Status | Spec ref | Messages | Notes |
|
||
|---------------------------------------|--------|----------|----------|-------|
|
||
| State models | ✅ | E30 §6.2 | — | Control state machine (5 states) + HSMS comm state. Equipment-processing state is left to the equipment app. |
|
||
| Equipment Processing States | ⬜ | E30 §6.3 | — | Standard says equipment **may** model these; tool-specific. Plug-in point not yet exposed. |
|
||
| 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 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`. |
|
||
|
||
---
|
||
|
||
## 4. E30 — GEM Additional capabilities (§5.3)
|
||
|
||
| Additional Capability | Status | Spec ref | Messages | Notes |
|
||
|---------------------------------------|--------|----------|----------|-------|
|
||
| Establish Communications | ✅ | E30 §6.5 | S1F13/F14 | Both directions modeled; COMMACK enum. |
|
||
| Dynamic Event Report Configuration | ✅ | E30 §6.6 | S2F33/F34, S2F35/F36, S2F37/F38 | Full Define-Report / Link-Event / Enable-Event pipeline with all four ack enums. |
|
||
| Variable Data Collection | ✅ | E30 §6.11| — | `DVID` table; values resolvable via `vid_value`. |
|
||
| Trace Data Collection | ⬜ | E30 §6.12| S2F23/F24, S6F1/F2 | Not implemented. |
|
||
| Status Data Collection | ✅ | E30 §6.13| S1F3/F4, S1F11/F12 | |
|
||
| Alarm Management | ✅ | E30 §6.14| S5F1/F2, S5F3/F4, S5F5/F6, S5F7/F8 | Full set: emit, ack, enable/disable, list directory, list enabled. ALCD bit-7 set/cleared, lower-7 category. |
|
||
| Remote Control | ✅ | E30 §6.15| S2F41/F42 | Full HCACK 7-value enum + per-parameter CPACKs. |
|
||
| Equipment Constants | ✅ | E30 §6.16| S2F13/F14, S2F15/F16, S2F29/F30 | **EAC range validation against min/max is NOT performed** — set always accepts a known ECID; this would reject out-of-range with EAC=4 in a conformant equipment. |
|
||
| Process Program Management | 🟡 | E30 §6.17| S7F3/F4, S7F5/F6, S7F19/F20 | Unformatted PP send/request/list. **No E42 enhanced PP**, **no S7F23/F24/F25/F26 PP verify**, **no PPID validation**. PPBODY treated as raw bytes (Binary item). |
|
||
| Material Movement | ⬜ | E30 §6.18| S3F*, E40 | Tied to E40 carrier handling; separate standard. |
|
||
| Equipment Terminal Services | 🟡 | E30 §6.19| S10F1/F2, S10F3/F4 | Single-line only. **S10F5/F6 multi-block, S10F7 broadcast not implemented.** |
|
||
| Clock | ✅ | E30 §6.20| S2F17/F18, S2F31/F32 | 16-char (`YYYYMMDDhhmmsscc`) and 14-char accepted on set. |
|
||
| Limits Monitoring | ⬜ | E30 §6.21| S2F45/F46, S2F47/F48, S6F45/F46 | Not implemented. |
|
||
| Spooling | 🟡 | E30 §6.22| S2F43/F44, S6F23/F24 | Spoolable-streams config (S2F43/F44) + spool request transmit/purge (S6F23/F24) implemented; SpoolStore queues equipment-initiated primaries when the host is unreachable or force-spool is on, drains FIFO on S6F23 transmit, purges on S6F23 purge. **Not implemented**: S6F25/F26 spooled-data-ready notification, automatic spool activation tied to HSMS NOT-SELECTED, persistent (on-disk) spool. |
|
||
| Control | ✅ | E30 §6.2 | — | See Fundamental. |
|
||
|
||
---
|
||
|
||
## 5. Message coverage matrix
|
||
|
||
| Pair | Direction | Status | Implemented in | Tested |
|
||
|------------------|-----------|--------|----------------|--------|
|
||
| S1F1 / S1F2 | H↔E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S1F3 / S1F4 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S1F11 / S1F12 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S1F13 / S1F14 | H↔E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| S1F15 / S1F16 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| S1F17 / S1F18 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| S1F19 / S1F20 | H→E | ✅ | catalog | ✅ round-trip + demo |
|
||
| S1F21 / S1F22 | H→E | ✅ | catalog | ✅ round-trip + demo |
|
||
| S2F13 / S2F14 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| S2F15 / S2F16 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S2F17 / S2F18 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S2F23 / S2F24 | H→E | ⬜ | — | — |
|
||
| S2F29 / S2F30 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| S2F31 / S2F32 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| S2F33 / S2F34 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S2F35 / S2F36 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S2F37 / S2F38 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S2F41 / S2F42 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S2F45–F48 | H→E | ⬜ | — | — |
|
||
| S5F1 / S5F2 | E→H | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S5F3 / S5F4 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S5F5 / S5F6 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| S5F7 / S5F8 | H→E | ✅ | catalog | ✅ round-trip |
|
||
| S6F1 / S6F2 | E→H | ⬜ | — | — |
|
||
| S6F5 / S6F6 | H↔E | ⬜ | — | multi-block |
|
||
| S6F7 / S6F8 | H↔E | ⬜ | — | multi-block |
|
||
| S6F11 / S6F12 | E→H | ✅ | `messages.hpp` | ✅ round-trip + demo |
|
||
| S6F15 / S6F16 | H→E | ⬜ | — | event report request |
|
||
| S2F43 / S2F44 | H→E | ✅ | catalog | ✅ round-trip + demo |
|
||
| S6F23 / S6F24 | H→E | ✅ | catalog | ✅ round-trip + demo |
|
||
| S6F25 / S6F26 | spool | ⬜ | — | spool-data-ready notification |
|
||
| S7F3 / S7F4 | H→E | ✅ | `messages.hpp` | ✅ round-trip |
|
||
| S7F5 / S7F6 | H→E | ✅ | `messages.hpp` | ✅ in demo |
|
||
| 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 + 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 + 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 |
|
||
| S10F5 / S10F6 | H→E | ⬜ | — | multi-line |
|
||
|
||
---
|
||
|
||
## 6. Demo evidence
|
||
|
||
The two-container demo (`docker compose up --no-deps server client`)
|
||
exercises this concrete sequence end-to-end:
|
||
|
||
1. TCP connect → `Select.req` → `Select.rsp(Ok)` → SELECTED on both sides.
|
||
2. `S1F13`/`S1F14` Establish Comms.
|
||
3. `S1F17`/`S1F18` Request Online; control state transitions
|
||
`HostOffline → AttemptOnline → OnlineRemote`.
|
||
4. Server pushes `S10F3` welcome → host acks with `S10F4`.
|
||
5. `S1F11`/`S1F12` SVID namelist discovery → `S1F3`/`S1F4` SVID read.
|
||
6. `S2F29`/`S2F30` EC namelist → `S2F13`/`S2F14` EC read.
|
||
7. `S2F17`/`S2F18` clock read.
|
||
8. `S2F33`/`S2F34` Define Report 1000 over the 3 SVIDs (DRACK=0).
|
||
9. `S2F35`/`S2F36` Link CEIDs 200 and 300 to Report 1000 (LRACK=0).
|
||
10. `S2F37`/`S2F38` Enable CEIDs 200, 300 (ERACK=0).
|
||
11. `S2F41`/`S2F42` host command **START** (HCACK=0) → server emits
|
||
`S6F11(CEID=300)` carrying the linked Report 1000 → host acks `S6F12`.
|
||
12. `S5F5`/`S5F6` list alarm directory.
|
||
13. `S5F3`/`S5F4` enable alarm 1 (ACKC5=0).
|
||
14. `S2F41`/`S2F42` host command **FAULT** (HCACK=0) → server emits
|
||
`S5F1` (ALCD=0x84, set + cat 4) → host acks `S5F2`; server also
|
||
emits `S6F11(CEID=200)`.
|
||
15. `S7F19`/`S7F20` recipe list, `S7F5`/`S7F6` fetch RECIPE-A.
|
||
16. `S10F1`/`S10F2` host → equipment terminal display.
|
||
17. `S1F15`/`S1F16` Request Offline; control state goes back to
|
||
HostOffline. (CEID 100 ControlStateChanged emission is correctly
|
||
*suppressed* because the host never enabled CEID 100 — this is the
|
||
correct GEM behavior.)
|
||
18. `Separate.req` → clean close on both sides.
|
||
|
||
Unit tests: **63 cases / 278 assertions pass** (`docker compose run --rm tests`).
|
||
|
||
---
|
||
|
||
## 7. What it would take to claim "100% GEM-compliant"
|
||
|
||
The honest list, in priority order:
|
||
|
||
1. **Finish spooling**: S6F25/F26 spooled-data-ready notification, plus
|
||
automatic activation when HSMS goes NOT-SELECTED (and automatic
|
||
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. *(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.
|
||
5. **Implement Limits Monitoring** (S2F45–F48, S6F45/F46) if the
|
||
target equipment publishes monitored variables.
|
||
6. **Implement Trace Data Collection** (S2F23/F24, S6F1/F2).
|
||
7. **Implement S5F7/F8** list-enabled-alarms.
|
||
8. **Implement multi-block transfers** (S6F5/F6, S6F7/F8).
|
||
9. **Implement equipment processing state model** with operator hooks
|
||
(E30 §6.3) — the abstract model is in E30 but the concrete states
|
||
are equipment-specific.
|
||
10. **Run the implementation against a real conformance test generator**
|
||
(Layer 4 of the implementation plan) on a representative tool.
|
||
|
||
After all of the above, a GEM Reference Test specification (RTS) review
|
||
would still be needed before any "GEM compliant" marketing claim could
|
||
be honestly made.
|