Files
meshcore-spec/corpus/schema/test-vector.schema.json
T
swaitsandClaude Opus 4.7 d785e35919 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>
2026-05-12 09:36:28 -06:00

460 lines
14 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/meshcore-spec/corpus/schema/test-vector.schema.json",
"title": "MeshCore Test Vector File",
"description": "Schema for MeshCore protocol test vector files. Each file contains a named collection of test vectors for a specific aspect of the protocol.",
"type": "object",
"required": ["name", "protocol_version", "vectors"],
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"description": "JSON Schema reference"
},
"name": {
"type": "string",
"description": "Human-readable name for this test vector collection"
},
"spec_ref": {
"type": "string",
"description": "Path to the relevant spec section (e.g., 'src/01-wire-format.md')"
},
"protocol_version": {
"type": "integer",
"minimum": 0,
"maximum": 3,
"description": "MeshCore protocol version (0-3, encoded in header bits 6-7)"
},
"description": {
"type": "string",
"description": "Optional longer description of what this file tests"
},
"vectors": {
"type": "array",
"description": "Array of test vectors",
"items": {
"$ref": "#/$defs/test_vector"
}
}
},
"$defs": {
"hex_string": {
"type": "string",
"pattern": "^[0-9A-F\\s]*$",
"description": "Hex-encoded binary data. MUST be uppercase. Whitespace allowed and stripped on parse."
},
"header": {
"type": "object",
"required": ["version", "payload_type", "route_type"],
"additionalProperties": false,
"properties": {
"version": {
"type": "integer",
"minimum": 0,
"maximum": 3,
"description": "Protocol version (header bits 6-7)"
},
"payload_type": {
"enum": [
"request",
"response",
"txt_msg",
"ack",
"advert",
"grp_txt",
"grp_data",
"anon_req",
"path",
"trace",
"multipart",
"control",
"raw_custom"
],
"description": "Payload type (header bits 2-5). Values 0x0C-0x0E are reserved."
},
"route_type": {
"enum": [
"transport_flood",
"flood",
"direct",
"transport_direct"
],
"description": "Route type (header bits 0-1). 0=transport_flood, 1=flood, 2=direct, 3=transport_direct"
}
}
},
"path_info": {
"type": "object",
"required": ["hash_size", "hash_count", "hashes"],
"additionalProperties": false,
"properties": {
"hash_size": {
"type": "integer",
"minimum": 1,
"maximum": 3,
"description": "Size of each hash in bytes (1, 2, or 3). Encoded as (hash_size - 1) in bits 6-7 of path_len byte."
},
"hash_count": {
"type": "integer",
"minimum": 0,
"maximum": 63,
"description": "Number of hashes in path (bits 0-5 of path_len byte)"
},
"hashes": {
"type": "array",
"items": {
"$ref": "#/$defs/hex_string"
},
"description": "Array of hex-encoded path hashes. Length MUST equal hash_count. Each hash MUST be hash_size bytes."
}
}
},
"transport_codes": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 65535
},
"minItems": 2,
"maxItems": 2,
"description": "Two 16-bit transport codes [code1, code2], encoded as little-endian uint16"
},
"payload_ack": {
"type": "object",
"required": ["ack_crc"],
"additionalProperties": false,
"properties": {
"ack_crc": {
"$ref": "#/$defs/hex_string",
"description": "4-byte ACK CRC as hex (big-endian representation of the value, wire encoding is little-endian)"
}
}
},
"payload_advert": {
"type": "object",
"required": ["pub_key", "timestamp", "signature"],
"additionalProperties": false,
"properties": {
"pub_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key"
},
"timestamp": {
"type": "integer",
"minimum": 0,
"description": "4-byte Unix timestamp"
},
"signature": {
"$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 signature over (pub_key + timestamp_le + app_data)"
},
"app_data": {
"$ref": "#/$defs/advert_app_data",
"description": "Optional advertisement application data"
}
}
},
"advert_app_data": {
"type": "object",
"required": ["flags"],
"additionalProperties": false,
"properties": {
"flags": {
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "8-bit flags: bits 0-3 = type (0=None,1=Chat,2=Repeater,3=Room,4=Sensor), bit 4 = has_location, bit 5 = has_feat1, bit 6 = has_feat2, bit 7 = has_name"
},
"latitude": {
"type": "integer",
"description": "Latitude * 1,000,000 as int32 (present when flags bit 4 set)"
},
"longitude": {
"type": "integer",
"description": "Longitude * 1,000,000 as int32 (present when flags bit 4 set)"
},
"feat1": {
"type": "integer",
"minimum": 0,
"maximum": 65535,
"description": "Feature field 1 as uint16 (present when flags bit 5 set)"
},
"feat2": {
"type": "integer",
"minimum": 0,
"maximum": 65535,
"description": "Feature field 2 as uint16 (present when flags bit 6 set)"
},
"name": {
"type": "string",
"description": "Node name as UTF-8 string (present when flags bit 7 set)"
}
}
},
"payload_encrypted": {
"type": "object",
"required": ["dest_hash", "src_hash", "cipher_mac", "ciphertext"],
"additionalProperties": false,
"properties": {
"dest_hash": {
"$ref": "#/$defs/hex_string",
"description": "Destination hash (PATH_HASH_SIZE bytes, default 1)"
},
"src_hash": {
"$ref": "#/$defs/hex_string",
"description": "Source hash (PATH_HASH_SIZE bytes, default 1)"
},
"cipher_mac": {
"$ref": "#/$defs/hex_string",
"description": "2-byte HMAC-SHA256 truncated MAC"
},
"ciphertext": {
"$ref": "#/$defs/hex_string",
"description": "AES-128-ECB encrypted data (zero-padded to block boundary)"
}
}
},
"payload_anon_req": {
"type": "object",
"required": ["dest_hash", "sender_pub_key", "cipher_mac", "ciphertext"],
"additionalProperties": false,
"properties": {
"dest_hash": {
"$ref": "#/$defs/hex_string",
"description": "Destination hash (PATH_HASH_SIZE bytes)"
},
"sender_pub_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key of sender"
},
"cipher_mac": {
"$ref": "#/$defs/hex_string",
"description": "2-byte cipher MAC"
},
"ciphertext": {
"$ref": "#/$defs/hex_string",
"description": "Encrypted data"
}
}
},
"payload_group": {
"type": "object",
"required": ["channel_hash", "cipher_mac", "ciphertext"],
"additionalProperties": false,
"properties": {
"channel_hash": {
"$ref": "#/$defs/hex_string",
"description": "1-byte channel hash (first byte of SHA-256 of channel shared key)"
},
"cipher_mac": {
"$ref": "#/$defs/hex_string",
"description": "2-byte cipher MAC"
},
"ciphertext": {
"$ref": "#/$defs/hex_string",
"description": "Encrypted data"
}
}
},
"payload_trace": {
"type": "object",
"required": ["tag", "auth_code", "flags"],
"additionalProperties": false,
"properties": {
"tag": {
"type": "integer",
"minimum": 0,
"description": "4-byte random tag set by initiator"
},
"auth_code": {
"type": "integer",
"minimum": 0,
"description": "4-byte authentication code"
},
"flags": {
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "1-byte flags (bits 0-1 encode path hash size)"
}
}
},
"payload_multipart": {
"type": "object",
"required": ["remaining", "sub_type", "sub_payload"],
"additionalProperties": false,
"properties": {
"remaining": {
"type": "integer",
"minimum": 0,
"maximum": 15,
"description": "Remaining parts count (upper 4 bits of first byte)"
},
"sub_type": {
"type": "integer",
"minimum": 0,
"maximum": 15,
"description": "Sub-payload type (lower 4 bits of first byte)"
},
"sub_payload": {
"$ref": "#/$defs/hex_string",
"description": "Sub-payload data"
}
}
},
"payload_control": {
"type": "object",
"required": ["data"],
"additionalProperties": false,
"properties": {
"data": {
"$ref": "#/$defs/hex_string",
"description": "Raw control data bytes"
}
}
},
"payload_raw": {
"type": "object",
"required": ["data"],
"additionalProperties": false,
"properties": {
"data": {
"$ref": "#/$defs/hex_string",
"description": "Raw custom payload bytes"
}
}
},
"crypto_context": {
"type": "object",
"additionalProperties": false,
"properties": {
"sender_private_key": {
"$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 private key of sender"
},
"sender_public_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key of sender"
},
"recipient_private_key": {
"$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 private key of recipient"
},
"recipient_public_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key of recipient"
},
"shared_secret": {
"$ref": "#/$defs/hex_string",
"description": "32-byte ECDH shared secret"
},
"encryption_key": {
"$ref": "#/$defs/hex_string",
"description": "16-byte AES-128 key (first 16 bytes of shared secret)"
},
"plaintext": {
"$ref": "#/$defs/hex_string",
"description": "Plaintext before encryption"
}
}
},
"structured_packet": {
"type": "object",
"required": ["header", "path"],
"additionalProperties": false,
"properties": {
"header": {
"$ref": "#/$defs/header"
},
"transport_codes": {
"$ref": "#/$defs/transport_codes"
},
"path": {
"$ref": "#/$defs/path_info"
},
"payload": {
"description": "Payload structure — the schema depends on the payload_type in header",
"anyOf": [
{ "$ref": "#/$defs/payload_ack" },
{ "$ref": "#/$defs/payload_advert" },
{ "$ref": "#/$defs/payload_encrypted" },
{ "$ref": "#/$defs/payload_anon_req" },
{ "$ref": "#/$defs/payload_group" },
{ "$ref": "#/$defs/payload_trace" },
{ "$ref": "#/$defs/payload_multipart" },
{ "$ref": "#/$defs/payload_control" },
{ "$ref": "#/$defs/payload_raw" }
]
}
}
},
"test_vector": {
"type": "object",
"required": ["id", "description", "type"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*$",
"description": "Unique identifier within this file (e.g., 'ack-001')"
},
"description": {
"type": "string",
"description": "Human-readable description of what this vector tests"
},
"type": {
"enum": ["encode_decode", "decode_only", "invalid"],
"description": "Test mode: encode_decode (bidirectional), decode_only (binary->structured), invalid (must reject)"
},
"structured": {
"$ref": "#/$defs/structured_packet",
"description": "Structured packet representation (required for encode_decode and decode_only)"
},
"binary": {
"$ref": "#/$defs/hex_string",
"description": "Wire-format binary encoding as hex (required for all types)"
},
"crypto_context": {
"$ref": "#/$defs/crypto_context",
"description": "Cryptographic key material for encrypted payload vectors"
},
"expected_error": {
"type": "string",
"description": "Expected error category for invalid vectors"
},
"notes": {
"type": "string",
"description": "Optional notes explaining the test vector"
}
},
"allOf": [
{
"if": {
"properties": { "type": { "const": "encode_decode" } }
},
"then": {
"required": ["structured", "binary"]
}
},
{
"if": {
"properties": { "type": { "const": "decode_only" } }
},
"then": {
"required": ["structured", "binary"]
}
},
{
"if": {
"properties": { "type": { "const": "invalid" } }
},
"then": {
"required": ["binary"]
}
}
]
}
}
}