This commit is contained in:
wb2osz 2020-01-05 20:08:22 -05:00
parent f08ed518b3
commit 1f2a5cfdb8
35 changed files with 3692 additions and 305 deletions

View File

@ -5,15 +5,19 @@
## Version 1.6 -- Under Development ##
### Bugs Fixed: ###
### New Build Procedure: ###
- Rather than trying to keep a bunch of different platform specific Makefiles in sync, "cmake" is now used for greater portability and easier maintenance.
- Proper counting of frames in transmit queue for AGW protocol 'Y' command.
### New Features: ###
- "-X" option enables FX.25 transmission. FX.25 reception is always enabled so you don't need to do anything special.
- "-t" option now accepts more values to accommodate inconsistent handling of text color control codes by different terminal emulators. The default, 1, should work with most modern terminal types. If the colors are not right, try "-t 9" to see the result of the different choices and pick the best one. If none of them look right, file a bug report and specify: operating system version (e.g. Raspbian Buster), terminal emulator type and version (e.g. LXTerminal 0.3.2). Include a screen capture.
@ -27,10 +31,20 @@
### Bugs Fixed: ###
- Proper counting of frames in transmit queue for AGW protocol 'Y' command.
### New Documentation: ###
- ***AX.25 + FEC = FX.25***
- ***AX.25 Throughput: Why is 9600 bps Packet Radio only twice as fast as 1200?***
- [***Ham Radio of Things - IoT over Ham Radio***](https://github.com/wb2osz/hrot)
- Power Point slide show in separate repository. [https://github.com/wb2osz/direwolf-presentation ](https://github.com/wb2osz/direwolf-presentation)

View File

@ -9,7 +9,10 @@ Why settle for mediocre receive performance from a 1980's technology TNC using
![](tnc-test-cd-results.png)
Dire Wolf now includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction/) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless.
![](fx25.png)
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:
@ -20,7 +23,7 @@ Without any additional software, it can perform as:
- [APRStt](http://www.aprs.org/aprstt.html) gateway
It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [Winlink Express (formerly known as RMS Express, formerly known as Winlink 2000 or WL2K)](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), and many others.
It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [Winlink Express (formerly known as RMS Express, formerly known as Winlink 2000 or WL2K)](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), [Ham Radio of Things](https://github.com/wb2osz/hrot), and many others.
## Features & Benefits ##
@ -48,6 +51,11 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h
IGate stations allow communication between disjoint radio networks by allowing some content to flow between them over the Internet.
- **Ham Radio of Things.**
There have been occasional mentions of merging Ham Radio with the Internet of Things but only ad hoc incompatible narrowly focused applications. Here is a proposal for a standardized more flexible method so different systems can communicate with each other.
[Ham Radio of Things - IoT over Ham Radio](https://github.com/wb2osz/hrot)
- **AX.25 v2.2 Link Layer.**
@ -63,7 +71,7 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h
Lower cost and better performance than specialized hardware.
Compatible interfaces include [UDRC](https://nw-digital-radio.groups.io/g/udrc/wiki/UDRC%E2%84%A2-and-Direwolf-Packet-Modem), [SignaLink USB](http://www.tigertronics.com/slusbmain.htm), [DMK URI](http://www.dmkeng.com/URI_Order_Page.htm), [RB-USB RIM](http://www.repeater-builder.com/products/usb-rim-lite.html), [RA-35](http://www.masterscommunications.com/products/radio-adapter/ra35.html), and many others.
Compatible interfaces include [DRAWS](http://nwdigitalradio.com/draws/), [UDRC](https://nw-digital-radio.groups.io/g/udrc/wiki/UDRC%E2%84%A2-and-Direwolf-Packet-Modem), [SignaLink USB](http://www.tigertronics.com/slusbmain.htm), [DMK URI](http://www.dmkeng.com/URI_Order_Page.htm), [RB-USB RIM](http://www.repeater-builder.com/products/usb-rim-lite.html), [RA-35](http://www.masterscommunications.com/products/radio-adapter/ra35.html), [DINAH](https://hamprojects.info/dinah/), [SHARI](https://hamprojects.info/shari/), and many others.

Binary file not shown.

View File

@ -8,6 +8,7 @@ install(FILES "${CUSTOM_DOC_DIR}/APRS-Telemetry-Toolkit.pdf" DESTINATION ${INSTA
install(FILES "${CUSTOM_DOC_DIR}/APRStt-Implementation-Notes.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/APRStt-interface-for-SARTrack.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/APRStt-Listening-Example.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/AX25_plus_FEC_equals_FX25.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/Bluetooth-KISS-TNC.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/Going-beyond-9600-baud.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/Raspberry-Pi-APRS.pdf" DESTINATION ${INSTALL_DOC_DIR})
@ -16,3 +17,4 @@ install(FILES "${CUSTOM_DOC_DIR}/Raspberry-Pi-SDR-IGate.pdf" DESTINATION ${INSTA
install(FILES "${CUSTOM_DOC_DIR}/Successful-APRS-IGate-Operation.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/User-Guide.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/WA8LMF-TNC-Test-CD-Results.pdf" DESTINATION ${INSTALL_DOC_DIR})
install(FILES "${CUSTOM_DOC_DIR}/Why-is-9600-only-twice-as-fast-as-1200.pdf" DESTINATION ${INSTALL_DOC_DIR})

View File

@ -27,6 +27,14 @@ Brief summary of packet radio / APRS history and the capbilities of Dire Wolf.
These dive into more detail for specialized topics or typical usage scenarios.
- [**AX.25 + FEC = FX.25**](AX25_plus_FEC_equals_FX25.pdf) [ [*download*](../../../raw/dev/doc/AX25_plus_FEC_equals_FX25.pdf) ]
What can you do if your radio signal isnt quite strong enough to get through reliably? Move to higher ground? Get a better antenna? More power? Use very narrow bandwidth and very slow data?
Sometimes those are not options. Another way to improve communication reliability is to add redundant information so the message will still get through even if small parts are missing. FX.25 adds forward error correction (FEC) which maintaining complete compatibility with older equipment.
- [**AX.25 Throughput: Why is 9600 bps Packet Radio only twice as fast as 1200?**](Why-is-9600-only-twice-as-fast-as-1200.pdf) [ [*download*](../../../raw/dev/doc/Why-is-9600-only-twice-as-fast-as-1200.pdf) ]
Simply switching to a higher data rate will probably result in great disappointment. You might expect it to be 8 times faster but it can turn out to be only twice as fast.

BIN
fx25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -44,6 +44,11 @@ list(APPEND direwolf_SOURCES
encode_aprs.c
fcs_calc.c
fcs_calc.c
fx25_encode.c
fx25_extract.c
fx25_init.c
fx25_rec.c
fx25_send.c
gen_tone.c
hdlc_rec.c
hdlc_rec2.c
@ -257,6 +262,9 @@ target_link_libraries(log2gpx
list(APPEND gen_packets_SOURCES
gen_packets.c
ax25_pad.c
fx25_encode.c
fx25_init.c
fx25_send.c
hdlc_send.c
fcs_calc.c
gen_tone.c
@ -284,6 +292,9 @@ list(APPEND atest_SOURCES
demod_psk.c
demod_9600.c
dsp.c
fx25_extract.c
fx25_init.c
fx25_rec.c
hdlc_rec.c
hdlc_rec2.c
multi_modem.c
@ -426,6 +437,33 @@ if(WIN32 OR CYGWIN)
target_link_libraries(ttcalc ws2_32)
endif()
# Sample for packet radio server application.
# appserver
list(APPEND appserver_SOURCES
appserver.c
agwlib.c
dwsock.c
dtime_now.c
ax25_pad.c
fcs_calc.c
textcolor.c
)
add_executable(appserver
${appserver_SOURCES}
)
target_link_libraries(appserver
${MISC_LIBRARIES}
Threads::Threads
)
if(WIN32 OR CYGWIN)
target_link_libraries(appserver ws2_32)
endif()
install(TARGETS direwolf DESTINATION ${INSTALL_BIN_DIR})
install(TARGETS decode_aprs DESTINATION ${INSTALL_BIN_DIR})
install(TARGETS text2tt DESTINATION ${INSTALL_BIN_DIR})
@ -438,6 +476,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 appserver DESTINATION ${INSTALL_BIN_DIR})
if(UDEV_FOUND)
install(TARGETS cm108 DESTINATION ${INSTALL_BIN_DIR})
endif()

749
src/agwlib.c Normal file
View File

@ -0,0 +1,749 @@
// ****** PRELIMINARY - needs work ******
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
/*------------------------------------------------------------------
*
* Module: agwlib.c
*
* Purpose: Sample application Program Interface (API) to use network TNC with AGW protocol.
*
* Input:
*
* Outputs:
*
* Description: This file contains functions to attach to a TNC over a TCP socket and send
* commands to it. The current list includes some of the following:
*
* 'C' Connect, Start an AX.25 Connection
* 'v' Connect VIA, Start an AX.25 circuit thru digipeaters
* 'c' Connection with non-standard PID
* 'D' Send Connected Data
* 'd' Disconnect, Terminate an AX.25 Connection
* 'X' Register CallSign
* 'x' Unregister CallSign
* 'R' Request for version number.
* 'G' Ask about radio ports.
* 'g' Capabilities of a port.
* 'k' Ask to start receiving RAW AX25 frames.
* 'm' Ask to start receiving Monitor AX25 frames.
* 'V' Transmit UI data frame.
* 'H' Report recently heard stations. Not implemented yet in direwolf.
* 'K' Transmit raw AX.25 frame.
* 'y' Ask Outstanding frames waiting on a Port
* 'Y' How many frames waiting for transmit for a particular station
*
*
* The user supplied application must supply functions to handle or ignore
* messages that come from the TNC. Common examples:
*
* 'C' AX.25 Connection Received
* 'D' Connected AX.25 Data
* 'd' Disconnected
* 'R' Reply to Request for version number.
* 'G' Reply to Ask about radio ports.
* 'g' Reply to capabilities of a port.
* 'K' Received AX.25 frame in raw format. (Enabled with 'k' command.)
* 'U' Received AX.25 frame in monitor format. (Enabled with 'm' command.)
* 'y' Outstanding frames waiting on a Port
* 'Y' How many frames waiting for transmit for a particular station
* 'C' AX.25 Connection Received
* 'D' Connected AX.25 Data
* 'd' Disconnected
*
*
*
* References: AGWPE TCP/IP API Tutorial
* http://uz7ho.org.ua/includes/agwpeapi.htm
*
* Usage: See appclient.c and appserver.c for examples of how to use this.
*
*---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include <winsock2.h>
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include "textcolor.h"
#include "dwsock.h" // socket helper functions.
#include "ax25_pad.h" // forAX25_MAX_PACKET_LEN
#include "agwlib.h"
/*
* Message header for AGW protocol.
* Multibyte numeric values require rearranging for big endian cpu.
*/
/*
* With MinGW version 4.6, obviously x86.
* or Linux gcc version 4.9, Linux ARM.
*
* $ gcc -E -dM - < /dev/null | grep END
* #define __ORDER_LITTLE_ENDIAN__ 1234
* #define __FLOAT_WORD_ORDER__ __ORDER_LITTLE_ENDIAN__
* #define __ORDER_PDP_ENDIAN__ 3412
* #define __ORDER_BIG_ENDIAN__ 4321
* #define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__
*
* This is for standard OpenWRT on MIPS.
*
* #define __ORDER_LITTLE_ENDIAN__ 1234
* #define __FLOAT_WORD_ORDER__ __ORDER_BIG_ENDIAN__
* #define __ORDER_PDP_ENDIAN__ 3412
* #define __ORDER_BIG_ENDIAN__ 4321
* #define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__
*
* This was reported for an old Mac with PowerPC processor.
* (Newer versions have x86.)
*
* $ gcc -E -dM - < /dev/null | grep END
* #define __BIG_ENDIAN__ 1
* #define _BIG_ENDIAN 1
*/
#if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
// gcc >= 4.2 has __builtin_swap32() but might not be compatible with older versions or other compilers.
#define host2netle(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) )
#define netle2host(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) )
#else
#define host2netle(x) (x)
#define netle2host(x) (x)
#endif
struct agw_hdr_s { /* Command header. */
unsigned char portx; /* 0 for first, 1 for second, etc. */
/* Dire Wolf uses the term "channel" to avoid confusion with TCP ports */
/* or other places port might be used. */
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;
char call_from[10];
char call_to[10];
int data_len_NETLE; /* Number of data bytes following. */
/* _NETLE suffix is reminder to convert for network byte order. */
int user_reserved_NETLE;
};
struct agw_cmd_s { /* Complete command with header and data. */
struct agw_hdr_s hdr; /* Command header. */
char data[AX25_MAX_PACKET_LEN]; /* Possible variable length data. */
};
/*-------------------------------------------------------------------
*
* Name: agwlib_init
*
* Purpose: Attach to TNC over TCP.
*
* Inputs: host - Host name or address. Often "localhost".
*
* port - TCP port number as text. Usually "8000".
*
* init_func - Call this function after establishing communication
* with the TNC. We put it here, so that it can be done
* again automatically if the TNC disappears and we
* reattach to it.
* It must return 0 for success.
* Can be NULL if not needed.
*
* Returns: 0 for success, -1 for failure.
*
* Description: This starts up a thread which listens to the socket and
* dispatches the messages to the corresponding callback functions.
* It will also attempt to re-establish communication with the
* TNC if it goes away.
*
*--------------------------------------------------------------------*/
static char s_tnc_host[80];
static char s_tnc_port[8];
static int s_tnc_sock; // Socket handle or file descriptor.
static int (*s_tnc_init_func)(void); // Call after establishing socket.
// TODO: define macros somewhere to hide platform specifics.
#if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
#if __WIN32__
static HANDLE tnc_listen_th;
static THREAD_F tnc_listen_thread (void *arg);
#else
static pthread_t tnc_listen_tid;
static THREAD_F tnc_listen_thread (void *arg);
#endif
int agwlib_init (char *host, char *port, int (*init_func)(void))
{
char tncaddr[DWSOCK_IPADDR_LEN];
int e;
strlcpy (s_tnc_host, host, sizeof(s_tnc_host));
strlcpy (s_tnc_port, port, sizeof(s_tnc_port));
s_tnc_sock = -1;
s_tnc_init_func = init_func;
dwsock_init();
s_tnc_sock = dwsock_connect (host, port, "TNC", 0, 0, tncaddr);
if (s_tnc_sock == -1) {
return (-1);
}
/*
* Incoming messages are dispatched to application-supplied callback functions.
* If the TNC disappears, try to reestablish communication.
*/
#if __WIN32__
tnc_listen_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_thread, (void *)NULL, 0, NULL);
if (tnc_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: Could not create TNC listening thread\n");
return (-1);
}
#else
e = pthread_create (&tnc_listen_tid, NULL, tnc_listen_thread, (void *)NULL);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Internal error: Could not create TNC listening thread");
return (-1);
}
#endif
// TNC initialization if specified.
if (s_tnc_init_func != NULL) {
e = (*s_tnc_init_func)();
return (e);
}
return (0);
}
/*-------------------------------------------------------------------
*
* Name: tnc_listen_thread
*
* Purpose: Listen for anything from TNC and process it.
* Reconnect if something goes wrong and we got disconnected.
*
* Inputs: s_tnc_host
* s_tnc_port
*
* Outputs: s_tnc_sock - File descriptor for communicating with TNC.
* Will be -1 if not connected.
*
*--------------------------------------------------------------------*/
static void process_from_tnc (struct agw_cmd_s *cmd);
#if __WIN32__
static unsigned __stdcall tnc_listen_thread (void *arg)
#else
static void * tnc_listen_thread (void *arg)
#endif
{
char tncaddr[DWSOCK_IPADDR_LEN];
struct agw_cmd_s cmd;
while (1) {
/*
* Connect to TNC if not currently connected.
*/
if (s_tnc_sock == -1) {
text_color_set(DW_COLOR_ERROR);
// I'm using the term "attach" here, in an attempt to
// avoid confusion with the AX.25 connect.
dw_printf ("Attempting to reattach to network TNC...\n");
s_tnc_sock = dwsock_connect (s_tnc_host, s_tnc_port, "TNC", 0, 0, tncaddr);
if (s_tnc_sock != -1) {
dw_printf ("Succesfully reattached to network TNC.\n");
// Might need to run TNC initialization again.
// For example, a server would register its callsigns.
if (s_tnc_init_func != NULL) {
int e = (*s_tnc_init_func)();
(void) e;
}
}
SLEEP_SEC(5);
}
else {
int n = SOCK_RECV (s_tnc_sock, (char *)(&cmd.hdr), sizeof(cmd.hdr));
if (n == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Lost communication with network TNC. Will try to reattach.\n");
dwsock_close (s_tnc_sock);
s_tnc_sock = -1;
continue;
}
else if (n != sizeof(cmd.hdr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error reading message header from network TNC.\n");
dw_printf ("Tried to read %d bytes but got only %d.\n", (int)sizeof(cmd.hdr), n);
dw_printf ("Closing socket to TNC. Will try to reattach.\n");
dwsock_close (s_tnc_sock);
s_tnc_sock = -1;
continue;
}
/*
* Take some precautions to guard against bad data which could cause problems later.
*/
if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid channel number, %d, in command '%c', from network TNC.\n",
cmd.hdr.portx, cmd.hdr.datakind);
cmd.hdr.portx = 0; // avoid subscript out of bounds, try to keep going.
}
/*
* Call to/from fields are 10 bytes but contents must not exceeed 9 characters.
* It's not guaranteed that unused bytes will contain 0 so we
* don't issue error message in this case.
*/
cmd.hdr.call_from[sizeof(cmd.hdr.call_from)-1] = '\0';
cmd.hdr.call_to[sizeof(cmd.hdr.call_to)-1] = '\0';
/*
* Following data must fit in available buffer.
* Leave room for an extra nul byte terminator at end later.
*/
int data_len = netle2host(cmd.hdr.data_len_NETLE);
if (data_len < 0 || data_len > (int)(sizeof(cmd.data) - 1)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid message from network TNC.\n");
dw_printf ("Data Length of %d is out of range.\n", data_len);
/* This is a bad situation. */
/* If we tried to read again, the header probably won't be there. */
/* No point in trying to continue reading. */
dw_printf ("Closing connection to TNC.\n");
dwsock_close (s_tnc_sock);
s_tnc_sock = -1;
continue;
}
cmd.data[0] = '\0';
if (data_len > 0) {
n = SOCK_RECV (s_tnc_sock, cmd.data, data_len);
if (n != data_len) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error getting message data from network TNC.\n");
dw_printf ("Tried to read %d bytes but got only %d.\n", data_len, n);
dw_printf ("Closing socket to network TNC.\n\n");
dwsock_close (s_tnc_sock);
s_tnc_sock = -1;
continue;
}
if (n >= 0) {
cmd.data[n] = '\0'; // Terminate so it can be used as a C string.
}
process_from_tnc (&cmd);
} // additional data after command header
} // s_tnc_sock != -1
} // while (1)
return (0); // unreachable but shutup warning.
} // end tnc_listen_thread
/*
* The user supplied application must supply functions to handle or ignore
* messages that come from the TNC.
*/
static void process_from_tnc (struct agw_cmd_s *cmd)
{
int data_len = netle2host(cmd->hdr.data_len_NETLE);
//int session;
switch (cmd->hdr.datakind) {
case 'C': // AX.25 Connection Received
{
//agw_cb_C_connection_received (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, data_len, cmd->data);
// TODO: compute session id
// There are two different cases to consider here.
if (strncmp(cmd->data, "*** CONNECTED To Station", 24) == 0) {
// Incoming: Other station initiated the connect request.
on_C_connection_received (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, 1, cmd->data);
}
else if (strncmp(cmd->data, "*** CONNECTED With Station", 26) == 0) {
// Outgoing: Other station accepted my connect request.
on_C_connection_received (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, 0, cmd->data);
}
else {
// TBD
}
}
break;
case 'D': // Connected AX.25 Data
// FIXME: should probably add pid here.
agw_cb_D_connected_data (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, data_len, cmd->data);
break;
case 'd': // Disconnected
agw_cb_d_disconnected (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, data_len, cmd->data);
break;
case 'R': // Reply to Request for version number.
break;
case 'G': // Port Information.
// Data part should be fields separated by semicolon.
// First field is number of ports (we call them channels).
// Other fields are of the form "Port99 comment" where first is number 1.
{
int num_chan = 1; // FIXME: FIXME: actually parse it.
char *chans[20];
chans[0] = "Port1 blah blah";
chans[1] = "Port2 blah blah";
agw_cb_G_port_information (num_chan, chans);
}
break;
// TODO: Maybe fill in more someday.
case 'g': // Reply to capabilities of a port.
break;
case 'K': // Received AX.25 frame in raw format. (Enabled with 'k' command.)
break;
case 'U': // Received AX.25 frame in monitor format. (Enabled with 'm' command.)
break;
case 'y': // Outstanding frames waiting on a Port
break;
case 'Y': // How many frames waiting for transmit for a particular station
{
int *p = (int*)(cmd->data);
int frame_count = netle2host(*p);
agw_cb_Y_outstanding_frames_for_station (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, frame_count);
}
break;
default:
break;
}
} // end process_from_tnc
/*-------------------------------------------------------------------
*
* Name: agwlib_X_register_callsign
*
* Purpose: Tell TNC to accept incoming connect requests to given callsign.
*
* Inputs: chan - Radio channel number, first is 0.
*
* call_from - My callsign or alias.
*
* Returns: Number of bytes sent for success, -1 for error.
*
*--------------------------------------------------------------------*/
int agwlib_X_register_callsign (int chan, char *call_from)
{
struct agw_cmd_s cmd;
memset (&cmd.hdr, 0, sizeof(cmd.hdr));
cmd.hdr.portx = chan;
cmd.hdr.datakind = 'X';
strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from));
return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE)));
}
/*-------------------------------------------------------------------
*
* Name: agwlib_x_unregister_callsign
*
* Purpose: Tell TNC to stop accepting incoming connect requests to given callsign.
*
* Inputs: chan - Radio channel number, first is 0.
*
* call_from - My callsign or alias.
*
* Returns: Number of bytes sent for success, -1 for error.
*
* FIXME: question do we need channel here?
*
*--------------------------------------------------------------------*/
int agwlib_x_unregister_callsign (int chan, char *call_from)
{
struct agw_cmd_s cmd;
memset (&cmd.hdr, 0, sizeof(cmd.hdr));
cmd.hdr.portx = chan;
cmd.hdr.datakind = 'x';
strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from));
return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE)));
}
/*-------------------------------------------------------------------
*
* Name: agwlib_G_ask_port_information
*
* Purpose: Tell TNC to stop accepting incoming connect requests to given callsign.
*
* Inputs: call_from - My callsign or alias.
*
* Returns: 0 for success, -1 for error. TODO: all like this.
*
*--------------------------------------------------------------------*/
int agwlib_G_ask_port_information (void)
{
struct agw_cmd_s cmd;
memset (&cmd.hdr, 0, sizeof(cmd.hdr));
cmd.hdr.datakind = 'G';
int n = SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE));
return (n > 0 ? 0 : -1);
}
/*-------------------------------------------------------------------
*
* Name: agwlib_C_connect
*
* Purpose: Tell TNC to start sequence for connecting to remote station.
*
* Inputs: chan - Radio channel number, first is 0.
*
* call_from - My callsign.
*
* call_to - Callsign (or alias) of remote station.
*
* Returns: Number of bytes sent for success, -1 for error.
*
* Description: This only starts the sequence and does not wait.
* Success or failue will be indicated sometime later by ?
*
*--------------------------------------------------------------------*/
int agwlib_C_connect (int chan, char *call_from, char *call_to)
{
struct agw_cmd_s cmd;
memset (&cmd.hdr, 0, sizeof(cmd.hdr));
cmd.hdr.portx = chan;
cmd.hdr.datakind = 'C';
cmd.hdr.pid = 0xF0; // Shouldn't matter because this appears
// only in Information frame, not connect sequence.
strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from));
strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to));
return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE)));
}
/*-------------------------------------------------------------------
*
* Name: agwlib_d_disconnect
*
* Purpose: Tell TNC to disconnect from remote station.
*
* Inputs: chan - Radio channel number, first is 0.
*
* call_from - My callsign.
*
* call_to - Callsign (or alias) of remote station.
*
* Returns: Number of bytes sent for success, -1 for error.
*
* Description: This only starts the sequence and does not wait.
* Success or failue will be indicated sometime later by ?
*
*--------------------------------------------------------------------*/
int agwlib_d_disconnect (int chan, char *call_from, char *call_to)
{
struct agw_cmd_s cmd;
memset (&cmd.hdr, 0, sizeof(cmd.hdr));
cmd.hdr.portx = chan;
cmd.hdr.datakind = 'd';
strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from));
strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to));
return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE)));
}
/*-------------------------------------------------------------------
*
* Name: agwlib_D_send_connected_data
*
* Purpose: Send connected data to remote station.
*
* Inputs: chan - Radio channel number, first is 0.
*
* pid - Protocol ID. Normally 0xFo for Ax.25.
*
* call_from - My callsign.
*
* call_to - Callsign (or alias) of remote station.
*
* data_len - Number of bytes for Information part.
*
* data - Content for Information part.
*
* Returns: Number of bytes sent for success, -1 for error.
*
* Description: This should only be done when we are known to have
* an established link to other station.
*
*--------------------------------------------------------------------*/
int agwlib_D_send_connected_data (int chan, int pid, char *call_from, char *call_to, int data_len, char *data)
{
struct agw_cmd_s cmd;
memset (&cmd.hdr, 0, sizeof(cmd.hdr));
cmd.hdr.portx = chan;
cmd.hdr.datakind = 'D';
cmd.hdr.pid = pid; // Normally 0xF0 but other special cases are possible.
strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from));
strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to));
cmd.hdr.data_len_NETLE = host2netle(data_len);
// FIXME: DANGER possible buffer overflow, Need checking.
assert (data_len <= sizeof(cmd.data));
memcpy (cmd.data, data, data_len);
return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE)));
}
/*-------------------------------------------------------------------
*
* Name: agwlib_Y_outstanding_frames_for_station
*
* Purpose: Ask how many frames remain to be sent to station on other end of link.
*
* Inputs: chan - Radio channel number, first is 0.
*
* call_from - My call [ or is it Station which initiated the link? (sent SABM/SABME) ]
*
* call_to - Remote station call [ or is it Station which accepted the link? ]
*
* Returns: Number of bytes sent for success, -1 for error.
*
* Description: We expect to get a 'Y' frame response shortly.
*
* This would be useful for a couple different purposes.
*
* When sending bulk data, we want to keep a fair amount queued up to take
* advantage of large window sizes (MAXFRAME, EMAXFRAME). On the other
* hand we don't want to get TOO far ahead when transferring a large file.
*
* Before disconnecting from another station, it would be good to know
* that it actually recevied the last message we sent. For this reason,
* I think it would be good for this to include frames that were
* transmitted but not yet acknowleged. (Even if it was transmitted once,
* it could still be transmitted again, if lost, so you could say it is
* still waiting for transmission.)
*
* See server.c for a more precise definition of exacly how this is defined.
*
*--------------------------------------------------------------------*/
int agwlib_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to)
{
struct agw_cmd_s cmd;
memset (&cmd.hdr, 0, sizeof(cmd.hdr));
cmd.hdr.portx = chan;
cmd.hdr.datakind = 'Y';
strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from));
strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to));
return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE)));
}
/* end agwlib.c */

