Files
tcp-ingestion/test/fixtures/teltonika/README.md
T
julian 381287bacc Implement Phase 1 tasks 1.5-1.7 + 1.9 (Codec 8/8E/16 parsers + fixture suite)
- Codec 8 parser (1-byte IO IDs, no NX/Generation Type)
- Codec 8 Extended parser (2-byte IO IDs + variable-length NX section)
- Codec 16 parser (mixed widths + Generation Type, supports IO IDs > 255)
- Shared GPS element / timestamp helpers in gps-element.ts
- Fixture loader with bigint/Buffer sentinel encoding and auto-discovery
- 12 fixture pairs across codec8/8E/16 (canonical doc + synthetic edge cases)
- Cross-checked Codec 8 against Traccar's TeltonikaProtocolDecoder (no discrepancies)

26 new tests. Total 62 passing across 10 test files.
typecheck/lint/test/build all clean.
2026-04-30 16:24:17 +02:00

80 lines
2.6 KiB
Markdown

# Teltonika Fixture Suite
Binary protocol fixtures for the Codec 8, 8E, and 16 parsers.
## File format
Each fixture is a pair:
| File | Purpose |
|------|---------|
| `<name>.hex` | Hex-encoded AVL body (CodecID + N1 + records + N2) |
| `<name>.expected.json` | Expected `Position[]` output and ACK record count |
### .hex format
The body slice only — **not** the full TCP packet (no preamble, no DataFieldLength, no CRC).
This is exactly what `frame.payload` contains and what `handler.handle(body, ctx)` receives.
Whitespace and newlines are stripped before parsing, so you can write multi-line hex for readability.
### .expected.json format
```json
{
"positions": [
{
"device_id": "FIXTURE",
"timestamp": "2019-06-10T10:04:46.000Z",
"latitude": 0,
"longitude": 0,
"altitude": 0,
"angle": 0,
"speed": 0,
"satellites": 0,
"priority": 1,
"attributes": {
"21": 3,
"78": "__bigint:0",
"100": "__buffer_b64:AAEC"
}
}
],
"ack_record_count": 1
}
```
`device_id` is always `"FIXTURE"` — the IMEI comes from the session context, not the body.
#### Attribute value sentinels
| Sentinel | Decoded type | Example |
|---------|-------------|---------|
| number literal | `number` | `24079` |
| `"__bigint:<decimal>"` | `bigint` | `"__bigint:893700218"` |
| `"__buffer_b64:<base64>"` | `Buffer` | `"__buffer_b64:qw=="` |
For a zero-length Buffer, use `"__buffer_b64:"` (empty base64 string).
## How to add a new fixture
1. Get the AVL body bytes (from a live capture, a device emulator, or hand-computed).
2. Write the body as hex into `<name>.hex`.
3. Manually trace the expected parse output and write `<name>.expected.json`.
4. Run `pnpm test` — the new fixture is automatically discovered and tested.
No changes to any test file are needed.
## Bootstrap vs. synthetic fixtures
- **Bootstrap** fixtures (01-canonical, etc.) are sourced directly from the Teltonika documentation hex examples. Their expected outputs are read from the doc's parsed tables.
- **Synthetic** fixtures are hand-constructed for edge cases not covered by the canonical examples. Their expected outputs are computed manually and cross-checked.
## Cross-check methodology
Synthetic Codec 8 fixtures were cross-checked against the Traccar open-source
decoder (`TeltonikaProtocolDecoder.java`) for field widths and IO section parsing.
No discrepancies were found for the basic N1/N2/N4/N8 sections.
The NX section in Codec 8E has no equivalent in older Traccar versions; it was
verified by byte-level manual trace only.