Mercury Modem
Mercury ARQ/Broadcast modem
Loading...
Searching...
No Matches
arq_fsm.h
Go to the documentation of this file.
1/* HERMES Modem — ARQ FSM: state/event types and session structure
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_FSM_H_
10#define ARQ_FSM_H_
11
12#include <stdbool.h>
13#include <stddef.h>
14#include <stdint.h>
15
16#include "arq.h" /* CALLSIGN_MAX_SIZE, arq_action_t/type, arq_info */
17#include "arq_timing.h" /* arq_timing_ctx_t */
18#include "arq_protocol.h"
19#include "carousel.h" /* car_t: the carousel data plane */
20
21/* ======================================================================
22 * Level 1 — Connection FSM states
23 * ====================================================================== */
24
25typedef enum
26{
27 ARQ_CONN_DISCONNECTED = 0, /* no session; idle */
28 ARQ_CONN_LISTENING = 1, /* waiting for incoming CALL frame */
29 ARQ_CONN_CALLING = 2, /* outgoing CALL sent; awaiting ACCEPT */
30 ARQ_CONN_ACCEPTING = 3, /* ACCEPT sent; awaiting the caller's first frame */
31 ARQ_CONN_CONNECTED = 4, /* the carousel runs the session */
32 ARQ_CONN_DISCONNECTING = 5, /* DISCONNECT frame being exchanged */
35
36/* ======================================================================
37 * Events
38 * ====================================================================== */
39
40typedef enum
41{
42 /* Application events (from TCP interface via channel bus) */
43 ARQ_EV_APP_LISTEN = 0, /* LISTEN ON received */
44 ARQ_EV_APP_STOP_LISTEN, /* LISTEN OFF received */
45 ARQ_EV_APP_CONNECT, /* CONNECT <dst> received */
46 ARQ_EV_APP_DISCONNECT, /* DISCONNECT received */
47 ARQ_EV_APP_DATA_READY, /* TX data available in buffer */
48
49 /* Radio RX events (from modem worker) */
50 ARQ_EV_RX_CALL, /* CALL frame decoded */
51 ARQ_EV_RX_ACCEPT, /* ACCEPT frame decoded */
52 ARQ_EV_RX_DISCONNECT, /* DISCONNECT frame decoded */
53
54 /* Timer events */
55 ARQ_EV_TIMER_RETRY, /* CALL/ACCEPT retry, teardown waits */
56 ARQ_EV_TIMER_ACK, /* a reply guard, a DISCONNECT's wait */
57
58 /* Modem events */
59 ARQ_EV_TX_STARTED, /* PTT ON (frame on air) */
60 ARQ_EV_TX_COMPLETE, /* PTT OFF (TX finished) */
61
62 /* Carousel data plane */
63 ARQ_EV_RX_CAROUSEL, /* a frame with the session's CRC seed */
64 ARQ_EV_TIMER_CAROUSEL, /* the carousel's next deadline */
65 ARQ_EV_RX_PATTERN, /* a pattern heard (pattern_break) */
66
69
76typedef struct
77{
79
80 /* RX events: the session id, the carrier the frame came on, and the
81 * frame itself (RX_CAROUSEL). */
82 uint8_t session_id;
83 int mode;
84 uint8_t payload[1280]; /* >= largest user payload: QAM16C2
85 * carries 1213 - 8 = 1205 bytes */
87
88 /* RX_CAROUSEL: the control decoder (DATAC16) produced it; otherwise the
89 * payload decoder, in `mode`. RX_ACCEPT: the rung the callee starts our
90 * rounds on (0..6, or ARQ_ACCEPT_LEVEL_CTL_DEAF), its session nonce, and
91 * the CRC16 of the callsign we called, which the FSM checks. */
94 uint16_t car_nonce;
96
97 /* Local receive SNR at the time the frame was decoded (dB, 0 = unknown):
98 * a CALL's sets the start rung the ACCEPT names. */
99 float rx_snr;
100 /* RX_CAROUSEL: how long ago the frame ended when it was decoded. */
101 uint32_t rx_age_ms;
102
103 /* RX_PATTERN: the BREAK kind, else the session ACK. */
105
106 /* APP_DISCONNECT from the host's ABORT: drop what is still in flight and
107 * go at once, rather than drain it first as DISCONNECT does (#218). */
108 bool abort;
109
110 /* Call setup */
111 char remote_call[CALLSIGN_MAX_SIZE];
112 /* For an incoming CALL: which of OUR callsigns (primary or a secondary)
113 * the frame's DST CRC16 matched — i.e. the SSID the caller dialed. Empty
114 * when not applicable; the callee reports this as the connection's local
115 * address so a station listening on multiple SSIDs shows the dialed one. */
116 char local_call[CALLSIGN_MAX_SIZE];
118
119/* ======================================================================
120 * Session structure — replaces arq_ctx_t
121 *
122 * All state is in this struct; no hidden global flags.
123 * Monotonic timestamps are uint64_t milliseconds from hermes_log startup.
124 * ====================================================================== */
125
126typedef struct
127{
128 /* --- State machine fields --- */
129 arq_conn_state_t conn_state; /* connection state */
130
131 /* --- Identifiers --- */
132 uint8_t session_id; /* random byte chosen by caller */
133 int call_sends_done; /* CALLs sent this attempt (escalation) */
134 int call_carrier; /* the carrier the last CALL went out on */
135 int call_rx_mode; /* the carrier the CALL we answer came on */
136 char remote_call[CALLSIGN_MAX_SIZE];
137 char local_call[CALLSIGN_MAX_SIZE]; /* our dialed callsign/SSID for an
138 * accepted incoming CALL (empty =>
139 * fall back to the primary) */
140
141 /* --- Mode / speed --- */
142 int payload_mode; /* MY data TX mode (ISS); per-direction,
143 * independent of peer's TX mode */
144 int control_mode; /* always FREEDV_MODE_DATAC16 */
145 int initial_payload_mode; /* startup payload mode (= broadcast RX
146 * mode); restored on disconnect so the
147 * payload decoder matches broadcast */
148 int peer_tx_mode; /* my payload RX decoder's mode: the
149 * carousel binds it to the peer's
150 * rung, idle restores the listen mode */
151
152 /* --- Retry/timeout bookkeeping --- */
153 int tx_retries_left; /* retries remaining for current frame.
154 * In ACCEPTING this counts TIMER_RETRY
155 * slots, not transmissions: a slot may be
156 * spent silently when the RX window closes
157 * without a CALL (see fsm_accepting). */
158 uint64_t state_enter_ms; /* when current conn_state was entered */
159
160 /* When a decoder was last holding sync on an incoming burst, i.e. when the
161 * peer was last observed transmitting. Written by the modem RX thread via
162 * arq_update_link_metrics() under g_sess_lock, read by the FSM; used to
163 * avoid keying into the peer's frame. */
165 /* A decoder caught a frame's preamble: the frame is on the air until
166 * this, whatever its sync does meanwhile (arq_note_rx_preamble). */
168 uint64_t nav_busy_until_ms; /* a NAV header heard: the peer's keydown runs until then */
169 uint8_t disc_defer_count; /* consecutive busy-channel deferrals
170 * of our DISCONNECT */
171 bool tx_active; /* we are keyed (TX_STARTED seen, no
172 * TX_COMPLETE yet) */
173 uint64_t last_tx_end_ms; /* when our last transmission ended */
174 bool host_released; /* the application ended this session
175 * (DISCONNECT/ABORT): nothing more is
176 * delivered to it until a new one.
177 * Only APP_DISCONNECT (DISCONNECT,
178 * ABORT, control client gone) sets
179 * it: it is the one teardown that stays
180 * CONNECTED (draining the backlog)
181 * after the host is told DISCONNECTED.
182 * Peer DISCONNECT, timeouts and LISTEN
183 * OFF leave CONNECTED at once, so
184 * nothing is delivered after them. */
185 int local_snr_x10; /* local SNR EMA * 10 */
186
187 /* --- Data bookkeeping --- */
188 int tx_inflight_bytes; /* bytes the carousel holds unconfirmed */
189 int pending_burst_frames; /* frames accumulated for current PTT burst;
190 * written by cb_send_tx_frame under g_sess_lock
191 * (replaces the former function-static so that
192 * the cmd-bridge SEND_CQ path and the FSM event
193 * loop do not race on a shared static) */
194
195 /* --- Connection lifecycle --- */
196 bool listen_enabled; /* app listen intent (LISTEN ON/OFF):
197 * selects the post-call idle status,
198 * LISTENING vs DISCONNECTED, so an ARQ
199 * call always returns to where it was */
200
201 /* --- Connect handshake --- */
202 bool accept_fallback; /* LISTENING was entered because our ACCEPT
203 * retries ran out: the caller may still
204 * have heard it, so its first carousel
205 * frame may complete this session. Only
206 * then. */
207 bool accept_tx_pending; /* the pending TIMER_RETRY is an ACCEPT
208 * answering a CALL we actually heard,
209 * not the RX-window timer. Only the
210 * former may key the transmitter -- see
211 * fsm_accepting's TIMER_RETRY. */
212
213 /* --- Teardown flags --- */
214 bool deferred_listen_off; /* LISTEN OFF received during grace period;
215 * will be honoured once the grace expires */
216 bool pending_disconnect_notify;/* defer notify_disconnected until TX done */
217 bool disconnect_sent; /* our DISCONNECT is on the air this teardown */
218 bool pending_disconnect_reply; /* our reply to the peer's DISCONNECT is
219 * due when the reply guard expires */
220 bool pending_disconnect; /* APP_DISCONNECT deferred until TX buf empty */
221 bool pending_connect; /* CONNECT arrived while DISCONNECTING: place
222 * the call once the teardown completes */
223 char pending_connect_call[CALLSIGN_MAX_SIZE];
224 uint64_t disconnect_deadline_ms; /* absolute time by which a deferred
225 * APP_DISCONNECT must resolve into a
226 * clean teardown. 0 = none armed. */
227
228 uint64_t last_rx_ms; /* last frame decoded from the peer */
229
230 /* --- Carousel data plane (carousel.h) --- *
231 * car_active while CONNECTED (and through a callee's ending); deadline_ms
232 * then mirrors the carousel's next deadline (ARQ_EV_TIMER_CAROUSEL). crc_seed rides every carousel frame's CRC16. */
233 car_t *car; /* allocated by arq_fsm_init */
235 bool car_session; /* this session ran the carousel (until idle) */
236 bool car_ending; /* the callee ends it: DISCONNECT in its next slot */
237 uint64_t car_end_deadline_ms; /* ...or gives up then, silently */
238 uint16_t crc_seed;
239 uint16_t car_nonce; /* the callee's, in the ACCEPT: part of crc_seed */
240 int car_rx_level; /* start rung for the peer's direction:
241 * the SNR measured here */
242 int car_tx_level; /* ...for ours, from the peer (-1: none) */
243 bool car_ctl_deaf; /* connect: I heard the peer below the control mode */
244 bool car_peer_ctl_deaf; /* connect: ...and it, me (the CALL came on MFSK) */
245 bool ctl_floor_at_stop; /* the carousel's peer_ctl_deaf when it stopped,
246 * for the DISCONNECT exchange after it */
247 uint64_t car_last_rx_ms; /* last carousel frame from the peer */
248
249 /* --- Timer mechanism --- */
250 uint64_t deadline_ms; /* absolute monotonic deadline */
251 arq_event_id_t deadline_event; /* event to fire when deadline fires */
253
254/* ======================================================================
255 * FSM action callbacks
256 *
257 * Registered once at startup via arq_fsm_set_callbacks().
258 * All callbacks are called from the ARQ event-loop thread.
259 * ====================================================================== */
260
261typedef struct
262{
264 /* burst_remaining: DATA frames still to follow in the same PTT burst
265 * (0 = last/only frame — the bridge enqueues the modem action then).
266 * Control frames always pass 0. */
267 void (*send_tx_frame)(int packet_type, int mode,
268 size_t frame_size, const uint8_t *frame,
269 int burst_remaining);
270
274 void (*notify_connected)(const char *remote_call, const char *local_call);
275
279 void (*notify_pending)(const char *remote_call, const char *local_call);
280
282 void (*notify_cancelpending)(void);
283
286 void (*notify_disconnected)(bool to_no_client);
287
289 void (*deliver_rx_data)(const uint8_t *data, size_t len);
290
292 int (*tx_backlog)(void);
293
295 int (*tx_read)(uint8_t *buf, size_t len);
296
298 void (*send_buffer_status)(int backlog_bytes);
299
304 bool (*channel_busy)(void);
305
309 void (*send_keydown)(const arq_keydown_t *kd);
311 void (*send_pattern)(int pattern_kind);
312 void (*set_crc_seed)(uint16_t seed);
314
319
325
326/* ======================================================================
327 * FSM public API (implemented in arq_fsm.c)
328 * ====================================================================== */
329
334void arq_fsm_init(arq_session_t *sess);
335
336/* Free what arq_fsm_init allocated. */
338
348void arq_fsm_dispatch(arq_session_t *sess, const arq_event_t *event);
349
359int arq_fsm_timeout_ms(const arq_session_t *sess, uint64_t now);
360
363int arq_call_carrier(const arq_session_t *sess);
365bool arq_fsm_expect_pattern(const arq_session_t *sess, uint64_t now);
366
371
375const char *arq_event_name(arq_event_id_t ev);
376
377#endif /* ARQ_FSM_H_ */
#define CALLSIGN_MAX_SIZE
Definition arq.h:24
arq_event_id_t
Definition arq_fsm.h:41
@ ARQ_EV_TX_STARTED
Definition arq_fsm.h:59
@ ARQ_EV_TIMER_ACK
Definition arq_fsm.h:56
@ ARQ_EV_TX_COMPLETE
Definition arq_fsm.h:60
@ ARQ_EV_TIMER_RETRY
Definition arq_fsm.h:55
@ ARQ_EV_RX_PATTERN
Definition arq_fsm.h:65
@ ARQ_EV_APP_DATA_READY
Definition arq_fsm.h:47
@ ARQ_EV_RX_ACCEPT
Definition arq_fsm.h:51
@ ARQ_EV_APP_DISCONNECT
Definition arq_fsm.h:46
@ ARQ_EV_RX_CALL
Definition arq_fsm.h:50
@ ARQ_EV_RX_DISCONNECT
Definition arq_fsm.h:52
@ ARQ_EV_APP_STOP_LISTEN
Definition arq_fsm.h:44
@ ARQ_EV_RX_CAROUSEL
Definition arq_fsm.h:63
@ ARQ_EV_TIMER_CAROUSEL
Definition arq_fsm.h:64
@ ARQ_EV_APP_CONNECT
Definition arq_fsm.h:45
@ ARQ_EV__COUNT
Definition arq_fsm.h:67
@ ARQ_EV_APP_LISTEN
Definition arq_fsm.h:43
bool arq_fsm_expect_pattern(const arq_session_t *sess, uint64_t now)
Whether a pattern can arrive now (the modem's detector runs only then).
Definition arq_fsm.c:438
void arq_fsm_release(arq_session_t *sess)
Definition arq_fsm.c:110
void arq_fsm_init(arq_session_t *sess)
Initialise a session structure to DISCONNECTED state.
Definition arq_fsm.c:96
const char * arq_conn_state_name(arq_conn_state_t s)
Human-readable name for a connection state (for log output).
Definition arq_fsm.c:34
void arq_fsm_set_callbacks(const arq_fsm_callbacks_t *cbs)
Register FSM action callbacks (call once before first dispatch).
Definition arq_fsm.c:82
arq_conn_state_t
Definition arq_fsm.h:26
@ ARQ_CONN_DISCONNECTED
Definition arq_fsm.h:27
@ ARQ_CONN_LISTENING
Definition arq_fsm.h:28
@ ARQ_CONN_CONNECTED
Definition arq_fsm.h:31
@ ARQ_CONN_ACCEPTING
Definition arq_fsm.h:30
@ ARQ_CONN_CALLING
Definition arq_fsm.h:29
@ ARQ_CONN__COUNT
Definition arq_fsm.h:33
@ ARQ_CONN_DISCONNECTING
Definition arq_fsm.h:32
int arq_fsm_timeout_ms(const arq_session_t *sess, uint64_t now)
Return milliseconds until the next deadline, or INT_MAX if idle.
Definition arq_fsm.c:116
int arq_call_carrier(const arq_session_t *sess)
The carrier the next CALL goes out on: the control mode for the first ARQ_CALL_FAST_SLOTS,...
Definition arq_fsm.c:246
void arq_fsm_dispatch(arq_session_t *sess, const arq_event_t *event)
Dispatch an event through both FSM levels.
Definition arq_fsm.c:1532
const char * arq_event_name(arq_event_id_t ev)
Human-readable name for an event (for log output).
Definition arq_fsm.c:48
void arq_fsm_set_timing(arq_timing_ctx_t *timing)
Register timing context for recording (call once at init).
Definition arq_fsm.c:87
static void send_pattern(car_t *c, int kind)
Definition carousel.c:1459
int mode
Definition carousel.c:2330
ARQ event with all possible payload fields.
Definition arq_fsm.h:77
bool pattern_break
Definition arq_fsm.h:104
uint16_t car_called_crc
Definition arq_fsm.h:95
bool from_control
Definition arq_fsm.h:92
size_t payload_len
Definition arq_fsm.h:86
bool abort
Definition arq_fsm.h:108
uint8_t session_id
Definition arq_fsm.h:82
uint16_t car_nonce
Definition arq_fsm.h:94
uint32_t rx_age_ms
Definition arq_fsm.h:101
int mode
Definition arq_fsm.h:83
int car_level
Definition arq_fsm.h:93
float rx_snr
Definition arq_fsm.h:99
arq_event_id_t id
Definition arq_fsm.h:78
Definition arq_fsm.h:262
Definition arq.h:97
Definition arq_fsm.h:127
car_t * car
Definition arq_fsm.h:233
uint64_t nav_busy_until_ms
Definition arq_fsm.h:168
bool accept_fallback
Definition arq_fsm.h:202
bool ctl_floor_at_stop
Definition arq_fsm.h:245
uint64_t last_rx_sync_ms
Definition arq_fsm.h:164
bool car_active
Definition arq_fsm.h:234
uint8_t disc_defer_count
Definition arq_fsm.h:169
uint8_t session_id
Definition arq_fsm.h:132
bool pending_connect
Definition arq_fsm.h:221
bool pending_disconnect_notify
Definition arq_fsm.h:216
uint64_t last_rx_ms
Definition arq_fsm.h:228
uint16_t car_nonce
Definition arq_fsm.h:239
int call_rx_mode
Definition arq_fsm.h:135
bool car_session
Definition arq_fsm.h:235
bool host_released
Definition arq_fsm.h:174
bool deferred_listen_off
Definition arq_fsm.h:214
bool accept_tx_pending
Definition arq_fsm.h:207
bool tx_active
Definition arq_fsm.h:171
uint16_t crc_seed
Definition arq_fsm.h:238
int tx_retries_left
Definition arq_fsm.h:153
int call_carrier
Definition arq_fsm.h:134
int local_snr_x10
Definition arq_fsm.h:185
int car_tx_level
Definition arq_fsm.h:242
bool listen_enabled
Definition arq_fsm.h:196
uint64_t car_last_rx_ms
Definition arq_fsm.h:247
uint64_t last_tx_end_ms
Definition arq_fsm.h:173
uint64_t state_enter_ms
Definition arq_fsm.h:158
bool disconnect_sent
Definition arq_fsm.h:217
int call_sends_done
Definition arq_fsm.h:133
int peer_tx_mode
Definition arq_fsm.h:148
int car_rx_level
Definition arq_fsm.h:240
int payload_mode
Definition arq_fsm.h:142
uint64_t disconnect_deadline_ms
Definition arq_fsm.h:224
int initial_payload_mode
Definition arq_fsm.h:145
arq_event_id_t deadline_event
Definition arq_fsm.h:251
bool car_ending
Definition arq_fsm.h:236
bool car_ctl_deaf
Definition arq_fsm.h:243
uint64_t rx_frame_busy_until_ms
Definition arq_fsm.h:167
arq_conn_state_t conn_state
Definition arq_fsm.h:129
int control_mode
Definition arq_fsm.h:144
bool pending_disconnect
Definition arq_fsm.h:220
int pending_burst_frames
Definition arq_fsm.h:189
bool car_peer_ctl_deaf
Definition arq_fsm.h:244
uint64_t deadline_ms
Definition arq_fsm.h:250
int tx_inflight_bytes
Definition arq_fsm.h:188
bool pending_disconnect_reply
Definition arq_fsm.h:218
uint64_t car_end_deadline_ms
Definition arq_fsm.h:237
Definition arq_timing.h:20
Definition carousel.h:135