45
src/agwlib.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef AGWLIB_H
#define AGWLIB_H 1
// Call at beginning to start it up.
int agwlib_init (char *host, char *port, int (*init_func)(void));
// Send commands to TNC.
int agwlib_X_register_callsign (int chan, char *call_from);
int agwlib_x_unregister_callsign (int chan, char *call_from);
int agwlib_G_ask_port_information (void);
int agwlib_C_connect (int chan, char *call_from, char *call_to);
int agwlib_d_disconnect (int chan, char *call_from, char *call_to);
int agwlib_D_send_connected_data (int chan, int pid, char *call_from, char *call_to, int data_len, char *data);
int agwlib_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to);
// The application must define these.
void agw_cb_C_connection_received (int chan, char *call_from, char *call_to, int data_len, char *data);
void on_C_connection_received (int chan, char *call_from, char *call_to, int incoming, char *data);
void agw_cb_d_disconnected (int chan, char *call_from, char *call_to, int data_len, char *data);
void agw_cb_D_connected_data (int chan, char *call_from, char *call_to, int data_len, char *data);
void agw_cb_G_port_information (int num_chan, char *chan_descriptions[]);
void agw_cb_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to, int frame_count);
#endif

737
src/appserver.c Normal file
View File

@ -0,0 +1,737 @@
// ****** PRELIMINARY - needs work ******
#define DEBUG 1
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
#include "direwolf.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <getopt.h>
#include "ax25_pad.h"
#include "textcolor.h"
#include "agwlib.h" // Network TNC interface.
/*------------------------------------------------------------------
*
* Module: appserver.c
*
* Purpose: Simple application server for connected mode AX.25.
*
* This demonstrates how you can write a application that will wait for
* a connection from another station and respond to commands.
* It can be used as a starting point for developing your own applications.
*
* Description: This attaches to an instance of Dire Wolf via the AGW network interface.
* It processes commands from other radio stations and responds.
*
*---------------------------------------------------------------*/
static void usage()
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("Usage: \n");
dw_printf (" \n");
dw_printf ("appserver [ -h hostname ] [ -p port ] mycall \n");
dw_printf (" \n");
dw_printf (" -h hostname for TNC. Default is localhost. \n");
dw_printf (" \n");
dw_printf (" -p tcp port for TNC. Default is 8000. \n");
dw_printf (" \n");
dw_printf (" mycall is required because that is the callsign for \n");
dw_printf (" which the TNC will accept connections. \n");
dw_printf (" \n");
exit (EXIT_FAILURE);
}
static char mycall[AX25_MAX_ADDR_LEN]; /* Callsign, with SSID, for the application. */
/* Future? Could have multiple applications, on the same */
/* radio channel, each with its own SSID. */
static char tnc_hostname[80]; /* 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 tnc_port[8]; /* a TCP port number. Default 8000. */
/*
* Maintain information about connections from users which we will call "sessions."
* It should be possible to have multiple users connected at the same time.
*
* This allows a "who" command to see who is currently connected and a place to keep
* possible state information for each user.
*
* Each combination of channel & callsign is a separate session.
* The same user (callsign), on a different channel, is a different session.
*/
struct session_s {
char client_addr[AX25_MAX_ADDR_LEN]; // Callsign of other station.
// Clear to mean this table entry is not in use.
int channel; // Radio channel.
time_t login_time; // Time when connection established.
// For the timing test.
// Send specified number of frames, optional length.
// When finished summarize with statistics.
time_t tt_start_time;
volatile int tt_count; // Number to send.
int tt_length; // Bytes in info part.
int tt_next; // Next sequence to send.
volatile int tx_queue_len; // Number in transmit queue. For flow control.
};
#define MAX_SESSIONS 12
static struct session_s session[MAX_SESSIONS];
static int find_session (int chan, char *addr, int create);
static void poll_timing_test (void);
/*------------------------------------------------------------------
*
* Name: main
*
* Purpose: Attach to Dire Wolf TNC, wait for requests from users.
*
* Usage: Described above.
*
*---------------------------------------------------------------*/
int main (int argc, char *argv[])
{
int c;
char *p;
#if __WIN32__
setvbuf(stdout, NULL, _IONBF, 0);
#else
setlinebuf (stdout);
#endif
memset (session, 0, sizeof(session));
strlcpy (tnc_hostname, "localhost", sizeof(tnc_hostname));
strlcpy (tnc_port, "8000", sizeof(tnc_port));
/*
* Extract command line args.
*/
while ((c = getopt (argc, argv, "h:p:")) != -1) {
switch (c) {
case 'h':
strlcpy (tnc_hostname, optarg, sizeof(tnc_hostname));
break;
case 'p':
strlcpy (tnc_port, optarg, sizeof(tnc_port));
break;
default:
usage ();
}
}
if (argv[optind] == NULL) {
usage ();
}
strlcpy (mycall, argv[optind], sizeof(mycall));
// Force to upper case.
for (p = mycall; *p != '\0'; p++) {
if (islower(*p)) {
*p = toupper(*p);
}
}
/*
* Establish a TCP socket to the network TNC.
* It starts up a thread, which listens for messages from the TNC,
* and calls the corresponding agw_cb_... callback functions.
*
* After attaching to the TNC, the specified init function is called.
* We pass it to the library, rather than doing it here, so it can
* repeated automatically if the TNC goes away and comes back again.
* We need to reestablish what it knows about the application.
*/
if (agwlib_init (tnc_hostname, tnc_port, agwlib_G_ask_port_information) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not attach to network TNC %s:%s.\n", tnc_hostname, tnc_port);
exit (EXIT_FAILURE);
}
/*
* Send command to ask what channels are available.
* The response will be handled by agw_cb_G_port_information.
*/
// FIXME: Need to do this again if we lose TNC and reattach to it.
/// should happen automatically now. agwlib_G_ask_port_information ();
while (1) {
SLEEP_SEC(1); // other places based on 1 second assumption.
poll_timing_test ();
}
} /* end main */
static void poll_timing_test (void)
{
int s;
for (s = 0; s < MAX_SESSIONS; s++) {
if (session[s].tt_count == 0) {
continue; // nothing to do
}
else if (session[s].tt_next <= session[s].tt_count) {
int rem = session[s].tt_count - session[s].tt_next + 1; // remaining to send.
agwlib_Y_outstanding_frames_for_station (session[s].channel, mycall, session[s].client_addr);
SLEEP_MS(10);
if (session[s].tx_queue_len > 128) continue; // enough queued up for now.
if (rem > 64) rem = 64; // add no more than 64 at a time.
int i;
for (i = 0; i < rem; i++) {
char c = 'a';
char stuff[AX25_MAX_INFO_LEN+2];
snprintf (stuff, sizeof(stuff), "%06d ", session[s].tt_next);
int k;
for (k = strlen(stuff); k < session[s].tt_length - 1; k++) {
stuff[k] = c;
c++;
if (c == 'z' + 1) c = 'A';
if (c == 'Z' + 1) c = '0';
if (c == '9' + 1) c = 'a';
}
stuff[k++] = '\r';
stuff[k++] = '\0';
agwlib_D_send_connected_data (session[s].channel, 0xF0, mycall, session[s].client_addr, strlen(stuff), stuff);
session[s].tt_next++;
}
}
else {
// All done queuing up the packets.
// Wait until they have all been sent and ack'ed by other end.
agwlib_Y_outstanding_frames_for_station (session[s].channel, mycall, session[s].client_addr);
SLEEP_MS(10);
if (session[s].tx_queue_len > 0) continue; // not done yet.
int elapsed = time(NULL) - session[s].tt_start_time;
if (elapsed <= 0) elapsed = 1; // avoid divide by 0
int byte_count = session[s].tt_count * session[s].tt_length;
char summary[100];
snprintf (summary, sizeof(summary), "%d bytes in %d seconds, %d bytes/sec, efficiency %d%% at 1200, %d%% at 9600.\r",
byte_count, elapsed, byte_count/elapsed,
byte_count * 8 * 100 / elapsed / 1200,
byte_count * 8 * 100 / elapsed / 9600);
agwlib_D_send_connected_data (session[s].channel, 0xF0, mycall, session[s].client_addr, strlen(summary), summary);
session[s].tt_count = 0; // all done.
}
}
} // end poll_timing_test
/*-------------------------------------------------------------------
*
* Name: agw_cb_C_connection_received
*
* Purpose: Callback for the "connection received" command from the TNC.
*
* Inputs: chan - Radio channel, first is 0.
*
* call_from - Address of other station.
*
* call_to - Callsign I responded to. (could be an alias.)
*
* data_len - Length of data field.
*
* data - Should look something like this for incoming:
* *** CONNECTED to Station xxx\r
*
* Description: Add to the sessions table.
*
*--------------------------------------------------------------------*/
/*-------------------------------------------------------------------
*
* Name: on_C_connection_received
*
* Purpose: Callback for the "connection received" command from the TNC.
*
* Inputs: chan - Radio channel, first is 0.
*
* call_from - Address of other station.
*
* call_to - My call.
* In the case of an incoming connect request (i.e. to
* a server) this is the callsign I responded to.
* It is possible to define additional aliases and respond
* to any one of them. It would be possible to have a server
* that responds to multiple names and behaves differently
* depending on the name.
*
* incoming - true(1) if other station made connect request.
* false(0) if I made request and other statio accepted.
*
* data - Should look something like this for incoming:
* *** CONNECTED to Station xxx\r
* and ths for my request being accepted:
* *** CONNECTED With Station xxx\r
*
* session_id - Session id to be used in data transfer and
* other control functions related to this connection.
* Think of it like a file handle. Once it is open
* we usually don't care about the name anymore and
* and just refer to the handle. This is used to
* keep track of multiple connections at the same
* time. e.g. a server could be handling multiple
* clients at once on the same or different channels.
*
* Description: Add to the table of clients.
*
*--------------------------------------------------------------------*/
// old void agw_cb_C_connection_received (int chan, char *call_from, char *call_to, int data_len, char *data)
void on_C_connection_received (int chan, char *call_from, char *call_to, int incoming, char *data)
{
int s;
char *p;
char greeting[256];
for (p = data; *p != '\0'; p++) {
if (! isprint(*p)) *p = '\0'; // Remove any \r character at end.
}
s = find_session (chan, call_from, 1);
if (s >= 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Begin session %d: %s\n", s, data);
// Send greeting.
snprintf (greeting, sizeof(greeting), "Welcome! Type ? for list of commands or HELP <command> for details.\r");
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
}
else {
text_color_set(DW_COLOR_INFO);
dw_printf ("Too many users already: %s\n", data);
// Sorry, too many users already.
snprintf (greeting, sizeof(greeting), "Sorry, maximum number of users has been exceeded. Try again later.\r");
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
// FIXME: Ideally we'd want to wait until nothing in the outgoing queue
// to that station so we know the rejection message was received.
SLEEP_SEC (10);
agwlib_d_disconnect (chan, mycall, call_from);
}
} /* end agw_cb_C_connection_received */
/*-------------------------------------------------------------------
*
* Name: agw_cb_d_disconnected
*
* Purpose: Process the "disconnected" command from the TNC.
*
* Inputs: chan - Radio channel.
*
* call_from - Address of other station.
*
* call_to - Callsign I responded to. (could be aliases.)
*
* data_len - Length of data field.
*
* data - Should look something like one of these:
* *** DISCONNECTED RETRYOUT With xxx\r
* *** DISCONNECTED From Station xxx\r
*
* Description: Remove from the sessions table.
*
*--------------------------------------------------------------------*/
void agw_cb_d_disconnected (int chan, char *call_from, char *call_to, int data_len, char *data)
{
int s;
char *p;
s = find_session (chan, call_from, 0);
for (p = data; *p != '\0'; p++) {
if (! isprint(*p)) *p = '\0'; // Remove any \r character at end.
}
text_color_set(DW_COLOR_INFO);
dw_printf ("End session %d: %s\n", s, data);
// Remove from session table.
if (s >= 0) {
memset (&(session[s]), 0, sizeof(struct session_s));
}
} /* end agw_cb_d_disconnected */
/*-------------------------------------------------------------------
*
* Name: agw_cb_D_connected_data
*
* Purpose: Process "connected ax.25 data" from the TNC.
*
* Inputs: chan - Radio channel.
*
* addr - Address of other station.
*
* msg - What the user sent us. Probably a command.
*
* Global In: tnc_sock - Socket for TNC.
*
* Description: Remove from the session table.
*
*--------------------------------------------------------------------*/
void agw_cb_D_connected_data (int chan, char *call_from, char *call_to, int data_len, char *data)
{
int s;
char *p;
char logit[AX25_MAX_INFO_LEN+100];
char *pcmd;
char *save;
s = find_session (chan, call_from, 0);
for (p = data; *p != '\0'; p++) {
if (! isprint(*p)) *p = '\0'; // Remove any \r character at end.
}
// TODO: Should timestamp to all output.
snprintf (logit, sizeof(logit), "%d,%d,%s: %s\n", s, chan, call_from, data);
text_color_set(DW_COLOR_INFO);
dw_printf ("%s", logit);
if (s < 0) {
// Uh oh. Data from some station when not connected.
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error. Incoming data, no corresponding session.\n");
return;
}
// Process the command from user.
pcmd = strtok_r (data, " ", &save);
if (pcmd == NULL || strlen(pcmd) == 0) {
char greeting[80];
strlcpy (greeting, "Type ? for list of commands or HELP <command> for details.\r", sizeof(greeting));
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
return;
}
if (strcasecmp(pcmd, "who") == 0) {
// who - list people currently logged in.
int n;
char greeting[80];
snprintf (greeting, sizeof(greeting), "Session Channel User Since\r");
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
for (n = 0; n < MAX_SESSIONS; n++) {
if (session[n].client_addr[0]) {
snprintf (greeting, sizeof(greeting), " %2d %d %-9s [time later]\r",
n, session[n].channel, session[n].client_addr);
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
}
}
}
else if (strcasecmp(pcmd, "test") == 0) {
// test - timing test
// Send specified number of frames with optional length.
char *pcount = strtok_r (NULL, " ", &save);
char *plength = strtok_r (NULL, " ", &save);
session[s].tt_start_time = time(NULL);
session[s].tt_next = 1;
session[s].tt_length = 256;
session[s].tt_count = 1;
if (plength != NULL) {
session[s].tt_length = atoi(plength);
if (session[s].tt_length < 16) session[s].tt_length = 16;
if (session[s].tt_length > AX25_MAX_INFO_LEN) session[s].tt_length = AX25_MAX_INFO_LEN;
}
if (pcount != NULL) {
session[s].tt_count = atoi(pcount);
}
// The background polling will take it from here.
}
else if (strcasecmp(pcmd, "bye") == 0) {
// bye - disconnect.
char greeting[80];
strlcpy (greeting, "Thank you folks for kindly droppin' in. Y'all come on back now, ya hear?\r", sizeof(greeting));
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
// Ideally we'd want to wait until nothing in the outgoing queue
// to that station so we know the message was received.
SLEEP_SEC (10);
agwlib_d_disconnect (chan, mycall, call_from);
}
else if (strcasecmp(pcmd, "help") == 0 || strcasecmp(pcmd, "?") == 0) {
// help.
char greeting[80];
strlcpy (greeting, "Help not yet available.\r", sizeof(greeting));
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
}
else {
// command not recognized.
char greeting[80];
strlcpy (greeting, "Invalid command. Type ? for list of commands or HELP <command> for details.\r", sizeof(greeting));
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
}
} /* end agw_cb_D_connected_data */
/*-------------------------------------------------------------------
*
* Name: agw_cb_G_port_information
*
* Purpose: Process the port information "radio channels available" response from the TNC.
*
*
* Inputs: num_chan_avail - Number of radio channels available.
*
* chan_descriptions - Array of string pointers to form "Port99 description".
* Port1 is channel 0.
*
*--------------------------------------------------------------------*/
void agw_cb_G_port_information (int num_chan_avail, char *chan_descriptions[])
{
char *p;
int n;
text_color_set(DW_COLOR_INFO);
dw_printf("TNC has %d radio channel%s available:\n", num_chan_avail, (num_chan_avail != 1) ? "s" : "");
for (n = 0; n < num_chan_avail; n++) {
p = chan_descriptions[n];
// Expecting something like this: "Port1 first soundcard mono"
if (strncasecmp(p, "Port", 4) == 0 && isdigit(p[4])) {
int chan = atoi(p+4) - 1; // "Port1" is our channel 0.
if (chan >= 0 && chan < MAX_CHANS) {
char *desc = p + 4;
while (*desc != '\0' && (*desc == ' ' || isdigit(*desc))) {
desc++;
}
text_color_set(DW_COLOR_INFO);
dw_printf(" Channel %d: %s\n", chan, desc);
// Later? Use 'g' to get speed and maybe other properties?
// Though I'm not sure why we would care here.
/*
* Send command to register my callsign for incoming connect requests.
*/
agwlib_X_register_callsign (chan, mycall);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("Radio channel number is out of bounds: %s\n", p);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("Radio channel description not in expected format: %s\n", p);
}
}
} /* end agw_cb_G_port_information */
/*-------------------------------------------------------------------
*
* Name: agw_cb_Y_outstanding_frames_for_station
*
* Purpose: Process the "disconnected" command from the TNC.
*
* Inputs: chan - Radio channel.
*
* call_from - Should be my call.
*
* call_to - Callsign of other station.
*
* frame_count
*
* Description: Remove from the sessions table.
*
*--------------------------------------------------------------------*/
void agw_cb_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to, int frame_count)
{
int s;
s = find_session (chan, call_to, 0);
text_color_set(DW_COLOR_DEBUG); // FIXME temporary
dw_printf ("debug ----------------------> session %d, callback Y outstanding frame_count %d\n", s, frame_count);
// Update the transmit queue length
if (s >= 0) {
session[s].tx_queue_len = frame_count;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Oops! Did not expect to be here.\n");
}
} /* end agw_cb_Y_outstanding_frames_for_station */
/*-------------------------------------------------------------------
*
* Name: find_session
*
* Purpose: Given a channel number and address (callsign), find existing
* table entry or create a new one.
*
* Inputs: chan - Radio channel number.
*
* addr - Address of station contacting us.
*
* create - If true, try create a new entry if not already there.
*
* Returns: "session id" which is an index into "session" array or -1 for failure.
*
*--------------------------------------------------------------------*/
static int find_session (int chan, char *addr, int create)
{
int i;
int s = -1;
// Is it there already?
//#if DEBUG
//
// text_color_set(DW_COLOR_DEBUG);
// dw_printf("find_session (%d, %s, %d)\n", chan, addr, create);
//#endif
for (i = 0; i < MAX_SESSIONS; i++) {
if (session[i].channel == chan && strcmp(session[i].client_addr, addr) == 0) {
s = i;
break;
}
}
if (s >= 0) return (s);
if (! create) return (-1);
// No, and there is a request to add a new entry.
// See if we have any available space.
s = -1;
for (i = 0; i < MAX_SESSIONS; i++) {
if (strlen(session[i].client_addr) == 0) {
s = i;
break;
}
}
if (s < 0) return (-1);
strlcpy (session[s].client_addr, addr, sizeof(session[s].client_addr));
session[s].channel = chan;
session[s].login_time = time(NULL);
return (s);
} /* end find_session */
/* end appserver.c */

