hfmodem 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (729) hide show
  1. hfmodem/__init__.py +12 -0
  2. hfmodem/__main__.py +12 -0
  3. hfmodem/besra/__init__.py +12 -0
  4. hfmodem/besra/arq/__init__.py +5 -0
  5. hfmodem/besra/arq/modem.py +342 -0
  6. hfmodem/besra/arq/session.py +1265 -0
  7. hfmodem/besra/crc.py +80 -0
  8. hfmodem/besra/dsp/__init__.py +5 -0
  9. hfmodem/besra/dsp/detect.py +301 -0
  10. hfmodem/besra/dsp/templates.py +132 -0
  11. hfmodem/besra/fec/__init__.py +8 -0
  12. hfmodem/besra/fec/rs.py +299 -0
  13. hfmodem/besra/frame/__init__.py +6 -0
  14. hfmodem/besra/frame/callsign.py +145 -0
  15. hfmodem/besra/frame/frame.py +199 -0
  16. hfmodem/besra/host/__init__.py +10 -0
  17. hfmodem/besra/host/modem_core.py +329 -0
  18. hfmodem/besra/host/protocol.py +68 -0
  19. hfmodem/besra/host/run_server.py +453 -0
  20. hfmodem/besra/host/server.py +633 -0
  21. hfmodem/besra/monitor.py +312 -0
  22. hfmodem/besra/phy/__init__.py +5 -0
  23. hfmodem/besra/phy/demodulator.py +1418 -0
  24. hfmodem/besra/phy/memory.py +301 -0
  25. hfmodem/besra/phy/modulator.py +343 -0
  26. hfmodem/besra/phy/quality.py +136 -0
  27. hfmodem/besra/radio.py +828 -0
  28. hfmodem/besra/sim/__init__.py +5 -0
  29. hfmodem/besra/sim/air.py +92 -0
  30. hfmodem/besra/sim/channel.py +80 -0
  31. hfmodem/besra/sim/echo.py +60 -0
  32. hfmodem/cli.py +222 -0
  33. hfmodem/core/__init__.py +2 -0
  34. hfmodem/core/audio.py +964 -0
  35. hfmodem/core/band.py +52 -0
  36. hfmodem/core/busy.py +488 -0
  37. hfmodem/core/cbtrace.py +183 -0
  38. hfmodem/core/config.py +441 -0
  39. hfmodem/core/cwid.py +124 -0
  40. hfmodem/core/devices.py +88 -0
  41. hfmodem/core/gil.py +120 -0
  42. hfmodem/core/levels.py +242 -0
  43. hfmodem/core/occupied.py +138 -0
  44. hfmodem/core/ptt.py +745 -0
  45. hfmodem/core/rates.py +213 -0
  46. hfmodem/core/regulatory/__init__.py +164 -0
  47. hfmodem/core/regulatory/part97.py +300 -0
  48. hfmodem/core/resample.py +55 -0
  49. hfmodem/core/rig.py +912 -0
  50. hfmodem/core/rigs.py +46 -0
  51. hfmodem/core/rxreadiness.py +238 -0
  52. hfmodem/core/wav.py +246 -0
  53. hfmodem/kestrel/__init__.py +2 -0
  54. hfmodem/kestrel/arq/__init__.py +2 -0
  55. hfmodem/kestrel/arq/frames.py +187 -0
  56. hfmodem/kestrel/arq/fsm.py +608 -0
  57. hfmodem/kestrel/arq/modem.py +247 -0
  58. hfmodem/kestrel/arq/phy.py +421 -0
  59. hfmodem/kestrel/arq/run_pair.py +62 -0
  60. hfmodem/kestrel/assets.py +26 -0
  61. hfmodem/kestrel/coding/__init__.py +2 -0
  62. hfmodem/kestrel/coding/crc.py +40 -0
  63. hfmodem/kestrel/coding/turbo.py +260 -0
  64. hfmodem/kestrel/host/__init__.py +19 -0
  65. hfmodem/kestrel/host/modem_core.py +329 -0
  66. hfmodem/kestrel/host/protocol.py +30 -0
  67. hfmodem/kestrel/host/run_server.py +54 -0
  68. hfmodem/kestrel/host/server.py +399 -0
  69. hfmodem/kestrel/rx/__init__.py +2 -0
  70. hfmodem/kestrel/rx/bw2750_allocations.py +68 -0
  71. hfmodem/kestrel/rx/tablegen.py +772 -0
  72. hfmodem/kestrel/rx/varahf2300.py +844 -0
  73. hfmodem/kestrel/rx/varahf500.py +787 -0
  74. hfmodem/kestrel/tx/__init__.py +4 -0
  75. hfmodem/kestrel/tx/assets/prototype_pulse.npz +0 -0
  76. hfmodem/kestrel/tx/bw500_pulse.py +101 -0
  77. hfmodem/kestrel/tx/varahf2300_tx.py +249 -0
  78. hfmodem/kestrel/tx/varahf500_tx.py +258 -0
  79. hfmodem/kestrel/vara/__init__.py +11 -0
  80. hfmodem/kestrel/vara/vara_arq.py +7361 -0
  81. hfmodem/kestrel/vara/vara_control.py +167 -0
  82. hfmodem/kestrel/vara/vara_frames.py +1599 -0
  83. hfmodem/kestrel/vara/vara_mfsk.py +322 -0
  84. hfmodem/kestrel/vara/vara_ofdm.py +146 -0
  85. hfmodem/sabir/__init__.py +15 -0
  86. hfmodem/sabir/arq/__init__.py +9 -0
  87. hfmodem/sabir/arq/fastctl.py +92 -0
  88. hfmodem/sabir/arq/fsm.py +1085 -0
  89. hfmodem/sabir/arq/layout.py +119 -0
  90. hfmodem/sabir/arq/modem.py +383 -0
  91. hfmodem/sabir/arq/profiles.py +79 -0
  92. hfmodem/sabir/arq/wire.py +396 -0
  93. hfmodem/sabir/compress/__init__.py +83 -0
  94. hfmodem/sabir/dsp/__init__.py +8 -0
  95. hfmodem/sabir/dsp/channel.py +88 -0
  96. hfmodem/sabir/dsp/constellation.py +157 -0
  97. hfmodem/sabir/dsp/fading.py +215 -0
  98. hfmodem/sabir/dsp/sync.py +24 -0
  99. hfmodem/sabir/fec/__init__.py +7 -0
  100. hfmodem/sabir/fec/ldpc.py +190 -0
  101. hfmodem/sabir/fec/tbcc.py +207 -0
  102. hfmodem/sabir/floor/__init__.py +11 -0
  103. hfmodem/sabir/floor/beacon.py +352 -0
  104. hfmodem/sabir/floor/mfsk.py +282 -0
  105. hfmodem/sabir/frame/__init__.py +6 -0
  106. hfmodem/sabir/frame/codec.py +116 -0
  107. hfmodem/sabir/frame/datagram.py +158 -0
  108. hfmodem/sabir/frame/reporting.py +57 -0
  109. hfmodem/sabir/host/__init__.py +9 -0
  110. hfmodem/sabir/host/cbor.py +126 -0
  111. hfmodem/sabir/host/client.py +114 -0
  112. hfmodem/sabir/host/hostlink.py +382 -0
  113. hfmodem/sabir/host/messages.py +115 -0
  114. hfmodem/sabir/host/modem_core.py +425 -0
  115. hfmodem/sabir/host/run_server.py +52 -0
  116. hfmodem/sabir/monitor.py +365 -0
  117. hfmodem/sabir/offair.py +211 -0
  118. hfmodem/sabir/onair.py +456 -0
  119. hfmodem/sabir/phy/__init__.py +6 -0
  120. hfmodem/sabir/phy/modem.py +579 -0
  121. hfmodem/sabir/phy/preamble.py +125 -0
  122. hfmodem/sabir/phy/rate.py +7 -0
  123. hfmodem/sabir/proof.py +261 -0
  124. hfmodem/sabir/radio.py +352 -0
  125. hfmodem/sabir/sim/__init__.py +2 -0
  126. hfmodem/sabir/sim/air.py +159 -0
  127. hfmodem/sabir/sim/design.py +151 -0
  128. hfmodem/sabir/sim/impulse.py +37 -0
  129. hfmodem/sabir/sim/m1.py +239 -0
  130. hfmodem/sabir/sim/m2.py +250 -0
  131. hfmodem/sabir/sim/m2b.py +219 -0
  132. hfmodem/sabir/sim/m3.py +360 -0
  133. hfmodem/sabir/sim/m4.py +191 -0
  134. hfmodem/sabir/sim/m6a.py +160 -0
  135. hfmodem/sabir/sim/m6b.py +195 -0
  136. hfmodem/sabir/sim/m6c.py +260 -0
  137. hfmodem/sabir/sim/negotiation_campaign.py +81 -0
  138. hfmodem/sabir/sim/release.py +91 -0
  139. hfmodem/sabir/sim/watterson.py +119 -0
  140. hfmodem/shrike/__init__.py +15 -0
  141. hfmodem/shrike/arq.py +4208 -0
  142. hfmodem/shrike/beacon.py +102 -0
  143. hfmodem/shrike/coding.py +490 -0
  144. hfmodem/shrike/compress.py +1075 -0
  145. hfmodem/shrike/frame.py +130 -0
  146. hfmodem/shrike/hostmode.py +345 -0
  147. hfmodem/shrike/live.py +298 -0
  148. hfmodem/shrike/modem.py +377 -0
  149. hfmodem/shrike/monitor.py +174 -0
  150. hfmodem/shrike/onair.py +14490 -0
  151. hfmodem/shrike/ota.py +798 -0
  152. hfmodem/shrike/p1rx.py +1989 -0
  153. hfmodem/shrike/p2rx.py +973 -0
  154. hfmodem/shrike/p3acquire.py +301 -0
  155. hfmodem/shrike/p3frame.py +393 -0
  156. hfmodem/shrike/p3rx.py +1355 -0
  157. hfmodem/shrike/p3trial.py +253 -0
  158. hfmodem/shrike/p4chirp.py +160 -0
  159. hfmodem/shrike/p4coding.py +128 -0
  160. hfmodem/shrike/p4decode.py +143 -0
  161. hfmodem/shrike/p4normal.py +186 -0
  162. hfmodem/shrike/p4probe.py +47 -0
  163. hfmodem/shrike/p4rx.py +165 -0
  164. hfmodem/shrike/p4sig.py +196 -0
  165. hfmodem/shrike/pactor1.py +801 -0
  166. hfmodem/shrike/pactor2.py +699 -0
  167. hfmodem/shrike/placement.py +1053 -0
  168. hfmodem/shrike/ptc.py +2776 -0
  169. hfmodem/shrike/rx.py +632 -0
  170. hfmodem/shrike/rxfront.py +3011 -0
  171. hfmodem/shrike/session.py +146 -0
  172. hfmodem/shrike/spec.py +519 -0
  173. hfmodem/shrike/tablegen.py +200 -0
  174. hfmodem/shrike/traffic.py +163 -0
  175. hfmodem/shrike/unknowns.py +774 -0
  176. hfmodem/station/__init__.py +2 -0
  177. hfmodem/station/air.py +196 -0
  178. hfmodem/station/arbiter.py +282 -0
  179. hfmodem/station/hosts.py +157 -0
  180. hfmodem/station/link.py +271 -0
  181. hfmodem/station/mail.py +681 -0
  182. hfmodem/station/narrate.py +506 -0
  183. hfmodem/station/preflight.py +603 -0
  184. hfmodem/station/process.py +308 -0
  185. hfmodem/tests/__init__.py +4 -0
  186. hfmodem/tests/besra/__init__.py +2 -0
  187. hfmodem/tests/besra/groundtruth.py +151 -0
  188. hfmodem/tests/besra/test_ardopcf_interop.py +91 -0
  189. hfmodem/tests/besra/test_arq_ladder_descent.py +158 -0
  190. hfmodem/tests/besra/test_arq_robustness.py +909 -0
  191. hfmodem/tests/besra/test_arq_session.py +1158 -0
  192. hfmodem/tests/besra/test_arq_unreadable_budget.py +384 -0
  193. hfmodem/tests/besra/test_callsign.py +126 -0
  194. hfmodem/tests/besra/test_cli.py +384 -0
  195. hfmodem/tests/besra/test_demod_robustness.py +814 -0
  196. hfmodem/tests/besra/test_demodulator.py +247 -0
  197. hfmodem/tests/besra/test_host_besra.py +114 -0
  198. hfmodem/tests/besra/test_host_loopback.py +275 -0
  199. hfmodem/tests/besra/test_integration.py +167 -0
  200. hfmodem/tests/besra/test_lint.py +40 -0
  201. hfmodem/tests/besra/test_mail.py +264 -0
  202. hfmodem/tests/besra/test_memory_arq.py +464 -0
  203. hfmodem/tests/besra/test_modulator.py +106 -0
  204. hfmodem/tests/besra/test_monitor.py +321 -0
  205. hfmodem/tests/besra/test_offair_conack.py +71 -0
  206. hfmodem/tests/besra/test_offair_ke8lva_greeting.py +396 -0
  207. hfmodem/tests/besra/test_primitives.py +41 -0
  208. hfmodem/tests/besra/test_publication.py +61 -0
  209. hfmodem/tests/besra/test_quality.py +793 -0
  210. hfmodem/tests/besra/test_radio.py +30 -0
  211. hfmodem/tests/besra/test_radio_audio_path.py +1076 -0
  212. hfmodem/tests/besra/test_radio_line_ptt.py +285 -0
  213. hfmodem/tests/besra/test_rf_corpus.py +138 -0
  214. hfmodem/tests/besra/test_rig.py +321 -0
  215. hfmodem/tests/besra/test_rs.py +73 -0
  216. hfmodem/tests/besra/test_rx_funnel.py +265 -0
  217. hfmodem/tests/besra/test_rx_local.py +144 -0
  218. hfmodem/tests/besra/test_tx_exact.py +130 -0
  219. hfmodem/tests/conftest.py +48 -0
  220. hfmodem/tests/core/__init__.py +2 -0
  221. hfmodem/tests/core/fakerig.py +279 -0
  222. hfmodem/tests/core/test_air.py +169 -0
  223. hfmodem/tests/core/test_arbiter.py +224 -0
  224. hfmodem/tests/core/test_audio.py +661 -0
  225. hfmodem/tests/core/test_band.py +29 -0
  226. hfmodem/tests/core/test_busy_evidence.py +254 -0
  227. hfmodem/tests/core/test_call_bandwidth.py +84 -0
  228. hfmodem/tests/core/test_capture_loss.py +321 -0
  229. hfmodem/tests/core/test_cbtrace.py +89 -0
  230. hfmodem/tests/core/test_client_sid.py +171 -0
  231. hfmodem/tests/core/test_config.py +228 -0
  232. hfmodem/tests/core/test_devices.py +90 -0
  233. hfmodem/tests/core/test_drive_sweep.py +492 -0
  234. hfmodem/tests/core/test_gil.py +116 -0
  235. hfmodem/tests/core/test_gwsurvey.py +293 -0
  236. hfmodem/tests/core/test_id_reminder.py +147 -0
  237. hfmodem/tests/core/test_levels.py +158 -0
  238. hfmodem/tests/core/test_link.py +376 -0
  239. hfmodem/tests/core/test_narrate.py +464 -0
  240. hfmodem/tests/core/test_occupant_identity.py +158 -0
  241. hfmodem/tests/core/test_occupied.py +286 -0
  242. hfmodem/tests/core/test_one_ladder.py +327 -0
  243. hfmodem/tests/core/test_preflight.py +524 -0
  244. hfmodem/tests/core/test_ptt.py +220 -0
  245. hfmodem/tests/core/test_ptt_last_resort.py +907 -0
  246. hfmodem/tests/core/test_rates.py +278 -0
  247. hfmodem/tests/core/test_regulatory.py +317 -0
  248. hfmodem/tests/core/test_rehear.py +462 -0
  249. hfmodem/tests/core/test_rehear_shrike_verdicts.py +324 -0
  250. hfmodem/tests/core/test_replay.py +97 -0
  251. hfmodem/tests/core/test_resample_wav_cwid.py +148 -0
  252. hfmodem/tests/core/test_rig.py +583 -0
  253. hfmodem/tests/core/test_rig_panic.py +167 -0
  254. hfmodem/tests/core/test_rigs.py +32 -0
  255. hfmodem/tests/core/test_signal_teardown.py +713 -0
  256. hfmodem/tests/core/test_station_capture_loss.py +165 -0
  257. hfmodem/tests/core/test_station_rehearsal.py +537 -0
  258. hfmodem/tests/core/test_tx_drive.py +277 -0
  259. hfmodem/tests/core/test_witness.py +457 -0
  260. hfmodem/tests/evidence.py +63 -0
  261. hfmodem/tests/gates/__init__.py +2 -0
  262. hfmodem/tests/gates/test_corpus_present.py +238 -0
  263. hfmodem/tests/gates/test_entry_points.py +118 -0
  264. hfmodem/tests/gates/test_figures.py +84 -0
  265. hfmodem/tests/gates/test_gates_script.py +194 -0
  266. hfmodem/tests/gates/test_import_direction.py +223 -0
  267. hfmodem/tests/gates/test_no_secret_in_tracked_files.py +151 -0
  268. hfmodem/tests/gates/test_one_gateway_list.py +198 -0
  269. hfmodem/tests/gates/test_packaging.py +212 -0
  270. hfmodem/tests/gates/test_singleton_facts.py +192 -0
  271. hfmodem/tests/gates/test_stale_negatives.py +1543 -0
  272. hfmodem/tests/gates/test_tablegen.py +328 -0
  273. hfmodem/tests/gates/test_unproven_failsafe.py +160 -0
  274. hfmodem/tests/kestrel/__init__.py +2 -0
  275. hfmodem/tests/kestrel/corpora.py +1298 -0
  276. hfmodem/tests/kestrel/fake_rigctld.py +239 -0
  277. hfmodem/tests/kestrel/test_ack_downshift.py +101 -0
  278. hfmodem/tests/kestrel/test_arq_bw2300.py +91 -0
  279. hfmodem/tests/kestrel/test_arq_loopback.py +105 -0
  280. hfmodem/tests/kestrel/test_arq_teardown.py +226 -0
  281. hfmodem/tests/kestrel/test_arq_vara_tokens.py +185 -0
  282. hfmodem/tests/kestrel/test_burst_naming.py +144 -0
  283. hfmodem/tests/kestrel/test_bw2300_cut_head.py +94 -0
  284. hfmodem/tests/kestrel/test_bw2300_preamble.py +104 -0
  285. hfmodem/tests/kestrel/test_bw2300_real_audio.py +57 -0
  286. hfmodem/tests/kestrel/test_bw2300_rec1_rec0.py +130 -0
  287. hfmodem/tests/kestrel/test_bw2300_rec2.py +557 -0
  288. hfmodem/tests/kestrel/test_bw2300_roundtrip.py +63 -0
  289. hfmodem/tests/kestrel/test_bw2750.py +450 -0
  290. hfmodem/tests/kestrel/test_bw2750_below_offer.py +89 -0
  291. hfmodem/tests/kestrel/test_bw2750_control_tails.py +76 -0
  292. hfmodem/tests/kestrel/test_bw2750_floor.py +91 -0
  293. hfmodem/tests/kestrel/test_bw2750_low_levels.py +101 -0
  294. hfmodem/tests/kestrel/test_bw2750_nak.py +324 -0
  295. hfmodem/tests/kestrel/test_bw2750_peer_answer.py +171 -0
  296. hfmodem/tests/kestrel/test_bw2750_responder_nak.py +316 -0
  297. hfmodem/tests/kestrel/test_bw2750_tx_levels.py +303 -0
  298. hfmodem/tests/kestrel/test_bw500_answer.py +207 -0
  299. hfmodem/tests/kestrel/test_bw500_connect_request.py +206 -0
  300. hfmodem/tests/kestrel/test_bw500_continue_table.py +231 -0
  301. hfmodem/tests/kestrel/test_bw500_cut_head.py +71 -0
  302. hfmodem/tests/kestrel/test_bw500_data_over.py +170 -0
  303. hfmodem/tests/kestrel/test_bw500_handshake.py +89 -0
  304. hfmodem/tests/kestrel/test_bw500_low_levels.py +163 -0
  305. hfmodem/tests/kestrel/test_bw500_peer_answer.py +164 -0
  306. hfmodem/tests/kestrel/test_bw500_rec2.py +281 -0
  307. hfmodem/tests/kestrel/test_bw500_recovery.py +285 -0
  308. hfmodem/tests/kestrel/test_bw500_session.py +226 -0
  309. hfmodem/tests/kestrel/test_bw500_session_frames.py +137 -0
  310. hfmodem/tests/kestrel/test_bw500_stream_low_levels.py +75 -0
  311. hfmodem/tests/kestrel/test_bw500_terminal_window.py +117 -0
  312. hfmodem/tests/kestrel/test_bw500_tx_native.py +93 -0
  313. hfmodem/tests/kestrel/test_caller_in_peer_turn.py +397 -0
  314. hfmodem/tests/kestrel/test_capture_clock.py +158 -0
  315. hfmodem/tests/kestrel/test_channel_busy.py +608 -0
  316. hfmodem/tests/kestrel/test_connect_ask_stream.py +125 -0
  317. hfmodem/tests/kestrel/test_connect_confirm_stream.py +289 -0
  318. hfmodem/tests/kestrel/test_connect_retry.py +443 -0
  319. hfmodem/tests/kestrel/test_connect_rx_priority.py +106 -0
  320. hfmodem/tests/kestrel/test_connect_teardown.py +164 -0
  321. hfmodem/tests/kestrel/test_connected_ack.py +92 -0
  322. hfmodem/tests/kestrel/test_connected_ack_channel.py +351 -0
  323. hfmodem/tests/kestrel/test_connected_ack_stream.py +626 -0
  324. hfmodem/tests/kestrel/test_connected_turn_requests.py +140 -0
  325. hfmodem/tests/kestrel/test_crc.py +73 -0
  326. hfmodem/tests/kestrel/test_data_ack_pairs.py +45 -0
  327. hfmodem/tests/kestrel/test_data_ack_refusal.py +26 -0
  328. hfmodem/tests/kestrel/test_data_nak_pairs.py +148 -0
  329. hfmodem/tests/kestrel/test_data_naks_0919.py +143 -0
  330. hfmodem/tests/kestrel/test_data_over_gate.py +742 -0
  331. hfmodem/tests/kestrel/test_data_replies_0919.py +139 -0
  332. hfmodem/tests/kestrel/test_data_retry_clock.py +89 -0
  333. hfmodem/tests/kestrel/test_drained_frame.py +65 -0
  334. hfmodem/tests/kestrel/test_drained_handover.py +233 -0
  335. hfmodem/tests/kestrel/test_drained_handover_2300.py +140 -0
  336. hfmodem/tests/kestrel/test_early_idle_release.py +61 -0
  337. hfmodem/tests/kestrel/test_final_ack_recovery.py +203 -0
  338. hfmodem/tests/kestrel/test_fractional_continue.py +120 -0
  339. hfmodem/tests/kestrel/test_greeting_by_stream.py +469 -0
  340. hfmodem/tests/kestrel/test_greeting_spans_overs.py +195 -0
  341. hfmodem/tests/kestrel/test_handshake_channel.py +95 -0
  342. hfmodem/tests/kestrel/test_handshake_offair.py +140 -0
  343. hfmodem/tests/kestrel/test_handshake_replay.py +103 -0
  344. hfmodem/tests/kestrel/test_head_cut_continue.py +166 -0
  345. hfmodem/tests/kestrel/test_hfcapture_segments.py +211 -0
  346. hfmodem/tests/kestrel/test_host_loopback.py +316 -0
  347. hfmodem/tests/kestrel/test_idle_recovery.py +144 -0
  348. hfmodem/tests/kestrel/test_intermediate_over_answer.py +413 -0
  349. hfmodem/tests/kestrel/test_intermediate_query_answer.py +235 -0
  350. hfmodem/tests/kestrel/test_intermediate_query_probe.py +150 -0
  351. hfmodem/tests/kestrel/test_k0si_data_ack_0920.py +93 -0
  352. hfmodem/tests/kestrel/test_kb5_partial_idle.py +69 -0
  353. hfmodem/tests/kestrel/test_kd7uhr_response_zero.py +46 -0
  354. hfmodem/tests/kestrel/test_key_refusal.py +404 -0
  355. hfmodem/tests/kestrel/test_link_frequency.py +243 -0
  356. hfmodem/tests/kestrel/test_linksetup_frame.py +167 -0
  357. hfmodem/tests/kestrel/test_loopback_honesty.py +133 -0
  358. hfmodem/tests/kestrel/test_lower_answer_recovery.py +252 -0
  359. hfmodem/tests/kestrel/test_mail.py +645 -0
  360. hfmodem/tests/kestrel/test_mfsk_timing.py +136 -0
  361. hfmodem/tests/kestrel/test_monitor_traffic.py +580 -0
  362. hfmodem/tests/kestrel/test_nak_repacketization.py +118 -0
  363. hfmodem/tests/kestrel/test_nak_scan_deadline.py +47 -0
  364. hfmodem/tests/kestrel/test_negative_corpus.py +138 -0
  365. hfmodem/tests/kestrel/test_negotiated_setup.py +89 -0
  366. hfmodem/tests/kestrel/test_no_data_retries.py +121 -0
  367. hfmodem/tests/kestrel/test_onair_connect_chain.py +479 -0
  368. hfmodem/tests/kestrel/test_onair_dress_rehearsal.py +1204 -0
  369. hfmodem/tests/kestrel/test_onair_frequency.py +112 -0
  370. hfmodem/tests/kestrel/test_onair_mail_path.py +379 -0
  371. hfmodem/tests/kestrel/test_over_buffer_phase.py +61 -0
  372. hfmodem/tests/kestrel/test_over_carry_bw2750.py +95 -0
  373. hfmodem/tests/kestrel/test_over_continue_answer.py +315 -0
  374. hfmodem/tests/kestrel/test_over_splice.py +479 -0
  375. hfmodem/tests/kestrel/test_over_stream_cut_head.py +99 -0
  376. hfmodem/tests/kestrel/test_owed_ask.py +140 -0
  377. hfmodem/tests/kestrel/test_peer_answer.py +229 -0
  378. hfmodem/tests/kestrel/test_peer_gap_idle_clock.py +117 -0
  379. hfmodem/tests/kestrel/test_preamble_lock.py +75 -0
  380. hfmodem/tests/kestrel/test_ptt_release.py +118 -0
  381. hfmodem/tests/kestrel/test_ptt_tail_check.py +558 -0
  382. hfmodem/tests/kestrel/test_reack_budget.py +99 -0
  383. hfmodem/tests/kestrel/test_reack_over.py +268 -0
  384. hfmodem/tests/kestrel/test_reack_release.py +127 -0
  385. hfmodem/tests/kestrel/test_ready_pounce.py +194 -0
  386. hfmodem/tests/kestrel/test_real_audio.py +124 -0
  387. hfmodem/tests/kestrel/test_recovery_diagnostics.py +59 -0
  388. hfmodem/tests/kestrel/test_release_reoffer.py +138 -0
  389. hfmodem/tests/kestrel/test_repeated_intermediate_queries.py +121 -0
  390. hfmodem/tests/kestrel/test_response_by_payload.py +311 -0
  391. hfmodem/tests/kestrel/test_response_by_stream.py +1018 -0
  392. hfmodem/tests/kestrel/test_rig_unkey_safety.py +348 -0
  393. hfmodem/tests/kestrel/test_rig_wire_evidence.py +201 -0
  394. hfmodem/tests/kestrel/test_rx_levels.py +331 -0
  395. hfmodem/tests/kestrel/test_rx_segmenter.py +1023 -0
  396. hfmodem/tests/kestrel/test_session_bursts.py +201 -0
  397. hfmodem/tests/kestrel/test_session_replay.py +205 -0
  398. hfmodem/tests/kestrel/test_static.py +194 -0
  399. hfmodem/tests/kestrel/test_station_id.py +100 -0
  400. hfmodem/tests/kestrel/test_tone_band.py +151 -0
  401. hfmodem/tests/kestrel/test_tools_cli.py +353 -0
  402. hfmodem/tests/kestrel/test_trace_timing.py +302 -0
  403. hfmodem/tests/kestrel/test_transmitted_bursts.py +167 -0
  404. hfmodem/tests/kestrel/test_turbo_exactness.py +89 -0
  405. hfmodem/tests/kestrel/test_turn_law.py +1455 -0
  406. hfmodem/tests/kestrel/test_turn_request_confirms_connect.py +115 -0
  407. hfmodem/tests/kestrel/test_two_over_window.py +436 -0
  408. hfmodem/tests/kestrel/test_tx_dc_tail.py +61 -0
  409. hfmodem/tests/kestrel/test_tx_selfloop.py +76 -0
  410. hfmodem/tests/kestrel/test_txwitness.py +768 -0
  411. hfmodem/tests/kestrel/test_unasked_handover.py +421 -0
  412. hfmodem/tests/kestrel/test_unattributed_answer.py +213 -0
  413. hfmodem/tests/kestrel/test_unknown_data_reply.py +125 -0
  414. hfmodem/tests/kestrel/test_vara_connect.py +58 -0
  415. hfmodem/tests/kestrel/test_vara_control.py +207 -0
  416. hfmodem/tests/kestrel/test_vara_data_framing.py +190 -0
  417. hfmodem/tests/kestrel/test_vara_giveup.py +237 -0
  418. hfmodem/tests/kestrel/test_vara_interop.py +233 -0
  419. hfmodem/tests/kestrel/test_vara_launcher_cleanup.py +49 -0
  420. hfmodem/tests/kestrel/test_vara_mail_rx.py +322 -0
  421. hfmodem/tests/kestrel/test_vara_nak.py +74 -0
  422. hfmodem/tests/kestrel/test_vara_tx_recovery.py +160 -0
  423. hfmodem/tests/kestrel/test_w0lon_idle_train.py +230 -0
  424. hfmodem/tests/kestrel/test_weak_data_0920.py +84 -0
  425. hfmodem/tests/kestrel/test_weak_known_continue.py +73 -0
  426. hfmodem/tests/kestrel/test_whole_burst.py +121 -0
  427. hfmodem/tests/sabir/__init__.py +2 -0
  428. hfmodem/tests/sabir/clips.py +41 -0
  429. hfmodem/tests/sabir/test_harq_buffer.py +140 -0
  430. hfmodem/tests/sabir/test_hostapi.py +577 -0
  431. hfmodem/tests/sabir/test_kat.py +153 -0
  432. hfmodem/tests/sabir/test_m1.py +125 -0
  433. hfmodem/tests/sabir/test_m2.py +214 -0
  434. hfmodem/tests/sabir/test_m2b.py +144 -0
  435. hfmodem/tests/sabir/test_m3.py +207 -0
  436. hfmodem/tests/sabir/test_m4.py +285 -0
  437. hfmodem/tests/sabir/test_m6a.py +193 -0
  438. hfmodem/tests/sabir/test_m6b.py +143 -0
  439. hfmodem/tests/sabir/test_m6c.py +91 -0
  440. hfmodem/tests/sabir/test_monitor.py +153 -0
  441. hfmodem/tests/sabir/test_negotiation.py +331 -0
  442. hfmodem/tests/sabir/test_offair.py +242 -0
  443. hfmodem/tests/sabir/test_onair.py +655 -0
  444. hfmodem/tests/sabir/test_preroll_bound.py +54 -0
  445. hfmodem/tests/sabir/test_proof.py +155 -0
  446. hfmodem/tests/sabir/test_release_profiles.py +251 -0
  447. hfmodem/tests/sabir/test_release_safety.py +312 -0
  448. hfmodem/tests/sabir/test_static.py +127 -0
  449. hfmodem/tests/sabir/test_waveform.py +88 -0
  450. hfmodem/tests/shrike/__init__.py +2 -0
  451. hfmodem/tests/shrike/archive.py +116 -0
  452. hfmodem/tests/shrike/ptyrig.py +192 -0
  453. hfmodem/tests/shrike/recorded_pcm.py +19 -0
  454. hfmodem/tests/shrike/test_ackfloor.py +261 -0
  455. hfmodem/tests/shrike/test_ackoff.py +148 -0
  456. hfmodem/tests/shrike/test_ackplace.py +458 -0
  457. hfmodem/tests/shrike/test_alc_knee.py +252 -0
  458. hfmodem/tests/shrike/test_announcement.py +27 -0
  459. hfmodem/tests/shrike/test_answer_band.py +499 -0
  460. hfmodem/tests/shrike/test_answer_band_validity.py +62 -0
  461. hfmodem/tests/shrike/test_answer_instruments_0915.py +209 -0
  462. hfmodem/tests/shrike/test_armdefaults.py +352 -0
  463. hfmodem/tests/shrike/test_arq.py +135 -0
  464. hfmodem/tests/shrike/test_arq_refusal_budget.py +160 -0
  465. hfmodem/tests/shrike/test_assessed_radio_startup.py +89 -0
  466. hfmodem/tests/shrike/test_b2f_loopback_send_ws8eoc.py +417 -0
  467. hfmodem/tests/shrike/test_b2f_loopback_ws8eoc.py +877 -0
  468. hfmodem/tests/shrike/test_beacon.py +233 -0
  469. hfmodem/tests/shrike/test_breakin.py +1362 -0
  470. hfmodem/tests/shrike/test_breakin_prompt_placement.py +211 -0
  471. hfmodem/tests/shrike/test_burstlock.py +1130 -0
  472. hfmodem/tests/shrike/test_bytestream.py +198 -0
  473. hfmodem/tests/shrike/test_callb.py +226 -0
  474. hfmodem/tests/shrike/test_capture_drain.py +178 -0
  475. hfmodem/tests/shrike/test_changeover_clock.py +89 -0
  476. hfmodem/tests/shrike/test_changeover_cs5.py +72 -0
  477. hfmodem/tests/shrike/test_changeover_p1_cs.py +146 -0
  478. hfmodem/tests/shrike/test_changeover_progression.py +145 -0
  479. hfmodem/tests/shrike/test_changeover_request.py +236 -0
  480. hfmodem/tests/shrike/test_clockfit.py +197 -0
  481. hfmodem/tests/shrike/test_codeword_reach.py +297 -0
  482. hfmodem/tests/shrike/test_coding.py +193 -0
  483. hfmodem/tests/shrike/test_compress.py +346 -0
  484. hfmodem/tests/shrike/test_connect_evidence_0916.py +355 -0
  485. hfmodem/tests/shrike/test_connect_gate.py +747 -0
  486. hfmodem/tests/shrike/test_connect_prefix.py +215 -0
  487. hfmodem/tests/shrike/test_connect_tail.py +149 -0
  488. hfmodem/tests/shrike/test_connected_hush.py +117 -0
  489. hfmodem/tests/shrike/test_control_offset_follow.py +361 -0
  490. hfmodem/tests/shrike/test_control_order.py +64 -0
  491. hfmodem/tests/shrike/test_control_profiles.py +261 -0
  492. hfmodem/tests/shrike/test_corpus.py +169 -0
  493. hfmodem/tests/shrike/test_cs6_arq.py +404 -0
  494. hfmodem/tests/shrike/test_cs6_driver.py +361 -0
  495. hfmodem/tests/shrike/test_cs6_rx.py +205 -0
  496. hfmodem/tests/shrike/test_cs6_stalls.py +287 -0
  497. hfmodem/tests/shrike/test_cs6_waveform.py +108 -0
  498. hfmodem/tests/shrike/test_cs_evidence.py +196 -0
  499. hfmodem/tests/shrike/test_csalias.py +397 -0
  500. hfmodem/tests/shrike/test_cssearch.py +205 -0
  501. hfmodem/tests/shrike/test_data_pulse_flush.py +309 -0
  502. hfmodem/tests/shrike/test_duplex.py +567 -0
  503. hfmodem/tests/shrike/test_emptybk.py +197 -0
  504. hfmodem/tests/shrike/test_entry_answer.py +490 -0
  505. hfmodem/tests/shrike/test_entry_candidate_body.py +208 -0
  506. hfmodem/tests/shrike/test_entry_counter_law.py +294 -0
  507. hfmodem/tests/shrike/test_entry_counter_slots.py +232 -0
  508. hfmodem/tests/shrike/test_entry_delay.py +118 -0
  509. hfmodem/tests/shrike/test_entry_slots_kept.py +480 -0
  510. hfmodem/tests/shrike/test_first_loaded_packet.py +114 -0
  511. hfmodem/tests/shrike/test_fixed_clock_peer.py +91 -0
  512. hfmodem/tests/shrike/test_flushdebt.py +114 -0
  513. hfmodem/tests/shrike/test_follow_p1_cross_check.py +148 -0
  514. hfmodem/tests/shrike/test_free_signal_event.py +110 -0
  515. hfmodem/tests/shrike/test_gear_gate_speed_up.py +194 -0
  516. hfmodem/tests/shrike/test_grant_credit_bound.py +107 -0
  517. hfmodem/tests/shrike/test_grant_reconnect.py +24 -0
  518. hfmodem/tests/shrike/test_granted_entry_retry.py +238 -0
  519. hfmodem/tests/shrike/test_granted_entry_slots.py +374 -0
  520. hfmodem/tests/shrike/test_granted_progress.py +270 -0
  521. hfmodem/tests/shrike/test_grantslot.py +372 -0
  522. hfmodem/tests/shrike/test_grid.py +2865 -0
  523. hfmodem/tests/shrike/test_grid_phase.py +145 -0
  524. hfmodem/tests/shrike/test_holdbudget.py +451 -0
  525. hfmodem/tests/shrike/test_honest_account.py +393 -0
  526. hfmodem/tests/shrike/test_honest_lines.py +214 -0
  527. hfmodem/tests/shrike/test_hostmode.py +15 -0
  528. hfmodem/tests/shrike/test_hostmode_manual_0916.py +453 -0
  529. hfmodem/tests/shrike/test_identify.py +153 -0
  530. hfmodem/tests/shrike/test_inlink_anchored_read_0916.py +291 -0
  531. hfmodem/tests/shrike/test_inlink_grid_read_0917.py +207 -0
  532. hfmodem/tests/shrike/test_iss_answer_placement.py +187 -0
  533. hfmodem/tests/shrike/test_iss_login_advance.py +212 -0
  534. hfmodem/tests/shrike/test_iss_turn_changeover.py +549 -0
  535. hfmodem/tests/shrike/test_k0nts_sl3_0915.py +161 -0
  536. hfmodem/tests/shrike/test_kb5lzk_answer_radius_0915.py +148 -0
  537. hfmodem/tests/shrike/test_keyed_truth.py +620 -0
  538. hfmodem/tests/shrike/test_keyingguard.py +171 -0
  539. hfmodem/tests/shrike/test_late_changeover.py +118 -0
  540. hfmodem/tests/shrike/test_late_entry_loop.py +248 -0
  541. hfmodem/tests/shrike/test_late_recorded_entry.py +74 -0
  542. hfmodem/tests/shrike/test_listen_cycle.py +184 -0
  543. hfmodem/tests/shrike/test_long_sl1.py +192 -0
  544. hfmodem/tests/shrike/test_longcycle.py +505 -0
  545. hfmodem/tests/shrike/test_longcycle_cadence.py +205 -0
  546. hfmodem/tests/shrike/test_mail.py +317 -0
  547. hfmodem/tests/shrike/test_mail_wait_greeting.py +155 -0
  548. hfmodem/tests/shrike/test_modem.py +319 -0
  549. hfmodem/tests/shrike/test_no_p3_fallback.py +280 -0
  550. hfmodem/tests/shrike/test_offair.py +78 -0
  551. hfmodem/tests/shrike/test_offset_fine_estimate.py +237 -0
  552. hfmodem/tests/shrike/test_over_grant_hold.py +198 -0
  553. hfmodem/tests/shrike/test_p1.py +142 -0
  554. hfmodem/tests/shrike/test_p1_6a9.py +77 -0
  555. hfmodem/tests/shrike/test_p1_late_render.py +162 -0
  556. hfmodem/tests/shrike/test_p1_oracle.py +679 -0
  557. hfmodem/tests/shrike/test_p1_setup_phase.py +164 -0
  558. hfmodem/tests/shrike/test_p1_speed_down.py +195 -0
  559. hfmodem/tests/shrike/test_p1_speedup_seam.py +264 -0
  560. hfmodem/tests/shrike/test_p1_stream_epoch_floor.py +88 -0
  561. hfmodem/tests/shrike/test_p1_stream_protocol_scope.py +122 -0
  562. hfmodem/tests/shrike/test_p1ack.py +237 -0
  563. hfmodem/tests/shrike/test_p1counter.py +282 -0
  564. hfmodem/tests/shrike/test_p1cs.py +1118 -0
  565. hfmodem/tests/shrike/test_p1data.py +343 -0
  566. hfmodem/tests/shrike/test_p1drive.py +66 -0
  567. hfmodem/tests/shrike/test_p1memory.py +160 -0
  568. hfmodem/tests/shrike/test_p1memory_session.py +188 -0
  569. hfmodem/tests/shrike/test_p1peer.py +624 -0
  570. hfmodem/tests/shrike/test_p1rx.py +281 -0
  571. hfmodem/tests/shrike/test_p1rx_heads.py +315 -0
  572. hfmodem/tests/shrike/test_p1status.py +184 -0
  573. hfmodem/tests/shrike/test_p1transparent.py +164 -0
  574. hfmodem/tests/shrike/test_p2.py +785 -0
  575. hfmodem/tests/shrike/test_p2_entry_deadline.py +40 -0
  576. hfmodem/tests/shrike/test_p2_oracle.py +602 -0
  577. hfmodem/tests/shrike/test_p2_render_equivalence.py +38 -0
  578. hfmodem/tests/shrike/test_p2entry.py +154 -0
  579. hfmodem/tests/shrike/test_p2link.py +133 -0
  580. hfmodem/tests/shrike/test_p2memory.py +105 -0
  581. hfmodem/tests/shrike/test_p2qso.py +475 -0
  582. hfmodem/tests/shrike/test_p2rx.py +345 -0
  583. hfmodem/tests/shrike/test_p2session.py +491 -0
  584. hfmodem/tests/shrike/test_p3_acquisition_budget.py +147 -0
  585. hfmodem/tests/shrike/test_p3_answer_acquire_budget.py +166 -0
  586. hfmodem/tests/shrike/test_p3_breakin_carriers.py +214 -0
  587. hfmodem/tests/shrike/test_p3_breakin_timing.py +813 -0
  588. hfmodem/tests/shrike/test_p3_budget_goodbye_0914.py +81 -0
  589. hfmodem/tests/shrike/test_p3_callback_budget.py +117 -0
  590. hfmodem/tests/shrike/test_p3_carrier_frequency.py +94 -0
  591. hfmodem/tests/shrike/test_p3_changeover_order.py +62 -0
  592. hfmodem/tests/shrike/test_p3_changeover_refined_body.py +96 -0
  593. hfmodem/tests/shrike/test_p3_cold_acquisition.py +66 -0
  594. hfmodem/tests/shrike/test_p3_cold_entry_2323.py +124 -0
  595. hfmodem/tests/shrike/test_p3_comb.py +185 -0
  596. hfmodem/tests/shrike/test_p3_comb_offset.py +196 -0
  597. hfmodem/tests/shrike/test_p3_control_edge.py +146 -0
  598. hfmodem/tests/shrike/test_p3_critic_risks.py +360 -0
  599. hfmodem/tests/shrike/test_p3_current_packet_loop.py +188 -0
  600. hfmodem/tests/shrike/test_p3_current_sl1_0919.py +119 -0
  601. hfmodem/tests/shrike/test_p3_emitted_turn_retry.py +151 -0
  602. hfmodem/tests/shrike/test_p3_entry_controls.py +231 -0
  603. hfmodem/tests/shrike/test_p3_entry_iss_phase.py +162 -0
  604. hfmodem/tests/shrike/test_p3_entry_timing_trial.py +113 -0
  605. hfmodem/tests/shrike/test_p3_failed_turn_clock.py +333 -0
  606. hfmodem/tests/shrike/test_p3_first_changeover_ack.py +208 -0
  607. hfmodem/tests/shrike/test_p3_flow_phase.py +185 -0
  608. hfmodem/tests/shrike/test_p3_gateway_acquisition.py +126 -0
  609. hfmodem/tests/shrike/test_p3_late_turn_answer_2300.py +85 -0
  610. hfmodem/tests/shrike/test_p3_late_turn_regrid_integration.py +112 -0
  611. hfmodem/tests/shrike/test_p3_mail_changeover_phase.py +214 -0
  612. hfmodem/tests/shrike/test_p3_mail_clock.py +122 -0
  613. hfmodem/tests/shrike/test_p3_mail_turn_0919.py +189 -0
  614. hfmodem/tests/shrike/test_p3_max_down.py +149 -0
  615. hfmodem/tests/shrike/test_p3_morning_loop.py +177 -0
  616. hfmodem/tests/shrike/test_p3_morning_timing.py +175 -0
  617. hfmodem/tests/shrike/test_p3_offer.py +258 -0
  618. hfmodem/tests/shrike/test_p3_oracle.py +592 -0
  619. hfmodem/tests/shrike/test_p3_pending_turn_clock_0914.py +173 -0
  620. hfmodem/tests/shrike/test_p3_pending_turn_regrid.py +88 -0
  621. hfmodem/tests/shrike/test_p3_protocol_geometry.py +139 -0
  622. hfmodem/tests/shrike/test_p3_reply_delay.py +83 -0
  623. hfmodem/tests/shrike/test_p3_reply_placement.py +181 -0
  624. hfmodem/tests/shrike/test_p3_reply_timing.py +294 -0
  625. hfmodem/tests/shrike/test_p3_retry_downshift.py +194 -0
  626. hfmodem/tests/shrike/test_p3_rx_recovery.py +323 -0
  627. hfmodem/tests/shrike/test_p3_stale_answer.py +134 -0
  628. hfmodem/tests/shrike/test_p3_standing_request_2325.py +64 -0
  629. hfmodem/tests/shrike/test_p3_terminal_confirm_arq.py +203 -0
  630. hfmodem/tests/shrike/test_p3_terminal_marker.py +85 -0
  631. hfmodem/tests/shrike/test_p3_terminal_radio.py +69 -0
  632. hfmodem/tests/shrike/test_p3_timing_trial.py +225 -0
  633. hfmodem/tests/shrike/test_p3_timing_unbounded.py +102 -0
  634. hfmodem/tests/shrike/test_p3_tracked_crop_0914.py +38 -0
  635. hfmodem/tests/shrike/test_p3_turn_budget_0914.py +339 -0
  636. hfmodem/tests/shrike/test_p3_turn_recovery.py +293 -0
  637. hfmodem/tests/shrike/test_p3_upgrade.py +1684 -0
  638. hfmodem/tests/shrike/test_p3counter.py +181 -0
  639. hfmodem/tests/shrike/test_p3level_session.py +256 -0
  640. hfmodem/tests/shrike/test_p3memory.py +190 -0
  641. hfmodem/tests/shrike/test_p3rx.py +728 -0
  642. hfmodem/tests/shrike/test_p4chirp.py +248 -0
  643. hfmodem/tests/shrike/test_p4coding.py +123 -0
  644. hfmodem/tests/shrike/test_p4decode.py +108 -0
  645. hfmodem/tests/shrike/test_p4normal.py +119 -0
  646. hfmodem/tests/shrike/test_p4probe.py +112 -0
  647. hfmodem/tests/shrike/test_p4rx.py +130 -0
  648. hfmodem/tests/shrike/test_p4sig.py +163 -0
  649. hfmodem/tests/shrike/test_phase_power_cat.py +447 -0
  650. hfmodem/tests/shrike/test_placement.py +178 -0
  651. hfmodem/tests/shrike/test_prekey_read_0913.py +296 -0
  652. hfmodem/tests/shrike/test_ptc.py +1153 -0
  653. hfmodem/tests/shrike/test_ptc_transport.py +85 -0
  654. hfmodem/tests/shrike/test_qrmguard.py +342 -0
  655. hfmodem/tests/shrike/test_qrtack.py +470 -0
  656. hfmodem/tests/shrike/test_qso.py +490 -0
  657. hfmodem/tests/shrike/test_radio_mode_readback.py +121 -0
  658. hfmodem/tests/shrike/test_radio_startup_flow.py +130 -0
  659. hfmodem/tests/shrike/test_raster.py +219 -0
  660. hfmodem/tests/shrike/test_reclaim.py +121 -0
  661. hfmodem/tests/shrike/test_reclaim_geometry.py +140 -0
  662. hfmodem/tests/shrike/test_refused_breakin_ack.py +247 -0
  663. hfmodem/tests/shrike/test_repeat_gear_stall.py +253 -0
  664. hfmodem/tests/shrike/test_replay_keying.py +42 -0
  665. hfmodem/tests/shrike/test_replay_recut.py +286 -0
  666. hfmodem/tests/shrike/test_rise.py +104 -0
  667. hfmodem/tests/shrike/test_rx.py +267 -0
  668. hfmodem/tests/shrike/test_rx_anchor_pin.py +106 -0
  669. hfmodem/tests/shrike/test_rx_control_phase.py +107 -0
  670. hfmodem/tests/shrike/test_rxcost.py +280 -0
  671. hfmodem/tests/shrike/test_second_session_readback.py +32 -0
  672. hfmodem/tests/shrike/test_session.py +237 -0
  673. hfmodem/tests/shrike/test_session_clock.py +177 -0
  674. hfmodem/tests/shrike/test_shared_memory_field.py +166 -0
  675. hfmodem/tests/shrike/test_shortfall.py +426 -0
  676. hfmodem/tests/shrike/test_signoff.py +247 -0
  677. hfmodem/tests/shrike/test_silence.py +665 -0
  678. hfmodem/tests/shrike/test_sl2_gear_prekey.py +162 -0
  679. hfmodem/tests/shrike/test_sl2_rotation_field.py +230 -0
  680. hfmodem/tests/shrike/test_sl2_rx_tracked.py +239 -0
  681. hfmodem/tests/shrike/test_slot_deadline.py +763 -0
  682. hfmodem/tests/shrike/test_spec.py +183 -0
  683. hfmodem/tests/shrike/test_static.py +264 -0
  684. hfmodem/tests/shrike/test_stinttail.py +183 -0
  685. hfmodem/tests/shrike/test_stream_recording.py +376 -0
  686. hfmodem/tests/shrike/test_teardown_grant.py +116 -0
  687. hfmodem/tests/shrike/test_terminal_ack.py +158 -0
  688. hfmodem/tests/shrike/test_timing_recovery.py +100 -0
  689. hfmodem/tests/shrike/test_traffic_logging.py +142 -0
  690. hfmodem/tests/shrike/test_traffic_sl_hold.py +119 -0
  691. hfmodem/tests/shrike/test_tx_gear_hold.py +135 -0
  692. hfmodem/tests/shrike/test_tx_latency_correction.py +156 -0
  693. hfmodem/tests/shrike/test_txhead.py +637 -0
  694. hfmodem/tests/shrike/test_unexpected_long_window_0914.py +232 -0
  695. hfmodem/tests/shrike/test_uninvited_entry.py +180 -0
  696. hfmodem/tests/shrike/test_unkey_on_signal.py +98 -0
  697. hfmodem/tests/shrike/test_v27_arq_emission_opportunity.py +267 -0
  698. hfmodem/tests/shrike/test_v28_prekey_recovery.py +153 -0
  699. hfmodem/tests/shrike/test_v29_native_short_fallback.py +61 -0
  700. hfmodem/tests/shrike/test_v29_p3_control_collect.py +85 -0
  701. hfmodem/tests/shrike/test_v29_short_fallback_qualification.py +91 -0
  702. hfmodem/tests/shrike/test_ve3kpg_cs6_0920.py +59 -0
  703. hfmodem/tests/shrike/test_wideband_header_0914.py +115 -0
  704. hfmodem/tests/shrike/test_wideband_prekey_0914.py +262 -0
  705. hfmodem/tests/shrike/test_ws8eoc_long_sl1_0920.py +92 -0
  706. hfmodem/tests/test_pending_terminal_grant.py +184 -0
  707. hfmodem/tests/test_v31_changeover_frequency_range.py +23 -0
  708. hfmodem/tests/winlink/__init__.py +2 -0
  709. hfmodem/tests/winlink/audio_rehearsal.py +129 -0
  710. hfmodem/tests/winlink/corpora.py +52 -0
  711. hfmodem/tests/winlink/test_audio_rehearsal.py +110 -0
  712. hfmodem/tests/winlink/test_client.py +406 -0
  713. hfmodem/tests/winlink/test_lzhuf.py +114 -0
  714. hfmodem/tests/winlink/test_mail_command.py +142 -0
  715. hfmodem/tests/winlink/test_message.py +176 -0
  716. hfmodem/tests/winlink/test_session.py +1175 -0
  717. hfmodem/tests/winlink/test_ww2mi_login.py +248 -0
  718. hfmodem/winlink/__init__.py +28 -0
  719. hfmodem/winlink/client.py +311 -0
  720. hfmodem/winlink/lzhuf.py +444 -0
  721. hfmodem/winlink/message.py +186 -0
  722. hfmodem/winlink/session.py +871 -0
  723. hfmodem-0.1.0.dist-info/METADATA +36 -0
  724. hfmodem-0.1.0.dist-info/RECORD +729 -0
  725. hfmodem-0.1.0.dist-info/WHEEL +5 -0
  726. hfmodem-0.1.0.dist-info/entry_points.txt +2 -0
  727. hfmodem-0.1.0.dist-info/licenses/LICENSE +661 -0
  728. hfmodem-0.1.0.dist-info/licenses/NOTICE +126 -0
  729. hfmodem-0.1.0.dist-info/top_level.txt +1 -0
