AA: catalog growth — S7F1/F17, S6F5-F22, S2F21, S12F9-F18

Adds the SECS-II messages secsgem-py 0.3.0 ships but our C++ catalog
didn't have, plus the alternative wafer-map formats from E5 §13.
None of these were strictly required for GEM core compliance, but
they're the messages a host might send to a conformant equipment.

  * S7F1/F2 — Process Program Load Inquire / Grant.  Equipment-side
    space-and-policy check before a host commits to S7F3.
  * S7F17/F18 — Delete Process Program.  Empty list = delete-all.
  * S6F5/F6 — Multi-block Data Send Inquire / Grant (with MultiBlockGrant
    enum: Ok/Busy/NoSpace/DuplicateMsg/BadMsg).
  * S6F7/F8 — Data Transfer Request / Send.  Host pulls a DATAID;
    equipment replies with the nested DS/DV structure.
  * S6F15/F16 — Event Report Request (host-initiated).  Reply mirrors
    the unsolicited S6F11.
  * S6F19/F20 — Individual Report Request (RPTID -> values).
  * S6F21/F22 — Annotated Individual Report Request (RPTID -> (VID, value)).
  * S2F21/F22 — Legacy Remote Command (no parameter list).  Delegates
    to the same HostCommandRegistry as S2F41.
  * S12F9/F10 — Map Data Send (array format, MAPFT=1).
  * S12F11/F12 — Map Data Send (coordinate format, MAPFT=2).
  * S12F13/F14, F15/F16, F17/F18 — Map Data Request variants for the
    row, array, and coordinate formats.

