adding initial arq code

This commit is contained in:
Rafael Diniz 2025-08-02 01:05:03 +01:00
parent 3741b0cab5
commit c7c3f385a4
14 changed files with 2000 additions and 196 deletions

View file

@ -1,6 +1,6 @@
/* Audio subsystem
*
* Copyright (C) 2024 Rhizomatica
* Copyright (C) 2024-2025 Rhizomatica
* Author: Rafael Diniz <rafael@rhizomatica.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later
@ -19,7 +19,7 @@
#include "ring_buffer_posix.h"
#include "shm_posix.h"
#include "common_defines.h"
#include "../datalink/defines.h"
#include "os_interop.h"
#include "audioio.h"
@ -32,13 +32,6 @@ cbuf_handle_t playback_buffer;
int audio_subsystem;
#if defined(_WIN32)
HANDLE capture_prep_mutex;
#else
pthread_mutex_t capture_prep_mutex;
#endif
// tap to file FOR DEBUGGING PURPOSES //
#define ENABLE_FLOAT64_TAP 0
#define ENABLE_FLOAT64_TAP_BEFORE 0
@ -128,9 +121,9 @@ void *radio_playback_thread(void *device_ptr)
ffuint frame_size;
ffuint msec_bytes;
uint8_t *buffer = (uint8_t *) malloc(AUDIO_PAYLOAD_BUFFER_SIZE * sizeof(double) * 2);
uint8_t *buffer = (uint8_t *) malloc(SIGNAL_BUFFER_SIZE * sizeof(double) * 2);
double *buffer_double = (double *) buffer;
int32_t *buffer_internal_stereo = (int32_t *) malloc(AUDIO_PAYLOAD_BUFFER_SIZE * sizeof(int32_t) * 2); // a big enough buffer
int32_t *buffer_internal_stereo = (int32_t *) malloc(SIGNAL_BUFFER_SIZE * sizeof(int32_t) * 2); // a big enough buffer
ffuint total_written = 0;
int ch_layout = STEREO;
@ -370,7 +363,7 @@ void *radio_capture_thread(void *device_ptr)
frame_size = cfg->channels * (cfg->format & 0xff) / 8;
msec_bytes = cfg->sample_rate * frame_size / 1000;
buffer_internal = (double *) malloc(AUDIO_PAYLOAD_BUFFER_SIZE * sizeof(double) * 2);
buffer_internal = (double *) malloc(SIGNAL_BUFFER_SIZE * sizeof(double) * 2);
#if 0 // TODO: parametrize this
if (radio_type == RADIO_SBITX)
@ -567,7 +560,7 @@ int rx_transfer(double *buffer, size_t len)
int audioio_init_internal(char *capture_dev, char *playback_dev, int audio_subsys, pthread_t *radio_capture,
pthread_t *radio_playback, pthread_t *radio_capture_prep)
pthread_t *radio_playback)
{
audio_subsystem = audio_subsys;
@ -576,13 +569,13 @@ int audioio_init_internal(char *capture_dev, char *playback_dev, int audio_subsy
#endif
#if defined(_WIN32)
uint8_t *buffer_cap = (uint8_t *)malloc(AUDIO_PAYLOAD_BUFFER_SIZE);
uint8_t *buffer_play = (uint8_t *)malloc(AUDIO_PAYLOAD_BUFFER_SIZE);
capture_buffer = circular_buf_init(buffer_cap, AUDIO_PAYLOAD_BUFFER_SIZE);
playback_buffer = circular_buf_init(buffer_play, AUDIO_PAYLOAD_BUFFER_SIZE);
uint8_t *buffer_cap = (uint8_t *)malloc(SIGNAL_BUFFER_SIZE);
uint8_t *buffer_play = (uint8_t *)malloc(SIGNAL_BUFFER_SIZE);
capture_buffer = circular_buf_init(buffer_cap, SIGNAL_BUFFER_SIZE);
playback_buffer = circular_buf_init(buffer_play, SIGNAL_BUFFER_SIZE);
#else
capture_buffer = circular_buf_init_shm(AUDIO_PAYLOAD_BUFFER_SIZE, (char *) AUDIO_CAPT_PAYLOAD_NAME);
playback_buffer = circular_buf_init_shm(AUDIO_PAYLOAD_BUFFER_SIZE, (char *) AUDIO_PLAY_PAYLOAD_NAME);
capture_buffer = circular_buf_init_shm(SIGNAL_BUFFER_SIZE, (char *) SIGNAL_INPUT);
playback_buffer = circular_buf_init_shm(SIGNAL_BUFFER_SIZE, (char *) SIGNAL_OUTPUT);
#endif
clear_buffer(capture_buffer);
@ -594,9 +587,8 @@ int audioio_init_internal(char *capture_dev, char *playback_dev, int audio_subsy
return 0;
}
int audioio_deinit(pthread_t *radio_capture, pthread_t *radio_playback, pthread_t *radio_capture_prep)
int audioio_deinit(pthread_t *radio_capture, pthread_t *radio_playback)
{
pthread_join(*radio_capture_prep, NULL);
pthread_join(*radio_capture, NULL);
pthread_join(*radio_playback, NULL);
@ -610,10 +602,10 @@ int audioio_deinit(pthread_t *radio_capture, pthread_t *radio_playback, pthread_
free(playback_buffer->buffer);
circular_buf_free(playback_buffer);
#else
circular_buf_destroy_shm(capture_buffer, AUDIO_PAYLOAD_BUFFER_SIZE, (char *) AUDIO_CAPT_PAYLOAD_NAME);
circular_buf_destroy_shm(capture_buffer, SIGNAL_BUFFER_SIZE, (char *) SIGNAL_INPUT);
circular_buf_free_shm(capture_buffer);
circular_buf_destroy_shm(playback_buffer, AUDIO_PAYLOAD_BUFFER_SIZE, (char *) AUDIO_PLAY_PAYLOAD_NAME);
circular_buf_destroy_shm(playback_buffer, SIGNAL_BUFFER_SIZE, (char *) SIGNAL_OUTPUT);
circular_buf_free_shm(playback_buffer);
#endif
return 0;

View file

@ -34,17 +34,11 @@
extern cbuf_handle_t capture_buffer;
extern cbuf_handle_t playback_buffer;
#if defined(_WIN32)
extern HANDLE capture_prep_mutex;
#else
extern pthread_mutex_t capture_prep_mutex;
#endif
int audioio_init_internal(char *capture_dev, char *playback_dev, int audio_subsys, pthread_t *radio_capture,
pthread_t *radio_playback, pthread_t *radio_capture_prep);
pthread_t *radio_playback);
int audioio_deinit(pthread_t *radio_capture, pthread_t *radio_playback, pthread_t *radio_capture_prep);
int audioio_deinit(pthread_t *radio_capture, pthread_t *radio_playback);
int tx_transfer(double *buffer, size_t len);
int rx_transfer(double *buffer, size_t len);

View file

@ -1,157 +0,0 @@
/*
* Mercury: A configurable open-source software-defined modem.
* Copyright (C) 2022-2024 Fadi Jerji
* Author: Fadi Jerji
* Email: fadi.jerji@ <gmail.com, caisresearch.com, ieee.org>
* ORCID: 0000-0002-2076-5831
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, version 3 of the
* License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#ifndef INC_COMMON_DEFINES_H_
#define INC_COMMON_DEFINES_H_
#define VERSION__ "0.4.0"
#define BER_PLOT_baseband 0
#define BER_PLOT_passband 1
#define TX_RAND 2
#define RX_RAND 3
#define TX_TEST 4
#define RX_TEST 5
#define TX_SHM 6
#define RX_SHM 7
#define ARQ_MODE 8
#define NUMBER_OF_CONFIGS 17
#define CONFIG_NONE -1
#define CONFIG_0 0
#define CONFIG_1 1
#define CONFIG_2 2
#define CONFIG_3 3
#define CONFIG_4 4
#define CONFIG_5 5
#define CONFIG_6 6
#define CONFIG_7 7
#define CONFIG_8 8
#define CONFIG_9 9
#define CONFIG_10 10
#define CONFIG_11 11
#define CONFIG_12 12
#define CONFIG_13 13
#define CONFIG_14 14
#define CONFIG_15 15
#define CONFIG_16 16
/*
* Config CODE Mode EsN0(FER<0,1)
0 BPSK 1/16 BPSK 1/16 -10
1 BPSK 2/16 BPSK 2/16 -7,5
2 BPSK 3/16 BPSK 3/16 -6
3 BPSK 4/16 BPSK 4/16 -4,5
4 BPSK 5/16 BPSK 5/16 -3,5
5 BPSK 6/16 BPSK 6/16 -2,5
6 BPSK 8/16 BPSK 8/16 -1,5
7 QPSK 5/16 QPSK 5/16 -0,5
8 QPSK 6/16 QPSK 6/16 0,5
9 QPSK 8/16 QPSK 8/16 1,5
10 8PSK 6/16 8PSK 6/16 3
11 8PSK 8/16 8PSK 8/16 4
12 QPSK 14/16 QPSK 14/16 6,5
13 16QAM 8/16 16QAM 8/16 7,5
14 8PSK 14/16 8PSK 14/16 9
15 16QAM 14/16 16QAM 14/16 12,5
16 32QAM 14/16 32QAM 14/16 13,5
HIGH_DENSITY PILOTS
CONFIG_0 (71.3 bps).
CONFIG_1 (156.1 bps).
CONFIG_2 (241.0 bps).
CONFIG_3 (325.8 bps).
CONFIG_4 (410.6 bps).
CONFIG_5 (495.5 bps).
CONFIG_6 (665.2 bps).
CONFIG_7 (762.6 bps).
CONFIG_8 (920.2 bps).
CONFIG_9 (1235.3 bps).
CONFIG_10 (1353.7 bps).
CONFIG_11 (1818.1 bps).
CONFIG_12 (2261.4 bps).
CONFIG_13 (2470.6 bps).
CONFIG_14 (3389.7 bps).
CONFIG_15 (4361.3 bps).
CONFIG_16 (5664.7 bps).
LOW DENSITY PILOTS
CONFIG_0 (84.2 bps).
CONFIG_1 (184.5 bps).
CONFIG_2 (284.8 bps).
CONFIG_3 (385.0 bps).
CONFIG_4 (485.3 bps).
CONFIG_5 (585.6 bps).
CONFIG_6 (786.1 bps).
CONFIG_7 (889.7 bps).
CONFIG_8 (1073.5 bps).
CONFIG_9 (1441.2 bps).
CONFIG_10 (1353.7 bps).
CONFIG_11 (1818.1 bps).
CONFIG_12 (2654.7 bps).
CONFIG_13 (2882.4 bps).
CONFIG_14 (3389.7 bps).
CONFIG_15 (5088.2 bps).
CONFIG_16 (5664.7 bps).
*
*/
// messages definition
#define FIRST_MESSAGE 0
#define MIDDLE_MESSAGE 1
#define FLUSH_MESSAGE 2
#define SINGLE_MESSAGE 3
#define NO_FILTER_MESSAGE 4
// supported radios
#define RADIO_SBITX 0
#define RADIO_STOCKHF 1
// {TX,RX}_SHM shared memory interface
#define SHM_PAYLOAD_BUFFER_SIZE 131072
#define SHM_PAYLOAD_NAME "/mercury-comm"
// audio buffers shared memory interface
// 1536000 * 8
#define AUDIO_PAYLOAD_BUFFER_SIZE 12288000
#define AUDIO_CAPT_PAYLOAD_NAME "/audio-capt"
#define AUDIO_PLAY_PAYLOAD_NAME "/audio-play"
// Gear shifting modes
#define NO_GEAR_SHIFT 0
#define GEAR_SHIFT_ENABLED 1
// #define NO_GEAR_SHIFT_LADDER 2
// #define NO_GEAR_SHIFT_SNR 3
#define YES 1
#define NO 0
#endif // INC_COMMON_DEFINES_H_

