HERMES Modem
Hermes ARQ/Broadcast modem
Loading...
Searching...
No Matches
arq_protocol.h
Go to the documentation of this file.
1/* HERMES Modem — ARQ Protocol: wire format, mode timing, codec API
2 *
3 * Copyright (C) 2025 Rhizomatica
4 * Author: Rafael Diniz <rafael@riseup.net>
5 *
6 * SPDX-License-Identifier: GPL-3.0-or-later
7 */
8
9#ifndef ARQ_PROTOCOL_H_
10#define ARQ_PROTOCOL_H_
11
12#include <stdbool.h>
13#include <stddef.h>
14#include <stdint.h>
15
16/* ======================================================================
17 * Protocol version (informational — not carried in wire frames)
18 * ====================================================================== */
19
20#define ARQ_PROTO_VERSION 3 /* v3: 8-byte header, no proto_ver field on wire */
21
22/* ======================================================================
23 * Frame header layout (v3, 8 bytes total)
24 *
25 * Proto_ver field removed — both sides always run the same binary.
26 * ack_delay reduced to 1 byte (10ms units, max 2.55s — covers all real delays).
27 * HAS_SNR bit removed — snr_raw==0 already signals "unknown".
28 *
29 * Byte 0: framer byte — set/validated by write_frame_header()/parse_frame_header()
30 * bits [7:5] = packet_type (3 bits: PACKET_TYPE_ARQ_CONTROL=0, ARQ_DATA=1, ARQ_CALL=2)
31 * bits [4:0] = CRC5 of bytes [1..frame_size-1]
32 * Byte 1: subtype — arq_subtype_t
33 * Byte 2: flags — bit7=TURN_REQ, bit6=HAS_DATA, bits[5:0]=spare
34 * Byte 3: session_id — random byte chosen by caller at connect time
35 * Byte 4: tx_seq — sender's frame sequence number
36 * Byte 5: rx_ack_seq — last sequence number received from peer
37 * Byte 6: snr_raw — local RX SNR feedback to peer; 0=unknown
38 * encoded as uint8_t: (int)round(snr_dB) + 128, clamped 1-255
39 * Byte 7: ack_delay — IRS→ISS: time from data_rx to ack_tx, in 10ms units; 0=unknown
40 * ISS computes: OTA_RTT = (ack_rx_ms - data_tx_start_ms) - ack_delay×10
41 *
42 * CONNECT frames (CALL/ACCEPT) use a separate compact layout — see below.
43 * They are identified by PACKET_TYPE_ARQ_CALL in the framer byte.
44 *
45 * Payload bytes (DATA frames only) follow immediately after byte 7.
46 * ====================================================================== */
47
48#define ARQ_HDR_SUBTYPE_IDX 1
49#define ARQ_HDR_FLAGS_IDX 2
50#define ARQ_HDR_SESSION_IDX 3
51#define ARQ_HDR_SEQ_IDX 4
52#define ARQ_HDR_ACK_IDX 5
53#define ARQ_HDR_SNR_IDX 6
54#define ARQ_HDR_DELAY_IDX 7
55#define ARQ_FRAME_HDR_SIZE 8 /* bytes 0-7 inclusive */
56
57/* CONNECT frames (CALL/ACCEPT) compact layout — 14 bytes, DATAC13 only.
58 * Uses PACKET_TYPE_ARQ_CALL in the framer byte.
59 *
60 * Byte 0: framer byte (PACKET_TYPE_ARQ_CALL | CRC5, set by write_frame_header)
61 * Byte 1: connect_meta = (session_id & 0x7F) | (is_accept ? 0x80 : 0x00)
62 * Bytes 2-13: arithmetic-encoded "DST|SRC" callsign string (12 bytes max)
63 */
64#define ARQ_CONNECT_SESSION_IDX 1
65#define ARQ_CONNECT_PAYLOAD_IDX 2
66#define ARQ_CONNECT_SESSION_MASK 0x7F
67#define ARQ_CONNECT_ACCEPT_FLAG 0x80
68#define ARQ_CONTROL_FRAME_SIZE 14
69#define ARQ_CONNECT_META_SIZE 2 /* framer byte + connect_meta byte */
70#define ARQ_CONNECT_MAX_ENCODED (ARQ_CONTROL_FRAME_SIZE - ARQ_CONNECT_META_SIZE)
71
72/* ======================================================================
73 * Flags byte (byte 2)
74 * ====================================================================== */
75
76#define ARQ_FLAG_TURN_REQ 0x80 /* bit 7: sender requests role turn */
77#define ARQ_FLAG_HAS_DATA 0x40 /* bit 6: sender has data queued (IRS→ISS) */
78#define ARQ_FLAG_LEN_HI 0x20 /* bit 5: DATA frames only — payload_valid *
79 * field carries bits [7:0] of valid byte *
80 * count; this flag carries bit 8, allowing *
81 * counts up to 511 (needed for DATAC1 which *
82 * has 502-byte payloads). */
83
84/* ======================================================================
85 * Frame subtypes
86 * ====================================================================== */
88typedef enum
101 /* Subtype 12 (FLOW_HINT) removed in v3 — replaced by HAS_DATA flag */
103
104/* ======================================================================
105 * Parsed frame header (in-memory representation, not wire layout)
106 * ====================================================================== */
108typedef struct
110 uint8_t packet_type; /* PACKET_TYPE_ARQ_CONTROL or _DATA (from framer byte) */
111 uint8_t subtype; /* arq_subtype_t */
112 uint8_t flags; /* ARQ_FLAG_* bitmask */
113 uint8_t session_id;
114 uint8_t tx_seq;
115 uint8_t rx_ack_seq;
116 uint8_t snr_raw; /* 0=unknown; decode via arq_protocol_decode_snr */
117 uint8_t ack_delay_raw; /* 0=unknown; 10ms units; decode via _decode_ack_delay */
119
120/* ======================================================================
121 * Per-mode timing table
122 *
123 * All times are in seconds (float) measured from the moment PTT goes ON
124 * unless noted otherwise.
125 *
126 * frame_duration_s: empirically measured on-air TX duration.
127 * tx_period_s: expected queue-to-PTT-ON latency (scheduling jitter).
128 * ack_timeout_s: maximum time from PTT-ON until ACK must be received.
129 * ack_timeout ≥ frame_duration + propagation + ACK return
130 * First frame deadline: enqueue_time + tx_period_s + ack_timeout_s
131 * Retry deadline: tx_start_ms + ack_timeout_s
132 * retry_interval_s: = ack_timeout_s + ARQ_ACK_GUARD_S
133 * payload_bytes: usable data bytes per frame.
134 * ====================================================================== */
136typedef struct
138 int freedv_mode; /* FREEDV_MODE_* constant */
139 float frame_duration_s; /* measured TX duration */
140 float tx_period_s; /* queue-to-PTT-ON latency */
141 float ack_timeout_s; /* from PTT-ON to ACK deadline */
142 float retry_interval_s; /* ack_timeout_s + ACK_GUARD_S */
143 int payload_bytes; /* usable payload per frame */
146/* Timing constants shared across modules */
147#define ARQ_CHANNEL_GUARD_MS 700 /* IRS response guard after frame decode.
148 * OFDM decode fires ~200ms before sender
149 * PTT-OFF, so effective gap at sender is
150 * (guard - 200ms) ≈ 500ms. Radio needs
151 * ~340ms for TX→RX switch → 160ms margin
152 * for preamble detection. At 500ms the
153 * effective gap was ~300ms, causing ~50%
154 * ACK loss on DATAC1 (< 340ms switch). */
155#define ARQ_ISS_POST_ACK_GUARD_MS 900 /* ISS guard before resuming DATA TX
156 * after receiving an ACK from the IRS.
157 * Larger than ARQ_CHANNEL_GUARD_MS:
158 * ack_rx fires ~168ms before IRS PTT-OFF,
159 * so the effective gap at IRS is only
160 * (guard + 100ms head) - 168ms.
161 * At 500ms: gap=432ms, too tight for
162 * DATAC1 re-sync after IRS ACK TX.
163 * At 900ms: gap=832ms — 492ms of clear
164 * air before the DATAC1 preamble. */
165#define ARQ_TURN_WAIT_AFTER_ACK_MS 3500 /* IRS post-ACK wait before TURN_REQ:
166 * ISS guard(900ms)+frame(2510ms)+margin */
167#define ARQ_ACCEPT_RX_WINDOW_MS 9000 /* ACCEPTING RX window after ACCEPT TX:
168 * ISS_guard(900)+DATAC4(5800)+margin(2300)
169 * Old value 7000 left only ~300ms margin
170 * and raced with TIMER_RETRY, causing
171 * 3-4 wasted ACCEPT retries (~28s). */
172#define ARQ_ACK_GUARD_S 1 /* extra slack added to retry interval */
173#define ARQ_CALL_RETRY_SLOTS 4 /* CALL retries before giving up */
174#define ARQ_ACCEPT_RETRY_SLOTS 4 /* ACCEPT retries before returning */
175#define ARQ_DATA_RETRY_SLOTS 10 /* DATA retries before disconnect */
176#define ARQ_DISCONNECT_RETRY_SLOTS 2 /* DISCONNECT frame retries */
177#define ARQ_CONNECT_GRACE_SLOTS 2 /* extra wait slots for ACCEPT */
178#define ARQ_CONNECT_BUSY_EXT_S 2 /* busy-extension guard after CALL */
179#define ARQ_KEEPALIVE_INTERVAL_S 20 /* keepalive TX interval */
180#define ARQ_KEEPALIVE_MISS_LIMIT 5 /* missed keepalives before disconnect */
181#define ARQ_TURN_REQ_RETRIES 2
182#define ARQ_MODE_REQ_RETRIES 2
183#define ARQ_MODE_SWITCH_HYST_COUNT 1 /* SNR provides stability gate; 1 = immediate */
184#define ARQ_STARTUP_MAX_S 8 /* DATAC13-only startup window */
185#define ARQ_STARTUP_ACKS_REQUIRED 1
186#define ARQ_PEER_PAYLOAD_HOLD_S 15 /* hold peer payload mode after activity */
187#define ARQ_SNR_HYST_DB 1.0f
188#define ARQ_SNR_MIN_DATAC4_DB -4.0f /* target MPP SNR (codec2 README) */
189#define ARQ_SNR_MIN_DATAC3_DB -1.0f
190#define ARQ_SNR_MIN_DATAC1_DB 3.0f
191#define ARQ_BACKLOG_MIN_DATAC3 56
192#define ARQ_BACKLOG_MIN_DATAC1 126
193#define ARQ_BACKLOG_MIN_BIDIR_UPGRADE 48 /* > DATAC4 payload capacity */
194#define ARQ_LADDER_LEVELS 3 /* 0=DATAC4, 1=DATAC3, 2=DATAC1 */
195#define ARQ_LADDER_UP_SUCCESSES 4 /* clean ACKs required to step up */
196#define ARQ_RETRY_DOWNGRADE_THRESHOLD 2 /* consecutive retries to force downgrade */
197#define ARQ_MODE_HOLD_AFTER_DOWNGRADE_S 15 /* hold lower mode after forced downgrade */
198
199/* In DATA frames the ack_delay byte is repurposed to carry payload_valid:
200 * 0 = full frame (all user bytes are valid data)
201 * 1 .. user_bytes = only this many leading bytes are valid; rest is padding
202 * This lets partial last-frames be transmitted with correct CRC5 (the slot is
203 * always filled to the full modem payload, CRC5 covers all bytes). */
204#define ARQ_DATA_LEN_FULL 0
205
206/* Mode table (defined in arq_protocol.c) */
207extern const arq_mode_timing_t arq_mode_table[];
208extern const int arq_mode_table_count;
209
210/* ======================================================================
211 * Frame codec API
212 * ====================================================================== */
213
218int arq_protocol_encode_hdr(uint8_t *buf, size_t buf_len, const arq_frame_hdr_t *hdr);
219
224int arq_protocol_decode_hdr(const uint8_t *buf, size_t buf_len, arq_frame_hdr_t *hdr);
225
230uint8_t arq_protocol_encode_snr(float snr_db);
231
236float arq_protocol_decode_snr(uint8_t snr_raw);
237
241uint8_t arq_protocol_encode_ack_delay(uint32_t delay_ms);
242
246uint32_t arq_protocol_decode_ack_delay(uint8_t raw);
247
252const arq_mode_timing_t *arq_protocol_mode_timing(int freedv_mode);
253
254/* ======================================================================
255 * Frame builder API
256 *
257 * Each function fills `buf` (caller-provided) with a complete ready-to-TX
258 * frame (framer byte + header/payload) and returns the total byte count,
259 * or -1 if buf_len < required size or arguments are invalid.
260 *
261 * The framer byte (byte 0, CRC5 + packet_type) is written by
262 * write_frame_header() inside each builder.
263 *
264 * For control frames, frame_size = ARQ_CONTROL_FRAME_SIZE (14 bytes).
265 * Callers typically allocate INT_BUFFER_SIZE and pass ARQ_CONTROL_FRAME_SIZE.
266 * ====================================================================== */
267
268/* --- Control frames (all use PACKET_TYPE_ARQ_CONTROL) --- */
269
271int arq_protocol_build_ack(uint8_t *buf, size_t buf_len,
272 uint8_t session_id, uint8_t rx_ack_seq,
273 uint8_t flags, uint8_t snr_raw,
274 uint8_t ack_delay_raw);
275
277int arq_protocol_build_disconnect(uint8_t *buf, size_t buf_len,
278 uint8_t session_id, uint8_t snr_raw);
279
281int arq_protocol_build_keepalive(uint8_t *buf, size_t buf_len,
282 uint8_t session_id, uint8_t snr_raw);
283
285int arq_protocol_build_keepalive_ack(uint8_t *buf, size_t buf_len,
286 uint8_t session_id, uint8_t snr_raw);
287
296int arq_protocol_build_turn_req(uint8_t *buf, size_t buf_len,
297 uint8_t session_id, uint8_t rx_ack_seq,
298 uint8_t snr_raw);
299
301int arq_protocol_build_turn_ack(uint8_t *buf, size_t buf_len,
302 uint8_t session_id, uint8_t snr_raw);
303
312int arq_protocol_build_mode_req(uint8_t *buf, size_t buf_len,
313 uint8_t session_id, uint8_t snr_raw,
314 int freedv_mode);
315
317int arq_protocol_build_mode_ack(uint8_t *buf, size_t buf_len,
318 uint8_t session_id, uint8_t snr_raw,
319 int freedv_mode);
320
321/* --- Data frame (PACKET_TYPE_ARQ_DATA) --- */
322
336int arq_protocol_build_data(uint8_t *buf, size_t buf_len,
337 uint8_t session_id, uint8_t tx_seq,
338 uint8_t rx_ack_seq, uint8_t flags,
339 uint8_t snr_raw, uint8_t payload_valid,
340 const uint8_t *payload, size_t payload_len);
341
342/* --- CALL/ACCEPT compact frames (PACKET_TYPE_ARQ_CALL) --- */
343
353int arq_protocol_build_call(uint8_t *buf, size_t buf_len,
354 uint8_t session_id,
355 const char *src, const char *dst);
356
365int arq_protocol_build_accept(uint8_t *buf, size_t buf_len,
366 uint8_t session_id,
367 const char *src, const char *dst);
368
378int arq_protocol_parse_call(const uint8_t *buf, size_t buf_len,
379 uint8_t *session_id_out,
380 char *src_out, char *dst_out);
381
385int arq_protocol_parse_accept(const uint8_t *buf, size_t buf_len,
386 uint8_t *session_id_out,
387 char *src_out, char *dst_out);
388
389#endif /* ARQ_PROTOCOL_H_ */
int arq_protocol_parse_accept(const uint8_t *buf, size_t buf_len, uint8_t *session_id_out, char *src_out, char *dst_out)
Parse an ACCEPT frame; same layout as CALL.
Definition arq_protocol.c:417
int arq_protocol_build_mode_ack(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t snr_raw, int freedv_mode)
MODE_ACK frame — accept peer's mode request.
Definition arq_protocol.c:260
int arq_protocol_build_accept(uint8_t *buf, size_t buf_len, uint8_t session_id, const char *src, const char *dst)
Build an ACCEPT frame.
Definition arq_protocol.c:389
int arq_protocol_build_turn_ack(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t snr_raw)
TURN_ACK frame.
Definition arq_protocol.c:240
const int arq_mode_table_count
Definition arq_protocol.c:60
const arq_mode_timing_t * arq_protocol_mode_timing(int freedv_mode)
Look up mode timing entry for a FreeDV mode.
Definition arq_protocol.c:67
int arq_protocol_build_ack(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t rx_ack_seq, uint8_t flags, uint8_t snr_raw, uint8_t ack_delay_raw)
ACK frame.
Definition arq_protocol.c:192
int arq_protocol_build_disconnect(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t snr_raw)
DISCONNECT frame.
Definition arq_protocol.c:203
int arq_protocol_build_keepalive_ack(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t snr_raw)
KEEPALIVE_ACK frame.
Definition arq_protocol.c:221
int arq_protocol_build_keepalive(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t snr_raw)
KEEPALIVE frame.
Definition arq_protocol.c:212
uint8_t arq_protocol_encode_ack_delay(uint32_t delay_ms)
Encode ack_delay_ms to the 8-bit wire value (10ms units, max 2.55s).
Definition arq_protocol.c:143
int arq_protocol_parse_call(const uint8_t *buf, size_t buf_len, uint8_t *session_id_out, char *src_out, char *dst_out)
Parse a CALL frame; extract callsigns.
Definition arq_protocol.c:410
int arq_protocol_build_turn_req(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t rx_ack_seq, uint8_t snr_raw)
TURN_REQ frame.
Definition arq_protocol.c:230
int arq_protocol_build_call(uint8_t *buf, size_t buf_len, uint8_t session_id, const char *src, const char *dst)
Build a CALL frame.
Definition arq_protocol.c:382
int arq_protocol_build_data(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t tx_seq, uint8_t rx_ack_seq, uint8_t flags, uint8_t snr_raw, uint8_t payload_valid, const uint8_t *payload, size_t payload_len)
DATA frame — 8-byte header + payload bytes.
Definition arq_protocol.c:271
uint32_t arq_protocol_decode_ack_delay(uint8_t raw)
Decode the 8-bit wire ack_delay to milliseconds.
Definition arq_protocol.c:153
int arq_protocol_build_mode_req(uint8_t *buf, size_t buf_len, uint8_t session_id, uint8_t snr_raw, int freedv_mode)
MODE_REQ frame.
Definition arq_protocol.c:249
uint8_t arq_protocol_encode_snr(float snr_db)
Encode a floating-point SNR (dB) into the snr_raw wire byte.
Definition arq_protocol.c:121
const arq_mode_timing_t arq_mode_table[]
Definition arq_protocol.c:52
int arq_protocol_decode_hdr(const uint8_t *buf, size_t buf_len, arq_frame_hdr_t *hdr)
Decode the ARQ header from the first bytes of buf.
Definition arq_protocol.c:98
float arq_protocol_decode_snr(uint8_t snr_raw)
Decode snr_raw wire byte back to float dB.
Definition arq_protocol.c:129
arq_subtype_t
Definition arq_protocol.h:85
@ ARQ_SUBTYPE_DISCONNECT
Definition arq_protocol.h:89
@ ARQ_SUBTYPE_ACCEPT
Definition arq_protocol.h:87
@ ARQ_SUBTYPE_DATA
Definition arq_protocol.h:90
@ ARQ_SUBTYPE_CALL
Definition arq_protocol.h:86
@ ARQ_SUBTYPE_KEEPALIVE
Definition arq_protocol.h:91
@ ARQ_SUBTYPE_MODE_ACK
Definition arq_protocol.h:94
@ ARQ_SUBTYPE_TURN_ACK
Definition arq_protocol.h:96
@ ARQ_SUBTYPE_TURN_REQ
Definition arq_protocol.h:95
@ ARQ_SUBTYPE_KEEPALIVE_ACK
Definition arq_protocol.h:92
@ ARQ_SUBTYPE_MODE_REQ
Definition arq_protocol.h:93
@ ARQ_SUBTYPE_ACK
Definition arq_protocol.h:88
int arq_protocol_encode_hdr(uint8_t *buf, size_t buf_len, const arq_frame_hdr_t *hdr)
Encode a parsed header into the first ARQ_FRAME_HDR_SIZE bytes of buf.
Definition arq_protocol.c:82
Definition arq_protocol.h:105
Definition arq_protocol.h:133