From 53e9ff7908621307cd9d46d6f54f5a1e06102ff7 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Fri, 22 Oct 2021 17:29:20 -0400 Subject: [PATCH] Add IL2P. --- CHANGES.md | 2 + README.md | 6 +- man/direwolf.1 | 10 +- man/gen_packets.1 | 12 + src/CMakeLists.txt | 47 ++ src/atest.c | 6 +- src/audio.h | 20 +- src/ax25_pad.c | 4 +- src/config.c | 60 +- src/direwolf.c | 86 ++- src/gen_packets.c | 73 +- src/gen_tone.c | 9 +- src/hdlc_rec.c | 2 + src/hdlc_send.c | 136 ++-- src/hdlc_send.h | 11 +- src/il2p.h | 145 ++++ src/il2p.h-bak16 | 150 ++++ src/il2p_codec.c | 263 +++++++ src/il2p_codec.c-bak16 | 265 +++++++ src/il2p_header.c | 673 +++++++++++++++++ src/il2p_init.c | 226 ++++++ src/il2p_payload.c | 298 ++++++++ src/il2p_payload.c-bak16 | 317 ++++++++ src/il2p_rec.c | 276 +++++++ src/il2p_scramble.c | 157 ++++ src/il2p_scramble.c-bak16 | 194 +++++ src/il2p_send.c | 147 ++++ src/il2p_test.c | 977 ++++++++++++++++++++++++ src/il2p_test.c-bak16 | 1030 +++++++++++++++++++++++++ src/multi_modem.c | 14 +- src/multi_modem.h | 3 + src/tnctest.c | 1284 ++++++++++++++++++++++++++++++++ src/xmit.c | 15 +- test/CMakeLists.txt | 103 ++- test/scripts/check-il2p | 4 + test/scripts/check-modem1200-i | 4 + 36 files changed, 6888 insertions(+), 141 deletions(-) create mode 100644 src/il2p.h create mode 100644 src/il2p.h-bak16 create mode 100644 src/il2p_codec.c create mode 100644 src/il2p_codec.c-bak16 create mode 100644 src/il2p_header.c create mode 100644 src/il2p_init.c create mode 100644 src/il2p_payload.c create mode 100644 src/il2p_payload.c-bak16 create mode 100644 src/il2p_rec.c create mode 100644 src/il2p_scramble.c create mode 100644 src/il2p_scramble.c-bak16 create mode 100644 src/il2p_send.c create mode 100644 src/il2p_test.c create mode 100644 src/il2p_test.c-bak16 create mode 100644 src/tnctest.c create mode 100644 test/scripts/check-il2p create mode 100644 test/scripts/check-modem1200-i diff --git a/CHANGES.md b/CHANGES.md index 19ef887..49dc22a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ ### New Features: ### +- Improved Layer 2 Protocol [(IL2P)](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction). Use "-I 1" to enable transmit for first channel. + - Limited support for CM109/CM119 GPIO PTT on Windows. - Dire Wolf now advertises itself using DNS Service Discovery. This allows suitable APRS / Packet Radio applications to find a network KISS TNC without knowing the IP address or TCP port. Thanks to Hessu for providing this. Currently available only for Linux and Mac OSX. [Read all about it here.](https://github.com/hessu/aprs-specs/blob/master/TCP-KISS-DNS-SD.md) diff --git a/README.md b/README.md index c7505c3..fc08c69 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,11 @@ Dire Wolf now includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error ![](fx25.png) -Dire Wolf is a modern software replacement for the old 1980's style TNC built with special hardware. +Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead. + + + +### Dire Wolf is a modern software replacement for the old 1980's style TNC built with special hardware. ### Without any additional software, it can perform as: diff --git a/man/direwolf.1 b/man/direwolf.1 index f619aa8..93f786d 100644 --- a/man/direwolf.1 +++ b/man/direwolf.1 @@ -87,7 +87,15 @@ Divide audio sample by n for first channel. .TP .BI "-X " "n" -1 to enable FX.25 transmit. +1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes. + +.TP +.BI "-I " "n" +Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC. + +.TP +.BI "-i " "n" +Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC. .TP .BI "-d " "x" diff --git a/man/gen_packets.1 b/man/gen_packets.1 index 773a88e..a404288 100644 --- a/man/gen_packets.1 +++ b/man/gen_packets.1 @@ -62,6 +62,18 @@ Force G3RUH modem regardless of data rate. .BI "-J " 2400 bps QPSK compatible with MFJ-2400. +.TP +.BI "-X " "n" +1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes. + +.TP +.BI "-I " "n" +Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC. + +.TP +.BI "-i " "n" +Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC. + .TP .BI "-m " "n" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9fe8306..a2c3963 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,13 @@ list(APPEND direwolf_SOURCES hdlc_rec2.c hdlc_send.c igate.c + il2p_codec.c + il2p_scramble.c + il2p_rec.c + il2p_payload.c + il2p_init.c + il2p_header.c + il2p_send.c kiss_frame.c kiss.c kissserial.c @@ -289,12 +296,20 @@ target_link_libraries(log2gpx list(APPEND gen_packets_SOURCES gen_packets.c ax25_pad.c + ax25_pad2.c fx25_encode.c + fx25_extract.c fx25_init.c fx25_send.c hdlc_send.c fcs_calc.c gen_tone.c + il2p_codec.c + il2p_scramble.c + il2p_payload.c + il2p_init.c + il2p_header.c + il2p_send.c morse.c dtmf.c textcolor.c @@ -321,14 +336,22 @@ list(APPEND atest_SOURCES demod_9600.c dsp.c fx25_extract.c + fx25_encode.c fx25_init.c fx25_rec.c hdlc_rec.c hdlc_rec2.c + il2p_codec.c + il2p_scramble.c + il2p_rec.c + il2p_payload.c + il2p_init.c + il2p_header.c multi_modem.c rrbb.c fcs_calc.c ax25_pad.c + ax25_pad2.c decode_aprs.c dwgpsnmea.c dwgps.c @@ -421,6 +444,29 @@ if(WIN32 OR CYGWIN) endif() +# TNC interoperability testing for AX.25 connected mode. +# tnctest +list(APPEND tnctest_SOURCES + tnctest.c + textcolor.c + dtime_now.c + serial_port.c + ) + +add_executable(tnctest + ${tnctest_SOURCES} + ) + +target_link_libraries(tnctest + ${MISC_LIBRARIES} + Threads::Threads + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(tnctest ws2_32) +endif() + + # List USB audio adapters than can use GPIO for PTT. # Originally for Linux only (using udev). # Version 1.7 adds it for Windows. Needs hidapi library. @@ -520,6 +566,7 @@ install(TARGETS gen_packets DESTINATION ${INSTALL_BIN_DIR}) install(TARGETS atest DESTINATION ${INSTALL_BIN_DIR}) install(TARGETS ttcalc DESTINATION ${INSTALL_BIN_DIR}) install(TARGETS kissutil DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS tnctest DESTINATION ${INSTALL_BIN_DIR}) install(TARGETS appserver DESTINATION ${INSTALL_BIN_DIR}) if(UDEV_FOUND OR WIN32 OR CYGWIN) install(TARGETS cm108 DESTINATION ${INSTALL_BIN_DIR}) diff --git a/src/atest.c b/src/atest.c index d07ec2e..caea4bc 100644 --- a/src/atest.c +++ b/src/atest.c @@ -2,7 +2,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2021 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -82,6 +82,7 @@ #include "ptt.h" #include "dtime_now.h" #include "fx25.h" +#include "il2p.h" #include "hdlc_rec.h" @@ -189,6 +190,7 @@ static int h_opt = 0; // Hexadecimal display of received packet. static char P_opt[16] = ""; // Demodulator profiles. static int d_x_opt = 1; // FX.25 debug. static int d_o_opt = 0; // "-d o" option for DCD output control. */ +static int d_2_opt = 0; // "-d 2" option for IL2P details. */ static int dcd_count = 0; static int dcd_missing_errors = 0; @@ -389,6 +391,7 @@ int main (int argc, char *argv[]) switch (*p) { case 'x': d_x_opt++; break; // FX.25 case 'o': d_o_opt++; break; // DCD output control + case '2': d_2_opt++; break; // IL2P debug out default: break; } } @@ -539,6 +542,7 @@ int main (int argc, char *argv[]) } fx25_init (d_x_opt); + il2p_init (d_2_opt); start_time = dtime_now(); diff --git a/src/audio.h b/src/audio.h index 1a4f0eb..f112f76 100644 --- a/src/audio.h +++ b/src/audio.h @@ -107,10 +107,11 @@ struct audio_s { float recv_ber; /* Receive Bit Error Rate (BER). */ /* Probability of inverting a bit coming out of the modem. */ - int fx25_xmit_enable; /* Enable transmission of FX.25. */ + //int fx25_xmit_enable; /* Enable transmission of FX.25. */ /* See fx25_init.c for explanation of values. */ /* Initially this applies to all channels. */ /* This should probably be per channel. One step at a time. */ + /* v1.7 - replaced by layer2_xmit==LAYER2_FX25 */ int fx25_auto_enable; /* Turn on FX.25 for current connected mode session */ /* under poor conditions. */ @@ -156,6 +157,23 @@ struct audio_s { /* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */ /* No modem. Might want this for DTMF only channel. */ + enum layer2_t { LAYER2_AX25 = 0, LAYER2_FX25, LAYER2_IL2P } layer2_xmit; + + // IL2P - New for version 1.7. + // New layer 2 with FEC. Much less overhead than FX.25 but no longer backward compatible. + // Only applies to transmit. + // Listening for FEC sync word should add negligible overhead so + // we leave reception enabled all the time as we do with FX.25. + // TODO: FX.25 should probably be put here rather than global for all channels. + + int fx25_strength; // Strength of FX.25 FEC. + // 16, 23, 64 for specific number of parity symbols. + // 1 for automatic selection based on frame size. + + int il2p_max_fec; // 1 for max FEC length, 0 for automatic based on size. + + int il2p_invert_polarity; // 1 means invert on transmit. Receive handles either automatically. + enum v26_e { V26_UNSPECIFIED=0, V26_A, V26_B } v26_alternative; // Original implementation used alternative A for 2400 bbps PSK. diff --git a/src/ax25_pad.c b/src/ax25_pad.c index 7039d9f..fe9368e 100644 --- a/src/ax25_pad.c +++ b/src/ax25_pad.c @@ -2751,6 +2751,7 @@ unsigned short ax25_m_m_crc (packet_t pp) unsigned char fbuf[AX25_MAX_PACKET_LEN]; int flen; + // TODO: I think this can be more efficient by getting the packet content pointer instead of copying. flen = ax25_pack (pp, fbuf); crc = 0xffff; @@ -2803,7 +2804,8 @@ unsigned short ax25_m_m_crc (packet_t pp) * *------------------------------------------------------------------*/ -#define MAXSAFE 500 +//#define MAXSAFE 500 +#define MAXSAFE AX25_MAX_INFO_LEN void ax25_safe_print (char *pstr, int len, int ascii_only) { diff --git a/src/config.c b/src/config.c index 32b2665..527e832 100644 --- a/src/config.c +++ b/src/config.c @@ -585,7 +585,7 @@ static int check_via_path (char *via_path) * *--------------------------------------------------------------------*/ -#define MAXCMDLEN 256 +#define MAXCMDLEN 1200 static char *split (char *string, int rest_of_line) @@ -770,6 +770,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_audio_config->achan[channel].num_freq = 1; p_audio_config->achan[channel].offset = 0; + p_audio_config->achan[channel].layer2_xmit = LAYER2_AX25; + p_audio_config->achan[channel].il2p_max_fec = 1; + p_audio_config->achan[channel].il2p_invert_polarity = 0; + p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS; p_audio_config->achan[channel].sanity_test = SANITY_APRS; p_audio_config->achan[channel].passall = 0; @@ -2252,7 +2256,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, * 0 = off, 1 = auto mode, others are suggestions for testing * or special cases. 16, 32, 64 is number of parity bytes to add. * Also set by "-X n" command line option. - * Current a global setting. Could be per channel someday. + * V1.7 changed from global to per-channel setting. */ else if (strcasecmp(t, "FX25TX") == 0) { @@ -2265,13 +2269,15 @@ void config_init (char *fname, struct audio_s *p_audio_config, } n = atoi(t); if (n >= 0 && n < 200) { - p_audio_config->fx25_xmit_enable = n; + p_audio_config->achan[channel].fx25_strength = n; + p_audio_config->achan[channel].layer2_xmit = LAYER2_FX25; } else { - p_audio_config->fx25_xmit_enable = 1; + p_audio_config->achan[channel].fx25_strength = 1; + p_audio_config->achan[channel].layer2_xmit = LAYER2_FX25; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable value for FX.25 transmission mode. Using %d.\n", - line, p_audio_config->fx25_xmit_enable); + line, p_audio_config->achan[channel].fx25_strength); } } @@ -2304,6 +2310,48 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } +/* + * IL2PTX [ + - ] [ 0 1 ] - Enable IL2P transmission. Default off. + * "+" means normal polarity. Redundant since it is the default. + * (command line -I for first channel) + * "-" means inverted polarity. Do not use for 1200 bps. + * (command line -i for first channel) + * "0" means weak FEC. Not recommended. + * "1" means stronger FEC. "Max FEC." Default if not specified. + */ + + else if (strcasecmp(t, "IL2PTX") == 0) { + + p_audio_config->achan[channel].layer2_xmit = LAYER2_IL2P; + p_audio_config->achan[channel].il2p_max_fec = 1; + p_audio_config->achan[channel].il2p_invert_polarity = 0; + + while ((t = split(NULL,0)) != NULL) { + for (char *c = t; *t != '\0'; c++) { + switch (*c) { + case '+': + p_audio_config->achan[channel].il2p_invert_polarity = 0; + break; + case '-': + p_audio_config->achan[channel].il2p_invert_polarity = 1; + break; + case '0': + p_audio_config->achan[channel].il2p_max_fec = 0; + break; + case '1': + p_audio_config->achan[channel].il2p_max_fec = 1; + break; + default: + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid parameter '%c' fol IL2PTX command.\n", line, *c); + continue; + break; + } + } + } + } + + /* * ==================== APRS Digipeater parameters ==================== */ @@ -5399,7 +5447,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ while ((t = split(NULL,0)) != NULL) { char keyword[20]; - char value[200]; + char value[1000]; char *e; char *p; diff --git a/src/direwolf.c b/src/direwolf.c index 61bc401..76247f5 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -33,7 +33,9 @@ * Internet Gateway (IGate) * Ham Radio of Things - IoT with Ham Radio * FX.25 Forward Error Correction. - * + * IL2P Forward Error Correction. + * Emergency Alert System (EAS) Specific Area Message Encoding (SAME) receiver. + * AIS receiver for tracking ships. * *---------------------------------------------------------------*/ @@ -123,6 +125,7 @@ #include "ax25_link.h" #include "dtime_now.h" #include "fx25.h" +#include "il2p.h" #include "dwsock.h" #include "dns_sd_dw.h" @@ -222,6 +225,8 @@ int main (int argc, char *argv[]) int d_h_opt = 0; /* "-d h" option for hamlib debugging. Repeat for more detail */ #endif int d_x_opt = 1; /* "-d x" option for FX.25. Default minimal. Repeat for more detail. -qx to silence. */ + int d_2_opt = 0; /* "-d 2" option for IL2P. Default minimal. Repeat for more detail. */ + int aprstt_debug = 0; /* "-d d" option for APRStt (think Dtmf) debug. */ int E_tx_opt = 0; /* "-E n" Error rate % for clobbering transmit frames. */ @@ -230,6 +235,9 @@ int main (int argc, char *argv[]) float e_recv_ber = 0.0; /* Receive Bit Error Rate (BER). */ int X_fx25_xmit_enable = 0; /* FX.25 transmit enable. */ + int I_opt = -1; /* IL2P transmit, normal polarity, arg is max_fec. */ + int i_opt = -1; /* IL2P transmit, inverted polarity, arg is max_fec. */ + char x_opt_mode = ' '; /* "-x N" option for transmitting calibration tones. */ int x_opt_chan = 0; /* Split into 2 parts. Mode e.g. m, a, and optional channel. */ @@ -291,7 +299,7 @@ int main (int argc, char *argv[]) text_color_init(t_opt); text_color_set(DW_COLOR_INFO); //dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "A", __DATE__); + dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "B", __DATE__); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); @@ -363,7 +371,20 @@ int main (int argc, char *argv[]) text_color_set(DW_COLOR_INFO); #endif +// I've seen many references to people running this as root. +// There is no reason to do that. +// There is for some privileges to access the audio system, GPIO (if needed for PTT), +// etc. but ordinary users have those abilities. +// Giving an applications permission to do things it does not need to do +// is a huge security risk. +#ifndef __WIN32__ + if (getuid() == 0 || geteuid() == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Dire Wolf requires only privileges available to ordinary users.\n"); + dw_printf ("Running this as root is an unnecssary security risk.\n"); + } +#endif /* * Default location of configuration file is current directory. @@ -393,7 +414,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "hP:B:gjJD:U:c:px:r:b:n:d:q:t:ul:L:Sa:E:T:e:X:A", + c = getopt_long(argc, argv, "hP:B:gjJD:U:c:px:r:b:n:d:q:t:ul:L:Sa:E:T:e:X:AI:i:", long_options, &option_index); if (c == -1) break; @@ -606,6 +627,7 @@ int main (int argc, char *argv[]) case 'h': d_h_opt++; break; // Hamlib verbose level. #endif case 'x': d_x_opt++; break; // FX.25 + case '2': d_2_opt++; break; // IL2P case 'd': aprstt_debug++; break; // APRStt (mnemonic Dtmf) default: break; } @@ -694,6 +716,16 @@ int main (int argc, char *argv[]) X_fx25_xmit_enable = atoi(optarg); break; + case 'I': // IL2P, normal polarity + + I_opt = atoi(optarg); + break; + + case 'i': // IL2P, inverted polarity + + i_opt = atoi(optarg); + break; + case 'A': // -A convert AIS to APRS object A_opt_ais_to_obj = 1; @@ -894,7 +926,45 @@ int main (int argc, char *argv[]) audio_config.recv_ber = e_recv_ber; - audio_config.fx25_xmit_enable = X_fx25_xmit_enable; + if (X_fx25_xmit_enable > 0) { + if (I_opt != -1 || i_opt != -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't mix -X with -I or -i.\n"); + exit (EXIT_FAILURE); + } + audio_config.achan[0].fx25_strength = X_fx25_xmit_enable; + audio_config.achan[0].layer2_xmit = LAYER2_FX25; + } + + if (I_opt != -1 && i_opt != -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't use both -I and -i at the same time.\n"); + exit (EXIT_FAILURE); + } + + if (I_opt >= 0) { + audio_config.achan[0].layer2_xmit = LAYER2_IL2P; + audio_config.achan[0].il2p_max_fec = (I_opt > 0); + if (audio_config.achan[0].il2p_max_fec == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("It is highly recommended that 1, rather than 0, is used with -I for best results.\n"); + } + audio_config.achan[0].il2p_invert_polarity = 0; // normal + } + + if (i_opt >= 0) { + audio_config.achan[0].layer2_xmit = LAYER2_IL2P; + audio_config.achan[0].il2p_max_fec = (i_opt > 0); + if (audio_config.achan[0].il2p_max_fec == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("It is highly recommended that 1, rather than 0, is used with -i for best results.\n"); + } + audio_config.achan[0].il2p_invert_polarity = 1; // invert for transmit + if (audio_config.achan[0].baud == 1200) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Using -i with 1200 bps is a bad idea. Use -I instead.\n"); + } + } /* @@ -915,10 +985,11 @@ int main (int argc, char *argv[]) } /* - * Initialize the demodulator(s) and HDLC decoder. + * Initialize the demodulator(s) and layer 2 decoder (HDLC, IL2P). */ multi_modem_init (&audio_config); fx25_init (d_x_opt); + il2p_init (d_2_opt); /* * Initialize the touch tone decoder & APRStt gateway. @@ -1573,7 +1644,9 @@ static void usage (char **argv) dw_printf (" -P xxx Modem Profiles.\n"); dw_printf (" -A Convert AIS positions to APRS Object Reports.\n"); dw_printf (" -D n Divide audio sample rate by n for channel 0.\n"); - dw_printf (" -X n 1 to enable FX.25 transmit.\n"); + dw_printf (" -X n 1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes.\n"); + dw_printf (" -I n Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC.\n"); + dw_printf (" -i n Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC.\n"); dw_printf (" -d Debug options:\n"); dw_printf (" a a = AGWPE network protocol client.\n"); dw_printf (" k k = KISS serial port or pseudo terminal client.\n"); @@ -1591,6 +1664,7 @@ static void usage (char **argv) dw_printf (" h h = hamlib increase verbose level.\n"); #endif dw_printf (" x x = FX.25 increase verbose level.\n"); + dw_printf (" 2 2 = IL2P.\n"); dw_printf (" d d = APRStt (DTMF to APRS object translation).\n"); dw_printf (" -q Quiet (suppress output) options:\n"); dw_printf (" h h = Heard line with the audio level.\n"); diff --git a/src/gen_packets.c b/src/gen_packets.c index b097790..7f7f3b4 100644 --- a/src/gen_packets.c +++ b/src/gen_packets.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019, 2021 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -76,6 +76,7 @@ #include "morse.h" #include "dtmf.h" #include "fx25.h" +#include "il2p.h" /* Own random number generator so we can get */ @@ -123,6 +124,7 @@ static void send_packet (char *str) return; } flen = ax25_pack (pp, fbuf); + (void)flen; for (c=0; c 0) { + if (I_opt != -1 || i_opt != -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't mix -X with -I or -i.\n"); + exit (EXIT_FAILURE); + } + modem.achan[0].fx25_strength = X_opt; + modem.achan[0].layer2_xmit = LAYER2_FX25; + } + + if (I_opt != -1 && i_opt != -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't use both -I and -i at the same time.\n"); + exit (EXIT_FAILURE); + } + + if (I_opt >= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Using IL2P normal polarity.\n"); + modem.achan[0].layer2_xmit = LAYER2_IL2P; + modem.achan[0].il2p_max_fec = (I_opt > 0); + modem.achan[0].il2p_invert_polarity = 0; // normal + } + + if (i_opt >= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Using IL2P inverted polarity.\n"); + modem.achan[0].layer2_xmit = LAYER2_IL2P; + modem.achan[0].il2p_max_fec = (i_opt > 0); + modem.achan[0].il2p_invert_polarity = 1; // invert for transmit + if (modem.achan[0].baud == 1200) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Using -i with 1200 bps is a bad idea. Use -I instead.\n"); + } + } + + /* * Open the output file. */ @@ -536,6 +586,7 @@ int main(int argc, char **argv) // Just use the default of minimal information. fx25_init (1); + il2p_init (0); // There are no "-d" options so far but it could be handy here. assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16); assert (modem.adev[0].num_channels == 1 || modem.adev[0].num_channels == 2); @@ -669,7 +720,9 @@ static void usage (char **argv) dw_printf (" -g Scrambled baseband rather than AFSK.\n"); dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n"); dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n"); - dw_printf (" -X n Generate FX.25 frames. Specify number of check bytes: 16, 32, or 64.\n"); + dw_printf (" -X n 1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes.\n"); + dw_printf (" -I n Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC.\n"); + dw_printf (" -i n Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC.\n"); dw_printf (" -m Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ); dw_printf (" -s Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ); dw_printf (" -r Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC); diff --git a/src/gen_tone.c b/src/gen_tone.c index 68f72bc..a0caedc 100644 --- a/src/gen_tone.c +++ b/src/gen_tone.c @@ -380,7 +380,14 @@ void tone_gen_put_bit (int chan, int dat) text_color_set(DW_COLOR_DEBUG); dw_printf ("tone_gen_put_bit %d AFSK\n", __LINE__); #endif - tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan]; + + // v1.7 reversed. + // Previously a data '1' selected the second (usually higher) tone. + // It never really mattered before because we were using NRZI. + // With the addition of IL2P, we need to be more careful. + // A data '1' should be the mark tone. + + tone_phase[chan] += dat ? f1_change_per_sample[chan] : f2_change_per_sample[chan]; sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; gen_tone_put_sample (chan, a, sam); break; diff --git a/src/hdlc_rec.c b/src/hdlc_rec.c index 9a84d3c..619d308 100644 --- a/src/hdlc_rec.c +++ b/src/hdlc_rec.c @@ -46,6 +46,7 @@ #include "demod_9600.h" /* for descramble() */ #include "ptt.h" #include "fx25.h" +#include "il2p.h" //#define TEST 1 /* Define for unit testing. */ @@ -496,6 +497,7 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, if (g_audio_p->achan[chan].modem_type != MODEM_AIS) { fx25_rec_bit (chan, subchan, slice, dbit); + il2p_rec_bit (chan, subchan, slice, raw); // Note: skip NRZI. } /* diff --git a/src/hdlc_send.c b/src/hdlc_send.c index 2d170f6..0818d0d 100644 --- a/src/hdlc_send.c +++ b/src/hdlc_send.c @@ -2,7 +2,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2013, 2014, 2019 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 2019, 2021 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,37 +27,37 @@ #include "gen_tone.h" #include "textcolor.h" #include "fcs_calc.h" +#include "ax25_pad.h" #include "fx25.h" +#include "il2p.h" -static void send_control (int, int); -static void send_data (int, int); -static void send_bit (int, int); +static void send_byte_msb_first (int chan, int x, int polarity); + +static void send_control_nrzi (int, int); +static void send_data_nrzi (int, int); +static void send_bit_nrzi (int, int); -static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags" - - +static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags" /*------------------------------------------------------------- * - * Name: hdlc_send + * Name: layer2_send_frame * - * Purpose: Convert HDLC frames to a stream of bits. + * Purpose: Convert frames to a stream of bits. + * Originally this was for AX.25 only, hence the file name. + * Over time, FX.25 and IL2P were shoehorned in. * * Inputs: chan - Audio channel number, 0 = first. * - * fbuf - Frame buffer address. - * - * flen - Frame length, not including the FCS. + * pp - Packet object. * * bad_fcs - Append an invalid FCS for testing purposes. * Applies only to regular AX.25. * - * fx25_xmit_enable - Just like the name says. - * * Outputs: Bits are shipped out by calling tone_gen_put_bit(). * * Returns: Number of bits sent including "flags" and the @@ -65,12 +65,12 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl * The required time can be calculated by dividing this * number by the transmit rate of bits/sec. * - * Description: Convert to stream of bits including: + * Description: For AX.25, send: * start flag * bit stuffed data * calculated FCS * end flag - * NRZI encoding + * NRZI encoding for all but the "flags." * * * Assumptions: It is assumed that the tone_gen module has been @@ -81,23 +81,40 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs); -// New in 1.6: Option to encapsulate in FX.25. -int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs, int fx25_xmit_enable) +int layer2_send_frame (int chan, packet_t pp, int bad_fcs, struct audio_s *audio_config_p) { - if (fx25_xmit_enable) { - int n = fx25_send_frame (chan, fbuf, flen, fx25_xmit_enable); + if (audio_config_p->achan[chan].layer2_xmit == LAYER2_IL2P) { + + int n = il2p_send_frame (chan, pp, audio_config_p->achan[chan].il2p_max_fec, + audio_config_p->achan[chan].il2p_invert_polarity); + if (n > 0) { + return (n); + } + text_color_set(DW_COLOR_ERROR); + dw_printf ("Unable to send IL2p frame. Falling back to regular AX.25.\n"); + // Not sure if we should fall back to AX.25 or not here. + } + else if (audio_config_p->achan[chan].layer2_xmit == LAYER2_FX25) { + unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; + int flen = ax25_pack (pp, fbuf); + int n = fx25_send_frame (chan, fbuf, flen, audio_config_p->achan[chan].fx25_strength); if (n > 0) { return (n); } text_color_set(DW_COLOR_ERROR); dw_printf ("Unable to send FX.25. Falling back to regular AX.25.\n"); + // Definitely need to fall back to AX.25 here because + // the FX.25 frame length is so limited. } + unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; + int flen = ax25_pack (pp, fbuf); return (ax25_only_hdlc_send_frame (chan, fbuf, flen, bad_fcs)); } + static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs) { int j, fcs; @@ -105,33 +122,31 @@ static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, i number_of_bits_sent[chan] = 0; - #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs); fflush (stdout); #endif - - send_control (chan, 0x7e); /* Start frame */ + send_control_nrzi (chan, 0x7e); /* Start frame */ for (j=0; j> 8) & 0xff); + send_data_nrzi (chan, (~fcs) & 0xff); + send_data_nrzi (chan, ((~fcs) >> 8) & 0xff); } else { - send_data (chan, fcs & 0xff); - send_data (chan, (fcs >> 8) & 0xff); + send_data_nrzi (chan, fcs & 0xff); + send_data_nrzi (chan, (fcs >> 8) & 0xff); } - send_control (chan, 0x7e); /* End frame */ + send_control_nrzi (chan, 0x7e); /* End frame */ return (number_of_bits_sent[chan]); } @@ -139,22 +154,25 @@ static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, i /*------------------------------------------------------------- * - * Name: hdlc_send_flags + * Name: layer2_preamble_postamble * - * Purpose: Send HDLC flags before and after the frame. + * Purpose: Send filler pattern before and after the frame. + * For HDLC it is 01111110, for IL2P 01010101. * * Inputs: chan - Audio channel number, 0 = first. * - * nflags - Number of flag patterns to send. + * nbytes - Number of bytes to send. * * finish - True for end of transmission. * This causes the last audio buffer to be flushed. * + * audio_config_p - Configuration for audio and modems. + * * Outputs: Bits are shipped out by calling tone_gen_put_bit(). * * Returns: Number of bits sent. * There is no bit-stuffing so we would expect this to - * be 8 * nflags. + * be 8 * nbytes. * The required time can be calculated by dividing this * number by the transmit rate of bits/sec. * @@ -164,25 +182,30 @@ static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, i * *--------------------------------------------------------------*/ -int hdlc_send_flags (int chan, int nflags, int finish) +int layer2_preamble_postamble (int chan, int nbytes, int finish, struct audio_s *audio_config_p) { int j; - number_of_bits_sent[chan] = 0; - #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("hdlc_send_flags ( chan = %d, nflags = %d, finish = %d )\n", chan, nflags, finish); fflush (stdout); #endif - /* The AX.25 spec states that when the transmitter is on but not sending data */ - /* it should send a continuous stream of "flags." */ + // When the transmitter is on but not sending data, it should be sending + // a stream of a filler pattern. + // For AX.25, it is the 01111110 "flag" pattern with NRZI and no bit stuffing. + // For IL2P, it is 01010101 without NRZI. - for (j=0; jachan[chan].layer2_xmit == LAYER2_IL2P) { + send_byte_msb_first (chan, IL2P_PREAMBLE, audio_config_p->achan[chan].il2p_invert_polarity); + } + else { + send_control_nrzi (chan, 0x7e); + } } /* Push out the final partial buffer! */ @@ -196,33 +219,54 @@ int hdlc_send_flags (int chan, int nflags, int finish) +// The next one is only for IL2P. No NRZI. +// MSB first, opposite of AX.25. + +static void send_byte_msb_first (int chan, int x, int polarity) +{ + int i; + + for (i=0; i<8; i++) { + int dbit = (x & 0x80) != 0; + tone_gen_put_bit (chan, (dbit ^ polarity) & 1); + x <<= 1; + number_of_bits_sent[chan]++; + } +} + + +// The following are only for HDLC. +// All bits are sent NRZI. +// Data (non flags) use bit stuffing. + + static int stuff[MAX_CHANS]; // Count number of "1" bits to keep track of when we // need to break up a long run by "bit stuffing." // Needs to be array because we could be transmitting // on multiple channels at the same time. -static void send_control (int chan, int x) +static void send_control_nrzi (int chan, int x) { int i; for (i=0; i<8; i++) { - send_bit (chan, x & 1); + send_bit_nrzi (chan, x & 1); x >>= 1; } stuff[chan] = 0; } -static void send_data (int chan, int x) +static void send_data_nrzi (int chan, int x) { int i; for (i=0; i<8; i++) { - send_bit (chan, x & 1); + send_bit_nrzi (chan, x & 1); if (x & 1) { stuff[chan]++; if (stuff[chan] == 5) { - send_bit (chan, 0); + send_bit_nrzi (chan, 0); stuff[chan] = 0; } } else { @@ -238,7 +282,7 @@ static void send_data (int chan, int x) * data 0 bit -> invert signal. */ -static void send_bit (int chan, int b) +static void send_bit_nrzi (int chan, int b) { static int output[MAX_CHANS]; diff --git a/src/hdlc_send.h b/src/hdlc_send.h index 4ebbcc2..4f8a105 100644 --- a/src/hdlc_send.h +++ b/src/hdlc_send.h @@ -1,9 +1,16 @@ /* hdlc_send.h */ -int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs, int fx25_xmit_enable); +// In version 1.7 an extra layer of abstraction was added here. +// Rather than calling hdlc_send_frame, we now use another function +// which sends AX.25, FX.25, or IL2P depending on -int hdlc_send_flags (int chan, int flags, int finish); +#include "ax25_pad.h" +#include "audio.h" + +int layer2_send_frame (int chan, packet_t pp, int bad_fcs, struct audio_s *audio_config_p); + +int layer2_preamble_postamble (int chan, int flags, int finish, struct audio_s *audio_config_p); /* end hdlc_send.h */ diff --git a/src/il2p.h b/src/il2p.h new file mode 100644 index 0000000..d18e2d0 --- /dev/null +++ b/src/il2p.h @@ -0,0 +1,145 @@ + + +#ifndef IL2P_H +#define IL2P_H 1 + + +#define IL2P_PREAMBLE 0x55 + +#define IL2P_SYNC_WORD 0xF15E48 + +#define IL2P_SYNC_WORD_SIZE 3 +#define IL2P_HEADER_SIZE 13 // Does not include 2 parity. +#define IL2P_HEADER_PARITY 2 + +#define IL2P_MAX_PAYLOAD_SIZE 1023 +#define IL2P_MAX_PAYLOAD_BLOCKS 5 +#define IL2P_MAX_PARITY_SYMBOLS 16 // For payload only. +#define IL2P_MAX_ENCODED_PAYLOAD_SIZE (IL2P_MAX_PAYLOAD_SIZE + IL2P_MAX_PAYLOAD_BLOCKS * IL2P_MAX_PARITY_SYMBOLS) + +#define IL2P_MAX_PACKET_SIZE (IL2P_SYNC_WORD_SIZE + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY + IL2P_MAX_ENCODED_PAYLOAD_SIZE) + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_init.c +// +/////////////////////////////////////////////////////////////////////////////// + + +// Init must be called at start of application. + +extern void il2p_init (int debug); + +#include "fx25.h" // For Reed Solomon stuff. e.g. struct rs + // Maybe rearrange someday because RS now used another place. + +extern struct rs *il2p_find_rs(int nparity); // Internal later? + +extern void il2p_encode_rs (unsigned char *tx_data, int data_size, int num_parity, unsigned char *parity_out); + +extern int il2p_decode_rs (unsigned char *rec_block, int data_size, int num_parity, unsigned char *out); + +extern int il2p_get_debug(void); +extern void il2p_set_debug(int debug); + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_rec.c +// +/////////////////////////////////////////////////////////////////////////////// + +// Receives a bit stream from demodulator. + +extern void il2p_rec_bit (int chan, int subchan, int slice, int dbit); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_send.c +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ax25_pad.h" // For packet object. + +// Send bit stream to modulator. + +int il2p_send_frame (int chan, packet_t pp, int max_fec, int polarity); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_codec.c +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ax25_pad.h" + +extern int il2p_encode_frame (packet_t pp, int max_fec, unsigned char *iout); + +packet_t il2p_decode_frame (unsigned char *irec); + +packet_t il2p_decode_header_payload (unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_header.c +// +/////////////////////////////////////////////////////////////////////////////// + + +extern int il2p_type_1_header (packet_t pp, int max_fec, unsigned char *hdr); + +extern packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed); + + +extern int il2p_type_0_header (packet_t pp, int max_fec, unsigned char *hdr); + +extern int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_descrambled_hdr); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_scramble.c +// +/////////////////////////////////////////////////////////////////////////////// + +extern void il2p_scramble_block (unsigned char *in, unsigned char *out, int len); + +extern void il2p_descramble_block (unsigned char *in, unsigned char *out, int len); + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_payload.c +// +/////////////////////////////////////////////////////////////////////////////// + + +typedef struct { + int payload_byte_count; // Total size, 0 thru 1023 + int payload_block_count; + int small_block_size; + int large_block_size; + int large_block_count; + int small_block_count; + int parity_symbols_per_block; // 2, 4, 6, 8, 16 +} il2p_payload_properties_t; + +extern int il2p_payload_compute (il2p_payload_properties_t *p, int payload_size, int max_fec); + +extern int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, unsigned char *enc); + +extern int il2p_decode_payload (unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected); + +extern int il2p_get_header_attributes (unsigned char *hdr, int *hdr_type, int *max_fec); + +#endif diff --git a/src/il2p.h-bak16 b/src/il2p.h-bak16 new file mode 100644 index 0000000..6a77318 --- /dev/null +++ b/src/il2p.h-bak16 @@ -0,0 +1,150 @@ + + +#ifndef IL2P_H +#define IL2P_H 1 + + +#define IL2P_PREAMBLE 0x55 + +#define IL2P_SYNC_WORD 0xF15E48 + +#define IL2P_SYNC_WORD_SIZE 3 +#define IL2P_HEADER_SIZE 13 // Does not include 2 parity. +#define IL2P_HEADER_PARITY 2 + +#define IL2P_MAX_PAYLOAD_SIZE 1023 +#define IL2P_MAX_PAYLOAD_BLOCKS 5 +#define IL2P_MAX_PARITY_SYMBOLS 16 // For payload only. +#define IL2P_MAX_ENCODED_SIZE (IL2P_MAX_PAYLOAD_SIZE + IL2P_MAX_PAYLOAD_BLOCKS * IL2P_MAX_PARITY_SYMBOLS) +// FIXME: above & below IL2P_MAX_ENCODED_PAYLOAD_SIZE + +#define IL2P_MAX_PACKET_SIZE (IL2P_SYNC_WORD_SIZE + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY + IL2P_MAX_ENCODED_SIZE) + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_init.c +// +/////////////////////////////////////////////////////////////////////////////// + + +// Init must be called at start of application. + +extern void il2p_init (int debug); + +#include "fx25.h" // For Reed Solomon stuff. e.g. struct rs + // Maybe rearrange someday because RS now used another place. + +extern struct rs *il2p_find_rs(int nparity); // Internal later? + +extern void il2p_encode_rs (unsigned char *tx_data, int data_size, int num_parity, unsigned char *parity_out); + +extern int il2p_decode_rs (unsigned char *rec_block, int data_size, int num_parity, unsigned char *out); + +extern int il2p_get_debug(void); +extern void il2p_set_debug(int debug); + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_rec.c +// +/////////////////////////////////////////////////////////////////////////////// + +// Receives a bit stream from demodulator. + +extern void il2p_rec_bit (int chan, int subchan, int slice, int dbit); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_send.c +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ax25_pad.h" // For packet object. + +// Send bit stream to modulator. + +int il2p_send_frame (int chan, packet_t pp, int max_fec, int polarity); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_codec.c +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ax25_pad.h" + +extern int il2p_encode_frame (packet_t pp, int max_fec, unsigned char *iout); + +packet_t il2p_decode_frame (unsigned char *irec); + +packet_t il2p_decode_header_payload (unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_header.c +// +/////////////////////////////////////////////////////////////////////////////// + + +extern int il2p_type_1_header (packet_t pp, int max_fec, unsigned char *hdr); + +extern packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed); + + +extern int il2p_type_0_header (packet_t pp, int max_fec, unsigned char *hdr); + +extern int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_unscrambled_hdr); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_scramble.c +// +/////////////////////////////////////////////////////////////////////////////// + +extern void il2p_scramble_reset (int *lsfr_state); + +extern void il2p_scramble_block (unsigned char *in, unsigned char *out, int len, int *lsfr_state); + +extern void il2p_descramble_reset (int *lsfr_state); + +extern void il2p_descramble_block (unsigned char *in, unsigned char *out, int len, int *lsfr_state); + + +/////////////////////////////////////////////////////////////////////////////// +// +// il2p_payload.c +// +/////////////////////////////////////////////////////////////////////////////// + + +typedef struct { + int payload_byte_count; // Total size, 0 thru 1023 + int payload_block_count; + int small_block_size; + int large_block_size; + int large_block_count; + int small_block_count; + int parity_symbols_per_block; // 2, 4, 6, 8, 16 +} il2p_payload_properties_t; + +extern int il2p_payload_compute (il2p_payload_properties_t *p, int payload_size, int max_fec); + +extern int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, unsigned char *enc); + +extern int il2p_decode_payload (unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected); + +extern int il2p_get_header_attributes (unsigned char *hdr, int *hdr_type, int *max_fec); + +#endif diff --git a/src/il2p_codec.c b/src/il2p_codec.c new file mode 100644 index 0000000..0c0832c --- /dev/null +++ b/src/il2p_codec.c @@ -0,0 +1,263 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "il2p.h" +#include "textcolor.h" +#include "demod.h" + + +/*------------------------------------------------------------- + * + * File: il2p_codec.c + * + * Purpose: Convert IL2P encoded format from and to direwolf internal packet format. + * + *--------------------------------------------------------------*/ + + +/*------------------------------------------------------------- + * + * Name: il2p_encode_frame + * + * Purpose: Convert AX.25 frame to IL2P encoding. + * + * Inputs: chan - Audio channel number, 0 = first. + * + * pp - Packet object pointer. + * + * max_fec - 1 to send maximum FEC size rather than automatic. + * + * Outputs: iout - Encoded result, excluding the 3 byte sync word. + * Caller should provide IL2P_MAX_PACKET_SIZE bytes. + * + * Returns: Number of bytes for transmission. + * -1 is returned for failure. + * + * Description: Encode into IL2P format. + * + * Errors: If something goes wrong, return -1. + * + * Most likely reason is that the frame is too large. + * IL2P has a max payload size of 1023 bytes. + * For a type 1 header, this is the maximum AX.25 Information part size. + * For a type 0 header, this is the entire AX.25 frame. + * + *--------------------------------------------------------------*/ + +int il2p_encode_frame (packet_t pp, int max_fec, unsigned char *iout) +{ + +// Can a type 1 header be used? + + unsigned char hdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY]; + int e; + int out_len = 0; + + e = il2p_type_1_header (pp, max_fec, hdr); + if (e >= 0) { + il2p_scramble_block (hdr, iout, IL2P_HEADER_SIZE); + il2p_encode_rs (iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout+IL2P_HEADER_SIZE); + out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY; + + if (e == 0) { + // Sucess. No info part. + return (out_len); + } + + // Payload is AX.25 info part. + unsigned char *pinfo; + int info_len; + info_len = ax25_get_info (pp, &pinfo); + + int k = il2p_encode_payload (pinfo, info_len, max_fec, iout+out_len); + if (k > 0) { + out_len += k; + // Success. Info part was <= 1023 bytes. + return (out_len); + } + + // Something went wrong with the payload encoding. + return (-1); + } + else if (e == -1) { + +// Could not use type 1 header for some reason. +// e.g. More than 2 addresses, extended (mod 128) sequence numbers, etc. + + e = il2p_type_0_header (pp, max_fec, hdr); + if (e > 0) { + + il2p_scramble_block (hdr, iout, IL2P_HEADER_SIZE); + il2p_encode_rs (iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout+IL2P_HEADER_SIZE); + out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY; + + // Payload is entire AX.25 frame. + + unsigned char *frame_data_ptr = ax25_get_frame_data_ptr (pp); + int frame_len = ax25_get_frame_len (pp); + int k = il2p_encode_payload (frame_data_ptr, frame_len, max_fec, iout+out_len); + if (k > 0) { + out_len += k; + // Success. Entire AX.25 frame <= 1023 bytes. + return (out_len); + } + // Something went wrong with the payload encoding. + return (-1); + } + else if (e == 0) { + // Impossible condition. Type 0 header must have payload. + return (-1); + } + else { + // AX.25 frame is too large. + return (-1); + } + } + + // AX.25 Information part is too large. + return (-1); +} + + + +/*------------------------------------------------------------- + * + * Name: il2p_decode_frame + * + * Purpose: Convert IL2P encoding to AX.25 frame. + * This is only used during testing, with a whole encoded frame. + * During reception, the header would have FEC and descrambling + * applied first so we would know how much to collect for the payload. + * + * Inputs: irec - Received IL2P frame excluding the 3 byte sync word. + * + * Future Out: Number of symbols corrected. + * + * Returns: Packet pointer or NULL for error. + * + *--------------------------------------------------------------*/ + +packet_t il2p_decode_frame (unsigned char *irec) +{ + unsigned char uhdr[IL2P_HEADER_SIZE]; // After FEC and descrambling. + int e = il2p_clarify_header (irec, uhdr); + + // TODO?: for symmetry we might want to clarify the payload before combining. + + return (il2p_decode_header_payload(uhdr, irec + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY, &e)); +} + + +/*------------------------------------------------------------- + * + * Name: il2p_decode_header_payload + * + * Purpose: Convert IL2P encoding to AX.25 frame + * + * Inputs: uhdr - Received header after FEC and descrambling. + * epayload - Encoded payload. + * + * In/Out: symbols_corrected - Symbols (bytes) corrected in the header. + * Should be 0 or 1 because it has 2 parity symbols. + * Here we add number of corrections for the payload. + * + * Returns: Packet pointer or NULL for error. + * + *--------------------------------------------------------------*/ + +packet_t il2p_decode_header_payload (unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected) +{ + int hdr_type; + int max_fec; + int payload_len = il2p_get_header_attributes (uhdr, &hdr_type, &max_fec); + + packet_t pp = NULL; + + if (hdr_type == 1) { + +// Header type 1. Any payload is the AX.25 Information part. + + pp = il2p_decode_header_type_1 (uhdr, *symbols_corrected); + if (pp == NULL) { + // Failed for some reason. + return (NULL); + } + + if (payload_len > 0) { + // This is the AX.25 Information part. + + unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE]; + int e = il2p_decode_payload (epayload, payload_len, max_fec, extracted, symbols_corrected); + + // It would be possible to have a good header but too many errors in the payload. + + if (e <= 0) { + ax25_delete (pp); + pp = NULL; + return (pp); + } + + if (e != payload_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P Internal Error: %s(): hdr_type=%d, max_fec=%d, payload_len=%d, e=%d.\n", __func__, hdr_type, max_fec, payload_len, e); + } + + ax25_set_info (pp, extracted, payload_len); + } + return (pp); + } + else { + +// Header type 0. The payload is the entire AX.25 frame. + + unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE]; + int e = il2p_decode_payload (epayload, payload_len, max_fec, extracted, symbols_corrected); + + if (e <= 0) { // Payload was not received correctly. + return (NULL); + } + + if (e != payload_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P Internal Error: %s(): hdr_type=%d, e=%d, payload_len=%d\n", __func__, hdr_type, e, payload_len); + return (NULL); + } + + alevel_t alevel; + memset (&alevel, 0, sizeof(alevel)); + //alevel = demod_get_audio_level (chan, subchan); // What TODO? We don't know channel here. + // I think alevel gets filled in somewhere later making + // this redundant. + + pp = ax25_from_frame (extracted, payload_len, alevel); + return (pp); + } + +} // end il2p_decode_header_payload + +// end il2p_codec.c + + diff --git a/src/il2p_codec.c-bak16 b/src/il2p_codec.c-bak16 new file mode 100644 index 0000000..de2c05a --- /dev/null +++ b/src/il2p_codec.c-bak16 @@ -0,0 +1,265 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "il2p.h" +#include "textcolor.h" +#include "demod.h" + + +/*------------------------------------------------------------- + * + * File: il2p_codec.c + * + * Purpose: Convert IL2P encoded format from and to direwolf internal packet format. + * + *--------------------------------------------------------------*/ + + +/*------------------------------------------------------------- + * + * Name: il2p_encode_frame + * + * Purpose: Convert AX.25 frame to IL2P encoding. + * + * Inputs: chan - Audio channel number, 0 = first. + * + * pp - Packet object pointer. + * + * max_fec - 1 to send maximum FEC size rather than automatic. + * + * Outputs: iout - Encoded result, excluding the 3 byte sync word. + * Caller should provide IL2P_MAX_PACKET_SIZE bytes. + * + * Returns: Number of bytes for transmission. + * -1 is returned for failure. + * + * Description: Encode into IL2P format. + * + * Errors: If something goes wrong, return -1. + * + * Most likely reason is that the frame is too large. + * IL2P has a max payload size of 1023 bytes. + * For a type 1 header, this is the maximum AX.25 Information part size. + * For a type 0 header, this is the entire AX.25 frame. + * + *--------------------------------------------------------------*/ + +int il2p_encode_frame (packet_t pp, int max_fec, unsigned char *iout) +{ + +// Can a type 1 header be used? + + unsigned char hdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY]; + int e; + int tx_lfsr_state; + il2p_scramble_reset(&tx_lfsr_state); + int out_len = 0; + + e = il2p_type_1_header (pp, max_fec, hdr); + if (e >= 0) { + il2p_scramble_block (hdr, iout, IL2P_HEADER_SIZE, &tx_lfsr_state); + il2p_encode_rs (iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout+IL2P_HEADER_SIZE); + out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY; + + if (e == 0) { + // Sucess. No info part. + return (out_len); + } + + // Payload is AX.25 info part. + unsigned char *pinfo; + int info_len; + info_len = ax25_get_info (pp, &pinfo); + + int k = il2p_encode_payload (pinfo, info_len, max_fec, iout+out_len); + if (k > 0) { + out_len += k; + // Success. Info part was <= 1023 bytes. + return (out_len); + } + + // Something went wrong with the payload encoding. + return (-1); + } + else if (e == -1) { + +// Could not use type 1 header for some reason. +// e.g. More than 2 addresses, extended (mod 128) sequence numbers, etc. + + e = il2p_type_0_header (pp, max_fec, hdr); + if (e > 0) { + + il2p_scramble_block (hdr, iout, IL2P_HEADER_SIZE, &tx_lfsr_state); + il2p_encode_rs (iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout+IL2P_HEADER_SIZE); + out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY; + + // Payload is entire AX.25 frame. + + unsigned char *frame_data_ptr = ax25_get_frame_data_ptr (pp); + int frame_len = ax25_get_frame_len (pp); + int k = il2p_encode_payload (frame_data_ptr, frame_len, max_fec, iout+out_len); + if (k > 0) { + out_len += k; + // Success. Entire AX.25 frame <= 1023 bytes. + return (out_len); + } + // Something went wrong with the payload encoding. + return (-1); + } + else if (e == 0) { + // Impossible condition. Type 0 header must have payload. + return (-1); + } + else { + // AX.25 frame is too large. + return (-1); + } + } + + // AX.25 Information part is too large. + return (-1); +} + + + +/*------------------------------------------------------------- + * + * Name: il2p_decode_frame + * + * Purpose: Convert IL2P encoding to AX.25 frame. + * This is only used during testing, with a whole encoded frame. + * During reception, the header would have FEC and descrambling + * applied first so we would know how much to collect for the payload. + * + * Inputs: irec - Received IL2P frame excluding the 3 byte sync word. + * + * Future Out: Number of symbols corrected. + * + * Returns: Packet pointer or NULL for error. + * + *--------------------------------------------------------------*/ + +packet_t il2p_decode_frame (unsigned char *irec) +{ + unsigned char uhdr[IL2P_HEADER_SIZE]; // After FEC and descrambling. + int e = il2p_clarify_header (irec, uhdr); + +// FIXME: for symmetry we might want to clarify the payload before combining. + + return (il2p_decode_header_payload(uhdr, irec + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY, &e)); +} + + +/*------------------------------------------------------------- + * + * Name: il2p_decode_header_payload + * + * Purpose: Convert IL2P encoding to AX.25 frame + * + * Inputs: uhdr - Received header after FEC and descrambling. + * epayload - Encoded payload. + * + * In/Out: symbols_corrected - Symbols (bytes) corrected in the header. + * Should be 0 or 1 because it has 2 parity symbols. + * Here we add number of corrections for the payload. + * + * Returns: Packet pointer or NULL for error. + * + *--------------------------------------------------------------*/ + +packet_t il2p_decode_header_payload (unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected) +{ + int hdr_type; + int max_fec; + int payload_len = il2p_get_header_attributes (uhdr, &hdr_type, &max_fec); + + packet_t pp = NULL; + + if (hdr_type == 1) { + +// Header type 1. Any payload is the AX.25 Information part. + + pp = il2p_decode_header_type_1 (uhdr, *symbols_corrected); + if (pp == NULL) { + // Failed for some reason. + return (NULL); + } + + if (payload_len > 0) { + // This is the AX.25 Information part. + + unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE]; + int e = il2p_decode_payload (epayload, payload_len, max_fec, extracted, symbols_corrected); + + // It would be possible to have a good header but too many errors in the payload. + + if (e <= 0) { + ax25_delete (pp); + pp = NULL; + return (pp); + } + + if (e != payload_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P Internal Error: %s(): hdr_type=%d, max_fec=%d, payload_len=%d, e=%d.\n", __func__, hdr_type, max_fec, payload_len, e); + } + + ax25_set_info (pp, extracted, payload_len); + } + return (pp); + } + else { + +// Header type 0. The payload is the entire AX.25 frame. + + unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE]; + int e = il2p_decode_payload (epayload, payload_len, max_fec, extracted, symbols_corrected); + + if (e <= 0) { // Payload was not received correctly. + return (NULL); + } + + if (e != payload_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P Internal Error: %s(): hdr_type=%d, e=%d, payload_len=%d\n", __func__, hdr_type, e, payload_len); + return (NULL); + } + + alevel_t alevel; + memset (&alevel, 0, sizeof(alevel)); + //alevel = demod_get_audio_level (chan, subchan); // What TODO? We don't know channel here. + // I think alevel gets filled in somewhere later making + // this redundant. + + pp = ax25_from_frame (extracted, payload_len, alevel); + return (pp); + } + +} // end il2p_decode_header_payload + +// end il2p_codec.c + + diff --git a/src/il2p_header.c b/src/il2p_header.c new file mode 100644 index 0000000..347a2c3 --- /dev/null +++ b/src/il2p_header.c @@ -0,0 +1,673 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "textcolor.h" +#include "ax25_pad.h" +#include "ax25_pad2.h" +#include "il2p.h" + + + +/*-------------------------------------------------------------------------------- + * + * File: il2p_header.c + * + * Purpose: Functions to deal with the IL2P header. + * + * Reference: http://tarpn.net/t/il2p/il2p-specification0-4.pdf + * + *--------------------------------------------------------------------------------*/ + + + +// Convert ASCII to/from DEC SIXBIT as defined here: +// https://en.wikipedia.org/wiki/Six-bit_character_code#DEC_six-bit_code + +static inline int ascii_to_sixbit (int a) +{ + if (a >= ' ' && a <= '_') return (a - ' '); + return (31); // '?' for any invalid. +} + +static inline int sixbit_to_ascii (int s) +{ + return (s + ' '); +} + +// Functions for setting the various header fields. +// It is assumed that it was zeroed first so only the '1' bits are set. + +static void set_field (unsigned char *hdr, int bit_num, int lsb_index, int width, int value) +{ + while (width > 0 && value != 0) { + assert (lsb_index >= 0 && lsb_index <= 11); + if (value & 1) { + hdr[lsb_index] |= 1 << bit_num; + } + value >>= 1; + lsb_index--; + width--; + } + assert (value == 0); +} + +#define SET_UI(hdr,val) set_field(hdr, 6, 0, 1, val) + +#define SET_PID(hdr,val) set_field(hdr, 6, 4, 4, val) + +#define SET_CONTROL(hdr,val) set_field(hdr, 6, 11, 7, val) + + +#define SET_FEC_LEVEL(hdr,val) set_field(hdr, 7, 0, 1, val) + +#define SET_HDR_TYPE(hdr,val) set_field(hdr, 7, 1, 1, val) + +#define SET_PAYLOAD_BYTE_COUNT(hdr,val) set_field(hdr, 7, 11, 10, val) + + +// Extracting the fields. + +static int get_field (unsigned char *hdr, int bit_num, int lsb_index, int width) +{ + int result = 0; + lsb_index -= width - 1; + while (width > 0) { + result <<= 1; + assert (lsb_index >= 0 && lsb_index <= 11); + if (hdr[lsb_index] & (1 << bit_num)) { + result |= 1; + } + lsb_index++; + width--; + } + return (result); +} + +#define GET_UI(hdr) get_field(hdr, 6, 0, 1) + +#define GET_PID(hdr) get_field(hdr, 6, 4, 4) + +#define GET_CONTROL(hdr) get_field(hdr, 6, 11, 7) + + +#define GET_FEC_LEVEL(hdr) get_field(hdr, 7, 0, 1) + +#define GET_HDR_TYPE(hdr) get_field(hdr, 7, 1, 1) + +#define GET_PAYLOAD_BYTE_COUNT(hdr) get_field(hdr, 7, 11, 10) + + + +// AX.25 'I' and 'UI' frames have a protocol ID which determines how the +// information part should be interpretted. +// Here we squeeze the most common cases down to 4 bits. +// Return -1 if translation is not possible. Fall back to type 0 header in this case. + +static int encode_pid (packet_t pp) +{ + int pid = ax25_get_pid(pp); + + if ((pid & 0x30) == 0x20) return (0x2); // AX.25 Layer 3 + if ((pid & 0x30) == 0x10) return (0x2); // AX.25 Layer 3 + if (pid == 0x01) return (0x3); // ISO 8208 / CCIT X.25 PLP + if (pid == 0x06) return (0x4); // Compressed TCP/IP + if (pid == 0x07) return (0x5); // Uncompressed TCP/IP + if (pid == 0x08) return (0x6); // Segmentation fragmen + if (pid == 0xcc) return (0xb); // ARPA Internet Protocol + if (pid == 0xcd) return (0xc); // ARPA Address Resolution + if (pid == 0xce) return (0xd); // FlexNet + if (pid == 0xcf) return (0xe); // TheNET + if (pid == 0xf0) return (0xf); // No L3 + return (-1); +} + +// Convert IL2P 4 bit PID to AX.25 8 bit PID. + + +static int decode_pid (int pid) +{ + static const unsigned char axpid[16] = { + 0xf0, // Should not happen. 0 is for 'S' frames. + 0xf0, // Should not happen. 1 is for 'U' frames (but not UI). + 0x20, // AX.25 Layer 3 + 0x01, // ISO 8208 / CCIT X.25 PLP + 0x06, // Compressed TCP/IP + 0x07, // Uncompressed TCP/IP + 0x08, // Segmentation fragment + 0xf0, // Future + 0xf0, // Future + 0xf0, // Future + 0xf0, // Future + 0xcc, // ARPA Internet Protocol + 0xcd, // ARPA Address Resolution + 0xce, // FlexNet + 0xcf, // TheNET + 0xf0 }; // No L3 + + assert (pid >= 0 && pid <= 15); + return (axpid[pid]); +} + + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_type_1_header + * + * Purpose: Attempt to create type 1 header from packet object. + * + * Inputs: pp - Packet object. + * + * max_fec - 1 to use maximum FEC symbols , 0 for automatic. + * + * Outputs: hdr - IL2P header with no scrambling or parity symbols. + * Must be large enough to hold IL2P_HEADER_SIZE unsigned bytes. + * + * Returns: Number of bytes for information part or -1 for failure. + * In case of failure, fall back to type 0 transparent encapsulation. + * + * Description: Type 1 Headers do not support AX.25 repeater callsign addressing, + * Modulo-128 extended mode window sequence numbers, nor any callsign + * characters that cannot translate to DEC SIXBIT. + * If these cases are encountered during IL2P packet encoding, + * the encoder switches to Type 0 Transparent Encapsulation. + * SABME can't be handled by type 1. + * + *--------------------------------------------------------------------------------*/ + +int il2p_type_1_header (packet_t pp, int max_fec, unsigned char *hdr) +{ + memset (hdr, 0, IL2P_HEADER_SIZE); + + if (ax25_get_num_addr(pp) != 2) { + // Only two addresses are allowed for type 1 header. + return (-1); + } + + // Check does not apply for 'U' frames but put in one place rather than two. + + if (ax25_get_modulo(pp) == 128) return(-1); + +// Destination and source addresses go into low bits 0-5 for bytes 0-11. + + char dst_addr[AX25_MAX_ADDR_LEN]; + char src_addr[AX25_MAX_ADDR_LEN]; + + ax25_get_addr_no_ssid (pp, AX25_DESTINATION, dst_addr); + int dst_ssid = ax25_get_ssid (pp, AX25_DESTINATION); + + ax25_get_addr_no_ssid (pp, AX25_SOURCE, src_addr); + int src_ssid = ax25_get_ssid (pp, AX25_SOURCE); + + unsigned char *a = (unsigned char *)dst_addr; + for (int i = 0; *a != '\0'; i++, a++) { + if (*a < ' ' || *a > '_') { + // Shouldn't happen but follow the rule. + return (-1); + } + hdr[i] = ascii_to_sixbit(*a); + } + + a = (unsigned char *)src_addr; + for (int i = 6; *a != '\0'; i++, a++) { + if (*a < ' ' || *a > '_') { + // Shouldn't happen but follow the rule. + return (-1); + } + hdr[i] = ascii_to_sixbit(*a); + } + + // Byte 12 has DEST SSID in upper nybble and SRC SSID in lower nybble and + hdr[12] = (dst_ssid << 4) | src_ssid; + + ax25_frame_type_t frame_type; + cmdres_t cr; // command or response. + char description[64]; + int pf; // Poll/Final. + int nr, ns; // Sequence numbers. + + frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns); + + //dw_printf ("%s(): %s-%d>%s-%d: %s\n", __func__, src_addr, src_ssid, dst_addr, dst_ssid, description); + + switch (frame_type) { + + case frame_type_S_RR: // Receive Ready - System Ready To Receive + case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full + case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate + case frame_type_S_SREJ: // Selective Reject - Request single frame repeat + + // S frames (RR, RNR, REJ, SREJ), mod 8, have control N(R) P/F S S 0 1 + // These are mapped into P/F N(R) C S S + // Bit 6 is not mentioned in documentation but it is used for P/F for the other frame types. + // C is copied from the C bit in the destination addr. + // C from source is not used here. Reception assumes it is the opposite. + // PID is set to 0, meaning none, for S frames. + + SET_UI(hdr, 0); + SET_PID(hdr, 0); + SET_CONTROL(hdr, (pf<<6) | (nr<<3) | (((cr == cr_cmd) | (cr == cr_11))<<2)); + + // This gets OR'ed into the above. + switch (frame_type) { + case frame_type_S_RR: SET_CONTROL(hdr, 0); break; + case frame_type_S_RNR: SET_CONTROL(hdr, 1); break; + case frame_type_S_REJ: SET_CONTROL(hdr, 2); break; + case frame_type_S_SREJ: SET_CONTROL(hdr, 3); break; + default: break; + } + + break; + + case frame_type_U_SABM: // Set Async Balanced Mode + case frame_type_U_DISC: // Disconnect + case frame_type_U_DM: // Disconnect Mode + case frame_type_U_UA: // Unnumbered Acknowledge + case frame_type_U_FRMR: // Frame Reject + case frame_type_U_UI: // Unnumbered Information + case frame_type_U_XID: // Exchange Identification + case frame_type_U_TEST: // Test + + // The encoding allows only 3 bits for frame type and SABME got left out. + // Control format: P/F opcode[3] C n/a n/a + // The grayed out n/a bits are observed as 00 in the example. + // The header UI field must also be set for UI frames. + // PID is set to 1 for all U frames other than UI. + + if (frame_type == frame_type_U_UI) { + SET_UI(hdr, 1); // I guess this is how we distinguish 'I' and 'UI' + // on the receving end. + int pid = encode_pid(pp); + if (pid < 0) return (-1); + SET_PID(hdr, pid); + } + else { + SET_PID(hdr, 1); // 1 for 'U' other than 'UI'. + } + + // Each of the destination and source addresses has a "C" bit. + // They should normally have the opposite setting. + // IL2P has only a single bit to represent 4 possbilities. + // + // dst src il2p meaning + // --- --- ---- ------- + // 0 0 0 Not valid (earlier protocol version) + // 1 0 1 Command (v2) + // 0 1 0 Response (v2) + // 1 1 1 Not valid (earlier protocol version) + // + // APRS does not mention how to set these bits and all 4 combinations + // are seen in the wild. Apparently these are ignord on receive and no + // one cares. Here we copy from the C bit in the destination address. + // It should be noted that the case of both C bits being the same can't + // be represented so the il2p encode/decode bit not produce exactly the + // same bits. We see this in the second example in the protocol spec. + // The original UI frame has both C bits of 0 so it is received as a response. + + SET_CONTROL(hdr, (pf<<6) | (((cr == cr_cmd) | (cr == cr_11))<<2)); + + // This gets OR'ed into the above. + switch (frame_type) { + case frame_type_U_SABM: SET_CONTROL(hdr, 0<<3); break; + case frame_type_U_DISC: SET_CONTROL(hdr, 1<<3); break; + case frame_type_U_DM: SET_CONTROL(hdr, 2<<3); break; + case frame_type_U_UA: SET_CONTROL(hdr, 3<<3); break; + case frame_type_U_FRMR: SET_CONTROL(hdr, 4<<3); break; + case frame_type_U_UI: SET_CONTROL(hdr, 5<<3); break; + case frame_type_U_XID: SET_CONTROL(hdr, 6<<3); break; + case frame_type_U_TEST: SET_CONTROL(hdr, 7<<3); break; + default: break; + } + break; + + case frame_type_I: // Information + + // I frames (mod 8 only) + // encoded control: P/F N(R) N(S) + + SET_UI(hdr, 0); + + int pid2 = encode_pid(pp); + if (pid2 < 0) return (-1); + SET_PID(hdr, pid2); + + SET_CONTROL(hdr, (pf<<6) | (nr<<3) | ns); + break; + + case frame_type_U_SABME: // Set Async Balanced Mode, Extended + case frame_type_U: // other Unnumbered, not used by AX.25. + case frame_not_AX25: // Could not get control byte from frame. + default: + + // Fall back to the header type 0 for these. + return (-1); + } + +// Common for all header type 1. + + // Bit 7 has [FEC Level:1], [HDR Type:1], [Payload byte Count:10] + + SET_FEC_LEVEL(hdr, max_fec); + SET_HDR_TYPE(hdr, 1); + + unsigned char *pinfo; + int info_len; + + info_len = ax25_get_info (pp, &pinfo); + if (info_len < 0 || info_len > IL2P_MAX_PAYLOAD_SIZE) { + return (-2); + } + + SET_PAYLOAD_BYTE_COUNT(hdr, info_len); + return (info_len); +} + + +// This should create a packet from the IL2P header. +// The information part will not be filled in. + +static void trim (char *stuff) +{ + char *p = stuff + strlen(stuff) - 1; + while (strlen(stuff) > 0 && (*p == ' ')) { + *p = '\0'; + p--; + } +} + + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_decode_header_type_1 + * + * Purpose: Attempt to convert type 1 header to a packet object. + * + * Inputs: hdr - IL2P header with no scrambling or parity symbols. + * + * num_sym_changed - Number of symbols changed by FEC in the header. + * Should be 0 or 1. + * + * Returns: Packet Object or NULL for failure. + * + * Description: A later step will process the payload for the information part. + * + *--------------------------------------------------------------------------------*/ + +packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed) +{ + + if (GET_HDR_TYPE(hdr) != 1 ) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P Internal error. Should not be here: %s, when header type is 0.\n", __func__); + return (NULL); + } + +// First get the addresses including SSID. + + char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; + int num_addr = 2; + memset (addrs, 0, 2*AX25_MAX_ADDR_LEN); + +// The IL2P header uses 2 parity symbols which means a single corrupted symbol (byte) +// can always be corrected. +// However, I have seen cases, where the error rate is very high, where the RS decoder +// thinks it found a valid code block by changing one symbol but it was the wrong one. +// The result is trash. This shows up as address fields like 'R&G4"A' and 'TEW\ !'. +// I added a sanity check here to catch characters other than uppper case letters and digits. +// The frame should be rejected in this case. The question is whether to discard it +// silently or print a message so the user can see that something strange is happening? +// My current thinking is that it should be silently ignored if the header has been +// modified (correctee or more likely, made worse in this cases). +// If no changes were made, something weird is happening. We should mention it for +// troubleshooting rather than sweeping it under the rug. + +// The same thing has been observed with the payload, under very high error conditions, +// and max_fec==0. Here I don't see a good solution. AX.25 information can contain +// "binary" data so I'm not sure what sort of sanity check could be added. +// This was not observed with max_fec==1. If we make that the default, same as Nino TNC, +// it would be extremely extremely unlikely unless someone explicitly selects weaker FEC. + +// TODO: We could do something similar for header type 0. +// The address fields should be all binary zero values. +// Someone overly ambitious might check the addresses found in the first payload block. + + for (int i = 0; i <= 5; i++) { + addrs[AX25_DESTINATION][i] = sixbit_to_ascii(hdr[i] & 0x3f); + } + trim (addrs[AX25_DESTINATION]); + for (int i = 0; i < strlen(addrs[AX25_DESTINATION]); i++) { + if (! isupper(addrs[AX25_DESTINATION][i]) && ! isdigit(addrs[AX25_DESTINATION][i])) { + if (num_sym_changed == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]); + } + return (NULL); + } + } + snprintf (addrs[AX25_DESTINATION]+strlen(addrs[AX25_DESTINATION]), 4, "-%d", (hdr[12] >> 4) &0xf); + + for (int i = 0; i <= 5; i++) { + addrs[AX25_SOURCE][i] = sixbit_to_ascii(hdr[i+6] & 0x3f); + } + trim (addrs[AX25_SOURCE]); + for (int i = 0; i < strlen(addrs[AX25_SOURCE]); i++) { + if (! isupper(addrs[AX25_SOURCE][i]) && ! isdigit(addrs[AX25_SOURCE][i])) { + if (num_sym_changed == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]); + } + return (NULL); + } + } + snprintf (addrs[AX25_SOURCE]+strlen(addrs[AX25_SOURCE]), 4, "-%d", hdr[12] &0xf); + +// The PID field gives us the general type. +// 0 = 'S' frame. +// 1 = 'U' frame other than UI. +// others are either 'UI' or 'I' depending on the UI field. + + int pid = GET_PID(hdr); + int ui = GET_UI(hdr); + + if (pid == 0) { + +// 'S' frame. +// The control field contains: P/F N(R) C S S + + int control = GET_CONTROL(hdr); + cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res; + ax25_frame_type_t ftype; + switch (control & 0x03) { + case 0: ftype = frame_type_S_RR; break; + case 1: ftype = frame_type_S_RNR; break; + case 2: ftype = frame_type_S_REJ; break; + default: ftype = frame_type_S_SREJ; break; + } + int modulo = 8; + int nr = (control >> 3) & 0x07; + int pf = (control >> 6) & 0x01; + unsigned char *pinfo = NULL; // Any info for SREJ will be added later. + int info_len = 0; + return (ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, pinfo, info_len)); + } + else if (pid == 1) { + +// 'U' frame other than 'UI'. +// The control field contains: P/F OPCODE{3) C x x + + int control = GET_CONTROL(hdr); + cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res; + int axpid = 0; // unused for U other than UI. + ax25_frame_type_t ftype; + switch ((control >> 3) & 0x7) { + case 0: ftype = frame_type_U_SABM; break; + case 1: ftype = frame_type_U_DISC; break; + case 2: ftype = frame_type_U_DM; break; + case 3: ftype = frame_type_U_UA; break; + case 4: ftype = frame_type_U_FRMR; break; + case 5: ftype = frame_type_U_UI; axpid = 0xf0; break; // Should not happen with IL2P pid == 1. + case 6: ftype = frame_type_U_XID; break; + default: ftype = frame_type_U_TEST; break; + } + int pf = (control >> 6) & 0x01; + unsigned char *pinfo = NULL; // Any info for UI, XID, TEST will be added later. + int info_len = 0; + return (ax25_u_frame (addrs, num_addr, cr, ftype, pf, axpid, pinfo, info_len)); + } + else if (ui) { + +// 'UI' frame. +// The control field contains: P/F OPCODE{3) C x x + + int control = GET_CONTROL(hdr); + cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res; + ax25_frame_type_t ftype = frame_type_U_UI; + int pf = (control >> 6) & 0x01; + int axpid = decode_pid(GET_PID(hdr)); + unsigned char *pinfo = NULL; // Any info for UI, XID, TEST will be added later. + int info_len = 0; + return (ax25_u_frame (addrs, num_addr, cr, ftype, pf, axpid, pinfo, info_len)); + } + else { + +// 'I' frame. +// The control field contains: P/F N(R) N(S) + + int control = GET_CONTROL(hdr); + cmdres_t cr = cr_cmd; // Always command. + int pf = (control >> 6) & 0x01; + int nr = (control >> 3) & 0x7; + int ns = control & 0x7; + int modulo = 8; + int axpid = decode_pid(GET_PID(hdr)); + unsigned char *pinfo = NULL; // Any info for UI, XID, TEST will be added later. + int info_len = 0; + return (ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, axpid, pinfo, info_len)); + } + return (NULL); // unreachable but avoid warning. + +} // end + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_type_0_header + * + * Purpose: Attempt to create type 0 header from packet object. + * + * Inputs: pp - Packet object. + * + * max_fec - 1 to use maximum FEC symbols, 0 for automatic. + * + * Outputs: hdr - IL2P header with no scrambling or parity symbols. + * Must be large enough to hold IL2P_HEADER_SIZE unsigned bytes. + * + * Returns: Number of bytes for information part or -1 for failure. + * In case of failure, fall back to type 0 transparent encapsulation. + * + * Description: The type 0 header is used when it is not one of the restricted cases + * covered by the type 1 header. + * The AX.25 frame is put in the payload. + * This will cover: more than one address, mod 128 sequences, etc. + * + *--------------------------------------------------------------------------------*/ + +int il2p_type_0_header (packet_t pp, int max_fec, unsigned char *hdr) +{ + memset (hdr, 0, IL2P_HEADER_SIZE); + + // Bit 7 has [FEC Level:1], [HDR Type:1], [Payload byte Count:10] + + SET_FEC_LEVEL(hdr, max_fec); + SET_HDR_TYPE(hdr, 0); + + int frame_len = ax25_get_frame_len (pp); + + if (frame_len < 14 || frame_len > IL2P_MAX_PAYLOAD_SIZE) { + return (-2); + } + + SET_PAYLOAD_BYTE_COUNT(hdr, frame_len); + return (frame_len); +} + + +/*********************************************************************************** + * + * Name: il2p_get_header_attributes + * + * Purpose: Extract a few attributes from an IL2p header. + * + * Inputs: hdr - IL2P header structure. + * + * Outputs: hdr_type - 0 or 1. + * + * max_fec - 0 for automatic or 1 for fixed maximum size. + * + * Returns: Payload byte count. (actual payload size, not the larger encoded format) + * + ***********************************************************************************/ + + +int il2p_get_header_attributes (unsigned char *hdr, int *hdr_type, int *max_fec) +{ + *hdr_type = GET_HDR_TYPE(hdr); + *max_fec = GET_FEC_LEVEL(hdr); + return(GET_PAYLOAD_BYTE_COUNT(hdr)); +} + + +/*********************************************************************************** + * + * Name: il2p_clarify_header + * + * Purpose: Convert received header to usable form. + * This involves RS FEC then descrambling. + * + * Inputs: rec_hdr - Header as received over the radio. + * + * Outputs: corrected_descrambled_hdr - After RS FEC and unscrambling. + * + * Returns: Number of symbols that were corrected: + * 0 = No errors + * 1 = Single symbol corrected. + * <0 = Unable to obtain good header. + * + ***********************************************************************************/ + +int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_descrambled_hdr) +{ + unsigned char corrected[IL2P_HEADER_SIZE+IL2P_HEADER_PARITY]; + + int e = il2p_decode_rs (rec_hdr, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, corrected); + + il2p_descramble_block (corrected, corrected_descrambled_hdr, IL2P_HEADER_SIZE); + + return (e); +} + +// end il2p_header.c \ No newline at end of file diff --git a/src/il2p_init.c b/src/il2p_init.c new file mode 100644 index 0000000..bfab719 --- /dev/null +++ b/src/il2p_init.c @@ -0,0 +1,226 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "textcolor.h" +#include "fx25.h" // For Reed Solomon stuff. +#include "il2p.h" + +// Interesting related stuff: +// https://www.kernel.org/doc/html/v4.15/core-api/librs.html +// https://berthub.eu/articles/posts/reed-solomon-for-programmers/ + + +#define MAX_NROOTS 16 + +#define NTAB 5 + +static struct { + int symsize; // Symbol size, bits (1-8). Always 8 for this application. + int genpoly; // Field generator polynomial coefficients. + int fcs; // First root of RS code generator polynomial, index form. + // FX.25 uses 1 but IL2P uses 0. + int prim; // Primitive element to generate polynomial roots. + int nroots; // RS code generator polynomial degree (number of roots). + // Same as number of check bytes added. + struct rs *rs; // Pointer to RS codec control block. Filled in at init time. +} Tab[NTAB] = { + {8, 0x11d, 0, 1, 2, NULL }, // 2 parity + {8, 0x11d, 0, 1, 4, NULL }, // 4 parity + {8, 0x11d, 0, 1, 6, NULL }, // 6 parity + {8, 0x11d, 0, 1, 8, NULL }, // 8 parity + {8, 0x11d, 0, 1, 16, NULL }, // 16 parity +}; + + + +static int g_il2p_debug = 0; + + +/*------------------------------------------------------------- + * + * Name: il2p_init + * + * Purpose: This must be called at application start up time. + * It sets up tables for the Reed-Solomon functions. + * + * Inputs: debug - Enable debug output. + * + *--------------------------------------------------------------*/ + +void il2p_init (int il2p_debug) +{ + g_il2p_debug = il2p_debug; + + for (int i = 0 ; i < NTAB ; i++) { + assert (Tab[i].nroots <= MAX_NROOTS); + Tab[i].rs = INIT_RS(Tab[i].symsize, Tab[i].genpoly, Tab[i].fcs, Tab[i].prim, Tab[i].nroots); + if (Tab[i].rs == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf("IL2P internal error: init_rs_char failed!\n"); + exit(EXIT_FAILURE); + } + } + +} // end il2p_init + + +int il2p_get_debug(void) +{ + return (g_il2p_debug); +} +void il2p_set_debug(int debug) +{ + g_il2p_debug = debug; +} + + +// Find RS codec control block for specified number of parity symbols. + +struct rs *il2p_find_rs(int nparity) +{ + for (int n = 0; n < NTAB; n++) { + if (Tab[n].nroots == nparity) { + return (Tab[n].rs); + } + } + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P INTERNAL ERROR: il2p_find_rs: control block not found for nparity = %d.\n", nparity); + return (Tab[0].rs); +} + + +/*------------------------------------------------------------- + * + * Name: void il2p_encode_rs + * + * Purpose: Add parity symbols to a block of data. + * + * Inputs: tx_data Header or other data to transmit. + * data_size Number of data bytes in above. + * num_parity Number of parity symbols to add. + * Maximum of IL2P_MAX_PARITY_SYMBOLS. + * + * Outputs: parity_out Specified number of parity symbols + * + * Restriction: data_size + num_parity <= 255 which is the RS block size. + * The caller must ensure this. + * + *--------------------------------------------------------------*/ + +void il2p_encode_rs (unsigned char *tx_data, int data_size, int num_parity, unsigned char *parity_out) +{ + assert (data_size >= 1); + assert (num_parity == 2 || num_parity == 4 || num_parity == 6 || num_parity == 8 || num_parity == 16); + assert (data_size + num_parity <= 255); + + unsigned char rs_block[FX25_BLOCK_SIZE]; + memset (rs_block, 0, sizeof(rs_block)); + memcpy (rs_block + sizeof(rs_block) - data_size - num_parity, tx_data, data_size); + ENCODE_RS (il2p_find_rs(num_parity), rs_block, parity_out); +} + +/*------------------------------------------------------------- + * + * Name: void il2p_decode_rs + * + * Purpose: Check and attempt to fix block with FEC. + * + * Inputs: rec_block Received block composed of data and parity. + * Total size is sum of following two parameters. + * data_size Number of data bytes in above. + * num_parity Number of parity symbols (bytes) in above. + * + * Outputs: out Original with possible corrections applied. + * data_size bytes. + * + * Returns: -1 for unrecoverable. + * >= 0 for sucess. Number of symbols corrected. + * + *--------------------------------------------------------------*/ + +int il2p_decode_rs (unsigned char *rec_block, int data_size, int num_parity, unsigned char *out) +{ + + // Use zero padding in front if data size is too small. + + int n = data_size + num_parity; // total size in. + + unsigned char rs_block[FX25_BLOCK_SIZE]; + + // We could probably do this more efficiently by skipping the + // processing of the bytes known to be zero. Good enough for now. + + memset (rs_block, 0, sizeof(rs_block) - n); + memcpy (rs_block + sizeof(rs_block) - n, rec_block, n); + + if (il2p_get_debug() >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("============================== il2p_decode_rs ==============================\n"); + dw_printf ("%d filler zeros, %d data, %d parity\n", (int)(sizeof(rs_block) - n), data_size, num_parity); + fx_hex_dump (rs_block, sizeof (rs_block)); + } + + int derrlocs[FX25_MAX_CHECK]; // Half would probably be OK. + + int derrors = DECODE_RS(il2p_find_rs(num_parity), rs_block, derrlocs, 0); + memcpy (out, rs_block + sizeof(rs_block) - n, data_size); + + if (il2p_get_debug() >= 3) { + if (derrors == 0) { + dw_printf ("No errors reported for RS block.\n"); + } + else if (derrors > 0) { + dw_printf ("%d errors fixed in positions:\n", derrors); + for (int j = 0; j < derrors; j++) { + dw_printf (" %3d (0x%02x)\n", derrlocs[j] , derrlocs[j]); + } + fx_hex_dump (rs_block, sizeof (rs_block)); + } + } + + // It is possible to have a situation where too many errors are + // present but the algorithm could get a good code block by "fixing" + // one of the padding bytes that should be 0. + + for (int i = 0; i < derrors; i++) { + if (derrlocs[i] < sizeof(rs_block) - n) { + if (il2p_get_debug() >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("RS DECODE ERROR! Padding position %d should be 0 but it was set to %02x.\n", derrlocs[i], rs_block[derrlocs[i]]); + } + derrors = -1; + break; + } + } + + if (il2p_get_debug() >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("============================== il2p_decode_rs returns %d ==============================\n", derrors); + } + return (derrors); +} + +// end il2p_init.c \ No newline at end of file diff --git a/src/il2p_payload.c b/src/il2p_payload.c new file mode 100644 index 0000000..b65c38a --- /dev/null +++ b/src/il2p_payload.c @@ -0,0 +1,298 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "textcolor.h" +#include "il2p.h" + + +/*-------------------------------------------------------------------------------- + * + * File: il2p_payload.c + * + * Purpose: Functions dealing with the payload. + * + *--------------------------------------------------------------------------------*/ + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_payload_compute + * + * Purpose: Compute number and sizes of data blocks based on total size. + * + * Inputs: payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) + * max_fec true for 16 parity symbols, false for automatic. + * + * Outputs: *p Payload block sizes and counts. + * Number of parity symbols per block. + * + * Returns: Number of bytes in the encoded format. + * Could be 0 for no payload blocks. + * -1 for error (i.e. invalid unencoded size: <0 or >1023) + * + *--------------------------------------------------------------------------------*/ + +int il2p_payload_compute (il2p_payload_properties_t *p, int payload_size, int max_fec) +{ + memset (p, 0, sizeof(il2p_payload_properties_t)); + + if (payload_size < 0 || payload_size > IL2P_MAX_PAYLOAD_SIZE) { + return (-1); + } + if (payload_size == 0) { + return (0); + } + + if (max_fec) { + p->payload_byte_count = payload_size; + p->payload_block_count = (p->payload_byte_count + 238) / 239; + p->small_block_size = p->payload_byte_count / p->payload_block_count; + p->large_block_size = p->small_block_size + 1; + p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size); + p->small_block_count = p->payload_block_count - p->large_block_count; + p->parity_symbols_per_block = 16; + } + else { + p->payload_byte_count = payload_size; + p->payload_block_count = (p->payload_byte_count + 246) / 247; + p->small_block_size = p->payload_byte_count / p->payload_block_count; + p->large_block_size = p->small_block_size + 1; + p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size); + p->small_block_count = p->payload_block_count - p->large_block_count; + //p->parity_symbols_per_block = (p->small_block_size / 32) + 2; // Looks like error in documentation + + // It would work if the number of parity symbols was based on large block size. + + if (p->small_block_size <= 61) p->parity_symbols_per_block = 2; + else if (p->small_block_size <= 123) p->parity_symbols_per_block = 4; + else if (p->small_block_size <= 185) p->parity_symbols_per_block = 6; + else if (p->small_block_size <= 247) p->parity_symbols_per_block = 8; + else { + // Should not happen. But just in case... + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P parity symbol per payload block error. small_block_size = %d\n", p->small_block_size); + return (-1); + } + } + + // Return the total size for the encoded format. + + return (p->small_block_count * (p->small_block_size + p->parity_symbols_per_block) + + p->large_block_count * (p->large_block_size + p->parity_symbols_per_block)); + +} // end il2p_payload_compute + + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_encode_payload + * + * Purpose: Split payload into multiple blocks such that each set + * of data and parity symbols fit into a 255 byte RS block. + * + * Inputs: *payload Array of bytes. + * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) + * max_fec true for 16 parity symbols, false for automatic. + * + * Outputs: *enc Encoded payload for transmission. + * Up to IL2P_MAX_ENCODED_SIZE bytes. + * + * Returns: -1 for error (i.e. invalid size) + * 0 for no blocks. (i.e. size zero) + * Number of bytes generated. Maximum IL2P_MAX_ENCODED_SIZE. + * + * Note: I interpretted the protocol spec as saying the LFSR state is retained + * between data blocks. During interoperability testing, I found that + * was not the case. It is reset for each data block. + * + *--------------------------------------------------------------------------------*/ + + +int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, unsigned char *enc) +{ + if (payload_size > IL2P_MAX_PAYLOAD_SIZE) return (-1); + if (payload_size == 0) return (0); + +// Determine number of blocks and sizes. + + il2p_payload_properties_t ipp; + int e; + e = il2p_payload_compute (&ipp, payload_size, max_fec); + if (e <= 0) { + return (e); + } + + unsigned char *pin = payload; + unsigned char *pout = enc; + int encoded_length = 0; + unsigned char scram[256]; + unsigned char parity[IL2P_MAX_PARITY_SYMBOLS]; + +// First the large blocks. + + for (int b = 0; b < ipp.large_block_count; b++) { + + il2p_scramble_block (pin, scram, ipp.large_block_size); + memcpy (pout, scram, ipp.large_block_size); + pin += ipp.large_block_size; + pout += ipp.large_block_size; + encoded_length += ipp.large_block_size; + il2p_encode_rs (scram, ipp.large_block_size, ipp.parity_symbols_per_block, parity); + memcpy (pout, parity, ipp.parity_symbols_per_block); + pout += ipp.parity_symbols_per_block; + encoded_length += ipp.parity_symbols_per_block; + } + +// Then the small blocks. + + for (int b = 0; b < ipp.small_block_count; b++) { + + il2p_scramble_block (pin, scram, ipp.small_block_size); + memcpy (pout, scram, ipp.small_block_size); + pin += ipp.small_block_size; + pout += ipp.small_block_size; + encoded_length += ipp.small_block_size; + il2p_encode_rs (scram, ipp.small_block_size, ipp.parity_symbols_per_block, parity); + memcpy (pout, parity, ipp.parity_symbols_per_block); + pout += ipp.parity_symbols_per_block; + encoded_length += ipp.parity_symbols_per_block; + } + + return (encoded_length); + +} // end il2p_encode_payload + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_decode_payload + * + * Purpose: Extract original data from encoded payload. + * + * Inputs: received Array of bytes. Size is unknown but in practice it + * must not exceeed IL2P_MAX_ENCODED_SIZE. + * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) + * Expected result size based on header. + * max_fec true for 16 parity symbols, false for automatic. + * + * Outputs: payload_out Recovered payload. + * + * In/Out: symbols_corrected Number of symbols corrected. + * + * + * Returns: Number of bytes extracted. Should be same as payload_size going in. + * -3 for unexpected internal inconsistency. + * -2 for unable to recover from signal corruption. + * -1 for invalid size. + * 0 for no blocks. (i.e. size zero) + * + * Description: Each block is scrambled separately but the LSFR state is carried + * from the first payload block to the next. + * + *--------------------------------------------------------------------------------*/ + +int il2p_decode_payload (unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected) +{ +// Determine number of blocks and sizes. + + il2p_payload_properties_t ipp; + int e; + e = il2p_payload_compute (&ipp, payload_size, max_fec); + if (e <= 0) { + return (e); + } + + unsigned char *pin = received; + unsigned char *pout = payload_out; + int decoded_length = 0; + int failed = 0; + +// First the large blocks. + + for (int b = 0; b < ipp.large_block_count; b++) { + unsigned char corrected_block[255]; + int e = il2p_decode_rs (pin, ipp.large_block_size, ipp.parity_symbols_per_block, corrected_block); + + // dw_printf ("%s:%d: large block decode_rs returned status = %d\n", __FILE__, __LINE__, e); + + if (e < 0) failed = 1; + *symbols_corrected += e; + + il2p_descramble_block (corrected_block, pout, ipp.large_block_size); + + if (il2p_get_debug() >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Descrambled large payload block, %d bytes:\n", ipp.large_block_size); + fx_hex_dump(pout, ipp.large_block_size); + } + + pin += ipp.large_block_size + ipp.parity_symbols_per_block; + pout += ipp.large_block_size; + decoded_length += ipp.large_block_size; + } + +// Then the small blocks. + + for (int b = 0; b < ipp.small_block_count; b++) { + unsigned char corrected_block[255]; + int e = il2p_decode_rs (pin, ipp.small_block_size, ipp.parity_symbols_per_block, corrected_block); + + // dw_printf ("%s:%d: small block decode_rs returned status = %d\n", __FILE__, __LINE__, e); + + if (e < 0) failed = 1; + *symbols_corrected += e; + + il2p_descramble_block (corrected_block, pout, ipp.small_block_size); + + if (il2p_get_debug() >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Descrambled small payload block, %d bytes:\n", ipp.small_block_size); + fx_hex_dump(pout, ipp.small_block_size); + } + + pin += ipp.small_block_size + ipp.parity_symbols_per_block; + pout += ipp.small_block_size; + decoded_length += ipp.small_block_size; + } + + if (failed) { + //dw_printf ("%s:%d: failed = %0x\n", __FILE__, __LINE__, failed); + return (-2); + } + + if (decoded_length != payload_size) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P Internal error: decoded_length = %d, payload_size = %d\n", decoded_length, payload_size); + return (-3); + } + + return (decoded_length); + +} // end il2p_decode_payload + +// end il2p_payload.c + diff --git a/src/il2p_payload.c-bak16 b/src/il2p_payload.c-bak16 new file mode 100644 index 0000000..d17444a --- /dev/null +++ b/src/il2p_payload.c-bak16 @@ -0,0 +1,317 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "textcolor.h" +#include "il2p.h" + + +/*-------------------------------------------------------------------------------- + * + * File: il2p_payload.c + * + * Purpose: Functions dealing with the payload. + * + *--------------------------------------------------------------------------------*/ + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_payload_compute + * + * Purpose: Compute number and sizes of data blocks based on total size. + * + * Inputs: payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) + * max_fec true for 16 parity symbols, false for automatic. + * + * Outputs: *p Payload block sizes and counts. + * Number of parity symbols per block. + * + * Returns: Number of bytes in the encoded format. + * Could be 0 for no payload blocks. + * -1 for error (i.e. invalid unencoded size: <0 or >1023) + * + *--------------------------------------------------------------------------------*/ + +int il2p_payload_compute (il2p_payload_properties_t *p, int payload_size, int max_fec) +{ + memset (p, 0, sizeof(il2p_payload_properties_t)); + + if (payload_size < 0 || payload_size > IL2P_MAX_PAYLOAD_SIZE) { + return (-1); + } + if (payload_size == 0) { + return (0); + } + + if (max_fec) { + p->payload_byte_count = payload_size; + p->payload_block_count = (p->payload_byte_count + 238) / 239; + p->small_block_size = p->payload_byte_count / p->payload_block_count; + p->large_block_size = p->small_block_size + 1; + p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size); + p->small_block_count = p->payload_block_count - p->large_block_count; + p->parity_symbols_per_block = 16; + } + else { + p->payload_byte_count = payload_size; + p->payload_block_count = (p->payload_byte_count + 246) / 247; + p->small_block_size = p->payload_byte_count / p->payload_block_count; + p->large_block_size = p->small_block_size + 1; + p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size); + p->small_block_count = p->payload_block_count - p->large_block_count; + //p->parity_symbols_per_block = (p->small_block_size / 32) + 2; // Looks like error in documentation + + // It would work if the number of parity symbols was based on large block size. + + if (p->small_block_size <= 61) p->parity_symbols_per_block = 2; + else if (p->small_block_size <= 123) p->parity_symbols_per_block = 4; + else if (p->small_block_size <= 185) p->parity_symbols_per_block = 6; + else if (p->small_block_size <= 247) p->parity_symbols_per_block = 8; + else { + // Should not happen. But just in case... + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P parity symbol per payload block error. small_block_size = %d\n", p->small_block_size); + return (-1); + } + } + + // Return the total size for the encoded format. + + return (p->small_block_count * (p->small_block_size + p->parity_symbols_per_block) + + p->large_block_count * (p->large_block_size + p->parity_symbols_per_block)); + +} // end il2p_payload_compute + + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_encode_payload + * + * Purpose: Split payload into multiple blocks such that each set + * of data and parity symbols fit into a 255 byte RS block. + * + * Inputs: *payload Array of bytes. + * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) + * max_fec true for 16 parity symbols, false for automatic. + * + * Outputs: *enc Encoded payload for transmission. + * Up to IL2P_MAX_ENCODED_SIZE bytes. + * + * Returns: -1 for error (i.e. invalid size) + * 0 for no blocks. (i.e. size zero) + * Number of bytes generated. Maximum IL2P_MAX_ENCODED_SIZE. + * + * Note: I interpretted the protocol spec as saying the LFSR state is retained + * between data blocks. During interoperability testing, I found that + * was not the case. It is reset for each data block. + * + *--------------------------------------------------------------------------------*/ + + +int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, unsigned char *enc) +{ + if (payload_size > IL2P_MAX_PAYLOAD_SIZE) return (-1); + if (payload_size == 0) return (0); + + int tx_lsfr_state; + il2p_scramble_reset(&tx_lsfr_state); + +// Determine number of blocks and sizes. + + il2p_payload_properties_t ipp; + int e; + e = il2p_payload_compute (&ipp, payload_size, max_fec); + if (e <= 0) { + return (e); + } + + unsigned char *pin = payload; + unsigned char *pout = enc; + int encoded_length = 0; + unsigned char scram[256]; + unsigned char parity[IL2P_MAX_PARITY_SYMBOLS]; + +// First the large blocks. + + for (int b = 0; b < ipp.large_block_count; b++) { + + +// FIXME experiment +il2p_scramble_reset(&tx_lsfr_state); + + il2p_scramble_block (pin, scram, ipp.large_block_size, &tx_lsfr_state); + memcpy (pout, scram, ipp.large_block_size); + pin += ipp.large_block_size; + pout += ipp.large_block_size; + encoded_length += ipp.large_block_size; + il2p_encode_rs (scram, ipp.large_block_size, ipp.parity_symbols_per_block, parity); + memcpy (pout, parity, ipp.parity_symbols_per_block); + pout += ipp.parity_symbols_per_block; + encoded_length += ipp.parity_symbols_per_block; + } + +// Then the small blocks. + + for (int b = 0; b < ipp.small_block_count; b++) { + +// FIXME experiment -- works - no need to pass lfsr state in and out of scramble block. +il2p_scramble_reset(&tx_lsfr_state); + + il2p_scramble_block (pin, scram, ipp.small_block_size, &tx_lsfr_state); + memcpy (pout, scram, ipp.small_block_size); + pin += ipp.small_block_size; + pout += ipp.small_block_size; + encoded_length += ipp.small_block_size; + il2p_encode_rs (scram, ipp.small_block_size, ipp.parity_symbols_per_block, parity); + memcpy (pout, parity, ipp.parity_symbols_per_block); + pout += ipp.parity_symbols_per_block; + encoded_length += ipp.parity_symbols_per_block; + } + + return (encoded_length); + +} // end il2p_encode_payload + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_decode_payload + * + * Purpose: Extract original data from encoded payload. + * + * Inputs: received Array of bytes. Size is unknown but in practice it + * must not exceeed IL2P_MAX_ENCODED_SIZE. + * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) + * Expected result size based on header. + * max_fec true for 16 parity symbols, false for automatic. + * + * Outputs: payload_out Recovered payload. + * + * In/Out: symbols_corrected Number of symbols corrected. + * + * + * Returns: Number of bytes extracted. Should be same as payload_size going in. + * -3 for unexpected internal inconsistency. + * -2 for unable to recover from signal corruption. + * -1 for invalid size. + * 0 for no blocks. (i.e. size zero) + * + * Description: Each block is scrambled separately but the LSFR state is carried + * from the first payload block to the next. + * + *--------------------------------------------------------------------------------*/ + +int il2p_decode_payload (unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected) +{ +// Determine number of blocks and sizes. + + il2p_payload_properties_t ipp; + int e; + e = il2p_payload_compute (&ipp, payload_size, max_fec); + if (e <= 0) { + return (e); + } + + unsigned char *pin = received; + unsigned char *pout = payload_out; + int decoded_length = 0; + int failed = 0; + int rx_lfsr_state; + il2p_descramble_reset(&rx_lfsr_state); + + +// First the large blocks. + + for (int b = 0; b < ipp.large_block_count; b++) { + unsigned char corrected_block[255]; + int e = il2p_decode_rs (pin, ipp.large_block_size, ipp.parity_symbols_per_block, corrected_block); + + // dw_printf ("%s:%d: large block decode_rs returned status = %d\n", __FILE__, __LINE__, e); + + if (e < 0) failed = 1; + *symbols_corrected += e; + +// FIXME - simplify +il2p_descramble_reset(&rx_lfsr_state); + + il2p_descramble_block (corrected_block, pout, ipp.large_block_size, &rx_lfsr_state); + + if (il2p_get_debug() >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Descrambled large payload block, %d bytes:\n", ipp.large_block_size); + fx_hex_dump(pout, ipp.large_block_size); + } + + pin += ipp.large_block_size + ipp.parity_symbols_per_block; + pout += ipp.large_block_size; + decoded_length += ipp.large_block_size; + } + +// Then the small blocks. + + for (int b = 0; b < ipp.small_block_count; b++) { + unsigned char corrected_block[255]; + int e = il2p_decode_rs (pin, ipp.small_block_size, ipp.parity_symbols_per_block, corrected_block); + + // dw_printf ("%s:%d: small block decode_rs returned status = %d\n", __FILE__, __LINE__, e); + + if (e < 0) failed = 1; + *symbols_corrected += e; + +// FIXME - just reset in descramble block - no need to pass around as argument. +il2p_descramble_reset(&rx_lfsr_state); + + il2p_descramble_block (corrected_block, pout, ipp.small_block_size, &rx_lfsr_state); + + if (il2p_get_debug() >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Descrambled small payload block, %d bytes:\n", ipp.small_block_size); + fx_hex_dump(pout, ipp.small_block_size); + } + + pin += ipp.small_block_size + ipp.parity_symbols_per_block; + pout += ipp.small_block_size; + decoded_length += ipp.small_block_size; + } + + if (failed) { + //dw_printf ("%s:%d: failed = %0x\n", __FILE__, __LINE__, failed); + return (-2); + } + + if (decoded_length != payload_size) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P Internal error: decoded_length = %d, payload_size = %d\n", decoded_length, payload_size); + return (-3); + } + + return (decoded_length); + +} // end il2p_decode_payload + +// end il2p_payload.c + diff --git a/src/il2p_rec.c b/src/il2p_rec.c new file mode 100644 index 0000000..bc39bd1 --- /dev/null +++ b/src/il2p_rec.c @@ -0,0 +1,276 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 . +// + + +/******************************************************************************** + * + * File: il2p_rec.c + * + * Purpose: Extract IL2P frames from a stream of bits and process them. + * + * References: http://tarpn.net/t/il2p/il2p-specification0-4.pdf + * + *******************************************************************************/ + +#include "direwolf.h" + +#include +#include +#include +#include + +#include "textcolor.h" +#include "il2p.h" +#include "multi_modem.h" +#include "demod.h" + + +struct il2p_context_s { + + enum { IL2P_SEARCHING=0, IL2P_HEADER, IL2P_PAYLOAD, IL2P_DECODE } state; + + unsigned int acc; // Accumulate most recent 24 bits for sync word matching. + // Lower 8 bits are also used for accumulating bytes for + // the header and payload. + + int bc; // Bit counter so we know when a complete byte has been accumulated. + + int polarity; // 1 if opposite of expected polarity. + + unsigned char shdr[IL2P_HEADER_SIZE+IL2P_HEADER_PARITY]; + // Scrambled header as received over the radio. Includes parity. + int hc; // Number if bytes placed in above. + + unsigned char uhdr[IL2P_HEADER_SIZE]; // Header after FEC and unscrambling. + + int eplen; // Encoded payload length. This is not the nuumber from + // from the header but rather the number of encoded bytes to gather. + + unsigned char spayload[IL2P_MAX_ENCODED_PAYLOAD_SIZE]; + // Scrambled and encoded payload as received over the radio. + int pc; // Number of bytes placed in above. + + int corrected; // Number of symbols corrected by RS FEC. +}; + +static struct il2p_context_s *il2p_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; + + + +/*********************************************************************************** + * + * Name: il2p_rec_bit + * + * Purpose: Extract FX.25 packets from a stream of bits. + * + * Inputs: chan - Channel number. + * + * subchan - This allows multiple demodulators per channel. + * + * slice - Allows multiple slicers per demodulator (subchannel). + * + * dbit - One bit from the received data stream. + * + * Description: This is called once for each received bit. + * For each valid packet, process_rec_frame() is called for further processing. + * It can gather multiple candidates from different parallel demodulators + * ("subchannels") and slicers, then decide which one is the best. + * + ***********************************************************************************/ + +void il2p_rec_bit (int chan, int subchan, int slice, int dbit) +{ + +// Allocate context blocks only as needed. + + struct il2p_context_s *F = il2p_context[chan][subchan][slice]; + if (F == NULL) { + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + assert (slice >= 0 && slice < MAX_SLICERS); + F = il2p_context[chan][subchan][slice] = (struct il2p_context_s *)malloc(sizeof (struct il2p_context_s)); + assert (F != NULL); + memset (F, 0, sizeof(struct il2p_context_s)); + } + +// Accumulate most recent 24 bits received. Most recent is LSB. + + F->acc = ((F->acc << 1) | (dbit & 1)) & 0x00ffffff; + +// State machine to look for sync word then gather appropriate number of header and payload bytes. + + switch (F->state) { + + case IL2P_SEARCHING: // Searching for the sync word. + + if (__builtin_popcount (F->acc ^ IL2P_SYNC_WORD) <= 1) { // allow single bit mismatch + //text_color_set (DW_COLOR_INFO); + //dw_printf ("IL2P header has normal polarity\n"); + F->polarity = 0; + F->state = IL2P_HEADER; + F->bc = 0; + F->hc = 0; + } + else if (__builtin_popcount ((~F->acc & 0x00ffffff) ^ IL2P_SYNC_WORD) <= 1) { + text_color_set (DW_COLOR_INFO); + // FIXME - this pops up occasionally with random noise. Find better way to convey information. + // This also happens for each slicer - to noisy. + dw_printf ("IL2P header has reverse polarity\n"); + F->polarity = 1; + F->state = IL2P_HEADER; + F->bc = 0; + F->hc = 0; + } + break; + + case IL2P_HEADER: // Gathering the header. + + F->bc++; + if (F->bc == 8) { // full byte has been collected. + F->bc = 0; + if ( ! F->polarity) { + F->shdr[F->hc++] = F->acc & 0xff; + } + else { + F->shdr[F->hc++] = (~ F->acc) & 0xff; + } + if (F->hc == IL2P_HEADER_SIZE+IL2P_HEADER_PARITY) { // Have all of header + + if (il2p_get_debug() >= 1) { + text_color_set (DW_COLOR_DEBUG); + dw_printf ("IL2P header as received [%d.%d.%d]:\n", chan, subchan, slice); + fx_hex_dump (F->shdr, IL2P_HEADER_SIZE+IL2P_HEADER_PARITY); + } + + // Fix any errors and descramble. + F->corrected = il2p_clarify_header(F->shdr, F->uhdr); + + if (F->corrected >= 0) { // Good header. + // How much payload is expected? + il2p_payload_properties_t plprop; + int hdr_type, max_fec; + int len = il2p_get_header_attributes (F->uhdr, &hdr_type, &max_fec); + + F->eplen = il2p_payload_compute (&plprop, len, max_fec); + + if (il2p_get_debug() >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("IL2P header after correcting %d symbols and unscrambling [%d.%d.%d]:\n", F->corrected, chan, subchan, slice); + fx_hex_dump (F->uhdr, IL2P_HEADER_SIZE); + dw_printf ("Header type %d, max fec = %d\n", hdr_type, max_fec); + dw_printf ("Need to collect %d encoded bytes for %d byte payload.\n", F->eplen, len); + dw_printf ("%d small blocks of %d and %d large blocks of %d. %d parity symbols per block\n", + plprop.small_block_count, plprop.small_block_size, + plprop.large_block_count, plprop.large_block_size, plprop.parity_symbols_per_block); + } + + if (F->eplen >= 1) { // Need to gather payload. + F->pc = 0; + F->state = IL2P_PAYLOAD; + } + else if (F->eplen == 0) { // No payload. + F->pc = 0; + F->state = IL2P_DECODE; + } + else { // Error. + + if (il2p_get_debug() >= 1) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("IL2P header INVALID.\n"); + } + + F->state = IL2P_SEARCHING; + } + } // good header after FEC. + else { + F->state = IL2P_SEARCHING; // Header failed FEC check. + } + } // entire header has been collected. + } // full byte collected. + break; + + case IL2P_PAYLOAD: // Gathering the payload, if any. + + F->bc++; + if (F->bc == 8) { // full byte has been collected. + F->bc = 0; + if ( ! F->polarity) { + F->spayload[F->pc++] = F->acc & 0xff; + } + else { + F->spayload[F->pc++] = (~ F->acc) & 0xff; + } + if (F->pc == F->eplen) { + + // TODO?: for symmetry it seems like we should clarify the payload before combining. + + F->state = IL2P_DECODE; + } + } + break; + + case IL2P_DECODE: + // We get here after a good header and any payload has been collected. + // Processing is delayed by one bit but I think it makes the logic cleaner. + // During unit testing be sure to send an extra bit to flush it out at the end. + + // in uhdr[IL2P_HEADER_SIZE]; // Header after FEC and descrambling. + + // TODO?: for symmetry, we might decode the payload here and later build the frame. + + { + packet_t pp = il2p_decode_header_payload (F->uhdr, F->spayload, &(F->corrected)); + + if (il2p_get_debug() >= 1) { + if (pp != NULL) { + ax25_hex_dump (pp); + } + else { + // Most likely too many FEC errors. + text_color_set(DW_COLOR_ERROR); + dw_printf ("FAILED to construct frame in %s.\n", __func__); + } + } + + if (pp != NULL) { + alevel_t alevel = demod_get_audio_level (chan, subchan); + retry_t retries = F->corrected; + int is_fx25 = 1; // FIXME: distinguish fx.25 and IL2P. + // Currently this just means that a FEC mode was used. + + // TODO: Could we put last 3 arguments in packet object rather than passing around separately? + + multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, is_fx25); + } + } // end block for local variables. + + if (il2p_get_debug() >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("-----\n"); + } + + F->state = IL2P_SEARCHING; + break; + + } // end of switch + +} // end il2p_rec_bit + + +// end il2p_rec.c diff --git a/src/il2p_scramble.c b/src/il2p_scramble.c new file mode 100644 index 0000000..6219cdd --- /dev/null +++ b/src/il2p_scramble.c @@ -0,0 +1,157 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 . +// + +/*-------------------------------------------------------------------------------- + * + * File: il2p_scramble.c + * + * Purpose: Scramble / descramble data as specified in the IL2P protocol specification. + * + *--------------------------------------------------------------------------------*/ + + +#include "direwolf.h" + +#include +#include +#include +#include + +#include "il2p.h" + + +// Scramble bits for il2p transmit. + +// Note that there is a delay of 5 until the first bit comes out. +// So we need to need to ignore the first 5 out and stick in +// an extra 5 filler bits to flush at the end. + +#define INIT_TX_LSFR 0x00f + +static inline int scramble_bit (int in, int *state) +{ + int out = ((*state >> 4) ^ *state) & 1; + *state = ( (((in ^ *state) & 1) << 9) | (*state ^ ((*state & 1) << 4)) ) >> 1; + return (out); +} + + +// Undo data scrambling for il2p receive. + +#define INIT_RX_LSFR 0x1f0 + +static inline int descramble_bit (int in, int *state) +{ + int out = (in ^ *state) & 1; + *state = ((*state >> 1) | ((in & 1) << 8)) ^ ((in & 1) << 3); + return (out); +} + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_scramble_block + * + * Purpose: Scramble a block before adding RS parity. + * + * Inputs: in Array of bytes. + * len Number of bytes both in and out. + * + * Outputs: out Array of bytes. + * + *--------------------------------------------------------------------------------*/ + +void il2p_scramble_block (unsigned char *in, unsigned char *out, int len) +{ + int tx_lfsr_state = INIT_TX_LSFR; + + memset (out, 0, len); + + int skipping = 1; // Discard the first 5 out. + int ob = 0; // Index to output byte. + int om = 0x80; // Output bit mask; + for (int ib = 0; ib < len; ib++) { + for (int im = 0x80; im != 0; im >>= 1) { + int s = scramble_bit((in[ib] & im) != 0, &tx_lfsr_state); + if (ib == 0 && im == 0x04) skipping = 0; + if ( ! skipping) { + if (s) { + out[ob] |= om; + } + om >>= 1; + if (om == 0) { + om = 0x80; + ob++; + } + } + } + } + // Flush it. + + // This is a relic from when I thought the state would need to + // be passed along for the next block. + // Preserve the LSFR state from before flushing. + // This might be needed as the initial state for later payload blocks. + int x = tx_lfsr_state; + for (int n = 0; n < 5; n++) { + int s = scramble_bit(0, &x); + if (s) { + out[ob] |= om; + } + om >>=1; + if (om == 0) { + om = 0x80; + ob++; + } + } + +} // end il2p_scramble_block + + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_descramble_block + * + * Purpose: Descramble a block after removing RS parity. + * + * Inputs: in Array of bytes. + * len Number of bytes both in and out. + * + * Outputs: out Array of bytes. + * + *--------------------------------------------------------------------------------*/ + +void il2p_descramble_block (unsigned char *in, unsigned char *out, int len) +{ + int rx_lfsr_state = INIT_RX_LSFR; + + memset (out, 0, len); + + for (int b = 0; b < len; b++) { + for (int m = 0x80; m != 0; m >>= 1) { + int d = descramble_bit((in[b] & m) != 0, &rx_lfsr_state); + if (d) { + out[b] |= m; + } + } + } +} + +// end il2p_scramble.c diff --git a/src/il2p_scramble.c-bak16 b/src/il2p_scramble.c-bak16 new file mode 100644 index 0000000..fc4bf05 --- /dev/null +++ b/src/il2p_scramble.c-bak16 @@ -0,0 +1,194 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 . +// + +// FIXME: be consistent about descramble or descramble. not unscramble. + +/*-------------------------------------------------------------------------------- + * + * File: il2p_scramble.c + * + * Purpose: Scramble / descramble data as specified in the IL2P protocol specification. + * + *--------------------------------------------------------------------------------*/ + + +#include "direwolf.h" + +#include +#include +#include +#include + +#include "il2p.h" + + +// Scramble bits for il2p transmit. + +// Note that there is a delay of 5 until the first bit comes out. +// So we need to need to ignore the first 5 out and stick in +// an extra 5 filler bits to flush at the end. + +#define INIT_TX_LSFR 0x00f + +static inline int scramble_bit (int in, int *state) +{ + int out = ((*state >> 4) ^ *state) & 1; + *state = ( (((in ^ *state) & 1) << 9) | (*state ^ ((*state & 1) << 4)) ) >> 1; + return (out); +} + + +// Undo data scrambling for il2p receive. + +#define INIT_RX_LSFR 0x1f0 + +static inline int descramble_bit (int in, int *state) +{ + int out = (in ^ *state) & 1; + *state = ((*state >> 1) | ((in & 1) << 8)) ^ ((in & 1) << 3); + return (out); +} + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_scramble_reset + * + * Purpose: Reset the TX LFSR register at the beginning of a packet or payload. + * + * Outputs: lfsr_state Current state of transmit LFSR. + * This is reset at the start of the packet and is + * cummulative across the header and data blocks. + * + *--------------------------------------------------------------------------------*/ + +void il2p_scramble_reset (int *lfsr_state) +{ + *lfsr_state = INIT_TX_LSFR; +} + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_scramble_block + * + * Purpose: Scramble a block before adding RS parity. + * + * Inputs: in Array of bytes. + * len Number of bytes both in and out. + * + * Outputs: out Array of bytes. + * + * In/Out: lfsr_state Current state of transmit LFSR. ---- FIXME --- remove this + * + *--------------------------------------------------------------------------------*/ + +void il2p_scramble_block (unsigned char *in, unsigned char *out, int len, int *lfsr_state) +{ + il2p_scramble_reset (lfsr_state); + + memset (out, 0, len); + + int skipping = 1; // Discard the first 5 out. + int ob = 0; // Index to output byte. + int om = 0x80; // Output bit mask; + for (int ib = 0; ib < len; ib++) { + for (int im = 0x80; im != 0; im >>= 1) { + int s = scramble_bit((in[ib] & im) != 0, lfsr_state); + if (ib == 0 && im == 0x04) skipping = 0; + if ( ! skipping) { + if (s) { + out[ob] |= om; + } + om >>= 1; + if (om == 0) { + om = 0x80; + ob++; + } + } + } + } + // Flush it. + + // Preserve the LSFR state from before flushing. + // This might be needed as the initial state for later payload blocks. + int x = *lfsr_state; + for (int n = 0; n < 5; n++) { + int s = scramble_bit(0, &x); + if (s) { + out[ob] |= om; + } + om >>=1; + if (om == 0) { + om = 0x80; + ob++; + } + } + +} // end il2p_scramble_block + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_descramble_reset + * + * Purpose: Reset the RX LFSR register at the beginning of a packet or payload. + * + * Outputs: lfsr_state Current state of transmit LFSR. + * This is cummulative across the payload data blocks. + * + *--------------------------------------------------------------------------------*/ + +void il2p_descramble_reset (int *lfsr_state) +{ + *lfsr_state = INIT_RX_LSFR; +} + + + +/*-------------------------------------------------------------------------------- + * + * Function: il2p_descramble_block + * + * Purpose: Unscramble a block after removing RS parity. + * + * Inputs: in Array of bytes. + * len Number of bytes both in and out. + * + * Outputs: out Array of bytes. + * + * In/Out: lfsr_state Current state of receive LSFR. --- FIXME - remove this. + * + *--------------------------------------------------------------------------------*/ + +void il2p_descramble_block (unsigned char *in, unsigned char *out, int len, int *lfsr_state) +{ + memset (out, 0, len); + + for (int b = 0; b < len; b++) { + for (int m = 0x80; m != 0; m >>= 1) { + int d = descramble_bit((in[b] & m) != 0, lfsr_state); + if (d) { + out[b] |= m; + } + } + } +} + +// end il2p_scramble.c diff --git a/src/il2p_send.c b/src/il2p_send.c new file mode 100644 index 0000000..3c4554e --- /dev/null +++ b/src/il2p_send.c @@ -0,0 +1,147 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "il2p.h" +#include "textcolor.h" +#include "audio.h" +#include "gen_tone.h" + + +static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "il2p_send_frame" + +static void send_bytes (int chan, unsigned char *b, int count, int polarity); +static void send_bit (int chan, int b, int polarity); + + + +/*------------------------------------------------------------- + * + * Name: il2p_send_frame + * + * Purpose: Convert frames to a stream of bits in IL2P format. + * + * Inputs: chan - Audio channel number, 0 = first. + * + * pp - Pointer to packet object. + * + * max_fec - 1 to force 16 parity symbols for each payload block. + * 0 for automatic depending on block size. + * + * polarity - 0 for normal. 1 to invert signal. + * 2 special case for testing - introduce some errors to test FEC. + * + * Outputs: Bits are shipped out by calling tone_gen_put_bit(). + * + * Returns: Number of bits sent including + * - Preamble (01010101...) + * - 3 byte Sync Word. + * - 15 bytes for Header. + * - Optional payload. + * The required time can be calculated by dividing this + * number by the transmit rate of bits/sec. + * -1 is returned for failure. + * + * Description: Generate an IL2P encoded frame. + * + * Assumptions: It is assumed that the tone_gen module has been + * properly initialized so that bits sent with + * tone_gen_put_bit() are processed correctly. + * + * Errors: Return -1 for error. Probably frame too large. + * + * Note: Inconsistency here. ax25 version has just a byte array + * and length going in. Here we need the full packet object. + * + *--------------------------------------------------------------*/ + +int il2p_send_frame (int chan, packet_t pp, int max_fec, int polarity) +{ + unsigned char encoded[IL2P_MAX_PACKET_SIZE]; + + encoded[0] = ( IL2P_SYNC_WORD >> 16 ) & 0xff; + encoded[1] = ( IL2P_SYNC_WORD >> 8 ) & 0xff; + encoded[2] = ( IL2P_SYNC_WORD ) & 0xff; + + int elen = il2p_encode_frame (pp, max_fec, encoded + IL2P_SYNC_WORD_SIZE); + if (elen <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("IL2P: Unable to encode frame into IL2P.\n"); + return (-1); + } + + elen += IL2P_SYNC_WORD_SIZE; + + number_of_bits_sent[chan] = 0; + + if (il2p_get_debug() >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("IL2P frame, max_fec = %d, %d encoded bytes total\n", max_fec, elen); + fx_hex_dump (encoded, elen); + } + + // Clobber some bytes for testing. + if (polarity >= 2) { + for (int j = 10; j < elen; j+=100) { + encoded[j] = ~ encoded[j]; + } + } + + // Send bits to modulator. + + static unsigned char preamble = IL2P_PREAMBLE; + + send_bytes (chan, &preamble, 1, polarity); + send_bytes (chan, encoded, elen, polarity); + + return (number_of_bits_sent[chan]); +} + + +static void send_bytes (int chan, unsigned char *b, int count, int polarity) +{ + for (int j = 0; j < count; j++) { + unsigned int x = b[j]; + for (int k = 0; k < 8; k++) { + send_bit (chan, (x & 0x80) != 0, polarity); + x <<= 1; + } + } +} + +// NRZI would be applied for AX.25 but IL2P does not use it. +// However we do have an option to invert the signal. +// The direwolf receive implementation will automatically compensate +// for either polarity but other implementations might not. + +static void send_bit (int chan, int b, int polarity) +{ + tone_gen_put_bit (chan, (b ^ polarity) & 1); + number_of_bits_sent[chan]++; +} + + + +// end il2p_send.c \ No newline at end of file diff --git a/src/il2p_test.c b/src/il2p_test.c new file mode 100644 index 0000000..2d6d432 --- /dev/null +++ b/src/il2p_test.c @@ -0,0 +1,977 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "textcolor.h" +#include "il2p.h" +#include "ax25_pad.h" +#include "ax25_pad2.h" +#include "multi_modem.h" + + +static void test_scramble(void); +static void test_rs(void); +static void test_payload(void); +static void test_example_headers(void); +static void all_frame_types(void); +static void test_serdes(void); +static void decode_bitstream(void); + +/*------------------------------------------------------------- + * + * Name: il2p_test.c + * + * Purpose: Unit tests for IL2P protocol functions. + * + * Errors: Die if anything goes wrong. + * + *--------------------------------------------------------------*/ + + +int main () +{ + int enable_color = 1; + text_color_init (enable_color); + + int enable_debug_out = 0; + il2p_init(enable_debug_out); + + text_color_set(DW_COLOR_INFO); + dw_printf ("Begin IL2P unit tests.\n"); + +// These start simple and later complex cases build upon earlier successes. + +// Test scramble and descramble. + + test_scramble(); + +// Test Reed Solomon error correction. + + test_rs(); + +// Test payload functions. + + test_payload(); + +// Try encoding the example headers in the protocol spec. + + test_example_headers(); + +// Convert all of the AX.25 frame types to IL2P and back again. + + all_frame_types(); + +// Use same serialize / deserialize functions used on the air. + + test_serdes (); + +// Decode bitstream from demodulator if data file is available. +// TODO: Very large info parts. Appropriate error if too long. +// TODO: More than 2 addresses. + + decode_bitstream(); + + text_color_set(DW_COLOR_REC); + dw_printf ("\n----------\n\n"); + dw_printf ("\nSUCCESS!\n"); + + return (EXIT_SUCCESS); +} + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test scrambling and descrambling. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void test_scramble(void) +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("Test scrambling...\n"); + +// First an example from the protocol specification to make sure I'm compatible. + + static unsigned char scramin1[] = { 0x63, 0xf1, 0x40, 0x40, 0x40, 0x00, 0x6b, 0x2b, 0x54, 0x28, 0x25, 0x2a, 0x0f }; + static unsigned char scramout1[] = { 0x6a, 0xea, 0x9c, 0xc2, 0x01, 0x11, 0xfc, 0x14, 0x1f, 0xda, 0x6e, 0xf2, 0x53 }; + unsigned char scramout[sizeof(scramin1)]; + + il2p_scramble_block (scramin1, scramout, sizeof(scramin1)); + assert (memcmp(scramout, scramout, sizeof(scramout1)) == 0); + +} // end test_scramble. + + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test Reed Solomon encode/decode examples found in the protocol spec. +// The data part is scrambled but that does not matter here because. +// We are only concerned abound adding the parity and verifying. +// +///////////////////////////////////////////////////////////////////////////////////////////// + + +static void test_rs() +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("Test Reed Solomon functions...\n"); + + static unsigned char example_s[] = { 0x26, 0x57, 0x4d, 0x57, 0xf1, 0x96, 0xcc, 0x85, 0x42, 0xe7, 0x24, 0xf7, 0x2e, + 0x8a, 0x97 }; + unsigned char parity_out[2]; + il2p_encode_rs (example_s, 13, 2, parity_out); + //dw_printf ("DEBUG RS encode %02x %02x\n", parity_out[0], parity_out[1]); + assert (memcmp(parity_out, example_s + 13, 2) == 0); + + + static unsigned char example_u[] = { 0x6a, 0xea, 0x9c, 0xc2, 0x01, 0x11, 0xfc, 0x14, 0x1f, 0xda, 0x6e, 0xf2, 0x53, + 0x91, 0xbd }; + il2p_encode_rs (example_u, 13, 2, parity_out); + //dw_printf ("DEBUG RS encode %02x %02x\n", parity_out[0], parity_out[1]); + assert (memcmp(parity_out, example_u + 13, 2) == 0); + + // See if we can go the other way. + + unsigned char received[15]; + unsigned char corrected[15]; + int e; + + e = il2p_decode_rs (example_s, 13, 2, corrected); + assert (e == 0); + assert (memcmp(example_s, corrected, 13) == 0); + + memcpy (received, example_s, 15); + received[0] = '?'; + e = il2p_decode_rs (received, 13, 2, corrected); + assert (e == 1); + assert (memcmp(example_s, corrected, 13) == 0); + + e = il2p_decode_rs (example_u, 13, 2, corrected); + assert (e == 0); + assert (memcmp(example_u, corrected, 13) == 0); + + memcpy (received, example_u, 15); + received[12] = '?'; + e = il2p_decode_rs (received, 13, 2, corrected); + assert (e == 1); + assert (memcmp(example_u, corrected, 13) == 0); + + received[1] = '?'; + received[2] = '?'; + e = il2p_decode_rs (received, 13, 2, corrected); + assert (e == -1); +} + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test payload functions. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void test_payload(void) +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("Test payload functions...\n"); + + il2p_payload_properties_t ipp; + int e; + +// Examples in specification. + + e = il2p_payload_compute (&ipp, 100, 0); + assert (ipp.small_block_size == 100); + assert (ipp.large_block_size == 101); + assert (ipp.large_block_count == 0); + assert (ipp.small_block_count == 1); + assert (ipp.parity_symbols_per_block == 4); + + e = il2p_payload_compute (&ipp, 236, 0); + assert (ipp.small_block_size == 236); + assert (ipp.large_block_size == 237); + assert (ipp.large_block_count == 0); + assert (ipp.small_block_count == 1); + assert (ipp.parity_symbols_per_block == 8); + + e = il2p_payload_compute (&ipp, 512, 0); + assert (ipp.small_block_size == 170); + assert (ipp.large_block_size == 171); + assert (ipp.large_block_count == 2); + assert (ipp.small_block_count == 1); + assert (ipp.parity_symbols_per_block == 6); + + e = il2p_payload_compute (&ipp, 1023, 0); + assert (ipp.small_block_size == 204); + assert (ipp.large_block_size == 205); + assert (ipp.large_block_count == 3); + assert (ipp.small_block_count == 2); + assert (ipp.parity_symbols_per_block == 8); + +// Now try all possible sizes for Baseline FEC Parity. + + for (int n = 1; n <= IL2P_MAX_PAYLOAD_SIZE; n++) { + e = il2p_payload_compute (&ipp, n, 0); + //dw_printf ("bytecount=%d, smallsize=%d, largesize=%d, largecount=%d, smallcount=%d\n", n, + // ipp.small_block_size, ipp.large_block_size, + // ipp.large_block_count, ipp.small_block_count); + //fflush (stdout); + + assert (ipp.payload_block_count >= 1 && ipp.payload_block_count <= IL2P_MAX_PAYLOAD_BLOCKS); + assert (ipp.payload_block_count == ipp.small_block_count + ipp.large_block_count); + assert (ipp.small_block_count * ipp.small_block_size + + ipp.large_block_count * ipp.large_block_size == n); + assert (ipp.parity_symbols_per_block == 2 || + ipp.parity_symbols_per_block == 4 || + ipp.parity_symbols_per_block == 6 || + ipp.parity_symbols_per_block == 8); + + // Data and parity must fit in RS block size of 255. + // Size test does not apply if block count is 0. + assert (ipp.small_block_count == 0 || ipp.small_block_size + ipp.parity_symbols_per_block <= 255); + assert (ipp.large_block_count == 0 || ipp.large_block_size + ipp.parity_symbols_per_block <= 255); + } + +// All sizes for MAX FEC. + + for (int n = 1; n <= IL2P_MAX_PAYLOAD_SIZE; n++) { + e = il2p_payload_compute (&ipp, n, 1); // 1 for max fec. + //dw_printf ("bytecount=%d, smallsize=%d, largesize=%d, largecount=%d, smallcount=%d\n", n, + // ipp.small_block_size, ipp.large_block_size, + // ipp.large_block_count, ipp.small_block_count); + //fflush (stdout); + + assert (ipp.payload_block_count >= 1 && ipp.payload_block_count <= IL2P_MAX_PAYLOAD_BLOCKS); + assert (ipp.payload_block_count == ipp.small_block_count + ipp.large_block_count); + assert (ipp.small_block_count * ipp.small_block_size + + ipp.large_block_count * ipp.large_block_size == n); + assert (ipp.parity_symbols_per_block == 16); + + // Data and parity must fit in RS block size of 255. + // Size test does not apply if block count is 0. + assert (ipp.small_block_count == 0 || ipp.small_block_size + ipp.parity_symbols_per_block <= 255); + assert (ipp.large_block_count == 0 || ipp.large_block_size + ipp.parity_symbols_per_block <= 255); + } + +// Now let's try encoding payloads and extracting original again. +// This will also provide exercise for scrambling and Reed Solomon under more conditions. + + unsigned char original_payload[IL2P_MAX_PAYLOAD_SIZE]; + for (int n = 0; n < IL2P_MAX_PAYLOAD_SIZE; n++) { + original_payload[n] = n & 0xff; + } + for (int max_fec = 0; max_fec <= 1; max_fec++) { + for (int payload_length = 1; payload_length <= IL2P_MAX_PAYLOAD_SIZE; payload_length++) { + //dw_printf ("\n--------- max_fec = %d, payload_length = %d\n", max_fec, payload_length); + unsigned char encoded[IL2P_MAX_ENCODED_PAYLOAD_SIZE]; + int k = il2p_encode_payload (original_payload, payload_length, max_fec, encoded); + + //dw_printf ("payload length %d %s -> %d\n", payload_length, max_fec ? "M" : "", k); + assert (k > payload_length && k <= IL2P_MAX_ENCODED_PAYLOAD_SIZE); + + // Now extract. + + unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE]; + int symbols_corrected = 0; + int e = il2p_decode_payload (encoded, payload_length, max_fec, extracted, &symbols_corrected); + //dw_printf ("e = %d, payload_length = %d\n", e, payload_length); + assert (e == payload_length); + + // if (memcmp (original_payload, extracted, payload_length) != 0) { + // dw_printf ("********** Received message not as expected. **********\n"); + // fx_hex_dump(extracted, payload_length); + // } + assert (memcmp (original_payload, extracted, payload_length) == 0); + } + } + (void)e; +} // end test_payload + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test header examples found in protocol specification. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void test_example_headers() +{ + +//----------- Example 1: AX.25 S-Frame -------------- + +// This frame sample only includes a 15 byte header, without PID field. +// Destination Callsign: ?KA2DEW-2 +// Source Callsign: ?KK4HEJ-7 +// N(R): 5 +// P/F: 1 +// C: 1 +// Control Opcode: 00 (Receive Ready) +// +// AX.25 data: +// 96 82 64 88 8a ae e4 96 96 68 90 8a 94 6f b1 +// +// IL2P Data Prior to Scrambling and RS Encoding: +// 2b a1 12 24 25 77 6b 2b 54 68 25 2a 27 +// +// IL2P Data After Scrambling and RS Encoding: +// 26 57 4d 57 f1 96 cc 85 42 e7 24 f7 2e 8a 97 + + text_color_set(DW_COLOR_INFO); + dw_printf ("Example 1: AX.25 S-Frame...\n"); + + static unsigned char example1[] = {0x96, 0x82, 0x64, 0x88, 0x8a, 0xae, 0xe4, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x6f, 0xb1}; + static unsigned char header1[] = {0x2b, 0xa1, 0x12, 0x24, 0x25, 0x77, 0x6b, 0x2b, 0x54, 0x68, 0x25, 0x2a, 0x27 }; + unsigned char header[IL2P_HEADER_SIZE]; + unsigned char sresult[32]; + memset (header, 0, sizeof(header)); + memset (sresult, 0, sizeof(sresult)); + unsigned char check[2]; + alevel_t alevel; + memset(&alevel, 0, sizeof(alevel)); + + packet_t pp = ax25_from_frame (example1, sizeof(example1), alevel); + assert (pp != NULL); + int e; + e = il2p_type_1_header (pp, 0, header); + assert (e == 0); + ax25_delete(pp); + + //dw_printf ("Example 1 header:\n"); + //for (int i = 0 ; i < sizeof(header); i++) { + // dw_printf (" %02x", header[i]); + //} + ///dw_printf ("\n"); + + assert (memcmp(header, header1, sizeof(header)) == 0); + + il2p_scramble_block (header, sresult, 13); + //dw_printf ("Expect scrambled 26 57 4d 57 f1 96 cc 85 42 e7 24 f7 2e\n"); + //for (int i = 0 ; i < sizeof(sresult); i++) { + // dw_printf (" %02x", sresult[i]); + //} + //dw_printf ("\n"); + + il2p_encode_rs (sresult, 13, 2, check); + + //dw_printf ("expect checksum = 8a 97\n"); + //dw_printf ("check = "); + //for (int i = 0 ; i < sizeof(check); i++) { + // dw_printf (" %02x", check[i]); + //} + //dw_printf ("\n"); + assert (check[0] == 0x8a); + assert (check[1] == 0x97); + +// Can we go from IL2P back to AX.25? + + pp = il2p_decode_header_type_1 (header, 0); + assert (pp != NULL); + + char dst_addr[AX25_MAX_ADDR_LEN]; + char src_addr[AX25_MAX_ADDR_LEN]; + + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr); + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr); + + ax25_frame_type_t frame_type; + cmdres_t cr; // command or response. + char description[64]; + int pf; // Poll/Final. + int nr, ns; // Sequence numbers. + + frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns); + (void)frame_type; +#if 1 + dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description); +#endif +// TODO: compare binary. + ax25_delete (pp); + + dw_printf ("Example 1 header OK\n"); + + +// -------------- Example 2 - UI frame, no info part ------------------ + +// This is an AX.25 Unnumbered Information frame, such as APRS. +// Destination Callsign: ?CQ -0 +// Source Callsign: ?KK4HEJ-15 +// P/F: 0 +// C: 0 +// Control Opcode: 3 Unnumbered Information +// PID: 0xF0 No L3 +// +// AX.25 Data: +// 86 a2 40 40 40 40 60 96 96 68 90 8a 94 7f 03 f0 +// +// IL2P Data Prior to Scrambling and RS Encoding: +// 63 f1 40 40 40 00 6b 2b 54 28 25 2a 0f +// +// IL2P Data After Scrambling and RS Encoding: +// 6a ea 9c c2 01 11 fc 14 1f da 6e f2 53 91 bd + + + //dw_printf ("---------- example 2 ------------\n"); + static unsigned char example2[] = { 0x86, 0xa2, 0x40, 0x40, 0x40, 0x40, 0x60, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x7f, 0x03, 0xf0 }; + static unsigned char header2[] = { 0x63, 0xf1, 0x40, 0x40, 0x40, 0x00, 0x6b, 0x2b, 0x54, 0x28, 0x25, 0x2a, 0x0f }; + memset (header, 0, sizeof(header)); + memset (sresult, 0, sizeof(sresult)); + memset(&alevel, 0, sizeof(alevel)); + + pp = ax25_from_frame (example2, sizeof(example2), alevel); + assert (pp != NULL); + e = il2p_type_1_header (pp, 0, header); + assert (e == 0); + ax25_delete(pp); + + //dw_printf ("Example 2 header:\n"); + //for (int i = 0 ; i < sizeof(header); i++) { + // dw_printf (" %02x", header[i]); + //} + //dw_printf ("\n"); + + assert (memcmp(header, header2, sizeof(header2)) == 0); + + il2p_scramble_block (header, sresult, 13); + //dw_printf ("Expect scrambled 6a ea 9c c2 01 11 fc 14 1f da 6e f2 53\n"); + //for (int i = 0 ; i < sizeof(sresult); i++) { + // dw_printf (" %02x", sresult[i]); + //} + //dw_printf ("\n"); + + il2p_encode_rs (sresult, 13, 2, check); + + //dw_printf ("expect checksum = 91 bd\n"); + //dw_printf ("check = "); + //for (int i = 0 ; i < sizeof(check); i++) { + // dw_printf (" %02x", check[i]); + //} + //dw_printf ("\n"); + assert (check[0] == 0x91); + assert (check[1] == 0xbd); + +// Can we go from IL2P back to AX.25? + + pp = il2p_decode_header_type_1 (header, 0); + assert (pp != NULL); + + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr); + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr); + + frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns); + (void)frame_type; +#if 1 + dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description); +#endif +// TODO: compare binary. + + ax25_delete (pp); +// TODO: more examples + + dw_printf ("Example 2 header OK\n"); + + +// -------------- Example 3 - I Frame ------------------ + +// This is an AX.25 I-Frame with 9 bytes of information after the 16 byte header. +// +// Destination Callsign: ?KA2DEW-2 +// Source Callsign: ?KK4HEJ-2 +// P/F: 1 +// C: 1 +// N(R): 5 +// N(S): 4 +// AX.25 PID: 0xCF TheNET +// IL2P Payload Byte Count: 9 +// +// AX.25 Data: +// 96 82 64 88 8a ae e4 96 96 68 90 8a 94 65 b8 cf 30 31 32 33 34 35 36 37 38 +// +// IL2P Scrambled and Encoded Data: +// 26 13 6d 02 8c fe fb e8 aa 94 2d 6a 34 43 35 3c 69 9f 0c 75 5a 38 a1 7f f3 fc + + + //dw_printf ("---------- example 3 ------------\n"); + static unsigned char example3[] = { 0x96, 0x82, 0x64, 0x88, 0x8a, 0xae, 0xe4, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x65, 0xb8, 0xcf, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; + static unsigned char header3[] = { 0x2b, 0xe1, 0x52, 0x64, 0x25, 0x77, 0x6b, 0x2b, 0xd4, 0x68, 0x25, 0xaa, 0x22 }; + static unsigned char complete3[] = { 0x26, 0x13, 0x6d, 0x02, 0x8c, 0xfe, 0xfb, 0xe8, 0xaa, 0x94, 0x2d, 0x6a, 0x34, 0x43, 0x35, 0x3c, 0x69, 0x9f, 0x0c, 0x75, 0x5a, 0x38, 0xa1, 0x7f, 0xf3, 0xfc }; + memset (header, 0, sizeof(header)); + memset (sresult, 0, sizeof(sresult)); + memset(&alevel, 0, sizeof(alevel)); + + pp = ax25_from_frame (example3, sizeof(example3), alevel); + assert (pp != NULL); + e = il2p_type_1_header (pp, 0, header); + assert (e == 9); + ax25_delete(pp); + + //dw_printf ("Example 3 header:\n"); + //for (int i = 0 ; i < sizeof(header); i++) { + // dw_printf (" %02x", header[i]); + //} + //dw_printf ("\n"); + + assert (memcmp(header, header3, sizeof(header)) == 0); + + il2p_scramble_block (header, sresult, 13); + //dw_printf ("Expect scrambled 26 13 6d 02 8c fe fb e8 aa 94 2d 6a 34\n"); + //for (int i = 0 ; i < sizeof(sresult); i++) { + // dw_printf (" %02x", sresult[i]); + //} + //dw_printf ("\n"); + + il2p_encode_rs (sresult, 13, 2, check); + + //dw_printf ("expect checksum = 43 35\n"); + //dw_printf ("check = "); + //for (int i = 0 ; i < sizeof(check); i++) { + // dw_printf (" %02x", check[i]); + //} + //dw_printf ("\n"); + + assert (check[0] == 0x43); + assert (check[1] == 0x35); + + // That was only the header. We will get to the info part in a later test. + +// Can we go from IL2P back to AX.25? + + pp = il2p_decode_header_type_1 (header, 0); + assert (pp != NULL); + + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr); + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr); + + frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns); + (void)frame_type; +#if 1 + dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description); +#endif +// TODO: compare binary. + + ax25_delete (pp); + dw_printf ("Example 3 header OK\n"); + +// Example 3 again, this time the Information part is included. + + pp = ax25_from_frame (example3, sizeof(example3), alevel); + assert (pp != NULL); + + int max_fec = 0; + unsigned char iout[IL2P_MAX_PACKET_SIZE]; + e = il2p_encode_frame (pp, max_fec, iout); + + //dw_printf ("expected for example 3:\n"); + //fx_hex_dump(complete3, sizeof(complete3)); + //dw_printf ("actual result for example 3:\n"); + //fx_hex_dump(iout, e); + // Does it match the example in the protocol spec? + assert (e == sizeof(complete3)); + assert (memcmp(iout, complete3, sizeof(complete3)) == 0); + ax25_delete (pp); + + dw_printf ("Example 3 with info OK\n"); + +} // end test_example_headers + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test all of the frame types. +// +// Encode to IL2P format, decode, and verify that the result is the same as the original. +// +///////////////////////////////////////////////////////////////////////////////////////////// + + +static void enc_dec_compare (packet_t pp1) +{ + for (int max_fec = 0; max_fec <= 1; max_fec++) { + + unsigned char encoded[IL2P_MAX_PACKET_SIZE]; + int enc_len; + enc_len = il2p_encode_frame (pp1, max_fec, encoded); + assert (enc_len >= 0); + + packet_t pp2; + pp2 = il2p_decode_frame (encoded); + assert (pp2 != NULL); + +// Is it the same after encoding to IL2P and then decoding? + + int len1 = ax25_get_frame_len (pp1); + unsigned char *data1 = ax25_get_frame_data_ptr (pp1); + + int len2 = ax25_get_frame_len (pp2); + unsigned char *data2 = ax25_get_frame_data_ptr (pp2); + + if (len1 != len2 || memcmp(data1, data2, len1) != 0) { + + dw_printf ("\nEncode/Decode Error. Original:\n"); + ax25_hex_dump (pp1); + + dw_printf ("IL2P encoded as:\n"); + fx_hex_dump(encoded, enc_len); + + dw_printf ("Got turned into this:\n"); + ax25_hex_dump (pp2); + } + + assert (len1 == len2 && memcmp(data1, data2, len1) == 0); + + ax25_delete (pp2); + } +} + +static void all_frame_types(void) +{ + char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; + int num_addr = 2; + cmdres_t cr; + ax25_frame_type_t ftype; + int pf = 0; + int pid = 0xf0; + int modulo; + int nr, ns; + unsigned char *pinfo = NULL; + int info_len = 0; + packet_t pp; + + strcpy (addrs[0], "W2UB"); + strcpy (addrs[1], "WB2OSZ-12"); + num_addr = 2; + + text_color_set(DW_COLOR_INFO); + dw_printf ("Testing all frame types.\n"); + +/* U frame */ + + dw_printf ("\nU frames...\n"); + + for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) { + + for (pf = 0; pf <= 1; pf++) { + + int cmin = 0, cmax = 1; + + switch (ftype) { + // 0 = response, 1 = command + case frame_type_U_SABME: cmin = 1; cmax = 1; break; + case frame_type_U_SABM: cmin = 1; cmax = 1; break; + case frame_type_U_DISC: cmin = 1; cmax = 1; break; + case frame_type_U_DM: cmin = 0; cmax = 0; break; + case frame_type_U_UA: cmin = 0; cmax = 0; break; + case frame_type_U_FRMR: cmin = 0; cmax = 0; break; + case frame_type_U_UI: cmin = 0; cmax = 1; break; + case frame_type_U_XID: cmin = 0; cmax = 1; break; + case frame_type_U_TEST: cmin = 0; cmax = 1; break; + default: break; // avoid compiler warning. + } + + for (cr = cmin; cr <= cmax; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct U frame, cr=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_u_frame (addrs, num_addr, cr, ftype, pf, pid, pinfo, info_len); + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + } + } + + +/* S frame */ + + //strcpy (addrs[2], "DIGI1-1"); + //num_addr = 3; + + dw_printf ("\nS frames...\n"); + + for (ftype = frame_type_S_RR; ftype <= frame_type_S_SREJ; ftype++) { + + for (pf = 0; pf <= 1; pf++) { + + modulo = 8; + nr = modulo / 2 + 1; + + // SREJ can only be response. + + for (cr = 0; cr <= (int)(ftype!=frame_type_S_SREJ); cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + + modulo = 128; + nr = modulo / 2 + 1; + + for (cr = 0; cr <= (int)(ftype!=frame_type_S_SREJ); cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + } + } + +/* SREJ is only S frame which can have information part. */ + + static unsigned char srej_info[] = { 1<<1, 2<<1, 3<<1, 4<<1 }; + + ftype = frame_type_S_SREJ; + for (pf = 0; pf <= 1; pf++) { + + modulo = 128; + nr = 127; + cr = cr_res; + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct Multi-SREJ S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, srej_info, (int)(sizeof(srej_info))); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + + +/* I frame */ + + dw_printf ("\nI frames...\n"); + + pinfo = (unsigned char*)"The rain in Spain stays mainly on the plain."; + info_len = strlen((char*)pinfo); + + for (pf = 0; pf <= 1; pf++) { + + modulo = 8; + nr = 0x55 & (modulo - 1); + ns = 0xaa & (modulo - 1); + + for (cr = 1; cr <= 1; cr++) { // can only be command + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + + modulo = 128; + nr = 0x55 & (modulo - 1); + ns = 0xaa & (modulo - 1); + + for (cr = 1; cr <= 1; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + } + +} // end all_frame_types + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test bitstream tapped off from demodulator. +// +// 5 frames were sent to Nino TNC and a recording was made. +// This was demodulated and the resulting bit stream saved to a file. +// +// No automatic test here - must be done manually with audio recording. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static int decoding_bitstream = 0; + +static void decode_bitstream(void) +{ + dw_printf("-----\nReading il2p-bitstream.txt if available...\n"); + + FILE *fp = fopen ("il2p-bitstream.txt", "r"); + if (fp == NULL) { + dw_printf ("Bitstream test file not available.\n"); + return; + } + + decoding_bitstream = 1; + int save_previous = il2p_get_debug(); + il2p_set_debug (1); + + int ch; + while ( (ch = fgetc(fp)) != EOF) { + + if (ch == '0' || ch == '1') { + il2p_rec_bit (0, 0, 0, ch - '0'); + } + } + fclose(fp); + il2p_set_debug (save_previous); + decoding_bitstream = 0; + +} // end decode_bitstream + + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test serialize / deserialize. +// +// This uses same functions used on the air. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static char addrs2[] = "AA1AAA-1>ZZ9ZZZ-9"; +static char addrs3[] = "AA1AAA-1>ZZ9ZZZ-9,DIGI*"; +static char text[] = + "'... As I was saying, that seems to be done right - though I haven't time to look it over thoroughly just now - and that shows that there are three hundred and sixty-four days when you might get un-birthday presents -'" + "\n" + "'Certainly,' said Alice." + "\n" + "'And only one for birthday presents, you know. There's glory for you!'" + "\n" + "'I don't know what you mean by \"glory\",' Alice said." + "\n" + "Humpty Dumpty smiled contemptuously. 'Of course you don't - till I tell you. I meant \"there's a nice knock-down argument for you!\"'" + "\n" + "'But \"glory\" doesn't mean \"a nice knock-down argument\",' Alice objected." + "\n" + "'When I use a word,' Humpty Dumpty said, in rather a scornful tone, 'it means just what I choose it to mean - neither more nor less.'" + "\n" + "'The question is,' said Alice, 'whether you can make words mean so many different things.'" + "\n" + "'The question is,' said Humpty Dumpty, 'which is to be master - that's all.'" + "\n" ; + + +static int rec_count = -1; // disable deserialized packet test. +static int polarity = 0; + +static void test_serdes (void) +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("\nTest serialize / deserialize...\n"); + rec_count = 0; + + int max_fec = 1; + + // try combinations of header type, max_fec, polarity, errors. + + for (int hdr_type = 0; hdr_type <= 1; hdr_type++) { + char packet[1024]; + snprintf (packet, sizeof(packet), "%s:%s", hdr_type ? addrs2 : addrs3, text); + packet_t pp = ax25_from_text (packet, 1); + assert (pp != NULL); + + int chan = 0; + + + for (max_fec = 0; max_fec <= 1; max_fec++) { + for (polarity = 0; polarity <= 2; polarity++) { // 2 means throw in some errors. + int num_bits_sent = il2p_send_frame (chan, pp, max_fec, polarity); + dw_printf ("%d bits sent.\n", num_bits_sent); + + // Need extra bit at end to flush out state machine. + il2p_rec_bit (0, 0, 0, 0); + } + } + ax25_delete(pp); + } + + dw_printf ("Serdes receive count = %d\n", rec_count); + assert (rec_count == 12); + rec_count = -1; // disable deserialized packet test. +} + + +// Serializing calls this which then simulates the demodulator output. + +void tone_gen_put_bit (int chan, int data) +{ + il2p_rec_bit (chan, 0, 0, data); +} + +// This is called when a complete frame has been deserialized. + +void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25) +{ + if (rec_count < 0) return; // Skip check before serdes test. + + rec_count++; + + // Does it have the the expected content? + + unsigned char *pinfo; + int len = ax25_get_info(pp, &pinfo); + assert (len == strlen(text)); + assert (strcmp(text, (char*)pinfo) == 0); + + dw_printf ("Number of symbols corrected: %d\n", retries); + if (polarity == 2) { // expecting errors corrrected. + assert (retries == 10); + } + else { // should be no errors. + assert (retries == 0); + } + + ax25_delete (pp); +} + +alevel_t demod_get_audio_level (int chan, int subchan) +{ + alevel_t alevel; + memset (&alevel, 0, sizeof(alevel)); + return (alevel); +} + +// end il2p_test.c \ No newline at end of file diff --git a/src/il2p_test.c-bak16 b/src/il2p_test.c-bak16 new file mode 100644 index 0000000..c66b488 --- /dev/null +++ b/src/il2p_test.c-bak16 @@ -0,0 +1,1030 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2021 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 "direwolf.h" + +#include +#include +#include +#include + +#include "textcolor.h" +#include "il2p.h" +#include "ax25_pad.h" +#include "ax25_pad2.h" +#include "multi_modem.h" + + +static void test_scramble(void); +static void test_rs(void); +static void test_payload(void); +static void test_example_headers(void); +static void all_frame_types(void); +static void test_serdes(void); +static void decode_bitstream(void); + +/*------------------------------------------------------------- + * + * Name: il2p_test.c + * + * Purpose: Unit tests for IL2P protocol functions. + * + * Errors: Die if anything goes wrong. + * + *--------------------------------------------------------------*/ + + +int main () +{ + int enable_color = 1; + text_color_init (enable_color); + + int enable_debug_out = 0; + il2p_init(enable_debug_out); + + text_color_set(DW_COLOR_INFO); + dw_printf ("Begin IL2P unit tests.\n"); + +// These start simple and later complex cases build upon earlier successes. + +// Test scramble and descramble. + + test_scramble(); + +// Test Reed Solomon error correction. + + test_rs(); + +// Test payload functions. + + test_payload(); + +// Try encoding the example headers in the protocol spec. + + test_example_headers(); + +// Convert all of the AX.25 frame types to IL2P and back again. + + all_frame_types(); + +// Use same serialize / deserialize functions used on the air. + + test_serdes (); + +// Decode bitstream from demodulator if data file is available. +// TODO: Very large info parts. Appropriate error if too long. +// TODO: More than 2 addresses. + + decode_bitstream(); + + text_color_set(DW_COLOR_REC); + dw_printf ("\n----------\n\n"); + dw_printf ("\nSUCCESS!\n"); + + return (EXIT_SUCCESS); +} + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test scrambling and descrambling. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void test_scramble(void) +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("Test scrambling...\n"); + +// First an example from the protocol specification to make sure I'm compatible. + + static unsigned char scramin1[] = { 0x63, 0xf1, 0x40, 0x40, 0x40, 0x00, 0x6b, 0x2b, 0x54, 0x28, 0x25, 0x2a, 0x0f }; + static unsigned char scramout1[] = { 0x6a, 0xea, 0x9c, 0xc2, 0x01, 0x11, 0xfc, 0x14, 0x1f, 0xda, 0x6e, 0xf2, 0x53 }; + unsigned char scramout[sizeof(scramin1)]; + + int tx_lsfr; + il2p_scramble_reset (&tx_lsfr); + il2p_scramble_block (scramin1, scramout, sizeof(scramin1), &tx_lsfr); + assert (memcmp(scramout, scramout, sizeof(scramout1)) == 0); + +// Now try multiple blocks with LSFR state carried over. --- FIXME -no carry over + + static unsigned char msg1[] = "The quick brown fox jumps over the lazy dog."; + static unsigned char msg2[] = "The rain in Spain stays mainly on the plain."; + static unsigned char msg3[] = "Peter Piper picked a peck of pickled peppers."; + + unsigned char scram1[sizeof(msg1)]; + unsigned char scram2[sizeof(msg2)]; + unsigned char scram3[sizeof(msg3)]; + il2p_scramble_reset (&tx_lsfr); + il2p_scramble_block (msg1, scram1, sizeof(msg1), &tx_lsfr); + il2p_scramble_reset (&tx_lsfr); + il2p_scramble_block (msg2, scram2, sizeof(msg2), &tx_lsfr); + il2p_scramble_reset (&tx_lsfr); + il2p_scramble_block (msg3, scram3, sizeof(msg3), &tx_lsfr); + + int rx_lsfr; + unsigned char un1[sizeof(msg1)]; + unsigned char un2[sizeof(msg2)]; + unsigned char un3[sizeof(msg3)]; + il2p_descramble_reset (&rx_lsfr); + il2p_descramble_block (scram1, un1, sizeof(msg1), &rx_lsfr); + il2p_descramble_reset (&rx_lsfr); + il2p_descramble_block (scram2, un2, sizeof(msg2), &rx_lsfr); + il2p_descramble_reset (&rx_lsfr); + il2p_descramble_block (scram3, un3, sizeof(msg3), &rx_lsfr); + + //dw_printf ("%s\n", un1); + //dw_printf ("%s\n", un2); + //dw_printf ("%s\n", un3); + + assert (strcmp((char*)msg1,(char*)un1) == 0); + assert (strcmp((char*)msg2,(char*)un2) == 0); + assert (strcmp((char*)msg3,(char*)un3) == 0); + +} // end test_scramble. + + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test Reed Solomon encode/decode examples found in the protocol spec. +// The data part is scrambled but that does not matter here because. +// We are only concerned abound adding the parity and verifying. +// +///////////////////////////////////////////////////////////////////////////////////////////// + + +static void test_rs() +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("Test Reed Solomon functions...\n"); + + static unsigned char example_s[] = { 0x26, 0x57, 0x4d, 0x57, 0xf1, 0x96, 0xcc, 0x85, 0x42, 0xe7, 0x24, 0xf7, 0x2e, + 0x8a, 0x97 }; + unsigned char parity_out[2]; + il2p_encode_rs (example_s, 13, 2, parity_out); + //dw_printf ("DEBUG RS encode %02x %02x\n", parity_out[0], parity_out[1]); + assert (memcmp(parity_out, example_s + 13, 2) == 0); + + + static unsigned char example_u[] = { 0x6a, 0xea, 0x9c, 0xc2, 0x01, 0x11, 0xfc, 0x14, 0x1f, 0xda, 0x6e, 0xf2, 0x53, + 0x91, 0xbd }; + il2p_encode_rs (example_u, 13, 2, parity_out); + //dw_printf ("DEBUG RS encode %02x %02x\n", parity_out[0], parity_out[1]); + assert (memcmp(parity_out, example_u + 13, 2) == 0); + + // See if we can go the other way. + // FIXME: see if clobbered byte was fixed. + + unsigned char received[15]; + unsigned char corrected[15]; + int e; + + e = il2p_decode_rs (example_s, 13, 2, corrected); + assert (e == 0); + assert (memcmp(example_s, corrected, 13) == 0); + + memcpy (received, example_s, 15); + received[0] = '?'; + e = il2p_decode_rs (received, 13, 2, corrected); + assert (e == 1); + assert (memcmp(example_s, corrected, 13) == 0); + + e = il2p_decode_rs (example_u, 13, 2, corrected); + assert (e == 0); + assert (memcmp(example_u, corrected, 13) == 0); + + memcpy (received, example_u, 15); + received[12] = '?'; + e = il2p_decode_rs (received, 13, 2, corrected); + assert (e == 1); + assert (memcmp(example_u, corrected, 13) == 0); + + received[1] = '?'; + received[2] = '?'; + e = il2p_decode_rs (received, 13, 2, corrected); + assert (e == -1); +} + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test payload functions. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void test_payload(void) +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("Test payload functions...\n"); + + il2p_payload_properties_t ipp; + int e; + +// Examples in specification. + + e = il2p_payload_compute (&ipp, 100, 0); + assert (ipp.small_block_size == 100); + assert (ipp.large_block_size == 101); + assert (ipp.large_block_count == 0); + assert (ipp.small_block_count == 1); + assert (ipp.parity_symbols_per_block == 4); + + e = il2p_payload_compute (&ipp, 236, 0); + assert (ipp.small_block_size == 236); + assert (ipp.large_block_size == 237); + assert (ipp.large_block_count == 0); + assert (ipp.small_block_count == 1); + assert (ipp.parity_symbols_per_block == 8); + + e = il2p_payload_compute (&ipp, 512, 0); + assert (ipp.small_block_size == 170); + assert (ipp.large_block_size == 171); + assert (ipp.large_block_count == 2); + assert (ipp.small_block_count == 1); + assert (ipp.parity_symbols_per_block == 6); + + e = il2p_payload_compute (&ipp, 1023, 0); + assert (ipp.small_block_size == 204); + assert (ipp.large_block_size == 205); + assert (ipp.large_block_count == 3); + assert (ipp.small_block_count == 2); + assert (ipp.parity_symbols_per_block == 8); + +// Now try all possible sizes for Baseline FEC Parity. + + for (int n = 1; n <= IL2P_MAX_PAYLOAD_SIZE; n++) { + e = il2p_payload_compute (&ipp, n, 0); + //dw_printf ("bytecount=%d, smallsize=%d, largesize=%d, largecount=%d, smallcount=%d\n", n, + // ipp.small_block_size, ipp.large_block_size, + // ipp.large_block_count, ipp.small_block_count); + //fflush (stdout); + + assert (ipp.payload_block_count >= 1 && ipp.payload_block_count <= IL2P_MAX_PAYLOAD_BLOCKS); + assert (ipp.payload_block_count == ipp.small_block_count + ipp.large_block_count); + assert (ipp.small_block_count * ipp.small_block_size + + ipp.large_block_count * ipp.large_block_size == n); + assert (ipp.parity_symbols_per_block == 2 || + ipp.parity_symbols_per_block == 4 || + ipp.parity_symbols_per_block == 6 || + ipp.parity_symbols_per_block == 8); + + // Data and parity must fit in RS block size of 255. + // Size test does not apply if block count is 0. + assert (ipp.small_block_count == 0 || ipp.small_block_size + ipp.parity_symbols_per_block <= 255); + assert (ipp.large_block_count == 0 || ipp.large_block_size + ipp.parity_symbols_per_block <= 255); + } + +// All sizes for MAX FEC. + + for (int n = 1; n <= IL2P_MAX_PAYLOAD_SIZE; n++) { + e = il2p_payload_compute (&ipp, n, 1); // 1 for max fec. + //dw_printf ("bytecount=%d, smallsize=%d, largesize=%d, largecount=%d, smallcount=%d\n", n, + // ipp.small_block_size, ipp.large_block_size, + // ipp.large_block_count, ipp.small_block_count); + //fflush (stdout); + + assert (ipp.payload_block_count >= 1 && ipp.payload_block_count <= IL2P_MAX_PAYLOAD_BLOCKS); + assert (ipp.payload_block_count == ipp.small_block_count + ipp.large_block_count); + assert (ipp.small_block_count * ipp.small_block_size + + ipp.large_block_count * ipp.large_block_size == n); + assert (ipp.parity_symbols_per_block == 16); + + // Data and parity must fit in RS block size of 255. + // Size test does not apply if block count is 0. + assert (ipp.small_block_count == 0 || ipp.small_block_size + ipp.parity_symbols_per_block <= 255); + assert (ipp.large_block_count == 0 || ipp.large_block_size + ipp.parity_symbols_per_block <= 255); + } + +// Now let's try encoding payloads and extracting original again. +// This will also provide exercise for scrambling and Reed Solomon under more conditions. + + unsigned char original_payload[IL2P_MAX_PAYLOAD_SIZE]; + for (int n = 0; n < IL2P_MAX_PAYLOAD_SIZE; n++) { + original_payload[n] = n & 0xff; + } + for (int max_fec = 0; max_fec <= 1; max_fec++) { + for (int payload_length = 1; payload_length <= IL2P_MAX_PAYLOAD_SIZE; payload_length++) { + //dw_printf ("\n--------- max_fec = %d, payload_length = %d\n", max_fec, payload_length); + int tx_lsfr_state; + il2p_scramble_reset(&tx_lsfr_state); + unsigned char encoded[IL2P_MAX_ENCODED_SIZE]; + int k = il2p_encode_payload (original_payload, payload_length, max_fec, encoded); + + //dw_printf ("payload length %d %s -> %d\n", payload_length, max_fec ? "M" : "", k); + assert (k > payload_length && k <= IL2P_MAX_ENCODED_SIZE); + + // Now extract. + + //int rx_lsfr_state; + //il2p_descramble_reset(&rx_lsfr_state); + unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE]; + int symbols_corrected = 0; + int e = il2p_decode_payload (encoded, payload_length, max_fec, extracted, &symbols_corrected); + //dw_printf ("e = %d, payload_length = %d\n", e, payload_length); + assert (e == payload_length); + + // if (memcmp (original_payload, extracted, payload_length) != 0) { + // dw_printf ("********** Received message not as expected. **********\n"); + // fx_hex_dump(extracted, payload_length); + // } + assert (memcmp (original_payload, extracted, payload_length) == 0); + } + } + (void)e; +} // end test_payload + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test header examples found in protocol specification. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void test_example_headers() +{ + +//----------- Example 1: AX.25 S-Frame -------------- + +// This frame sample only includes a 15 byte header, without PID field. +// Destination Callsign: ?KA2DEW-2 +// Source Callsign: ?KK4HEJ-7 +// N(R): 5 +// P/F: 1 +// C: 1 +// Control Opcode: 00 (Receive Ready) +// +// AX.25 data: +// 96 82 64 88 8a ae e4 96 96 68 90 8a 94 6f b1 +// +// IL2P Data Prior to Scrambling and RS Encoding: +// 2b a1 12 24 25 77 6b 2b 54 68 25 2a 27 +// +// IL2P Data After Scrambling and RS Encoding: +// 26 57 4d 57 f1 96 cc 85 42 e7 24 f7 2e 8a 97 + + text_color_set(DW_COLOR_INFO); + dw_printf ("Example 1: AX.25 S-Frame...\n"); + + static unsigned char example1[] = {0x96, 0x82, 0x64, 0x88, 0x8a, 0xae, 0xe4, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x6f, 0xb1}; + static unsigned char header1[] = {0x2b, 0xa1, 0x12, 0x24, 0x25, 0x77, 0x6b, 0x2b, 0x54, 0x68, 0x25, 0x2a, 0x27 }; + unsigned char header[IL2P_HEADER_SIZE]; + unsigned char sresult[32]; + memset (header, 0, sizeof(header)); + memset (sresult, 0, sizeof(sresult)); + unsigned char check[2]; + alevel_t alevel; + memset(&alevel, 0, sizeof(alevel)); + + packet_t pp = ax25_from_frame (example1, sizeof(example1), alevel); + assert (pp != NULL); + int e; + e = il2p_type_1_header (pp, 0, header); + assert (e == 0); + ax25_delete(pp); + + //dw_printf ("Example 1 header:\n"); + //for (int i = 0 ; i < sizeof(header); i++) { + // dw_printf (" %02x", header[i]); + //} + ///dw_printf ("\n"); + + assert (memcmp(header, header1, sizeof(header)) == 0); + + int tx_lsfr_state; + il2p_scramble_reset(&tx_lsfr_state); + il2p_scramble_block (header, sresult, 13, &tx_lsfr_state); + //dw_printf ("Expect scrambled 26 57 4d 57 f1 96 cc 85 42 e7 24 f7 2e\n"); + //for (int i = 0 ; i < sizeof(sresult); i++) { + // dw_printf (" %02x", sresult[i]); + //} + //dw_printf ("\n"); + + il2p_encode_rs (sresult, 13, 2, check); + + //dw_printf ("expect checksum = 8a 97\n"); + //dw_printf ("check = "); + //for (int i = 0 ; i < sizeof(check); i++) { + // dw_printf (" %02x", check[i]); + //} + //dw_printf ("\n"); + assert (check[0] == 0x8a); + assert (check[1] == 0x97); + +// Can we go from IL2P back to AX.25? + + pp = il2p_decode_header_type_1 (header, 0); + assert (pp != NULL); + + char dst_addr[AX25_MAX_ADDR_LEN]; + char src_addr[AX25_MAX_ADDR_LEN]; + + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr); + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr); + + ax25_frame_type_t frame_type; + cmdres_t cr; // command or response. + char description[64]; + int pf; // Poll/Final. + int nr, ns; // Sequence numbers. + + frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns); + (void)frame_type; +#if 1 + dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description); +#endif +// TODO: compare binary. + ax25_delete (pp); + +// FIXME: continue + + dw_printf ("Example 1 header OK\n"); + + +// -------------- Example 2 - UI frame, no info part ------------------ + +// This is an AX.25 Unnumbered Information frame, such as APRS. +// Destination Callsign: ?CQ -0 +// Source Callsign: ?KK4HEJ-15 +// P/F: 0 +// C: 0 +// Control Opcode: 3 Unnumbered Information +// PID: 0xF0 No L3 +// +// AX.25 Data: +// 86 a2 40 40 40 40 60 96 96 68 90 8a 94 7f 03 f0 +// +// IL2P Data Prior to Scrambling and RS Encoding: +// 63 f1 40 40 40 00 6b 2b 54 28 25 2a 0f +// +// IL2P Data After Scrambling and RS Encoding: +// 6a ea 9c c2 01 11 fc 14 1f da 6e f2 53 91 bd + + + //dw_printf ("---------- example 2 ------------\n"); + static unsigned char example2[] = { 0x86, 0xa2, 0x40, 0x40, 0x40, 0x40, 0x60, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x7f, 0x03, 0xf0 }; + static unsigned char header2[] = { 0x63, 0xf1, 0x40, 0x40, 0x40, 0x00, 0x6b, 0x2b, 0x54, 0x28, 0x25, 0x2a, 0x0f }; + memset (header, 0, sizeof(header)); + memset (sresult, 0, sizeof(sresult)); + memset(&alevel, 0, sizeof(alevel)); + + pp = ax25_from_frame (example2, sizeof(example2), alevel); + assert (pp != NULL); + e = il2p_type_1_header (pp, 0, header); + assert (e == 0); + ax25_delete(pp); + + //dw_printf ("Example 2 header:\n"); + //for (int i = 0 ; i < sizeof(header); i++) { + // dw_printf (" %02x", header[i]); + //} + //dw_printf ("\n"); + + assert (memcmp(header, header2, sizeof(header2)) == 0); + + il2p_scramble_reset(&tx_lsfr_state); + il2p_scramble_block (header, sresult, 13, &tx_lsfr_state); + //dw_printf ("Expect scrambled 6a ea 9c c2 01 11 fc 14 1f da 6e f2 53\n"); + //for (int i = 0 ; i < sizeof(sresult); i++) { + // dw_printf (" %02x", sresult[i]); + //} + //dw_printf ("\n"); + + il2p_encode_rs (sresult, 13, 2, check); + + //dw_printf ("expect checksum = 91 bd\n"); + //dw_printf ("check = "); + //for (int i = 0 ; i < sizeof(check); i++) { + // dw_printf (" %02x", check[i]); + //} + //dw_printf ("\n"); + assert (check[0] == 0x91); + assert (check[1] == 0xbd); + +// Can we go from IL2P back to AX.25? + + pp = il2p_decode_header_type_1 (header, 0); + assert (pp != NULL); + + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr); + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr); + + frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns); + (void)frame_type; +#if 1 + dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description); +#endif +// TODO: compare binary. + + ax25_delete (pp); +// FIXME: continue + + dw_printf ("Example 2 header OK\n"); + + +// -------------- Example 3 - I Frame ------------------ + +// This is an AX.25 I-Frame with 9 bytes of information after the 16 byte header. +// +// Destination Callsign: ?KA2DEW-2 +// Source Callsign: ?KK4HEJ-2 +// P/F: 1 +// C: 1 +// N(R): 5 +// N(S): 4 +// AX.25 PID: 0xCF TheNET +// IL2P Payload Byte Count: 9 +// +// AX.25 Data: +// 96 82 64 88 8a ae e4 96 96 68 90 8a 94 65 b8 cf 30 31 32 33 34 35 36 37 38 +// +// IL2P Scrambled and Encoded Data: +// 26 13 6d 02 8c fe fb e8 aa 94 2d 6a 34 43 35 3c 69 9f 0c 75 5a 38 a1 7f f3 fc + + + //dw_printf ("---------- example 3 ------------\n"); + static unsigned char example3[] = { 0x96, 0x82, 0x64, 0x88, 0x8a, 0xae, 0xe4, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x65, 0xb8, 0xcf, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; + static unsigned char header3[] = { 0x2b, 0xe1, 0x52, 0x64, 0x25, 0x77, 0x6b, 0x2b, 0xd4, 0x68, 0x25, 0xaa, 0x22 }; + static unsigned char complete3[] = { 0x26, 0x13, 0x6d, 0x02, 0x8c, 0xfe, 0xfb, 0xe8, 0xaa, 0x94, 0x2d, 0x6a, 0x34, 0x43, 0x35, 0x3c, 0x69, 0x9f, 0x0c, 0x75, 0x5a, 0x38, 0xa1, 0x7f, 0xf3, 0xfc }; + memset (header, 0, sizeof(header)); + memset (sresult, 0, sizeof(sresult)); + memset(&alevel, 0, sizeof(alevel)); + + pp = ax25_from_frame (example3, sizeof(example3), alevel); + assert (pp != NULL); + e = il2p_type_1_header (pp, 0, header); + assert (e == 9); + ax25_delete(pp); + + //dw_printf ("Example 3 header:\n"); + //for (int i = 0 ; i < sizeof(header); i++) { + // dw_printf (" %02x", header[i]); + //} + //dw_printf ("\n"); + + assert (memcmp(header, header3, sizeof(header)) == 0); + + il2p_scramble_reset(&tx_lsfr_state); + il2p_scramble_block (header, sresult, 13, &tx_lsfr_state); + //dw_printf ("Expect scrambled 26 13 6d 02 8c fe fb e8 aa 94 2d 6a 34\n"); + //for (int i = 0 ; i < sizeof(sresult); i++) { + // dw_printf (" %02x", sresult[i]); + //} + //dw_printf ("\n"); + + il2p_encode_rs (sresult, 13, 2, check); + + //dw_printf ("expect checksum = 43 35\n"); + //dw_printf ("check = "); + //for (int i = 0 ; i < sizeof(check); i++) { + // dw_printf (" %02x", check[i]); + //} + //dw_printf ("\n"); + + assert (check[0] == 0x43); + assert (check[1] == 0x35); + + // That was only the header. We will get to the info part in a later test. + +// Can we go from IL2P back to AX.25? + + pp = il2p_decode_header_type_1 (header, 0); + assert (pp != NULL); + + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr); + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr); + + frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns); + (void)frame_type; +#if 1 + dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description); +#endif +// TODO: compare binary. + + ax25_delete (pp); +// FIXME: continue + dw_printf ("Example 3 header OK\n"); + +// Example 3 again, this time the Information part is included. + + pp = ax25_from_frame (example3, sizeof(example3), alevel); + assert (pp != NULL); + + int max_fec = 0; + unsigned char iout[IL2P_MAX_PACKET_SIZE]; + e = il2p_encode_frame (pp, max_fec, iout); + + //dw_printf ("expected for example 3:\n"); + //fx_hex_dump(complete3, sizeof(complete3)); + //dw_printf ("actual result for example 3:\n"); + //fx_hex_dump(iout, e); + // Does it match the example in the protocol spec? + assert (e == sizeof(complete3)); + assert (memcmp(iout, complete3, sizeof(complete3)) == 0); + ax25_delete (pp); + + dw_printf ("Example 3 with info OK\n"); + +} // end test_example_headers + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test all of the frame types. +// +// Encode to IL2P format, decode, and verify that the result is the same as the original. +// +///////////////////////////////////////////////////////////////////////////////////////////// + + +static void enc_dec_compare (packet_t pp1) +{ + for (int max_fec = 0; max_fec <= 1; max_fec++) { + + unsigned char encoded[IL2P_MAX_PACKET_SIZE]; + int enc_len; + enc_len = il2p_encode_frame (pp1, max_fec, encoded); + assert (enc_len >= 0); + +// FIXME: add something like: hdr_type=1, max_fec=0, payload_len=44, encoded_payload=99 + + packet_t pp2; + pp2 = il2p_decode_frame (encoded); + assert (pp2 != NULL); + +// Is it the same after encoding to IL2P and then decoding? + + int len1 = ax25_get_frame_len (pp1); + unsigned char *data1 = ax25_get_frame_data_ptr (pp1); + + int len2 = ax25_get_frame_len (pp2); + unsigned char *data2 = ax25_get_frame_data_ptr (pp2); + + if (len1 != len2 || memcmp(data1, data2, len1) != 0) { + + dw_printf ("\nEncode/Decode Error. Original:\n"); + ax25_hex_dump (pp1); + + dw_printf ("IL2P encoded as:\n"); + fx_hex_dump(encoded, enc_len); + + dw_printf ("Got turned into this:\n"); + ax25_hex_dump (pp2); + } + + assert (len1 == len2 && memcmp(data1, data2, len1) == 0); + + ax25_delete (pp2); + } +// FIXME repeat with max_fec=1 + +} + +static void all_frame_types(void) +{ + char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; + int num_addr = 2; + cmdres_t cr; + ax25_frame_type_t ftype; + int pf = 0; + int pid = 0xf0; + int modulo; + int nr, ns; + unsigned char *pinfo = NULL; + int info_len = 0; + packet_t pp; + + strcpy (addrs[0], "W2UB"); + strcpy (addrs[1], "WB2OSZ-12"); + num_addr = 2; + + text_color_set(DW_COLOR_INFO); + dw_printf ("Testing all frame types.\n"); + +/* U frame */ + + dw_printf ("\nU frames...\n"); + + for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) { + + for (pf = 0; pf <= 1; pf++) { + + int cmin = 0, cmax = 1; + + switch (ftype) { + // 0 = response, 1 = command + case frame_type_U_SABME: cmin = 1; cmax = 1; break; + case frame_type_U_SABM: cmin = 1; cmax = 1; break; + case frame_type_U_DISC: cmin = 1; cmax = 1; break; + case frame_type_U_DM: cmin = 0; cmax = 0; break; + case frame_type_U_UA: cmin = 0; cmax = 0; break; + case frame_type_U_FRMR: cmin = 0; cmax = 0; break; + case frame_type_U_UI: cmin = 0; cmax = 1; break; + case frame_type_U_XID: cmin = 0; cmax = 1; break; + case frame_type_U_TEST: cmin = 0; cmax = 1; break; + default: break; // avoid compiler warning. + } + + for (cr = cmin; cr <= cmax; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct U frame, cr=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_u_frame (addrs, num_addr, cr, ftype, pf, pid, pinfo, info_len); + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + } + } + + +/* S frame */ + + //strcpy (addrs[2], "DIGI1-1"); + //num_addr = 3; + + dw_printf ("\nS frames...\n"); + + for (ftype = frame_type_S_RR; ftype <= frame_type_S_SREJ; ftype++) { + + for (pf = 0; pf <= 1; pf++) { + + modulo = 8; + nr = modulo / 2 + 1; + + // SREJ can only be response. + + for (cr = 0; cr <= (int)(ftype!=frame_type_S_SREJ); cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + + modulo = 128; + nr = modulo / 2 + 1; + + for (cr = 0; cr <= (int)(ftype!=frame_type_S_SREJ); cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + } + } + +/* SREJ is only S frame which can have information part. */ + + static unsigned char srej_info[] = { 1<<1, 2<<1, 3<<1, 4<<1 }; + + ftype = frame_type_S_SREJ; + for (pf = 0; pf <= 1; pf++) { + + modulo = 128; + nr = 127; + cr = cr_res; + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct Multi-SREJ S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, srej_info, (int)(sizeof(srej_info))); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + + +/* I frame */ + + dw_printf ("\nI frames...\n"); + + pinfo = (unsigned char*)"The rain in Spain stays mainly on the plain."; + info_len = strlen((char*)pinfo); + + for (pf = 0; pf <= 1; pf++) { + + modulo = 8; + nr = 0x55 & (modulo - 1); + ns = 0xaa & (modulo - 1); + + for (cr = 1; cr <= 1; cr++) { // can only be command + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + + modulo = 128; + nr = 0x55 & (modulo - 1); + ns = 0xaa & (modulo - 1); + + for (cr = 1; cr <= 1; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); + + ax25_hex_dump (pp); + enc_dec_compare (pp); + ax25_delete (pp); + } + } + +} // end all_frame_types + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test bitstream tapped off from demodulator. +// +// 5 frames were sent to Nino TNC and a recording was made. +// This was demodulated and the resulting bit stream saved to a file. +// +// No automatic test here - must be done manually with audio recording. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static int decoding_bitstream = 0; + +static void decode_bitstream(void) +{ + dw_printf("-----\nReading il2p-bitstream.txt if available...\n"); + + FILE *fp = fopen ("il2p-bitstream.txt", "r"); + if (fp == NULL) { + dw_printf ("Bitstream test file not available.\n"); + return; + } + + decoding_bitstream = 1; + int save_previous = il2p_get_debug(); + il2p_set_debug (1); + + int ch; + while ( (ch = fgetc(fp)) != EOF) { + + if (ch == '0' || ch == '1') { + il2p_rec_bit (0, 0, 0, ch - '0'); + } + } + fclose(fp); + il2p_set_debug (save_previous); + decoding_bitstream = 0; + +} // end decode_bitstream + + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// +// Test serialize / deserialize. +// +// This uses same functions used on the air. +// +///////////////////////////////////////////////////////////////////////////////////////////// + +static char addrs2[] = "AA1AAA-1>ZZ9ZZZ-9"; +static char addrs3[] = "AA1AAA-1>ZZ9ZZZ-9,DIGI*"; +static char text[] = + "'... As I was saying, that seems to be done right - though I haven't time to look it over thoroughly just now - and that shows that there are three hundred and sixty-four days when you might get un-birthday presents -'" + "\n" + "'Certainly,' said Alice." + "\n" + "'And only one for birthday presents, you know. There's glory for you!'" + "\n" + "'I don't know what you mean by \"glory\",' Alice said." + "\n" + "Humpty Dumpty smiled contemptuously. 'Of course you don't - till I tell you. I meant \"there's a nice knock-down argument for you!\"'" + "\n" + "'But \"glory\" doesn't mean \"a nice knock-down argument\",' Alice objected." + "\n" + "'When I use a word,' Humpty Dumpty said, in rather a scornful tone, 'it means just what I choose it to mean - neither more nor less.'" + "\n" + "'The question is,' said Alice, 'whether you can make words mean so many different things.'" + "\n" + "'The question is,' said Humpty Dumpty, 'which is to be master - that's all.'" + "\n" ; + + +static int rec_count = -1; // disable deserialized packet test. +static int polarity = 0; + +static void test_serdes (void) +{ + text_color_set(DW_COLOR_INFO); + dw_printf ("\nTest serialize / deserialize...\n"); + rec_count = 0; + + int max_fec = 1; + + // try combinations of header type, max_fec, polarity, errors. + + for (int hdr_type = 0; hdr_type <= 1; hdr_type++) { + char packet[1024]; + snprintf (packet, sizeof(packet), "%s:%s", hdr_type ? addrs2 : addrs3, text); + packet_t pp = ax25_from_text (packet, 1); + assert (pp != NULL); + + int chan = 0; + + + for (max_fec = 0; max_fec <= 1; max_fec++) { + for (polarity = 0; polarity <= 2; polarity++) { // 2 means throw in some errors. + int num_bits_sent = il2p_send_frame (chan, pp, max_fec, polarity); + dw_printf ("%d bits sent.\n", num_bits_sent); + + // Need extra bit at end to flush out state machine. + il2p_rec_bit (0, 0, 0, 0); + } + } + ax25_delete(pp); + } + + dw_printf ("Serdes receive count = %d\n", rec_count); + assert (rec_count == 12); + rec_count = -1; // disable deserialized packet test. +} + + +// Serializing calls this which then simulates the demodulator output. + +void tone_gen_put_bit (int chan, int data) +{ + il2p_rec_bit (chan, 0, 0, data); +} + +// This is called when a complete frame has been deserialized. + +void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25) +{ + if (rec_count < 0) return; // Skip check before serdes test. + + rec_count++; + + // Does it have the the expected content? + + unsigned char *pinfo; + int len = ax25_get_info(pp, &pinfo); + assert (len == strlen(text)); + assert (strcmp(text, (char*)pinfo) == 0); + + dw_printf ("Number of symbols corrected: %d\n", retries); + if (polarity == 2) { // expecting errors corrrected. + assert (retries == 10); + } + else { // should be no errors. + assert (retries == 0); + } + + ax25_delete (pp); +} + +alevel_t demod_get_audio_level (int chan, int subchan) +{ + alevel_t alevel; + memset (&alevel, 0, sizeof(alevel)); + return (alevel); +} + +// end il2p_test.c \ No newline at end of file diff --git a/src/multi_modem.c b/src/multi_modem.c index bdb706d..c738875 100644 --- a/src/multi_modem.c +++ b/src/multi_modem.c @@ -83,7 +83,7 @@ //#define DEBUG 1 -#define DIGIPEATER_C +#define DIGIPEATER_C // Why? #include "direwolf.h" @@ -345,13 +345,20 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c else { pp = ax25_from_frame (fbuf, flen, alevel); } + + multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, is_fx25); +} + +// TODO: Eliminate function above and move code elsewhere? + +void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25) +{ if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unexpected internal problem, %s %d\n", __FILE__, __LINE__); return; /* oops! why would it fail? */ } - /* * If only one demodulator/slicer, and no FX.25 in progress, * push it thru and forget about all this foolishness. @@ -488,6 +495,9 @@ static void pick_best_candidate (int chan) } } + // FIXME: IL2p & FX.25 don't have CRC calculated. Must fill it in first. + + /* Bump it up slightly if others nearby have the same CRC. */ for (n = 0; n < num_bars; n++) { diff --git a/src/multi_modem.h b/src/multi_modem.h index 0734492..de3061e 100644 --- a/src/multi_modem.h +++ b/src/multi_modem.h @@ -16,6 +16,9 @@ void multi_modem_process_sample (int c, int audio_sample); int multi_modem_get_dc_average (int chan); +// Deprecated. Replace with ...packet void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, int is_fx25); +void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25); + #endif diff --git a/src/tnctest.c b/src/tnctest.c new file mode 100644 index 0000000..ed584e6 --- /dev/null +++ b/src/tnctest.c @@ -0,0 +1,1284 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2016 John Langner, WB2OSZ +// +// This program 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 2 of the License, or +// (at your option) any later version. +// +// 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 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 . +// + + +/*------------------------------------------------------------------ + * + * Module: tnctest.c + * + * Purpose: Test AX.25 connected mode between two TNCs. + * + * Description: The first TNC will connect to the second TNC and send a bunch of data. + * Proper transfer of data will be verified. + * + * Usage: tnctest [options] port0=name0 port1=name1 + * + * Example: tnctest localhost:8000=direwolf COM1=KPC-3+ + * + * Each port can have the following forms: + * + * * host-name:tcp-port + * * ip-addr:tcp-port + * * tcp-port + * * serial port name (e.g. COM1, /dev/ttyS0) + * + *---------------------------------------------------------------*/ + + + +/* + * Native Windows: Use the Winsock interface. + * Linux: Use the BSD socket interface. + */ + +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h + +#if __WIN32__ + +#include +#include // _WIN32_WINNT must be set to 0x0501 before including this + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#endif + +#include +#include +#include +#include +#include +#include + + +//#include "ax25_pad.h" +#include "textcolor.h" +#include "dtime_now.h" +#include "serial_port.h" + + +/* We don't deal with big-endian processors here. */ +/* TODO: Use agwlib (which did not exist when this was written) */ +/* rather than duplicating the effort here. */ + +struct agwpe_s { + +#if 1 + + unsigned char portx; /* 0 for first, 1 for second, etc. */ + unsigned char reserved1; + unsigned char reserved2; + unsigned char reserved3; + + unsigned char datakind; /* message type, usually written as a letter. */ + unsigned char reserved4; + unsigned char pid; + unsigned char reserved5; + +#else + short portx; /* 0 for first, 1 for second, etc. */ + short port_hi_reserved; + short kind_lo; /* message type */ + short kind_hi; +#endif + char call_from[10]; + char call_to[10]; + int data_len; /* Number of data bytes following. */ + int user_reserved; +}; + + +#if __WIN32__ +static unsigned __stdcall tnc_thread_net (void *arg); +static unsigned __stdcall tnc_thread_serial (void *arg); +#else +static void * tnc_thread_net (void *arg); +static void * tnc_thread_serial (void *arg); +#endif + + +static void tnc_connect (int from, int to); +static void tnc_disconnect (int from, int to); +static void tnc_send_data (int from, int to, char * data); +static void tnc_reset (int from, int to); + +/* + * Convert Internet address to text. + * Can't use InetNtop because it is supported only on Windows Vista and later. + */ + +static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize) +{ + struct sockaddr_in *sa4; + struct sockaddr_in6 *sa6; + + switch (Family) { + case AF_INET: + sa4 = (struct sockaddr_in *)pAddr; +#if __WIN32__ + snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1, + sa4->sin_addr.S_un.S_un_b.s_b2, + sa4->sin_addr.S_un.S_un_b.s_b3, + sa4->sin_addr.S_un.S_un_b.s_b4); +#else + inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize); +#endif + break; + case AF_INET6: + sa6 = (struct sockaddr_in6 *)pAddr; +#if __WIN32__ + snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x", + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]), + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]), + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]), + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]), + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]), + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]), + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]), + ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7])); +#else + inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize); +#endif + break; + default: + snprintf (pStringBuf, StringBufSize, "Invalid address family!"); + } + assert (strlen(pStringBuf) < StringBufSize); + return pStringBuf; +} + + + +/*------------------------------------------------------------------ + * + * Name: main + * + * Purpose: Basic test for connected AX.25 data mode between TNCs. + * + * Usage: Described above. + * + *---------------------------------------------------------------*/ + +#define MAX_TNC 2 // Just 2 for now. + // Could be more later for multiple concurrent connections. + +/* Obtained from the command line. */ + +static int num_tnc; /* How many TNCs for this test? */ + /* Initially only 2 but long term we might */ + /* enhance it to allow multiple concurrent connections. */ + +static char hostname[MAX_TNC][50]; /* DNS host name or IPv4 address. */ + /* Some of the code is there for IPv6 but */ + /* needs more work. */ + /* Defaults to "localhost" if not specified. */ + +static char port[MAX_TNC][30]; /* If it begins with a digit, it is considered */ + /* a TCP port number at the hostname. */ + /* Otherwise, we treat it as a serial port name. */ + +static char description[MAX_TNC][50]; /* Name used in the output. */ + +static int using_tcp[MAX_TNC]; /* Are we using TCP or serial port for each TNC? */ + /* Use corresponding one of the next two. */ + +static int server_sock[MAX_TNC]; /* File descriptor for AGW socket interface. */ + /* Set to -1 if not used. */ + /* (Don't use SOCKET type because it is unsigned.) */ + +static MYFDTYPE serial_fd[MAX_TNC]; /* Serial port handle. */ + +static volatile int busy[MAX_TNC]; /* True when TNC busy and can't accept more data. */ + /* For serial port, this is set by XON / XOFF characters. */ + +#define XOFF 0x13 +#define XON 0x11 + +static char tnc_address[MAX_TNC][12]; /* Name of the TNC used in the frames. Originally, this */ + /* was simply TNC0 and TNC1 but that can get hard to read */ + /* and confusing. Later used DW0, DW1, for direwolf */ + /* so the direction of flow is easier to grasp. */ + +#if __WIN32__ + static HANDLE tnc_th[MAX_TNC]; +#else + static pthread_t tnc_tid[MAX_TNC]; +#endif + +#define LINE_WIDTH 80 +//#define LINE_WIDTH 120 /* If I was more ambitious I might try to get */ + /* this from the terminal properties. */ + +static int column_width; /* Line width divided by number of TNCs. */ + + +/* + * Current state for each TNC. + */ + +static int is_connected[MAX_TNC]; /* -1 = not yet available. */ + /* 0 = not connected. */ + /* 1 = not connected. */ + +static int have_cmd_prompt[MAX_TNC]; /* Set if "cmd:" was the last thing seen. */ + +static int last_rec_seq[MAX_TNC]; /* Each data packet will contain a sequence number. */ + /* This is used to verify that all have been */ + /* received in the correct order. */ + + + +/* + * Start time so we can print relative elapsed time. + */ + +static double start_dtime; + + +static int max_count; + +int main (int argc, char *argv[]) +{ + int j; + int timeout; + int send_count = 0; + int burst_size = 1; + int errors = 0; + + //max_count = 20; + max_count = 200; + //max_count = 6; + max_count = 1000; + max_count = 9999; + +#if __WIN32__ +#else + int e; + + setlinebuf (stdout); +#endif + + start_dtime = dtime_now(); + +/* + * Extract command line args. + */ + num_tnc = argc - 1; + + if (num_tnc < 2 || num_tnc > MAX_TNC) { + printf ("Specify minimum 2, maximum %d TNCs on the command line.\n", MAX_TNC); + exit (EXIT_FAILURE); + } + + column_width = LINE_WIDTH / num_tnc; + + for (j=0; j 0) { + + SLEEP_MS(100); + timeout--; + ready = 1; + for (j=0; j last0) { + last0 = last_rec_seq[0]; + no_activity = 0; + } + if (last_rec_seq[1] > last1) { + last1 = last_rec_seq[1]; + no_activity = 0; + } + } + + if (last_rec_seq[0] == max_count) { + printf ("Got last expected reply.\n"); + } + else { + printf ("ERROR: Timeout - No incoming activity for %d seconds.\n", no_activity); + errors++; + } + +/* + * Did we get all expected replies? + */ + if (last_rec_seq[0] != max_count) { + printf ("ERROR: Last received reply was %d when we were expecting %d.\n", last_rec_seq[0], max_count); + errors++; + } + +/* + * Ask for disconnect. Wait until complete. + */ + + tnc_disconnect (0, 1); + + timeout = 200; // 20 sec should be generous. + ready = 0; + while ( ! ready && timeout > 0) { + + SLEEP_MS(100); + timeout--; + ready = 1; + for (j=0; j1 for the other end which answers. + * + * data - Should look something like this: + * 9999 send data + * 9999 reply + * + * Global In/Out: last_rec_seq[my_index] + * + * Description: Look for expected format. + * Extract the sequence number. + * Verify that it is the next expected one. + * Update it. + * + *--------------------------------------------------------------------*/ + +void process_rec_data (int my_index, char *data) +{ + int n; + + if (isdigit(*data) && strncmp(data+4, " send", 5) == 0) { + if (my_index > 0) { + last_rec_seq[my_index]++; + + n = atoi(data); + if (n != last_rec_seq[my_index]) { + printf ("%*s%s: Received %d when %d was expected.\n", my_index*column_width, "", tnc_address[my_index], n, last_rec_seq[my_index]); + SLEEP_MS(10000); + printf ("TEST FAILED!\n"); + exit (EXIT_FAILURE); + } + } + } + + else if (isdigit(*data) && strncmp(data+4, " reply", 6) == 0) { + if (my_index == 0) { + last_rec_seq[my_index]++; + n = atoi(data); + if (n != last_rec_seq[my_index]) { + printf ("%*s%s: Received %d when %d was expected.\n", my_index*column_width, "", tnc_address[my_index], n, last_rec_seq[my_index]); + SLEEP_MS(10000); + printf ("TEST FAILED!\n"); + exit (EXIT_FAILURE); + } + } + } + + else if (data[0] == 'A') { + + if (strncmp(data, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", strlen(data)-1) != 0) { + printf ("%*s%s: Segmentation is broken.\n", my_index*column_width, "", tnc_address[my_index]); + SLEEP_MS(10000); + printf ("TEST FAILED!\n"); + exit (EXIT_FAILURE); + } + } +} + + + +/*------------------------------------------------------------------- + * + * Name: tnc_thread_net + * + * Purpose: Establish connection with a TNC via network. + * + * Inputs: arg - My instance index, 0 thru MAX_TNC-1. + * + * Outputs: packets - Received packets are put in the corresponding column + * and sent to a common function to check that they + * all arrived in order. + * + * Global Out: is_connected - Updated when connected/disconnected notfications are received. + * + * Description: Perform any necessary configuration for the TNC then wait + * for responses and process them. + * + *--------------------------------------------------------------------*/ + +#define MAX_HOSTS 30 + +#if __WIN32__ +static unsigned __stdcall tnc_thread_net (void *arg) +#else +static void * tnc_thread_net (void *arg) +#endif +{ + int my_index; + struct addrinfo hints; + struct addrinfo *ai_head = NULL; + struct addrinfo *ai; + struct addrinfo *hosts[MAX_HOSTS]; + int num_hosts, n; + int err; + char ipaddr_str[46]; /* text form of IP address */ +#if __WIN32__ + WSADATA wsadata; +#endif + + struct agwpe_s mon_cmd; + char data[4096]; + double dnow; + + + my_index = (int)(ptrdiff_t)arg; + +#if DEBUGx + printf ("DEBUG: tnc_thread_net %d start, port = '%s'\n", my_index, port[my_index]); +#endif + +#if __WIN32__ + err = WSAStartup (MAKEWORD(2,2), &wsadata); + if (err != 0) { + printf("WSAStartup failed: %d\n", err); + return (0); + } + + if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { + printf("Could not find a usable version of Winsock.dll\n"); + WSACleanup(); + //sleep (1); + return (0); + } +#endif + + memset (&hints, 0, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */ + // hints.ai_family = AF_INET; /* IPv4 only. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + +/* + * Connect to TNC server. + */ + + ai_head = NULL; + err = getaddrinfo(hostname[my_index], port[my_index], &hints, &ai_head); + if (err != 0) { +#if __WIN32__ + printf ("Can't get address for server %s, err=%d\n", + hostname[my_index], WSAGetLastError()); +#else + printf ("Can't get address for server %s, %s\n", + hostname[my_index], gai_strerror(err)); +#endif + freeaddrinfo(ai_head); + exit (1); + } + +#if DEBUG_DNS + printf ("getaddrinfo returns:\n"); +#endif + num_hosts = 0; + for (ai = ai_head; ai != NULL; ai = ai->ai_next) { +#if DEBUG_DNS + ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); + printf (" %s\n", ipaddr_str); +#endif + hosts[num_hosts] = ai; + if (num_hosts < MAX_HOSTS) num_hosts++; + } + +#if DEBUG_DNS + printf ("addresses for hostname:\n"); + for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str)); + printf (" %s\n", ipaddr_str); + } +#endif + + // Try each address until we find one that is successful. + + for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); + is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); +#if __WIN32__ + if (is == INVALID_SOCKET) { + printf ("Socket creation failed, err=%d", WSAGetLastError()); + WSACleanup(); + is = -1; + continue; + } +#else + if (err != 0) { + printf ("Socket creation failed, err=%s", gai_strerror(err)); + (void) close (is); + is = -1; + continue; + } +#endif + +#ifndef DEBUG_DNS + err = connect(is, ai->ai_addr, (int)ai->ai_addrlen); +#if __WIN32__ + if (err == SOCKET_ERROR) { +#if DEBUGx + printf("Connect to %s on %s (%s), port %s failed.\n", + description[my_index], hostname[my_index], ipaddr_str, port[my_index]); +#endif + closesocket (is); + is = -1; + continue; + } +#else + if (err != 0) { +#if DEBUGx + printf("Connect to %s on %s (%s), port %s failed.\n", + description[my_index], hostname[my_index], ipaddr_str, port[my_index]); +#endif + (void) close (is); + is = -1; + continue; + } + int flag = 1; + err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag)); + if (err < 0) { + printf("setsockopt TCP_NODELAY failed.\n"); + } +#endif + +/* Success. */ + + + server_sock[my_index] = is; +#endif + break; + } + + freeaddrinfo(ai_head); + + if (server_sock[my_index] == -1) { + + printf("TNC %d unable to connect to %s on %s (%s), port %s\n", + my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] ); + exit (1); + } + + +#if 1 // Temp test just to get something. + +/* + * Send command to toggle reception of frames in raw format. + */ + memset (&mon_cmd, 0, sizeof(mon_cmd)); + + mon_cmd.datakind = 'k'; + + SOCK_SEND(server_sock[my_index], (char*)(&mon_cmd), sizeof(mon_cmd)); + +#endif + +/* + * Send command to register my callsign for incoming connect request. + * Not really needed when we initiate the connection. + */ + + memset (&mon_cmd, 0, sizeof(mon_cmd)); + + mon_cmd.datakind = 'X'; + strlcpy (mon_cmd.call_from, tnc_address[my_index], sizeof(mon_cmd.call_from)); + + SOCK_SEND(server_sock[my_index], (char*)(&mon_cmd), sizeof(mon_cmd)); + + + +/* + * Inform main program and observer that we are ready to go. + */ + printf("TNC %d now available. %s on %s (%s), port %s\n", + my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] ); + is_connected[my_index] = 0; + +/* + * Print what we get from TNC. + */ + + while (1) { + int n; + + n = SOCK_RECV (server_sock[my_index], (char*)(&mon_cmd), sizeof(mon_cmd)); + + if (n != sizeof(mon_cmd)) { + printf ("Read error, TNC %d received %d command bytes.\n", my_index, n); + exit (1); + } + + +#if DEBUGx + printf ("TNC %d received '%c' data, data_len = %d\n", + my_index, mon_cmd.datakind, mon_cmd.data_len); +#endif + assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data))); + + if (mon_cmd.data_len > 0) { + + n = SOCK_RECV (server_sock[my_index], data, mon_cmd.data_len); + + if (n != mon_cmd.data_len) { + printf ("Read error, TNC %d received %d data bytes.\n", my_index, n); + exit (1); + } + data[mon_cmd.data_len] = '\0'; + } + +/* + * What did we get? + */ + + dnow = dtime_now(); + + switch (mon_cmd.datakind) { + + case 'C': // AX.25 Connection Received + + printf("%*s[R %.3f] *** Connected to %s ***\n", my_index*column_width, "", dnow-start_dtime, mon_cmd.call_from); + is_connected[my_index] = 1; + + break; + + case 'D': // Connected AX.25 Data + + printf("%*s[R %.3f] %s\n", my_index*column_width, "", dnow-start_dtime, data); + + process_rec_data (my_index, data); + + + if (isdigit(data[0]) && isdigit(data[1]) && isdigit(data[2]) && isdigit(data[3]) && + strncmp(data+4, " send", 5) == 0) { + // Expected message. Make sure it is expected sequence and send reply. + int n = atoi(data); + char reply[80]; + sprintf (reply, "%04d reply\r", n); + tnc_send_data (my_index, 1 - my_index, reply); + + // HACK! + // It gets very confusing because N(S) and N(R) are very close. + // Send a couple dozen I frames so they will be easier to distinguish visually. + // Currently don't have the same in serial port version. + + // We change the length each time to test segmentation. + // Set PACLEN to some very small number like 5. + + if (n == 1 && max_count > 1) { + int j; + for (j = 1; j <= 26; j++) { + sprintf (reply, "%.*s\r", j, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + tnc_send_data (my_index, 1 - my_index, reply); + } + } + } + + break; + + case 'd': // Disconnected + + printf("%*s[R %.3f] *** Disconnected from %s ***\n", my_index*column_width, "", dnow-start_dtime, mon_cmd.call_from); + is_connected[my_index] = 0; + + break; + + case 'y': // Outstanding frames waiting on a Port + + printf("%*s[R %.3f] *** Outstanding frames waiting %d ***\n", my_index*column_width, "", dnow-start_dtime, 123); // TODO + + break; + + default: + + //printf("%*s[R %.3f] --- Ignoring cmd kind '%c' ---\n", my_index*column_width, "", dnow-start_dtime, mon_cmd.datakind); + + break; + } + } + +} /* end tnc_thread_net */ + + + + + +/*------------------------------------------------------------------- + * + * Name: tnc_thread_serial + * + * Purpose: Establish connection with a TNC via serial port. + * + * Inputs: arg - My instance index, 0 thru MAX_TNC-1. + * + * Outputs: packets - Received packets are put in the corresponding column + * and sent to a common function to check that they + * all arrived in order. + * + * Global Out: is_connected - Updated when connected/disconnected notfications are received. + * + * Description: Perform any necessary configuration for the TNC then wait + * for responses and process them. + * + *--------------------------------------------------------------------*/ + + +#if __WIN32__ +typedef HANDLE MYFDTYPE; +#define MYFDERROR INVALID_HANDLE_VALUE +#else +typedef int MYFDTYPE; +#define MYFDERROR (-1) +#endif + + +#if __WIN32__ +static unsigned __stdcall tnc_thread_serial (void *arg) +#else +static void * tnc_thread_serial (void *arg) +#endif +{ + int my_index = (int)(ptrdiff_t)arg; + char cmd[80]; + + serial_fd[my_index] = serial_port_open (port[my_index], 9600); + + if (serial_fd[my_index] == MYFDERROR) { + printf("TNC %d unable to connect to %s on %s.\n", + my_index, description[my_index], port[my_index] ); + exit (1); + } + + +/* + * Make sure we are in command mode. + */ + + strcpy (cmd, "\003\rreset\r"); + serial_port_write (serial_fd[my_index], cmd, strlen(cmd)); + SLEEP_MS (3000); + + strcpy (cmd, "echo on\r"); + serial_port_write (serial_fd[my_index], cmd, strlen(cmd)); + SLEEP_MS (200); + +// do any necessary set up here. such as setting mycall + + sprintf (cmd, "mycall %s\r", tnc_address[my_index]); + serial_port_write (serial_fd[my_index], cmd, strlen(cmd)); + SLEEP_MS (200); + +// Don't want to stop tty output when typing begins. + + strcpy (cmd, "flow off\r"); + serial_port_write (serial_fd[my_index], cmd, strlen(cmd)); + + strcpy (cmd, "echo off\r"); + serial_port_write (serial_fd[my_index], cmd, strlen(cmd)); + +/* Success. */ + + printf("TNC %d now available. %s on %s\n", + my_index, description[my_index], port[my_index] ); + is_connected[my_index] = 0; + + +/* + * Read and print. + */ + + while (1) { + int ch; + char result[500]; + int len; + int done; + + len = 0; + result[len] = '\0'; + done = 0; + + while ( ! done) { + + ch = serial_port_get1(serial_fd[my_index]); + + if (ch < 0) { + printf("TNC %d fatal read error.\n", my_index); + exit (1); + } + + if (ch == '\r' || ch == '\n') { + done = 1; + } + else if (ch == XOFF) { + double dnow = dtime_now(); + printf("%*s[R %.3f] \n", my_index*column_width, "", dnow-start_dtime); + busy[my_index] = 1; + } + else if (ch == XON) { + double dnow = dtime_now(); + printf("%*s[R %.3f] \n", my_index*column_width, "", dnow-start_dtime); + busy[my_index] = 0; + } + else if (isprint(ch)) { + result[len] = ch; + len++; + result[len] = '\0'; + } + else { + char hex[12]; + + snprintf (hex, sizeof(hex), "", ch); + strlcat (result, hex, sizeof(result)); + len = strlen(result); + } + if (strcmp(result, "cmd:") == 0) { + done = 1; + have_cmd_prompt[my_index] = 1; + } + else { + have_cmd_prompt[my_index] = 0; + } + } + + if (len > 0) { + + double dnow = dtime_now(); + + printf("%*s[R %.3f] %s\n", my_index*column_width, "", dnow-start_dtime, result); + + if (strncmp(result, "*** CONNECTED", 13) == 0) { + is_connected[my_index] = 1; + } + + if (strncmp(result, "*** DISCONNECTED", 16) == 0) { + is_connected[my_index] = 0; + } + + if (strncmp(result, "Not while connected", 19) == 0) { + // Not expecting this. + // What to do? + } + + process_rec_data (my_index, result); + + if (isdigit(result[0]) && isdigit(result[1]) && isdigit(result[2]) && isdigit(result[3]) && + strncmp(result+4, " send", 5) == 0) { + // Expected message. Make sure it is expected sequence and send reply. + int n = atoi(result); + char reply[80]; + sprintf (reply, "%04d reply\r", n); + tnc_send_data (my_index, 1 - my_index, reply); + } + + } + } + +} /* end tnc_thread_serial */ + + + + +static void tnc_connect (int from, int to) +{ + + double dnow = dtime_now(); + + printf("%*s[T %.3f] *** Send connect request ***\n", from*column_width, "", dnow-start_dtime); + + if (using_tcp[from]) { + +//struct agwpe_s { +// short portx; /* 0 for first, 1 for second, etc. */ +// short port_hi_reserved; +// short datakind; /* message type */ +// short kind_hi; +// char call_from[10]; +// char call_to[10]; +// int data_len; /* Number of data bytes following. */ +// int user_reserved; +//}; + struct agwpe_s cmd; + + memset (&cmd, 0, sizeof(cmd)); + + cmd.datakind = 'C'; + strlcpy (cmd.call_from, tnc_address[from], sizeof(cmd.call_from)); + strlcpy (cmd.call_to, tnc_address[to], sizeof(cmd.call_to)); + + SOCK_SEND(server_sock[from], (char*)(&cmd), sizeof(cmd)); + } + else { + + char cmd[80]; + + if (! have_cmd_prompt[from]) { + + SLEEP_MS (1500); + strcpy (cmd, "\003\003\003"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + SLEEP_MS (1500); + + strcpy (cmd, "\r"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + SLEEP_MS (200); + } + + sprintf (cmd, "connect %s\r", tnc_address[to]); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + } + +} /* end tnc_connect */ + + +static void tnc_disconnect (int from, int to) +{ + double dnow = dtime_now(); + + printf("%*s[T %.3f] *** Send disconnect request ***\n", from*column_width, "", dnow-start_dtime); + + if (using_tcp[from]) { + + struct agwpe_s cmd; + + memset (&cmd, 0, sizeof(cmd)); + + cmd.datakind = 'd'; + strlcpy (cmd.call_from, tnc_address[from], sizeof(cmd.call_from)); + strlcpy (cmd.call_to, tnc_address[to], sizeof(cmd.call_to)); + + SOCK_SEND(server_sock[from], (char*)(&cmd), sizeof(cmd)); + } + else { + + char cmd[80]; + + if (! have_cmd_prompt[from]) { + + SLEEP_MS (1500); + strcpy (cmd, "\003\003\003"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + SLEEP_MS (1500); + + strcpy (cmd, "\r"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + SLEEP_MS (200); + } + + strcpy (cmd, "disconnect\r"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + } + +} /* end tnc_disconnect */ + + +static void tnc_reset (int from, int to) +{ + double dnow = dtime_now(); + + printf("%*s[T %.3f] *** Send reset ***\n", from*column_width, "", dnow-start_dtime); + + if (using_tcp[from]) { + + + } + else { + + char cmd[80]; + + SLEEP_MS (1500); + strcpy (cmd, "\003\003\003"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + SLEEP_MS (1500); + + strcpy (cmd, "\r"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + SLEEP_MS (200); + + strcpy (cmd, "reset\r"); + serial_port_write (serial_fd[from], cmd, strlen(cmd)); + } + +} /* end tnc_disconnect */ + + + +static void tnc_send_data (int from, int to, char * data) +{ + double dnow = dtime_now(); + + printf("%*s[T %.3f] %s\n", from*column_width, "", dnow-start_dtime, data); + + if (using_tcp[from]) { + + struct { + struct agwpe_s hdr; + char data[256]; + } cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + + cmd.hdr.datakind = 'D'; + cmd.hdr.pid = 0xf0; + snprintf (cmd.hdr.call_from, sizeof(cmd.hdr.call_from), "%s", tnc_address[from]); + snprintf (cmd.hdr.call_to, sizeof(cmd.hdr.call_to), "%s", tnc_address[to]); + cmd.hdr.data_len = strlen(data); + strlcpy (cmd.data, data, sizeof(cmd.data)); + + SOCK_SEND(server_sock[from], (char*)(&cmd), sizeof(cmd.hdr) + strlen(data)); + } + else { + + // The assumption is that we are in CONVERS mode. + // The data sould be terminated by carriage return. + + int timeout = 600; // 60 sec. I've seen it take more than 20. + while (timeout > 0 && busy[from]) { + SLEEP_MS(100); + timeout--; + } + if (timeout == 0) { + printf ("ERROR: Gave up waiting while TNC busy.\n"); + tnc_disconnect (0,1); + SLEEP_MS(5000); + printf ("TEST FAILED!\n"); + exit (EXIT_FAILURE); + } + else { + serial_port_write (serial_fd[from], data, strlen(data)); + } + } + +} /* end tnc_disconnect */ + + + + +/* end tnctest.c */ diff --git a/src/xmit.c b/src/xmit.c index c4e6985..65faafd 100644 --- a/src/xmit.c +++ b/src/xmit.c @@ -766,7 +766,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) // machine, that the transmission opportunity has arrived." pre_flags = MS_TO_BITS(xmit_txdelay[chan] * 10, chan) / 8; - num_bits = hdlc_send_flags (chan, pre_flags, 0); + num_bits = layer2_preamble_postamble (chan, pre_flags, 0, save_audio_config_p); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: t=%.3f, txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", dtime_now()-time_ptt, xmit_txdelay[chan], pre_flags, num_bits); @@ -867,7 +867,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) */ post_flags = MS_TO_BITS(xmit_txtail[chan] * 10, chan) / 8; - nb = hdlc_send_flags (chan, post_flags, 1); + nb = layer2_preamble_postamble (chan, post_flags, 1, save_audio_config_p); num_bits += nb; #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -962,8 +962,6 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) static int send_one_frame (int c, int p, packet_t pp) { - unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; - int flen; char stemp[1024]; /* max size needed? */ int info_len; unsigned char *pinfo; @@ -1007,10 +1005,10 @@ static int send_one_frame (int c, int p, packet_t pp) ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_XMIT); -#if 0 +#if 0 // FIXME - enable this? dw_printf ("[%d%c%s%s] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L', - save_audio_config_p->fx25_xmit_enable ? "F" : "", + save_audio_config_p->achan[c].fx25_strength ? "F" : "", ts); #else dw_printf ("[%d%c%s] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L', ts); @@ -1064,9 +1062,6 @@ static int send_one_frame (int c, int p, packet_t pp) /* * Transmit the frame. */ - flen = ax25_pack (pp, fbuf); - assert (flen >= 1 && flen <= (int)(sizeof(fbuf))); - int send_invalid_fcs2 = 0; if (save_audio_config_p->xmit_error_rate != 0) { @@ -1079,7 +1074,7 @@ static int send_one_frame (int c, int p, packet_t pp) } } - nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2, save_audio_config_p->fx25_xmit_enable); + nb = layer2_send_frame (c, pp, send_invalid_fcs2, save_audio_config_p); // Optionally send confirmation to AGW client app if monitoring enabled. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e3e375..1b08a07 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ set(GEN_PACKETS_BIN "${CMAKE_BINARY_DIR}/src/gen_packets${CMAKE_EXECUTABLE_SUFFI set(ATEST_BIN "${CMAKE_BINARY_DIR}/src/atest${CMAKE_EXECUTABLE_SUFFIX}") set(FXSEND_BIN "${CMAKE_BINARY_DIR}/test/fxsend${CMAKE_EXECUTABLE_SUFFIX}") set(FXREC_BIN "${CMAKE_BINARY_DIR}/test/fxrec${CMAKE_EXECUTABLE_SUFFIX}") +set(IL2P_TEST_BIN "${CMAKE_BINARY_DIR}/test/il2p_test${CMAKE_EXECUTABLE_SUFFIX}") if(WIN32) set(CUSTOM_SCRIPT_SUFFIX ".bat") @@ -14,7 +15,9 @@ else() endif() set(TEST_CHECK-FX25_FILE "check-fx25") +set(TEST_CHECK-IL2P_FILE "check-il2p") set(TEST_CHECK-MODEM1200_FILE "check-modem1200") +set(TEST_CHECK-MODEM1200_IL2P_FILE "check-modem1200-i") set(TEST_CHECK-MODEM300_FILE "check-modem300") set(TEST_CHECK-MODEM9600_FILE "check-modem9600") set(TEST_CHECK-MODEM19200_FILE "check-modem19200") @@ -31,12 +34,24 @@ configure_file( @ONLY ) +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-IL2P_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}" + @ONLY + ) + configure_file( "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM1200_FILE}" "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_FILE}${CUSTOM_SCRIPT_SUFFIX}" @ONLY ) +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM1200_IL2P_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}" + @ONLY + ) + configure_file( "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM300_FILE}" "${CUSTOM_TEST_BINARY_DIR}${CUSTOM_SCRIPT_SUFFIX}" @@ -100,63 +115,12 @@ if(WIN32 OR CYGWIN) ) endif() -# Why is there a special atest9 instead of using the normal one? - -# Unit test for demodulators -list(APPEND atest9_SOURCES - ${CUSTOM_SRC_DIR}/atest.c - ${CUSTOM_SRC_DIR}/ais.c - ${CUSTOM_SRC_DIR}/demod.c - ${CUSTOM_SRC_DIR}/dsp.c - ${CUSTOM_SRC_DIR}/demod_afsk.c - ${CUSTOM_SRC_DIR}/demod_psk.c - ${CUSTOM_SRC_DIR}/demod_9600.c - ${CUSTOM_SRC_DIR}/fx25_extract.c - ${CUSTOM_SRC_DIR}/fx25_init.c - ${CUSTOM_SRC_DIR}/fx25_rec.c - ${CUSTOM_SRC_DIR}/hdlc_rec.c - ${CUSTOM_SRC_DIR}/hdlc_rec2.c - ${CUSTOM_SRC_DIR}/multi_modem.c - ${CUSTOM_SRC_DIR}/rrbb.c - ${CUSTOM_SRC_DIR}/fcs_calc.c - ${CUSTOM_SRC_DIR}/ax25_pad.c - ${CUSTOM_SRC_DIR}/decode_aprs.c - ${CUSTOM_SRC_DIR}/dwgpsnmea.c - ${CUSTOM_SRC_DIR}/dwgps.c - ${CUSTOM_SRC_DIR}/dwgpsd.c - ${CUSTOM_SRC_DIR}/serial_port.c - ${CUSTOM_SRC_DIR}/latlong.c - ${CUSTOM_SRC_DIR}/symbols.c - ${CUSTOM_SRC_DIR}/textcolor.c - ${CUSTOM_SRC_DIR}/telemetry.c - ${CUSTOM_SRC_DIR}/dtime_now.c - ${CUSTOM_SRC_DIR}/tt_text.c - ) - if(WIN32 OR CYGWIN) list(REMOVE_ITEM atest9_SOURCES ${CUSTOM_SRC_DIR}/dwgpsd.c ) endif() -add_executable(atest9 - ${atest9_SOURCES} - ) - -target_link_libraries(atest9 - ${MISC_LIBRARIES} - ${REGEX_LIBRARIES} - ${GPSD_LIBRARIES} - Threads::Threads - ) - -if(WIN32 OR CYGWIN) - set_target_properties(atest9 - PROPERTIES COMPILE_FLAGS "-DUSE_REGEX_STATIC" - ) - target_link_libraries(atest9 ws2_32) -endif() - # Unit test for inner digipeater algorithm list(APPEND dtest_SOURCES @@ -468,6 +432,39 @@ set_target_properties(fxrec ) +# Unit Test IL2P with out modems. + +list(APPEND il2p_test_SOURCES + ${CUSTOM_SRC_DIR}/il2p_test.c + ${CUSTOM_SRC_DIR}/il2p_init.c + ${CUSTOM_SRC_DIR}/il2p_rec.c + ${CUSTOM_SRC_DIR}/il2p_send.c + ${CUSTOM_SRC_DIR}/il2p_codec.c + ${CUSTOM_SRC_DIR}/il2p_payload.c + ${CUSTOM_SRC_DIR}/il2p_header.c + ${CUSTOM_SRC_DIR}/il2p_scramble.c + ${CUSTOM_SRC_DIR}/ax25_pad.c + ${CUSTOM_SRC_DIR}/ax25_pad2.c + ${CUSTOM_SRC_DIR}/fx25_encode.c + ${CUSTOM_SRC_DIR}/fx25_extract.c + ${CUSTOM_SRC_DIR}/fx25_init.c + ${CUSTOM_SRC_DIR}/fcs_calc.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(il2p_test + ${il2p_test_SOURCES} + ) + +#FIXME - remove if not needed. +#set_target_properties(il2p_test +# PROPERTIES COMPILE_FLAGS "-DXXXXX" +# ) + +target_link_libraries(il2p_test + ${MISC_LIBRARIES} + ) + # doing ctest on previous programs add_test(dtest dtest) @@ -483,7 +480,9 @@ add_test(xidtest xidtest) add_test(dtmftest dtmftest) add_test(check-fx25 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-FX25_FILE}${CUSTOM_SCRIPT_SUFFIX}") +add_test(check-il2p "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}") add_test(check-modem1200 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_FILE}${CUSTOM_SCRIPT_SUFFIX}") +add_test(check-modem1200-i "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}") add_test(check-modem300 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM300_FILE}${CUSTOM_SCRIPT_SUFFIX}") add_test(check-modem9600 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM9600_FILE}${CUSTOM_SCRIPT_SUFFIX}") add_test(check-modem19200 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM19200_FILE}${CUSTOM_SCRIPT_SUFFIX}") @@ -492,8 +491,6 @@ add_test(check-modem2400-b "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-b_F add_test(check-modem2400-g "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-g_FILE}${CUSTOM_SCRIPT_SUFFIX}") add_test(check-modem4800 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM4800_FILE}${CUSTOM_SCRIPT_SUFFIX}") -# TODO miss the audio file -# ./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out # ----------------------------- Manual tests and experiments --------------------------- diff --git a/test/scripts/check-il2p b/test/scripts/check-il2p new file mode 100644 index 0000000..da40472 --- /dev/null +++ b/test/scripts/check-il2p @@ -0,0 +1,4 @@ +@CUSTOM_SHELL_SHABANG@ + +@IL2P_TEST_BIN@ + diff --git a/test/scripts/check-modem1200-i b/test/scripts/check-modem1200-i new file mode 100644 index 0000000..9ef01ba --- /dev/null +++ b/test/scripts/check-modem1200-i @@ -0,0 +1,4 @@ +@CUSTOM_SHELL_SHABANG@ + +@GEN_PACKETS_BIN@ -I1 -n 100 -o test12-il2p.wav +@ATEST_BIN@ -P+ -D1 -L92 -G94 test12-il2p.wav