View file

@ -46,11 +46,28 @@ LDFLAGS=$(FFAUDIO_LINKFLAGS) -lm
all: ../modem
../modem: main.o ../freedv/libfreedvdata.a ../audioio/audioio.a ../common/os_interop.o ../common/ring_buffer_posix.o ../common/shm_posix.o
$(CC) -o ../modem main.o ../freedv/libfreedvdata.a ../audioio/audioio.a ../common/os_interop.o ../common/ring_buffer_posix.o ../common/shm_posix.o $(LDFLAGS)
../modem: main.o arq.o net.o fsm.o arith.o crc6.o ../freedv/libfreedvdata.a ../audioio/audioio.a \
../common/os_interop.o ../common/ring_buffer_posix.o ../common/shm_posix.o
$(CC) -o ../modem main.o arq.o net.o fsm.o arith.o crc6.o ../freedv/libfreedvdata.a ../audioio/audioio.a \
../common/os_interop.o ../common/ring_buffer_posix.o ../common/shm_posix.o $(LDFLAGS)
main.o: main.c
$(CC) $(CFLAGS) -c main.c
arq.o: arq.c
$(CC) $(CFLAGS) -c arq.c
net.o: net.c
$(CC) $(CFLAGS) -c net.c
fsm.o: fsm.c
$(CC) $(CFLAGS) -c fsm.c
arith.o: arith.c
$(CC) $(CFLAGS) -c arith.c
crc6.o: crc6.c
$(CC) $(CFLAGS) -c crc6.c
clean:
rm -f *.o

382
datalink/arith.c Normal file
View file

