Files
tcp-ingestion/.planning/phase-1-telemetry/06-codec-8-extended.md
T
julian 90d6a73a60 Sync ROADMAP statuses with landed work; mark 1.10/1.12/1.13 as paused
Tasks 1.1-1.9 marked done with their landing commit SHAs. Tasks 1.10
(observability), 1.12 (production hardening), and 1.13 (device
authority) marked paused with explicit resume triggers — pilot
deployment on real Teltonika hardware takes priority. Task 1.11
remains as next, in slimmed form for the pilot (no /readyz healthcheck
since the metrics endpoint is part of paused 1.10).
2026-04-30 16:49:07 +02:00

82 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Task 1.6 — Codec 8 Extended parser
**Phase:** 1 — Inbound telemetry
**Status:** 🟩 Done — landed in commit `381287b`
**Depends on:** 1.4, 1.5 (shared GPS Element / timestamp helpers), 1.9
**Wiki refs:** `docs/wiki/concepts/avl-data-format.md` § Codec 8 Extended, `docs/wiki/sources/teltonika-data-sending-protocols.md` § Codec 8 Extended
## Goal
Parse Codec 8 Extended (`0x8E`) AVL data bodies into `Position` records, including the **NX variable-length IO section** that does not exist in Codecs 8 or 16.
## Deliverables
- `src/adapters/teltonika/codec/data/codec8e.ts` exporting `codec8eHandler: CodecDataHandler` with `codec_id: 0x8E`.
- Test file `test/codec8e.test.ts` with the canonical doc example plus at least two synthetic fixtures: one with NX entries, one with mixed N1/N2/N4/N8/NX.
## Specification
### Differences from Codec 8
| Field | Codec 8 | Codec 8 Extended |
|-------|---------|------------------|
| Codec ID | `0x08` | `0x8E` |
| Event IO ID width | 1B | 2B |
| N total / N* counts | 1B | **2B** |
| IO ID width | 1B | 2B |
| Value widths | 1/2/4/8B | 1/2/4/8B (same) |
| Variable-length IO (NX) | — | **Yes** |
The fixed AVL fields (timestamp, priority, GPS element 15B) are identical to Codec 8.
### IO Element layout (Codec 8E)
```
[Event IO ID 2B]
[N total 2B]
[N1 2B] then N1 × ([IO ID 2B][Value 1B])
[N2 2B] then N2 × ([IO ID 2B][Value 2B])
[N4 2B] then N4 × ([IO ID 2B][Value 4B])
[N8 2B] then N8 × ([IO ID 2B][Value 8B])
[NX 2B] then NX × ([IO ID 2B][Length 2B][Value <Length> bytes]) ← unique to 8E
```
### NX section — the load-bearing complication
The NX section is the most error-prone part of Codec 8E. Each entry self-describes:
- 2 bytes IO ID.
- 2 bytes length (unsigned big-endian).
- `length` bytes of raw value.
Store NX values as **`Buffer`** (not number/bigint) — they may be ICCID-class data, BLE sensor payloads, or similar binary content. The Processor decodes them per model.
```ts
attributes[String(ioId)] = buf.subarray(offset, offset + length); // copy via .subarray; treat as Buffer
```
**Common bug:** off-by-one in the length field's endianness or width. Verify with a fixture that has at least one NX entry whose length value spans both bytes (e.g. 256+ bytes).
**Common bug 2:** mishandling NX length 0. Permitted by the spec; treat as a 0-byte Buffer.
### Cursor invariant
Same as Codec 8: after parsing all N records and the trailing N2 byte, the cursor must equal the body's last byte. Mismatch = parser bug; throw with offset details.
## Acceptance criteria
- [ ] Canonical doc example (one record with N1=1, N2=1, N4=1, N8=2, NX=0) parses correctly. Note: the doc's NX section count is `00 00`, so this fixture covers the "NX present but empty" path.
- [ ] At least one synthetic fixture has NX > 0 with mixed lengths (e.g. 1B, 8B, 64B values).
- [ ] At least one synthetic fixture has an NX entry with `length = 0`.
- [ ] At least one synthetic fixture has an NX entry with `length` requiring full 16 bits (≥ 256B).
- [ ] All NX values land in `attributes` as `Buffer` instances; non-NX values land as `number` or `bigint` per width.
## Risks / open questions
- Maximum total record size remains 255 bytes per the spec. NX with large values can push this — verify the per-record size guard.
- Memory pressure: storing many `Buffer` instances per record could add up. Use `Buffer.subarray` (zero-copy view) rather than `Buffer.from(slice)` (copy). Confirm that downstream consumers (the publisher, task 1.8) handle the view semantics correctly — they should be safe because the underlying frame buffer is held until publish completes.
## Done
(Fill in once complete.)