hfmodem/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
3
+
4
+ """hfmodem — four HF softmodems and the station that runs them.
5
+
6
+ Each protocol is a subpackage: `shrike` (PACTOR-1/2/3), `kestrel` (VARA),
7
+ `besra` (ARDOP) and `sabir`. They share `core` for everything that touches the
8
+ radio rather than the waveform, and `host` for the dialects an application
9
+ speaks to a modem.
10
+ """
11
+
12
+ __version__ = "0.1.0"
hfmodem/__main__.py ADDED
@@ -0,0 +1,12 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
3
+
4
+ """`python -m hfmodem`, for when the console script is not on PATH.
5
+
6
+ At the radio the venv is usually addressed by path rather than activated, and
7
+ `.venv/bin/hfmodem` and `.venv/bin/python -m hfmodem` should not disagree about
8
+ whether the station will start.
9
+ """
10
+ from hfmodem.cli import main
11
+
12
+ raise SystemExit(main())
@@ -0,0 +1,12 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
3
+
4
+ """besra — an open, ARDOP-compatible HF modem.
5
+
6
+ The ARDOP-compatible member of the hfmodem flock (`compat/ardop`). ARDOP is an
7
+ open, published amateur mode; besra implements it from that published
8
+ specification, and from ardopcf where the wire demands exactness (`NOTICE` names
9
+ the parts). See `docs/protocols/ardop/` for the functional record.
10
+ """
11
+
12
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
3
+
4
+ """The ARDOP ARQ link layer: the NEWSTATE machine and the BesraModem that
5
+ replaces LoopbackModem behind the host server's ModemCore seam."""
@@ -0,0 +1,342 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # SPDX-FileCopyrightText: 2026 Saul St John (W9SSJ)
3
+
4
+ """`BesraModem` — the real ARDOP modem: PHY + ARQ behind the host `ModemCore` seam.
5
+
6
+ This binds the three validated layers into one modem:
7
+
8
+ * the ARQ session (`besra.arq.session`) drives the NEWSTATE protocol,
9
+ * the modulator (`besra.phy.modulator`) renders each frame the session sends
10
+ to 12 kHz audio, and
11
+ * the demodulator (`besra.phy.demodulator`) turns received audio back into the
12
+ decoded frames the session consumes.
13
+
14
+ The modem renders into and decodes out of an injected audio link (`audio_out`
15
+ for transmit, `receive_audio` for receive). Time and I/O are driven one of two
16
+ ways over the same core:
17
+
18
+ * **passive** — a synchronous pump (`besra.sim.air.StepAir`) calls
19
+ `receive_audio`/`tick` directly, for deterministic radioless tests; or
20
+ * **threaded** — `start(observer, threaded=True)` runs a background pump that
21
+ drains delivered audio and advances timers off the wall clock, so the host
22
+ server can drive it live. A single lock serialises the ARQ session across the
23
+ host thread and the pump thread.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import logging
29
+ import queue
30
+ import threading
31
+ import time
32
+
33
+ import numpy as np
34
+
35
+ from ..frame import callsign, frame as F
36
+ from ..host import protocol as P
37
+ from ..host.modem_core import ModemCore, ModemObserver
38
+ from ..phy import modulator
39
+ from ..phy.demodulator import SAMPLE_RATE, DecodedFrame, Demodulator
40
+ from .session import ArqSession, peer_quality
41
+
42
+ log = logging.getLogger(__name__)
43
+
44
+
45
+ class _ObserverAdapter:
46
+ """Maps the ARQ session's `Observer` calls onto the host `ModemObserver`.
47
+
48
+ The two vocabularies differ only in spelling (`newstate` vs `modem_newstate`);
49
+ this is the thin seam that keeps the session ignorant of the host dialect."""
50
+
51
+ def __init__(self, obs: ModemObserver) -> None:
52
+ self._obs = obs
53
+
54
+ def newstate(self, state: str) -> None:
55
+ self._obs.modem_newstate(state)
56
+
57
+ def connected(self, remote: str, bw: int) -> None:
58
+ self._obs.modem_connected(remote, bw)
59
+
60
+ def disconnected(self) -> None:
61
+ self._obs.modem_disconnected()
62
+
63
+ def data_received(self, kind: str, blob: bytes) -> None:
64
+ self._obs.modem_data_received(kind, blob)
65
+
66
+ def buffer(self, nbytes: int) -> None:
67
+ self._obs.modem_buffer(nbytes)
68
+
69
+ def pending(self, cancel: bool = False) -> None:
70
+ self._obs.modem_pending(cancel)
71
+
72
+ def target(self, call: str) -> None:
73
+ self._obs.modem_target(call)
74
+
75
+ def status(self, text: str) -> None:
76
+ self._obs.modem_status(text)
77
+
78
+
79
+ class BesraModem(ModemCore):
80
+ """A radio-capable ARDOP modem. Transmits by rendering frames into its audio
81
+ link; receives by decoding audio the link delivers via `receive_audio`."""
82
+
83
+ #: silence padded around a delivered burst so the leader detector has room to
84
+ #: acquire and the decoder to flush (the demod's own convention).
85
+ _PAD_LEAD = 2400
86
+ _PAD_TAIL = 4800
87
+
88
+ def __init__(self, *, bandwidth: int = 500, timeout_s: int = 90,
89
+ leader_ms: int = modulator.DEFAULT_LEADER_MS,
90
+ threaded: bool = False) -> None:
91
+ self._start_threaded = threaded # host server drives live; sim drives passively
92
+ self._mycall = ""
93
+ self._gridsquare = ""
94
+ self._bandwidth = bandwidth
95
+ self._bw_forced = False
96
+ self._listen = False
97
+ self._protocolmode = "ARQ"
98
+ self._timeout_s = timeout_s
99
+ self._leader_ms = leader_ms
100
+
101
+ self._observer: ModemObserver | None = None
102
+ self._session: ArqSession | None = None
103
+ self._demod = Demodulator(expect_session=self.expected_session,
104
+ rx_epoch=self.rx_epoch)
105
+ self._clock = 0.0
106
+ self.audio_out = None # set by the air / device: called with int16 samples
107
+
108
+ self._lock = threading.RLock()
109
+ self._rx_q: "queue.Queue[np.ndarray]" = queue.Queue()
110
+ self._pump: threading.Thread | None = None
111
+ self._running = False
112
+
113
+ # -- ModemCore lifecycle ----------------------------------------------
114
+
115
+ def start(self, observer: ModemObserver, *, threaded: bool | None = None) -> None:
116
+ self._observer = observer
117
+ self._session = ArqSession(
118
+ self._mycall, transport=self, observer=_ObserverAdapter(observer),
119
+ bandwidth=self._bandwidth, timeout_s=self._timeout_s,
120
+ listen=self._listen)
121
+ self._session.tick(self._clock) # publishes the initial NEWSTATE DISC
122
+ if self._start_threaded if threaded is None else threaded:
123
+ self._running = True
124
+ self._pump = threading.Thread(target=self._run, name="besra-pump",
125
+ daemon=True)
126
+ self._pump.start()
127
+
128
+ def stop(self) -> None:
129
+ self._running = False
130
+ with self._lock:
131
+ self._session = None
132
+
133
+ def status(self, text: str) -> None:
134
+ """Say something to the host on the STATUS line, from outside the session —
135
+ the radio backend's receive funnel is the one thing here the ARQ layer
136
+ cannot see. Silent before `start`, when there is no host to say it to."""
137
+ if self._observer is not None:
138
+ self._observer.modem_status(text)
139
+
140
+ @property
141
+ def state(self) -> str:
142
+ return self._session.state if self._session else P.ArdopState.OFFLINE
143
+
144
+ @property
145
+ def queued(self) -> int:
146
+ with self._lock:
147
+ return self._session.queued if self._session else 0
148
+
149
+ def expected_session(self) -> int | None:
150
+ """The ARQ session id the demodulator should treat as corroborating a bare
151
+ control frame right now (`ArqSession.expected_session`), or None. A method
152
+ rather than a property so it can be handed to a `Demodulator` as its
153
+ `expect_session` callable — the radio backend's decoder polls it live."""
154
+ s = self._session
155
+ return s.expected_session if s is not None else None
156
+
157
+ def rx_epoch(self) -> int:
158
+ """`ArqSession.rx_epoch` for the demodulator's memory ARQ, polled the same
159
+ way and for the same reason as `expected_session`."""
160
+ s = self._session
161
+ return s.rx_epoch if s is not None else 0
162
+
163
+ # -- config (the host server pushes these before start) ----------------
164
+
165
+ def set_mycall(self, call: str) -> None:
166
+ # The host sets MYCALL after start(), so push it into the live session.
167
+ self._mycall = call
168
+ with self._lock:
169
+ if self._session is not None:
170
+ self._session.mycall = call
171
+
172
+ def set_listen(self, on: bool) -> None:
173
+ self._listen = on
174
+ with self._lock:
175
+ if self._session is not None:
176
+ self._session.listen = on
177
+
178
+ @property
179
+ def bandwidth(self) -> int:
180
+ """The session bandwidth in force, which is also the passband a burst
181
+ occupies — `radio.RadioLink` states the emission from it, and a host
182
+ `ARQBW` moves both at once."""
183
+ return self._bandwidth
184
+
185
+ def set_bandwidth(self, hz: int, forced: bool) -> None:
186
+ # ARQBW arrives after start() too, so push it into the live session —
187
+ # otherwise the next ConReq goes out at whatever the modem was built
188
+ # with, and the host's setting is a stored number that governs nothing.
189
+ self._bandwidth = hz
190
+ self._bw_forced = forced
191
+ with self._lock:
192
+ if self._session is not None:
193
+ self._session.bandwidth = hz
194
+
195
+ # -- ModemCore session verbs ------------------------------------------
196
+
197
+ def connect(self, target: str, repeats: int = 5) -> None:
198
+ with self._lock:
199
+ if self._session is None:
200
+ return
201
+ self._session.connect(target, repeats)
202
+
203
+ def transmit(self, blob: bytes) -> None:
204
+ with self._lock:
205
+ if self._session is None:
206
+ return
207
+ self._session.queue_data(blob)
208
+
209
+ def purge_buffer(self) -> None:
210
+ with self._lock:
211
+ if self._session is None:
212
+ return
213
+ self._session.purge()
214
+
215
+ def disconnect(self) -> None:
216
+ with self._lock:
217
+ if self._session is None:
218
+ return
219
+ self._session.disconnect()
220
+
221
+ def abort(self) -> None:
222
+ with self._lock:
223
+ if self._session is None:
224
+ return
225
+ self._session.abort()
226
+
227
+ # -- the ARQ session's Transport: render a frame into the audio link ---
228
+
229
+ def send(self, frame_type: int, payload: bytes, session_id: int) -> float:
230
+ # ConReq/Ping/ID (unconnected frames) go out with Session ID 0xFF; both
231
+ # ends re-derive the real session from the callsigns (spec §2.1).
232
+ wire_session = 0xFF if F.FRAMES[frame_type].forces_session else session_id
233
+ samples = modulator.render_frame(frame_type, payload=payload,
234
+ session_id=wire_session,
235
+ leader_ms=self._leader_ms)
236
+ log.info("TX %s %.2fs", F.FRAMES[frame_type].name, len(samples) / SAMPLE_RATE)
237
+ obs = self._observer
238
+ if obs is not None:
239
+ obs.modem_ptt(True)
240
+ if self.audio_out is not None:
241
+ self.audio_out(samples)
242
+ if obs is not None:
243
+ obs.modem_ptt(False)
244
+ # The transmit-frame duration, so the session can schedule its repeat from
245
+ # when the frame *ends* (the reference anchors dttNextPlay after playback),
246
+ # not from when it starts — otherwise a 1.75 s ConReq eats the listen window.
247
+ return len(samples) / SAMPLE_RATE
248
+
249
+ # -- receive / timing --------------------------------------------------
250
+
251
+ def deliver_rx(self, samples: np.ndarray) -> None:
252
+ """Hand received audio to the modem. In threaded mode it is queued for
253
+ the pump; passively it decodes inline."""
254
+ if self._running:
255
+ self._rx_q.put(samples)
256
+ else:
257
+ self.receive_audio(samples)
258
+
259
+ def receive_audio(self, samples: np.ndarray) -> None:
260
+ padded = np.concatenate([
261
+ np.zeros(self._PAD_LEAD, dtype="<i2"), samples,
262
+ np.zeros(self._PAD_TAIL, dtype="<i2")])
263
+ self.receive_frames(self._demod.decode(padded))
264
+
265
+ def receive_frames(self, frames: list[DecodedFrame]) -> None:
266
+ """Feed already-decoded frames to the session. The radio backend decodes a
267
+ rolling window on its own thread and reports each frame once it has decoded,
268
+ so it enters here rather than through `receive_audio` — its windows overlap,
269
+ and the same frame decoded a second time would replay its DATAACK and clear a
270
+ data frame the peer never acknowledged. The one repeat it does hand up is a
271
+ position it had only failed at before (`RollingDecoder.DEDUP_S`), which
272
+ arrives as a NAK this end now has the payload to correct."""
273
+ for frame in frames:
274
+ fd = F.FRAMES.get(frame.type)
275
+ # A DATAACK/DATANAK's own type carries the peer's grade of the frame it
276
+ # answers — the whole of our transmit-rate feedback, and invisible while
277
+ # all 32 codes logged under the one frame name.
278
+ graded = peer_quality(frame.type)
279
+ log.info("RX %s sess=%#04x ok=%s%s%s%s%s%s", fd.name if fd else hex(frame.type),
280
+ frame.session_id,
281
+ "UNVERIFIED" if frame.unverified else frame.ok,
282
+ " HEADER-ONLY" if frame.header_only else "",
283
+ "" if graded is None else f" theirq={graded}",
284
+ "" if frame.quality is None else f" q={frame.quality}",
285
+ # q=0 is a legal grade and also what is left when the body ran
286
+ # off the end of the capture (`DecodedFrame.truncated`), and
287
+ # five of the second kind were carried for days as the first.
288
+ " TRUNCATED" if frame.truncated else "",
289
+ f" {frame.caller}>{frame.target}" if frame.caller else "")
290
+ with self._lock:
291
+ if self._session is None:
292
+ return
293
+ for frame in frames:
294
+ self._session.on_receive(frame.type, self._frame_payload(frame),
295
+ frame.session_id, frame.ok, frame.quality,
296
+ header_only=frame.header_only)
297
+
298
+ def tick(self, now: float) -> None:
299
+ self._clock = now
300
+ with self._lock:
301
+ if self._session is not None:
302
+ self._session.tick(now)
303
+
304
+ def _run(self) -> None:
305
+ """Threaded pump: decode delivered audio and advance timers off the wall
306
+ clock until stopped."""
307
+ start = time.monotonic()
308
+ while self._running:
309
+ try:
310
+ samples = self._rx_q.get(timeout=0.1)
311
+ except queue.Empty:
312
+ samples = None
313
+ if samples is not None:
314
+ try:
315
+ self.receive_audio(samples)
316
+ except Exception as exc: # a malformed burst must not kill the pump
317
+ obs = self._observer
318
+ if obs is not None:
319
+ obs.modem_fault(f"receive error: {exc}")
320
+ self.tick(time.monotonic() - start)
321
+
322
+ @staticmethod
323
+ def _frame_payload(frame: DecodedFrame) -> bytes:
324
+ """Reconstruct the byte payload the ARQ session expects, since the demod
325
+ surfaces some frames as parsed fields rather than raw bytes: the packed
326
+ caller‖target for ConReq/Ping, the packed callsign‖grid for an ID frame,
327
+ the timing triple for a ConAck. Everything else carries its bytes."""
328
+ fd = F.FRAMES.get(frame.type)
329
+ name = fd.name if fd else ""
330
+ if (name.startswith("ConReq") or name == "Ping") and frame.caller and frame.target:
331
+ return callsign.pack_callsign(frame.caller) + callsign.pack_callsign(frame.target)
332
+ if name == "IDFrame" and frame.caller:
333
+ # The ARQ session never consumes an ID's grid, so a missing/short one
334
+ # is zero-filled rather than raising out of the pump (pack_grid is
335
+ # strict about 2/4/6/8-char grids).
336
+ grid = frame.grid or ""
337
+ packed_grid = callsign.pack_grid(grid) if len(grid) in (2, 4, 6, 8) else b"\x00" * 6
338
+ return callsign.pack_callsign(frame.caller) + packed_grid
339
+ if name.startswith("ConAck"):
340
+ t = min(255, (frame.conack_timing_ms or 0) // 10)
341
+ return bytes([t, t, t])
342
+ return frame.payload or b""