@ -0,0 +1,382 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define NUM_SYMBOLS 39 // 37 symbols + 1 EOF + separator
#define CODE_BITS 32
#define MAX_CODE ((1ULL << CODE_BITS) - 1)
#define HALF (1ULL << (CODE_BITS - 1))
#define QUARTER (HALF >> 1)
#define THREE_QUARTERS (HALF + QUARTER)
#define BUFFER_SIZE 4096
#define MAX_ENCODE_BITS (BUFFER_SIZE * 8)
#define MAX_PENDING 1024
char symbols[NUM_SYMBOLS] = {
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'0','1','2','3','4','5','6','7','8','9','-', '|',
'\0' // EOF symbol
};
uint64_t cum_freq[NUM_SYMBOLS + 1];
void init_model() {
for (int i = 0; i <= NUM_SYMBOLS; i++)
cum_freq[i] = i;
}
int find_index(char c) {
for (int i = 0; i < NUM_SYMBOLS; i++)
if (symbols[i] == c)
return i;
return -1;
}
typedef struct {
uint8_t* buffer;
int bitpos;
int bytepos;
int total_bits;
} BitWriter;
void bw_init(BitWriter* bw, uint8_t* buf) {
bw->buffer = buf;
bw->bitpos = 0;
bw->bytepos = 0;
bw->total_bits = 0;
memset(buf, 0, BUFFER_SIZE);
}
void bw_write_bit(BitWriter* bw, int bit) {
if (bw->bytepos * 8 + bw->bitpos >= MAX_ENCODE_BITS) {
fprintf(stderr, "Fatal: bit output exceeded safe limit (%d bits)\n", MAX_ENCODE_BITS);
exit(1);
}
if (bit)
bw->buffer[bw->bytepos] |= (1 << (7 - bw->bitpos));
if (++bw->bitpos == 8) {
bw->bitpos = 0;
bw->bytepos++;
}
bw->total_bits++;
}
int bw_bytes(BitWriter* bw) {
return bw->bytepos + ((bw->bitpos != 0)?1:0);
}
#if 0
typedef struct {
uint8_t* buffer;
int bitpos;
int bytepos;
int length;
int total_bits_read; // NEW: track total bits consumed
} BitReader;
void br_init(BitReader* br, uint8_t* buf, int len) {
br->buffer = buf;
br->bitpos = 0;
br->bytepos = 0;
br->length = len;
br->total_bits_read = 0;
}
int br_read_bit(BitReader* br) {
if (br->bytepos >= br->length)
return -1; // signal bitstream exhausted
int bit = (br->buffer[br->bytepos] >> (7 - br->bitpos)) & 1;
br->total_bits_read++;
if (++br->bitpos == 8) {
br->bitpos = 0;
br->bytepos++;
}
return bit;
}
#endif
#if 1
typedef struct {
uint8_t* buffer;
int bitpos;
int bytepos;
int length;
int max_read_ahead;
} BitReader;
void br_init(BitReader* br, uint8_t* buf, int len) {
br->buffer = buf;
br->bitpos = 0;
br->bytepos = 0;
br->length = len;
br->max_read_ahead = 24;
}
int br_read_bit(BitReader* br)
{
// limit how far we go...
if (br->bytepos >= br->length)
{
br->max_read_ahead--;
if (br->max_read_ahead == 0)
return -1;
else
return 0;
}
int bit = (br->buffer[br->bytepos] >> (7 - br->bitpos)) & 1;
if (++br->bitpos == 8) {
br->bitpos = 0;
br->bytepos++;
}
return bit;
}
#endif
int arithmetic_encode(const char* msg, uint8_t* output) {
BitWriter bw;
bw_init(&bw, output);
uint64_t low = 0, high = MAX_CODE;
int pending = 0;
uint64_t total = cum_freq[NUM_SYMBOLS];
for (int i = 0;; i++) {
int sym;
if (msg[i] == '\0') {
sym = NUM_SYMBOLS - 1; // EOF symbol
} else {
sym = find_index(msg[i]);
if (sym < 0) {
fprintf(stderr, "Unknown symbol: %c\n", msg[i]);
return 0;
}
}
uint64_t range = high - low + 1;
uint64_t sym_low = cum_freq[sym];
uint64_t sym_high = cum_freq[sym + 1];
high = low + (range * sym_high) / total - 1;
low = low + (range * sym_low) / total;
while (1) {
if (high < HALF) {
bw_write_bit(&bw, 0);
while (pending-- > 0)
bw_write_bit(&bw, 1);
pending = 0;
} else if (low >= HALF) {
bw_write_bit(&bw, 1);
while (pending-- > 0)
bw_write_bit(&bw, 0);
pending = 0;
low -= HALF;
high -= HALF;
} else if (low >= QUARTER && high < THREE_QUARTERS) {
pending++;
low -= QUARTER;
high -= QUARTER;
} else {
break;
}
low <<= 1;
high = (high << 1) | 1;
}
if (sym == NUM_SYMBOLS - 1) break; // EOF encoded
}
// Final bits
pending++;
if (low < QUARTER) {
bw_write_bit(&bw, 0);
while (pending-- > 0)
bw_write_bit(&bw, 1);
} else {
bw_write_bit(&bw, 1);
while (pending-- > 0)
bw_write_bit(&bw, 0);
}
printf("\n[ENCODE DONE] Total bits written: %d (%d bytes)\n", bw.total_bits, bw_bytes(&bw));
printf("Bit packing: MSB-first within each byte (big-endian per byte)\n");
return bw_bytes(&bw);
}
#if 1
int arithmetic_decode(uint8_t* input, int max_len, char* output) {
BitReader br;
br_init(&br, input, max_len);
uint64_t low = 0, high = MAX_CODE;
uint64_t value = 0;
uint64_t total = cum_freq[NUM_SYMBOLS];
int outpos = 0;
//printf("br.total_bits_read %d\n", br.total_bits_read);
// Initialize value
for (int i = 0; i < CODE_BITS; i++) {
int b = br_read_bit(&br);
if (b < 0) {
fprintf(stderr, "Decode error: bitstream ended too early (init)\n");
return -1;
}
value = (value << 1) | b;
}
while (1) {
//printf("br.total_bits_read %d\n", br.total_bits_read);
uint64_t range = high - low + 1;
uint64_t scaled = ((value - low + 1) * total - 1) / range;
int sym = 0;
while (!(scaled >= cum_freq[sym] && scaled < cum_freq[sym + 1])) {
sym++;
if (sym >= NUM_SYMBOLS) {
fprintf(stderr, "Decode error: no matching symbol for scaled=%lu\n", scaled);
return -1;
}
}
if (symbols[sym] == '\0') {
break; // EOF reached
}
output[outpos++] = symbols[sym];
high = low + (range * cum_freq[sym + 1]) / total - 1;
low = low + (range * cum_freq[sym]) / total;
while (1) {
if (high < HALF) {
// no renormalization needed
} else if (low >= HALF) {
value -= HALF;
low -= HALF;
high -= HALF;
} else if (low >= QUARTER && high < THREE_QUARTERS) {
value -= QUARTER;
low -= QUARTER;
high -= QUARTER;
} else {
break;
}
low <<= 1;
high = (high << 1) | 1;
int b = br_read_bit(&br);
if (b < 0) {
fprintf(stderr, "Decode error: bitstream ended too early (loop)\n");
goto finish;
}
value = (value << 1) | b;
}
}
//printf("br.total_bits_read %d\n", br.total_bits_read);
finish:
output[outpos] = '\0';
return 0;
}
#endif
#if 0
int arithmetic_decode(uint8_t* input, int max_len, char* output) {
BitReader br;
br_init(&br, input, max_len);
uint64_t low = 0, high = MAX_CODE;
uint64_t value = 0;
uint64_t total = cum_freq[NUM_SYMBOLS];
// printf("br.bytepos %d br.bitpos %d\n", br.bytepos, br.bitpos);
for (int i = 0; i < CODE_BITS; i++)
value = (value << 1) | br_read_bit(&br);
int outpos = 0;
while (1) {
// printf("br.bytepos %d br.bitpos %d\n", br.bytepos, br.bitpos);
uint64_t range = high - low + 1;
uint64_t scaled = ((value - low + 1) * total - 1) / range;
int sym = 0;
while (!(scaled >= cum_freq[sym] && scaled < cum_freq[sym + 1])) {
sym++;
if (sym >= NUM_SYMBOLS) {
fprintf(stderr, "Decode error: no matching symbol for scaled=%lu\n", scaled);
return -1;
}
}
if (symbols[sym] == '\0')
{
break; // EOF reached
}
output[outpos++] = symbols[sym];
high = low + (range * cum_freq[sym + 1]) / total - 1;
low = low + (range * cum_freq[sym]) / total;
while (1) {
if (high < HALF) {
// do nothing
} else if (low >= HALF) {
value -= HALF;
low -= HALF;
high -= HALF;
} else if (low >= QUARTER && high < THREE_QUARTERS) {
value -= QUARTER;
low -= QUARTER;
high -= QUARTER;
} else break;
low <<= 1;
high = (high << 1) | 1;
value = (value << 1) | br_read_bit(&br);
}
}
output[outpos] = '\0';
return 0;
}
#endif
#if 0
int main() {
init_model();
const char* msg1 = "PU2UIT-15|PU4GNU-15";
printf("Inputs: %s \n", msg1);
uint8_t encoded[BUFFER_SIZE];
int enc_len1 = arithmetic_encode(msg1, encoded);
printf("Encoded length 1: %d bytes\n", enc_len1);
// int enc_len2 = arithmetic_encode(msg2, encoded + enc_len1);
// printf("Encoded length 2: %d bytes\n", enc_len2);
init_model();
char decoded[100];
int dec_len1;
if (arithmetic_decode(encoded, 11, decoded) == 0)
printf("Decoded: %s\n", decoded);
// int dec_len2;
// if (arithmetic_decode(encoded + dec_len1, 16 - dec_len1, &dec_len2, decoded) == 0)
// printf("Decoded: %s encoded length: %d\n", decoded, dec_len2);
return 0;
}
#endif

904
datalink/arq.c Normal file
View file

