Files
secs-gem/docs/30_repository_tour.md
raphael 0355c73211
tests / build-and-test (push) Successful in 2m9s
tests / thread-sanitizer (push) Successful in 2m35s
tests / tshark-dissector (push) Successful in 2m17s
tests / secs4j-interop (push) Successful in 1m6s
tests / libfuzzer (push) Successful in 3m7s
docs: refresh stale file paths after store/ reorg + gen_messages rename
generate_messages.py → gen_messages.py and several gem/ headers moved
under gem/store/ (carrier_store.hpp → store/carriers.hpp, etc.);
e84.hpp split into e84_state.hpp.  The guided-tour chapters still
pointed at the old paths — relink them so the deep-link footnotes
resolve.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 23:23:42 +02:00

232 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 30 — Repository tour
← [19 E42 + E148 + S9 — Misc](19_e42_e148_s9_misc.md) | [Back to index](00_index.md) | Next: [31 Spec-as-data + codegen](31_spec_as_data_and_codegen.md) →
You've seen what every SEMI standard *does*. Now we shift to how
this **codebase** is laid out. This chapter answers: when you
`git clone` this repo, what are you looking at?
The repo is small — about 15 k lines of C++ + tests + tooling. It
fits in your head with a little patience. By the end of this
chapter you'll know:
- What each top-level directory contains.
- Which binaries get built.
- The dependency graph between modules.
- How the build system finds and links them.
---
## Top-level layout
```
secs-gem/
├── README.md One-page project summary.
├── LICENSE Proprietary terms.
├── CMakeLists.txt Build config (CMake 3.16+, single file).
├── Dockerfile Ubuntu 24.04 + g++-13 + libasio + yaml-cpp.
├── docker-compose.yml Multi-container demo wiring.
├── .gitea/workflows/ci.yml CI pipeline.
├── include/secsgem/ Public headers. All API here.
│ ├── secs2/ E5 codec + SML.
│ ├── hsms/ E37 transport (TCP + framing).
│ ├── secsi/ E4 transport (FSM + TCP tunnel).
│ ├── config/ YAML loader + multi-error validator.
│ ├── metrics/ Prometheus exporter.
│ ├── endpoint.hpp asio::ip::tcp::endpoint factory.
│ └── gem/ E30 + every GEM 300 standard.
│ ├── store/ Per-domain bundles (SVIDs, alarms, …).
│ └── *.hpp State machines + composers.
├── src/ Implementations. Mirrors include/.
│ ├── secs2/{codec,sml}.cpp
│ ├── hsms/{header,connection}.cpp
│ ├── secsi/{header,block,protocol,tcp_transport}.cpp
│ ├── config/...
│ ├── gem/...
│ └── endpoint.cpp
├── apps/ Standalone binaries.
│ ├── secs_server.cpp Passive equipment (demo + integration target).
│ ├── secs_client.cpp Active host driving the demo flow.
│ ├── secs_conformance.cpp 47-check wire-level conformance harness.
│ ├── secs_interop_probe.cpp Probe against secsgem-py passive equip.
│ ├── secs_bench.cpp Throughput / latency / memory bench.
│ ├── fuzz_secs2_decode.cpp libFuzzer harness for secs2::decode.
│ └── fuzz_sml_parse.cpp libFuzzer harness for try_parse_sml.
├── tests/ doctest unit + integration tests.
│ └── test_*.cpp 50 files, 445 cases, 2753 assertions.
├── data/ YAML configs (the spec-as-data).
│ ├── messages.yaml SECS-II message catalog (164 msgs).
│ ├── control_state.yaml E30 §6.2 transition table.
│ ├── process_job_state.yaml E40 transition table.
│ ├── control_job_state.yaml E94 transition table.
│ └── equipment.yaml Demo SVIDs/ECIDs/CEIDs/alarms/recipes.
├── tools/ Build-time scripts.
│ └── gen_messages.py Codegen: messages.yaml → messages.hpp.
├── interop/ External-validator harnesses.
│ ├── README.md Harness-by-harness detail.
│ ├── host_vs_cpp_server.py secsgem-py active host driving us.
│ ├── passive_equipment.py secsgem-py passive equipment for us to drive.
│ ├── raw_gem300_harness.py Raw S3/S14/S16/S12 round-trip.
│ ├── tshark_validate.sh pcap + tshark HSMS dissector check.
│ ├── secs4j_validate.sh secs4java8 (Java) cross-validation.
│ └── secs4j/ Dockerfile + harness for secs4java8.
├── examples/
│ └── pvd_tool/ Worked vendor example: fictional PVD tool.
│ ├── README.md What the example shows.
│ ├── equipment.yaml Realistic SVIDs/ECIDs/CEIDs/alarms/recipes.
│ └── main.cpp Sensor sim, recipe runner, alarm monitor.
└── docs/ This guide + reference docs.
├── 00_index.md The series TOC.
├── 0151_*.md Tutorial chapters.
├── ARCHITECTURE.md One-page architecture overview.
├── COMPLIANCE.md Per-capability audit.
├── INTEGRATION.md Vendor-side production deploy.
├── PROOFS.md 8 commands proving feature-completeness.
├── VERIFICATION.md External-validator test plan.
├── BENCHMARKS.md Performance envelope.
├── MES_INTEROP.md Commercial-MES day-1 punch list.
├── SECURITY.md nftables / stunnel / minisign configs.
├── GLOSSARY.md SEMI vocabulary cheat sheet.
└── FAQ.md Canonical answers.
```
---
## The dependency graph
```
data/*.yaml
┌─────────────┼──────────────────┐
│ (codegen) │ (runtime load) │
▼ ▼ ▼
generated/messages.hpp config::loader
│ │
└──────────► gem::EquipmentDataModel
│ used by
gem::Router
│ wraps
secs2::Message ◄─── codec / SML
│ over
hsms::Connection / secsi::TcpTransport
TCP socket
```
Read it bottom-up: a TCP socket carries bytes; `hsms::Connection`
frames them into `secs2::Message`s; `gem::Router` dispatches by
`(stream, function)` to handlers; handlers read/write
`EquipmentDataModel`; the model composes per-domain stores; the
stores were built from the YAML at startup.
No layer ever calls *up* the graph. `secs2::Item` has no idea
HSMS exists. `hsms::Connection` doesn't know about CEIDs.
`gem::Router` doesn't know whether the bytes came over HSMS or
SECS-I. Strict layering is what keeps the codebase small.
---
## The binaries
Built by [`CMakeLists.txt`](../CMakeLists.txt) (one file, ~250
lines). Each binary lives in `build/` after `cmake --build`.
| Binary | Source | What it does |
|----------------------|-----------------------------------------------------------------|-------------------------------------------------------|
| `secs_server` | [`apps/secs_server.cpp`](../apps/secs_server.cpp) | Passive equipment. Listens on TCP, dispatches via Router. |
| `secs_client` | [`apps/secs_client.cpp`](../apps/secs_client.cpp) | Active host. Drives ~24 transactions in the demo. |
| `secs_conformance` | [`apps/secs_conformance.cpp`](../apps/secs_conformance.cpp) | 47 wire-level conformance checks against a live server. |
| `secs_interop_probe` | [`apps/secs_interop_probe.cpp`](../apps/secs_interop_probe.cpp) | Active host probing a secsgem-py passive equipment. |
| `secs_bench` | [`apps/secs_bench.cpp`](../apps/secs_bench.cpp) | Throughput / latency / memory harness. |
| `secsgem_tests` | All `tests/*.cpp` | The 445-case doctest binary. |
| `fuzz_secs2_decode` | [`apps/fuzz_secs2_decode.cpp`](../apps/fuzz_secs2_decode.cpp) | libFuzzer (clang only, opt-in `-DSECSGEM_FUZZ=ON`). |
| `fuzz_sml_parse` | [`apps/fuzz_sml_parse.cpp`](../apps/fuzz_sml_parse.cpp) | libFuzzer for the SML parser. |
A worked example binary `pvd_tool` (from `examples/pvd_tool/`) is
also built by the same `CMakeLists.txt` when the example is
included.
---
## How the build system finds everything
`CMakeLists.txt` does five things in order:
1. **Pull in dependencies**`find_package(Threads)`,
`find_package(yaml-cpp)`, `FetchContent` for doctest. Standalone
Asio is header-only (no link step).
2. **Run codegen** — invokes `tools/gen_messages.py` to turn
`data/messages.yaml` into `build/generated/secsgem/gem/messages.hpp`.
Listed as a custom command so it re-runs when `messages.yaml`
changes.
3. **Build the library**`add_library(secsgem ...)` with every
source under `src/` plus the generated header.
4. **Build the apps** — one `add_executable` per `apps/*.cpp`,
each linking against `secsgem`.
5. **Build the tests**`add_executable(secsgem_tests ...)` with
every `tests/*.cpp`, linked against doctest + `secsgem`.
Build flags:
- **`-DSECSGEM_TSAN=ON`** — adds `-fsanitize=thread` to a
separate build dir. CI runs this lane.
- **`-DSECSGEM_FUZZ=ON`** — requires clang; adds libFuzzer + ASan +
UBSan; builds the two fuzz harnesses.
Everything else (Release / Debug, parallelism, output dirs) is
standard CMake.
---
## Test layout
50 test files; 445 test cases; 2 753 assertions. One file per
concern. Naming is `test_<thing>.cpp` consistently:
- `test_secs2.cpp`, `test_e5_kat.cpp`, `test_sml.cpp`,
`test_messages.cpp` — codec.
- `test_hsms*.cpp` (5 files), `test_secsi*.cpp` (3 files) — transport.
- `test_control_state.cpp`, `test_communication_state.cpp`,
`test_data_model.cpp`, `test_host_handler.cpp`, `test_loader.cpp`,
`test_config_validate.cpp` — E30.
- `test_process_jobs.cpp`, `test_control_jobs.cpp`,
`test_carriers.cpp`, `test_substrates.cpp`, `test_ept.cpp`,
`test_modules.cpp`, `test_cem_objects.cpp`, `test_e84*.cpp`,
`test_e42_formatted_pp.cpp` — GEM 300.
- `test_*_persistence.cpp` (4) — file-backed journal.
- `test_robustness_fuzz.cpp` — randomized property test.
- `test_thread_safety.cpp` — TSan-validated single-threaded contract.
- `test_metrics_prometheus.cpp` — Prometheus exporter.
- `test_wire_ceid_emission.cpp` — CEID firings observed on a real socket.
- `test_live_gem300.cpp`, `test_gem300_scenario.cpp` — multi-FSM cascades.
Full per-standard breakdown:
[`docs/PROOFS.md`](PROOFS.md) "Per-standard test coverage" table.
---
## Where to go next
Now that you know what's where, the next chapter explains the
*philosophy* that makes the codebase this small: the **spec-as-data**
principle, and how the YAML files + codegen + runtime loader work
together so adding a new SVID / state / message rarely requires C++.
Next: [→ 31 Spec-as-data + codegen](31_spec_as_data_and_codegen.md)