View File

@ -1708,7 +1708,7 @@ static void raw_tt_data_to_app (int chan, char *msg)
alevel.mark = -2;
alevel.space = -2;
dlq_rec_frame (chan, -1, 0, pp, alevel, RETRY_NONE, "tt");
dlq_rec_frame (chan, -1, 0, pp, alevel, 0, RETRY_NONE, "tt");
}
else {
text_color_set(DW_COLOR_ERROR);

View File

@ -81,6 +81,7 @@
#include "dlq.h"
#include "ptt.h"
#include "dtime_now.h"
#include "fx25.h"
@ -186,6 +187,7 @@ static int j_opt = 0; /* 2400 bps PSK compatible with direwolf <= 1.5 */
static int J_opt = 0; /* 2400 bps PSK compatible MFJ-2400 and maybe others. */
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.
int main (int argc, char *argv[])
@ -259,7 +261,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "B:P:D:U:gjJF:L:G:012h",
c = getopt_long(argc, argv, "B:P:D:U:gjJF:L:G:012he:d:",
long_options, &option_index);
if (c == -1)
break;
@ -364,6 +366,21 @@ int main (int argc, char *argv[])
h_opt = 1;
break;
case 'e': /* Receive Bit Error Rate (BER). */
my_audio_config.recv_ber = atof(optarg);
break;
case 'd': /* Debug message options. */
for (char *p=optarg; *p!='\0'; p++) {
switch (*p) {
case 'x': d_x_opt++; break; // FX.25
default: break;
}
}
break;
case '?':
/* Unknown option message was already printed. */
@ -491,6 +508,7 @@ int main (int argc, char *argv[])
usage ();
}
fx25_init (d_x_opt);
start_time = dtime_now();
@ -589,6 +607,7 @@ int main (int argc, char *argv[])
/*
* Initialize the AFSK demodulator and HDLC decoder.
* Needs to be done for each file because they could have different sample rates.
*/
multi_modem_init (&my_audio_config);
packets_decoded_one = 0;
@ -701,7 +720,7 @@ int audio_get (int a)
* This is called when we have a good frame.
*/
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum)
{
char stemp[500];
@ -755,7 +774,11 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) {
dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum);
}
else if (is_fx25) {
dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum);
}
else {
assert (retries >= RETRY_NONE && retries <= RETRY_MAX);
dw_printf ("%s audio level = %s [%s] %s\n", heard, alevel_text, retry_text[(int)retries], spectrum);
}
@ -881,6 +904,8 @@ static void usage (void) {
dw_printf (" 1 = Try to fix only a single bit. \n");
dw_printf (" more = Try modifying more bits to get a good CRC.\n");
dw_printf ("\n");
dw_printf (" -d x Debug information for FX.25. Repeat for more detail.\n");
dw_printf ("\n");
dw_printf (" -L Error if less than this number decoded.\n");
dw_printf ("\n");
dw_printf (" -G Error if greater than this number decoded.\n");

View File

@ -104,6 +104,14 @@ struct audio_s {
int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */
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. */
/* 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. */
char timestamp_format[40]; /* -T option */
/* Precede received & transmitted frames with timestamp. */
/* Command line option uses "strftime" format string. */

View File

@ -1041,7 +1041,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
/* Simulated reception from radio. */
memset (&alevel, 0xff, sizeof(alevel));
dlq_rec_frame (bp->sendto_chan, 0, 0, pp, alevel, 0, "");
dlq_rec_frame (bp->sendto_chan, 0, 0, pp, alevel, 0, 0, "");
break;
}
}