@ -0,0 +1,904 @@
/* HERMES Modem
*
* Copyright (C) 2025 Rhizomatica
* Author: Rafael Diniz <rafael@riseup.net>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include <sys/time.h>
#include "arq.h"
#include "fsm.h"
#include "../audioio/audioio.h"
#include "net.h"
#include "defines.h"
extern cbuf_handle_t capture_buffer;
extern cbuf_handle_t playback_buffer;
cbuf_handle_t data_tx_buffer;
cbuf_handle_t data_rx_buffer;
extern bool shutdown_;
// ARQ definitions
arq_info arq_conn;
#define DEBUG
static pthread_t tid[8];
// our simple fsm struct
fsm_handle arq_fsm;
/* FSM States */
void state_no_connected_client(int event)
{
printf("FSM State: no_connected_client\n");
switch(event)
{
case EV_CLIENT_CONNECT:
clear_connection_data();
arq_fsm.current = state_idle;
break;
default:
printf("Event: %d ignored in state_no_connected_client().\n", event);
}
return;
}
void state_link_connected(int event)
{
printf("FSM State: link_connected\n");
switch(event)
{
case EV_CLIENT_DISCONNECT:
arq_fsm.current = state_no_connected_client;
break;
case EV_LINK_DISCONNECT:
arq_fsm.current = (arq_conn.listen == true)? state_listen : state_idle;
break;
default:
printf("Event: %d ignored in state_disconnected().\n", event);
}
return;
}
void state_listen(int event)
{
printf("FSM State: listen\n");
switch(event)
{
case EV_START_LISTEN:
printf("EV_START_LISTEN ignored in state_listen() - already listening.\n");
break;
case EV_STOP_LISTEN:
arq_conn.listen = false;
arq_fsm.current = state_idle;
break;
case EV_LINK_CALL_REMOTE:
call_remote();
arq_fsm.current = state_connecting_caller;
break;
case EV_LINK_DISCONNECT:
printf("EV_LINK_DISCONNECT ignored in state_listen() - not connected.\n");
break;
case EV_CLIENT_DISCONNECT:
clear_connection_data();
arq_fsm.current = state_no_connected_client;
break;
case EV_LINK_INCOMING_CALL:
callee_accept_connection(); //packets created to anwser in case of incoming call with correct callsign
arq_fsm.current = state_connecting_callee;
break;
default:
printf("Event: %d ignored in state_listen().\n", event);
}
return;
}
void state_idle(int event)
{
printf("FSM State: idle\n");
switch(event)
{
case EV_START_LISTEN:
arq_conn.listen = true;
arq_fsm.current = state_listen;
break;
case EV_STOP_LISTEN:
arq_conn.listen = false;
printf("EV_STOP_LISTEN ignored in state_idle() - already stopped.\n");
break;
case EV_LINK_CALL_REMOTE:
call_remote();
arq_fsm.current = state_connecting_caller;
break;
case EV_LINK_DISCONNECT:
printf("EV_LINK_DISCONNECT ignored in state_idle() - not connected.\n");
break;
case EV_CLIENT_DISCONNECT:
clear_connection_data();
arq_fsm.current = state_no_connected_client;
break;
default:
printf("Event: %d ignored from state_idle\n", event);
}
return;
}
void state_connecting_caller(int event)
{
printf("FSM State: connecting_caller\n");
switch(event)
{
case EV_START_LISTEN:
arq_conn.listen = true;
break;
case EV_STOP_LISTEN:
arq_conn.listen = false;
break;
case EV_LINK_CALL_REMOTE:
printf("EV_LINK_CALL_REMOTE ignored in state_connecting_caller() - already connecting.\n");
break;
case EV_LINK_DISCONNECT:
// TODO: kill the connection first? Do we need to do something?
arq_fsm.current = (arq_conn.listen == true)? state_listen : state_idle;
break;
case EV_CLIENT_DISCONNECT:
clear_connection_data();
arq_fsm.current = state_no_connected_client;
break;
case EV_LINK_ESTABLISHED:
tnc_send_connected();
arq_fsm.current = state_link_connected;
break;
default:
printf("Event: %d ignored from state_idle\n", event);
}
return;
}
void state_connecting_callee(int event)
{
printf("FSM State: connecting_callee\n");
switch(event)
{
break;
case EV_STOP_LISTEN:
arq_conn.listen = false;
break;
case EV_LINK_CALL_REMOTE:
printf("EV_LINK_CALL_REMOTE ignored in state_connecting_caller() - already connecting.\n");
break;
case EV_LINK_DISCONNECT:
// TODO: kill the connection first? Do we need to do something?
arq_fsm.current = (arq_conn.listen == true)? state_idle : state_listen;
break;
case EV_CLIENT_DISCONNECT:
clear_connection_data();
arq_fsm.current = state_no_connected_client;
break;
case EV_LINK_ESTABLISHMENT_TIMEOUT:
// TODO: do some house cleeping here?
arq_fsm.current = (arq_conn.listen == true)? state_idle : state_listen;
break;
case EV_LINK_ESTABLISHED:
// TODO: do some house cleeping here?
tnc_send_connected();
arq_fsm.current = state_link_connected;
break;
default:
printf("Event: %d ignored from state_idle\n", event);
}
return;
}
void tnc_send_connected()
{
char buffer[128];
sprintf(buffer, "CONNECTED %s %s %d\r", arq_conn.my_call_sign, arq_conn.dst_addr, 2300);
ssize_t i = tcp_write(CTL_TCP_PORT, (uint8_t *)buffer, strlen(buffer));
if (i < 0)
printf("Error sending connected message: %s\n", strerror(errno));
}
void tnc_send_disconnected()
{
char buffer[128];
sprintf(buffer, "DISCONNECTED\r");
ssize_t i = tcp_write(CTL_TCP_PORT, (uint8_t *)buffer, strlen(buffer));
if (i < 0)
printf("Error sending disconnected message: %s\n", strerror(errno));
}
bool check_crc(uint8_t *data)
{
// TODO: put the correct frame size here
int frame_size = 0;
uint16_t crc = (uint16_t) (data[0] & 0x3f);
uint16_t calculated_crc = crc6_0X6F(1, data + HEADER_SIZE, frame_size - HEADER_SIZE);
if (crc == calculated_crc)
return true;
return false;
}
int check_for_incoming_connection(uint8_t *data)
{
char callsigns[CALLSIGN_MAX_SIZE * 2];
char dst_callsign[CALLSIGN_MAX_SIZE] = { 0 };
char src_callsign[CALLSIGN_MAX_SIZE] = { 0 };
// TODO: put the correct frame size here
int frame_size = 0;
uint8_t pack_type = (data[0] >> 6) & 0xff;
if (check_crc(data) == false)
{
printf("Bad crc for packet type %u.\n", pack_type);
return -1;
}
if (pack_type != PACKET_ARQ_CONTROL)
{
return 1;
}
if (arithmetic_decode(data + HEADER_SIZE, frame_size - HEADER_SIZE, callsigns) < 0)
{
printf("Truncated callsigns.\n");
}
printf("Decoded callsigns: %s\n", callsigns);
char *needle;
if ( (needle = strstr(callsigns,"|")) )
{
int i = 0;
while (callsigns[i] != '|')
{
dst_callsign[i] = callsigns[i];
i++;
}
i++;
dst_callsign[i] = 0;
i = 0;
needle++;
while (callsigns[i] != 0)
{
src_callsign[i] = needle[i];
i++;
}
src_callsign[i] = 0;
}
else // corner case where only the destination address fits in the frame
{
strcpy(dst_callsign, callsigns);
}
if (!strncmp(dst_callsign, arq_conn.my_call_sign, strlen(dst_callsign)))
{
fsm_dispatch(&arq_fsm, EV_LINK_INCOMING_CALL);
}
else
{
// TODO: or we just wait for timeout, or drop a EV_LINK_ESTABLISHMENT_FAILURE
fsm_dispatch(&arq_fsm, EV_LINK_ESTABLISHMENT_TIMEOUT);
printf("Called call %s sign does not match my callsign %s. Doing nothing.\n", dst_callsign, arq_conn.my_call_sign);
}
return 0;
}
int check_for_connection_acceptance_caller(uint8_t *data)
{
char callsign[CALLSIGN_MAX_SIZE];
// TODO: put the correct frame size here
int frame_size = 0;
uint8_t pack_type = (data[0] >> 6) & 0xff;
if (check_crc(data) == false)
{
printf("Bad crc for packet type %u.\n", pack_type);
return -1;
}
if (pack_type != PACKET_ARQ_CONTROL)
{
return 1;
}
if (arithmetic_decode(data + HEADER_SIZE, frame_size - HEADER_SIZE, callsign) < 0)
{
printf("Truncated callsigns.\n");
}
printf("Decoded callsign: %s\n", callsign);
if (!strncmp(callsign, arq_conn.dst_addr, strlen(callsign)))
{
fsm_dispatch(&arq_fsm, EV_LINK_ESTABLISHED);
}
else
{
// TODO:
// fsm_dispatch(&arq_fsm, EV_LINK_ESTABLISHED_FAILURE);
printf("Responded callsign %s does not match called %s. Doing nothing.\n", callsign, arq_conn.dst_addr);
}
return 0;
}
void callee_accept_connection()
{
// here we just send the callee callsign back
uint8_t data[INT_BUFFER_SIZE];
char callsign[CALLSIGN_MAX_SIZE];
uint8_t encoded_callsign[CALLSIGN_MAX_SIZE];
// TODO: put the correct frame size here
int frame_size = 0;
memset(data, 0, frame_size);
// 1 byte header, 4 bits packet type, 6 bits crc
data[0] = (PACKET_ARQ_CONTROL << 6) & 0xff; // set packet type
// encode the callsign
sprintf(callsign, "%s", arq_conn.my_call_sign);
int enc_len = arithmetic_encode(callsign, encoded_callsign);
if (enc_len > frame_size - HEADER_SIZE)
{
printf("Trucating callsigns. This is ok (%d bytes out of %d transmitted)\n", frame_size - HEADER_SIZE, enc_len);
enc_len = frame_size - HEADER_SIZE;
}
memcpy(data + HEADER_SIZE, encoded_callsign, enc_len);
data[0] |= (uint8_t) crc6_0X6F(1, data + HEADER_SIZE, frame_size - HEADER_SIZE);
write_buffer(data_tx_buffer, data, frame_size);
}
void call_remote()
{
uint8_t data[INT_BUFFER_SIZE];
char joint_callsigns[CALLSIGN_MAX_SIZE * 2];
uint8_t encoded_callsigns[INT_BUFFER_SIZE];
// TODO: put the correct frame size here
int frame_size = 0;
printf("Calling remote %s, frame_size: %d\n", arq_conn.dst_addr, frame_size);
memset(data, 0, frame_size);
// 1 byte header, 4 bits packet type, 6 bits crc
data[0] = (PACKET_ARQ_CONTROL << 6) & 0xff; // set packet type
// encode the destination callsign first, then the source, separated by "|"
sprintf(joint_callsigns,"%s|%s", arq_conn.dst_addr, arq_conn.src_addr);
printf("Joint callsigns: %s\n", joint_callsigns);
int enc_len = arithmetic_encode(joint_callsigns, encoded_callsigns);
printf("Encoded callsigns: %s, length: %d\n", joint_callsigns, enc_len);
if (enc_len > frame_size - HEADER_SIZE)
{
printf("Trucating joint destination + source callsigns. This is ok (%d bytes out of %d transmitted)\n", frame_size - HEADER_SIZE, enc_len);
enc_len = frame_size - HEADER_SIZE;
}
memcpy(data + HEADER_SIZE, encoded_callsigns, enc_len);
data[0] |= (uint8_t) crc6_0X6F(1, (uint8_t *)data + HEADER_SIZE, frame_size - HEADER_SIZE);
write_buffer(data_tx_buffer, data, frame_size);
return;
}
/* ---- END OF FSM Definitions ---- */
void clear_connection_data()
{
clear_buffer(data_tx_buffer);
clear_buffer(data_rx_buffer);
reset_arq_info(&arq_conn);
}
void reset_arq_info(arq_info *arq_conn_i)
{
arq_conn_i->TRX = RX;
arq_conn_i->bw = 0; // 0 = auto
arq_conn_i->encryption = false;
arq_conn_i->listen = false;
arq_conn_i->my_call_sign[0] = 0;
arq_conn_i->src_addr[0] = 0;
arq_conn_i->dst_addr[0] = 0;
}
int arq_init(int tcp_base_port, int initial_mode)
{
status_ctl = NET_NONE;
status_data = NET_NONE;
arq_conn.call_burst_size = CALL_BURST_SIZE;
uint8_t *buffer_tx = (uint8_t *) malloc(DATA_TX_BUFFER_SIZE);
uint8_t *buffer_rx = (uint8_t *) malloc(DATA_RX_BUFFER_SIZE);
data_tx_buffer = circular_buf_init(buffer_tx, DATA_TX_BUFFER_SIZE);
data_rx_buffer = circular_buf_init(buffer_rx, DATA_RX_BUFFER_SIZE);
// TODO: init modem here?
reset_arq_info(&arq_conn);
init_model(); // the arithmetic encoder init function
fsm_init(&arq_fsm, state_no_connected_client);
// here is the thread that runs the accept(), each per port, and mantains the
// state of the connection
pthread_create(&tid[0], NULL, server_worker_thread_ctl, (void *) &tcp_base_port);
pthread_create(&tid[1], NULL, server_worker_thread_data, (void *) &tcp_base_port);
// control channel threads
pthread_create(&tid[2], NULL, control_worker_thread_rx, (void *) NULL);
pthread_create(&tid[3], NULL, control_worker_thread_tx, (void *) NULL);
// data channel threads
pthread_create(&tid[4], NULL, data_worker_thread_tx, (void *) NULL);
pthread_create(&tid[5], NULL, data_worker_thread_rx, (void *) NULL);
// dsp threads
pthread_create(&tid[6], NULL, dsp_thread_tx, (void *) NULL);
pthread_create(&tid[7], NULL, dsp_thread_rx, (void *) NULL);
return EXIT_SUCCESS;
}
void arq_shutdown()
{
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_join(tid[2], NULL);
pthread_join(tid[3], NULL);
pthread_join(tid[4], NULL);
pthread_join(tid[5], NULL);
pthread_join(tid[6], NULL);
pthread_join(tid[7], NULL);
free(data_tx_buffer->buffer);
free(data_rx_buffer->buffer);
}
char *get_timestamp()
{
static char buffer[32];
struct timeval tv;
gettimeofday(&tv, NULL);
struct tm *tm = localtime(&tv.tv_sec);
snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d.%03ld\n", tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec / 1000);
return buffer;
}
void ptt_on()
{
char buffer[] = "PTT ON\r";
arq_conn.TRX = TX;
ssize_t i = tcp_write(CTL_TCP_PORT, (uint8_t *)buffer, strlen(buffer));
// print timestamp with miliseconds precision
#ifdef DEBUG
printf("PTT ON %s", get_timestamp());
#endif
}
void ptt_off()
{
char buffer[] = "PTT OFF\r";
arq_conn.TRX = RX;
ssize_t i = tcp_write(CTL_TCP_PORT, (uint8_t *)buffer, strlen(buffer));
// print timestamp with miliseconds precision
#ifdef DEBUG
printf("PTT OFF %s", get_timestamp());
#endif
}
// tx to tcp socket the received data from the modem
void *data_worker_thread_tx(void *conn)
{
uint8_t *buffer = (uint8_t *) malloc(DATA_TX_BUFFER_SIZE);
while(!shutdown_)
{
if (status_data != NET_CONNECTED)
{
sleep(1);
continue;
}
if(arq_fsm.current == state_link_connected)
{
size_t n = read_buffer_all(data_rx_buffer, buffer);
ssize_t i = tcp_write(DATA_TCP_PORT, buffer, n);
if (i < (ssize_t) n)
fprintf(stderr, "Problems in data_worker_thread_tx!\n");
}
else
{
msleep(50);
}
}
free(buffer);
return NULL;
}
// rx from tcp socket and send to trasmit by the modem
void *data_worker_thread_rx(void *conn)
{
uint8_t *buffer = (uint8_t *) malloc(TCP_BLOCK_SIZE);
while(!shutdown_)
{
if (status_data != NET_CONNECTED)
{
sleep(1);
continue;
}
if(arq_fsm.current == state_link_connected)
{
int n = tcp_read(DATA_TCP_PORT, buffer, TCP_BLOCK_SIZE);
write_buffer(data_tx_buffer, buffer, n);
}
else
{
msleep(50);
}
}
free(buffer);
return NULL;
}
void *control_worker_thread_tx(void *conn)
{
int counter = 0;
char imalive[] = "IAMALIVE\r";
while(!shutdown_)
{
if (status_ctl != NET_CONNECTED)
{
sleep(1);
continue;
}
if (counter == 60)
{
counter = 0;
ssize_t i = tcp_write(CTL_TCP_PORT, (uint8_t *)imalive, strlen(imalive));
}
sleep(1);
counter++;
}
return NULL;
}
void *control_worker_thread_rx(void *conn)
{
char *buffer = (char *) malloc(TCP_BLOCK_SIZE+1);
char temp[16];
int count = 0;
memset(buffer, 0, TCP_BLOCK_SIZE+1);
while(!shutdown_)
{
if (status_ctl != NET_CONNECTED)
{
sleep(1);
continue;
}
int n = tcp_read(CTL_TCP_PORT, (uint8_t *)buffer + count, 1);
if (n < 0)
{
count = 0;
fprintf(stderr, "ERROR ctl socket reading\n");
status_ctl = NET_RESTART;
continue;
}
if (buffer[count] != '\r')
{
count++;
continue;
}
// we found the '\r'
buffer[count] = 0;
if (count >= TCP_BLOCK_SIZE)
{
count = 0;
fprintf(stderr, "ERROR in command parsing\n");
continue;
}
count = 0;
#ifdef DEBUG
fprintf(stderr,"Command received: %s\n", buffer);
#endif
// now we parse the commands
if (!memcmp(buffer, "MYCALL", strlen("MYCALL")))
{
sscanf(buffer,"MYCALL %s", arq_conn.my_call_sign);
goto send_ok;
}
if (!memcmp(buffer, "LISTEN", strlen("LISTEN")))
{
sscanf(buffer,"LISTEN %s", temp);
if (temp[1] == 'N') // ON
{
fsm_dispatch(&arq_fsm, EV_START_LISTEN);
}
if (temp[1] == 'F') // OFF
{
fsm_dispatch(&arq_fsm, EV_STOP_LISTEN);
}
goto send_ok;
}
if (!memcmp(buffer, "PUBLIC", strlen("PUBLIC")))
{
sscanf(buffer,"PUBLIC %s", temp);
if (temp[1] == 'N') // ON
arq_conn.encryption = false;
if (temp[1] == 'F') // OFF
arq_conn.encryption = true;
goto send_ok;
}
if (!memcmp(buffer, "BW", strlen("BW")))
{
sscanf(buffer,"BW%d", &arq_conn.bw);
goto send_ok;
}
if (!memcmp(buffer, "CONNECT", strlen("CONNECT")))
{
sscanf(buffer,"CONNECT %s %s", arq_conn.src_addr, arq_conn.dst_addr);
fsm_dispatch(&arq_fsm, EV_LINK_CALL_REMOTE);
goto send_ok;
}
if (!memcmp(buffer, "DISCONNECT", strlen("DISCONNECT")))
{
fsm_dispatch(&arq_fsm, EV_LINK_DISCONNECT);
goto send_ok;
}
fprintf(stderr, "Unknown command\n");
tcp_write(CTL_TCP_PORT, (uint8_t *) "WRONG\r", 6);
continue;
send_ok:
tcp_write(CTL_TCP_PORT, (uint8_t *) "OK\r", 3);
}
free(buffer);
return NULL;
}
void *server_worker_thread_ctl(void *port)
{
int tcp_base_port = *((int *) port);
int socket;
while(!shutdown_)
{
int ret = tcp_open(tcp_base_port, CTL_TCP_PORT);
if (ret < 0)
{
fprintf(stderr, "Could not open TCP port %d\n", tcp_base_port);
shutdown_ = true;
}
socket = listen4connection(CTL_TCP_PORT);
if (socket < 0)
{
status_ctl = NET_RESTART;
tcp_close(CTL_TCP_PORT);
continue;
}
fsm_dispatch(&arq_fsm, EV_CLIENT_CONNECT);
// TODO: pthread wait here?
while (status_ctl == NET_CONNECTED)
sleep(1);
// inform the data thread
if (status_data == NET_CONNECTED)
status_data = NET_RESTART;
fsm_dispatch(&arq_fsm, EV_CLIENT_DISCONNECT);
tcp_close(CTL_TCP_PORT);
}
return NULL;
}
void *server_worker_thread_data(void *port)
{
int tcp_base_port = *((int *) port);
int socket;
while(!shutdown_)
{
int ret = tcp_open(tcp_base_port+1, DATA_TCP_PORT);
if (ret < 0)
{
fprintf(stderr, "Could not open TCP port %d\n", tcp_base_port+1);
shutdown_ = true;
}
socket = listen4connection(DATA_TCP_PORT);
if (socket < 0)
{
status_data = NET_RESTART;
tcp_close(DATA_TCP_PORT);
continue;
}
// pthread wait here?
while (status_data == NET_CONNECTED)
sleep(1);
tcp_close(DATA_TCP_PORT);
}
return NULL;
}
void *dsp_thread_tx(void *conn)
{
static uint32_t spinner_anim = 0; char spinner[] = ".oOo";
// TODO: put the correct frame size here
int frame_size = 0;
uint8_t data[INT_BUFFER_SIZE];
// TODO: may be we need another function to queue the already prepared packets?
while(!shutdown_)
{
// should we add a header already here, to know the size of each package? (size need to match frame_size
if ((int) size_buffer(data_tx_buffer) < frame_size ||
arq_fsm.current == state_idle ||
arq_fsm.current == state_no_connected_client)
{
msleep(50);
continue;
}
if(arq_fsm.current != state_idle && arq_fsm.current != state_listen)
{
read_buffer(data_tx_buffer, data, frame_size);
}
else
{
msleep(50);
continue;
}
for (int i = 0; i < frame_size; i++)
{
printf("%02x ", data[i]);
}
ptt_on();
msleep(10); // TODO: tune me!
// our connection request
if (arq_fsm.current == state_connecting_caller || arq_fsm.current == state_connecting_callee)
{
// tx_transfer(...);
}
if (arq_fsm.current == state_link_connected)
{
// here we have the data to transmit, so we call the tx_transfer function
//tx_transfer(); }
}
// TODO: signal when stream is finished playing via pthread_cond_wait() here
while (size_buffer(playback_buffer) != 0)
{
printf("%c\033[1D", spinner[spinner_anim % 4]); spinner_anim++;
fflush(stdout);
msleep(10);
}
msleep(40); // TODO: parametrize-me!
ptt_off();
printf("%c\033[1D", spinner[spinner_anim % 4]); spinner_anim++;
fflush(stdout);
}
return NULL;
}
void *dsp_thread_rx(void *conn)
{
static uint32_t spinner_anim = 0; char spinner[] = ".oOo";
while(!shutdown_)
{
msleep(50);
}
return NULL;
}

