feat(mdbook): publish spec via mdBook on GitHub Pages

Set up the protocol spec as an mdBook and add a GitHub Actions
workflow that builds it on every push/PR and deploys to GitHub
Pages on main. The repo is now both a machine-readable corpus and
a browsable, searchable site.

Structure changes (one-time, mechanical):

- Rename the spec source directory from `spec/` to `src/` to match
  mdBook's default convention. All 21 chapter files move; their
  internal relative cross-links are unchanged.
- Add `src/SUMMARY.md` as the mdBook table of contents, grouping
  chapters under Wire Format, Payloads, Cryptography & Identity,
  and Routing & Higher-Layer Protocols.
- Add `book.toml` at repo root: title, author, search enabled,
  fold enabled, edit-on-GitHub links, navy theme.
- Add `.github/workflows/mdbook.yml`: build on push + PR, deploy
  on push to main only (configure-pages + upload-pages-artifact +
  deploy-pages from actions/).
- Add `/book/` to .gitignore.

Reference updates so the old path is fully retired:

- Every `spec_ref` in `corpus/**/*.json` rewritten from `spec/` to
  `src/` (54 files).
- Schema description text and example path in
  `corpus/schema/test-vector.schema.json`.
- `README.md`: prominent link to the published site near the top;
  "What's Here" updated for the new layout.
- `CONTRIBUTING.md`: spec-editing instructions point at `src/`
  and note the SUMMARY.md requirement.
- `CLAUDE.md`: directory-layout notes updated.

CHANGELOG.md historical entries intentionally keep their `spec/`
paths since those record what the paths were at the time of those
commits.

After this lands, the user needs to enable GitHub Pages in repo
settings with source = "GitHub Actions" (one-time, manual). The
workflow will then publish to https://swaits.github.io/meshcore-spec/
on every push to main.

