381287bacc
- 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.
80 lines
2.6 KiB
Markdown
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.
|