--- layout: page title: M17 link layer description: M17 digital-voice link-layer metadata decoder — callsigns, mode, events bus, SQLite log, REST endpoint nav_group: Reference --- # M17 link-layer decoder GopherTrunk decodes the **link layer** of [M17](https://m17project.org) — the open, Codec2-based amateur digital-voice protocol (4FSK, 4800 sym/s). This slice recovers the *metadata* of a transmission: who is calling whom, and in what mode. It answers "is M17 active on this frequency, and from which station" without decoding audio — the pure-Go Codec2 voice path is a planned follow-up. ## Pipeline ``` IQ → FM demod → resample to 48 kHz → C4FM matched filter → Mueller-Müller symbol timing → 4FSK slice → dibit → bits → sync hunt (stream 0xFF5D) → LICH reassembly → LSF parse → events.KindM17LinkSetup ``` - `internal/radio/m17/receiver` owns IQ → bits (C4FM, mirrors the P25 Phase 1 frontend); `internal/radio/m17` owns bits → Link Setup Frame. - **Two LSF routes.** The dedicated LSF frame at the head of a transmission is convolutionally coded + punctured; the **LICH** (Link Information CHannel) embedded in every stream frame carries 1/6 of the LSF, Golay(24,12)-coded. This decoder takes the LICH route — six consecutive LICH chunks reassemble the full 240-bit LSF using only Golay (no convolutional machinery), so a receiver tuned to an in-progress transmission picks up the metadata within ~240 ms. - **LSF fields:** DST(48) + SRC(48) + TYPE(16) + META(112) + CRC(16). Addresses are **base-40** callsigns (`DecodeAddress`), with the all-ones address rendered `BROADCAST`. TYPE yields the mode (voice / data / packet) and channel-access number. CRC-16 (poly 0x5935, init 0xFFFF) is checked over DST..META. ## What's wired - **Bus event** — `events.KindM17LinkSetup`, payload `storage.M17LinkSetup` (src, dst, mode, CAN, hex META, CRC-OK flag, display body). - **Storage** — `storage.M17Log` writes one `m17_log` row per reassembled LSF (indexed on `received_at` and `src`). - **REST** — `GET /api/v1/m17/linksetups?limit=N` returns the recent rows, newest first. - **Config** — pin an SDR under `m17.channels`: ```yaml m17: channels: - serial: "vhf-antenna" frequency_hz: 144_975_000 # 2 m M17 simplex calling (region-dependent) ``` ## What's pending - **Codec2 voice.** The stream-frame payload (frame number + 128-bit Codec2 frame, convolutionally coded) is skipped here; decoding it needs the pure-Go Codec2 port (a later milestone) and the K=5 Viterbi + P2 de-puncture path. - **Dedicated LSF-frame decode.** The standalone LSF frame (P1 puncture + Viterbi) would surface metadata one frame sooner than the LICH route; deferred since the LICH route already covers it. - **Calibration.** Sync words, LSF layout, CRC, base-40 alphabet, and the LICH structure are from the M17 specification; the Golay matrix and the C4FM slicer deviation / symbol-timing constants are validated against a synthetic encoder and should be confirmed against a real M17 capture (shared caveat with the DSC / FLEX frontends).