110
datalink/arq.h Normal file
View file

@ -0,0 +1,110 @@
/* HERMES Modem
*
* Copyright (C) 2025 Rhizomatica
* Author: Rafael Diniz <rafael@riseup.net>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#ifndef ARQ_H_
#define ARQ_H_
#define DEFAULT_ARQ_PORT 8300
#define DEFAULT_BROADCAST_PORT 8100
#define TCP_BLOCK_SIZE 128
#define CALLSIGN_MAX_SIZE 16
#define DATA_TX_BUFFER_SIZE 8192
#define DATA_RX_BUFFER_SIZE 8192
#define RX 0
#define TX 1
#define HEADER_SIZE 1
#define PACKET_ARQ_CONTROL 0x00
#define PACKET_ARQ_DATA 0x01
#define PACKET_BROADCAST_CONTROL 0x02
#define PACKET_BROADCAST_PAYLOAD 0x03
#define CALL_BURST_SIZE 3 // 3 frames
#include <stdint.h>
#include <stdbool.h>
typedef struct
{
int TRX; // RX (0) or TX (1)
char my_call_sign[CALLSIGN_MAX_SIZE];
char src_addr[CALLSIGN_MAX_SIZE], dst_addr[CALLSIGN_MAX_SIZE];
bool encryption;
bool call_burst_size;
bool listen;
int bw; // in Hz
} arq_info;
// frame sizes, no CRC enabled, modes 0 to 16.
// extern uint32_t mercury_frame_size[];
// FSM states
void state_listen(int event);
void state_idle(int event);
void state_connecting_caller(int event);
void state_connecting_callee(int event);
// ARQ core functions
int arq_init(int tcp_base_port, int initial_mode);
void arq_shutdown();
void print_arq_stats();
// TCP/IP server threads
void *server_worker_thread_ctl(void *port);
void *server_worker_thread_data(void *port);
void *data_worker_thread_tx(void *conn);
void *data_worker_thread_rx(void *conn);
void *control_worker_thread_tx(void *conn);
void *control_worker_thread_rx(void *conn);
// DSP threads
void *dsp_thread_tx(void *conn);
void *dsp_thread_rx(void *conn);
// auxiliary functions
void clear_connection_data();
void reset_arq_info(arq_info *arq_conn);
void call_remote();
void callee_accept_connection();
int check_for_incoming_connection(uint8_t *data);
int check_for_connection_acceptance_caller(uint8_t *data);
char *get_timestamp();
// TNC / radio functions
void ptt_on();
void ptt_off();
void tnc_send_connected();
void tnc_send_disconnected();
// file crc6.cc
uint16_t crc6_0X6F(uint16_t crc, const uint8_t *data, int data_len);
// from arith.cc
void init_model();
int arithmetic_encode(const char* msg, uint8_t* output);
int arithmetic_decode(uint8_t* input, int max_len, char* output);
#endif

73
datalink/crc6.c Normal file
View file

@ -0,0 +1,73 @@
/* HERMES Modem
*
* Copyright (C) 2025 Rhizomatica
* Author: Rafael Diniz <rafael@riseup.net>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
/**
* Functions and types for CRC checks.
*
* Generated on Wed Jan 2 2019,
* by pycrc v0.9.1, http://www.tty1.net/pycrc/
* using the configuration:
* Width = 6
* Poly = 0x6f
* XorIn = 0
* ReflectIn = False
* XorOut = 0
* ReflectOut = False
*/
static const uint8_t crc6_table[256] = {
0x00, 0x2f, 0x31, 0x1e, 0x0d, 0x22, 0x3c, 0x13, 0x1a, 0x35, 0x2b, 0x04, 0x17, 0x38, 0x26, 0x09,
0x34, 0x1b, 0x05, 0x2a, 0x39, 0x16, 0x08, 0x27, 0x2e, 0x01, 0x1f, 0x30, 0x23, 0x0c, 0x12, 0x3d,
0x07, 0x28, 0x36, 0x19, 0x0a, 0x25, 0x3b, 0x14, 0x1d, 0x32, 0x2c, 0x03, 0x10, 0x3f, 0x21, 0x0e,
0x33, 0x1c, 0x02, 0x2d, 0x3e, 0x11, 0x0f, 0x20, 0x29, 0x06, 0x18, 0x37, 0x24, 0x0b, 0x15, 0x3a,
0x0e, 0x21, 0x3f, 0x10, 0x03, 0x2c, 0x32, 0x1d, 0x14, 0x3b, 0x25, 0x0a, 0x19, 0x36, 0x28, 0x07,
0x3a, 0x15, 0x0b, 0x24, 0x37, 0x18, 0x06, 0x29, 0x20, 0x0f, 0x11, 0x3e, 0x2d, 0x02, 0x1c, 0x33,
0x09, 0x26, 0x38, 0x17, 0x04, 0x2b, 0x35, 0x1a, 0x13, 0x3c, 0x22, 0x0d, 0x1e, 0x31, 0x2f, 0x00,
0x3d, 0x12, 0x0c, 0x23, 0x30, 0x1f, 0x01, 0x2e, 0x27, 0x08, 0x16, 0x39, 0x2a, 0x05, 0x1b, 0x34,
0x1c, 0x33, 0x2d, 0x02, 0x11, 0x3e, 0x20, 0x0f, 0x06, 0x29, 0x37, 0x18, 0x0b, 0x24, 0x3a, 0x15,
0x28, 0x07, 0x19, 0x36, 0x25, 0x0a, 0x14, 0x3b, 0x32, 0x1d, 0x03, 0x2c, 0x3f, 0x10, 0x0e, 0x21,
0x1b, 0x34, 0x2a, 0x05, 0x16, 0x39, 0x27, 0x08, 0x01, 0x2e, 0x30, 0x1f, 0x0c, 0x23, 0x3d, 0x12,
0x2f, 0x00, 0x1e, 0x31, 0x22, 0x0d, 0x13, 0x3c, 0x35, 0x1a, 0x04, 0x2b, 0x38, 0x17, 0x09, 0x26,
0x12, 0x3d, 0x23, 0x0c, 0x1f, 0x30, 0x2e, 0x01, 0x08, 0x27, 0x39, 0x16, 0x05, 0x2a, 0x34, 0x1b,
0x26, 0x09, 0x17, 0x38, 0x2b, 0x04, 0x1a, 0x35, 0x3c, 0x13, 0x0d, 0x22, 0x31, 0x1e, 0x00, 0x2f,
0x15, 0x3a, 0x24, 0x0b, 0x18, 0x37, 0x29, 0x06, 0x0f, 0x20, 0x3e, 0x11, 0x02, 0x2d, 0x33, 0x1c,
0x21, 0x0e, 0x10, 0x3f, 0x2c, 0x03, 0x1d, 0x32, 0x3b, 0x14, 0x0a, 0x25, 0x36, 0x19, 0x07, 0x28
};
/**
* CRC6 is used by 3GPP (TS 25.415, TS 25.446) for header CRCs
* Poly: D^6 + D^5 + D^3 + D^2 + D^1 + 1
*
* TS 25.415 docs: https://www.etsi.org/deliver/etsi_ts/125400_125499/125415/04.06.00_60/ts_125415v040600p.pdf
* TS 25.446 docs: https://www.etsi.org/deliver/etsi_ts/125400_125499/125446/10.01.00_60/ts_125446v100100p.pdf
*/
uint16_t crc6_0X6F(uint16_t crc, const uint8_t *data, int data_len)
{
uint8_t tbl_idx;
while (data_len--)
{
tbl_idx = (crc << 2) ^ *data;
crc = crc6_table[tbl_idx] & 0x3f;
data++;
}
return crc & 0x3f;
}

