Files
secs-gem/data/messages.yaml
T
raphael 547fd2116b
tests / build-and-test (push) Failing after 43s
Close COMPLIANCE.md gap: S9 error stream
Adds S9F1, F3, F5, F7, F9, F11, F13 to the message catalog and wires
the two emission paths that the Connection layer can drive without help
from the Router or the application: S9F7 on a body-decode failure and
S9F9 on a T3 transaction-timer timeout.

Catalog (data/messages.yaml -> generated messages.hpp)

  All six MHEAD-carrying messages (F1/F3/F5/F7/F9/F11) use the same
  shape — a single <B 10> body with the offending 10-byte HSMS header.
  S9F13 (conversation timeout) carries <L,2 <A MEXP> <A EDID>>.

Connection-side emissions (src/hsms/connection.cpp)

  emit_s9(function, mhead)   New private helper.  Builds a 9/function/W=0
                              data message whose body is <B 10> with the
                              MHEAD bytes, allocates a fresh sys_bytes,
                              and queues it onto the write path.  No
                              reply is tracked.

  S9F7 on body decode        handle_data wraps Message::from_body in a
                              try/catch.  Previously any decode error
                              closed the connection; now it emits S9F7
                              with the offending header and continues
                              reading.  Reply-side decode failure also
                              emits S9F7 and surfaces the new
                              Error::IllegalData to the waiting
                              ReplyHandler (rather than making the
                              caller wait out T3).

  S9F9 on T3 timeout         The send_request T3 callback rebuilds the
                              original outgoing MHEAD from
                              (device_id, expected_stream,
                              expected_function-1, sys, W=1) and emits
                              S9F9 before invoking the callback with
                              Error::Timeout (unchanged).

What's intentionally not yet wired (logged in COMPLIANCE.md)

  - S9F3 / S9F5 — "unknown stream / function".  These need to live in
    the Router's fallback path, which would require either the Router
    knowing about a Connection-shaped sender or the Connection's
    message wrapper learning which streams the Router has handlers
    for.  Deferred — today the fallback returns SxF0 only.
  - S9F11 — "Data Too Long".  Currently we close on oversized frames;
    we'd need to also build a synthetic 10-byte MHEAD substitute (the
    real header isn't yet available at the point of detection) and
    flush it through close_after_flush.

Tests + docs

  tests/test_messages.cpp  Round-trip every S9F* using a representative
                           10-byte MHEAD literal; check S9F13 carries
                           MEXP + EDID.  +2 cases / +37 assertions.

  COMPLIANCE.md            Error Messages row moved from "no S9 stream"
                           to a detailed status describing what's
                           emitted vs catalog-only.  Coverage matrix
                           expanded per-message (F1/F7/F9/F13 ;
                           F3/F5/F11 🟡 catalog-only).

Build/demo unaffected: 75 cases / 420 assertions pass; the happy-path
demo never trips a decode error or T3, so the S9 path isn't exercised
end-to-end (but unit tests prove the wire shape).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 19:14:41 +02:00

656 lines
18 KiB
YAML