View File

@ -32,6 +32,7 @@
* APRStt (touch tone input) gateway
* Internet Gateway (IGate)
* Ham Radio of Things - IoT with Ham Radio
* FX.25 Forward Error Correction.
*
*
*---------------------------------------------------------------*/
@ -121,6 +122,7 @@
#include "mheard.h"
#include "ax25_link.h"
#include "dtime_now.h"
#include "fx25.h"
//static int idx_decoded = 0;
@ -181,7 +183,6 @@ static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio
static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */
int main (int argc, char *argv[])
{
int err;
@ -217,9 +218,14 @@ int main (int argc, char *argv[])
#if USE_HAMLIB
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 E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit frames. */
int E_rx_opt = 0; /* "-E Rn" Error rate % for clobbering receive frames. */
float e_recv_ber = 0.0; /* Receive Bit Error Rate (BER). */
int X_fx25_xmit_enable = 0; /* FX.25 transmit enable. */
strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir));
strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile));
strlcpy(P_opt, "", sizeof(P_opt));
@ -278,7 +284,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, "C", __DATE__);
dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "D", __DATE__);
//dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
@ -367,7 +373,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "P:B:gjJD:U:c:pxr:b:n:d:q:t:ul:L:Sa:E:T:",
c = getopt_long(argc, argv, "P:B:gjJD:U:c:pxr:b:n:d:q:t:ul:L:Sa:E:T:e:X:",
long_options, &option_index);
if (c == -1)
break;
@ -535,6 +541,7 @@ int main (int argc, char *argv[])
#if USE_HAMLIB
case 'h': d_h_opt++; break; // Hamlib verbose level.
#endif
case 'x': d_x_opt++; break; // FX.25
default: break;
}
}
@ -549,6 +556,7 @@ int main (int argc, char *argv[])
switch (*p) {
case 'h': q_h_opt = 1; break;
case 'd': q_d_opt = 1; break;
case 'x': d_x_opt = 0; break; // Defaults to minimal info. This silences.
default: break;
}
}
@ -611,6 +619,16 @@ int main (int argc, char *argv[])
strlcpy (T_opt_timestamp, optarg, sizeof(T_opt_timestamp));
break;
case 'e': /* -e Receive Bit Error Rate (BER). */
e_recv_ber = atof(optarg);
break;
case 'X':
X_fx25_xmit_enable = atoi(optarg);
break;
default:
/* Should not be here. */
@ -788,6 +806,10 @@ int main (int argc, char *argv[])
}
audio_config.recv_ber = e_recv_ber;
audio_config.fx25_xmit_enable = X_fx25_xmit_enable;
/*
* Open the audio source
@ -810,6 +832,7 @@ int main (int argc, char *argv[])
* Initialize the demodulator(s) and HDLC decoder.
*/
multi_modem_init (&audio_config);
fx25_init (d_x_opt);
/*
* Initialize the touch tone decoder & APRStt gateway.
@ -940,7 +963,7 @@ int main (int argc, char *argv[])
// TODO: Use only one printf per line so output doesn't get jumbled up with stuff from other threads.
void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum)
{
char stemp[500];
@ -957,7 +980,12 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
assert (pp != NULL); // 1.1J+
strlcpy (display_retries, "", sizeof(display_retries));
if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) {
if (is_fx25) {
;
}
else if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) {
assert (retries >= RETRY_NONE && retries <= RETRY_MAX);
snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]);
}
@ -1343,6 +1371,7 @@ static void usage (char **argv)
dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n");
dw_printf (" -P xxx Modem Profiles.\n");
dw_printf (" -D n Divide audio sample rate by n for channel 0.\n");
dw_printf (" -X n Enable FX.25 transmit. Specify number of check bytes: 16, 32, or 64.\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");
@ -1359,9 +1388,11 @@ static void usage (char **argv)
#if USE_HAMLIB
dw_printf (" h h = hamlib increase verbose level.\n");
#endif
dw_printf (" x x = FX.25 increase verbose level.\n");
dw_printf (" -q Quiet (suppress output) options:\n");
dw_printf (" h h = Heard line with the audio level.\n");
dw_printf (" d d = Decoding of APRS packets.\n");
dw_printf (" x x = Silence FX.25 information.\n");
dw_printf (" -t n Text colors. 0=disabled. 1=default. 2,3,4,... alternatives.\n");
dw_printf (" Use 9 to test compatibility with your terminal.\n");
dw_printf (" -a n Audio statistics interval in seconds. 0 to disable.\n");
@ -1373,6 +1404,7 @@ static void usage (char **argv)
dw_printf (" -u Print UTF-8 test string and exit.\n");
dw_printf (" -S Print symbol tables and exit.\n");
dw_printf (" -T fmt Time stamp format for sent and received frames.\n");
dw_printf (" -e ber Receive Bit Error Rate (BER), e.g. 1e-5\n");
dw_printf ("\n");
dw_printf ("After any options, there can be a single command line argument for the source of\n");

View File

@ -209,6 +209,9 @@ void dlq_init (void)
* display of audio level line.
* Use -2 to indicate DTMF message.)
*
* is_fx25 - Was it from FX.25? Need to know because
* meaning of retries is different.
*
* retries - Level of bit correction used.
*
* spectrum - Display of how well multiple decoders did.
@ -219,7 +222,7 @@ void dlq_init (void)
*
*--------------------------------------------------------------------*/
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum)
{
struct dlq_item_s *pnew;
@ -264,6 +267,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
pnew->subchan = subchan;
pnew->pp = pp;
pnew->alevel = alevel;
pnew->is_fx25 = is_fx25;
pnew->retries = retries;
if (spectrum == NULL)
strlcpy(pnew->spectrum, "", sizeof(pnew->spectrum));

View File

@ -68,7 +68,11 @@ typedef struct dlq_item_s {
alevel_t alevel; /* Audio level. */
int is_fx25; /* Was it from FX.25? */
retry_t retries; /* Effort expended to get a valid CRC. */
/* Bits changed for regular AX.25. */
/* Number of bytes fixed for FX.25. */
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */
@ -102,7 +106,7 @@ void dlq_init (void);
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum);
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum);
void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid);

96
src/fx25.h Normal file
View File

@ -0,0 +1,96 @@
#ifndef FX25_H
#define FX25_H
#include <stdint.h> // for uint64_t
/* Reed-Solomon codec control block */
struct rs {
unsigned int mm; /* Bits per symbol */
unsigned int nn; /* Symbols per block (= (1<<mm)-1) */
unsigned char *alpha_to; /* log lookup table */
unsigned char *index_of; /* Antilog lookup table */
unsigned char *genpoly; /* Generator polynomial */
unsigned int nroots; /* Number of generator roots = number of parity symbols */
unsigned char fcr; /* First consecutive root, index form */
unsigned char prim; /* Primitive element, index form */
unsigned char iprim; /* prim-th root of 1, index form */
};
#define MM (rs->mm)
#define NN (rs->nn)
#define ALPHA_TO (rs->alpha_to)
#define INDEX_OF (rs->index_of)
#define GENPOLY (rs->genpoly)
#define NROOTS (rs->nroots)
#define FCR (rs->fcr)
#define PRIM (rs->prim)
#define IPRIM (rs->iprim)
#define A0 (NN)
__attribute__((always_inline))
static inline int modnn(struct rs *rs, int x){
while (x >= rs->nn) {
x -= rs->nn;
x = (x >> rs->mm) + (x & rs->nn);
}
return x;
}
#define MODNN(x) modnn(rs,x)
#define ENCODE_RS encode_rs_char
#define DECODE_RS decode_rs_char
#define INIT_RS init_rs_char
#define FREE_RS free_rs_char
#define DTYPE unsigned char
void ENCODE_RS(struct rs *rs, DTYPE *data, DTYPE *bb);
int DECODE_RS(struct rs *rs, DTYPE *data, int *eras_pos, int no_eras);
struct rs *INIT_RS(unsigned int symsize, unsigned int gfpoly,
unsigned int fcr, unsigned int prim, unsigned int nroots);
void FREE_RS(struct rs *rs);
// These 3 are the external interface.
// Maybe these should be in a different file, separated from the internal stuff.
void fx25_init ( int debug_level );
int fx25_send_frame (int chan, unsigned char *fbuf, int flen, int fx_mode);
void fx25_rec_bit (int chan, int subchan, int slice, int dbit);
int fx25_rec_busy (int chan);
// Other functions in fx25_init.c.
struct rs *fx25_get_rs (int ctag_num);
uint64_t fx25_get_ctag_value (int ctag_num);
int fx25_get_k_data_radio (int ctag_num);
int fx25_get_k_data_rs (int ctag_num);
int fx25_get_nroots (int ctag_num);
int fx25_get_debug (void);
int fx25_tag_find_match (uint64_t t);
int fx25_pick_mode (int fx_mode, int dlen);
void fx_hex_dump(unsigned char *x, int len);
#define CTAG_MIN 0x01
#define CTAG_MAX 0x0B
// Maximum sizes of "data" and "check" parts.
#define FX25_MAX_DATA 239 // i.e. RS(255,239)
#define FX25_MAX_CHECK 64 // e.g. RS(255, 191)
#define FX25_BLOCK_SIZE 255 // Block size always 255 for 8 bit symbols.
#endif // FX25_H

84
src/fx25_encode.c Normal file
View File

@ -0,0 +1,84 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------
//
//
// Most of this is based on:
//
// FX.25 Encoder
// Author: Jim McGuire KB3MPL
// Date: 23 October 2007
//
// This program is a single-file implementation of the FX.25 encapsulation
// structure for use with AX.25 data packets. Details of the FX.25
// specification are available at:
// http://www.stensat.org/Docs/Docs.htm
//
// This program implements a single RS(255,239) FEC structure. Future
// releases will incorporate more capabilities as accommodated in the FX.25
// spec.
//
// The Reed Solomon encoding routines are based on work performed by
// Phil Karn. Phil was kind enough to release his code under the GPL, as
// noted below. Consequently, this FX.25 implementation is also released
// under the terms of the GPL.
//
// Phil Karn's original copyright notice:
/* Test the Reed-Solomon codecs
* for various block sizes and with random data and random error patterns
*
* Copyright 2002 Phil Karn, KA9Q
* May be used under the terms of the GNU General Public License (GPL)
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "fx25.h"
void ENCODE_RS(struct rs * restrict rs, DTYPE * restrict data, DTYPE * restrict bb)
{
int i, j;
DTYPE feedback;
memset(bb,0,NROOTS*sizeof(DTYPE)); // clear out the FEC data area
for(i=0;i<NN-NROOTS;i++){
feedback = INDEX_OF[data[i] ^ bb[0]];
if(feedback != A0){ /* feedback term is non-zero */
for(j=1;j<NROOTS;j++)
bb[j] ^= ALPHA_TO[MODNN(feedback + GENPOLY[NROOTS-j])];
}
/* Shift */
memmove(&bb[0],&bb[1],sizeof(DTYPE)*(NROOTS-1));
if(feedback != A0)
bb[NROOTS-1] = ALPHA_TO[MODNN(feedback + GENPOLY[0])];
else
bb[NROOTS-1] = 0;
}
}
// end fx25_encode.c

326
src/fx25_extract.c Normal file
View File