26
datalink/defines.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef HAVE_DEFINES_H
#define HAVE_DEFINES_H
#define MAX_PATH 255
// {TX,RX}_SHM broadcast memory interface
#define SHM_PAYLOAD_BUFFER_SIZE 131072
#define SHM_PAYLOAD_NAME "/broadcast"
#define INT_BUFFER_SIZE 4096
// audio buffers shared memory interface
// 1536000 * 8
#define SIGNAL_BUFFER_SIZE 12288000
#define SIGNAL_INPUT "/signal-radio2modem"
#define SIGNAL_OUTPUT "/signal-modem2radio"
#if defined(_WIN32)
#define msleep(a) Sleep(a)
#else
#define msleep(a) usleep(a * 1000)
#endif
#endif // HAVE_DEFINES_H

76
datalink/fsm.c Normal file
View file

@ -0,0 +1,76 @@
/* HERMES Modem
*
* Copyright (C) 2025 Rhizomatica
* Author: Rafael Diniz <rafael@riseup.net>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include "fsm.h"
const char *fsm_event_names[] = {
"EV_CLIENT_CONNECT",
"EV_CLIENT_DISCONNECT",
"EV_START_LISTEN",
"EV_STOP_LISTEN",
"EV_LINK_CALL_REMOTE",
"EV_LINK_INCOMING_CALL",
"EV_LINK_DISCONNECT",
"EV_LINK_ESTABLISHMENT_TIMEOUT",
"EV_LINK_ESTABLISHED"
};
// Initialize the FSM
void fsm_init(fsm_handle* fsm, fsm_state initial_state)
{
printf("Initializing FSM\n");
if (!fsm)
return;
fsm->current = initial_state;
pthread_mutex_init(&fsm->lock, NULL);
}
// Dispatch an event (thread-safe)
void fsm_dispatch(fsm_handle* fsm, int event)
{
if (!fsm)
return;
printf("Dispatching event %s\n", fsm_event_names[event]);
pthread_mutex_lock(&fsm->lock);
if (fsm->current)
fsm->current(event); // Execute current state
pthread_mutex_unlock(&fsm->lock);
}
// Clean up resources
void fsm_destroy(fsm_handle* fsm)
{
if (!fsm)
return;
printf("Destroying FSM\n");
pthread_mutex_destroy(&fsm->lock);
fsm->current = NULL;
}

69
datalink/fsm.h Normal file
View file

@ -0,0 +1,69 @@
/* HERMES Modem
*
* Copyright (C) 2025 Rhizomatica
* Author: Rafael Diniz <rafael@riseup.net>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#ifndef FSM_H
#define FSM_H
#include <pthread.h>
/* ---- FSM Definitions ---- */
/* FSM Events */
// TNC TCP client event
#define EV_CLIENT_CONNECT 0
#define EV_CLIENT_DISCONNECT 1
#define EV_START_LISTEN 2
#define EV_STOP_LISTEN 3
#define EV_LINK_CALL_REMOTE 4
#define EV_LINK_INCOMING_CALL 5
#define EV_LINK_DISCONNECT 6
#define EV_LINK_ESTABLISHMENT_TIMEOUT 7
#define EV_LINK_ESTABLISHED 8
#ifdef __cplusplus
extern "C" {
#endif
extern const char* fsm_event_names[];
#ifdef __cplusplus
}
#endif
// State function pointer type
typedef void (*fsm_state)(int event);
// Thread-safe FSM structure
typedef struct {
fsm_state current;
pthread_mutex_t lock;
} fsm_handle;
// Public API
void fsm_init(fsm_handle* fsm, fsm_state initial_state);
void fsm_dispatch(fsm_handle* fsm, int event);
void fsm_destroy(fsm_handle* fsm);
#endif // FSM_H