# SECS-II message catalog. Each entry describes one SxFy primary or reply;
# the codegen (tools/gen_messages.py) reads this and emits builders + parsers.
#
# Shape grammar (see tools/gen_messages.py for the full description):
#
# none - header-only (no body)
# scalar: - single Item; cpp_type derived from item_type/enum
# item_type: ASCII | BINARY_BYTE | BOOLEAN | U1..U8 | I1..I8 | F4 | F8 | ITEM
# enum: <C++ enum> - optional
# param: <name> - optional, default "value"
# list: - fixed-arity <L,k>
# fields: [{name, shape}, ...]
# struct_name: <Name> - optional; if set, parser returns the struct
# external_struct: bool - optional; struct is assumed to exist already
# list_of: - variable-arity <L,n>
# element: <shape>
# name: <param-name> - optional, default "values"
#
# Special-case messages whose shape can't be expressed (S1F4's optional-item
# semantics, S5F6's per-row ALCD callback) live in messages_helpers.hpp.
messages:
# =====================================================================
# S1 — Equipment status
# =====================================================================
- id: S1F1
stream: 1
function: 1
w: true
builder: s1f1_are_you_there
- id: S1F2
stream: 1
function: 2
builder: s1f2_on_line_data
body:
kind: list
fields:
- {name: mdln, shape: {kind: scalar, item_type: ASCII}}
- {name: softrev, shape: {kind: scalar, item_type: ASCII}}
- id: S1F3
stream: 1
function: 3
w: true
builder: s1f3_selected_status_request
parser: parse_s1f3
body:
kind: list_of
element: {kind: scalar, item_type: U4}
name: svids
- id: S1F11
stream: 1
function: 11
w: true
builder: s1f11_status_namelist_request
parser: parse_s1f11
body:
kind: list_of
element: {kind: scalar, item_type: U4}
name: svids
- id: S1F12
stream: 1
function: 12
builder: s1f12_status_namelist_data
parser: parse_s1f12
body:
kind: list_of
name: items
element:
kind: list
struct_name: StatusName
fields:
- {name: id, shape: {kind: scalar, item_type: U4}}
- {name: name, shape: {kind: scalar, item_type: ASCII}}
- {name: units, shape: {kind: scalar, item_type: ASCII}}
- id: S1F13
stream: 1
function: 13
w: true
builder: s1f13_establish_comms
body:
kind: list
fields:
- {name: mdln, shape: {kind: scalar, item_type: ASCII}}
- {name: softrev, shape: {kind: scalar, item_type: ASCII}}
- id: S1F14
stream: 1
function: 14
builder: s1f14_establish_comms_ack
body:
kind: list
fields:
- {name: ack, shape: {kind: scalar, item_type: BINARY_BYTE, enum: CommAck}}
- name: identity
shape:
kind: list
struct_name: McAck
fields:
- {name: mdln, shape: {kind: scalar, item_type: ASCII}}
- {name: softrev, shape: {kind: scalar, item_type: ASCII}}
- id: S1F15
stream: 1
function: 15
w: true
builder: s1f15_request_offline
- id: S1F16
stream: 1
function: 16
builder: s1f16_offline_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: OfflineAck, param: ack}
- id: S1F17
stream: 1
function: 17
w: true
builder: s1f17_request_online
- id: S1F18
stream: 1
function: 18
builder: s1f18_online_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: OnlineAck, param: ack}
# =====================================================================
# S2 — Equipment control + reports
# =====================================================================
- id: S2F13
stream: 2
function: 13
w: true
builder: s2f13_ec_request
parser: parse_s2f13
body:
kind: list_of
element: {kind: scalar, item_type: U4}
name: ecids
- id: S2F14
stream: 2
function: 14
builder: s2f14_ec_data
parser: parse_s2f14
body:
kind: list_of
element: {kind: scalar, item_type: ITEM}
name: values
- id: S2F15
stream: 2
function: 15
w: true
builder: s2f15_ec_send
parser: parse_s2f15
body:
kind: list_of
name: sets
element:
kind: list
struct_name: EcSet
fields:
- {name: ecid, shape: {kind: scalar, item_type: U4}}
- {name: value, shape: {kind: scalar, item_type: ITEM}}
- id: S2F16
stream: 2
function: 16
builder: s2f16_ec_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: EquipmentAck, param: eac}
- id: S2F17
stream: 2
function: 17
w: true
builder: s2f17_date_time_request
- id: S2F18
stream: 2
function: 18
builder: s2f18_date_time_data
parser: parse_s2f18
body: {kind: scalar, item_type: ASCII, param: time_str}
- id: S2F29
stream: 2
function: 29
w: true
builder: s2f29_ec_namelist_request
body:
kind: list_of
element: {kind: scalar, item_type: U4}
name: ecids
- id: S2F30
stream: 2
function: 30
builder: s2f30_ec_namelist_data
body:
kind: list_of
name: items
element:
kind: list
struct_name: EcNameRow
fields:
- {name: ecid, shape: {kind: scalar, item_type: U4}}
- {name: name, shape: {kind: scalar, item_type: ASCII}}
- {name: min_str, shape: {kind: scalar, item_type: ASCII}}
- {name: max_str, shape: {kind: scalar, item_type: ASCII}}
- {name: default_str, shape: {kind: scalar, item_type: ASCII}}
- {name: units, shape: {kind: scalar, item_type: ASCII}}
- id: S2F31
stream: 2
function: 31
w: true
builder: s2f31_date_time_set
parser: parse_s2f31
body: {kind: scalar, item_type: ASCII, param: time_str}
- id: S2F32
stream: 2
function: 32
builder: s2f32_date_time_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: TimeAck, param: ack}
- id: S2F33
stream: 2
function: 33
w: true
builder: s2f33_define_report
parser: parse_s2f33
body:
kind: list
struct_name: DefineReportRequest
fields:
- {name: dataid, shape: {kind: scalar, item_type: U4}}
- name: reports
shape:
kind: list_of
element:
kind: list
struct_name: DefineReportEntry
fields:
- {name: rptid, shape: {kind: scalar, item_type: U4}}
- name: vids
shape:
kind: list_of
element: {kind: scalar, item_type: U4}
- id: S2F34
stream: 2
function: 34
builder: s2f34_define_report_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: DefineReportAck, param: ack}
- id: S2F35
stream: 2
function: 35
w: true
builder: s2f35_link_event_report
parser: parse_s2f35
body:
kind: list
struct_name: LinkEventReportRequest
fields:
- {name: dataid, shape: {kind: scalar, item_type: U4}}
- name: links
shape:
kind: list_of
element:
kind: list
struct_name: LinkEventEntry
fields:
- {name: ceid, shape: {kind: scalar, item_type: U4}}
- name: rptids
shape:
kind: list_of
element: {kind: scalar, item_type: U4}
- id: S2F36
stream: 2
function: 36
builder: s2f36_link_event_report_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: LinkEventAck, param: ack}
- id: S2F37
stream: 2
function: 37
w: true
builder: s2f37_enable_event
parser: parse_s2f37
body:
kind: list
struct_name: EnableEventRequest
fields:
- {name: enable, shape: {kind: scalar, item_type: BOOLEAN}}
- name: ceids
shape: {kind: list_of, element: {kind: scalar, item_type: U4}}
- id: S2F38
stream: 2
function: 38
builder: s2f38_enable_event_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: EnableEventAck, param: ack}
- id: S2F41
stream: 2
function: 41
w: true
builder: s2f41_host_command
parser: parse_s2f41
body:
kind: list
struct_name: HostCommand
fields:
- {name: rcmd, shape: {kind: scalar, item_type: ASCII}}
- name: params
shape:
kind: list_of
element:
kind: list
struct_name: CommandParameter
external_struct: true
fields:
- {name: name, shape: {kind: scalar, item_type: ASCII}}
- {name: value, shape: {kind: scalar, item_type: ITEM}}
- id: S2F42
stream: 2
function: 42
builder: s2f42_host_command_ack
parser: parse_s2f42
body:
kind: list
struct_name: HostCommandReply
fields:
- {name: hcack, shape: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck}}
- name: cpacks
shape:
kind: list_of
element:
kind: list
struct_name: CommandParameterAck
fields:
- {name: name, shape: {kind: scalar, item_type: ASCII}}
- {name: code, shape: {kind: scalar, item_type: BINARY_BYTE}}
# S2F43 / S2F44 — Reset Spooling (E30 §6.22, configure spoolable streams).
- id: S2F43
stream: 2
function: 43
w: true
builder: s2f43_reset_spooling
parser: parse_s2f43
body:
kind: list_of
element: {kind: scalar, item_type: BINARY_BYTE}
name: streams
- id: S2F44
stream: 2
function: 44
builder: s2f44_reset_spooling_ack
parser: parse_s2f44
body:
kind: list
struct_name: ResetSpoolingReply
fields:
- {name: rspack, shape: {kind: scalar, item_type: BINARY_BYTE, enum: ResetSpoolAck}}
- name: per_stream
shape:
kind: list_of
element:
kind: list
struct_name: ResetSpoolStreamAck
fields:
- {name: stream, shape: {kind: scalar, item_type: BINARY_BYTE}}
- {name: strack, shape: {kind: scalar, item_type: BINARY_BYTE}}
# =====================================================================
# S5 — Alarms
# =====================================================================
- id: S5F1
stream: 5
function: 1
w: true
builder: s5f1_alarm_report
parser: parse_s5f1
body:
kind: list
struct_name: AlarmReport
fields:
- {name: alcd, shape: {kind: scalar, item_type: BINARY_BYTE}}
- {name: alid, shape: {kind: scalar, item_type: U4}}
- {name: altx, shape: {kind: scalar, item_type: ASCII}}
- id: S5F2
stream: 5
function: 2
builder: s5f2_alarm_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack}
- id: S5F3
stream: 5
function: 3
w: true
builder: s5f3_enable_alarm
parser: parse_s5f3
body:
kind: list
struct_name: EnableAlarmRequest
fields:
- {name: aled, shape: {kind: scalar, item_type: BINARY_BYTE}}
- {name: alid, shape: {kind: scalar, item_type: U4}}
- id: S5F4
stream: 5
function: 4
builder: s5f4_enable_alarm_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: AlarmAck, param: ack}
- id: S5F5
stream: 5
function: 5
w: true
builder: s5f5_list_alarms_request
parser: parse_s5f5
body:
kind: list_of
element: {kind: scalar, item_type: U4}
name: alids
# S5F6 is hand-written (per-row ALCD requires an active-callback API).
# =====================================================================
# S6 — Data collection
# =====================================================================
- id: S6F11
stream: 6
function: 11
w: true
builder: s6f11_event_report
parser: parse_s6f11
body:
kind: list
struct_name: EventReportMessage
fields:
- {name: dataid, shape: {kind: scalar, item_type: U4}}
- {name: ceid, shape: {kind: scalar, item_type: U4}}
- name: reports
shape:
kind: list_of
element:
kind: list
struct_name: ReportData
external_struct: true
fields:
- {name: rptid, shape: {kind: scalar, item_type: U4}}
- name: values
shape: {kind: list_of, element: {kind: scalar, item_type: ITEM}}
- id: S6F12
stream: 6
function: 12
builder: s6f12_event_report_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: EventReportAck, param: ack}
# S6F23 / S6F24 — Request Spooled Data (E30 §6.22; host transmits or purges).
- id: S6F23
stream: 6
function: 23
w: true
builder: s6f23_request_spool_data
parser: parse_s6f23
body: {kind: scalar, item_type: BINARY_BYTE, enum: SpoolRequestCode, param: rsdc}
- id: S6F24
stream: 6
function: 24
builder: s6f24_request_spool_data_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: SpoolRequestAck, param: rsda}
# =====================================================================
# S7 — Process programs
# =====================================================================
- id: S7F3
stream: 7
function: 3
w: true
builder: s7f3_process_program_send
parser: parse_s7f3
body:
kind: list
struct_name: ProcessProgram
fields:
- {name: ppid, shape: {kind: scalar, item_type: ASCII}}
- {name: ppbody, shape: {kind: scalar, item_type: BINARY}}
- id: S7F4
stream: 7
function: 4
builder: s7f4_process_program_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: ProcessProgramAck, param: ack}
- id: S7F5
stream: 7
function: 5
w: true
builder: s7f5_process_program_request
parser: parse_s7f5
body: {kind: scalar, item_type: ASCII, param: ppid}
- id: S7F6
stream: 7
function: 6
builder: s7f6_process_program_data
parser: parse_s7f6
body:
kind: list
struct_name: ProcessProgram
fields:
- {name: ppid, shape: {kind: scalar, item_type: ASCII}}
- {name: ppbody, shape: {kind: scalar, item_type: BINARY}}
- id: S7F19
stream: 7
function: 19
w: true
builder: s7f19_current_eppd_request
- id: S7F20
stream: 7
function: 20
builder: s7f20_current_eppd_data
parser: parse_s7f20
body:
kind: list_of
element: {kind: scalar, item_type: ASCII}
name: ppids
# =====================================================================
# S9 — Protocol error notifications (E5). All carry the 10-byte
# offending message header (MHEAD) as a single BINARY item, except
# S9F13 which carries <L,2 <A MEXP> <A EDID>>. These are
# equipment-initiated (typically W=0) and host-side acks are not
# mandatory; we emit them on the wire but don't expect replies.
# =====================================================================
- id: S9F1
stream: 9
function: 1
builder: s9f1_unrecognized_device_id
parser: parse_s9f1
body: {kind: scalar, item_type: BINARY, param: mhead}
- id: S9F3
stream: 9
function: 3
builder: s9f3_unrecognized_stream_type
parser: parse_s9f3
body: {kind: scalar, item_type: BINARY, param: mhead}
- id: S9F5
stream: 9
function: 5
builder: s9f5_unrecognized_function_type
parser: parse_s9f5
body: {kind: scalar, item_type: BINARY, param: mhead}
- id: S9F7
stream: 9
function: 7
builder: s9f7_illegal_data
parser: parse_s9f7
body: {kind: scalar, item_type: BINARY, param: mhead}
- id: S9F9
stream: 9
function: 9
builder: s9f9_transaction_timer_timeout
parser: parse_s9f9
body: {kind: scalar, item_type: BINARY, param: mhead}
- id: S9F11
stream: 9
function: 11
builder: s9f11_data_too_long
parser: parse_s9f11
body: {kind: scalar, item_type: BINARY, param: mhead}
- id: S9F13
stream: 9
function: 13
builder: s9f13_conversation_timeout
parser: parse_s9f13
body:
kind: list
struct_name: ConversationTimeout
fields:
- {name: mexp, shape: {kind: scalar, item_type: ASCII}}
- {name: edid, shape: {kind: scalar, item_type: ASCII}}
# =====================================================================
# S10 — Terminal services
# =====================================================================
- id: S10F1
stream: 10
function: 1
w: true
builder: s10f1_terminal_display_single
parser: parse_s10f1
body:
kind: list
struct_name: TerminalDisplay
fields:
- {name: tid, shape: {kind: scalar, item_type: BINARY_BYTE}}
- {name: text, shape: {kind: scalar, item_type: ASCII}}
- id: S10F2
stream: 10
function: 2
builder: s10f2_terminal_display_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: TerminalAck, param: ack}
- id: S10F3
stream: 10
function: 3
w: true
builder: s10f3_terminal_display_single
parser: parse_s10f3
body:
kind: list
struct_name: TerminalDisplay
fields:
- {name: tid, shape: {kind: scalar, item_type: BINARY_BYTE}}
- {name: text, shape: {kind: scalar, item_type: ASCII}}
- id: S10F4
stream: 10
function: 4
builder: s10f4_terminal_display_ack
body: {kind: scalar, item_type: BINARY_BYTE, enum: TerminalAck, param: ack}