11 new round-trip tests; suite at 289 cases / 1495 assertions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 23:30:51 +02:00
parent 2d60571a9c
commit 4197cdfb25
4 changed files with 666 additions and 1 deletions
+133
View File
@@ -484,6 +484,139 @@ TEST_CASE("S7F3 process-program send round-trip") {
CHECK(parsed->ppbody == "step1\nstep2\n");
}
TEST_CASE("S7F1 / S7F2 PP-load inquire/grant round-trip") {
auto m = s7f1_pp_load_inquire("RECIPE-X", 4096);
CHECK(m.stream == 7);
CHECK(m.function == 1);
CHECK(m.reply_expected);
auto parsed = parse_s7f1(m);
REQUIRE(parsed.has_value());
CHECK(parsed->ppid == "RECIPE-X");
CHECK(parsed->length == 4096);
auto grant = s7f2_pp_load_grant(ProcessProgramAck::Accept);
CHECK(*ack_byte(grant) == 0);
}
TEST_CASE("S2F21 / S2F22 legacy remote command round-trip") {
auto req = s2f21_remote_command("PAUSE");
CHECK(req.stream == 2);
CHECK(req.function == 21);
CHECK(req.reply_expected);
auto parsed = parse_s2f21(req);
REQUIRE(parsed.has_value());
CHECK(*parsed == "PAUSE");
CHECK(*ack_byte(s2f22_remote_command_ack(HostCmdAck::Accept)) == 0);
}
TEST_CASE("S6F5 / S6F6 multi-block inquire / grant round-trip") {
auto req = s6f5_multi_block_inquire(42, 2048);
auto parsed = parse_s6f5(req);
REQUIRE(parsed.has_value());
CHECK(parsed->dataid == 42);
CHECK(parsed->datalength == 2048);
CHECK(*ack_byte(s6f6_multi_block_grant(MultiBlockGrant::Ok)) == 0);
CHECK(*ack_byte(s6f6_multi_block_grant(MultiBlockGrant::Busy)) == 1);
}
TEST_CASE("S6F7 round-trip carries DATAID") {
auto req = s6f7_data_transfer_request(7);
auto parsed = parse_s6f7(req);
REQUIRE(parsed.has_value());
CHECK(*parsed == 7);
}
TEST_CASE("S6F15 / S6F16 event report request/data round-trip") {
auto req = s6f15_event_report_request(300);
auto parsed = parse_s6f15(req);
REQUIRE(parsed.has_value());
CHECK(*parsed == 300);
EventReportMessage erm{0, 300, {ReportData{1, {s2::Item::u4(42)}}}};
auto data = s6f16_event_report_data(erm);
auto out = parse_s6f16(data);
REQUIRE(out.has_value());
CHECK(out->ceid == 300);
REQUIRE(out->reports.size() == 1);
CHECK(out->reports[0].rptid == 1);
}
TEST_CASE("S6F19 / S6F20 individual report round-trip") {
auto req = s6f19_individual_report_request(7);
auto parsed = parse_s6f19(req);
REQUIRE(parsed.has_value());
CHECK(*parsed == 7);
auto reply = s6f20_individual_report_data({s2::Item::u4(99), s2::Item::ascii("ok")});
auto out = parse_s6f20(reply);
REQUIRE(out.has_value());
CHECK(out->size() == 2);
}
TEST_CASE("S6F21 / S6F22 annotated report round-trip") {
auto req = s6f21_annotated_report_request(7);
auto parsed = parse_s6f21(req);
REQUIRE(parsed.has_value());
CHECK(*parsed == 7);
std::vector<AnnotatedValue> rows{{1, s2::Item::u4(42)}, {2, s2::Item::ascii("hi")}};
auto reply = s6f22_annotated_report_data(rows);
auto out = parse_s6f22(reply);
REQUIRE(out.has_value());
REQUIRE(out->size() == 2);
CHECK((*out)[0].vid == 1);
CHECK((*out)[1].vid == 2);
}
TEST_CASE("S12F9 / S12F10 array map round-trip") {
MapDataArray a{"WAFER-1", 0x01, ReferencePoint{0, 0}, std::string("\x00\x01\x02", 3)};
auto m = s12f9_map_data_send_array(a);
auto parsed = parse_s12f9(m);
REQUIRE(parsed.has_value());
CHECK(parsed->mid == "WAFER-1");
CHECK(parsed->idtyp == 0x01);
CHECK(parsed->binlt.size() == 3);
CHECK(*ack_byte(s12f10_map_data_send_array_ack(MapDataAck::Accept)) == 0);
}
TEST_CASE("S12F11 / S12F12 coordinate map round-trip") {
MapDataCoord c{"WAFER-1", 0x01,
{{ReferencePoint{1, 2}, std::string("\x05", 1)},
{ReferencePoint{3, 4}, std::string("\x06", 1)}}};
auto m = s12f11_map_data_send_coord(c);
auto parsed = parse_s12f11(m);
REQUIRE(parsed.has_value());
CHECK(parsed->mid == "WAFER-1");
REQUIRE(parsed->entries.size() == 2);
CHECK(parsed->entries[0].xypos.x == 1);
CHECK(parsed->entries[1].xypos.y == 4);
CHECK(*ack_byte(s12f12_map_data_send_coord_ack(MapDataAck::FormatError)) == 1);
}
TEST_CASE("S12F13/F14 + F15/F16 + F17/F18 request shapes round-trip") {
MapDataIdentifier id{"WAFER-1", 0x01};
CHECK(parse_s12f13(s12f13_map_data_request_row(id))->mid == "WAFER-1");
CHECK(parse_s12f15(s12f15_map_data_request_array(id))->idtyp == 0x01);
MapDataCoordRequest cr{"WAFER-1", 0x01, 0xFF};
auto parsed_cr = parse_s12f17(s12f17_map_data_request_coord(cr));
REQUIRE(parsed_cr.has_value());
CHECK(parsed_cr->sdbin == 0xFF);
}
TEST_CASE("S7F17 / S7F18 delete PP round-trip") {
auto m = s7f17_delete_pp_send({"R1", "R2", "R3"});
auto parsed = parse_s7f17(m);
REQUIRE(parsed.has_value());
REQUIRE(parsed->size() == 3);
CHECK((*parsed)[0] == "R1");
CHECK((*parsed)[2] == "R3");
auto empty = parse_s7f17(s7f17_delete_pp_send({}));
REQUIRE(empty.has_value());
CHECK(empty->empty());
CHECK(*ack_byte(s7f18_delete_pp_ack(ProcessProgramAck::Accept)) == 0);
CHECK(*ack_byte(s7f18_delete_pp_ack(ProcessProgramAck::PpidNotFound)) == 4);
}
TEST_CASE("S2F25 / S2F26 loopback diagnostic round-trip") {
// Arbitrary binary payload — host sends, equipment echoes back.
const std::string payload("\x00\x01\x02\xFE\xFF some text", 14);