@ -0,0 +1,326 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------
//
// This is based on:
//
//
// FX25_extract.c
// Author: Jim McGuire KB3MPL
// Date: 23 October 2007
//
//
// Accepts an FX.25 byte stream on STDIN, finds the correlation tag, stores 256 bytes,
// corrects errors with FEC, removes the bit-stuffing, and outputs the resultant AX.25
// byte stream out STDOUT.
//
// stdout prints a bunch of status information about the packet being processed.
//
//
// Usage : FX25_extract < infile > outfile [2> logfile]
//
//
//
// This program is a single-file implementation of the FX.25 extraction/decode
// structure for use with FX.25 data frames. Details of the FX.25
// specification are available at:
// http://www.stensat.org/Docs/Docs.htm
//
// This program implements a single RS(255,239) FEC structure. Future
// releases will incorporate more capabilities as accommodated in the FX.25
// spec.
//
// The Reed Solomon encoding routines are based on work performed by
// Phil Karn. Phil was kind enough to release his code under the GPL, as
// noted below. Consequently, this FX.25 implementation is also released
// under the terms of the GPL.
//
// Phil Karn's original copyright notice:
/* Test the Reed-Solomon codecs
* for various block sizes and with random data and random error patterns
*
* Copyright 2002 Phil Karn, KA9Q
* May be used under the terms of the GNU General Public License (GPL)
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fx25.h"
//#define DEBUG 5
//-----------------------------------------------------------------------
// Revision History
//-----------------------------------------------------------------------
// 0.0.1 - initial release
// Modifications from Phil Karn's GPL source code.
// Initially added code to run with PC file
// I/O and use the (255,239) decoder exclusively. Confirmed that the
// code produces the correct results.
//
//-----------------------------------------------------------------------
// 0.0.2 -
#define min(a,b) ((a) < (b) ? (a) : (b))
int DECODE_RS(struct rs * restrict rs, DTYPE * restrict data, int *eras_pos, int no_eras) {
int deg_lambda, el, deg_omega;
int i, j, r,k;
DTYPE u,q,tmp,num1,num2,den,discr_r;
// DTYPE lambda[NROOTS+1], s[NROOTS]; /* Err+Eras Locator poly and syndrome poly */
// DTYPE b[NROOTS+1], t[NROOTS+1], omega[NROOTS+1];
// DTYPE root[NROOTS], reg[NROOTS+1], loc[NROOTS];
DTYPE lambda[FX25_MAX_CHECK+1], s[FX25_MAX_CHECK]; /* Err+Eras Locator poly and syndrome poly */
DTYPE b[FX25_MAX_CHECK+1], t[FX25_MAX_CHECK+1], omega[FX25_MAX_CHECK+1];
DTYPE root[FX25_MAX_CHECK], reg[FX25_MAX_CHECK+1], loc[FX25_MAX_CHECK];
int syn_error, count;
/* form the syndromes; i.e., evaluate data(x) at roots of g(x) */
for(i=0;i<NROOTS;i++)
s[i] = data[0];
for(j=1;j<NN;j++){
for(i=0;i<NROOTS;i++){
if(s[i] == 0){
s[i] = data[j];
} else {
s[i] = data[j] ^ ALPHA_TO[MODNN(INDEX_OF[s[i]] + (FCR+i)*PRIM)];
}
}
}
/* Convert syndromes to index form, checking for nonzero condition */
syn_error = 0;
for(i=0;i<NROOTS;i++){
syn_error |= s[i];
s[i] = INDEX_OF[s[i]];
}
// fprintf(stderr,"syn_error = %4x\n",syn_error);
if (!syn_error) {
/* if syndrome is zero, data[] is a codeword and there are no
* errors to correct. So return data[] unmodified
*/
count = 0;
goto finish;
}
memset(&lambda[1],0,NROOTS*sizeof(lambda[0]));
lambda[0] = 1;
if (no_eras > 0) {
/* Init lambda to be the erasure locator polynomial */
lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))];
for (i = 1; i < no_eras; i++) {
u = MODNN(PRIM*(NN-1-eras_pos[i]));
for (j = i+1; j > 0; j--) {
tmp = INDEX_OF[lambda[j - 1]];
if(tmp != A0)
lambda[j] ^= ALPHA_TO[MODNN(u + tmp)];
}
}
#if DEBUG >= 1
/* Test code that verifies the erasure locator polynomial just constructed
Needed only for decoder debugging. */
/* find roots of the erasure location polynomial */
for(i=1;i<=no_eras;i++)
reg[i] = INDEX_OF[lambda[i]];
count = 0;
for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) {
q = 1;
for (j = 1; j <= no_eras; j++)
if (reg[j] != A0) {
reg[j] = MODNN(reg[j] + j);
q ^= ALPHA_TO[reg[j]];
}
if (q != 0)
continue;
/* store root and error location number indices */
root[count] = i;
loc[count] = k;
count++;
}
if (count != no_eras) {
fprintf(stderr,"count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras);
count = -1;
goto finish;
}
#if DEBUG >= 2
fprintf(stderr,"\n Erasure positions as determined by roots of Eras Loc Poly:\n");
for (i = 0; i < count; i++)
fprintf(stderr,"%d ", loc[i]);
fprintf(stderr,"\n");
#endif
#endif
}
for(i=0;i<NROOTS+1;i++)
b[i] = INDEX_OF[lambda[i]];
/*
* Begin Berlekamp-Massey algorithm to determine error+erasure
* locator polynomial
*/
r = no_eras;
el = no_eras;
while (++r <= NROOTS) { /* r is the step number */
/* Compute discrepancy at the r-th step in poly-form */
discr_r = 0;
for (i = 0; i < r; i++){
if ((lambda[i] != 0) && (s[r-i-1] != A0)) {
discr_r ^= ALPHA_TO[MODNN(INDEX_OF[lambda[i]] + s[r-i-1])];
}
}
discr_r = INDEX_OF[discr_r]; /* Index form */
if (discr_r == A0) {
/* 2 lines below: B(x) <-- x*B(x) */
memmove(&b[1],b,NROOTS*sizeof(b[0]));
b[0] = A0;
} else {
/* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
t[0] = lambda[0];
for (i = 0 ; i < NROOTS; i++) {
if(b[i] != A0)
t[i+1] = lambda[i+1] ^ ALPHA_TO[MODNN(discr_r + b[i])];
else
t[i+1] = lambda[i+1];
}
if (2 * el <= r + no_eras - 1) {
el = r + no_eras - el;
/*
* 2 lines below: B(x) <-- inv(discr_r) *
* lambda(x)
*/
for (i = 0; i <= NROOTS; i++)
b[i] = (lambda[i] == 0) ? A0 : MODNN(INDEX_OF[lambda[i]] - discr_r + NN);
} else {
/* 2 lines below: B(x) <-- x*B(x) */
memmove(&b[1],b,NROOTS*sizeof(b[0]));
b[0] = A0;
}
memcpy(lambda,t,(NROOTS+1)*sizeof(t[0]));
}
}
/* Convert lambda to index form and compute deg(lambda(x)) */
deg_lambda = 0;
for(i=0;i<NROOTS+1;i++){
lambda[i] = INDEX_OF[lambda[i]];
if(lambda[i] != A0)
deg_lambda = i;
}
/* Find roots of the error+erasure locator polynomial by Chien search */
memcpy(&reg[1],&lambda[1],NROOTS*sizeof(reg[0]));
count = 0; /* Number of roots of lambda(x) */
for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) {
q = 1; /* lambda[0] is always 0 */
for (j = deg_lambda; j > 0; j--){
if (reg[j] != A0) {
reg[j] = MODNN(reg[j] + j);
q ^= ALPHA_TO[reg[j]];
}
}
if (q != 0)
continue; /* Not a root */
/* store root (index-form) and error location number */
#if DEBUG>=2
fprintf(stderr,"count %d root %d loc %d\n",count,i,k);
#endif
root[count] = i;
loc[count] = k;
/* If we've already found max possible roots,
* abort the search to save time
*/
if(++count == deg_lambda)
break;
}
if (deg_lambda != count) {
/*
* deg(lambda) unequal to number of roots => uncorrectable
* error detected
*/
count = -1;
goto finish;
}
/*
* Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
* x**NROOTS). in index form. Also find deg(omega).
*/
deg_omega = 0;
for (i = 0; i < NROOTS;i++){
tmp = 0;
j = (deg_lambda < i) ? deg_lambda : i;
for(;j >= 0; j--){
if ((s[i - j] != A0) && (lambda[j] != A0))
tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])];
}
if(tmp != 0)
deg_omega = i;
omega[i] = INDEX_OF[tmp];
}
omega[NROOTS] = A0;
/*
* Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
* inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form
*/
for (j = count-1; j >=0; j--) {
num1 = 0;
for (i = deg_omega; i >= 0; i--) {
if (omega[i] != A0)
num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])];
}
num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)];
den = 0;
/* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
for (i = min(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) {
if(lambda[i+1] != A0)
den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])];
}
if (den == 0) {
#if DEBUG >= 1
fprintf(stderr,"\n ERROR: denominator = 0\n");
#endif
count = -1;
goto finish;
}
/* Apply error to data */
if (num1 != 0) {
data[loc[j]] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])];
}
}
finish:
if(eras_pos != NULL){
for(i=0;i<count;i++)
eras_pos[i] = loc[i];
}
return count;
}
// end fx25_extract.c

484
src/fx25_init.c Normal file
View File

@ -0,0 +1,484 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------
//
//
// Some of this is based on:
//
// FX.25 Encoder
// Author: Jim McGuire KB3MPL
// Date: 23 October 2007
//
// This program is a single-file implementation of the FX.25 encapsulation
// structure for use with AX.25 data packets. Details of the FX.25
// specification are available at:
// http://www.stensat.org/Docs/Docs.htm
//
// This program implements a single RS(255,239) FEC structure. Future
// releases will incorporate more capabilities as accommodated in the FX.25
// spec.
//
// The Reed Solomon encoding routines are based on work performed by
// Phil Karn. Phil was kind enough to release his code under the GPL, as
// noted below. Consequently, this FX.25 implementation is also released
// under the terms of the GPL.
//
// Phil Karn's original copyright notice:
/* Test the Reed-Solomon codecs
* for various block sizes and with random data and random error patterns
*
* Copyright 2002 Phil Karn, KA9Q
* May be used under the terms of the GNU General Public License (GPL)
*
*/
#include "direwolf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h> // uint64_t
#include <inttypes.h> // PRIx64
#include <assert.h>
#include "fx25.h"
#include "textcolor.h"
#define NTAB 3
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.
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, 1, 1, 16, NULL }, // RS(255,239)
{8, 0x11d, 1, 1, 32, NULL }, // RS(255,223)
{8, 0x11d, 1, 1, 64, NULL }, // RS(255,191)
};
/*
* Reference: http://www.stensat.org/docs/FX-25_01_06.pdf
* FX.25
* Forward Error Correction Extension to
* AX.25 Link Protocol For Amateur Packet Radio
* Version: 0.01 DRAFT
* Date: 01 September 2006
*/
struct correlation_tag_s {
uint64_t value; // 64 bit value, send LSB first.
int n_block_radio; // Size of transmitted block, all in bytes.
int k_data_radio; // Size of transmitted data part.
int n_block_rs; // Size of RS algorithm block.
int k_data_rs; // Size of RS algorithm data part.
int itab; // Index into Tab array.
};
static const struct correlation_tag_s tags[16] = {
/* Tag_00 */ { 0x566ED2717946107ELL, 0, 0, 0, 0, -1 }, // Reserved
/* Tag_01 */ { 0xB74DB7DF8A532F3ELL, 255, 239, 255, 239, 0 }, // RS(255, 239) 16-byte check value, 239 information bytes
/* Tag_02 */ { 0x26FF60A600CC8FDELL, 144, 128, 255, 239, 0 }, // RS(144,128) - shortened RS(255, 239), 128 info bytes
/* Tag_03 */ { 0xC7DC0508F3D9B09ELL, 80, 64, 255, 239, 0 }, // RS(80,64) - shortened RS(255, 239), 64 info bytes
/* Tag_04 */ { 0x8F056EB4369660EELL, 48, 32, 255, 239, 0 }, // RS(48,32) - shortened RS(255, 239), 32 info bytes
/* Tag_05 */ { 0x6E260B1AC5835FAELL, 255, 223, 255, 223, 1 }, // RS(255, 223) 32-byte check value, 223 information bytes
/* Tag_06 */ { 0xFF94DC634F1CFF4ELL, 160, 128, 255, 223, 1 }, // RS(160,128) - shortened RS(255, 223), 128 info bytes
/* Tag_07 */ { 0x1EB7B9CDBC09C00ELL, 96, 64, 255, 223, 1 }, // RS(96,64) - shortened RS(255, 223), 64 info bytes
/* Tag_08 */ { 0xDBF869BD2DBB1776LL, 64, 32, 255, 223, 1 }, // RS(64,32) - shortened RS(255, 223), 32 info bytes
/* Tag_09 */ { 0x3ADB0C13DEAE2836LL, 255, 191, 255, 191, 2 }, // RS(255, 191) 64-byte check value, 191 information bytes
/* Tag_0A */ { 0xAB69DB6A543188D6LL, 192, 128, 255, 191, 2 }, // RS(192, 128) - shortened RS(255, 191), 128 info bytes
/* Tag_0B */ { 0x4A4ABEC4A724B796LL, 128, 64, 255, 191, 2 }, // RS(128, 64) - shortened RS(255, 191), 64 info bytes
/* Tag_0C */ { 0x0293D578626B67E6LL, 0, 0, 0, 0, -1 }, // Undefined
/* Tag_0D */ { 0xE3B0B0D6917E58A6LL, 0, 0, 0, 0, -1 }, // Undefined
/* Tag_0E */ { 0x720267AF1BE1F846LL, 0, 0, 0, 0, -1 }, // Undefined
/* Tag_0F */ { 0x93210201E8F4C706LL, 0, 0, 0, 0, -1 } // Undefined
};
#define CLOSE_ENOUGH 8 // How many bits can be wrong in tag yet consider it a match?
// Needs to be large enough to match with significant errors
// but not so large to get frequent false matches.
// Probably don't want >= 16 because the hamming distance between
// any two pairs is 32.
// What is a good number? 8?? 12?? 15??
// 12 got many false matches with random noise.
// Even 8 might be too high. We see 2 or 4 bit errors here
// at the point where decoding the block is very improbable.
// Given a 64 bit correlation tag value, find acceptable match in table.
// Return index into table or -1 for no match.
// Both gcc and clang have a built in function to count the number of '1' bits
// in an integer. This can result in a single machine instruction. You might need
// to supply your own popcount function if using a different compiler.
int fx25_tag_find_match (uint64_t t)
{
for (int c = CTAG_MIN; c <= CTAG_MAX; c++) {
if (__builtin_popcountll(t ^ tags[c].value) <= CLOSE_ENOUGH) {
//printf ("%016" PRIx64 " received\n", t);
//printf ("%016" PRIx64 " tag %d\n", tags[c].value, c);
//printf ("%016" PRIx64 " xor, popcount = %d\n", t ^ tags[c].value, __builtin_popcountll(t ^ tags[c].value));
return (c);
}
}
return (-1);
}
void free_rs_char(struct rs *rs){
free(rs->alpha_to);
free(rs->index_of);
free(rs->genpoly);
free(rs);
}
/*-------------------------------------------------------------
*
* Name: fx25_init
*
* Purpose: This must be called once before any of the other fx25 functions.
*
* Inputs: debug_level - Controls level of informational / debug messages.
*
* 0 Only errors.
* 1 (default) Transmitting ctag. Currently no other way to know this.
* 2 Receive correlation tag detected. FEC decode complete.
* 3 Dump data going in and out.
*
* Use command line -dx to increase level or -qx for quiet.
*
* Description: Initialize 3 Reed-Solomon codecs, for 16, 32, and 64 check bytes.
*
*--------------------------------------------------------------*/
static int g_debug_level;
void fx25_init ( int debug_level )
{
g_debug_level = debug_level;
for (int i = 0 ; i < NTAB ; i++) {
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("FX.25 internal error: init_rs_char failed!\n");
exit(EXIT_FAILURE);
}
}
// Verify integrity of tables and assumptions.
// This also does a quick check for the popcount function.
for (int j = 0; j < 16 ; j++) {
for (int k = 0; k < 16; k++) {
if (j == k) {
assert (__builtin_popcountll(tags[j].value ^ tags[k].value) == 0);
}
else {
assert (__builtin_popcountll(tags[j].value ^ tags[k].value) == 32);
}
}
}
for (int j = CTAG_MIN; j <= CTAG_MAX; j++) {
assert (tags[j].n_block_radio - tags[j].k_data_radio == Tab[tags[j].itab].nroots);
assert (tags[j].n_block_rs - tags[j].k_data_rs == Tab[tags[j].itab].nroots);
assert (tags[j].n_block_rs == FX25_BLOCK_SIZE);
}
assert (fx25_pick_mode (1, 239) == 1);
assert (fx25_pick_mode (1, 240) == -1);
assert (fx25_pick_mode (5, 223) == 5);
assert (fx25_pick_mode (5, 224) == -1);
assert (fx25_pick_mode (9, 191) == 9);
assert (fx25_pick_mode (9, 192) == -1);
assert (fx25_pick_mode (16, 32) == 4);
assert (fx25_pick_mode (16, 64) == 3);
assert (fx25_pick_mode (16, 128) == 2);
assert (fx25_pick_mode (16, 239) == 1);
assert (fx25_pick_mode (16, 240) == -1);
assert (fx25_pick_mode (32, 32) == 8);
assert (fx25_pick_mode (32, 64) == 7);
assert (fx25_pick_mode (32, 128) == 6);
assert (fx25_pick_mode (32, 223) == 5);
assert (fx25_pick_mode (32, 234) == -1);
assert (fx25_pick_mode (64, 64) == 11);
assert (fx25_pick_mode (64, 128) == 10);
assert (fx25_pick_mode (64, 191) == 9);
assert (fx25_pick_mode (64, 192) == -1);
} // fx25_init
// Get properties of specified CTAG number.
struct rs *fx25_get_rs (int ctag_num)
{
assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX);
assert (tags[ctag_num].itab >= 0 && tags[ctag_num].itab < NTAB);
assert (Tab[tags[ctag_num].itab].rs != NULL);
return (Tab[tags[ctag_num].itab].rs);
}
uint64_t fx25_get_ctag_value (int ctag_num)
{
assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX);
return (tags[ctag_num].value);
}
int fx25_get_k_data_radio (int ctag_num)
{
assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX);
return (tags[ctag_num].k_data_radio);
}
int fx25_get_k_data_rs (int ctag_num)
{
assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX);
return (tags[ctag_num].k_data_rs);
}
int fx25_get_nroots (int ctag_num)
{
assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX);
return (Tab[tags[ctag_num].itab].nroots);
}
int fx25_get_debug (void)
{
return (g_debug_level);
}
/*-------------------------------------------------------------
*
* Name: fx25_pick_mode
*
* Purpose: Pick suitable transmission format based on user preference
* and size of data part required.
*
* Inputs: fx_mode - Normally, this would be 16, 32, or 64 for the desired number
* of check bytes. The shortest format, adequate for the
* required data length will be picked automatically.
* 0x01 thru 0x0b may also be specified for a specific format
* but this is expected to be mostly for testing, not normal
* operation.
*
* dlen - Required size for transmitted "data" part, in bytes.
* This includes the AX.25 frame with bit stuffing and a flag
* pattern on each end.
*
* Returns: Correlation tag number in range of CTAG_MIN thru CTAG_MAX.
* -1 is returned for failure.
*
* Future: Might be more accomodating.
* For example, if 64 check bytes were specified for 200 data bytes,
* we might automatically drop it down to 32 check bytes, print a
* warning and continue. Keep it simple at first. Fine tune later.
*
*--------------------------------------------------------------*/
int fx25_pick_mode (int fx_mode, int dlen)
{
if (fx_mode >= CTAG_MIN && fx_mode <= CTAG_MAX) {
if (dlen <= fx25_get_k_data_radio(fx_mode)) {
return (fx_mode);
}
else {
return (-1); // Assuming caller prints failure message.
}
}
else if (fx_mode == 16 || fx_mode == 32 || fx_mode == 64) {
for (int k = CTAG_MAX; k >= CTAG_MIN; k--) {
if (fx_mode == fx25_get_nroots(k) && dlen <= fx25_get_k_data_radio(k)) {
return (k);
}
}
return (-1);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("FX.25: Transmission format %d must be 0x00 thru 0x0b, 16, 32, or 64.\n", fx_mode);
exit(EXIT_FAILURE);
}
}
/* Initialize a Reed-Solomon codec
* symsize = symbol size, bits (1-8) - always 8 for this application.
* gfpoly = Field generator polynomial coefficients
* fcr = first root of RS code generator polynomial, index form
* prim = primitive element to generate polynomial roots
* nroots = RS code generator polynomial degree (number of roots)
*/
struct rs *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigned prim,
unsigned int nroots){
struct rs *rs;
int i, j, sr,root,iprim;
if(symsize > 8*sizeof(DTYPE))
return NULL; /* Need version with ints rather than chars */
if(fcr >= (1<<symsize))
return NULL;
if(prim == 0 || prim >= (1<<symsize))
return NULL;
if(nroots >= (1<<symsize))
return NULL; /* Can't have more roots than symbol values! */
rs = (struct rs *)calloc(1,sizeof(struct rs));
rs->mm = symsize;
rs->nn = (1<<symsize)-1;
rs->alpha_to = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1));
if(rs->alpha_to == NULL){
free(rs);
return NULL;
}
rs->index_of = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1));
if(rs->index_of == NULL){
free(rs->alpha_to);
free(rs);
return NULL;
}
/* Generate Galois field lookup tables */
rs->index_of[0] = A0; /* log(zero) = -inf */
rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */
sr = 1;
for(i=0;i<rs->nn;i++){
rs->index_of[sr] = i;
rs->alpha_to[i] = sr;
sr <<= 1;
if(sr & (1<<symsize))
sr ^= gfpoly;
sr &= rs->nn;
}
if(sr != 1){
/* field generator polynomial is not primitive! */
free(rs->alpha_to);
free(rs->index_of);
free(rs);
return NULL;
}
/* Form RS code generator polynomial from its roots */
rs->genpoly = (DTYPE *)malloc(sizeof(DTYPE)*(nroots+1));
if(rs->genpoly == NULL){
free(rs->alpha_to);
free(rs->index_of);
free(rs);
return NULL;
}
rs->fcr = fcr;
rs->prim = prim;
rs->nroots = nroots;
/* Find prim-th root of 1, used in decoding */
for(iprim=1;(iprim % prim) != 0;iprim += rs->nn)
;
rs->iprim = iprim / prim;
rs->genpoly[0] = 1;
for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) {
rs->genpoly[i+1] = 1;
/* Multiply rs->genpoly[] by @**(root + x) */
for (j = i; j > 0; j--){
if (rs->genpoly[j] != 0)
rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)];
else
rs->genpoly[j] = rs->genpoly[j-1];
}
/* rs->genpoly[0] can never be zero */
rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)];
}
/* convert rs->genpoly[] to index form for quicker encoding */
for (i = 0; i <= nroots; i++) {
rs->genpoly[i] = rs->index_of[rs->genpoly[i]];
}
// diagnostic prints
/*
printf("Alpha To:\n\r");
for (i=0; i < sizeof(DTYPE)*(rs->nn+1); i++)
printf("0x%2x,", rs->alpha_to[i]);
printf("\n\r");
printf("Index Of:\n\r");
for (i=0; i < sizeof(DTYPE)*(rs->nn+1); i++)
printf("0x%2x,", rs->index_of[i]);
printf("\n\r");
printf("GenPoly:\n\r");
for (i = 0; i <= nroots; i++)
printf("0x%2x,", rs->genpoly[i]);
printf("\n\r");
*/
return rs;
}
// TEMPORARY!!!
// FIXME: We already have multiple copies of this.
// Consolidate them into one somewhere.
void fx_hex_dump (unsigned char *p, int len)
{
int n, i, offset;
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
dw_printf (" %03x: ", offset);
for (i=0; i<n; i++) {
dw_printf (" %02x", p[i]);
}
for (i=n; i<16; i++) {
dw_printf (" ");
}
dw_printf (" ");
for (i=0; i<n; i++) {
dw_printf ("%c", isprint(p[i]) ? p[i] : '.');
}
dw_printf ("\n");
p += 16;
offset += 16;
len -= 16;
}
}

