diff --git a/justfile b/justfile index e17f579..57b3013 100644 --- a/justfile +++ b/justfile @@ -65,7 +65,12 @@ cut-version VERSION: exit 1 fi mkdir -p versions + # Snapshot both the spec text AND its paired test corpus, so the + # release is a self-contained pair. The site infrastructure + # (book.toml, theme, scripts, tools) stays unversioned at the + # repo root and applies to every build. cp -R src "versions/{{VERSION}}" + cp -R corpus "versions/{{VERSION}}/corpus" # Freeze the version marker in the snapshot. src/ keeps the literal # `latest (main)` so the rolling /latest/ build stays accurate. sed -i 's|`latest (main)`|`{{VERSION}}`|' "versions/{{VERSION}}/00-overview.md" @@ -74,16 +79,23 @@ cut-version VERSION: echo "snapshot still says 'latest (main)'. Check that src/00-overview.md" >&2 echo "contains the marker line ('**Spec version:** \`latest (main)\`')." >&2 fi - # Repoint corpus links to this version's GitHub tree. The spec keeps - # CHANGELOG links pointing at main (more useful — full history) by - # only matching .../tree/main/corpus and .../blob/main/corpus. - find "versions/{{VERSION}}" -name '*.md' -exec sed -i \ - -e 's|/tree/main/corpus|/tree/{{VERSION}}/corpus|g' \ - -e 's|/blob/main/corpus|/blob/{{VERSION}}/corpus|g' \ + # Repoint corpus URLs in the snapshot's spec text from main/corpus + # to main/versions//corpus — the frozen copy lives in the repo + # at a stable path on main, so the GitHub links resolve without + # needing a git tag. -maxdepth 1 keeps the substitution off the + # corpus *.json files (they get their own pass below). + find "versions/{{VERSION}}" -maxdepth 1 -name '*.md' -exec sed -i \ + -e 's|/tree/main/corpus|/tree/main/versions/{{VERSION}}/corpus|g' \ + -e 's|/blob/main/corpus|/blob/main/versions/{{VERSION}}/corpus|g' \ {} + - echo "Snapshotted src/ -> versions/{{VERSION}}" + # Repoint spec_ref inside the frozen corpus to this version's + # frozen spec, so a reader following a test vector lands on the + # spec text the vector was paired with at release time. + find "versions/{{VERSION}}/corpus" -name '*.json' -exec sed -i \ + 's|"spec_ref": "src/|"spec_ref": "versions/{{VERSION}}/|g' \ + {} + + echo "Snapshotted: src/ + corpus/ -> versions/{{VERSION}}/" echo echo "Next steps:" echo " - git add versions/{{VERSION}} && commit (e.g. \"release {{VERSION}}\")" - echo " - git tag {{VERSION}} # REQUIRED so the corpus links in /{{VERSION}}/" - echo " # resolve on GitHub (they point at tree/{{VERSION}}/...)" + echo " - optionally tag the commit: git tag {{VERSION}}" diff --git a/versions/v0.1.0/00-overview.md b/versions/v0.1.0/00-overview.md index b1db977..6c891a7 100644 --- a/versions/v0.1.0/00-overview.md +++ b/versions/v0.1.0/00-overview.md @@ -31,7 +31,7 @@ DongLoRa firmware and apps against live MeshCore deployments. Where DongLoRa behavior diverges from upstream, this spec follows upstream and treats the DongLoRa side as a follow-up bug, not a spec change. -Conformance against the [test corpus](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus) +Conformance against the [test corpus](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus) is a useful signal but **not a substitute** for cross-checking against upstream firmware. If you find a divergence from upstream, please [file an issue](https://github.com/swaits/meshcore-spec/issues) — that is diff --git a/versions/v0.1.0/01-wire-format.md b/versions/v0.1.0/01-wire-format.md index 9f9f8af..60c424a 100644 --- a/versions/v0.1.0/01-wire-format.md +++ b/versions/v0.1.0/01-wire-format.md @@ -131,7 +131,7 @@ A conforming decoder MUST reject packets with any of the following: - [Section 2: Header](02-header.md) — Header byte encoding details - [Section 3: Path](03-path.md) — Path length encoding and path field -- Test vectors: [`corpus/wire-format/framing/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/wire-format/framing/), [`corpus/wire-format/invalid/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/wire-format/invalid/) +- Test vectors: [`corpus/wire-format/framing/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/framing/), [`corpus/wire-format/invalid/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/invalid/) ### Reference Implementation diff --git a/versions/v0.1.0/02-header.md b/versions/v0.1.0/02-header.md index 7d8cc50..33aadb5 100644 --- a/versions/v0.1.0/02-header.md +++ b/versions/v0.1.0/02-header.md @@ -113,7 +113,7 @@ is an in-memory sentinel only and MUST NOT appear on the wire. A header of - [Section 1: Wire Format](01-wire-format.md) — Overall packet structure - [Section 3: Path](03-path.md) — Path encoding -- Test vectors: [`corpus/wire-format/header/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/wire-format/header/) +- Test vectors: [`corpus/wire-format/header/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/header/) ### Reference Implementation diff --git a/versions/v0.1.0/03-path.md b/versions/v0.1.0/03-path.md index a73610a..e5a3eda 100644 --- a/versions/v0.1.0/03-path.md +++ b/versions/v0.1.0/03-path.md @@ -143,7 +143,7 @@ this class of bug. - [Section 1: Wire Format](01-wire-format.md) — Overall packet structure - [Section 2: Header](02-header.md) — Header encoding - [Section 15: Identity](15-identity.md) — Hash derivation from public keys -- Test vectors: [`corpus/wire-format/path/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/wire-format/path/) +- Test vectors: [`corpus/wire-format/path/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/path/) ### Reference Implementation diff --git a/versions/v0.1.0/04-payload-ack.md b/versions/v0.1.0/04-payload-ack.md index 679aaeb..6239d49 100644 --- a/versions/v0.1.0/04-payload-ack.md +++ b/versions/v0.1.0/04-payload-ack.md @@ -87,7 +87,7 @@ sender attribute each received ACK to a specific transmission attempt. See - [Section 1: Wire Format](01-wire-format.md) — Packet framing - [Section 11: Multipart](11-payload-multipart.md) — Multipart ACK encoding -- Test vectors: [`corpus/payloads/ack/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/ack/) +- Test vectors: [`corpus/payloads/ack/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/ack/) ### Reference Implementation diff --git a/versions/v0.1.0/05-payload-advert.md b/versions/v0.1.0/05-payload-advert.md index f070a6b..76ad424 100644 --- a/versions/v0.1.0/05-payload-advert.md +++ b/versions/v0.1.0/05-payload-advert.md @@ -115,7 +115,7 @@ preceding optional fields are present. - [Section 15: Identity](15-identity.md) — Ed25519 key management - [Section 14: Cryptography](14-crypto.md) — Signature operations -- Test vectors: [`corpus/payloads/advert/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/advert/) +- Test vectors: [`corpus/payloads/advert/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/advert/) ### Reference Implementation diff --git a/versions/v0.1.0/06-payload-encrypted.md b/versions/v0.1.0/06-payload-encrypted.md index 28cb216..f4e03c9 100644 --- a/versions/v0.1.0/06-payload-encrypted.md +++ b/versions/v0.1.0/06-payload-encrypted.md @@ -159,7 +159,7 @@ patterns include: - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC details - [Section 15: Identity](15-identity.md) — ECDH shared secret computation -- Test vectors: [`corpus/payloads/encrypted/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/encrypted/) +- Test vectors: [`corpus/payloads/encrypted/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/encrypted/) ### Reference Implementation diff --git a/versions/v0.1.0/07-payload-anon-req.md b/versions/v0.1.0/07-payload-anon-req.md index bb2b7d4..b239ffc 100644 --- a/versions/v0.1.0/07-payload-anon-req.md +++ b/versions/v0.1.0/07-payload-anon-req.md @@ -66,7 +66,7 @@ Header payload type field: `0x07` (ANON_REQ) - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Standard encrypted format - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC - [Section 15: Identity](15-identity.md) — ECDH key exchange -- Test vectors: [`corpus/payloads/anon-req/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/anon-req/) +- Test vectors: [`corpus/payloads/anon-req/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/anon-req/) ### Reference Implementation diff --git a/versions/v0.1.0/08-payload-group.md b/versions/v0.1.0/08-payload-group.md index e7d4899..1324cac 100644 --- a/versions/v0.1.0/08-payload-group.md +++ b/versions/v0.1.0/08-payload-group.md @@ -72,7 +72,7 @@ secret. Only a successful MAC verification confirms the correct channel. ### Cross-References - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC -- Test vectors: [`corpus/payloads/group/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/group/) +- Test vectors: [`corpus/payloads/group/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/group/) ### Reference Implementation diff --git a/versions/v0.1.0/09-payload-path.md b/versions/v0.1.0/09-payload-path.md index 7b0fef2..e6450b8 100644 --- a/versions/v0.1.0/09-payload-path.md +++ b/versions/v0.1.0/09-payload-path.md @@ -82,7 +82,7 @@ hash is unique. - [Section 3: Path](03-path.md) — Path length encoding - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Outer envelope format - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC -- Test vectors: [`corpus/payloads/path-return/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/path-return/) +- Test vectors: [`corpus/payloads/path-return/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/path-return/) ### Reference Implementation diff --git a/versions/v0.1.0/10-payload-trace.md b/versions/v0.1.0/10-payload-trace.md index 939db61..a627265 100644 --- a/versions/v0.1.0/10-payload-trace.md +++ b/versions/v0.1.0/10-payload-trace.md @@ -97,7 +97,7 @@ At that point, `onTraceRecv` is called with: ### Cross-References - [Section 16: Packet Hash](16-packet-hash.md) — TRACE packets include path_len in hash -- Test vectors: [`corpus/payloads/trace/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/trace/) +- Test vectors: [`corpus/payloads/trace/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/trace/) ### Reference Implementation diff --git a/versions/v0.1.0/11-payload-multipart.md b/versions/v0.1.0/11-payload-multipart.md index d5bd4ab..69712e8 100644 --- a/versions/v0.1.0/11-payload-multipart.md +++ b/versions/v0.1.0/11-payload-multipart.md @@ -112,7 +112,7 @@ intends to emit, not a guarantee that they will arrive. ### Cross-References - [Section 4: ACK](04-payload-ack.md) — ACK CRC format -- Test vectors: [`corpus/payloads/multipart/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/multipart/) +- Test vectors: [`corpus/payloads/multipart/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/multipart/) ### Reference Implementation diff --git a/versions/v0.1.0/12-payload-control.md b/versions/v0.1.0/12-payload-control.md index 5fb564e..8df2153 100644 --- a/versions/v0.1.0/12-payload-control.md +++ b/versions/v0.1.0/12-payload-control.md @@ -60,7 +60,7 @@ formats are application-defined, but typically include: ### Cross-References - [Section 17: Routing](17-routing.md) — Zero-hop delivery -- Test vectors: [`corpus/payloads/control/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/control/) +- Test vectors: [`corpus/payloads/control/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/control/) ### Reference Implementation diff --git a/versions/v0.1.0/13-payload-raw-custom.md b/versions/v0.1.0/13-payload-raw-custom.md index 1e796e7..a8c0b95 100644 --- a/versions/v0.1.0/13-payload-raw-custom.md +++ b/versions/v0.1.0/13-payload-raw-custom.md @@ -41,7 +41,7 @@ when received via direct routing. They are NOT flood-routed. ### Cross-References -- Test vectors: [`corpus/payloads/raw-custom/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/payloads/raw-custom/) +- Test vectors: [`corpus/payloads/raw-custom/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/raw-custom/) ### Reference Implementation diff --git a/versions/v0.1.0/14-crypto.md b/versions/v0.1.0/14-crypto.md index 1fec74a..a8202cd 100644 --- a/versions/v0.1.0/14-crypto.md +++ b/versions/v0.1.0/14-crypto.md @@ -110,7 +110,7 @@ Output format: - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Usage in packet payloads - [Section 15: Identity](15-identity.md) — Shared secret computation via ECDH -- Test vectors: [`corpus/crypto/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/crypto/) +- Test vectors: [`corpus/crypto/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/crypto/) ### Reference Implementation diff --git a/versions/v0.1.0/15-identity.md b/versions/v0.1.0/15-identity.md index d38c3d5..29d175e 100644 --- a/versions/v0.1.0/15-identity.md +++ b/versions/v0.1.0/15-identity.md @@ -81,7 +81,7 @@ Hex representation uses uppercase characters with 2 hex digits per byte. - [Section 3: Path](03-path.md) — Hash size in path encoding - [Section 5: Advertisement](05-payload-advert.md) — Signature usage - [Section 14: Cryptography](14-crypto.md) — Shared secret usage -- Test vectors: [`corpus/crypto/ed25519/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/crypto/ed25519/), [`corpus/crypto/ecdh/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/crypto/ecdh/) +- Test vectors: [`corpus/crypto/ed25519/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/crypto/ed25519/), [`corpus/crypto/ecdh/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/crypto/ecdh/) ### Reference Implementation diff --git a/versions/v0.1.0/16-packet-hash.md b/versions/v0.1.0/16-packet-hash.md index fd205d6..8d9d139 100644 --- a/versions/v0.1.0/16-packet-hash.md +++ b/versions/v0.1.0/16-packet-hash.md @@ -59,7 +59,7 @@ function calculatePacketHash(packet): ### Cross-References - [Section 10: Trace](10-payload-trace.md) — TRACE packet special handling -- Test vectors: [`corpus/crypto/sha256/packet-hash.json`](https://github.com/swaits/meshcore-spec/blob/v0.1.0/corpus/crypto/sha256/packet-hash.json) +- Test vectors: [`corpus/crypto/sha256/packet-hash.json`](https://github.com/swaits/meshcore-spec/blob/main/versions/v0.1.0/corpus/crypto/sha256/packet-hash.json) ### Reference Implementation diff --git a/versions/v0.1.0/18-companion-protocol.md b/versions/v0.1.0/18-companion-protocol.md index 09582f2..970730e 100644 --- a/versions/v0.1.0/18-companion-protocol.md +++ b/versions/v0.1.0/18-companion-protocol.md @@ -89,7 +89,7 @@ implementations SHOULD request a larger MTU (up to 512 bytes). ### Cross-References - [Section 1: Wire Format](01-wire-format.md) — Underlying packet format -- Test vectors: [`corpus/companion/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/companion/) +- Test vectors: [`corpus/companion/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/companion/) ### Reference Implementation diff --git a/versions/v0.1.0/19-kiss-protocol.md b/versions/v0.1.0/19-kiss-protocol.md index 95032ad..6753d70 100644 --- a/versions/v0.1.0/19-kiss-protocol.md +++ b/versions/v0.1.0/19-kiss-protocol.md @@ -173,7 +173,7 @@ Response codes: `response = command | 0x80` - [Section 1: Wire Format](01-wire-format.md) — Packet format within data frames - [Section 14: Cryptography](14-crypto.md) — Crypto operations via SetHardware -- Test vectors: [`corpus/kiss/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/kiss/) +- Test vectors: [`corpus/kiss/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/kiss/) ### Reference Implementation diff --git a/versions/v0.1.0/corpus/bridge/framing.json b/versions/v0.1.0/corpus/bridge/framing.json new file mode 100644 index 0000000..dd91914 --- /dev/null +++ b/versions/v0.1.0/corpus/bridge/framing.json @@ -0,0 +1,91 @@ +{ + "$schema": "../schema/test-vector.schema.json", + "name": "Bridge Protocol - Framing", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "bridge-001", + "description": "Basic bridge frame: magic + ACK payload + checksum", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C03E 0D0001000000 0E52" + } + }, + "binary": "3D 00 C03E 0D0001000000 0E52", + "notes": "Magic=0xC03E. Payload: ACK flood packet (0D 00 01000000). Fletcher-16 checksum=0x520E stored as bytes 0E 52 (low byte first)." + }, + { + "id": "bridge-002", + "description": "RS232 bridge frame: magic + length + payload + checksum", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C03E 0600 0D0001000000 0E52" + } + }, + "binary": "3D 00 C03E 0600 0D0001000000 0E52", + "notes": "RS232 variant: magic(C03E) + length(6=0x0006 LE=0600) + 6-byte payload + checksum." + }, + { + "id": "bridge-003", + "description": "Bridge frame with minimal 3-byte inner packet", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C03E 3D00FF 9C3D" + } + }, + "binary": "3D 00 C03E 3D00FF 9C3D", + "notes": "Bridge frame wrapping the smallest valid FLOOD packet (3D 00 FF). Fletcher-16 of [3D, 00, FF]: sum1=(3D+00+FF)%255=0x3C=60, but computed stepwise: s1=61,s2=61 -> s1=61,s2=122 -> s1=60,s2=182 -> checksum low=0x3C high=0xB6 -> stored as 3C B6. Recalculated: s1=(0+0x3D)%255=61, s2=(0+61)%255=61, s1=(61+0x00)%255=61, s2=(61+61)%255=122, s1=(61+0xFF)%255=(316)%255=61, s2=(122+61)%255=183. Checksum = (183<<8)|61 = 0xB73D, stored as 3D B7. Corrected: payload data is C03E 3D00FF 9C3D with checksum bytes verified against reference." + }, + { + "id": "bridge-004", + "description": "Bridge frame with wrong magic bytes", + "type": "invalid", + "binary": "3D 00 DEAD 0D0001000000 0E52", + "expected_error": "invalid_magic", + "notes": "Magic bytes are 0xDEAD instead of 0xC03E. Bridge receiver must reject frames with wrong magic." + }, + { + "id": "bridge-005", + "description": "Bridge frame with corrupted checksum", + "type": "invalid", + "binary": "3D 00 C03E 0D0001000000 0000", + "expected_error": "checksum_invalid", + "notes": "Correct payload but checksum zeroed. Fletcher-16 verification must fail." + } + ] +} diff --git a/versions/v0.1.0/corpus/companion/commands.json b/versions/v0.1.0/corpus/companion/commands.json new file mode 100644 index 0000000..7062435 --- /dev/null +++ b/versions/v0.1.0/corpus/companion/commands.json @@ -0,0 +1,94 @@ +{ + "$schema": "../schema/test-vector.schema.json", + "name": "Companion Protocol - Commands", + "spec_ref": "versions/v0.1.0/18-companion-protocol.md", + "protocol_version": 0, + "description": "Tests companion protocol command type encoding for various command types.", + "vectors": [ + { + "id": "cmd-001", + "description": "CMD_SEND_CHANNEL_MESSAGE (0x04) with channel index and text", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "04 00 48656C6C6F" + } + }, + "binary": "3D 00 04 00 48656C6C6F", + "notes": "CMD=0x04, channel_idx=0, text='Hello' (48656C6C6F)." + }, + { + "id": "cmd-002", + "description": "CMD_GET_CONTACTS (0x06)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "06" + } + }, + "binary": "3D 00 06" + }, + { + "id": "cmd-003", + "description": "CMD_GET_CHANNELS (0x07)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "07" + } + }, + "binary": "3D 00 07" + }, + { + "id": "cmd-004", + "description": "CMD_REBOOT (0x12)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "12" + } + }, + "binary": "3D 00 12" + } + ] +} diff --git a/versions/v0.1.0/corpus/companion/framing.json b/versions/v0.1.0/corpus/companion/framing.json new file mode 100644 index 0000000..5659547 --- /dev/null +++ b/versions/v0.1.0/corpus/companion/framing.json @@ -0,0 +1,74 @@ +{ + "$schema": "../schema/test-vector.schema.json", + "name": "Companion Protocol - Framing", + "spec_ref": "versions/v0.1.0/18-companion-protocol.md", + "protocol_version": 0, + "description": "Tests companion protocol frame encoding: type byte followed by variable-length data.", + "vectors": [ + { + "id": "comp-001", + "description": "CMD_APP_START (0x01) - no data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "01" + } + }, + "binary": "3D 00 01", + "notes": "Companion command type 0x01 = CMD_APP_START. No payload data." + }, + { + "id": "comp-002", + "description": "CMD_DEVICE_QUERY (0x02) - no data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "02" + } + }, + "binary": "3D 00 02" + }, + { + "id": "comp-003", + "description": "CMD_SET_TIME (0x08) with timestamp", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "08 00F15365" + } + }, + "binary": "3D 00 08 00F15365", + "notes": "CMD_SET_TIME with timestamp 1700000000 = 0x6553F100 LE = 00F15365." + } + ] +} diff --git a/versions/v0.1.0/corpus/companion/responses.json b/versions/v0.1.0/corpus/companion/responses.json new file mode 100644 index 0000000..369908b --- /dev/null +++ b/versions/v0.1.0/corpus/companion/responses.json @@ -0,0 +1,53 @@ +{ + "$schema": "../schema/test-vector.schema.json", + "name": "Companion Protocol - Responses", + "spec_ref": "versions/v0.1.0/18-companion-protocol.md", + "protocol_version": 0, + "description": "Tests companion protocol response type encoding.", + "vectors": [ + { + "id": "resp-001", + "description": "PACKET_CONTACT_MSG_RECV (0x02) with message data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "02 00 00F15365 48656C6C6F" + } + }, + "binary": "3D 00 02 00 00F15365 48656C6C6F", + "notes": "Type=0x02 (contact msg recv), contact_idx=0, timestamp=1700000000 LE, text='Hello'." + }, + { + "id": "resp-002", + "description": "PACKET_ADV_RECV (0x03) with advert data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "03 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 01" + } + }, + "binary": "3D 00 03 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 01", + "notes": "Type=0x03 (advert recv). pub_key=AA×32, flags=0x01 (Chat)." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/aes128ecb/block-padding.json b/versions/v0.1.0/corpus/crypto/aes128ecb/block-padding.json new file mode 100644 index 0000000..e4e7a3b --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/aes128ecb/block-padding.json @@ -0,0 +1,113 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "AES-128-ECB Block Padding", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "aes-pad-001", + "description": "5-byte plaintext 'Hello' padded to 16 bytes with zeros", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "3D 00 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "48656C6C6F" + }, + "notes": "Padded input: 48656C6C6F 0000000000000000000000. Output: 1 block." + }, + { + "id": "aes-pad-002", + "description": "15-byte plaintext padded with 1 zero byte", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "BF70C038D511F2A8B528E06691C6D42D" + } + }, + "binary": "3D 00 BF70C038D511F2A8B528E06691C6D42D", + "crypto_context": { + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E" + }, + "notes": "15 bytes padded with 1 zero: 000102...0E00. Output: 1 block." + }, + { + "id": "aes-pad-003", + "description": "16-byte plaintext: no padding needed", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "0A940BB5416EF045F1C39458C653EA5A" + } + }, + "binary": "3D 00 0A940BB5416EF045F1C39458C653EA5A", + "crypto_context": { + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E0F" + }, + "notes": "Exact block boundary: no padding. Output: 1 block." + }, + { + "id": "aes-pad-004", + "description": "17-byte plaintext: 2 blocks, second padded with 15 zeros", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560" + } + }, + "binary": "3D 00 0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560", + "crypto_context": { + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E0F10" + }, + "notes": "Block 1: 000102...0F (no pad). Block 2: 10 + 15 zeros. Output: 2 blocks = 32 bytes." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/aes128ecb/encrypt.json b/versions/v0.1.0/corpus/crypto/aes128ecb/encrypt.json new file mode 100644 index 0000000..079818d --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/aes128ecb/encrypt.json @@ -0,0 +1,110 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "AES-128-ECB Encryption", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "aes-001", + "description": "AES-128-ECB: key=00..0F, plaintext=all zeros", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C6A13B37878F5B826F4F8162A1C8D879" + } + }, + "binary": "3D 00 C6A13B37878F5B826F4F8162A1C8D879", + "crypto_context": { + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "00000000000000000000000000000000" + }, + "notes": "AES-128-ECB(key=00..0F, pt=00×16) = C6A13B37878F5B826F4F8162A1C8D879. Wrapped in RAW_CUSTOM for schema compliance." + }, + { + "id": "aes-002", + "description": "AES-128-ECB: key=00..0F, plaintext=all FF", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "3C441F32CE07822364D7A2990E50BB13" + } + }, + "binary": "3D 00 3C441F32CE07822364D7A2990E50BB13", + "crypto_context": { + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + } + }, + { + "id": "aes-003", + "description": "AES-128-ECB: key=00..0F, plaintext=00..0F (sequential)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "0A940BB5416EF045F1C39458C653EA5A" + } + }, + "binary": "3D 00 0A940BB5416EF045F1C39458C653EA5A", + "crypto_context": { + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E0F" + } + }, + { + "id": "aes-004", + "description": "AES-128-ECB: key=all FF, plaintext=all zeros", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "A1F6258C877D5FCD8964484538BFC92C" + } + }, + "binary": "3D 00 A1F6258C877D5FCD8964484538BFC92C", + "crypto_context": { + "encryption_key": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "plaintext": "00000000000000000000000000000000" + } + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/ecdh/shared-secret.json b/versions/v0.1.0/corpus/crypto/ecdh/shared-secret.json new file mode 100644 index 0000000..f73ab64 --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/ecdh/shared-secret.json @@ -0,0 +1,87 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "ECDH Shared Secret (X25519)", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "ecdh-001", + "description": "RFC 7748 Test Vector: X25519 key exchange (Alice's perspective)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742" + } + }, + "binary": "3D 00 4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742", + "crypto_context": { + "sender_private_key": "77076D0A7318A57D3C16C17251B26645DF4C2F87EBC0992AB177FBA51DB92C2A", + "recipient_public_key": "DE9EDB7D7B7DC1B4D35B61C2ECE435373F8343C85B78674DADFC7E146F882B4F" + }, + "notes": "RFC 7748 Section 6.1. Alice's private key and Bob's public key produce the shared secret. Note: MeshCore transposes Ed25519 keys to X25519, so actual ECDH inputs differ from raw Ed25519 keys." + }, + { + "id": "ecdh-002", + "description": "RFC 7748 Test Vector: X25519 key exchange (Bob's perspective)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742" + } + }, + "binary": "3D 00 4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742", + "crypto_context": { + "sender_private_key": "5DAB087E624A8A4B79E17F8B83800EE66F3BB1292618B6FD1C2F8B27FF88E0EB", + "recipient_public_key": "8520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA4A98EAA9B4E6A" + }, + "notes": "RFC 7748 Section 6.1. Bob's private key and Alice's public key produce the same shared secret as ecdh-001, verifying the commutative property of ECDH." + }, + { + "id": "ecdh-003", + "description": "X25519 with all-zeros private key scalar (edge case)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "binary": "3D 00 0000000000000000000000000000000000000000000000000000000000000000", + "crypto_context": { + "sender_private_key": "0000000000000000000000000000000000000000000000000000000000000000", + "recipient_public_key": "DE9EDB7D7B7DC1B4D35B61C2ECE435373F8343C85B78674DADFC7E146F882B4F" + }, + "notes": "Edge case: all-zero private key after clamping becomes a low-order scalar. X25519 with a clamped all-zero key (which becomes 0x40...00) against any public key. The all-zero shared secret indicates this is a degenerate key and implementations SHOULD reject it." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/ed25519/sign-verify.json b/versions/v0.1.0/corpus/crypto/ed25519/sign-verify.json new file mode 100644 index 0000000..b647e85 --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/ed25519/sign-verify.json @@ -0,0 +1,90 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Ed25519 Sign/Verify", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "ed-001", + "description": "RFC 8032 Test Vector 1: empty message", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "E5564300C360AC729086E2CC806E828A84877F1EB8E5D974D873E065224901555FB8821590A33BACC61E39701CF9B46BD25BF5F0595BBE24655141438E7A100B" + } + }, + "binary": "3D 00 E5564300C360AC729086E2CC806E828A84877F1EB8E5D974D873E065224901555FB8821590A33BACC61E39701CF9B46BD25BF5F0595BBE24655141438E7A100B", + "crypto_context": { + "sender_private_key": "9D61B19DEFFD5A60BA844AF492EC2CC44449C5697B326919703BAC031CAE7F60", + "sender_public_key": "D75A980182B10AB7D54BFED3C964073A0EE172F3DAA3F4A18446B0B8D183F8E3", + "plaintext": "" + }, + "notes": "RFC 8032 Section 7.1 Test Vector 1. Private key (seed) is 32 bytes; the 64-byte format used by MeshCore includes the expanded key. Signature over empty message." + }, + { + "id": "ed-002", + "description": "RFC 8032 Test Vector 2: single-byte message 0x72", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E159C7E94B2BA35F6F6A1918E25682F9B0ADC5C33C2BF3C39F04E50" + } + }, + "binary": "3D 00 92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E159C7E94B2BA35F6F6A1918E25682F9B0ADC5C33C2BF3C39F04E50", + "crypto_context": { + "sender_private_key": "4CCD089B28FF96DA9DB6C346EC114E0F5B8A319F35ABA624DA8CF6ED4FB8A6FB", + "sender_public_key": "3D4017C3E843895A92B70AA74D1B7EBC9C982CCF2EC4968CC0CD55F12AF4660C", + "plaintext": "72" + }, + "notes": "RFC 8032 Section 7.1 Test Vector 2. Signature over the single byte 0x72." + }, + { + "id": "ed-003", + "description": "RFC 8032 Test Vector 3: two-byte message", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "6291D657DEEC24024827E69C3ABE01A30CE548A284743A445E3680D7DB5AC3AC18FF9B538D16F290AE67F760984DC6594A7C15E9716ED28DC027BECEEA1EC40A" + } + }, + "binary": "3D 00 6291D657DEEC24024827E69C3ABE01A30CE548A284743A445E3680D7DB5AC3AC18FF9B538D16F290AE67F760984DC6594A7C15E9716ED28DC027BECEEA1EC40A", + "crypto_context": { + "sender_private_key": "C5AA8DF43F9F837BEDB7442F31DCB7B166D38535076F094B85CE3A2E0B4458F7", + "sender_public_key": "FC51CD8E6218A1A38DA47ED00230F0580816ED13BA3303AC5DEB911548908025", + "plaintext": "AF82" + }, + "notes": "RFC 8032 Section 7.1 Test Vector 3. Signature over the two-byte message 0xAF82." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/encrypt-then-mac/roundtrip.json b/versions/v0.1.0/corpus/crypto/encrypt-then-mac/roundtrip.json new file mode 100644 index 0000000..c3d1bdd --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/encrypt-then-mac/roundtrip.json @@ -0,0 +1,63 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Encrypt-then-MAC Roundtrip", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "etm-001", + "description": "Encrypt-then-MAC: 'Hello' with key 00..1F", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "EAB5 D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "3D 00 EAB5D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "48656C6C6F" + }, + "notes": "AES key = secret[0..15], HMAC key = secret[0..31]. Output = MAC(2) || Ciphertext(16) = 18 bytes." + }, + { + "id": "etm-002", + "description": "Encrypt-then-MAC: 'Pipeline test!' with key 00..1F", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FB32 9EFCA5121B41B626F93A7CBEE440CACE" + } + }, + "binary": "3D 00 FB329EFCA5121B41B626F93A7CBEE440CACE", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "506970656C696E65207465737421" + }, + "notes": "Plaintext 'Pipeline test!' (14 bytes). Zero-padded to 16 bytes before AES encryption." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/hmac-sha256/truncated-mac.json b/versions/v0.1.0/corpus/crypto/hmac-sha256/truncated-mac.json new file mode 100644 index 0000000..c5c301b --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/hmac-sha256/truncated-mac.json @@ -0,0 +1,61 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "HMAC-SHA256 Truncated to 2 Bytes", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "hmac-001", + "description": "HMAC-SHA256(key=00..1F, msg='test message') truncated to 2 bytes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "90B8" + } + }, + "binary": "3D 00 90B8", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "74657374206D657373616765" + }, + "notes": "HMAC-SHA256(key=00..1F, 'test message') = 90B89BC3C5CA2C29... Truncated to 2 bytes = 90B8." + }, + { + "id": "hmac-002", + "description": "HMAC over AES ciphertext (encrypt-then-MAC pipeline)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "EAB5" + } + }, + "binary": "3D 00 EAB5", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "D9FD218D50A4409143A7243D6D913502" + }, + "notes": "HMAC key = full 32-byte secret. Message = the AES ciphertext of 'Hello'. Result truncated to 2 bytes = EAB5." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/sha256/ack-crc.json b/versions/v0.1.0/corpus/crypto/sha256/ack-crc.json new file mode 100644 index 0000000..072619b --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/sha256/ack-crc.json @@ -0,0 +1,112 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "SHA-256 ACK CRC Computation", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "ack-crc-001", + "description": "ACK CRC for TXT_MSG 'hello', ts=0x12345678, attempt=0, zero pubkey", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "B2EC0C9E" + } + }, + "binary": "0D 00 9E0CECB2", + "crypto_context": { + "plaintext": "78563412 00 68656C6C6F", + "sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "notes": "SHA-256(timestamp(4) || txt_type_attempt(1) || text || sender_pub_key(32))[0..3] = 9E0CECB2 (LE). Displayed ack_crc field is the uint32 value 0xB2EC0C9E." + }, + { + "id": "ack-crc-002", + "description": "Same message as ack-crc-001 but attempt=1 — distinct CRC", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "13F08151" + } + }, + "binary": "0D 00 5181F013", + "crypto_context": { + "plaintext": "78563412 01 68656C6C6F", + "sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "notes": "txt_type_attempt=0x01 (TXT_TYPE_PLAIN<<2 | attempt=1). Confirms attempt increment changes ACK CRC." + }, + { + "id": "ack-crc-003", + "description": "Same message as ack-crc-001 but attempt=2 — distinct CRC", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "4AD71DDF" + } + }, + "binary": "0D 00 DF1DD74A", + "crypto_context": { + "plaintext": "78563412 02 68656C6C6F", + "sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "id": "ack-crc-004", + "description": "Same message as ack-crc-001 but attempt=3 — distinct CRC (max attempt)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "1A537B8F" + } + }, + "binary": "0D 00 8F7B531A", + "crypto_context": { + "plaintext": "78563412 03 68656C6C6F", + "sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "notes": "attempt=3 is the maximum value representable in the 2-bit attempt field." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/sha256/basic.json b/versions/v0.1.0/corpus/crypto/sha256/basic.json new file mode 100644 index 0000000..36c0f3c --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/sha256/basic.json @@ -0,0 +1,84 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "SHA-256 Basic", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "sha-001", + "description": "SHA-256 of empty string", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" + } + }, + "binary": "3D 00 E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "crypto_context": { + "plaintext": "" + }, + "notes": "SHA-256('') = E3B0C442...B855. Standard NIST test vector." + }, + { + "id": "sha-002", + "description": "SHA-256 of 'abc'", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD" + } + }, + "binary": "3D 00 BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD", + "crypto_context": { + "plaintext": "616263" + }, + "notes": "SHA-256('abc') = BA7816BF...15AD. Standard NIST test vector." + }, + { + "id": "sha-003", + "description": "SHA-256 of 'MeshCore'", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "F4F17278283E082A4743CCA308285D601E0F9943E6D6F9271B5B8C7DFED09DC0" + } + }, + "binary": "3D 00 F4F17278283E082A4743CCA308285D601E0F9943E6D6F9271B5B8C7DFED09DC0", + "crypto_context": { + "plaintext": "4D657368436F7265" + }, + "notes": "SHA-256('MeshCore')." + } + ] +} diff --git a/versions/v0.1.0/corpus/crypto/sha256/packet-hash.json b/versions/v0.1.0/corpus/crypto/sha256/packet-hash.json new file mode 100644 index 0000000..d3dbd07 --- /dev/null +++ b/versions/v0.1.0/corpus/crypto/sha256/packet-hash.json @@ -0,0 +1,109 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "SHA-256 Packet Hash", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "phash-001", + "description": "Packet hash for ACK with CRC 0xDEADBEEF", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "1BEE08540E8F7E5B" + } + }, + "binary": "3D 00 1BEE08540E8F7E5B", + "crypto_context": { + "plaintext": "03 EFBEADDE" + }, + "notes": "SHA-256(0x03 || EFBEADDE)[0..7] = 1BEE08540E8F7E5B. Input: type byte 0x03 (ACK) + CRC 0xDEADBEEF as LE." + }, + { + "id": "phash-002", + "description": "Packet hash for TRACE (path_len=0): includes path_len in hash", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C105C34E45E60009" + } + }, + "binary": "3D 00 C105C34E45E60009", + "crypto_context": { + "plaintext": "09 00 01000000 02000000 00" + }, + "notes": "SHA-256(0x09 || 0x00 || tag=1_LE || auth=2_LE || flags=0)[0..7]. TRACE special case: path_len byte (0x00) included after type byte." + }, + { + "id": "phash-003", + "description": "Packet hash for TRACE (path_len=3): different hash due to path_len", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "B83FB2E0EE276404" + } + }, + "binary": "3D 00 B83FB2E0EE276404", + "crypto_context": { + "plaintext": "09 03 01000000 02000000 00" + }, + "notes": "Same TRACE payload but path_len=3 instead of 0. Different hash proves path_len is included in computation." + }, + { + "id": "phash-004", + "description": "Packet hash for ADVERT (all-AA pubkey, ts=0, all-BB sig)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "F73157720FB1B5E1" + } + }, + "binary": "3D 00 F73157720FB1B5E1", + "crypto_context": { + "plaintext": "04 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + }, + "notes": "SHA-256(0x04 || advert_payload)[0..7]. Type 0x04 = ADVERT. Not a TRACE, so path_len is NOT included." + } + ] +} diff --git a/versions/v0.1.0/corpus/kiss/framing.json b/versions/v0.1.0/corpus/kiss/framing.json new file mode 100644 index 0000000..85ffd71 --- /dev/null +++ b/versions/v0.1.0/corpus/kiss/framing.json @@ -0,0 +1,141 @@ +{ + "$schema": "../schema/test-vector.schema.json", + "name": "KISS Protocol - Framing", + "spec_ref": "versions/v0.1.0/19-kiss-protocol.md", + "protocol_version": 0, + "description": "Tests KISS frame encoding including FEND delimiters, byte stuffing (FESC sequences), and type byte encoding.", + "vectors": [ + { + "id": "kiss-001", + "description": "Simple data frame: FEND + type(0x00) + data + FEND", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 00 DEADBEEF C0" + } + }, + "binary": "3D 00 C0 00 DEADBEEF C0", + "notes": "KISS frame: FEND(C0) + DataFrame(00) + 4 bytes data + FEND(C0). Data contains no special bytes so no escaping needed." + }, + { + "id": "kiss-002", + "description": "Data frame with FEND in data (byte stuffing required)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 00 AA DBDC BB C0" + } + }, + "binary": "3D 00 C0 00 AA DBDC BB C0", + "notes": "Data byte 0xC0 escaped as DB DC (FESC TFEND). Original data: AA C0 BB." + }, + { + "id": "kiss-003", + "description": "Data frame with FESC in data (byte stuffing required)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 00 AA DBDD BB C0" + } + }, + "binary": "3D 00 C0 00 AA DBDD BB C0", + "notes": "Data byte 0xDB escaped as DB DD (FESC TFESC). Original data: AA DB BB." + }, + { + "id": "kiss-004", + "description": "Data frame with both FEND and FESC in data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 00 DBDC DBDD C0" + } + }, + "binary": "3D 00 C0 00 DBDC DBDD C0", + "notes": "Original data: C0 DB. Escaped: DBDC DBDD." + }, + { + "id": "kiss-005", + "description": "SetHardware frame: GetIdentity (sub-cmd 0x01)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 01 C0" + } + }, + "binary": "3D 00 C0 06 01 C0", + "notes": "KISS frame: FEND + SetHardware(06) + GetIdentity(01) + FEND." + }, + { + "id": "kiss-006", + "description": "SetHardware response: Identity (sub-cmd 0x81) with pubkey", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 81 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA C0" + } + }, + "binary": "3D 00 C0 06 81 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA C0", + "notes": "KISS response: FEND + SetHardware(06) + Identity(81) + 32-byte pubkey + FEND." + } + ] +} diff --git a/versions/v0.1.0/corpus/kiss/sethardware.json b/versions/v0.1.0/corpus/kiss/sethardware.json new file mode 100644 index 0000000..52b052d --- /dev/null +++ b/versions/v0.1.0/corpus/kiss/sethardware.json @@ -0,0 +1,185 @@ +{ + "$schema": "../schema/test-vector.schema.json", + "name": "KISS Protocol - SetHardware Extensions", + "spec_ref": "versions/v0.1.0/19-kiss-protocol.md", + "protocol_version": 0, + "description": "Tests MeshCore-specific SetHardware (0x06) sub-commands and responses.", + "vectors": [ + { + "id": "kiss-hw-001", + "description": "GetVersion request", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 11 C0" + } + }, + "binary": "3D 00 C0 06 11 C0", + "notes": "SetHardware(06) + GetVersion(11)." + }, + { + "id": "kiss-hw-002", + "description": "Version response: version=1, reserved=0", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 91 01 00 C0" + } + }, + "binary": "3D 00 C0 06 91 01 00 C0", + "notes": "SetHardware(06) + Version response(91) + version=1 + reserved=0." + }, + { + "id": "kiss-hw-003", + "description": "SetRadio: freq=869618000Hz, bw=62500Hz, sf=12, cr=5", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 09 D0C5D733 A4F40000 0C 05 C0" + } + }, + "binary": "3D 00 C0 06 09 D0C5D733 A4F40000 0C 05 C0", + "notes": "SetRadio(09). freq=869618000=0x33D7C5D0 LE=D0C5D733. bw=62500=0x0000F4A4 LE=A4F40000. sf=12, cr=5." + }, + { + "id": "kiss-hw-004", + "description": "Ping request and Pong response", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 17 C0" + } + }, + "binary": "3D 00 C0 06 17 C0", + "notes": "SetHardware(06) + Ping(17)." + }, + { + "id": "kiss-hw-005", + "description": "Error response: UnknownCmd (0x05)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 F1 05 C0" + } + }, + "binary": "3D 00 C0 06 F1 05 C0", + "notes": "SetHardware(06) + Error(F1) + UnknownCmd(05)." + }, + { + "id": "kiss-hw-006", + "description": "RxMeta unsolicited event: SNR=-2.5dB, RSSI=-100dBm", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 F9 F6 9C C0" + } + }, + "binary": "3D 00 C0 06 F9 F6 9C C0", + "notes": "RxMeta(F9). SNR: -2.5dB × 4 = -10 = 0xF6 (signed byte). RSSI: -100dBm = 0x9C (signed byte)." + }, + { + "id": "kiss-hw-007", + "description": "TxDone success", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 F8 01 C0" + } + }, + "binary": "3D 00 C0 06 F8 01 C0", + "notes": "TxDone(F8) + success(01)." + }, + { + "id": "kiss-hw-008", + "description": "Stats response: rx=100, tx=50, errors=2", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 06 92 64000000 32000000 02000000 C0" + } + }, + "binary": "3D 00 C0 06 92 64000000 32000000 02000000 C0", + "notes": "Stats(92). rx=100=0x64 LE=64000000, tx=50=0x32 LE=32000000, errors=2 LE=02000000." + } + ] +} diff --git a/versions/v0.1.0/corpus/kiss/standard-commands.json b/versions/v0.1.0/corpus/kiss/standard-commands.json new file mode 100644 index 0000000..a773444 --- /dev/null +++ b/versions/v0.1.0/corpus/kiss/standard-commands.json @@ -0,0 +1,97 @@ +{ + "$schema": "../schema/test-vector.schema.json", + "name": "KISS Protocol - Standard Commands", + "spec_ref": "versions/v0.1.0/19-kiss-protocol.md", + "protocol_version": 0, + "description": "Tests standard KISS commands (TXDELAY, Persistence, SlotTime, etc.).", + "vectors": [ + { + "id": "kiss-std-001", + "description": "TXDELAY command: 500ms (50 × 10ms)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 01 32 C0" + } + }, + "binary": "3D 00 C0 01 32 C0", + "notes": "Type=0x01 (TXDELAY), data=0x32 (50 decimal = 500ms)." + }, + { + "id": "kiss-std-002", + "description": "Persistence command: P=63", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 02 3F C0" + } + }, + "binary": "3D 00 C0 02 3F C0", + "notes": "Type=0x02 (Persistence), data=0x3F (63)." + }, + { + "id": "kiss-std-003", + "description": "SlotTime command: 100ms (10 × 10ms)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 03 0A C0" + } + }, + "binary": "3D 00 C0 03 0A C0", + "notes": "Type=0x03 (SlotTime), data=0x0A (10 = 100ms)." + }, + { + "id": "kiss-std-004", + "description": "FullDuplex command: half duplex (0)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "C0 05 00 C0" + } + }, + "binary": "3D 00 C0 05 00 C0", + "notes": "Type=0x05 (FullDuplex), data=0x00 (half duplex)." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/ack/basic.json b/versions/v0.1.0/corpus/payloads/ack/basic.json new file mode 100644 index 0000000..5913ae9 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/ack/basic.json @@ -0,0 +1,122 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "ACK Payload - Basic", + "spec_ref": "versions/v0.1.0/04-payload-ack.md", + "protocol_version": 0, + "description": "Basic ACK payload encoding/decoding tests with various CRC values.", + "vectors": [ + { + "id": "ack-001", + "description": "ACK with CRC 0xDEADBEEF", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "DEADBEEF" + } + }, + "binary": "0D 00 EFBEADDE", + "notes": "CRC 0xDEADBEEF stored as little-endian: EF BE AD DE." + }, + { + "id": "ack-002", + "description": "ACK with CRC 0x00000001", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 00 01000000" + }, + { + "id": "ack-003", + "description": "ACK with CRC 0x12345678", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "12345678" + } + }, + "binary": "0D 00 78563412" + }, + { + "id": "ack-004", + "description": "ACK via direct routing with path", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 2, + "hashes": [ + "AA", + "BB" + ] + }, + "payload": { + "ack_crc": "CAFEBABE" + } + }, + "binary": "0E 02 AABB BEBAFECA", + "notes": "Direct routed ACK. CRC 0xCAFEBABE as LE = BEBAFECA." + }, + { + "id": "ack-005", + "description": "ACK with 2-byte hash path", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "direct" + }, + "path": { + "hash_size": 2, + "hash_count": 1, + "hashes": [ + "AABB" + ] + }, + "payload": { + "ack_crc": "00000000" + } + }, + "binary": "0E 41 AABB 00000000", + "notes": "path_len=0x41: hash_size=2, count=1." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/ack/boundary.json b/versions/v0.1.0/corpus/payloads/ack/boundary.json new file mode 100644 index 0000000..cf0e3fb --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/ack/boundary.json @@ -0,0 +1,115 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "ACK Payload - Boundary Values", + "spec_ref": "versions/v0.1.0/04-payload-ack.md", + "protocol_version": 0, + "description": "ACK CRC boundary value tests including all-zeros, all-ones, and single-bit patterns.", + "vectors": [ + { + "id": "ack-bnd-001", + "description": "ACK CRC all zeros", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000000" + } + }, + "binary": "0D 00 00000000" + }, + { + "id": "ack-bnd-002", + "description": "ACK CRC all ones", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "FFFFFFFF" + } + }, + "binary": "0D 00 FFFFFFFF" + }, + { + "id": "ack-bnd-003", + "description": "ACK CRC 0x80000000 (high bit set)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "80000000" + } + }, + "binary": "0D 00 00000080" + }, + { + "id": "ack-bnd-004", + "description": "ACK CRC 0x00000080 (byte boundary)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000080" + } + }, + "binary": "0D 00 80000000" + }, + { + "id": "ack-bnd-005", + "description": "ACK CRC 0x01000000", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "01000000" + } + }, + "binary": "0D 00 00000001", + "notes": "Tests that 0x01000000 LE = 00 00 00 01, not confused with 0x00000001." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/advert/app-data-combined.json b/versions/v0.1.0/corpus/payloads/advert/app-data-combined.json new file mode 100644 index 0000000..ee54301 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/advert/app-data-combined.json @@ -0,0 +1,128 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Advertisement App Data - Combined Optional Fields", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "adc-001", + "description": "Chat node with location + feat1 + name", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 177, + "latitude": 37774900, + "longitude": -122419400, + "feat1": 256, + "name": "Node1" + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB B1 34664002 3807B4F8 0001 4E6F646531", + "notes": "flags=0xB1=10110001: type=Chat(1), has_location(bit4), has_feat1(bit5), has_name(bit7). No feat2 (bit6=0). feat1=256=0x0100 LE=0001. Name 'Node1' = 4E6F646531." + }, + { + "id": "adc-002", + "description": "Repeater with all optional fields", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 242, + "latitude": 0, + "longitude": 0, + "feat1": 1, + "feat2": 2, + "name": "RPT" + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB F2 00000000 00000000 0100 0200 525054", + "notes": "flags=0xF2=11110010: type=Repeater(2), all optional fields present. feat1=1 LE=0100, feat2=2 LE=0200, name='RPT'=525054." + }, + { + "id": "adc-003", + "description": "Node with name only (no location or features)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 129, + "name": "Hi" + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 81 4869", + "notes": "flags=0x81=10000001: type=Chat(1), has_name(bit7) only. name='Hi'=4869." + }, + { + "id": "adc-004", + "description": "Node with feat2 only (no location, feat1, or name)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 65, + "feat2": 65535 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 41 FFFF", + "notes": "flags=0x41=01000001: type=Chat(1), has_feat2(bit6). feat2=65535=0xFFFF." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/advert/app-data-location.json b/versions/v0.1.0/corpus/payloads/advert/app-data-location.json new file mode 100644 index 0000000..e573999 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/advert/app-data-location.json @@ -0,0 +1,96 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Advertisement App Data - Location", + "spec_ref": "versions/v0.1.0/05-payload-advert.md", + "protocol_version": 0, + "description": "Tests app_data with location fields (latitude and longitude as int32_le × 1,000,000).", + "vectors": [ + { + "id": "adl-001", + "description": "Chat node with location: San Francisco (37.7749, -122.4194)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 17, + "latitude": 37774900, + "longitude": -122419400 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 11 34664002 3807B4F8", + "notes": "flags=0x11: type=Chat(1), has_location(bit 4). Lat 37774900=0x02406634 LE=34664002. Lon -122419400=0xF8B40738 LE=3807B4F8." + }, + { + "id": "adl-002", + "description": "Repeater with location: equator/prime meridian (0, 0)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 18, + "latitude": 0, + "longitude": 0 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 12 00000000 00000000", + "notes": "flags=0x12: type=Repeater(2), has_location. Both coords zero." + }, + { + "id": "adl-003", + "description": "Sensor with negative latitude: south pole (-90, 0)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 20, + "latitude": -90000000, + "longitude": 0 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 14 80B5A2FA 00000000", + "notes": "flags=0x14: type=Sensor(4), has_location. Lat -90000000=0xFAA2B580 LE=80B5A2FA. Lon 0." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/advert/app-data-types.json b/versions/v0.1.0/corpus/payloads/advert/app-data-types.json new file mode 100644 index 0000000..f7dffb7 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/advert/app-data-types.json @@ -0,0 +1,140 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Advertisement App Data - Node Types", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "adt-001", + "description": "App data type: None (0)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 0 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 00", + "notes": "flags=0x00: type=None(0), no optional fields." + }, + { + "id": "adt-002", + "description": "App data type: Chat (1)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 1 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 01" + }, + { + "id": "adt-003", + "description": "App data type: Repeater (2)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 2 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 02" + }, + { + "id": "adt-004", + "description": "App data type: Room (3)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 3 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 03" + }, + { + "id": "adt-005", + "description": "App data type: Sensor (4)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "timestamp": 0, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "app_data": { + "flags": 4 + } + } + }, + "binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 04" + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/advert/basic.json b/versions/v0.1.0/corpus/payloads/advert/basic.json new file mode 100644 index 0000000..5d76602 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/advert/basic.json @@ -0,0 +1,88 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Advertisement Payload - Basic Structure", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "adv-001", + "description": "Minimal advertisement: pub_key + timestamp + signature, no app_data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20", + "timestamp": 1700000000, + "signature": "A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0" + } + }, + "binary": "11 00 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 00F15365 A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0", + "notes": "Header: 0x11 = ADVERT+FLOOD. Timestamp 1700000000 = 0x6553F100 LE = 00F15365. Payload: 32+4+64 = 100 bytes." + }, + { + "id": "adv-002", + "description": "Advertisement with 1-byte app_data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "pub_key": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20", + "timestamp": 1700000000, + "signature": "A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0", + "app_data": { + "flags": 1 + } + } + }, + "binary": "11 00 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 00F15365 A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0 01", + "notes": "flags=0x01: type=Chat(1), no optional fields. Payload: 101 bytes." + }, + { + "id": "adv-003", + "description": "Advertisement with path (3 hops, direct routing)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 3, + "hashes": [ + "AA", + "BB", + "CC" + ] + }, + "payload": { + "pub_key": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20", + "timestamp": 0, + "signature": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + "binary": "11 03 AABBCC 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 00000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "notes": "Timestamp=0 and zero signature for structural testing." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/anon-req/basic.json b/versions/v0.1.0/corpus/payloads/anon-req/basic.json new file mode 100644 index 0000000..1401818 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/anon-req/basic.json @@ -0,0 +1,85 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Anonymous Request Payload", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "anon-001", + "description": "Anonymous request with known keys", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "anon_req", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "sender_pub_key": "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "cipher_mac": "F4B4", + "ciphertext": "908A4FC137435774D0BB603F55A06069" + } + }, + "binary": "1D 00 AB 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F F4B4 908A4FC137435774D0BB603F55A06069", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "416E6F6E526571" + }, + "notes": "Header 0x1D = ANON_REQ(7)+FLOOD(1). Payload: dest_hash(1) + pub_key(32) + mac(2) + ct(16) = 51 bytes. Plaintext 'AnonReq' (7 bytes)." + }, + { + "id": "anon-002", + "description": "Anonymous request with tampered MAC", + "type": "invalid", + "binary": "1D 00 AB 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F 0000 908A4FC137435774D0BB603F55A06069", + "expected_error": "mac_invalid", + "notes": "MAC zeroed. Verification must fail." + }, + { + "id": "anon-003", + "description": "Anonymous request via DIRECT routing with 2-hop path", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "anon_req", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 2, + "hashes": ["AA", "BB"] + }, + "payload": { + "dest_hash": "CC", + "sender_pub_key": "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "cipher_mac": "F4B4", + "ciphertext": "908A4FC137435774D0BB603F55A06069" + } + }, + "binary": "1E 02 AABB CC 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F F4B4 908A4FC137435774D0BB603F55A06069", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "416E6F6E526571" + }, + "notes": "Header 0x1E = ANON_REQ(7)+DIRECT(2). Direct-routed anonymous request with 2 path hashes." + }, + { + "id": "anon-004", + "description": "Anonymous request truncated before ciphertext", + "type": "invalid", + "binary": "1D 00 AB 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F F4B4", + "expected_error": "too_short", + "notes": "Payload is only 35 bytes (dest_hash + pub_key + mac). Missing ciphertext. Minimum is 51 bytes." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/control/basic.json b/versions/v0.1.0/corpus/payloads/control/basic.json new file mode 100644 index 0000000..229851f --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/control/basic.json @@ -0,0 +1,96 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Control Payload - Basic", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "ctl-001", + "description": "Control packet with multi-hop allowed (bit 7 clear)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "control", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "01 AABBCCDD" + } + }, + "binary": "2D 00 01 AABBCCDD", + "notes": "control_byte=0x01: bit 7 clear, multi-hop allowed." + }, + { + "id": "ctl-002", + "description": "Control packet with zero-hop only (bit 7 set)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "control", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "80 AABBCCDD" + } + }, + "binary": "2E 00 80 AABBCCDD", + "notes": "control_byte=0x80: bit 7 set, zero-hop only. Direct route with path_count=0 = zero-hop." + }, + { + "id": "ctl-003", + "description": "Minimal control packet (1 byte)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "control", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "00" + } + }, + "binary": "2D 00 00" + }, + { + "id": "ctl-004", + "description": "Control packet with zero-hop restriction and data via direct routing", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "control", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF 0102030405" + } + }, + "binary": "2E 00 FF 0102030405", + "notes": "control_byte=0xFF: bit 7 set (zero-hop), all other bits set." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/encrypted/basic.json b/versions/v0.1.0/corpus/payloads/encrypted/basic.json new file mode 100644 index 0000000..a17a93b --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/encrypted/basic.json @@ -0,0 +1,129 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Encrypted Payload - Basic", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "enc-001", + "description": "Encrypted TXT_MSG with 5-byte plaintext 'Hello'", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "09 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "48656C6C6F" + }, + "notes": "plaintext 'Hello' = 48656C6C6F (5 bytes), zero-padded to 16 for AES-128-ECB. MAC is HMAC-SHA256(shared_secret, ciphertext) truncated to 2 bytes." + }, + { + "id": "enc-002", + "description": "Encrypted REQUEST with 16-byte plaintext (exact AES block)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "request", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "631C", + "ciphertext": "0A940BB5416EF045F1C39458C653EA5A" + } + }, + "binary": "01 00 ABCD 631C 0A940BB5416EF045F1C39458C653EA5A", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E0F" + }, + "notes": "16-byte plaintext fills exactly one AES block. No padding needed." + }, + { + "id": "enc-003", + "description": "Encrypted RESPONSE with 17-byte plaintext (spans 2 AES blocks)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "response", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "9822", + "ciphertext": "0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560" + } + }, + "binary": "05 00 ABCD 9822 0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E0F10" + }, + "notes": "17-byte plaintext: first 16 bytes in block 1, byte 17 zero-padded in block 2. 32 bytes ciphertext total." + }, + { + "id": "enc-004", + "description": "Encrypted TXT_MSG with 1-byte plaintext", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "83FB", + "ciphertext": "2D4135E80761429386BFE3833C535AA8" + } + }, + "binary": "09 00 ABCD 83FB 2D4135E80761429386BFE3833C535AA8", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "42" + }, + "notes": "1-byte plaintext 0x42, zero-padded to 16 bytes." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/encrypted/mac-verify.json b/versions/v0.1.0/corpus/payloads/encrypted/mac-verify.json new file mode 100644 index 0000000..1b2e31a --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/encrypted/mac-verify.json @@ -0,0 +1,69 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Encrypted Payload - MAC Verification", + "spec_ref": "versions/v0.1.0/06-payload-encrypted.md", + "protocol_version": 0, + "description": "Tests MAC verification: valid MACs must pass, tampered MACs or ciphertext must fail.", + "vectors": [ + { + "id": "mac-001", + "description": "Valid MAC with known ciphertext", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "09 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "48656C6C6F" + } + }, + { + "id": "mac-002", + "description": "Tampered MAC (first byte changed)", + "type": "invalid", + "binary": "09 00 ABCD 00B5 D9FD218D50A4409143A7243D6D913502", + "expected_error": "mac_invalid", + "notes": "MAC changed from EAB5 to 00B5. MACThenDecrypt must return 0." + }, + { + "id": "mac-003", + "description": "Tampered MAC (second byte changed)", + "type": "invalid", + "binary": "09 00 ABCD EA00 D9FD218D50A4409143A7243D6D913502", + "expected_error": "mac_invalid", + "notes": "MAC changed from EAB5 to EA00." + }, + { + "id": "mac-004", + "description": "Tampered ciphertext (first byte changed)", + "type": "invalid", + "binary": "09 00 ABCD EAB5 00FD218D50A4409143A7243D6D913502", + "expected_error": "mac_invalid", + "notes": "Ciphertext first byte changed from D9 to 00. HMAC over modified ciphertext won't match." + }, + { + "id": "mac-005", + "description": "Swapped MAC bytes", + "type": "invalid", + "binary": "09 00 ABCD B5EA D9FD218D50A4409143A7243D6D913502", + "expected_error": "mac_invalid", + "notes": "MAC bytes reversed: B5EA instead of EAB5." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/encrypted/padding.json b/versions/v0.1.0/corpus/payloads/encrypted/padding.json new file mode 100644 index 0000000..716af7e --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/encrypted/padding.json @@ -0,0 +1,99 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Encrypted Payload - AES Block Padding", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "pad-001", + "description": "1-byte plaintext: padded with 15 zero bytes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "83FB", + "ciphertext": "2D4135E80761429386BFE3833C535AA8" + } + }, + "binary": "09 00 ABCD 83FB 2D4135E80761429386BFE3833C535AA8", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "42" + }, + "notes": "1 byte padded to 16: 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00. 1 AES block output." + }, + { + "id": "pad-002", + "description": "16-byte plaintext: no padding needed (exact block boundary)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "631C", + "ciphertext": "0A940BB5416EF045F1C39458C653EA5A" + } + }, + "binary": "09 00 ABCD 631C 0A940BB5416EF045F1C39458C653EA5A", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E0F" + }, + "notes": "Exactly 16 bytes: no padding. 1 AES block output." + }, + { + "id": "pad-003", + "description": "17-byte plaintext: 1 byte in second block, padded with 15 zeros", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "9822", + "ciphertext": "0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560" + } + }, + "binary": "09 00 ABCD 9822 0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "000102030405060708090A0B0C0D0E0F10" + }, + "notes": "17 bytes spans 2 blocks. Block 2 = 10 00 00 ... 00. 32 bytes ciphertext." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/encrypted/roundtrip.json b/versions/v0.1.0/corpus/payloads/encrypted/roundtrip.json new file mode 100644 index 0000000..7b64498 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/encrypted/roundtrip.json @@ -0,0 +1,47 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Encrypted Payload - Roundtrip Verification", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "rt-enc-001", + "description": "Roundtrip: encrypt 'Hello', verify MAC, decrypt back", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "09 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "encryption_key": "000102030405060708090A0B0C0D0E0F", + "plaintext": "48656C6C6F" + }, + "notes": "Verification steps: (1) AES-128-ECB encrypt(key=00..0F, pad('Hello')) -> D9FD...3502. (2) HMAC-SHA256(key=00..1F, ct)[:2] -> EAB5. (3) HMAC verify: recompute and compare. (4) AES-128-ECB decrypt -> 48656C6C6F + 11 zero bytes." + }, + { + "id": "rt-enc-002", + "description": "Roundtrip with wrong MAC must fail verification", + "type": "invalid", + "binary": "09 00 ABCD FFFF D9FD218D50A4409143A7243D6D913502", + "expected_error": "mac_invalid", + "notes": "Same ciphertext as rt-enc-001 but MAC replaced with FFFF. MACThenDecrypt must return 0 (failure)." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/encrypted/with-extra.json b/versions/v0.1.0/corpus/payloads/encrypted/with-extra.json new file mode 100644 index 0000000..e8ffe2f --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/encrypted/with-extra.json @@ -0,0 +1,76 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Encrypted Payload - Extra Edge Cases", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "enc-extra-001", + "description": "Encrypted TXT_MSG via DIRECT routing with 2-byte hash path", + "type": "encode_decode", + "structured": { + "header": { "version": 0, "payload_type": "txt_msg", "route_type": "direct" }, + "path": { "hash_size": 2, "hash_count": 1, "hashes": ["AABB"] }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "0A 41 AABB ABCD EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "48656C6C6F" + }, + "notes": "Header 0x0A = TXT_MSG(2)+DIRECT(2). path_len=0x41 (hash_size=2, count=1). Tests encrypted payload with non-FLOOD routing and multi-byte hash." + }, + { + "id": "enc-extra-002", + "description": "Encrypted TXT_MSG via TRANSPORT_FLOOD with transport codes", + "type": "encode_decode", + "structured": { + "header": { "version": 0, "payload_type": "txt_msg", "route_type": "transport_flood" }, + "transport_codes": [1000, 0], + "path": { "hash_size": 1, "hash_count": 0, "hashes": [] }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "08 E803 0000 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "48656C6C6F" + }, + "notes": "Header 0x08 = TXT_MSG(2)+TRANSPORT_FLOOD(0). Tests encrypted payload with transport codes." + }, + { + "id": "enc-extra-003", + "description": "ACK payload too short (3 bytes instead of 4)", + "type": "invalid", + "binary": "0D 00 EFBEAD", + "expected_error": "incomplete_payload", + "notes": "ACK requires exactly 4 bytes. Only 3 provided." + }, + { + "id": "enc-extra-004", + "description": "Payload exceeds MAX_PACKET_PAYLOAD (185 bytes)", + "type": "invalid", + "binary": "3D 00 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8", + "expected_error": "payload_too_large", + "notes": "Payload is 185 bytes (0x00..0xB8), exceeding MAX_PACKET_PAYLOAD of 184. readFrom MUST reject." + }, + { + "id": "enc-extra-005", + "description": "Header 0xFF sentinel on wire (must reject)", + "type": "invalid", + "binary": "FF 00 DEADBEEF", + "expected_error": "sentinel_header", + "notes": "Header 0xFF is used internally as do-not-retransmit marker. MUST NOT appear on the wire." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/group/grp-data.json b/versions/v0.1.0/corpus/payloads/group/grp-data.json new file mode 100644 index 0000000..8a89d6e --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/group/grp-data.json @@ -0,0 +1,38 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Group Data Payload", + "spec_ref": "versions/v0.1.0/08-payload-group.md", + "protocol_version": 0, + "description": "Tests group datagram payload. Same wire format as GRP_TXT but payload type = 0x06.", + "vectors": [ + { + "id": "grp-data-001", + "description": "Group datagram with known channel secret", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "grp_data", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "channel_hash": "72", + "cipher_mac": "D184", + "ciphertext": "948C819389BA4ED7B1194A0FF8E62073" + } + }, + "binary": "19 00 72 D184 948C819389BA4ED7B1194A0FF8E62073", + "crypto_context": { + "shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F", + "encryption_key": "202122232425262728292A2B2C2D2E2F", + "plaintext": "47726F75704D736721" + }, + "notes": "Header 0x19 = GRP_DATA(6)+FLOOD(1). Same ciphertext as GRP_TXT since same key and plaintext." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/group/grp-txt.json b/versions/v0.1.0/corpus/payloads/group/grp-txt.json new file mode 100644 index 0000000..64ddc19 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/group/grp-txt.json @@ -0,0 +1,46 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Group Text Payload", + "spec_ref": "versions/v0.1.0/08-payload-group.md", + "protocol_version": 0, + "description": "Tests group text message payload: channel_hash(1) + cipher_mac(2) + ciphertext.", + "vectors": [ + { + "id": "grp-txt-001", + "description": "Group text message with known channel secret", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "grp_txt", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "channel_hash": "72", + "cipher_mac": "D184", + "ciphertext": "948C819389BA4ED7B1194A0FF8E62073" + } + }, + "binary": "15 00 72 D184 948C819389BA4ED7B1194A0FF8E62073", + "crypto_context": { + "shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F", + "encryption_key": "202122232425262728292A2B2C2D2E2F", + "plaintext": "47726F75704D736721" + }, + "notes": "Channel hash = SHA256(channel_secret)[0] = 0x72. Plaintext 'GroupMsg!' (9 bytes). AES key = first 16 bytes of channel secret." + }, + { + "id": "grp-txt-002", + "description": "Group text with tampered MAC must fail", + "type": "invalid", + "binary": "15 00 72 0000 948C819389BA4ED7B1194A0FF8E62073", + "expected_error": "mac_invalid", + "notes": "Same ciphertext as grp-txt-001 but MAC zeroed. Verification must fail." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/multipart/basic.json b/versions/v0.1.0/corpus/payloads/multipart/basic.json new file mode 100644 index 0000000..6c3ca20 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/multipart/basic.json @@ -0,0 +1,108 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Multipart Payload - Basic", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "mp-001", + "description": "Multipart ACK with remaining=1, CRC 0xDEADBEEF", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "multipart", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "remaining": 1, + "sub_type": 3, + "sub_payload": "EFBEADDE" + } + }, + "binary": "29 00 13 EFBEADDE", + "notes": "First byte: (1<<4)|3 = 0x13. Sub-payload: ACK CRC 0xDEADBEEF as LE." + }, + { + "id": "mp-002", + "description": "Multipart ACK with remaining=0 (last in sequence)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "multipart", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "remaining": 0, + "sub_type": 3, + "sub_payload": "01000000" + } + }, + "binary": "29 00 03 01000000", + "notes": "First byte: (0<<4)|3 = 0x03." + }, + { + "id": "mp-003", + "description": "Multipart ACK with remaining=15 (maximum)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "multipart", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "remaining": 15, + "sub_type": 3, + "sub_payload": "FFFFFFFF" + } + }, + "binary": "29 00 F3 FFFFFFFF", + "notes": "First byte: (15<<4)|3 = 0xF3." + }, + { + "id": "mp-004", + "description": "Multipart ACK via direct routing", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "multipart", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 2, + "hashes": [ + "AA", + "BB" + ] + }, + "payload": { + "remaining": 2, + "sub_type": 3, + "sub_payload": "78563412" + } + }, + "binary": "2A 02 AABB 23 78563412", + "notes": "Header 0x2A = MULTIPART(10)+DIRECT(2). First byte: (2<<4)|3 = 0x23." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/path-return/basic.json b/versions/v0.1.0/corpus/payloads/path-return/basic.json new file mode 100644 index 0000000..ea3f66a --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/path-return/basic.json @@ -0,0 +1,122 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Path Return Payload - Basic", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "path-001", + "description": "Path return with encrypted inner data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "path", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "21 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "48656C6C6F" + }, + "notes": "Header 0x21 = PATH(8)+FLOOD(1). Same ciphertext as enc-001 since same key/plaintext. The decrypted inner format would be parsed as: path_len=0x48, hash_size=2, hash_count=8 etc. - this is a structural test only." + }, + { + "id": "path-002", + "description": "Path return via DIRECT routing with 1-hop path", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "path", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 1, + "hashes": ["AB"] + }, + "payload": { + "dest_hash": "CD", + "src_hash": "EF", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "22 01 AB CDEF EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "48656C6C6F" + }, + "notes": "Header 0x22 = PATH(8)+DIRECT(2). Direct-routed path return with 1 hop." + }, + { + "id": "path-003", + "description": "Path return via TRANSPORT_FLOOD with transport codes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "path", + "route_type": "transport_flood" + }, + "transport_codes": [1, 2], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "EAB5", + "ciphertext": "D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "20 0100 0200 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502", + "crypto_context": { + "shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "plaintext": "48656C6C6F" + }, + "notes": "Header 0x20 = PATH(8)+TRANSPORT_FLOOD(0). Transport codes [1, 2] as LE uint16." + }, + { + "id": "path-004", + "description": "Path return with 2-block ciphertext (32 bytes plaintext)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "path", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "1234", + "ciphertext": "D9FD218D50A4409143A7243D6D913502D9FD218D50A4409143A7243D6D913502" + } + }, + "binary": "21 00 ABCD 1234 D9FD218D50A4409143A7243D6D913502D9FD218D50A4409143A7243D6D913502", + "notes": "Path return with 2 AES blocks of ciphertext (32 bytes). Tests multi-block encrypted path data." + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/raw-custom/basic.json b/versions/v0.1.0/corpus/payloads/raw-custom/basic.json new file mode 100644 index 0000000..2c454a7 --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/raw-custom/basic.json @@ -0,0 +1,97 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Raw Custom Payload - Basic", + "spec_ref": "versions/v0.1.0/13-payload-raw-custom.md", + "protocol_version": 0, + "description": "Tests raw custom payload encoding. RAW_CUSTOM carries application-defined raw bytes.", + "vectors": [ + { + "id": "raw-001", + "description": "Minimal raw custom payload (1 byte)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "42" + } + }, + "binary": "3E 00 42", + "notes": "Header 0x3E = RAW_CUSTOM(15)+DIRECT(2). Single byte payload." + }, + { + "id": "raw-002", + "description": "Raw custom with binary data", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 1, + "hashes": [ + "AA" + ] + }, + "payload": { + "data": "DEADBEEFCAFEBABE" + } + }, + "binary": "3E 01 AA DEADBEEFCAFEBABE", + "notes": "Direct route with 1-hop path." + }, + { + "id": "raw-003", + "description": "Raw custom with all-zero payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "0000000000000000" + } + }, + "binary": "3E 00 0000000000000000" + }, + { + "id": "raw-004", + "description": "Raw custom with all-FF payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FFFFFFFFFFFFFFFF" + } + }, + "binary": "3E 00 FFFFFFFFFFFFFFFF" + } + ] +} diff --git a/versions/v0.1.0/corpus/payloads/trace/basic.json b/versions/v0.1.0/corpus/payloads/trace/basic.json new file mode 100644 index 0000000..0b7853b --- /dev/null +++ b/versions/v0.1.0/corpus/payloads/trace/basic.json @@ -0,0 +1,127 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Trace Payload - Basic", + "spec_ref": "versions/v0.1.0/10-payload-trace.md", + "protocol_version": 0, + "description": "Tests the basic 9-byte TRACE payload structure: tag(4) + auth_code(4) + flags(1).", + "vectors": [ + { + "id": "trc-001", + "description": "Basic trace with tag=1, auth_code=2, flags=0", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "trace", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "tag": 1, + "auth_code": 2, + "flags": 0 + } + }, + "binary": "25 00 01000000 02000000 00", + "notes": "tag=1 LE=01000000, auth_code=2 LE=02000000, flags=0." + }, + { + "id": "trc-002", + "description": "Trace with large tag and auth_code values", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "trace", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "tag": 4294967295, + "auth_code": 305419896, + "flags": 0 + } + }, + "binary": "25 00 FFFFFFFF 78563412 00", + "notes": "tag=0xFFFFFFFF, auth_code=0x12345678 LE=78563412." + }, + { + "id": "trc-003", + "description": "Trace with flags indicating 1-byte path hash size (flags & 0x03 = 0)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "trace", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "tag": 100, + "auth_code": 200, + "flags": 0 + } + }, + "binary": "25 00 64000000 C8000000 00", + "notes": "flags=0: path_hash_size = 1 << (0 & 3) = 1 byte." + }, + { + "id": "trc-004", + "description": "Trace with flags indicating 2-byte path hash size (flags & 0x03 = 1)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "trace", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "tag": 100, + "auth_code": 200, + "flags": 1 + } + }, + "binary": "25 00 64000000 C8000000 01", + "notes": "flags=1: path_hash_size = 1 << (1 & 3) = 2 bytes." + }, + { + "id": "trc-005", + "description": "Trace sent via direct routing with path hashes appended to payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "trace", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "01000000 02000000 00 AABBCC" + } + }, + "binary": "26 00 01000000 02000000 00 AABBCC", + "notes": "Header 0x26 = TRACE(9)+DIRECT(2). path_len=0 (path field used for SNR). Payload: 9-byte header + 3 path hash bytes. Using raw payload since the appended hashes are semantically part of the trace." + } + ] +} diff --git a/versions/v0.1.0/corpus/schema/test-vector.schema.json b/versions/v0.1.0/corpus/schema/test-vector.schema.json new file mode 100644 index 0000000..c66a38c --- /dev/null +++ b/versions/v0.1.0/corpus/schema/test-vector.schema.json @@ -0,0 +1,459 @@ +{ + "$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"] + } + } + ] + } + } +} diff --git a/versions/v0.1.0/corpus/wire-format/framing/decode-only.json b/versions/v0.1.0/corpus/wire-format/framing/decode-only.json new file mode 100644 index 0000000..7ab9ff7 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/framing/decode-only.json @@ -0,0 +1,75 @@ +{ + "$schema": "../../../schema/test-vector.schema.json", + "name": "Framing - Decode Only", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "dec-001", + "description": "Decode-only: ACK FLOOD with trailing bytes beyond the 4-byte CRC payload", + "type": "decode_only", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "DEADBEEF" + } + }, + "binary": "0D 00 DEADBEEF 00", + "notes": "Binary has 1 trailing byte after the 4-byte ACK CRC. Decoders MUST accept this (payload_len includes the extra byte). Encoders would not produce trailing bytes." + }, + { + "id": "dec-002", + "description": "Decode-only: raw_custom FLOOD with path hash_count=0 but hash_size=2", + "type": "decode_only", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 2, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "AA" + } + }, + "binary": "3D 40 AA", + "notes": "Path_len byte 0x40 encodes hash_size=2 (bits 6-7 = 01) with hash_count=0. Decoders must handle non-default hash_size even when hash_count is zero. Encoders typically default hash_size=1 when hash_count=0." + }, + { + "id": "dec-003", + "description": "Decode-only: raw_custom FLOOD with path hash_size=3 and hash_count=0", + "type": "decode_only", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 3, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "BB" + } + }, + "binary": "3D 80 BB", + "notes": "Path_len byte 0x80 encodes hash_size=3 (bits 6-7 = 10) with hash_count=0. Same as dec-002 but with hash_size=3." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/framing/empty-payload.json b/versions/v0.1.0/corpus/wire-format/framing/empty-payload.json new file mode 100644 index 0000000..d83584a --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/framing/empty-payload.json @@ -0,0 +1,33 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Framing - Empty Payload Rejection", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "ep-001", + "description": "FLOOD packet with no payload bytes (just header + path_len)", + "type": "invalid", + "binary": "0D 00", + "expected_error": "empty_payload", + "notes": "2 bytes total: header 0x0D + path_len 0x00. No payload bytes remain. readFrom returns false because i(2) >= len(2)." + }, + { + "id": "ep-002", + "description": "TRANSPORT_FLOOD with no payload (header + transport + path_len only)", + "type": "invalid", + "binary": "0C 0000 0000 00", + "expected_error": "empty_payload", + "notes": "6 bytes: header(1) + transport(4) + path_len(1). i=6, len=6, so i>=len = true." + }, + { + "id": "ep-003", + "description": "FLOOD packet with path but no payload", + "type": "invalid", + "binary": "0D 02 AABB", + "expected_error": "empty_payload", + "notes": "4 bytes: header(1) + path_len(1) + path(2). i=4, len=4." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/framing/maximal.json b/versions/v0.1.0/corpus/wire-format/framing/maximal.json new file mode 100644 index 0000000..4e4cbfb --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/framing/maximal.json @@ -0,0 +1,139 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Framing - Maximum Size Packets", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "max-001", + "description": "Maximum FLOOD packet: 255 bytes with empty path and 253-byte payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFC" + } + }, + "binary": "3D 00 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFC", + "notes": "Total: 1 + 0 + 1 + 0 + 253 = 255 bytes. However payload exceeds MAX_PACKET_PAYLOAD (184). This is a wire-level max size test only; payload-level validation would reject this." + }, + { + "id": "max-002", + "description": "Maximum valid FLOOD packet respecting MAX_PACKET_PAYLOAD: 1 + 1 + 0 + 184 = 186 bytes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7" + } + }, + "binary": "3D 00 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7", + "notes": "Total: 1 + 1 + 184 = 186 bytes. Payload is exactly MAX_PACKET_PAYLOAD (184 bytes = 0x00..0xB7)." + }, + { + "id": "max-003", + "description": "Large FLOOD packet with maximum path (63 bytes) and payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 63, + "hashes": [ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "0A", + "0B", + "0C", + "0D", + "0E", + "0F", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "1A", + "1B", + "1C", + "1D", + "1E", + "1F", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "2A", + "2B", + "2C", + "2D", + "2E", + "2F", + "30", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "38", + "39", + "3A", + "3B", + "3C", + "3D", + "3E", + "3F" + ] + }, + "payload": { + "data": "DEADBEEF" + } + }, + "binary": "3D 3F 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F DEADBEEF", + "notes": "Total: 1 + 1 + 63 + 4 = 69 bytes. Max path with small payload." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/framing/minimal.json b/versions/v0.1.0/corpus/wire-format/framing/minimal.json new file mode 100644 index 0000000..0ac7baf --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/framing/minimal.json @@ -0,0 +1,127 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Framing - Minimal Valid Packets", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "min-001", + "description": "Minimal FLOOD packet: 3 bytes (header + path_len + 1-byte payload)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "3D 00 FF", + "notes": "Smallest valid FLOOD packet: 3 bytes total." + }, + { + "id": "min-002", + "description": "Minimal DIRECT packet: 3 bytes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "3E 00 FF", + "notes": "Smallest valid DIRECT packet: 3 bytes total." + }, + { + "id": "min-003", + "description": "Minimal TRANSPORT_FLOOD packet: 7 bytes (header + 4 transport + path_len + 1-byte payload)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "transport_flood" + }, + "transport_codes": [ + 0, + 0 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "3C 0000 0000 00 FF", + "notes": "Smallest valid TRANSPORT_FLOOD packet: 7 bytes total." + }, + { + "id": "min-004", + "description": "Minimal TRANSPORT_DIRECT packet: 7 bytes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "transport_direct" + }, + "transport_codes": [ + 0, + 0 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "3F 0000 0000 00 FF", + "notes": "Smallest valid TRANSPORT_DIRECT packet: 7 bytes total." + }, + { + "id": "min-005", + "description": "Minimal ACK via FLOOD: 6 bytes (header + path_len + 4-byte CRC)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000000" + } + }, + "binary": "0D 00 00000000", + "notes": "ACK with zero CRC. 6 bytes total." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/header/combined.json b/versions/v0.1.0/corpus/wire-format/header/combined.json new file mode 100644 index 0000000..aa4c619 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/header/combined.json @@ -0,0 +1,165 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Header - Combined Field Encoding", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "hdr-000", + "description": "All-zero header: version=0, request=0, transport_flood=0", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "request", + "route_type": "transport_flood" + }, + "transport_codes": [ + 0, + 0 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "00", + "src_hash": "00", + "cipher_mac": "0000", + "ciphertext": "00000000000000000000000000000000" + } + }, + "binary": "00 0000 0000 00 0000 0000 00000000000000000000000000000000", + "notes": "Header: (0<<6)|(0<<2)|0 = 0x00." + }, + { + "id": "hdr-001", + "description": "Version=0, ADVERT, DIRECT route", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 2, + "hashes": [ + "AA", + "BB" + ] + }, + "payload": { + "data": "FF" + } + }, + "binary": "12 02 AABB FF", + "notes": "Header: (0<<6)|(4<<2)|2 = 0x12. path_len=0x02 (hash_size=1, count=2). Path: AA BB." + }, + { + "id": "hdr-002", + "description": "Version=1, CONTROL, FLOOD route", + "type": "encode_decode", + "structured": { + "header": { + "version": 1, + "payload_type": "control", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "6D 00 FF", + "notes": "Header: (1<<6)|(11<<2)|1 = 0x40+0x2C+0x01 = 0x6D." + }, + { + "id": "hdr-003", + "description": "Version=0, RAW_CUSTOM, TRANSPORT_DIRECT with non-trivial transport codes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "transport_direct" + }, + "transport_codes": [ + 256, + 512 + ], + "path": { + "hash_size": 1, + "hash_count": 1, + "hashes": [ + "AA" + ] + }, + "payload": { + "data": "CAFEBABE" + } + }, + "binary": "3F 0001 0002 01 AA CAFEBABE", + "notes": "Header: (0<<6)|(15<<2)|3 = 0x3F. Transport codes: 256=0x0100 LE=0001, 512=0x0200 LE=0002." + }, + { + "id": "hdr-004", + "description": "Version=0, TRACE, FLOOD with path", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "trace", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 3, + "hashes": [ + "AA", + "BB", + "CC" + ] + }, + "payload": { + "data": "0102030405060708 09" + } + }, + "binary": "25 03 AABBCC 010203040506070809", + "notes": "Header: (0<<6)|(9<<2)|1 = 0x25. path_len=0x03 (1-byte hashes, 3 hops)." + }, + { + "id": "hdr-005", + "description": "Maximum header value that isn't 0xFF sentinel: version=2, RAW_CUSTOM, TRANSPORT_DIRECT", + "type": "encode_decode", + "structured": { + "header": { + "version": 2, + "payload_type": "raw_custom", + "route_type": "transport_direct" + }, + "transport_codes": [ + 0, + 0 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "AA" + } + }, + "binary": "BF 0000 0000 00 AA", + "notes": "Header: (2<<6)|(15<<2)|3 = 0x80+0x3C+0x03 = 0xBF." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/header/payload-types.json b/versions/v0.1.0/corpus/wire-format/header/payload-types.json new file mode 100644 index 0000000..e3ad58e --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/header/payload-types.json @@ -0,0 +1,308 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Header - Payload Types", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "pt-000", + "description": "Payload type REQUEST (0x00) - header byte encodes type in bits 2-5", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "request", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "1122", + "ciphertext": "DEADBEEFCAFEBABE0102030405060708" + } + }, + "binary": "01 00 ABCD 1122 DEADBEEFCAFEBABE0102030405060708", + "notes": "Header: (0<<6)|(0<<2)|1 = 0x01. Payload: dest_hash(1) + src_hash(1) + MAC(2) + 16 bytes ciphertext." + }, + { + "id": "pt-001", + "description": "Payload type RESPONSE (0x01)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "response", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "1122", + "ciphertext": "DEADBEEFCAFEBABE0102030405060708" + } + }, + "binary": "05 00 ABCD 1122 DEADBEEFCAFEBABE0102030405060708", + "notes": "Header: (0<<6)|(1<<2)|1 = 0x05." + }, + { + "id": "pt-002", + "description": "Payload type TXT_MSG (0x02)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "txt_msg", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "dest_hash": "AB", + "src_hash": "CD", + "cipher_mac": "1122", + "ciphertext": "DEADBEEFCAFEBABE0102030405060708" + } + }, + "binary": "09 00 ABCD 1122 DEADBEEFCAFEBABE0102030405060708", + "notes": "Header: (0<<6)|(2<<2)|1 = 0x09." + }, + { + "id": "pt-003", + "description": "Payload type ACK (0x03)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "DEADBEEF" + } + }, + "binary": "0D 00 EFBEADDE", + "notes": "Header: (0<<6)|(3<<2)|1 = 0x0D. CRC 0xDEADBEEF as LE = EFBEADDE." + }, + { + "id": "pt-004", + "description": "Payload type ADVERT (0x04) - minimal advert with 1 byte payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "advert", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "11 00 FF", + "notes": "Header: (0<<6)|(4<<2)|1 = 0x11. Using raw payload to test header encoding in isolation." + }, + { + "id": "pt-005", + "description": "Payload type GRP_TXT (0x05)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "grp_txt", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "channel_hash": "AB", + "cipher_mac": "1122", + "ciphertext": "DEADBEEFCAFEBABE0102030405060708" + } + }, + "binary": "15 00 AB 1122 DEADBEEFCAFEBABE0102030405060708", + "notes": "Header: (0<<6)|(5<<2)|1 = 0x15." + }, + { + "id": "pt-006", + "description": "Payload type GRP_DATA (0x06)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "grp_data", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "channel_hash": "AB", + "cipher_mac": "1122", + "ciphertext": "DEADBEEFCAFEBABE0102030405060708" + } + }, + "binary": "19 00 AB 1122 DEADBEEFCAFEBABE0102030405060708", + "notes": "Header: (0<<6)|(6<<2)|1 = 0x19." + }, + { + "id": "pt-007", + "description": "Payload type ANON_REQ (0x07)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "anon_req", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "1D 00 FF", + "notes": "Header: (0<<6)|(7<<2)|1 = 0x1D. Minimal payload to test header." + }, + { + "id": "pt-008", + "description": "Payload type PATH (0x08)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "path", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "21 00 FF", + "notes": "Header: (0<<6)|(8<<2)|1 = 0x21." + }, + { + "id": "pt-009", + "description": "Payload type TRACE (0x09)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "trace", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "25 00 FF", + "notes": "Header: (0<<6)|(9<<2)|1 = 0x25." + }, + { + "id": "pt-010", + "description": "Payload type MULTIPART (0x0A)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "multipart", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "29 00 FF", + "notes": "Header: (0<<6)|(10<<2)|1 = 0x29." + }, + { + "id": "pt-011", + "description": "Payload type CONTROL (0x0B)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "control", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "FF" + } + }, + "binary": "2D 00 FF", + "notes": "Header: (0<<6)|(11<<2)|1 = 0x2D." + }, + { + "id": "pt-012", + "description": "Payload type RAW_CUSTOM (0x0F)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "raw_custom", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "data": "CAFEBABE" + } + }, + "binary": "3D 00 CAFEBABE", + "notes": "Header: (0<<6)|(15<<2)|1 = 0x3D." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/header/route-types.json b/versions/v0.1.0/corpus/wire-format/header/route-types.json new file mode 100644 index 0000000..f65d6c0 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/header/route-types.json @@ -0,0 +1,131 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Header - Route Types", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "rt-000", + "description": "Route type TRANSPORT_FLOOD (0x00) with ACK payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_flood" + }, + "transport_codes": [ + 0, + 0 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "01020304" + } + }, + "binary": "0C 0000 0000 00 04030201", + "notes": "Header: version=0, payload_type=3 (ACK), route_type=0 (TRANSPORT_FLOOD). 0x0C = (0<<6)|(3<<2)|0. Transport codes both zero. path_len=0x00 (hash_size=1, count=0). ACK CRC 0x01020304 as LE = 04030201." + }, + { + "id": "rt-001", + "description": "Route type FLOOD (0x01) with ACK payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "01020304" + } + }, + "binary": "0D 00 04030201", + "notes": "Header: 0x0D = (0<<6)|(3<<2)|1. No transport codes. path_len=0x00. ACK CRC as LE." + }, + { + "id": "rt-002", + "description": "Route type DIRECT (0x02) with ACK payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "01020304" + } + }, + "binary": "0E 00 04030201", + "notes": "Header: 0x0E = (0<<6)|(3<<2)|2. No transport codes." + }, + { + "id": "rt-003", + "description": "Route type TRANSPORT_DIRECT (0x03) with ACK payload", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_direct" + }, + "transport_codes": [ + 4660, + 22136 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "01020304" + } + }, + "binary": "0F 3412 7856 00 04030201", + "notes": "Header: 0x0F = (0<<6)|(3<<2)|3. Transport codes: 4660=0x1234 as LE=3412, 22136=0x5678 as LE=7856." + }, + { + "id": "rt-004", + "description": "TRANSPORT_FLOOD with non-zero transport codes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_flood" + }, + "transport_codes": [ + 65535, + 1 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "FFFFFFFF" + } + }, + "binary": "0C FFFF 0100 00 FFFFFFFF", + "notes": "Transport code 1: 0xFFFF as LE = FFFF. Transport code 2: 1 as LE = 0100. CRC 0xFFFFFFFF as LE." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/header/versions.json b/versions/v0.1.0/corpus/wire-format/header/versions.json new file mode 100644 index 0000000..f5186e0 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/header/versions.json @@ -0,0 +1,97 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Header - Protocol Versions", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "ver-000", + "description": "Version 0 (V1, current) - bits 6-7 = 00", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 00 01000000", + "notes": "Header: (0<<6)|(3<<2)|1 = 0x0D. Version 0 = V1." + }, + { + "id": "ver-001", + "description": "Version 1 (V2, reserved) - bits 6-7 = 01", + "type": "encode_decode", + "structured": { + "header": { + "version": 1, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "4D 00 01000000", + "notes": "Header: (1<<6)|(3<<2)|1 = 0x4D. Version 1 = V2 (reserved)." + }, + { + "id": "ver-002", + "description": "Version 2 (V3, reserved) - bits 6-7 = 10", + "type": "encode_decode", + "structured": { + "header": { + "version": 2, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "8D 00 01000000", + "notes": "Header: (2<<6)|(3<<2)|1 = 0x8D. Version 2 = V3 (reserved)." + }, + { + "id": "ver-003", + "description": "Version 3 (V4, reserved) - bits 6-7 = 11", + "type": "encode_decode", + "structured": { + "header": { + "version": 3, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "CD 00 01000000", + "notes": "Header: (3<<6)|(3<<2)|1 = 0xCD. Version 3 = V4 (reserved)." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/invalid/bad-path-len.json b/versions/v0.1.0/corpus/wire-format/invalid/bad-path-len.json new file mode 100644 index 0000000..017a40d --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/invalid/bad-path-len.json @@ -0,0 +1,57 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Invalid - Bad Path Length Encoding", + "spec_ref": "versions/v0.1.0/03-path.md", + "protocol_version": 0, + "description": "Tests invalid path_len values including the reserved hash_size=4 and path overflow conditions.", + "vectors": [ + { + "id": "bpl-001", + "description": "Reserved hash size code 3 (hash_size would be 4): path_len=0xC0", + "type": "invalid", + "binary": "0D C0 01000000", + "expected_error": "reserved_hash_size", + "notes": "path_len=0xC0: bits 6-7=11 (hash size code 3). isValidPathLen returns false because hash_size==4." + }, + { + "id": "bpl-002", + "description": "Reserved hash size code 3 with non-zero count: path_len=0xC1", + "type": "invalid", + "binary": "0D C1 AA 01000000", + "expected_error": "reserved_hash_size", + "notes": "path_len=0xC1: hash size code 3 (reserved), count=1. Invalid regardless of count." + }, + { + "id": "bpl-003", + "description": "Reserved hash size code 3, max count: path_len=0xFF", + "type": "invalid", + "binary": "0D FF 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F 01000000", + "expected_error": "reserved_hash_size", + "notes": "path_len=0xFF: hash size code 3 (reserved), count=63. Even if hash_size=4 were valid, 63*4=252 > MAX_PATH_SIZE." + }, + { + "id": "bpl-004", + "description": "Path overflow: hash_size=2, count=33 (66 bytes > MAX_PATH_SIZE)", + "type": "invalid", + "binary": "0D 61 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233343536373839 3A3B3C3D3E3F4041 01000000", + "expected_error": "path_overflow", + "notes": "path_len=0x61: hash_size=2, count=33. 33*2=66 > MAX_PATH_SIZE (64). isValidPathLen returns false." + }, + { + "id": "bpl-005", + "description": "Path overflow: hash_size=3, count=22 (66 bytes > MAX_PATH_SIZE)", + "type": "invalid", + "binary": "0D 96 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F4041 01000000", + "expected_error": "path_overflow", + "notes": "path_len=0x96: hash_size=3, count=22. 22*3=66 > MAX_PATH_SIZE (64)." + }, + { + "id": "bpl-006", + "description": "Path overflow edge case: hash_size=1, count=63 is valid (63<=64), but with hash_size=2 and count=33 it overflows", + "type": "invalid", + "binary": "0D 61 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334353637383940 4142 DEADBEEF", + "expected_error": "path_overflow", + "notes": "path_len=0x61: size code 1 (hash_size=2), count 33. 33*2=66 > 64." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/invalid/too-short.json b/versions/v0.1.0/corpus/wire-format/invalid/too-short.json new file mode 100644 index 0000000..6d75023 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/invalid/too-short.json @@ -0,0 +1,49 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Invalid - Too Short Packets", + "spec_ref": "versions/v0.1.0/01-wire-format.md", + "protocol_version": 0, + "description": "Tests packets that are shorter than the minimum required size for their route type.", + "vectors": [ + { + "id": "short-001", + "description": "1-byte packet (FLOOD): only header, missing path_len and payload", + "type": "invalid", + "binary": "0D", + "expected_error": "too_short", + "notes": "Minimum FLOOD packet is 3 bytes. This is 1." + }, + { + "id": "short-002", + "description": "2-byte packet (FLOOD): header + path_len, missing payload", + "type": "invalid", + "binary": "0D 00", + "expected_error": "empty_payload", + "notes": "Header + path_len present but no payload. i=2, len=2, so i>=len." + }, + { + "id": "short-003", + "description": "5-byte TRANSPORT_FLOOD: header + transport codes, missing path_len", + "type": "invalid", + "binary": "0C 00000000", + "expected_error": "too_short", + "notes": "TRANSPORT_FLOOD needs at least 7 bytes (1+4+1+1). This has 5." + }, + { + "id": "short-004", + "description": "6-byte TRANSPORT_FLOOD: header + transport + path_len, missing payload", + "type": "invalid", + "binary": "0C 00000000 00", + "expected_error": "empty_payload", + "notes": "All framing present but zero payload." + }, + { + "id": "short-005", + "description": "FLOOD with path consuming all remaining bytes", + "type": "invalid", + "binary": "0D 03 AABBCC", + "expected_error": "empty_payload", + "notes": "header(1) + path_len(1) + path(3) = 5 bytes = total. i=5, len=5." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/invalid/truncated.json b/versions/v0.1.0/corpus/wire-format/invalid/truncated.json new file mode 100644 index 0000000..bc85b36 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/invalid/truncated.json @@ -0,0 +1,65 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Invalid - Truncated Packets", + "spec_ref": "versions/v0.1.0/01-wire-format.md", + "protocol_version": 0, + "description": "Tests packets that are truncated at various points, verifying that decoders reject them gracefully.", + "vectors": [ + { + "id": "trunc-001", + "description": "Empty packet (0 bytes)", + "type": "invalid", + "binary": "", + "expected_error": "too_short", + "notes": "Zero-length input. Cannot read header." + }, + { + "id": "trunc-002", + "description": "Header only (1 byte, FLOOD route)", + "type": "invalid", + "binary": "0D", + "expected_error": "too_short", + "notes": "Only header byte. Cannot read path_len." + }, + { + "id": "trunc-003", + "description": "Header only (1 byte, TRANSPORT_FLOOD route) - transport codes truncated", + "type": "invalid", + "binary": "0C", + "expected_error": "too_short", + "notes": "Route type 0x00 requires 4 transport code bytes, but only header is present." + }, + { + "id": "trunc-004", + "description": "TRANSPORT_FLOOD with partial transport codes (2 of 4 bytes)", + "type": "invalid", + "binary": "0C 1234", + "expected_error": "too_short", + "notes": "Only 2 of 4 transport code bytes present." + }, + { + "id": "trunc-005", + "description": "TRANSPORT_FLOOD with transport codes but no path_len", + "type": "invalid", + "binary": "0C 0000 0000", + "expected_error": "too_short", + "notes": "5 bytes: header + 4 transport code bytes. Missing path_len." + }, + { + "id": "trunc-006", + "description": "FLOOD with path_len indicating 3 hops but only 1 path byte", + "type": "invalid", + "binary": "0D 03 AA FF", + "expected_error": "truncated_path", + "notes": "path_len=0x03 means 3 bytes of path expected but only 1 byte follows before the alleged payload. The decoder would read 3 path bytes (AA FF ??) and run out." + }, + { + "id": "trunc-007", + "description": "FLOOD with 2-byte hash path_len=0x42 (4 path bytes needed) but only 2 available", + "type": "invalid", + "binary": "0D 42 AABB FF", + "expected_error": "truncated_path", + "notes": "path_len=0x42: hash_size=2, count=2, need 4 path bytes. Only 2 bytes before payload marker." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/path/boundary.json b/versions/v0.1.0/corpus/wire-format/path/boundary.json new file mode 100644 index 0000000..414551c --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/path/boundary.json @@ -0,0 +1,156 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Path - Boundary Conditions", + "spec_ref": "versions/v0.1.0/03-path.md", + "protocol_version": 0, + "description": "Tests boundary conditions for path encoding including maximum valid values and values just below overflow thresholds.", + "vectors": [ + { + "id": "pb-001", + "description": "hash_size=2, count=31 (62 bytes, 2 below max)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 2, + "hash_count": 31, + "hashes": [ + "0001", + "0203", + "0405", + "0607", + "0809", + "0A0B", + "0C0D", + "0E0F", + "1011", + "1213", + "1415", + "1617", + "1819", + "1A1B", + "1C1D", + "1E1F", + "2021", + "2223", + "2425", + "2627", + "2829", + "2A2B", + "2C2D", + "2E2F", + "3031", + "3233", + "3435", + "3637", + "3839", + "3A3B", + "3C3D" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 5F 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D 01000000", + "notes": "path_len=0x5F: hash_size=2, count=31. Path: 62 bytes. Just below max." + }, + { + "id": "pb-002", + "description": "hash_size=3, count=20 (60 bytes, 3 below max for this hash_size)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 3, + "hash_count": 20, + "hashes": [ + "000102", + "030405", + "060708", + "090A0B", + "0C0D0E", + "0F1011", + "121314", + "151617", + "18191A", + "1B1C1D", + "1E1F20", + "212223", + "242526", + "272829", + "2A2B2C", + "2D2E2F", + "303132", + "333435", + "363738", + "393A3B" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 94 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233343536373839 3A3B 01000000", + "notes": "path_len=0x94: hash_size=3, count=20. Path: 60 bytes." + }, + { + "id": "pb-003", + "description": "Single-byte path: hash_size=1, count=1", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 1, + "hashes": [ + "00" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 01 00 01000000", + "notes": "Minimal non-empty path. Hash value 0x00 is valid." + }, + { + "id": "pb-004", + "description": "Path with all-FF hashes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 3, + "hashes": [ + "FF", + "FF", + "FF" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 03 FFFFFF 01000000", + "notes": "All hashes are 0xFF. Valid encoding." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/path/hash-counts.json b/versions/v0.1.0/corpus/wire-format/path/hash-counts.json new file mode 100644 index 0000000..7395231 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/path/hash-counts.json @@ -0,0 +1,260 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Path - Hash Counts", + "spec_ref": "versions/v0.1.0/03-path.md", + "protocol_version": 0, + "description": "Tests various hash count values including zero, small, and maximum values for each hash size.", + "vectors": [ + { + "id": "hc-000", + "description": "Zero hops (hash_count=0, hash_size=1) - empty path", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 00 01000000", + "notes": "path_len=0x00. Zero-length path." + }, + { + "id": "hc-001", + "description": "Zero hops with hash_size=2 - still empty path", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 2, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 40 01000000", + "notes": "path_len=0x40: hash_size=2, count=0. Path is still 0 bytes even though hash_size>1." + }, + { + "id": "hc-002", + "description": "Zero hops with hash_size=3", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 3, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 80 01000000", + "notes": "path_len=0x80: hash_size=3, count=0. Path is 0 bytes." + }, + { + "id": "hc-010", + "description": "Maximum count for hash_size=1: 63 hops, 63 bytes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 63, + "hashes": [ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "0A", + "0B", + "0C", + "0D", + "0E", + "0F", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "1A", + "1B", + "1C", + "1D", + "1E", + "1F", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "2A", + "2B", + "2C", + "2D", + "2E", + "2F", + "30", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "38", + "39", + "3A", + "3B", + "3C", + "3D", + "3E", + "3F" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 3F 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F 01000000", + "notes": "path_len=0x3F: hash_size=1, count=63. Path: 63 bytes. Maximum valid path for 1-byte hashes." + }, + { + "id": "hc-020", + "description": "Maximum count for hash_size=2: 32 hops, 64 bytes (exactly MAX_PATH_SIZE)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 2, + "hash_count": 32, + "hashes": [ + "0001", + "0203", + "0405", + "0607", + "0809", + "0A0B", + "0C0D", + "0E0F", + "1011", + "1213", + "1415", + "1617", + "1819", + "1A1B", + "1C1D", + "1E1F", + "2021", + "2223", + "2425", + "2627", + "2829", + "2A2B", + "2C2D", + "2E2F", + "3031", + "3233", + "3435", + "3637", + "3839", + "3A3B", + "3C3D", + "3E3F" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 60 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F 01000000", + "notes": "path_len=0x60: bits 6-7=01 (hash_size=2), bits 0-5=32. Path: 64 bytes = MAX_PATH_SIZE. Maximum valid for 2-byte hashes." + }, + { + "id": "hc-030", + "description": "Maximum count for hash_size=3: 21 hops, 63 bytes", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 3, + "hash_count": 21, + "hashes": [ + "000102", + "030405", + "060708", + "090A0B", + "0C0D0E", + "0F1011", + "121314", + "151617", + "18191A", + "1B1C1D", + "1E1F20", + "212223", + "242526", + "272829", + "2A2B2C", + "2D2E2F", + "303132", + "333435", + "363738", + "393A3B", + "3C3D3E" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 95 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E 01000000", + "notes": "path_len=0x95: bits 6-7=10 (hash_size=3), bits 0-5=21. Path: 63 bytes. Maximum valid for 3-byte hashes (22*3=66 would overflow)." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/path/hash-sizes.json b/versions/v0.1.0/corpus/wire-format/path/hash-sizes.json new file mode 100644 index 0000000..1e46e35 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/path/hash-sizes.json @@ -0,0 +1,188 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Path - Hash Sizes", + "spec_ref": "versions/v0.1.0/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": [ + { + "id": "hs-001", + "description": "Hash size 1 (code 0), 1 hop", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 1, + "hashes": [ + "AA" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 01 AA 01000000", + "notes": "path_len=0x01: bits 6-7=00 (size code 0, hash_size=1), bits 0-5=1 (count=1). Path: 1 byte." + }, + { + "id": "hs-002", + "description": "Hash size 1 (code 0), 5 hops", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 5, + "hashes": [ + "AA", + "BB", + "CC", + "DD", + "EE" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 05 AABBCCDDEE 01000000", + "notes": "path_len=0x05: hash_size=1, count=5. Path: 5 bytes." + }, + { + "id": "hs-010", + "description": "Hash size 2 (code 1), 1 hop", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 2, + "hash_count": 1, + "hashes": [ + "AABB" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 41 AABB 01000000", + "notes": "path_len=0x41: bits 6-7=01 (size code 1, hash_size=2), bits 0-5=1 (count=1). Path: 2 bytes. An implementation treating 0x41=65 as raw byte count would fail." + }, + { + "id": "hs-011", + "description": "Hash size 2 (code 1), 2 hops", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 2, + "hash_count": 2, + "hashes": [ + "AABB", + "CCDD" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 42 AABBCCDD 01000000", + "notes": "path_len=0x42: hash_size=2, count=2. Path: 4 bytes. This is the example that catches the meshcore-rs bug (0x42=66 raw vs 4 actual bytes)." + }, + { + "id": "hs-012", + "description": "Hash size 2 (code 1), 5 hops", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 2, + "hash_count": 5, + "hashes": [ + "1122", + "3344", + "5566", + "7788", + "99AA" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 45 112233445566778899AA 01000000", + "notes": "path_len=0x45: hash_size=2, count=5. Path: 10 bytes." + }, + { + "id": "hs-020", + "description": "Hash size 3 (code 2), 1 hop", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 3, + "hash_count": 1, + "hashes": [ + "AABBCC" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 81 AABBCC 01000000", + "notes": "path_len=0x81: bits 6-7=10 (size code 2, hash_size=3), bits 0-5=1. Path: 3 bytes." + }, + { + "id": "hs-021", + "description": "Hash size 3 (code 2), 3 hops", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 3, + "hash_count": 3, + "hashes": [ + "AABBCC", + "DDEEFF", + "112233" + ] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 83 AABBCC DDEEFF 112233 01000000", + "notes": "path_len=0x83: hash_size=3, count=3. Path: 9 bytes." + } + ] +} diff --git a/versions/v0.1.0/corpus/wire-format/transport-codes/basic.json b/versions/v0.1.0/corpus/wire-format/transport-codes/basic.json new file mode 100644 index 0000000..3dcfb09 --- /dev/null +++ b/versions/v0.1.0/corpus/wire-format/transport-codes/basic.json @@ -0,0 +1,183 @@ +{ + "$schema": "../../schema/test-vector.schema.json", + "name": "Transport Codes - Basic", + "spec_ref": "versions/v0.1.0/01-wire-format.md", + "protocol_version": 0, + "description": "Tests transport code encoding with various values and route types that include/exclude transport codes.", + "vectors": [ + { + "id": "tc-001", + "description": "Transport codes with TRANSPORT_FLOOD, both zero", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_flood" + }, + "transport_codes": [ + 0, + 0 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0C 0000 0000 00 01000000", + "notes": "Both transport codes zero." + }, + { + "id": "tc-002", + "description": "Transport codes with TRANSPORT_DIRECT, typical values", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_direct" + }, + "transport_codes": [ + 1000, + 2000 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0F E803 D007 00 01000000", + "notes": "TC1: 1000=0x03E8 LE=E803. TC2: 2000=0x07D0 LE=D007." + }, + { + "id": "tc-003", + "description": "Transport codes at maximum uint16 values", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_flood" + }, + "transport_codes": [ + 65535, + 65535 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0C FFFF FFFF 00 01000000", + "notes": "Both transport codes at maximum: 0xFFFF." + }, + { + "id": "tc-004", + "description": "Transport codes: code1=1, code2=0", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_flood" + }, + "transport_codes": [ + 1, + 0 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0C 0100 0000 00 01000000", + "notes": "TC1=1 LE=0100, TC2=0." + }, + { + "id": "tc-005", + "description": "Transport codes: code1=0x8000, code2=0x0001 (verify high bit handling)", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "transport_direct" + }, + "transport_codes": [ + 32768, + 1 + ], + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0F 0080 0100 00 01000000", + "notes": "TC1: 0x8000 LE=0080. TC2: 1 LE=0100." + }, + { + "id": "tc-006", + "description": "FLOOD route (no transport codes) - verify they are absent from wire", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "flood" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0D 00 01000000", + "notes": "FLOOD route: no transport codes on wire. Total: 1 (header) + 1 (path_len) + 4 (ACK) = 6 bytes." + }, + { + "id": "tc-007", + "description": "DIRECT route (no transport codes) - verify they are absent from wire", + "type": "encode_decode", + "structured": { + "header": { + "version": 0, + "payload_type": "ack", + "route_type": "direct" + }, + "path": { + "hash_size": 1, + "hash_count": 0, + "hashes": [] + }, + "payload": { + "ack_crc": "00000001" + } + }, + "binary": "0E 00 01000000", + "notes": "DIRECT route: no transport codes on wire." + } + ] +}