From c7c3f385a490ddad86a778b22dbdffd3be7dffab Mon Sep 17 00:00:00 2001 From: Rafael Diniz Date: Sat, 2 Aug 2025 01:05:03 +0100 Subject: [PATCH] adding initial arq code --- audioio/audioio.c | 38 +- audioio/audioio.h | 10 +- common/common_defines.h | 157 ------- datalink/Makefile | 21 +- datalink/arith.c | 382 +++++++++++++++++ datalink/arq.c | 904 ++++++++++++++++++++++++++++++++++++++++ datalink/arq.h | 110 +++++ datalink/crc6.c | 73 ++++ datalink/defines.h | 26 ++ datalink/fsm.c | 76 ++++ datalink/fsm.h | 69 +++ datalink/main.c | 60 ++- datalink/net.c | 206 +++++++++ datalink/net.h | 64 +++ 14 files changed, 2000 insertions(+), 196 deletions(-) delete mode 100644 common/common_defines.h create mode 100644 datalink/arith.c create mode 100644 datalink/arq.c create mode 100644 datalink/arq.h create mode 100644 datalink/crc6.c create mode 100644 datalink/defines.h create mode 100644 datalink/fsm.c create mode 100644 datalink/fsm.h create mode 100644 datalink/net.c create mode 100644 datalink/net.h diff --git a/audioio/audioio.c b/audioio/audioio.c index 05f9256..c80f70e 100644 --- a/audioio/audioio.c +++ b/audioio/audioio.c @@ -1,6 +1,6 @@ /* Audio subsystem * - * Copyright (C) 2024 Rhizomatica + * Copyright (C) 2024-2025 Rhizomatica * Author: Rafael Diniz * * 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; diff --git a/audioio/audioio.h b/audioio/audioio.h index d851509..0aa7144 100644 --- a/audioio/audioio.h +++ b/audioio/audioio.h @@ -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); diff --git a/common/common_defines.h b/common/common_defines.h deleted file mode 100644 index 1bee0f3..0000000 --- a/common/common_defines.h +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Mercury: A configurable open-source software-defined modem. - * Copyright (C) 2022-2024 Fadi Jerji - * Author: Fadi Jerji - * Email: fadi.jerji@ - * 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 . - * - */ - -#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_ diff --git a/datalink/Makefile b/datalink/Makefile index 84b3b8f..946004c 100644 --- a/datalink/Makefile +++ b/datalink/Makefile @@ -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 diff --git a/datalink/arith.c b/datalink/arith.c new file mode 100644 index 0000000..5db6929 --- /dev/null +++ b/datalink/arith.c @@ -0,0 +1,382 @@ +#include +#include +#include +#include + +#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 diff --git a/datalink/arq.c b/datalink/arq.c new file mode 100644 index 0000000..f3ee797 --- /dev/null +++ b/datalink/arq.c @@ -0,0 +1,904 @@ +/* HERMES Modem + * + * Copyright (C) 2025 Rhizomatica + * Author: Rafael Diniz + * + * 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 . + * + */ + +#include + + +#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; +} diff --git a/datalink/arq.h b/datalink/arq.h new file mode 100644 index 0000000..8b04c40 --- /dev/null +++ b/datalink/arq.h @@ -0,0 +1,110 @@ +/* HERMES Modem + * + * Copyright (C) 2025 Rhizomatica + * Author: Rafael Diniz + * + * 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 . + * + */ + +#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 +#include + +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 diff --git a/datalink/crc6.c b/datalink/crc6.c new file mode 100644 index 0000000..901f2c9 --- /dev/null +++ b/datalink/crc6.c @@ -0,0 +1,73 @@ +/* HERMES Modem + * + * Copyright (C) 2025 Rhizomatica + * Author: Rafael Diniz + * + * 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 . + * + */ + +#include + +/** + * 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; +} diff --git a/datalink/defines.h b/datalink/defines.h new file mode 100644 index 0000000..e4e07db --- /dev/null +++ b/datalink/defines.h @@ -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 diff --git a/datalink/fsm.c b/datalink/fsm.c new file mode 100644 index 0000000..940b29c --- /dev/null +++ b/datalink/fsm.c @@ -0,0 +1,76 @@ +/* HERMES Modem + * + * Copyright (C) 2025 Rhizomatica + * Author: Rafael Diniz + * + * 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 . + * + */ + +#include +#include +#include + +#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; +} diff --git a/datalink/fsm.h b/datalink/fsm.h new file mode 100644 index 0000000..93931e4 --- /dev/null +++ b/datalink/fsm.h @@ -0,0 +1,69 @@ +/* HERMES Modem + * + * Copyright (C) 2025 Rhizomatica + * Author: Rafael Diniz + * + * 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 . + * + */ + +#ifndef FSM_H +#define FSM_H + +#include + +/* ---- 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 diff --git a/datalink/main.c b/datalink/main.c index 1000b13..774ad61 100644 --- a/datalink/main.c +++ b/datalink/main.c @@ -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; } diff --git a/datalink/net.c b/datalink/net.c new file mode 100644 index 0000000..3c47e09 --- /dev/null +++ b/datalink/net.c @@ -0,0 +1,206 @@ +/* HERMES Modem + * + * Copyright (C) 2025 Rhizomatica + * Author: Rafael Diniz + * + * 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 . + * + * Network related functions + * + */ + +#include "net.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +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; +} diff --git a/datalink/net.h b/datalink/net.h new file mode 100644 index 0000000..112bf9e --- /dev/null +++ b/datalink/net.h @@ -0,0 +1,64 @@ +/* HERMES Modem + * + * Copyright (C) 2025 Rhizomatica + * Author: Rafael Diniz + * + * 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 . + * + * 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 +#include + +#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 + +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__