480
src/fx25_rec.c Normal file
View File

@ -0,0 +1,480 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
//
/********************************************************************************
*
* File: fx25_rec.c
*
* Purpose: Extract FX.25 codeblocks from a stream of bits and process them.
*
*******************************************************************************/
#include "direwolf.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
//#if __WIN32__
//#include <fcntl.h>
//#endif
#include "fx25.h"
#include "fcs_calc.h"
#include "textcolor.h"
#include "multi_modem.h"
#include "demod.h"
struct fx_context_s {
enum { FX_TAG=0, FX_DATA, FX_CHECK } state;
uint64_t accum; // Accumulate bits for matching to correlation tag.
int ctag_num; // Correlation tag number, CTAG_MIN to CTAG_MAX if approx. match found.
int k_data_radio; // Expected size of "data" sent over radio.
int coffs; // Starting offset of the check part.
int nroots; // Expected number of check bytes.
int dlen; // Accumulated length in "data" below.
int clen; // Accumulated length in "check" below.
unsigned char imask; // Mask for storing a bit.
unsigned char block[FX25_BLOCK_SIZE+1];
};
static struct fx_context_s *fx_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS];
static void process_rs_block (int chan, int subchan, int slice, struct fx_context_s *F);
static int my_unstuff (unsigned char * restrict pin, int ilen, unsigned char * restrict frame_buf);
//#define FXTEST 1 // Define for standalone test application.
// It expects to find files fx01.dat, fx02.dat, ..., fx0b.dat/
#if FXTEST
static int fx25_test_count = 0;
int main ()
{
fx25_init(3);
for (int i = CTAG_MIN; i <= CTAG_MAX; i++) {
char fname[32];
snprintf (fname, sizeof(fname), "fx%02x.dat", i);
FILE *fp = fopen(fname, "rb");
if (fp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("****** Could not open %s ******\n", fname);
dw_printf ("****** Did you generate the test files first? ******\n");
exit (EXIT_FAILURE);
}
//#if 0 // reminder for future if reading from stdin.
//#if __WIN32__
// // So 0x1a byte does not signal EOF.
// _setmode(_fileno(stdin), _O_BINARY);
//#endif
// fp = stdin;
//#endif
unsigned char ch;
while (fread(&ch, 1, 1, fp) == 1) {
for (unsigned char imask = 0x01; imask != 0; imask <<=1) {
fx25_rec_bit (0, 0, 0, ch & imask);
}
}
fclose (fp);
}
if (fx25_test_count == 11) {
text_color_set(DW_COLOR_REC);
dw_printf ("\n");
dw_printf ("\n");
dw_printf ("\n");
dw_printf ("***** FX25 unit test Success - all tests passed. *****\n");
exit (EXIT_SUCCESS);
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("\n");
dw_printf ("***** FX25 unit test FAILED. Only %d/11 tests passed. *****\n", fx25_test_count);
exit (EXIT_SUCCESS);
} // end main
#endif // FXTEST
/***********************************************************************************
*
* Name: fx25_rec_bit
*
* Purpose: Extract FX.25 codeblocks from a stream of bits.
* In a completely integrated AX.25 / FX.25 receive system,
* this would see the same bit stream as hdlc_rec_bit.
*
* Inputs: chan - Channel number.
*
* subchan - This allows multiple demodulators per channel.
*
* slice - Allows multiple slicers per demodulator (subchannel).
*
* dbit - Data bit after NRZI and any descrambling.
* Any non-zero value is logic '1'.
*
* Description: This is called once for each received bit.
* For each valid frame, 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.
*
***********************************************************************************/
#define FENCE 0x55 // to detect buffer overflow.
void fx25_rec_bit (int chan, int subchan, int slice, int dbit)
{
// Allocate context blocks only as needed.
struct fx_context_s *F = fx_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 = fx_context[chan][subchan][slice] = (struct fx_context_s *)malloc(sizeof (struct fx_context_s));
assert (F != NULL);
memset (F, 0, sizeof(struct fx_context_s));
}
// State machine to identify correlation tag then gather appropriate number of data and check bytes.
switch (F->state) {
case FX_TAG:
F->accum >>= 1;
if (dbit) F->accum |= 1LL << 63;
int c = fx25_tag_find_match (F->accum);
if (c >= CTAG_MIN && c <= CTAG_MAX) {
F->ctag_num = c;
F->k_data_radio = fx25_get_k_data_radio (F->ctag_num);
F->nroots = fx25_get_nroots (F->ctag_num);
F->coffs = fx25_get_k_data_rs (F->ctag_num);
assert (F->coffs == FX25_BLOCK_SIZE - F->nroots);
if (fx25_get_debug() >= 2) {
text_color_set(DW_COLOR_INFO);
dw_printf ("FX.25: Matched correlation tag 0x%02x with %d bit errors. Expecting %d data & %d check bytes.\n",
c,
__builtin_popcountll(F->accum ^ fx25_get_ctag_value(c)),
F->k_data_radio, F->nroots);
}
F->imask = 0x01;
F->dlen = 0;
F->clen = 0;
memset (F->block, 0, sizeof(F->block));
F->block[FX25_BLOCK_SIZE] = FENCE;
F->state = FX_DATA;
}
break;
case FX_DATA:
if (dbit) F->block[F->dlen] |= F->imask;
F->imask <<= 1;
if (F->imask == 0) {
F->imask = 0x01;
F->dlen++;
if (F->dlen >= F->k_data_radio) {
F->state = FX_CHECK;
}
}
break;
case FX_CHECK:
if (dbit) F->block[F->coffs + F->clen] |= F->imask;
F->imask <<= 1;
if (F->imask == 0) {
F->imask = 0x01;
F->clen++;
if (F->clen >= F->nroots) {
process_rs_block (chan, subchan, slice, F); // see below
F->ctag_num = -1;
F->accum = 0;
F->state = FX_TAG;
}
}
break;
}
}
/***********************************************************************************
*
* Name: fx25_rec_busy
*
* Purpose: Is FX.25 reception currently in progress?
*
* Inputs: chan - Channel number.
*
* Returns: True if currently in progress for the specified channel.
*
* Description: This is required for duplicate removal. One channel and can have
* multiple demodulators (called subchannels) running in parallel.
* Each of them can have multiple slicers. Duplicates need to be
* removed. Normally a delay of a couple bits (or more accurately
* symbols) was fine because they all took about the same amount of time.
* Now, we can have an additional delay of up to 64 check bytes and
* some filler in the data portion. We can't simply wait that long.
* With normal AX.25 a couple frames can come and go during that time.
* We want to delay the duplicate removal while FX.25 block reception
* is going on.
*
***********************************************************************************/
int fx25_rec_busy (int chan)
{
assert (chan >= 0 && chan < MAX_CHANS);
// This could be a litle faster if we knew number of
// subchannels and slicers but it is probably insignificant.
for (int i = 0; i < MAX_SUBCHANS; i++) {
for (int j = 0; j < MAX_SLICERS; j++) {
if (fx_context[chan][i][j] != NULL) {
if (fx_context[chan][i][j]->state != FX_TAG) {
return (1);
}
}
}
}
return (0);
} // end fx25_rec_busy
/***********************************************************************************
*
* Name: process_rs_block
*
* Purpose: After the correlation tag was detected and the appropriate number
* of data and check bytes are accumulated, this performs the processing
*
* Inputs: chan, subchan, slice
*
* F->ctag_num - Correlation tag number (index into table)
*
* F->dlen - Number of "data" bytes.
*
* F->clen - Number of "check" bytes"
*
* F->block - Codeblock. Always 255 total bytes.
* Anything left over after data and check
* bytes is filled with zeros.
*
* <- - - - - - - - - - - 255 bytes total - - - - - - - - ->
* +-----------------------+---------------+---------------+
* | dlen bytes "data" | zero fill | check bytes |
* +-----------------------+---------------+---------------+
*
* Description: Use Reed-Solomon decoder to fix up any errors.
* Extract the AX.25 frame from the corrected data.
*
***********************************************************************************/
static void process_rs_block (int chan, int subchan, int slice, struct fx_context_s *F)
{
if (fx25_get_debug() >= 3) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("FX.25: Received RS codeblock.\n");
fx_hex_dump (F->block, FX25_BLOCK_SIZE);
}
assert (F->block[FX25_BLOCK_SIZE] == FENCE);
int derrlocs[FX25_MAX_CHECK]; // Half would probably be OK.
struct rs *rs = fx25_get_rs(F->ctag_num);
int derrors = DECODE_RS(rs, F->block, derrlocs, 0);
if (derrors >= 0) { // -1 for failure. >= 0 for success, number of bytes corrected.
if (fx25_get_debug() >= 2) {
text_color_set(DW_COLOR_INFO);
if (derrors == 0) {
dw_printf ("FX.25: FEC complete with no errors.\n");
}
else {
dw_printf ("FX.25: FEC complete, fixed %2d errors in byte positions:",derrors);
for (int k = 0; k < derrors; k++) {
dw_printf (" %d", derrlocs[k]);
}
dw_printf ("\n");
}
}
unsigned char frame_buf[FX25_MAX_DATA+1]; // Out must be shorter than input.
int frame_len = my_unstuff (F->block, F->dlen, frame_buf);
if (frame_len >= 14 + 1 + 2) { // Minimum length: Two addresses & control & FCS.
unsigned short actual_fcs = frame_buf[frame_len-2] | (frame_buf[frame_len-1] << 8);
unsigned short expected_fcs = fcs_calc (frame_buf, frame_len - 2);
if (actual_fcs == expected_fcs) {
if (fx25_get_debug() >= 3) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("FX.25: Extracted AX.25 frame:\n");
fx_hex_dump (frame_buf, frame_len);
}
#if FXTEST
fx25_test_count++;
#else
alevel_t alevel = demod_get_audio_level (chan, subchan);
multi_modem_process_rec_frame (chan, subchan, slice, frame_buf, frame_len - 2, alevel, derrors, 1); /* len-2 to remove FCS. */
#endif
} else {
// Most likely cause is defective sender software.
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: Bad FCS for AX.25 frame.\n");
fx_hex_dump (F->block, F->dlen);
fx_hex_dump (frame_buf, frame_len);
}
}
else {
// Most likely cause is defective sender software.
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: AX.25 frame is shorter than minimum length.\n");
fx_hex_dump (F->block, F->dlen);
fx_hex_dump (frame_buf, frame_len);
}
}
else if (fx25_get_debug() >= 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: FEC failed. Too many errors.\n");
}
} // process_rs_block
/***********************************************************************************
*
* Name: my_unstuff
*
* Purpose: Remove HDLC it stuffing and surrounding flag delimiters.
*
* Inputs: pin - "data" part of RS codeblock.
* First byte must be HDLC "flag".
* May be followed by additional flags.
* There must be terminating flag but it might not be byte aligned.
*
* ilen - Number of bytes in pin.
*
* Outputs: frame_buf - Frame contents including FCS.
* Bit stuffing is gone so it should be a whole number of bytes.
*
* Returns: Number of bytes in frame_buf, including 2 for FCS.
* This can never be larger than the max "data" size.
* 0 if any error.
*
* Errors: First byte is not not flag.
* Found seven '1' bits in a row.
* Result is not whole number of bytes after removing bit stuffing.
* Trailing flag not found.
* Most likely cause, for all of these, is defective sender software.
*
***********************************************************************************/
static int my_unstuff (unsigned char * restrict pin, int ilen, unsigned char * restrict frame_buf)
{
unsigned char pat_det = 0; // Pattern detector.
unsigned char oacc = 0; // Accumulator for a byte out.
int olen = 0; // Number of good bits in oacc.
int frame_len = 0; // Number of bytes accumulated, including CRC.
if (*pin != 0x7e) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25 error: Data section did not start with 0x7e.\n");
fx_hex_dump (pin, ilen);
return (0);
}
while (ilen > 0 && *pin == 0x7e) {
ilen--;
pin++; // Skip over leading flag byte(s).
}
for (int i=0; i<ilen; pin++, i++) {
for (unsigned char imask = 0x01; imask != 0; imask <<= 1) {
unsigned char dbit = (*pin & imask) != 0;
pat_det >>= 1; // Shift the most recent eight bits thru the pattern detector.
pat_det |= dbit << 7;
if (pat_det == 0xfe) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: Invalid AX.25 frame - Seven '1' bits in a row.\n");
fx_hex_dump (pin, ilen);
return 0;
}
if (dbit) {
oacc >>= 1;
oacc |= 0x80;
} else {
if (pat_det == 0x7e) { // "flag" pattern - End of frame.
if (olen == 7) {
return (frame_len); // Whole number of bytes in result including CRC
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: Invalid AX.25 frame - Not a whole number of bytes.\n");
fx_hex_dump (pin, ilen);
return (0);
}
} else if ( (pat_det >> 2) == 0x1f ) {
continue; // Five '1' bits in a row, followed by '0'. Discard the '0'.
}
oacc >>= 1;
}
olen++;
if (olen & 8) {
olen = 0;
frame_buf[frame_len++] = oacc;
}
}
} /* end of loop on all bits in block */
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: Invalid AX.25 frame - Terminating flag not found.\n");
fx_hex_dump (pin, ilen);
return (0); // Should never fall off the end.
} // my_unstuff
// end fx25_rec.c

