--- title: Codec Dispatch (Registry) type: concept created: 2026-04-30 updated: 2026-04-30 sources: [teltonika-ingestion-architecture, teltonika-data-sending-protocols] tags: [teltonika, parser, design-seam] --- # Codec Dispatch How the [[teltonika]] adapter routes incoming frames to the right parser, and the seam that makes [[phase-2-commands]] additive instead of a rewrite. ## The mechanism The codec ID (byte 0 of the AVL data payload) indexes into a **flat registry** of handlers. Each handler implements: ```ts interface CodecHandler { codec_id: number handle( payload: Buffer, ctx: { imei: string; publish: (p: Position) => Promise } ): Promise<{ ack_count: number }> } ``` Phase 1 registers handlers for IDs `0x08`, `0x8E`, and `0x10`. Phase 2 will register additional handlers for `0x0C`, `0x0D`, `0x0E` — these will use a different `ctx` shape (they need to write bytes back, not just `publish`), but the registry shape is identical. Per-codec directionality matters for the Phase 2 handler shape: | Codec | ID | Direction | Handler needs | |-------|-----|-----------|---------------| | 8 / 8E / 16 | `0x08` / `0x8E` / `0x10` | device → server | `publish(Position)` only | | 12 | `0x0C` | bidirectional | `respond(bytes)` for outbound + parse responses | | 13 | `0x0D` | device → server only | parse-only (no `respond`) | | 14 | `0x0E` | bidirectional | `respond(bytes)` + ACK type `0x06` / nACK type `0x11` | | 15 | `0x0F` | device → server only | out of scope (FMX6 RS232 only) | Codec 13 and 15, despite living in the GPRS-message family, are *one-way* — handlers for them never write to the socket. This matters for handler-shape design: the Phase 2 outbound family is not "all command codecs," it's specifically `0x0C` and `0x0E`. ## Why a registry, not a switch - Adding a new codec is a registration, not a code change in dispatch logic. - Phase 2 command codecs slot in alongside Phase 1 data codecs without modifying Phase 1 paths. - Codec parsers are **independent** — there is no shared base class. Their record shapes diverge in ways abstraction would obscure rather than help. ## The "session owns the socket; handler borrows it" rule Phase 1 handlers receive payload + a `publish(Position)` callback and emit records via Redis. They never write to the socket directly — the session loop handles ACKs. Phase 2 command handlers will need to **write to the socket** (to send commands). They borrow write access through a `respond(bytes: Buffer)` callback added to `ctx` for command codecs. The session retains socket ownership; handlers borrow write access through a narrow interface. ## Unknown codec policy When the codec ID does not match a registered handler: - `WARN` log entry with IMEI, offending codec ID, raw header bytes. - Socket is destroyed. - No ACK sent. - No attempt to "skip ahead" or guess record layout. Reasoning: a Teltonika device sending an unrecognized codec is **misconfigured**, not subtly broken. Silently truncating its data — or worse, mis-parsing — produces records with plausible-looking but wrong coordinates. Loud failure beats quiet corruption. The `teltonika_unknown_codec_total{codec_id}` counter is the canary for codec coverage drift. ## CRC failure policy Different from unknown-codec. CRC mismatch = transient transmission issue: - Frame is **not** ACK'd → device retransmits on next session. - `WARN` log with IMEI, expected CRC, computed CRC, frame length. - Connection stays open. Repeated CRC failures from the same device in a short window indicate a deeper problem (firmware, line quality) — surface via metrics, not just logs.