View file

@ -32,10 +32,14 @@
#include "freedv_api.h"
#include "arq.h"
#include "defines.h"
#include "audioio/audioio.h"
extern cbuf_handle_t capture_buffer;
extern cbuf_handle_t playback_buffer;
int freedv_modes[] = { FREEDV_MODE_DATAC1,
FREEDV_MODE_DATAC3,
FREEDV_MODE_DATAC0,
@ -65,8 +69,8 @@ int main(int argc, char *argv[])
int cpu_nr = -1;
bool list_modes = false;
bool list_sndcards = false;
int base_tcp_port = 7002; // default ARQ TCP port
int broadcast_port = 7004; // default broadcast TCP port
int base_tcp_port = DEFAULT_ARQ_PORT; // default ARQ TCP port
int broadcast_port = DEFAULT_BROADCAST_PORT; // default broadcast TCP port
int audio_system = -1; // default audio system
char *input_dev = (char *) malloc(MAX_PATH);
char *output_dev = (char *) malloc(MAX_PATH);
@ -88,8 +92,8 @@ int main(int argc, char *argv[])
printf(" -i [device] Radio Capture device id (eg: \"plughw:0,0\").\n");
printf(" -o [device] Radio Playback device id (eg: \"plughw:0,0\").\n");
printf(" -x [sound_system] Sets the sound system or IO API to use: alsa, pulse, dsound, wasapi or shm. Default is alsa on Linux and dsound on Windows.\n");
printf(" -p [arq_tcp_base_port] Sets the ARQ TCP base port (control is base_port, data is base_port + 1). Default is 7002.\n");
printf(" -b [broadcast_tcp_port] Sets the broadcast TCP port. Default is 7004.\n");
printf(" -p [arq_tcp_base_port] Sets the ARQ TCP base port (control is base_port, data is base_port + 1). Default is 8300.\n");
printf(" -b [broadcast_tcp_port] Sets the broadcast TCP port. Default is 8100.\n");
printf(" -l Lists all modulator/coding modes.\n");
printf(" -z Lists all available sound cards.\n");
printf(" -v Verbose mode. Prints more information during execution.\n");
@ -297,7 +301,7 @@ int main(int argc, char *argv[])
default:
printf("Selected audio system not supported. Trying to continue.\n");
}
if (list_sndcards)
{
list_soundcards(audio_system);
@ -307,7 +311,51 @@ int main(int argc, char *argv[])
free(output_dev);
return EXIT_SUCCESS;
}
pthread_t radio_capture;
pthread_t radio_playback;
if (audio_system == AUDIO_SUBSYSTEM_SHM)
{
try_shm_connect1:
capture_buffer = circular_buf_connect_shm(SIGNAL_BUFFER_SIZE, SIGNAL_INPUT);
if (capture_buffer == NULL)
{
printf("Shared memory not created. Waiting for the radio daemon\n");
sleep(2);
goto try_shm_connect1;
}
try_shm_connect2:
playback_buffer = circular_buf_connect_shm(SIGNAL_BUFFER_SIZE, SIGNAL_OUTPUT);
if (playback_buffer == NULL)
{
printf("Shared memory not created. Waiting for the radio daemon...\n");
sleep(2);
goto try_shm_connect2;
}
printf("Connected to shared memory buffers.\n");
}
else
{
audioio_init_internal(input_dev, output_dev, audio_system, &radio_capture, &radio_playback);
}
arq_init(base_tcp_port, mod_config);
if (audio_system == AUDIO_SUBSYSTEM_SHM)
{
// test code
circular_buf_destroy_shm(capture_buffer, SIGNAL_BUFFER_SIZE, SIGNAL_INPUT);
circular_buf_destroy_shm(playback_buffer, SIGNAL_BUFFER_SIZE, SIGNAL_OUTPUT);
circular_buf_free_shm(capture_buffer);
circular_buf_free_shm(playback_buffer);
}
else{
audioio_deinit(&radio_capture, &radio_playback);
}
return 0;
}