331
src/fx25_send.c Normal file
View File

@ -0,0 +1,331 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
//
#include "direwolf.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "fx25.h"
#include "fcs_calc.h"
#include "textcolor.h"
#include "audio.h"
#include "gen_tone.h"
//#define FXTEST 1 // To build unit test application.
#ifndef FXTEST
static void send_bytes (int chan, unsigned char *b, int count);
static void send_bit (int chan, int b);
#endif
static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize);
static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "fx25_send_frame" or "???"
#if FXTEST
static unsigned char preload[] = {
'T'<<1, 'E'<<1, 'S'<<1, 'T'<<1, ' '<<1, ' '<<1, 0x60,
'W'<<1, 'B'<<1, '2'<<1, 'O'<<1, 'S'<<1, 'Z'<<1, 0x63,
0x03, 0xf0,
'F', 'o', 'o', '?' , 'B', 'a', 'r', '?' , // '?' causes bit stuffing
0, 0, 0 // Room for FCS + extra
};
int main ()
{
text_color_set(DW_COLOR_ERROR);
dw_printf("fxsend - FX.25 unit test.\n");
dw_printf("This generates 11 files named fx01.dat, fx02.dat, ..., fx0b.dat\n");
dw_printf("Run fxrec as second part of test.\n");
fx25_init (3);
for (int i = CTAG_MIN; i <= CTAG_MAX; i++) {
fx25_send_frame (0, preload, (int)sizeof(preload)-3, i);
}
exit(EXIT_SUCCESS);
} // end main
#endif
/*-------------------------------------------------------------
*
* Name: fx25_send_frame
*
* Purpose: Convert HDLC frames to a stream of bits.
*
* Inputs: chan - Audio channel number, 0 = first.
*
* fbuf - Frame buffer address.
*
* flen - Frame length, before bit-stuffing, not including the FCS.
*
* fx_mode - Normally, this would be 16, 32, or 64 for the desired number
* of check bytes. The shortest format, adequate for the
* required data length will be picked automatically.
* 0x01 thru 0x0b may also be specified for a specific format
* but this is expected to be mostly for testing, not normal
* operation.
*
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
*
* Returns: Number of bits sent including "flags" and the
* stuffing bits.
* 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 AX.25 frame in the usual way then wrap
* it inside of the FX.25 correlation tag and check bytes.
*
* 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: If something goes wrong, return -1 and the caller should
* fallback to sending normal AX.25.
*
* This could happen if the frame is too large.
*
*--------------------------------------------------------------*/
int fx25_send_frame (int chan, unsigned char *fbuf, int flen, int fx_mode)
{
if (fx25_get_debug() >= 3) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("------\n");
dw_printf ("FX.25 send frame: chan = %d, FX.25 mode = %d\n", chan, fx_mode);
fx_hex_dump (fbuf, flen);
}
number_of_bits_sent[chan] = 0;
// Append the FCS.
int fcs = fcs_calc (fbuf, flen);
fbuf[flen++] = fcs & 0xff;
fbuf[flen++] = (fcs >> 8) & 0xff;
// Add bit-stuffing.
unsigned char data[FX25_MAX_DATA+1];
const unsigned char fence = 0xaa;
data[FX25_MAX_DATA] = fence;
int dlen = stuff_it(fbuf, flen, data, FX25_MAX_DATA);
assert (data[FX25_MAX_DATA] == fence);
if (dlen < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: Frame length of %d + overhead is too large to encode.\n", flen);
return (-1);
}
// Pick suitable correlation tag depending on
// user's preference, for number of check bytes,
// and the data size.
int ctag_num = fx25_pick_mode (fx_mode, dlen);
if (ctag_num < CTAG_MIN || ctag_num > CTAG_MAX) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FX.25: Could not find suitable format for requested %d and data length %d.\n", fx_mode, dlen);
return (-1);
}
uint64_t ctag_value = fx25_get_ctag_value (ctag_num);
// Zero out part of data which won't be transmitted.
// It should all be filled by extra HDLC "flag" patterns.
int k_data_radio = fx25_get_k_data_radio (ctag_num);
int k_data_rs = fx25_get_k_data_rs (ctag_num);
int shorten_by = FX25_MAX_DATA - k_data_radio;
if (shorten_by > 0) {
memset (data + k_data_radio, 0, shorten_by);
}
// Compute the check bytes.
unsigned char check[FX25_MAX_CHECK+1];
check[FX25_MAX_CHECK] = fence;
struct rs *rs = fx25_get_rs (ctag_num);
assert (k_data_rs + NROOTS == NN);
ENCODE_RS(rs, data, check);
assert (check[FX25_MAX_CHECK] == fence);
if (fx25_get_debug() >= 3) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("FX.25: transmit %d data bytes, ctag number 0x%02x\n", k_data_radio, ctag_num);
fx_hex_dump (data, k_data_radio);
dw_printf ("FX.25: transmit %d check bytes:\n", NROOTS);
fx_hex_dump (check, NROOTS);
dw_printf ("------\n");
}
#if FXTEST
// Standalone text application.
unsigned char flags[16] = { 0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e };
char fname[32];
snprintf (fname, sizeof(fname), "fx%02x.dat", ctag_num);
FILE *fp = fopen(fname, "wb");
fwrite (flags, sizeof(flags), 1, fp);
//fwrite ((unsigned char *)(&ctag_value), sizeof(ctag_value), 1, fp); // No - assumes little endian.
for (int k = 0; k < 8; k++) {
unsigned char b = (ctag_value >> (k * 8)) & 0xff; // Should be portable to big endian too.
fwrite (&b, 1, 1, fp);
}
#if 1
for (int j = 8; j < 16; j++) { // Introduce errors.
data[j] = ~ data[j];
}
#endif
fwrite (data, k_data_radio, 1, fp);
fwrite (check, NROOTS, 1, fp);
fwrite (flags, sizeof(flags), 1, fp);
fflush(fp);
fclose (fp);
#else
// Normal usage. Send bits to modulator.
for (int k = 0; k < 8; k++) {
unsigned char b = (ctag_value >> (k * 8)) & 0xff;
send_bytes (chan, &b, 1);
}
send_bytes (chan, data, k_data_radio);
send_bytes (chan, check, NROOTS);
#endif
return (number_of_bits_sent[chan]);
}
#ifndef FXTEST
static void send_bytes (int chan, unsigned char *b, int count)
{
for (int j = 0; j < count; j++) {
unsigned char x = b[j];
for (int k = 0; k < 8; k++) {
send_bit (chan, x & 0x01);
x >>= 1;
}
}
}
/*
* NRZI encoding.
* data 1 bit -> no change.
* data 0 bit -> invert signal.
*/
static void send_bit (int chan, int b)
{
static int output[MAX_CHANS];
if (b == 0) {
output[chan] = ! output[chan];
}
tone_gen_put_bit (chan, output[chan]);
number_of_bits_sent[chan]++;
}
#endif // FXTEST
/*-------------------------------------------------------------
*
* Name: stuff_it
*
* Purpose: Perform HDLC bit-stuffing and add "flag" octets in
* preparation for the RS encoding.
*
* Inputs: in - Frame, including FCS, in.
*
* ilen - Number of bytes in.
*
* osize - Size of out area.
*
* Outputs: out - Location to receive result.
*
* Returns: Number of bytes needed in output area including one trailing flag.
* -1 if it won't fit.
*
* Description: Convert to stream of bits including:
* start flag
* bit stuffed data, including FCS
* end flag
* Fill remainder with flag octets which might not be on byte boundaries.
*
*--------------------------------------------------------------*/
#define put_bit(value) { \
if (olen >= osize) return(-1); \
if (value) out[olen>>3] |= 1 << (olen & 0x7); \
olen++; \
}
static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize)
{
const unsigned char flag = 0x7e;
int ret = -1;
memset (out, 0, osize);
out[0] = flag;
int olen = 8; // Number of bits in output.
osize *= 8; // Now in bits rather than bytes.
int ones = 0;
for (int i = 0; i < ilen; i++) {
for (unsigned char imask = 1; imask != 0; imask <<= 1) {
int v = in[i] & imask;
put_bit(v);
if (v) {
ones++;
if (ones == 5) {
put_bit(0);
ones = 0;
}
}
else {
ones = 0;
}
}
}
for (unsigned char imask = 1; imask != 0; imask <<= 1) {
put_bit(flag & imask);
}
ret = (olen + 7) / 8; // Includes any partial byte.
unsigned char imask = 1;
while (olen < osize) {
put_bit( flag & imask);
imask = (imask << 1) | (imask >> 7); // Rotate.
}
return (ret);
} // end stuff_it
// end fx25_send.c

View File

@ -75,6 +75,7 @@
#include "textcolor.h"
#include "morse.h"
#include "dtmf.h"
#include "fx25.h"
/* Own random number generator so we can get */
@ -145,7 +146,7 @@ static void send_packet (char *str)
}
#endif
hdlc_send_flags (c, 8, 0);
hdlc_send_frame (c, fbuf, flen, 0);
hdlc_send_frame (c, fbuf, flen, 0, modem.fx25_xmit_enable);
hdlc_send_flags (c, 2, 1);
}
ax25_delete (pp);
@ -162,7 +163,6 @@ int main(int argc, char **argv)
int packet_count = 0;
int i;
int chan;
int experiment = 0;
int g_opt = 0;
int j_opt = 0;
@ -218,7 +218,7 @@ int main(int argc, char **argv)
/* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "gjJm:s:a:b:B:r:n:o:z:82M:X",
c = getopt_long(argc, argv, "gjJm:s:a:b:B:r:n:N:o:z:82M:X:",
long_options, &option_index);
if (c == -1)
break;
@ -353,9 +353,13 @@ int main(int argc, char **argv)
case 'n': /* -n number of packets with increasing noise. */
packet_count = atoi(optarg);
g_add_noise = 1;
break;
case 'N': /* -N number of packets. Don't add noise. */
packet_count = atoi(optarg);
g_add_noise = 0;
break;
case 'a': /* -a for amplitude */
@ -440,7 +444,7 @@ int main(int argc, char **argv)
case 'X':
experiment = 1;
modem.fx25_xmit_enable = atoi(optarg);
break;
case '?':
@ -515,99 +519,20 @@ int main(int argc, char **argv)
}
if (experiment) {
modem.achan[0].modem_type = MODEM_QPSK;
modem.achan[0].baud = 2400; // really bps not baud.
amplitude = 100;
}
gen_tone_init (&modem, amplitude/2, 1);
morse_init (&modem, amplitude/2);
dtmf_init (&modem, amplitude/2);
// We don't have -d or -q options here.
// Just use the default of minimal information.
fx25_init (1);
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);
assert (modem.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC);
if (experiment) {
int chan = 0;
int n;
// 6 cycles of 1800 Hz.
for (n=0; n<8; n++) {
tone_gen_put_bit (chan, 0);
}
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 180
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
// Shift 270
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// HDLC flag - six 1 in a row.
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0); // reverse even/odd position
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
audio_file_close ();
return (EXIT_SUCCESS);
}
/*
* Get user packets(s) from file or stdin if specified.
* "-n" option is ignored in this case.
@ -735,6 +660,7 @@ 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 (" -m <number> Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ);
dw_printf (" -s <number> Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ);
dw_printf (" -r <number> Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC);

View File

@ -44,6 +44,7 @@
#include "multi_modem.h"
#include "demod_9600.h" /* for descramble() */
#include "ptt.h"
#include "fx25.h"
//#define TEST 1 /* Define for unit testing. */
@ -127,6 +128,8 @@ static int composite_dcd[MAX_CHANS][MAX_SUBCHANS+1];
static int was_init = 0;
static struct audio_s *g_audio_p;
void hdlc_rec_init (struct audio_s *pa)
{
int ch, sub, slice;
@ -136,7 +139,8 @@ void hdlc_rec_init (struct audio_s *pa)
//dw_printf ("hdlc_rec_init (%p) \n", pa);
assert (pa != NULL);
g_audio_p = pa;
memset (composite_dcd, 0, sizeof(composite_dcd));
for (ch = 0; ch < MAX_CHANS; ch++)
@ -168,7 +172,18 @@ void hdlc_rec_init (struct audio_s *pa)
was_init = 1;
}
/* Own copy of random number generator so we can get */
/* same predictable results on different operating systems. */
/* TODO: Consolidate multiple copies somewhere. */
#define MY_RAND_MAX 0x7fffffff
static int seed = 1;
static int my_rand (void) {
// Perform the calculation as unsigned to avoid signed overflow error.
seed = (int)(((unsigned)seed * 1103515245) + 12345) & MY_RAND_MAX;
return (seed);
}
/***********************************************************************************
*
@ -196,8 +211,6 @@ void hdlc_rec_init (struct audio_s *pa)
*
***********************************************************************************/
// TODO: int not_used_remove
void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove)
{
@ -212,6 +225,21 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
assert (slice >= 0 && slice < MAX_SLICERS);
// -e option can be used to artificially introduce the desired
// Bit Error Rate (BER) for testing.
if (g_audio_p->recv_ber != 0) {
double r = (double)my_rand() / (double)MY_RAND_MAX; // calculate as double to preserve all 31 bits.
if (g_audio_p->recv_ber > r) {
// FIXME
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("hdlc_rec_bit randomly clobber bit, ber = %.6f\n", g_audio_p->recv_ber);
raw = ! raw;
}
}
/*
* Different state information for each channel / subchannel / slice.
*/
@ -239,6 +267,9 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
H->prev_raw = raw;
}
// After BER insertion, NRZI, and any descrambling, feed into FX.25 decoder as well.
fx25_rec_bit (chan, subchan, slice, dbit);
/*
* Octets are sent LSB first.
@ -394,7 +425,7 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
if (actual_fcs == expected_fcs) {
alevel_t alevel = demod_get_audio_level (chan, subchan);
multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE); /* len-2 to remove FCS. */
multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE, 0); /* len-2 to remove FCS. */
}
else {

View File

@ -772,7 +772,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t
assert (rrbb_get_chan(block) == chan);
assert (rrbb_get_subchan(block) == subchan);
multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry); /* len-2 to remove FCS. */
multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry, 0); /* len-2 to remove FCS. */
return 1; /* success */
} else if (passall) {
@ -781,7 +781,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t
//text_color_set(DW_COLOR_ERROR);
//dw_printf ("ATTEMPTING PASSALL PROCESSING\n");
multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX); /* len-2 to remove FCS. */
multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX, 0); /* len-2 to remove FCS. */
return 1; /* success */
}
else {

View File

@ -62,6 +62,6 @@ int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice,
/* Provided by the top level application to process a complete frame. */
void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t level, retry_t retries, char *spectrum);
void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t level, int is_fx25, retry_t retries, char *spectrum);
#endif

