From c28e77bdfa6aab87a03f24c3a0a256257ae1d838 Mon Sep 17 00:00:00 2001 From: "Matheus Henrique(Aka: TheusHen)" Date: Sat, 8 Aug 2026 18:27:33 -0300 Subject: [PATCH] spec: register Latch platform context TLVs --- CHANGELOG.md | 7 ++ implementations/go/lep/lep.go | 10 +++ implementations/go/lep/lep_test.go | 48 +++++++++++++ registry/tlv-types.md | 68 ++++++++++++++++++- spec/lep-v1.md | 15 ++-- test-vectors/manifest.json | 10 +++ test-vectors/valid/latch-cpu64-complete.hex | 1 + .../valid/latch-platform-contexts.hex | 1 + 8 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 test-vectors/valid/latch-cpu64-complete.hex create mode 100644 test-vectors/valid/latch-platform-contexts.hex diff --git a/CHANGELOG.md b/CHANGELOG.md index 6937e89..47ea552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +- Register the additive Latch platform-context TLVs (16–22), including RV64 + CPU state, retained blackbox records, mission, time synchronization, + provisioning, supervisor, and environment evidence. +- Add a golden vector covering the new context TLVs. + ## 1.0.0 - LEP v1 wire format, framing, crypto device path, TLV registry 1–15 diff --git a/implementations/go/lep/lep.go b/implementations/go/lep/lep.go index 12094ac..eb03555 100644 --- a/implementations/go/lep/lep.go +++ b/implementations/go/lep/lep.go @@ -22,6 +22,16 @@ const ( KnownFlags = FlagAuthenticated | FlagEncrypted | FlagAEAD | FlagTruncated | FlagCompressed ) +const ( + TLVCPU64 uint16 = 16 + TLVBlackbox uint16 = 17 + TLVMission uint16 = 18 + TLVTimeSync uint16 = 19 + TLVProvisioning uint16 = 20 + TLVSupervisor uint16 = 21 + TLVEnvironment uint16 = 22 +) + type ErrorKind string const ( diff --git a/implementations/go/lep/lep_test.go b/implementations/go/lep/lep_test.go index abfee2e..3f0ba17 100644 --- a/implementations/go/lep/lep_test.go +++ b/implementations/go/lep/lep_test.go @@ -42,6 +42,54 @@ func TestGoldenValid(t *testing.T) { } } +func TestGoldenLatchPlatformContexts(t *testing.T) { + raw := loadHex(t, "valid/latch-platform-contexts.hex") + h, err := lep.Validate(raw) + if err != nil { + t.Fatal(err) + } + if h.Architecture != 5 || h.Sequence != 29 || h.EventID != 30 { + t.Fatalf("unexpected header: %+v", h) + } + tlvs, err := lep.ParseTLVs(raw[lep.HeaderSize : lep.HeaderSize+int(h.PayloadLength)]) + if err != nil { + t.Fatal(err) + } + want := []uint16{ + lep.TLVCPU64, lep.TLVBlackbox, lep.TLVMission, lep.TLVTimeSync, + lep.TLVProvisioning, lep.TLVSupervisor, lep.TLVEnvironment, + } + if len(tlvs) != len(want) { + t.Fatalf("got %d TLVs, want %d", len(tlvs), len(want)) + } + for index, typ := range want { + if tlvs[index].Type != typ { + t.Fatalf("TLV %d: got %d, want %d", index, tlvs[index].Type, typ) + } + } + if got := tlvs[0].Value; len(got) != 4 || got[0] != 1 || got[1] != 2 || got[2] != 5 || got[3] != 8 { + t.Fatalf("unexpected CPU64 unavailable descriptor: %x", got) + } +} + +func TestGoldenLatchCPU64Complete(t *testing.T) { + raw := loadHex(t, "valid/latch-cpu64-complete.hex") + h, err := lep.Validate(raw) + if err != nil { + t.Fatal(err) + } + tlvs, err := lep.ParseTLVs(raw[lep.HeaderSize : lep.HeaderSize+int(h.PayloadLength)]) + if err != nil { + t.Fatal(err) + } + if len(tlvs) != 1 || tlvs[0].Type != lep.TLVCPU64 || len(tlvs[0].Value) != 292 { + t.Fatalf("unexpected CPU64 TLV: %+v", tlvs) + } + if got := tlvs[0].Value[:4]; got[0] != 1 || got[1] != 1 || got[2] != 5 || got[3] != 8 { + t.Fatalf("unexpected CPU64 complete descriptor: %x", got) + } +} + func TestRoundTrip(t *testing.T) { payload, _ := hex.DecodeString("01000200aabb") raw, err := lep.Encode(lep.Header{Version: 1, Type: 2, Sequence: 7, EventID: 9}, payload) diff --git a/registry/tlv-types.md b/registry/tlv-types.md index 3a26633..c1ac1e9 100644 --- a/registry/tlv-types.md +++ b/registry/tlv-types.md @@ -30,7 +30,13 @@ type u16 LE | length u16 LE | value[length] | 13 | MEMORY | yes | | 14 | STACK | no | | 15 | HEAP | no | -| 0x0010–0x0013 | build/project/release/hash extensions | — | +| 16 | CPU64 | no | +| 17 | BLACKBOX | yes | +| 18 | MISSION | no | +| 19 | TIME_SYNC | no | +| 20 | PROVISIONING | no | +| 21 | SUPERVISOR | no | +| 22 | ENVIRONMENT | no | | 0x0020–0x0021 | attachment meta/chunk | yes (chunk) | | 0x0030 | probe waveform (reserved) | — | | 0x8000–0x8FFF | vendor | — | @@ -145,3 +151,63 @@ flags: SAFE, HASH, VOLATILE, SENSITIVE. ## 15 HEAP (20 bytes) `free_bytes | minimum_free | largest_block | allocation_failures | pool_exhaustions` + +## 16 CPU64 + +`encoding u8 | flags u8 | architecture u8 | word_size u8` + +Encoding is `1`, architecture is `5` (`RISCV64`) and word size is `8`. +Exactly one flag is set: `COMPLETE` (`0x01`) or `UNAVAILABLE` (`0x02`). A +complete value is 292 bytes and appends `x0..x31`, `mstatus`, `mcause`, +`mtval`, and `mepc` as 36 little-endian `u64` values. An unavailable value is +exactly four bytes; consumers MUST NOT infer upper words from TLV 4 (`CPU`). + +## 17 BLACKBOX (27 bytes, repeatable) + +`encoding u8 | timestamp_ms u32 | kind u16 | source_id u16 | flags u16 | +value[4] i32` + +Encoding is `1`. A producer emits records oldest to newest within the bounded +export window and omits records marked sensitive. + +## 18 MISSION (46 bytes plus optional strings) + +`encoding u8 | mission_hash u32 | dive_hash u32 | node_hash u32 | +vehicle_mode_hash u32 | phase u32 | depth_cm i32 | elapsed_ms u32 | +incident_hi u64 | incident_lo u64 | incident_active u8` + +Encoding is `1`. If strings are retained, zero or more suffix fields follow as +`field_id u8 | length u8 | UTF-8 bytes`: 1 `mission_id`, 2 `dive_id`, 3 +`node_id`, 4 `vehicle_mode`. + +## 19 TIME_SYNC (22 bytes) + +`encoding u8 | source u8 | utc_ms_at_sync u64 | monotonic_ms_at_sync u32 | +uncertainty_ms u32 | generation u32` + +Encoding is `1`. Source values are 1 RTC, 2 GNSS, 3 NTP, 4 PTP, and 5 HOST. + +## 20 PROVISIONING (18 bytes) + +`encoding u8 | state u8 | key_id u32 | pending_key_id u32 | generation u32 | +monotonic_counter u32` + +Encoding is `1`. State values are 0 unprovisioned, 1 active, 2 rotating, 3 +revoked, 4 decommissioned, and 5 decommissioning. + +## 21 SUPERVISOR (17 bytes) + +`encoding u8 | active_alarms u32 | previous_alarms u32 | transitions u32 | +last_change_ms u32` + +Encoding is `1`. Alarm bit definitions are product policy; consumers preserve +unknown bits. + +## 22 ENVIRONMENT (29 bytes) + +`encoding u8 | timestamp_ms u32 | pressure_pa u32 | depth_cm i32 | +internal_temperature_c i16 | humidity_permyriad u16 | vibration_mg_rms u16 | +flags u16 | sample_count u32 | leak_events u32` + +Encoding is `1`. Environment flags are bit 0 leak detected, bit 1 water +ingress, bit 2 pressure-sensor fault, and bit 3 vibration limit. diff --git a/spec/lep-v1.md b/spec/lep-v1.md index 2439900..f9e94ca 100644 --- a/spec/lep-v1.md +++ b/spec/lep-v1.md @@ -141,18 +141,25 @@ Assigned types: [`registry/tlv-types.md`](../registry/tlv-types.md) and [`identi | 1 | `0x02` | `ENCRYPTED` | Payload is ciphertext | | 2 | `0x04` | `AEAD` | 28-byte AEAD metadata present | | 3 | `0x08` | `TRUNCATED` | Sender omitted optional fields due to capacity | -| 4–7 | | reserved | MUST be zero on send; unknown set bits MUST be rejected | +| 4 | `0x10` | `COMPRESSED` | Payload is compressed; see §5.2 | +| 5–7 | | reserved | MUST be zero on send; unknown set bits MUST be rejected | ### 5.1 Flag combination rules 1. `ENCRYPTED` and `AEAD` MUST both be set or both clear. 2. If `AEAD` is set, `AUTHENTICATED` MUST also be set. 3. `TRUNCATED` MAY combine with any valid security combination. -4. Receivers MUST reject unknown flag bits (v1: any bit outside `0x0F`). +4. `COMPRESSED` MAY combine with any valid security combination. A receiver + that does not implement the selected compression codec MUST reject the + envelope before interpreting its payload. +5. Receivers MUST reject unknown flag bits (v1: any bit outside `0x1F`). -### 5.2 Informative — historical compression flag collision +### 5.2 Compressed payloads -Some Relay builds treated bit 3 as `COMPRESSED` instead of `TRUNCATED`. That collides with Latch (the primary producer). **Canonical v1 bit 3 is `TRUNCATED`.** Compression, when standardized, uses a **different** mechanism (see [`compression.md`](compression.md) and [`compatibility.md`](compatibility.md)). +`COMPRESSED` is bit 4; `TRUNCATED` remains bit 3. The compressed payload is +opaque until decompressed successfully, so receivers MUST NOT parse TLVs or +make routing decisions from it first. Compression selection and bounds are +defined in [`compression.md`](compression.md). --- diff --git a/test-vectors/manifest.json b/test-vectors/manifest.json index b962ee5..9974edc 100644 --- a/test-vectors/manifest.json +++ b/test-vectors/manifest.json @@ -7,6 +7,16 @@ "path": "valid/lep-v1-basic.hex", "expect": "valid" }, + { + "id": "valid-latch-platform-contexts", + "path": "valid/latch-platform-contexts.hex", + "expect": "valid" + }, + { + "id": "valid-latch-cpu64-complete", + "path": "valid/latch-cpu64-complete.hex", + "expect": "valid" + }, { "id": "invalid-magic", "path": "invalid/bad-magic.hex", diff --git a/test-vectors/valid/latch-cpu64-complete.hex b/test-vectors/valid/latch-cpu64-complete.hex new file mode 100644 index 0000000..67aa88c --- /dev/null +++ b/test-vectors/valid/latch-cpu64-complete.hex @@ -0,0 +1 @@ +4c535450010105001f0000002000000028010000258918b1100024010101050800000000000000000100000000000000020000000000000003000000000000000400000000000000050000000000000006000000000000000700000000000000080000000000000009000000000000000a000000000000000b000000000000000c000000000000000d000000000000000e000000000000000f0000000000000010000000000000001100000000000000120000000000000013000000000000001400000000000000150000000000000016000000000000001700000000000000180000000000000019000000000000001a000000000000001b000000000000001c000000000000001d000000000000001e000000000000001f000000000000002000000000000000210000000000000022000000000000002300000000000000e2cdc85a diff --git a/test-vectors/valid/latch-platform-contexts.hex b/test-vectors/valid/latch-platform-contexts.hex new file mode 100644 index 0000000..c6f98d2 --- /dev/null +++ b/test-vectors/valid/latch-platform-contexts.hex @@ -0,0 +1 @@ +4c535450010205001d0000001e000000bf000000a68f4cfd100004000102050811001b00010403020105060708000001000000feffffff030000000400000012002e00011111111122222222333333334444444405000000faffffff0700000008000000000000000900000000000000011300160001020a000000000000000b0000000c0000000d0000001400120001010e000000000000000f0000001000000015001100011100000012000000130000001400000016001d00011500000016000000e9ffffffe8ff19001a0007001b0000001c0000009433e936