206
datalink/net.c Normal file
View file

@ -0,0 +1,206 @@
/* HERMES Modem
*
* Copyright (C) 2025 Rhizomatica
* Author: Rafael Diniz <rafael@riseup.net>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Network related functions
*
*/
#include "net.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/ioctl.h>
static int ctl_sockfd, data_sockfd;
int cli_ctl_sockfd, cli_data_sockfd;
atomic_int status_ctl, status_data;
static pthread_mutex_t read_mutex[2] = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER };
static pthread_mutex_t write_mutex[2] = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER };
int listen4connection(int port_type)
{
socklen_t clilen;
int newsockfd = 0;
struct sockaddr_in cli_addr;
clilen = sizeof(cli_addr);
if (port_type == CTL_TCP_PORT)
newsockfd = accept(ctl_sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (port_type == DATA_TCP_PORT)
newsockfd = accept(data_sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
{
fprintf(stderr, "ERROR on accept");
return -1;
}
if (port_type == CTL_TCP_PORT)
{
cli_ctl_sockfd = newsockfd;
status_ctl = NET_CONNECTED;
}
if (port_type == DATA_TCP_PORT)
{
cli_data_sockfd = newsockfd;
status_data = NET_CONNECTED;
}
return newsockfd;
}
int tcp_open(int portno, int port_type)
{
int sockfd;
struct sockaddr_in serv_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0){
fprintf(stderr, "ERROR opening socket\n");
return -1;
}
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
{
fprintf(stderr, "setsockopt(SO_REUSEADDR) failed\n");
close(sockfd);
return -1;
}
memset((char *) &serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
{
fprintf(stderr, "ERROR on binding\n");
return -1;
}
listen(sockfd,1); // just 1 concurrent connections
if (port_type == CTL_TCP_PORT)
{
status_ctl = NET_LISTENING;
ctl_sockfd = sockfd;
}
if (port_type == DATA_TCP_PORT)
{
status_data = NET_LISTENING;
data_sockfd = sockfd;
}
return sockfd;
}
ssize_t tcp_read(int port_type, uint8_t *buffer, size_t rx_size)
{
ssize_t n = 0;
pthread_mutex_lock(&read_mutex[port_type]);
size_t count = 0;
if (port_type == CTL_TCP_PORT && status_ctl == NET_CONNECTED)
{
ioctl(cli_ctl_sockfd, FIONREAD, &count);
if (count < rx_size)
rx_size = count ? count : rx_size;
n = recv(cli_ctl_sockfd, buffer, rx_size, MSG_NOSIGNAL);
if (n < 0) status_ctl = NET_RESTART;
}
if (port_type == DATA_TCP_PORT && status_data == NET_CONNECTED)
{
ioctl(cli_data_sockfd, FIONREAD, &count);
if (count < rx_size)
rx_size = count ? count : rx_size;
n = recv(cli_data_sockfd, buffer, rx_size, MSG_NOSIGNAL);
if (n < 0) status_data = NET_RESTART;
}
pthread_mutex_unlock(&read_mutex[port_type]);
if (n < 0)
fprintf(stderr, "ERROR reading from socket\n");
return n;
}
ssize_t tcp_write(int port_type, uint8_t *buffer, size_t tx_size)
{
ssize_t n = 0;
pthread_mutex_lock(&write_mutex[port_type]);
if (port_type == CTL_TCP_PORT && status_ctl == NET_CONNECTED)
{
n = send(cli_ctl_sockfd, buffer, tx_size, MSG_NOSIGNAL);
if (n != (ssize_t) tx_size)
status_ctl = NET_RESTART;
}
if (port_type == DATA_TCP_PORT && status_data == NET_CONNECTED)
{
n = send(cli_data_sockfd, buffer, tx_size, MSG_NOSIGNAL);
if (n != (ssize_t) tx_size)
status_data = NET_RESTART;
}
pthread_mutex_unlock(&write_mutex[port_type]);
if (n != (ssize_t) tx_size)
fprintf(stderr, "ERROR writing to socket\n");
return n;
}
int tcp_close(int port_type)
{
if(port_type == CTL_TCP_PORT)
{
close(cli_ctl_sockfd);
close(ctl_sockfd);
}
if(port_type == DATA_TCP_PORT)
{
close(cli_data_sockfd);
close(data_sockfd);
}
status_ctl = NET_NONE;
status_data = NET_NONE;
return 0;
}

64
datalink/net.h Normal file
View file

@ -0,0 +1,64 @@
/* HERMES Modem
*
* Copyright (C) 2025 Rhizomatica
* Author: Rafael Diniz <rafael@riseup.net>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Network related functions
*
*/
/**
* @file net.h
* @author Rafael Diniz
* @date 01 Apr 2025
* @brief File containing network related functions
*
* For the sake of reusable and clean code, some network related functions.
*
*/
#ifndef NET_H__
#define NET_H__
#include <stdint.h>
#include <unistd.h>
#define CTL_TCP_PORT 0
#define DATA_TCP_PORT 1
#define NET_NONE 0
#define NET_LISTENING 1
#define NET_RESTART 2
#define NET_CONNECTED 3
#include <stdatomic.h>
extern int cli_ctl_sockfd, cli_data_sockfd;
extern atomic_int status_ctl, status_data;
int listen4connection(int port_type);
int tcp_open(int portno, int port_type);
ssize_t tcp_read(int port_type, uint8_t *buffer, size_t rx_size);
ssize_t tcp_write(int port_type, uint8_t *buffer, size_t tx_size);
int tcp_close(int port_type);
#endif // NET_H__