View File

@ -2,7 +2,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013, 2014 John Langner, WB2OSZ
// Copyright (C) 2011, 2013, 2014, 2019 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,6 +27,7 @@
#include "gen_tone.h"
#include "textcolor.h"
#include "fcs_calc.h"
#include "fx25.h"
static void send_control (int, int);
static void send_data (int, int);
@ -53,6 +54,9 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl
* flen - Frame length, not including the FCS.
*
* 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().
*
@ -75,7 +79,26 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl
*
*--------------------------------------------------------------*/
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs)
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)
{
if (fx25_xmit_enable) {
int n = fx25_send_frame (chan, fbuf, flen, fx25_xmit_enable);
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");
}
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;

View File

@ -1,7 +1,7 @@
/* hdlc_send.h */
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs);
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs, int fx25_xmit_enable);
int hdlc_send_flags (int chan, int flags, int finish);

View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// Copyright (C) 2013, 2014, 2015, 2016, 2019 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
@ -69,9 +69,20 @@
* different fixup attempts.
* Set limit on number of packets in fix up later queue.
*
* New in version 1.6:
*
* FX.25. Previously a delay of a couple bits (or more accurately
* symbols) was fine because the decoders took about the same amount of time.
* Now, we can have an additional delay of up to 64 check bytes and
* some filler in the data portion. We can't simply wait that long.
* With normal AX.25 a couple frames can come and go during that time.
* We want to delay the duplicate removal while FX.25 block reception
* is going on.
*
*------------------------------------------------------------------*/
//#define DEBUG 1
#define DIGIPEATER_C
#include "direwolf.h"
@ -89,6 +100,7 @@
#include "hdlc_rec.h"
#include "hdlc_rec2.h"
#include "dlq.h"
#include "fx25.h"
// Properties of the radio channels.
@ -101,7 +113,12 @@ static struct audio_s *save_audio_config_p;
static struct {
packet_t packet_p;
alevel_t alevel;
retry_t retries;
int is_fx25; // 1 for FX.25, 0 for regular AX.25.
retry_t retries; // For the old "fix bits" strategy, this is the
// number of bits that were modified to get a good CRC.
// It would be 0 to something around 4.
// For FX.25, it is the number of corrected.
// This could be from 0 thru 32.
int age;
unsigned int crc;
int score;
@ -169,97 +186,6 @@ void multi_modem_init (struct audio_s *pa)
}
#if 0
//Add a crc to the end of the queue and returns the numbers of CRC stored in the queue
int crc_queue_append (unsigned int crc, unsigned int chan) {
crc_t plast;
// crc_t plast1;
crc_t pnext;
crc_t new_crc;
unsigned int nb_crc = 1;
if (chan>=MAX_CHANS) {
return -1;
}
new_crc = (crc_t) malloc (10*sizeof(struct crc_s));
if (!new_crc)
return -1;
new_crc->crc = crc;
new_crc->nextp = NULL;
if (crc_queue_of_last_to_app[chan] == NULL) {
crc_queue_of_last_to_app[chan] = new_crc;
nb_crc = 1;
}
else {
nb_crc = 2;
plast = crc_queue_of_last_to_app[chan];
pnext = plast->nextp;
while (pnext != NULL) {
nb_crc++;
plast = pnext;
pnext = pnext->nextp;
}
plast->nextp = new_crc;
}
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("Out crc_queue_append nb_crc = %d\n", nb_crc);
#endif
return nb_crc;
}
//Remove the crc from the top of the queue
unsigned int crc_queue_remove (unsigned int chan) {
unsigned int res;
// crc_t plast;
// crc_t pnext;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("In crc_queue_remove\n");
#endif
crc_t removed_crc;
if (chan>=MAX_CHANS) {
return 0;
}
removed_crc = crc_queue_of_last_to_app[chan];
if (removed_crc == NULL) {
return 0;
}
else {
crc_queue_of_last_to_app[chan] = removed_crc->nextp;
res = removed_crc->crc;
free(removed_crc);
}
return res;
}
unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) {
crc_t plast;
crc_t pnext;
if (crc_queue_of_last_to_app[chan] == NULL) {
return 0;
}
else {
plast = crc_queue_of_last_to_app[chan];
do {
pnext = plast->nextp;
if (plast->crc == crc) {
return 1;
}
plast = pnext;
} while (pnext != NULL);
}
return 0;
}
#endif /* if 0 */
/*------------------------------------------------------------------------------
*
@ -346,7 +272,12 @@ void multi_modem_process_sample (int chan, int audio_sample)
if (candidate[chan][subchan][slice].packet_p != NULL) {
candidate[chan][subchan][slice].age++;
if (candidate[chan][subchan][slice].age > process_age[chan]) {
pick_best_candidate (chan);
if (fx25_rec_busy(chan)) {
candidate[chan][subchan][slice].age = 0;
}
else {
pick_best_candidate (chan);
}
}
}
}
@ -371,94 +302,15 @@ void multi_modem_process_sample (int chan, int audio_sample)
* (Special case, use negative to skip
* display of audio level line.
* Use -2 to indicate DTMF message.)
* retries - Level of bit correction used.
*
* retries - Level of correction used.
* is_fx25 - 1 for FX.25, 0 for normal AX.25.
*
* Description: Add to list of candidates. Best one will be picked later.
*
*--------------------------------------------------------------------*/
/*
It gets a little more complicated when we try fixing frames
with imperfect CRCs.
Changing of adjacent bits is quick and done immediately. These
all come in at nearly the same time. The processing of two
separated bits can take a long time and is handled in the
background by another thread. These could come in seconds later.
We need a way to remove duplicates. I think these are the
two cases we need to consider.
(1) Same result as earlier no error or adjacent bit errors.
____||||_
0.0: ptr=00000000
0.1: ptr=00000000
0.2: ptr=00000000
0.3: ptr=00000000
0.4: ptr=009E5540, retry=0, age=295, crc=9458, score=5024
0.5: ptr=0082F008, retry=0, age=294, crc=9458, score=5026 ***
0.6: ptr=009CE560, retry=0, age=293, crc=9458, score=5026
0.7: ptr=009CEE08, retry=0, age=293, crc=9458, score=5024
0.8: ptr=00000000
___._____
0.0: ptr=00000000
0.1: ptr=00000000
0.2: ptr=00000000
0.3: ptr=009E5540, retry=4, age=295, crc=9458, score=1000 ***
0.4: ptr=00000000
0.5: ptr=00000000
0.6: ptr=00000000
0.7: ptr=00000000
0.8: ptr=00000000
(2) Only results from adjusting two non-adjacent bits.
||||||||_
0.0: ptr=022EBA08, retry=0, age=289, crc=5acd, score=5042
0.1: ptr=022EA8B8, retry=0, age=290, crc=5acd, score=5048
0.2: ptr=022EB160, retry=0, age=290, crc=5acd, score=5052
0.3: ptr=05BD0048, retry=0, age=291, crc=5acd, score=5054 ***
0.4: ptr=04FE0048, retry=0, age=292, crc=5acd, score=5054
0.5: ptr=05E10048, retry=0, age=294, crc=5acd, score=5052
0.6: ptr=053D0048, retry=0, age=294, crc=5acd, score=5048
0.7: ptr=02375558, retry=0, age=295, crc=5acd, score=5042
0.8: ptr=00000000
_______._
0.0: ptr=00000000
0.1: ptr=00000000
0.2: ptr=00000000
0.3: ptr=00000000
0.4: ptr=00000000
0.5: ptr=00000000
0.6: ptr=00000000
0.7: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 ***
0.8: ptr=00000000
________.
0.0: ptr=00000000
0.1: ptr=00000000
0.2: ptr=00000000
0.3: ptr=00000000
0.4: ptr=00000000
0.5: ptr=00000000
0.6: ptr=00000000
0.7: ptr=00000000
0.8: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 ***
These can both be covered by keepin the last CRC and dropping
duplicates. In theory we could get another frame in between with
a slow computer so the complete solution would be to remember more
than one.
*/
void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries)
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)
{
packet_t pp;
@ -477,10 +329,12 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
/*
* If only one demodulator/slicer, push it thru and forget about all this foolishness.
* If only one demodulator/slicer, and no FX.25 in progress,
* push it thru and forget about all this foolishness.
*/
if (save_audio_config_p->achan[chan].num_subchan == 1 &&
save_audio_config_p->achan[chan].num_slicers == 1) {
save_audio_config_p->achan[chan].num_slicers == 1 &&
! fx25_rec_busy(chan)) {
int drop_it = 0;
@ -501,7 +355,7 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
ax25_delete (pp);
}
else {
dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, "");
dlq_rec_frame (chan, subchan, slice, pp, alevel, is_fx25, retries, "");
}
return;
}
@ -511,13 +365,17 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
* Otherwise, save them up for a few bit times so we can pick the best.
*/
if (candidate[chan][subchan][slice].packet_p != NULL) {
/* Oops! Didn't expect it to be there. */
/* Plain old AX.25: Oops! Didn't expect it to be there. */
/* FX.25: Quietly replace anything already there. It will have priority. */
ax25_delete (candidate[chan][subchan][slice].packet_p);
candidate[chan][subchan][slice].packet_p = NULL;
}
assert (pp != NULL);
candidate[chan][subchan][slice].packet_p = pp;
candidate[chan][subchan][slice].alevel = alevel;
candidate[chan][subchan][slice].is_fx25 = is_fx25;
candidate[chan][subchan][slice].retries = retries;
candidate[chan][subchan][slice].age = 0;
candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp);
@ -567,6 +425,15 @@ static void pick_best_candidate (int chan)
if (candidate[chan][j][k].packet_p == NULL) {
spectrum[n] = '_';
}
else if (candidate[chan][j][k].is_fx25) {
// FIXME: using retries both as an enum and later int too.
if ((int)(candidate[chan][j][k].retries) <= 9) {
spectrum[n] = '0' + candidate[chan][j][k].retries;
}
else {
spectrum[n] = '+';
}
}
else if (candidate[chan][j][k].retries == RETRY_NONE) {
spectrum[n] = '|';
}
@ -583,12 +450,17 @@ static void pick_best_candidate (int chan)
candidate[chan][j][k].score = 0;
}
else {
/* Originally, this produced 0 for the PASSALL case. */
/* This didn't work so well when looking for the best score. */
/* Around 1.3 dev H, we add an extra 1 in here so the minimum */
/* score should now be 1 for anything received. */
if (candidate[chan][j][k].is_fx25) {
candidate[chan][j][k].score = 9000 - 100 * candidate[chan][j][k].retries;
}
else {
/* Originally, this produced 0 for the PASSALL case. */
/* This didn't work so well when looking for the best score. */
/* Around 1.3 dev H, we add an extra 1 in here so the minimum */
/* score should now be 1 for anything received. */
candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000) + 1;
candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000) + 1;
}
}
}
@ -644,8 +516,9 @@ static void pick_best_candidate (int chan)
candidate[chan][j][k].packet_p);
}
else {
dw_printf ("%d.%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k,
dw_printf ("%d.%d.%d: ptr=%p, is_fx25=%d, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k,
candidate[chan][j][k].packet_p,
candidate[chan][j][k].is_fx25,
(int)(candidate[chan][j][k].retries),
candidate[chan][j][k].age,
candidate[chan][j][k].crc,
@ -700,9 +573,11 @@ static void pick_best_candidate (int chan)
candidate[chan][j][k].packet_p = NULL;
}
else {
assert (candidate[chan][j][k].packet_p != NULL);
dlq_rec_frame (chan, j, k,
candidate[chan][j][k].packet_p,
candidate[chan][j][k].alevel,
candidate[chan][j][k].is_fx25,
(int)(candidate[chan][j][k].retries),
spectrum);

View File

@ -16,6 +16,6 @@ void multi_modem_process_sample (int c, int audio_sample);
int multi_modem_get_dc_average (int chan);
void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries);
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);
#endif

View File

@ -338,7 +338,7 @@ void recv_process (void)
* - Digipeater.
*/
app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->retries, pitem->spectrum);
app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->is_fx25, pitem->retries, pitem->spectrum);
/*

View File

@ -1067,7 +1067,7 @@ static int send_one_frame (int c, int p, packet_t pp)
}
}
nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2);
nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2, save_audio_config_p->fx25_xmit_enable);
return (nb);
} /* end send_one_frame */

View File

@ -1,8 +1,11 @@
# this is a trick to avoid more complication
# because configure_file() is done a configuration time
# This is a trick to avoid more complication
# because configure_file() is done at configuration time
set(CUSTOM_TEST_BINARY_DIR "${CMAKE_BINARY_DIR}/test")
set(GEN_PACKETS_BIN "${CMAKE_BINARY_DIR}/src/gen_packets${CMAKE_EXECUTABLE_SUFFIX}")
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}")
if(WIN32)
set(CUSTOM_SCRIPT_SUFFIX ".bat")
@ -10,6 +13,7 @@ else()
set(CUSTOM_SCRIPT_SUFFIX "")
endif()
set(TEST_CHECK-FX25_FILE "check-fx25")
set(TEST_CHECK-MODEM1200_FILE "check-modem1200")
set(TEST_CHECK-MODEM300_FILE "check-modem300")
set(TEST_CHECK-MODEM9600_FILE "check-modem9600")
@ -20,6 +24,13 @@ set(TEST_CHECK-MODEM2400-g_FILE "check-modem2400-g")
set(TEST_CHECK-MODEM4800_FILE "check-modem4800")
# generate the scripts that run the tests
configure_file(
"${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-FX25_FILE}"
"${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-FX25_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}"
@ -89,6 +100,7 @@ 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
@ -98,6 +110,9 @@ list(APPEND atest9_SOURCES
${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
@ -415,8 +430,43 @@ set_target_properties(dtmftest
PROPERTIES COMPILE_FLAGS "-DDTMF_TEST"
)
# Unit Test FX.25 algorithm.
list(APPEND fxsend_SOURCES
${CUSTOM_SRC_DIR}/fx25_send.c
${CUSTOM_SRC_DIR}/fx25_encode.c
${CUSTOM_SRC_DIR}/fx25_init.c
${CUSTOM_SRC_DIR}/fcs_calc.c
${CUSTOM_SRC_DIR}/textcolor.c
)
add_executable(fxsend
${fxsend_SOURCES}
)
set_target_properties(fxsend
PROPERTIES COMPILE_FLAGS "-DFXTEST"
)
list(APPEND fxrec_SOURCES
${CUSTOM_SRC_DIR}/fx25_rec.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(fxrec
${fxrec_SOURCES}
)
set_target_properties(fxrec
PROPERTIES COMPILE_FLAGS "-DFXTEST"
)
# doing ctest on previous programs
add_test(dtest dtest)
add_test(ttest ttest)
add_test(tttexttest tttexttest)
@ -429,6 +479,7 @@ add_test(pad2test pad2test)
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-modem1200 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_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}")

5
test/scripts/check-fx25 Normal file
View File

@ -0,0 +1,5 @@
@CUSTOM_SHELL_SHABANG@
@FXSEND_BIN@
@FXREC_BIN@