Verified locally: `mdbook build` produces a clean book/ tree with
all 21 chapters rendered and the SUMMARY-driven sidebar populated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.7 committed 2026-05-12 09:36:28 -06:00
1 parent 75fb228909
commit d785e35919
79 files changed
+173 -56

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Bridge Protocol - Framing",
"spec_ref": "spec/20-bridge-protocol.md",
"spec_ref": "src/20-bridge-protocol.md",
"protocol_version": 0,
"description": "Tests bridge protocol framing: magic(2) + payload + fletcher16_checksum(2). RS232 variant adds length(2) between magic and payload.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Companion Protocol - Commands",
"spec_ref": "spec/18-companion-protocol.md",
"spec_ref": "src/18-companion-protocol.md",
"protocol_version": 0,
"description": "Tests companion protocol command type encoding for various command types.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Companion Protocol - Framing",
"spec_ref": "spec/18-companion-protocol.md",
"spec_ref": "src/18-companion-protocol.md",
"protocol_version": 0,
"description": "Tests companion protocol frame encoding: type byte followed by variable-length data.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Companion Protocol - Responses",
"spec_ref": "spec/18-companion-protocol.md",
"spec_ref": "src/18-companion-protocol.md",
"protocol_version": 0,
"description": "Tests companion protocol response type encoding.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "AES-128-ECB Block Padding",
"spec_ref": "spec/14-crypto.md",
"spec_ref": "src/14-crypto.md",
"protocol_version": 0,
"description": "Tests AES-128-ECB zero-padding for partial blocks. MeshCore pads the final block with zeros before encryption.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "AES-128-ECB Encryption",
"spec_ref": "spec/14-crypto.md",
"spec_ref": "src/14-crypto.md",
"protocol_version": 0,
"description": "Standalone AES-128-ECB encryption test vectors. Key is the first 16 bytes of the shared secret. These vectors test the raw encryption operation without MAC.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "ECDH Shared Secret (X25519)",
"spec_ref": "spec/15-identity.md",
"spec_ref": "src/15-identity.md",
"protocol_version": 0,
"description": "X25519 ECDH key exchange test vectors. MeshCore uses Ed25519 keys transposed to X25519 for key exchange. The resulting 32-byte shared secret is used as: AES key (bytes 0-15) and HMAC key (bytes 0-31).",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Ed25519 Sign/Verify",
"spec_ref": "spec/15-identity.md",
"spec_ref": "src/15-identity.md",
"protocol_version": 0,
"description": "Ed25519 signature test vectors. MeshCore uses Ed25519 for advertisement signing. These vectors use RFC 8032 test keypairs for cross-validation.",
"vectors": [
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypt-then-MAC Roundtrip",
"spec_ref": "spec/14-crypto.md",
"spec_ref": "src/14-crypto.md",
"protocol_version": 0,
"description": "Full encrypt-then-MAC pipeline test vectors. Each vector provides the shared secret, plaintext, and expected MAC+ciphertext output. Implementations should verify: (1) encrypt produces the ciphertext, (2) HMAC produces the MAC, (3) MACThenDecrypt recovers the plaintext.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "HMAC-SHA256 Truncated to 2 Bytes",
"spec_ref": "spec/14-crypto.md",
"spec_ref": "src/14-crypto.md",
"protocol_version": 0,
"description": "Tests HMAC-SHA256 computation with 32-byte key, truncated to CIPHER_MAC_SIZE (2 bytes). The HMAC key is the full 32-byte shared secret (PUB_KEY_SIZE), not just the 16-byte AES key.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "SHA-256 ACK CRC Computation",
"spec_ref": "spec/04-payload-ack.md",
"spec_ref": "src/04-payload-ack.md",
"protocol_version": 0,
"description": "Tests MeshCore ACK CRC computation: SHA-256(timestamp_le || txt_type_attempt || text || sender_pub_key)[0..3]. Each vector presents the full ACK packet that would be returned, along with the crypto_context showing the plaintext fed to SHA-256. Vectors ack-crc-001..004 share timestamp/text/pub_key and differ only in the attempt nibble of txt_type_attempt, demonstrating that each retry produces a distinct ACK CRC.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "SHA-256 Basic",
"spec_ref": "spec/14-crypto.md",
"spec_ref": "src/14-crypto.md",
"protocol_version": 0,
"description": "Basic SHA-256 hash computation test vectors. MeshCore uses SHA-256 for packet hashing, channel hash derivation, and HMAC.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "SHA-256 Packet Hash",
"spec_ref": "spec/16-packet-hash.md",
"spec_ref": "src/16-packet-hash.md",
"protocol_version": 0,
"description": "Tests MeshCore packet hash computation: SHA-256(payload_type || payload)[0..7]. TRACE packets additionally include path_len in the hash input.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "KISS Protocol - Framing",
"spec_ref": "spec/19-kiss-protocol.md",
"spec_ref": "src/19-kiss-protocol.md",
"protocol_version": 0,
"description": "Tests KISS frame encoding including FEND delimiters, byte stuffing (FESC sequences), and type byte encoding.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "KISS Protocol - SetHardware Extensions",
"spec_ref": "spec/19-kiss-protocol.md",
"spec_ref": "src/19-kiss-protocol.md",
"protocol_version": 0,
"description": "Tests MeshCore-specific SetHardware (0x06) sub-commands and responses.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "KISS Protocol - Standard Commands",
"spec_ref": "spec/19-kiss-protocol.md",
"spec_ref": "src/19-kiss-protocol.md",
"protocol_version": 0,
"description": "Tests standard KISS commands (TXDELAY, Persistence, SlotTime, etc.).",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "ACK Payload - Basic",
"spec_ref": "spec/04-payload-ack.md",
"spec_ref": "src/04-payload-ack.md",
"protocol_version": 0,
"description": "Basic ACK payload encoding/decoding tests with various CRC values.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "ACK Payload - Boundary Values",
"spec_ref": "spec/04-payload-ack.md",
"spec_ref": "src/04-payload-ack.md",
"protocol_version": 0,
"description": "ACK CRC boundary value tests including all-zeros, all-ones, and single-bit patterns.",
"vectors": [
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement App Data - Combined Optional Fields",
"spec_ref": "spec/05-payload-advert.md",
"spec_ref": "src/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests app_data with multiple optional fields present simultaneously. Fields appear in order: flags, location (lat+lon), feat1, feat2, name.",
"vectors": [
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement App Data - Location",
"spec_ref": "spec/05-payload-advert.md",
"spec_ref": "src/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests app_data with location fields (latitude and longitude as int32_le × 1,000,000).",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement App Data - Node Types",
"spec_ref": "spec/05-payload-advert.md",
"spec_ref": "src/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests all defined node types in the app_data flags field (bits 0-3). Uses a common pub_key, zero timestamp, and zero signature for structural testing.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement Payload - Basic Structure",
"spec_ref": "spec/05-payload-advert.md",
"spec_ref": "src/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests the basic structure of advertisement payloads: pub_key(32) + timestamp(4) + signature(64) + optional app_data. Uses synthetic signature bytes since signature verification requires real Ed25519 operations (see signature-verify.json for verifiable vectors).",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Anonymous Request Payload",
"spec_ref": "spec/07-payload-anon-req.md",
"spec_ref": "src/07-payload-anon-req.md",
"protocol_version": 0,
"description": "Tests anonymous request payload: dest_hash(1) + sender_pub_key(32) + cipher_mac(2) + ciphertext.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Control Payload - Basic",
"spec_ref": "spec/12-payload-control.md",
"spec_ref": "src/12-payload-control.md",
"protocol_version": 0,
"description": "Tests control payload encoding. Control packets carry raw bytes where the first byte's bit 7 indicates zero-hop-only restriction.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - Basic",
"spec_ref": "spec/06-payload-encrypted.md",
"spec_ref": "src/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Tests encrypted payload encoding (REQ/RESPONSE/TXT_MSG) with known cryptographic material. Uses shared_secret = 0x000102...1F, AES key = first 16 bytes, HMAC key = full 32 bytes.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - MAC Verification",
"spec_ref": "spec/06-payload-encrypted.md",
"spec_ref": "src/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Tests MAC verification: valid MACs must pass, tampered MACs or ciphertext must fail.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - AES Block Padding",
"spec_ref": "spec/06-payload-encrypted.md",
"spec_ref": "src/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Tests AES-128-ECB zero-padding behavior at block boundaries. Plaintexts of 1, 15, 16, 17, and 32 bytes exercise all padding edge cases.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - Roundtrip Verification",
"spec_ref": "spec/06-payload-encrypted.md",
"spec_ref": "src/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Roundtrip encrypt-then-MAC verification vectors. Each vector provides full key material enabling implementations to verify: (1) encryption produces the given ciphertext, (2) MAC computation produces the given MAC, (3) MAC verification succeeds, (4) decryption recovers the plaintext.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - Extra Edge Cases",
"spec_ref": "spec/06-payload-encrypted.md",
"spec_ref": "src/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Additional encrypted payload tests: non-FLOOD routes, hash_size>1 paths, 32-byte plaintext, and payload-level invalid cases.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Group Data Payload",
"spec_ref": "spec/08-payload-group.md",
"spec_ref": "src/08-payload-group.md",
"protocol_version": 0,
"description": "Tests group datagram payload. Same wire format as GRP_TXT but payload type = 0x06.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Group Text Payload",
"spec_ref": "spec/08-payload-group.md",
"spec_ref": "src/08-payload-group.md",
"protocol_version": 0,
"description": "Tests group text message payload: channel_hash(1) + cipher_mac(2) + ciphertext.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Multipart Payload - Basic",
"spec_ref": "spec/11-payload-multipart.md",
"spec_ref": "src/11-payload-multipart.md",
"protocol_version": 0,
"description": "Tests multipart payload encoding: first byte = (remaining << 4) | sub_type. Currently only multipart ACK (sub_type=3) is defined.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path Return Payload - Basic",
"spec_ref": "spec/09-payload-path.md",
"spec_ref": "src/09-payload-path.md",
"protocol_version": 0,
"description": "Tests path return payload outer structure: dest_hash(1) + src_hash(1) + encrypt-then-MAC. The encrypted inner format contains path_len + path + extra_type + extra_data.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Raw Custom Payload - Basic",
"spec_ref": "spec/13-payload-raw-custom.md",
"spec_ref": "src/13-payload-raw-custom.md",
"protocol_version": 0,
"description": "Tests raw custom payload encoding. RAW_CUSTOM carries application-defined raw bytes.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Trace Payload - Basic",
"spec_ref": "spec/10-payload-trace.md",
"spec_ref": "src/10-payload-trace.md",
"protocol_version": 0,
"description": "Tests the basic 9-byte TRACE payload structure: tag(4) + auth_code(4) + flags(1).",
"vectors": [
+1 -1
View File
@@ -17,7 +17,7 @@
},
"spec_ref": {
"type": "string",
"description": "Path to the relevant spec section (e.g., 'spec/01-wire-format.md')"
"description": "Path to the relevant spec section (e.g., 'src/01-wire-format.md')"
},
"protocol_version": {
"type": "integer",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../../schema/test-vector.schema.json",
"name": "Framing - Decode Only",
"spec_ref": "spec/01-wire-format.md",
"spec_ref": "src/01-wire-format.md",
"protocol_version": 0,
"description": "Tests one-way decode cases where the binary is valid to decode but may not be the canonical encoding a conformant encoder would produce.",
"vectors": [
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Framing - Empty Payload Rejection",
"spec_ref": "spec/01-wire-format.md",
"spec_ref": "src/01-wire-format.md",
"protocol_version": 0,
"description": "Verifies that packets with zero-length payload are rejected. The reference implementation (Packet::readFrom) checks 'if (i >= len) return false' after reading header+transport+path_len+path, requiring at least 1 byte of payload.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Framing - Maximum Size Packets",
"spec_ref": "spec/01-wire-format.md",
"spec_ref": "src/01-wire-format.md",
"protocol_version": 0,
"description": "Tests packets at or near the maximum wire size of 255 bytes (MAX_TRANS_UNIT). Verifies size calculation: total = 1 (header) + transport(0|4) + 1 (path_len) + path_bytes + payload_len.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Framing - Minimal Valid Packets",
"spec_ref": "spec/01-wire-format.md",
"spec_ref": "src/01-wire-format.md",
"protocol_version": 0,
"description": "Tests the smallest valid packets for each route type. A valid packet must have at least header(1) + path_len(1) + payload(1+) bytes, plus 4 bytes for transport codes if applicable.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Combined Field Encoding",
"spec_ref": "spec/02-header.md",
"spec_ref": "src/02-header.md",
"protocol_version": 0,
"description": "Combinatorial tests of header byte encoding, exercising various combinations of version, payload type, and route type to verify correct bit packing.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Payload Types",
"spec_ref": "spec/02-header.md",
"spec_ref": "src/02-header.md",
"protocol_version": 0,
"description": "Tests encoding of all defined payload types in the header byte. Uses FLOOD routing and minimal payloads to isolate header encoding.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Route Types",
"spec_ref": "spec/02-header.md",
"spec_ref": "src/02-header.md",
"protocol_version": 0,
"description": "Tests encoding and decoding of all 4 route types in the header byte, using ACK (payload_type=3) as a simple payload.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Protocol Versions",
"spec_ref": "spec/02-header.md",
"spec_ref": "src/02-header.md",
"protocol_version": 0,
"description": "Tests encoding of all 4 protocol version values in header bits 6-7. Uses ACK (type=3) with FLOOD routing as a simple baseline.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Invalid - Bad Path Length Encoding",
"spec_ref": "spec/03-path.md",
"spec_ref": "src/03-path.md",
"protocol_version": 0,
"description": "Tests invalid path_len values including the reserved hash_size=4 and path overflow conditions.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Invalid - Too Short Packets",
"spec_ref": "spec/01-wire-format.md",
"spec_ref": "src/01-wire-format.md",
"protocol_version": 0,
"description": "Tests packets that are shorter than the minimum required size for their route type.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Invalid - Truncated Packets",
"spec_ref": "spec/01-wire-format.md",
"spec_ref": "src/01-wire-format.md",
"protocol_version": 0,
"description": "Tests packets that are truncated at various points, verifying that decoders reject them gracefully.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path - Boundary Conditions",
"spec_ref": "spec/03-path.md",
"spec_ref": "src/03-path.md",
"protocol_version": 0,
"description": "Tests boundary conditions for path encoding including maximum valid values and values just below overflow thresholds.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path - Hash Counts",
"spec_ref": "spec/03-path.md",
"spec_ref": "src/03-path.md",
"protocol_version": 0,
"description": "Tests various hash count values including zero, small, and maximum values for each hash size.",
"vectors": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path - Hash Sizes",
"spec_ref": "spec/03-path.md",
"spec_ref": "src/03-path.md",
"protocol_version": 0,
"description": "Tests all valid hash sizes (1, 2, 3 bytes) in the path length encoding. These vectors are critical for catching implementations that treat path_len as a raw byte count.",
"vectors": [
@@ -1,7 +1,7 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Transport Codes - Basic",
"spec_ref": "spec/01-wire-format.md",
"spec_ref": "src/01-wire-format.md",
"protocol_version": 0,
"description": "Tests transport code encoding with various values and route types that include/exclude transport codes.",
"vectors": [