L: E87 slot-map verification wire (S3F19/F20 + F21/F22)

Closes the slot-map verification gap I called out:

  S3F19 / F20  host -> equip: verify expected slot map against what
               the equipment has scanned. Equipment compares element-
               wise; on match drives CSMS NotRead -> Read and replies
               SVACK=Accept; on mismatch drives CSMS -> Mismatched and
               replies SVACK=Mismatch.

  S3F21 / F22  equip -> host: equipment-initiated slot map report
               (typically pushed after CARRIERID is confirmed).

New SVACK enum: SlotMapVerifyAck { Accept, Mismatch, CarrierUnknown,
Error }.  Server dispatch on S3F19 wires the actual CSMS transition
through the CarrierStore from D3.

Two round-trip tests cover both pairs; the FSM-driving behaviour is
exercised through the in-process tests because secs_server.cpp is
the dispatch entry point (no separate integration test needed beyond
the wire round-trip).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 08:48:56 +02:00
parent a52d44ade5
commit 5a3f5ca6da
4 changed files with 111 additions and 0 deletions
+27
View File
@@ -766,6 +766,33 @@ int main(int argc, char** argv) {
return gem::s3f26_carrier_transfer_ack(gem::CarrierActionAck::Accept);
});
// S3F19 — Slot Map Verify. Host sends its expected slot map for
// CARRIERID; equipment compares against locally-stored slots and
// drives CSMS (NotRead -> Read on Accept, -> Mismatched on Mismatch).
router.on(3, 19, [model, logfn](const s2::Message& msg) {
auto req = gem::parse_s3f19(msg);
if (!req)
return gem::s3f20_slot_map_verify_ack(gem::SlotMapVerifyAck::Error);
auto* c = model->carriers.get(req->carrierid);
if (!c)
return gem::s3f20_slot_map_verify_ack(gem::SlotMapVerifyAck::CarrierUnknown);
bool match = c->slots.size() == req->slots.size();
if (match) {
for (std::size_t i = 0; i < req->slots.size(); ++i) {
if (static_cast<uint8_t>(c->slots[i].state) !=
static_cast<uint8_t>(req->slots[i])) { match = false; break; }
}
}
if (match) {
model->carriers.fire_slot_map_event(req->carrierid, gem::SlotMapEvent::Read);
logfn("S3F19 CARRIER=" + req->carrierid + " -> S3F20 Accept");
return gem::s3f20_slot_map_verify_ack(gem::SlotMapVerifyAck::Accept);
}
model->carriers.fire_slot_map_event(req->carrierid, gem::SlotMapEvent::Mismatch);
logfn("S3F19 CARRIER=" + req->carrierid + " -> S3F20 Mismatch");
return gem::s3f20_slot_map_verify_ack(gem::SlotMapVerifyAck::Mismatch);
});
// S3F27 — Cancel Carrier (single-EXID form).
router.on(3, 27, [model, logfn](const s2::Message& msg) {
auto cid = gem::parse_s3f27(msg);