AX.25 v2.2 connected mode.

modified:   CHANGES.md
	modified:   Makefile.linux
	modified:   Makefile.macosx
	modified:   Makefile.win
	modified:   README.md
	modified:   atest.c
	modified:   audio.h
	new file:   ax25_link.c
	new file:   ax25_link.h
	modified:   ax25_pad.c
	modified:   ax25_pad.h
	modified:   ax25_pad2.c
	new file:   cdigipeater.c
	new file:   cdigipeater.h
	modified:   config.c
	modified:   config.h
	modified:   digipeater.c
	modified:   direwolf.c
	modified:   direwolf.h
	modified:   dlq.c
	modified:   dlq.h
	modified:   doc/Going-beyond-9600-baud.pdf
	modified:   doc/Raspberry-Pi-APRS.pdf
	modified:   doc/Raspberry-Pi-SDR-IGate.pdf
	modified:   doc/User-Guide.pdf
	modified:   gen_packets.c
	modified:   hdlc_rec.c
	modified:   hdlc_send.c
	modified:   hdlc_send.h
	modified:   igate.c
	modified:   log.c
	modified:   log.h
	modified:   multi_modem.c
	modified:   pfilter.c
	modified:   pfilter.h
	modified:   ptt.c
	modified:   recv.c
	modified:   serial_port.c
	modified:   server.c
	modified:   server.h
	modified:   symbols-new.txt
	modified:   tocalls.txt
	modified:   tq.c
	modified:   tq.h
	modified:   waypoint.c
	modified:   xid.c
	new file:   xid.h
	modified:   xmit.c
This commit is contained in:
WB2OSZ 2016-11-20 14:58:51 -05:00
parent 2a08f33966
commit d85abe214f
48 changed files with 9609 additions and 567 deletions

View File

@ -1,6 +1,20 @@
# Revision History #
----------
## Version 1.4 -- Development snapshot D -- November 2016 ##
This is a snapshot at some semi-stable point in the development of the next version. It is not well tested. New features might be incomplete, poorly documented, and subject to change.
### New Features: ###
- AX.25 v2.2 connected mode. See chapter 10 of User Guide for details.
----------
## Version 1.4 -- Development snapshot C -- June 2016 ##

View File

@ -230,12 +230,12 @@ z := $(notdir ${CURDIR})
direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \
hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o \
fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
gen_tone.o audio.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \
gen_tone.o audio.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \
ptt.o beacon.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o \
dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o ax25_link.o \
misc.a geotranz.a
$(CC) -o $@ $^ $(LDFLAGS)
ifneq ($(enable_gpsd),)
@ -588,7 +588,7 @@ install-rpi : dw-start.sh
# Combine some unit tests into a single regression sanity check.
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates?
@ -708,6 +708,15 @@ pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o misc.a
rm pad2test
# Unit Test for XID frame encode/decode.
.PHONY: xidtest
xidtest : xid.c textcolor.o misc.a
$(CC) $(CFLAGS) -DXIDTEST -o $@ $^ $(LDFLAGS)
./xidtest
rm xidtest
# ----------------------------- Manual tests and experiments ---------------------------

View File

@ -221,14 +221,14 @@ z := $(notdir ${CURDIR})
# Main application.
direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beacon.o \
direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_link.o ax25_pad.o ax25_pad2.o beacon.o \
config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o demod_psk.o \
demod.o digipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \
demod.o digipeater.o cdigipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \
encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \
geotranz.a hdlc_rec.o hdlc_rec2.o hdlc_send.o igate.o kiss_frame.o \
kiss.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \
waypoint.o serial_port.o pfilter.o ptt.o rdq.o recv.o rrbb.o server.o \
symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xmit.o \
symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xid.o xmit.o \
dwgps.o dwgpsnmea.o mheard.o
$(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm

View File

@ -16,7 +16,7 @@
#
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest
# People say we need -mthreads option for threads to work properly.
@ -39,8 +39,6 @@ CFLAGS += -g
#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
# TODO: Development in progress. Don't try using yet.
#CFLAGS += -DNEW14
#
# Let's see impact of various optimization levels.
@ -86,16 +84,14 @@ demod_afsk.o : fsk_demod_state.h
demod_psk.o : fsk_demod_state.h
# later ax25_link.o
direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \
hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o ax25_pad2.o \
fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o \
gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o \
ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
dwgps.o dwgpsnmea.o dtime_now.o mheard.o \
dwgps.o dwgpsnmea.o dtime_now.o mheard.o ax25_link.o \
dw-icon.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
@ -260,7 +256,7 @@ strlcat.o : misc/strlcat.c
# Combine some unit tests into a single regression sanity check.
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates?
# Verify that single bit fixup increases the count.
@ -317,6 +313,7 @@ atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_psk.c demod_9600.c
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.c \
symbols.c tt_text.c textcolor.c telemetry.c dtime_now.o \
decode_aprs.o log.o \
misc.a regex.a
echo " " > tune.h
$(CC) $(CFLAGS) -o $@ $^
@ -412,10 +409,23 @@ pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o regex.a misc.a
./pad2test
rm pad2test.exe
# Unit Test for XID frame encode/decode.
.PHONY: xidtest
xidtest : xid.c textcolor.o misc.a
$(CC) $(CFLAGS) -DXIDTEST -o $@ $^
./xidtest
rm xidtest.exe
# ------------------------------ Other manual testing & experimenting -------------------------------
tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# For tweaking the demodulator.
demod.o : tune.h

View File

@ -42,6 +42,8 @@ Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf
- Includes separate raw packet decoder, decode_aprs.
- AX.25 v2.2 connected mode. (New in version 1.4.)
- Open source so you can see how it works and make your own modifications.
- Runs in 3 different environments:
@ -95,4 +97,4 @@ Here are some good places to share information:
- [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/)
The github "issues" section is for reporting software defects and enhancement requests. It is NOT a place to ask questions or have general discussions. Please use one of the locations above.

18
atest.c
View File

@ -743,6 +743,24 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
ax25_safe_print ((char *)pinfo, info_len, 0);
dw_printf ("\n");
#if 1 // temp experiment TODO: remove this.
#include "decode_aprs.h"
#include "log.h"
if (ax25_is_aprs(pp)) {
decode_aprs_t A;
decode_aprs (&A, pp, 0);
// Temp experiment to see how different systems set the RR bits in the source and destination.
// log_rr_bits (&A, pp);
}
#endif
ax25_delete (pp);
} /* end fake dlq_append */

14
audio.h
View File

@ -89,6 +89,14 @@ struct audio_s {
/* statistics reports. This is set by */
/* the "-a" option. 0 to disable feature. */
int xmit_error_rate; /* For testing purposes, we can generate frames with an invalid CRC */
/* to simulate corruption while going over the air. */
/* This is the probability, in per cent, of randomly corrupting it. */
/* Normally this is 0. 25 would mean corrupt it 25% of the time. */
int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */
/* Properties for each audio channel, common to receive and transmit. */
/* Can be different for each radio channel. */
@ -166,18 +174,20 @@ struct audio_s {
int passall; /* Allow thru even with bad CRC. */
/* Additional properties for transmit. */
/* Originally we had control outputs only for PTT. */
/* In version 1.2, we generalize this to allow others such as DCD. */
/* In version 1.4 we add CON for connected to another station. */
/* Index following structure by one of these: */
#define OCTYPE_PTT 0
#define OCTYPE_DCD 1
#define OCTYPE_FUTURE 2
#define OCTYPE_CON 2
#define NUM_OCTYPES 4 /* number of values above */
#define NUM_OCTYPES 4 /* number of values above. WHY IS THIS NOT 3? */
struct {

6523
ax25_link.c Normal file

File diff suppressed because it is too large Load Diff

82
ax25_link.h Normal file
View File

@ -0,0 +1,82 @@
/* ax25_link.h */
#ifndef AX25_LINK_H
#define AX25_LINK_H 1
#include "ax25_pad.h" // for AX25_MAX_INFO_LEN
#include "dlq.h" // for dlq_item_t
#include "config.h" // for struct misc_config_s
// Limits and defaults for parameters.
#define AX25_N1_PACLEN_MIN 1 // Max bytes in Information part of frame.
#define AX25_N1_PACLEN_DEFAULT 256 // some v2.0 implementations have 128
#define AX25_N1_PACLEN_MAX AX25_MAX_INFO_LEN // from ax25_pad.h
#define AX25_N2_RETRY_MIN 1 // Number of times to retry before giving up.
#define AX25_N2_RETRY_DEFAULT 10
#define AX25_N2_RETRY_MAX 15
#define AX25_T1V_FRACK_MIN 1 // Number of seconds to wait before retrying.
#define AX25_T1V_FRACK_DEFAULT 3 // KPC-3+ has 4. TM-D710A has 3.
#define AX25_T1V_FRACK_MAX 15
#define AX25_K_MAXFRAME_BASIC_MIN 1 // Window size - number of I frames to send before waiting for ack.
#define AX25_K_MAXFRAME_BASIC_DEFAULT 4
#define AX25_K_MAXFRAME_BASIC_MAX 7
#define AX25_K_MAXFRAME_EXTENDED_MIN 1
#define AX25_K_MAXFRAME_EXTENDED_DEFAULT 32
#define AX25_K_MAXFRAME_EXTENDED_MAX 63 // In theory 127 but I'm restricting as explained in SREJ handling.
// Call once at startup time.
void ax25_link_init (struct misc_config_s *pconfig);
// IMPORTANT:
// These functions must be called on a single thread, one at a time.
// The Data Link Queue (DLQ) is used to serialize events from multiple sources.
void dl_connect_request (dlq_item_t *E);
void dl_disconnect_request (dlq_item_t *E);
void dl_data_request (dlq_item_t *E);
void dl_register_callsign (dlq_item_t *E);
void dl_unregister_callsign (dlq_item_t *E);
void dl_client_cleanup (dlq_item_t *E);
void lm_data_indication (dlq_item_t *E);
void lm_channel_busy (dlq_item_t *E);
void dl_timer_expiry (void);
double ax25_link_get_next_timer_expiry (void);
#endif
/* end ax25_link.h */

View File

@ -24,6 +24,10 @@
*
* Purpose: Packet assembler and disasembler.
*
* This was written when I was only concerned about APRS which
* uses only UI frames. ax25_pad2.c, added years later, has
* functions for dealing with other types of frames.
*
* We can obtain AX.25 packets from different sources:
*
* (a) from an HDLC frame.
@ -77,11 +81,24 @@
* SSID = substation ID
* 0 = zero
*
* The AX.25 spec states that the RR bits should be 11 if not used.
* There are a couple documents talking about possible uses for APRS.
* I'm ignoring them for now.
* http://www.aprs.org/aprs12/preemptive-digipeating.txt
* http://www.aprs.org/aprs12/RR-bits.txt
*
* I don't recall why I originally intended to set the source/destination C bits both to 1.
* Reviewing this 5 years later, after spending more time delving into the
* AX.25 spec, I think it should be 1 for destination and 0 for source.
* In practice you see all four combinations being used by APRS stations
* and no one really cares about these two bits.
*
* The final octet of the Source has the form:
*
* C R R SSID 0, where,
*
* C = command/response = 1
* C = command/response = 1 (originally, now I think it should be 0 for source.)
* (Haven't gone back to check to see what code actually does.)
* R R = Reserved = 1 1
* SSID = substation ID
* 0 = zero (or 1 if no repeaters)
@ -165,8 +182,8 @@ char *strtok_r(char *str, const char *delim, char **saveptr);
#endif
#include "ax25_pad.h"
#include "textcolor.h"
#include "ax25_pad.h"
#include "fcs_calc.h"
/*
@ -236,7 +253,13 @@ packet_t ax25_new (void)
/*
* check for memory leak.
*/
if (new_count > delete_count + 100) {
// version 1.4 push up the threshold. We could have considerably more with connected mode.
//if (new_count > delete_count + 100) {
if (new_count > delete_count + 256) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Report to WB2OSZ - Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count);
#if AX25MEMDEBUG
@ -434,7 +457,7 @@ packet_t ax25_from_text (char *monitor, int strict)
this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK;
this_p->frame_data[14] = AX25_UI_FRAME;
this_p->frame_data[15] = AX25_NO_LAYER_3;
this_p->frame_data[15] = AX25_PID_NO_LAYER_3;
this_p->frame_len = 7 + 7 + 1 + 1;
this_p->num_addr = (-1);
@ -1517,6 +1540,39 @@ int ax25_get_first_not_repeated(packet_t this_p)
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_rr
*
* Purpose: Return the two reserved "RR" bits in the specified address field.
*
* Inputs: pp - Packet object.
*
* n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Returns: 0, 1, 2, or 3.
*
*------------------------------------------------------------------------------*/
int ax25_get_rr (packet_t this_p, int n)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (n >= 0 && n < this_p->num_addr);
if (n >= 0 && n < this_p->num_addr) {
return ((this_p->frame_data[n*7+6] & SSID_RR_MASK) >> SSID_RR_SHIFT);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_get_rr(%d), num_addr=%d\n", n, this_p->num_addr);
return (0);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_info
@ -1678,6 +1734,25 @@ double ax25_get_release_time (packet_t this_p)
}
/*------------------------------------------------------------------------------
*
* Name: ax25_set_modulo
*
* Purpose: Set modulo value for I and S frame sequence numbers.
*
*------------------------------------------------------------------------------*/
void ax25_set_modulo (packet_t this_p, int modulo)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
this_p->modulo = modulo;
}
/*------------------------------------------------------------------
*
@ -1793,12 +1868,9 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
*
* Inputs: this_p - pointer to packet object.
*
* modulo - We often need to know this because context is // TODO: remove this - return cr instead.
* required to know if control is 1 or 2 bytes.
*
* Outputs: desc - Text description such as "I frame" or
* "U frame SABME".
* Supply 16 bytes to be safe.
* Supply 40 bytes to be safe.
*
* cr - Command or response?
*
@ -1813,13 +1885,14 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
*------------------------------------------------------------------*/
// TODO: need someway to ensure caller allocated enough space.
#define DESC_SIZ 32
// Should pass in as parameter.
#define DESC_SIZ 40
ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns)
{
int c; // U frames are always one control byte.
int c2; // I & S frames can have second Control byte.
int c2 = 0; // I & S frames can have second Control byte.
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
@ -1835,6 +1908,41 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, in
strlcpy (desc, "Not AX.25", DESC_SIZ);
return (frame_not_AX25);
}
/*
* TERRIBLE HACK :-( for display purposes.
*
* I and S frames can have 1 or 2 control bytes but there is
* no good way to determine this without dipping into the data
* link state machine. Can we guess?
*
* S frames have no protocol id or information so if there is one
* more byte beyond the control field, we could assume there are
* two control bytes.
*
* For I frames, the protocol id will usually be 0xf0. If we find
* that as the first byte of the information field, it is probably
* the pid and not part of the information. Ditto for segments 0x08.
* Not fool proof but good enough for troubleshooting text out.
*
* If we have a link to the peer station, this will be set properly
* before it needs to be used for other reasons.
*
* Setting one of the RR bits (find reference!) is sounding better and better.
* It's in common usage so I should lobby to get that in the official protocol spec.
*/
if (this_p->modulo == 0 && (c & 3) == 1 && ax25_get_c2(this_p) != -1) {
this_p->modulo = modulo_128;
}
else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0xF0) {
this_p->modulo = modulo_128;
}
else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0x08) { // same for segments
this_p->modulo = modulo_128;
}
if (this_p->modulo == modulo_128) {
c2 = ax25_get_c2 (this_p);
}
@ -1868,7 +1976,9 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, in
*pf = (c >> 4) & 1;
*nr = (c >> 5) & 7;
}
snprintf (desc, DESC_SIZ, "I %s, n(s)= %d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf);
//snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf);
snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d, pid=0x%02x", cr_text, *ns, *nr, pf_text, *pf, ax25_get_pid(this_p));
return (frame_type_I);
}
else if ((c & 2) == 0) {
@ -1960,6 +2070,7 @@ static void hex_dump (unsigned char *p, int len)
}
/* Text description of control octet. */
// FIXME: this is wrong. It doesn't handle modulo 128.
// TODO: use ax25_frame_type() instead.
@ -2108,10 +2219,12 @@ int ax25_is_aprs (packet_t this_p)
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(0);
ctrl = ax25_get_control(this_p);
pid = ax25_get_pid(this_p);
is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_NO_LAYER_3;
is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_PID_NO_LAYER_3;
#if 0
text_color_set(DW_COLOR_ERROR);
@ -2120,6 +2233,41 @@ int ax25_is_aprs (packet_t this_p)
return (is_aprs);
}
/*------------------------------------------------------------------
*
* Function: ax25_is_null_frame
*
* Purpose: Is this packet structure empty?
*
* Inputs: this_p - pointer to packet object.
*
* Returns: True if frame data length is 0.
*
* Description: This is used when we want to wake up the
* transmit queue processing thread but don't
* want to transmit a frame.
*
*------------------------------------------------------------------*/
int ax25_is_null_frame (packet_t this_p)
{
int is_null;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
is_null = this_p->frame_len == 0;
#if 0
text_color_set(DW_COLOR_ERROR);
dw_printf ("ax25_is_null_frame(): is_null=%d\n", is_null);
#endif
return (is_null);
}
/*------------------------------------------------------------------
*
* Function: ax25_get_control
@ -2140,6 +2288,8 @@ int ax25_get_control (packet_t this_p)
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_control_offset(this_p)]);
}
@ -2151,10 +2301,19 @@ int ax25_get_c2 (packet_t this_p)
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_control_offset(this_p)+1]);
int offset2 = ax25_get_control_offset(this_p)+1;
if (offset2 < this_p->frame_len) {
return (this_p->frame_data[offset2]);
}
else {
return (-1); /* attempt to go beyond the end of frame. */
}
}
return (-1);
return (-1); /* not AX.25 */
}
@ -2184,6 +2343,8 @@ int ax25_get_pid (packet_t this_p)
// TODO: handle 2 control byte case.
// TODO: sanity check: is it I or UI frame?
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
}
@ -2372,7 +2533,8 @@ void ax25_safe_print (char *pstr, int len, int ascii_only)
if (len > MAXSAFE)
len = MAXSAFE;
while (len > 0 && *pstr != '\0')
//while (len > 0 && *pstr != '\0')
while (len > 0)
{
ch = *((unsigned char *)pstr);

View File

@ -64,8 +64,10 @@
*/
#define AX25_UI_FRAME 3 /* Control field value. */
#define AX25_NO_LAYER_3 0xf0 /* protocol ID */
#define AX25_PID_NO_LAYER_3 0xf0 /* protocol ID used for APRS */
#define AX25_PID_SEGMENTATION_FRAGMENT 0x08
#define AX25_PID_ESCAPE_CHARACTER 0xff
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
@ -159,9 +161,11 @@ typedef struct packet_s *packet_t;
typedef enum cmdres_e { cr_00 = 2, cr_cmd = 1, cr_res = 0, cr_11 = 3 } cmdres_t;
extern packet_t ax25_new (void);
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
extern packet_t ax25_new (void);
/*
* APRS always has one control octet of 0x03 but the more
@ -169,6 +173,8 @@ extern packet_t ax25_new (void);
* whether "modulo 128 operation" is in effect.
*/
//#define DEBUGX 1
static inline int ax25_get_control_offset (packet_t this_p)
{
return (this_p->num_addr*7);
@ -176,7 +182,29 @@ static inline int ax25_get_control_offset (packet_t this_p)
static inline int ax25_get_num_control (packet_t this_p)
{
return (this_p->modulo == 128 ? 2 : 1);
int c;
c = this_p->frame_data[ax25_get_control_offset(this_p)];
if ( (c & 0x01) == 0 ) { /* I xxxx xxx0 */
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is I frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
#endif
return ((this_p->modulo == 128) ? 2 : 1);
}
if ( (c & 0x03) == 1 ) { /* S xxxx xx01 */
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is S frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
#endif
return ((this_p->modulo == 128) ? 2 : 1);
}
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is U frame, always returns 1.\n", c);
#endif
return (1); /* U xxxx xx11 */
}
@ -202,11 +230,17 @@ static int ax25_get_num_pid (packet_t this_p)
c == 0x03 || c == 0x13) { /* UI 000x 0011 */
pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
if (pid == 0xff) {
#if DEBUGX
dw_printf ("ax25_get_num_pid, %02x is I or UI frame, pid = %02x, returns %d\n", c, pid, (pid==AX25_PID_ESCAPE_CHARACTER) ? 2 : 1);
#endif
if (pid == AX25_PID_ESCAPE_CHARACTER) {
return (2); /* pid 1111 1111 means another follows. */
}
return (1);
}
#if DEBUGX
dw_printf ("ax25_get_num_pid, %02x is neither I nor UI frame, returns 0\n", c);
#endif
return (0);
}
@ -225,7 +259,11 @@ static int ax25_get_num_pid (packet_t this_p)
static inline int ax25_get_info_offset (packet_t this_p)
{
return (ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p));
int offset = ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p);
#if DEBUGX
dw_printf ("ax25_get_info_offset, returns %d\n", offset);
#endif
return (offset);
}
static inline int ax25_get_num_info (packet_t this_p)
@ -249,7 +287,7 @@ typedef enum ax25_modulo_e { modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
typedef enum ax25_frame_type_e {
frame_type_I, // Information
frame_type_I = 0, // Information
frame_type_S_RR, // Receive Ready - System Ready To Receive
frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full
@ -268,6 +306,8 @@ typedef enum ax25_frame_type_e {
frame_type_U, // other Unnumbered, not used by AX.25.
frame_not_AX25 // Could not get control byte from frame.
// This must be last because value plus 1 is
// for the size of an array.
} ax25_frame_type_t;
@ -354,6 +394,8 @@ extern int ax25_get_heard(packet_t this_p);
extern int ax25_get_first_not_repeated(packet_t pp);
extern int ax25_get_rr (packet_t this_p, int n);
extern int ax25_get_info (packet_t pp, unsigned char **paddr);
extern void ax25_set_nextp (packet_t this_p, packet_t next_p);
@ -365,6 +407,8 @@ extern packet_t ax25_get_nextp (packet_t this_p);
extern void ax25_set_release_time (packet_t this_p, double release_time);
extern double ax25_get_release_time (packet_t this_p);
extern void ax25_set_modulo (packet_t this_p, int modulo);
extern void ax25_format_addrs (packet_t pp, char *);
extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
@ -374,6 +418,7 @@ extern ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *d
extern void ax25_hex_dump (packet_t this_p);
extern int ax25_is_aprs (packet_t pp);
extern int ax25_is_null_frame (packet_t this_p);
extern int ax25_get_control (packet_t this_p);
extern int ax25_get_c2 (packet_t this_p);

View File

@ -254,7 +254,7 @@ packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
if (t != 2) {
if (cr != t) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, cr is %d but must be %d.\n", __func__, cr, t);
dw_printf ("Internal error in %s: U frame, cr is %d but must be %d. ftype=%d\n", __func__, cr, t, ftype);
}
}
@ -270,7 +270,7 @@ packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
if (pid < 0 || pid == 0 || pid == 0xff) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, Invalid pid value 0x%02x.\n", __func__, pid);
pid = AX25_NO_LAYER_3;
pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
@ -303,7 +303,7 @@ packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[32];
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
@ -444,7 +444,7 @@ packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[32];
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
@ -573,9 +573,9 @@ packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
// or 0xff (which means more bytes follow).
if (pid < 0 || pid == 0 || pid == 0xff) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: I frame, Invalid pid value 0x%02x.\n", __func__, pid);
pid = AX25_NO_LAYER_3;
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Warning: Client application provided invalid PID value, 0x%02x, for I frame.\n", pid);
pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
@ -601,7 +601,7 @@ packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[32];
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;

325
cdigipeater.c Normal file
View File

@ -0,0 +1,325 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Name: cdigipeater.c
*
* Purpose: Act as an digital repeater for connected AX.25 mode.
* Similar digipeater.c is for APRS.
*
*
* Description: Decide whether the specified packet should
* be digipeated. Put my callsign in the digipeater field used.
*
* APRS and connected mode were two split into two
* separate files. Yes, there is duplicate code but they
* are significantly different and I thought it would be
* too confusing to munge them together.
*
* References: The Ax.25 protcol barely mentions digipeaters and
* and doesn't describe how they should work.
*
*------------------------------------------------------------------*/
#define CDIGIPEATER_C
#include "direwolf.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h> /* for isdigit, isupper */
#include "regex.h"
#include <sys/unistd.h>
#include "ax25_pad.h"
#include "cdigipeater.h"
#include "textcolor.h"
#include "tq.h"
#include "pfilter.h"
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str);
/*
* Keep pointer to configuration options.
* Set by cdigipeater_init and used later.
*/
static struct audio_s *save_audio_config_p;
static struct cdigi_config_s *save_cdigi_config_p;
/*
* Maintain count of packets digipeated for each combination of from/to channel.
*/
static int cdigi_count[MAX_CHANS][MAX_CHANS];
int cdigipeater_get_count (int from_chan, int to_chan) {
return (cdigi_count[from_chan][to_chan]);
}
/*------------------------------------------------------------------------------
*
* Name: cdigipeater_init
*
* Purpose: Initialize with stuff from configuration file.
*
* Inputs: p_audio_config - Configuration for audio channels.
*
* p_cdigi_config - Connected Digipeater configuration details.
*
* Outputs: Save pointers to configuration for later use.
*
* Description: Called once at application startup time.
*
*------------------------------------------------------------------------------*/
void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config)
{
save_audio_config_p = p_audio_config;
save_cdigi_config_p = p_cdigi_config;
}
/*------------------------------------------------------------------------------
*
* Name: cdigipeater
*
* Purpose: Re-transmit packet if it matches the rules.
*
* Inputs: chan - Radio channel where it was received.
*
* pp - Packet object.
*
* Returns: None.
*
*------------------------------------------------------------------------------*/
void cdigipeater (int from_chan, packet_t pp)
{
int to_chan;
if ( from_chan < 0 || from_chan >= MAX_CHANS || ( ! save_audio_config_p->achan[from_chan].valid) ) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
return;
}
/*
* First pass: Look at packets being digipeated to same channel.
*
* There was a reason for two passes for APRS.
* Might not have a benefit here.
*/
for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
if (save_cdigi_config_p->enabled[from_chan][to_chan]) {
if (to_chan == from_chan) {
packet_t result;
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
}
}
}
}
/*
* Second pass: Look at packets being digipeated to different channel.
*/
for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
if (save_cdigi_config_p->enabled[from_chan][to_chan]) {
if (to_chan != from_chan) {
packet_t result;
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
}
}
}
}
} /* end cdigipeater */
/*------------------------------------------------------------------------------
*
* Name: cdigipeat_match
*
* Purpose: A simple digipeater for connected mode AX.25.
*
* Input: pp - Pointer to a packet object.
*
* mycall_rec - Call of my station, with optional SSID,
* associated with the radio channel where the
* packet was received.
*
* mycall_xmit - Call of my station, with optional SSID,
* associated with the radio channel where the
* packet is to be transmitted. Could be the same as
* mycall_rec or different.
*
* alias - Compiled pattern for my station aliases.
* Could be NULL if no aliases.
*
* to_chan - Channel number that we are transmitting to.
*
* filter_str - Filter expression string or NULL.
*
* Returns: Packet object for transmission or NULL.
* The original packet is not modified. The caller is responsible for freeing it.
* We make a copy and return that modified copy!
* This is very important because we could digipeat from one channel to many.
*
* Description: The packet will be digipeated if the next unused digipeater
* field matches one of the following:
*
* - mycall_rec
* - alias list
*
* APRS digipeating drops duplicates within 30 seconds but we don't do that here.
*
*------------------------------------------------------------------------------*/
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str)
{
int r;
char repeater[AX25_MAX_ADDR_LEN];
int err;
char err_msg[100];
/*
* First check if filtering has been configured.
*/
if (filter_str != NULL) {
if (pfilter(from_chan, to_chan, filter_str, pp, 0) != 1) {
// Take out debug message?
// Actually it turns out to be useful.
// Maybe add a quiet option to suppress it.
//#if DEBUG
text_color_set(DW_COLOR_INFO);
dw_printf ("Packet was rejected for digipeating from channel %d to %d by cfilter: %s\n", from_chan, to_chan, filter_str);
//#endif
return(NULL);
}
}
/*
* Find the first repeater station which doesn't have "has been repeated" set.
*
* r = index of the address position in the frame.
*/
r = ax25_get_first_not_repeated(pp);
if (r < AX25_REPEATER_1) {
return (NULL); // Nothing to do.
}
ax25_get_addr_with_ssid(pp, r, repeater);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("First unused digipeater is %s\n", repeater);
#endif
/*
* First check for explicit use of my call.
* Note that receive and transmit channels could have different callsigns.
*/
if (strcmp(repeater, mycall_rec) == 0) {
packet_t result;
result = ax25_dup (pp);
assert (result != NULL);
/* If using multiple radio channels, they could have different calls. */
ax25_set_addr (result, r, mycall_xmit);
ax25_set_h (result, r);
return (result);
}
/*
* If we have an alias match, substitute MYCALL.
*/
if (alias != NULL) {
err = regexec(alias,repeater,0,NULL,0);
if (err == 0) {
packet_t result;
result = ax25_dup (pp);
assert (result != NULL);
ax25_set_addr (result, r, mycall_xmit);
ax25_set_h (result, r);
return (result);
}
else if (err != REG_NOMATCH) {
regerror(err, alias, err_msg, sizeof(err_msg));
text_color_set (DW_COLOR_ERROR);
dw_printf ("%s\n", err_msg);
}
}
/*
* Don't repeat it if we get here.
*/
return (NULL);
} /* end cdigipeat_match */
/* end cdigipeater.c */

59
cdigipeater.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef CDIGIPEATER_H
#define CDIGIPEATER_H 1
#include "regex.h"
#include "direwolf.h" /* for MAX_CHANS */
#include "ax25_pad.h" /* for packet_t */
#include "audio.h" /* for radio channel properties */
/*
* Information required for Connected mode digipeating.
*
* The configuration file reader fills in this information
* and it is passed to cdigipeater_init at application start up time.
*/
struct cdigi_config_s {
/*
* Rules for each of the [from_chan][to_chan] combinations.
*/
regex_t alias[MAX_CHANS][MAX_CHANS];
int enabled[MAX_CHANS][MAX_CHANS];
char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
// NULL or optional Packet Filter strings such as "t/m".
// Notice the size of arrays is one larger than normal.
// That extra position is for the IGate.
};
/*
* Call once at application start up time.
*/
extern void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config);
/*
* Call this for each packet received.
* Suitable packets will be queued for transmission.
*/
extern void cdigipeater (int from_chan, packet_t pp);
/* Make statistics available. */
int cdigipeater_get_count (int from_chan, int to_chan);
#endif
/* end cdigipeater.h */

366
config.c
View File

@ -53,6 +53,7 @@
#include "textcolor.h"
#include "audio.h"
#include "digipeater.h"
#include "cdigipeater.h"
#include "config.h"
#include "aprs_tt.h"
#include "igate.h"
@ -60,6 +61,7 @@
#include "symbols.h"
#include "xmit.h"
#include "tt_text.h"
#include "ax25_link.h"
// geotranz
@ -680,7 +682,9 @@ static char *split (char *string, int rest_of_line)
*
* Outputs: p_audio_config - Radio channel parameters stored here.
*
* p_digi_config - Digipeater configuration stored here.
* p_digi_config - APRS Digipeater configuration stored here.
*
* p_cdigi_config - Connected Digipeater configuration stored here.
*
* p_tt_config - APRStt stuff.
*
@ -703,6 +707,7 @@ static char *split (char *string, int rest_of_line)
void config_init (char *fname, struct audio_s *p_audio_config,
struct digi_config_s *p_digi_config,
struct cdigi_config_s *p_cdigi_config,
struct tt_config_s *p_tt_config,
struct igate_config_s *p_igate_config,
struct misc_config_s *p_misc_config)
@ -789,6 +794,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
memset (p_digi_config, 0, sizeof(struct digi_config_s));
p_digi_config->dedupe_time = DEFAULT_DEDUPE;
memset (p_cdigi_config, 0, sizeof(struct cdigi_config_s));
memset (p_tt_config, 0, sizeof(struct tt_config_s));
p_tt_config->gateway_enabled = 0;
@ -865,6 +871,19 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strlcpy (p_misc_config->logdir, "", sizeof(p_misc_config->logdir));
/* connected mode. */
p_misc_config->frack = AX25_T1V_FRACK_DEFAULT; /* Number of seconds to wait for ack to transmission. */
p_misc_config->retry = AX25_N2_RETRY_DEFAULT; /* Number of times to retry before giving up. */
p_misc_config->paclen = AX25_N1_PACLEN_DEFAULT; /* Max number of bytes in information part of frame. */
p_misc_config->maxframe_basic = AX25_K_MAXFRAME_BASIC_DEFAULT; /* Max frames to send before ACK. mod 8 "Window" size. */
p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */
p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 2; /* Max SABME before falling back to SABM. */
/*
* Try to extract options from a file.
@ -1568,6 +1587,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* PTT - Push To Talk signal line.
* DCD - Data Carrier Detect indicator.
* CON - Connected to another station indicator.
*
* xxx serial-port [-]rts-or-dtr [ [-]rts-or-dtr ]
* xxx GPIO [-]gpio-num
@ -1582,7 +1602,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* Applies to most recent CHANNEL command.
*/
else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0) {
else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0 || strcasecmp(t, "CON") == 0) {
int ot;
char otname[8];
@ -1595,8 +1615,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strlcpy (otname, "DCD", sizeof(otname));
}
else {
ot = OCTYPE_FUTURE;
strlcpy (otname, "FUTURE", sizeof(otname));
ot = OCTYPE_CON;
strlcpy (otname, "CON", sizeof(otname));
}
t = split(NULL,0);
@ -1784,7 +1804,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
} /* end of second serial port control line. */
} /* end of serial port case. */
} /* end of PTT */
} /* end of PTT, DCD, CON */
/*
* INPUTS
@ -1984,14 +2004,14 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* ==================== Digipeater parameters ====================
* ==================== APRS Digipeater parameters ====================
*/
/*
* DIGIPEAT from-chan to-chan alias-pattern wide-pattern [ OFF|DROP|MARK|TRACE ]
*/
else if (strcasecmp(t, "digipeat") == 0) {
else if (strcasecmp(t, "DIGIPEAT") == 0 || strcasecmp(t, "DIGIPEATER") == 0) {
int from_chan, to_chan;
int e;
char message[100];
@ -2175,7 +2195,82 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* ==================== Packet Filtering for digipeater or IGate ====================
* ==================== Connected Digipeater parameters ====================
*/
/*
* CDIGIPEAT from-chan to-chan [ alias-pattern ]
*/
else if (strcasecmp(t, "CDIGIPEAT") == 0 || strcasecmp(t, "CDIGIPEATER") == 0) {
int from_chan, to_chan;
int e;
char message[100];
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
continue;
}
from_chan = atoi(t);
if (from_chan < 0 || from_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[from_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
continue;
}
to_chan = atoi(t);
if (to_chan < 0 || to_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[to_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
t = split(NULL,0);
if (t != NULL) {
e = regcomp (&(p_cdigi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
if (e != 0) {
regerror (e, &(p_cdigi_config->alias[from_chan][to_chan]), message, sizeof(message));
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n",
line, message);
continue;
}
t = split(NULL,0);
}
p_cdigi_config->enabled[from_chan][to_chan] = 1;
if (t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t);
}
}
/*
* ==================== Packet Filtering for APRS digipeater or IGate ====================
*/
/*
@ -2252,6 +2347,74 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* ==================== Packet Filtering for connected digipeater ====================
*/
/*
* CFILTER from-chan to-chan filter_specification_expression
*/
else if (strcasecmp(t, "CFILTER") == 0) {
int from_chan, to_chan;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
continue;
}
from_chan = isdigit(*t) ? atoi(t) : -999;
if (from_chan < 0 || from_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[from_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
continue;
}
to_chan = isdigit(*t) ? atoi(t) : -999;
if (to_chan < 0 || to_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[to_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
t = split(NULL,1); /* Take rest of line including spaces. */
if (t == NULL) {
t = " "; /* Empty means permit nothing. */
}
p_cdigi_config->filter_str[from_chan][to_chan] = strdup(t);
//TODO1.2: Do a test run to see errors now instead of waiting.
}
/*
* ==================== APRStt gateway ====================
*/
@ -4163,6 +4326,154 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_misc_config->sb_configured = 1;
}
/*
* ==================== AX.25 connected mode ====================
*/
/*
* FRACK n - Number of seconds to wait for ack to transmission.
*/
else if (strcasecmp(t, "FRACK") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for FRACK.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_T1V_FRACK_MIN && n <= AX25_T1V_FRACK_MAX) {
p_misc_config->frack = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid FRACK time. Using default %d.\n", line, p_misc_config->frack);
}
}
/*
* RETRY n - Number of times to retry before giving up.
*/
else if (strcasecmp(t, "RETRY") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for RETRY.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_N2_RETRY_MIN && n <= AX25_N2_RETRY_MAX) {
p_misc_config->retry = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid RETRY number. Using default %d.\n", line, p_misc_config->retry);
}
}
/*
* PACLEN n - Maximum number of bytes in information part.
*/
else if (strcasecmp(t, "PACLEN") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for PACLEN.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_N1_PACLEN_MIN && n <= AX25_N1_PACLEN_MAX) {
p_misc_config->paclen = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid PACLEN value. Using default %d.\n", line, p_misc_config->paclen);
}
}
/*
* MAXFRAME n - Max frames to send before ACK. mod 8 "Window" size.
*
* Window size would make more sense but everyone else calls it MAXFRAME.
*/
else if (strcasecmp(t, "MAXFRAME") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for MAXFRAME.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_K_MAXFRAME_BASIC_MIN && n <= AX25_K_MAXFRAME_BASIC_MAX) {
p_misc_config->maxframe_basic = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid MAXFRAME value outside range of %d to %d. Using default %d.\n",
line, AX25_K_MAXFRAME_BASIC_MIN, AX25_K_MAXFRAME_BASIC_MAX, p_misc_config->maxframe_basic);
}
}
/*
* EMAXFRAME n - Max frames to send before ACK. mod 128 "Window" size.
*/
else if (strcasecmp(t, "EMAXFRAME") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for EMAXFRAME.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_K_MAXFRAME_EXTENDED_MIN && n <= AX25_K_MAXFRAME_EXTENDED_MAX) {
p_misc_config->maxframe_extended = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid EMAXFRAME value outside of range %d to %d. Using default %d.\n",
line, AX25_K_MAXFRAME_EXTENDED_MIN, AX25_K_MAXFRAME_EXTENDED_MAX, p_misc_config->maxframe_extended);
}
}
/*
* MAXV22 n - Max number of SABME sent before trying SABM.
*/
else if (strcasecmp(t, "MAXV22") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for MAXV22.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n <= AX25_N2_RETRY_MAX) {
p_misc_config->maxv22 = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid MAXV22 number. Will use half of RETRY.\n", line);
}
}
/*
* Invalid command.
*/
@ -4189,6 +4500,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
for (i=0; i<MAX_CHANS; i++) {
for (j=0; j<MAX_CHANS; j++) {
/* APRS digipeating. */
if (p_digi_config->enabled[i][j]) {
if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 ||
@ -4221,6 +4534,39 @@ void config_init (char *fname, struct audio_s *p_audio_config,
//p_digi_config->enabled[i][j] = 0;
}
}
/* Connected mode digipeating. */
if (p_cdigi_config->enabled[i][j]) {
if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 ||
strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i);
p_cdigi_config->enabled[i][j] = 0;
}
if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 ||
strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i);
p_cdigi_config->enabled[i][j] = 0;
}
b = 0;
for (k=0; k<p_misc_config->num_beacons; k++) {
if (p_misc_config->beacon[k].sendto_chan == j) b++;
}
if (b == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", j);
// It's a recommendation, not a requirement.
}
}
}
if (p_audio_config->achan[i].valid && strlen(p_igate_config->t2_login) > 0) {
@ -4243,6 +4589,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
if (p_misc_config->maxv22 < 0) {
p_misc_config->maxv22 = p_misc_config->retry / 2;
}
} /* end config_init */

View File

@ -15,6 +15,7 @@
#include "audio.h" /* for struct audio_s */
#include "digipeater.h" /* for struct digi_config_s */
#include "cdigipeater.h" /* for struct cdigi_config_s */
#include "aprs_tt.h" /* for struct tt_config_s */
#include "igate.h" /* for struct igate_config_s */
@ -76,6 +77,25 @@ struct misc_config_s {
int sb_turn_angle; /* degrees */
int sb_turn_slope; /* degrees * MPH */
// AX.25 connected mode.
int frack; /* Number of seconds to wait for ack to transmission. */
int retry; /* Number of times to retry before giving up. */
int paclen; /* Max number of bytes in information part of frame. */
int maxframe_basic; /* Max frames to send before ACK. mod 8 "Window" size. */
int maxframe_extended; /* Max frames to send before ACK. mod 128 "Window" size. */
int maxv22; /* Maximum number of unanswered SABME frames sent before */
/* switching to SABM. This is to handle the case of an old */
/* TNC which simply ignores SABME rather than replying with FRMR. */
// Beacons.
int num_beacons; /* Number of beacons defined. */
@ -166,6 +186,7 @@ struct misc_config_s {
extern void config_init (char *fname, struct audio_s *p_modem,
struct digi_config_s *digi_config,
struct cdigi_config_s *cdigi_config,
struct tt_config_s *p_tt_config,
struct igate_config_s *p_igate_config,
struct misc_config_s *misc_config);

View File

@ -23,6 +23,7 @@
* Name: digipeater.c
*
* Purpose: Act as an APRS digital repeater.
* Similar cdigipeater.c is for connected mode.
*
*
* Description: Decide whether the specified packet should
@ -255,6 +256,10 @@ void digipeater (int from_chan, packet_t pp)
*
*------------------------------------------------------------------------------*/
#define OBSOLETE14 1
#ifndef OBSOLETE14
static char *dest_ssid_path[16] = {
"", /* Use VIA path */
"WIDE1-1",
@ -272,6 +277,7 @@ static char *dest_ssid_path[16] = {
"WIDE2-2", /* South */
"WIDE2-2", /* East */
"WIDE2-2" }; /* West */
#endif
static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
@ -290,7 +296,7 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
if (filter_str != NULL) {
if (pfilter(from_chan, to_chan, filter_str, pp) != 1) {
if (pfilter(from_chan, to_chan, filter_str, pp, 1) != 1) {
// TODO1.2: take out debug message
// Actually it turns out to be useful.
@ -322,11 +328,15 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
* Otherwise we don't want to modify the input because this could be called multiple times.
*/
#ifndef OBSOLETE14 // Took it out in 1.4
if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
ax25_set_ssid(pp, AX25_DESTINATION, 0);
/* Continue with general case, below. */
}
#endif
/*
* Find the first repeater station which doesn't have "has been repeated" set.
@ -795,11 +805,15 @@ int main (int argc, char *argv[])
/*
* Change destination SSID to normal digipeater if none specified.
* Change destination SSID to normal digipeater if none specified. (Obsolete, removed.)
*/
test ( "W1ABC>TEST-3:",
"W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
test ( "W1ABC>TEST-3:",
#ifndef OBSOLETE14
"W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
#else
"");
#endif
test ( "W1DEF>TEST-3,WIDE2-2:",
"W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");

View File

@ -88,6 +88,7 @@
#include "hdlc_rec.h"
#include "hdlc_rec2.h"
#include "ax25_pad.h"
#include "xid.h"
#include "decode_aprs.h"
#include "textcolor.h"
#include "server.h"
@ -97,6 +98,7 @@
#include "waypoint.h"
#include "gen_tone.h"
#include "digipeater.h"
#include "cdigipeater.h"
#include "tq.h"
#include "xmit.h"
#include "ptt.h"
@ -112,6 +114,7 @@
#include "recv.h"
#include "morse.h"
#include "mheard.h"
#include "ax25_link.h"
//static int idx_decoded = 0;
@ -158,7 +161,8 @@ static void __cpuid(int cpuinfo[4], int infotype){
static struct audio_s audio_config;
static struct tt_config_s tt_config;
struct digi_config_s digi_config;
//struct digi_config_s digi_config;
//struct cdigi_config_s cdigi_config;
static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */
@ -181,6 +185,7 @@ int main (int argc, char *argv[])
int xmit_calibrate_option = 0;
int enable_pseudo_terminal = 0;
struct digi_config_s digi_config;
struct cdigi_config_s cdigi_config;
struct igate_config_s igate_config;
int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */
char P_opt[16];
@ -199,6 +204,8 @@ 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 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. */
strlcpy(l_opt, "", sizeof(l_opt));
strlcpy(P_opt, "", sizeof(P_opt));
@ -247,7 +254,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\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);
#if defined(ENABLE_GPSD) || defined(USE_HAMLIB)
@ -332,7 +339,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:",
c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:E:",
long_options, &option_index);
if (c == -1)
break;
@ -518,6 +525,27 @@ int main (int argc, char *argv[])
exit (0);
break;
case 'E': /* -E Error rate (%) for corrupting frames. */
/* Just a number is transmit. Precede by R for receive. */
if (*optarg == 'r' || *optarg == 'R') {
E_rx_opt = atoi(optarg+1);
if (E_rx_opt < 1 || E_rx_opt > 99) {
text_color_set(DW_COLOR_ERROR);
dw_printf("-ER must be in range of 1 to 99.\n");
E_rx_opt = 10;
}
}
else {
E_tx_opt = atoi(optarg);
if (E_tx_opt < 1 || E_tx_opt > 99) {
text_color_set(DW_COLOR_ERROR);
dw_printf("-E must be in range of 1 to 99.\n");
E_tx_opt = 10;
}
}
break;
default:
/* Should not be here. */
@ -552,7 +580,7 @@ int main (int argc, char *argv[])
symbols_init ();
config_init (config_file, &audio_config, &digi_config, &tt_config, &igate_config, &misc_config);
config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config);
if (r_opt != 0) {
audio_config.adev[0].samples_per_sec = r_opt;
@ -621,6 +649,12 @@ int main (int argc, char *argv[])
audio_config.achan[0].decimate = D_opt;
}
// temp - only xmit errors.
audio_config.xmit_error_rate = E_tx_opt;
audio_config.recv_error_rate = E_rx_opt;
if (strlen(l_opt) > 0) {
strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir));
}
@ -711,6 +745,8 @@ int main (int argc, char *argv[])
*/
digipeater_init (&audio_config, &digi_config);
igate_init (&audio_config, &igate_config, &digi_config, d_i_opt);
cdigipeater_init (&audio_config, &cdigi_config);
ax25_link_init (&misc_config);
/*
* Provide the AGW & KISS socket interfaces for use by a client application.
@ -890,7 +926,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
text_color_set(DW_COLOR_REC);
}
else {
text_color_set(DW_COLOR_DEBUG);
text_color_set(DW_COLOR_DECODED);
}
if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) {
@ -914,26 +950,41 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
if ( ! ax25_is_aprs(pp)) {
ax25_frame_type_t ftype;
cmdres_t cr;
char desc[32];
char desc[80];
int pf;
int nr;
int ns;
ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
(void)ftype;
/* Could change by 1, since earlier call, if we guess at modulo 128. */
info_len = ax25_get_info (pp, &pinfo);
dw_printf ("(%s)", desc);
if (ftype == frame_type_U_XID) {
struct xid_param_s param;
char info2text[100];
xid_parse (pinfo, info_len, &param, info2text, sizeof(info2text));
dw_printf (" %s\n", info2text);
}
else {
ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
dw_printf ("\n");
}
}
else {
// for APRS we generally want to display non-ASCII to see UTF-8.
// for other, probably want to restrict to ASCII only because we are
// more likely to have compressed data than UTF-8 text.
// TODO: Might want to use d_u_opt for transmitted frames too.
ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
dw_printf ("\n");
}
// for APRS we generally want to display non-ASCII to see UTF-8.
// for other, probably want to restrict to ASCII only because we are
// more likely to have compressed data than UTF-8 text.
// TODO: Might want to use d_u_opt for transmitted frames too.
ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
dw_printf ("\n");
// Also display in pure ASCII if non-ASCII characters and "-d u" option specified.
@ -992,6 +1043,9 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
log_write (chan, &A, pp, alevel, retries);
// temp experiment.
//log_rr_bits (&A, pp);
// Add to list of stations heard.
mheard_save (chan, &A, pp, alevel, retries);
@ -1052,23 +1106,30 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
/*
* Note that the digipeater function can modify the packet in place so
* this is the last thing we should do with it.
* Again, use only those with correct CRC; We don't want to spread corrupted data!
* Single bit change appears to be safe from observations so far but be cautious.
* APRS digipeater.
* Use only those with correct CRC; We don't want to spread corrupted data!
*/
if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
digipeater (chan, pp);
}
/*
* Connected mode digipeater.
* Use only those with correct CRC.
*/
if (retries == RETRY_NONE) {
cdigipeater (chan, pp);
}
}
ax25_delete (pp);
} /* end app_process_rec_packet */
/* Process control C and window close events. */
#if __WIN32__

View File

@ -233,6 +233,11 @@ char *strcasestr(const char *S, const char *FIND);
#else // Use our own copy
// These prevent /usr/include/gps.h from providing its own definition.
#define HAVE_STRLCAT 1
#define HAVE_STRLCPY 1
#define DEBUG_STRL 1
#if DEBUG_STRL

351
dlq.c
View File

@ -24,7 +24,7 @@
*
* Purpose: Received frame queue.
*
* Description: In earlierversions, the main thread read from the
* Description: In earlier versions, the main thread read from the
* audio device and performed the receive demodulation/decoding.
*
* Since version 1.2 we have a separate receive thread
@ -32,7 +32,7 @@
* received frames from all channels and process them
* serially.
*
* In version 1.4, other types of events go into this
* In version 1.4, other types of events also go into this
* queue and we use it to drive the data link state machine.
*
*---------------------------------------------------------------*/
@ -85,10 +85,13 @@ static int was_init = 0; /* was initialization performed? */
static void append_to_queue (struct dlq_item_s *pnew);
static volatile int s_new_count = 0; /* To detect memory leak. */
static volatile int s_delete_count = 0; // TODO: need to test.
static volatile int s_new_count = 0; /* To detect memory leak for queue items. */
static volatile int s_delete_count = 0; // TODO: need to test.
static volatile int s_cdata_new_count = 0; /* To detect memory leak for connected mode data. */
static volatile int s_cdata_delete_count = 0; // TODO: need to test.
/*-------------------------------------------------------------------
@ -109,8 +112,6 @@ static volatile int s_delete_count = 0; // TODO: need to test.
void dlq_init (void)
{
//int c, p;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_init ( )\n");
@ -189,6 +190,7 @@ void dlq_init (void)
* Normally this was received over the radio but we can create
* our own from APRStt or beaconing.
*
* This would correspond to PH-DATA Indication in the AX.25 protocol spec.
*
* Inputs: chan - Channel, 0 is first.
*
@ -244,11 +246,17 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
}
#endif
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
if (s_new_count > s_delete_count + 50) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: DLQ memory leak, new=%d, delete=%d\n", s_new_count, s_delete_count);
}
pnew->nextp = NULL;
pnew->type = DLQ_REC_FRAME;
pnew->chan = chan;
@ -458,6 +466,7 @@ static void append_to_queue (struct dlq_item_s *pnew)
* pid - Protocol ID for data. Normally 0xf0 but the API
* allows the client app to use something non-standard
* for special situations.
* TODO: remove this. PID is only for I and UI frames.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
@ -486,7 +495,6 @@ void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num
memcpy (pnew->addrs, addrs, sizeof(pnew->addrs));
pnew->num_addr = num_addr;
pnew->client = client;
pnew->pid = pid;
/* Put it into queue. */
@ -603,9 +611,10 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n
memcpy (pnew->addrs, addrs, sizeof(pnew->addrs));
pnew->num_addr = num_addr;
pnew->client = client;
pnew->pid = pid;
/* TODO: haven't thought about user data yet. */
/* Attach the transmit data. */
pnew->txdata = cdata_new(pid,xdata_ptr,xdata_len);
/* Put it into queue. */
@ -614,6 +623,195 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n
} /* end dlq_xmit_data_request */
/*-------------------------------------------------------------------
*
* Name: dlq_register_callsign
* dlq_unregister_callsign
*
* Purpose: Register callsigns that we will recognize for incoming connection requests.
*
* Inputs: addrs - Source (owncall), destination (peercall),
* and possibly digipeaters.
*
* chan - Channel, 0 is first.
*
* client - Client application instance.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
*
* Description: The data link state machine does not use MYCALL from the APRS configuration.
* For outgoing frames, the client supplies the source callsign.
* For incoming connection requests, we need to know what address(es) to respond to.
*
* Note that one client application can register multiple callsigns for
* multiple channels.
* Different clients can register different different addresses on the same channel.
*
*--------------------------------------------------------------------*/
void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
{
struct dlq_item_s *pnew;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_register_callsign (%s, chan=%d, client=%d)\n", addr, chan, client);
#endif
assert (chan >= 0 && chan < MAX_CHANS);
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->type = DLQ_REGISTER_CALLSIGN;
pnew->chan = chan;
strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN);
pnew->num_addr = 1;
pnew->client = client;
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_register_callsign */
void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
{
struct dlq_item_s *pnew;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_unregister_callsign (%s, chan=%d, client=%d)\n", addr, chan, client);
#endif
assert (chan >= 0 && chan < MAX_CHANS);
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->type = DLQ_UNREGISTER_CALLSIGN;
pnew->chan = chan;
strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN);
pnew->num_addr = 1;
pnew->client = client;
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_unregister_callsign */
/*-------------------------------------------------------------------
*
* Name: dlq_channel_busy
*
* Purpose: Inform data link state machine about activity on the radio channel.
*
* Inputs: chan - Radio channel number.
*
* activity - OCTYPE_PTT or OCTYPE_DCD, as defined in audio.h.
* Other values will be discarded.
*
* status - 1 for active or 0 for quiet.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
*
* Description: Notify the link state machine about changes in carrier detect
* and our transmitter.
* This is needed for pausing some of our timers. For example,
* if we transmit a frame and expect a response in 3 seconds, that
* might be delayed because someone else is using the channel.
*
*--------------------------------------------------------------------*/
void dlq_channel_busy (int chan, int activity, int status)
{
struct dlq_item_s *pnew;
if (activity == OCTYPE_PTT || activity == OCTYPE_DCD) {
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_channel_busy (...)\n");
#endif
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->type = DLQ_CHANNEL_BUSY;
pnew->chan = chan;
pnew->activity = activity;
pnew->status = status;
/* Put it into queue. */
append_to_queue (pnew);
}
} /* end dlq_channel_busy */
/*-------------------------------------------------------------------
*
* Name: dlq_client_cleanup
*
* Purpose: Client application has disappeared.
* i.e. The TCP connection has been broken.
*
* Inputs: client - Client application instance.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
*
* Description: Notify the link state machine that given client has gone away.
* Clean up all information related to that client application.
*
*--------------------------------------------------------------------*/
void dlq_client_cleanup (int client)
{
struct dlq_item_s *pnew;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_client_cleanup (...)\n");
#endif
// assert (client >= 0 && client < MAX_NET_CLIENTS);
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
// All we care about is the client number.
pnew->type = DLQ_CLIENT_CLEANUP;
pnew->client = client;
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_client_cleanup */
/*-------------------------------------------------------------------
*
* Name: dlq_wait_while_empty
@ -817,12 +1015,145 @@ struct dlq_item_s *dlq_remove (void)
void dlq_delete (struct dlq_item_s *pitem)
{
if (pitem == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: dlq_delete() given NULL pointer.\n");
return;
}
s_delete_count++;
if (pitem->pp != NULL) ax25_delete (pitem->pp)
if (pitem->pp != NULL) {
ax25_delete (pitem->pp);
pitem->pp = NULL;
}
if (pitem->txdata != NULL) {
cdata_delete (pitem->txdata);
pitem->txdata = NULL;
}
free (pitem);
} /* end dlq_delete */
/*-------------------------------------------------------------------
*
* Name: cdata_new
*
* Purpose: Allocate blocks of data for sending and receiving connected data.
*
* Inputs: pid - protocol id.
* data - pointer to data. Can be NULL for segment reassembler.
* len - length of data.
*
* Returns: Structure with a copy of the data.
*
* Description: The flow goes like this:
*
* Client application extablishes a connection with another station.
* Client application calls "dlq_xmit_data_request."
* A copy of the data is made with this function and attached to the queue item.
* The txdata block is attached to the appropriate link state machine.
* At the proper time, it is transmitted in an I frame.
* It needs to be kept around in case it needs to be retransmitted.
* When no longer needed, it is freed with cdata_delete.
*
*--------------------------------------------------------------------*/
cdata_t *cdata_new (int pid, char *data, int len)
{
int size;
cdata_t *cdata;
s_cdata_new_count++;
/* Round up the size to the next 128 bytes. */
/* The theory is that a smaller number of unique sizes might be */
/* beneficial for memory fragmentation and garbage collection. */
size = ( len + 127 ) & ~0x7f;
cdata = malloc ( sizeof(cdata_t) + size );
cdata->magic = TXDATA_MAGIC;
cdata->next = NULL;
cdata->pid = pid;
cdata->size = size;
cdata->len = len;
assert (len >= 0 && len <= size);
if (data == NULL) {
memset (cdata->data, '?', size);
}
else {
memcpy (cdata->data, data, len);
}
return (cdata);
} /* end cdata_new */
/*-------------------------------------------------------------------
*
* Name: cdata_delete
*
* Purpose: Release storage used by a connected data block.
*
* Inputs: cdata - Pointer to a data block.
*
*--------------------------------------------------------------------*/
void cdata_delete (cdata_t *cdata)
{
if (cdata == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: cdata_delete() given NULL pointer.\n");
return;
}
if (cdata->magic != TXDATA_MAGIC) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: cdata_delete() given corrupted data.\n");
return;
}
s_cdata_delete_count++;
cdata->magic = 0;
free (cdata);
} /* end cdata_delete */
/*-------------------------------------------------------------------
*
* Name: cdata_check_leak
*
* Purpose: Check for memory leak of cdata items.
*
* Description: This is called when we expect no outstanding allocations.
*
*--------------------------------------------------------------------*/
void cdata_check_leak (void)
{
if (s_cdata_delete_count != s_cdata_new_count) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal Error, %s, new=%d, delete=%d\n", __func__, s_cdata_new_count, s_cdata_delete_count);
}
} /* end cdata_check_leak */
/* end dlq.c */

63
dlq.h
View File

@ -12,10 +12,30 @@
#include "audio.h"
/* A transmit or receive data block for connected mode. */
typedef struct cdata_s {
int magic; /* For integrity checking. */
#define TXDATA_MAGIC 0x09110911
struct cdata_s *next; /* Pointer to next when part of a list. */
int pid; /* Protocol id. */
int size; /* Number of bytes allocated. */
int len; /* Number of bytes actually used. */
char data[]; /* Variable length data. */
} cdata_t;
/* Types of things that can be in queue. */
typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST} dlq_type_t;
typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_CHANNEL_BUSY, DLQ_CLIENT_CLEANUP} dlq_type_t;
/* A queue item. */
@ -28,10 +48,13 @@ typedef struct dlq_item_s {
struct dlq_item_s *nextp; /* Next item in queue. */
dlq_type_t type; /* Type of item. */
/* DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST */
/* See enum definition above. */
int chan; /* Radio channel of origin. */
// I'm not worried about amount of memory used but this might be a
// little clearer if a union was used for the different event types.
// Used for received frame.
int subchan; /* Winning "subchannel" when using multiple */
@ -49,7 +72,7 @@ typedef struct dlq_item_s {
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */
// Used by requests from a client application.
// Used by requests from a client application, connect, etc.
char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
@ -57,9 +80,19 @@ typedef struct dlq_item_s {
int client;
int pid;
/* TODO: xmit data */
// Used only by client request to transmit connected data.
cdata_t *txdata;
// Used for channel activity change.
// It is useful to know when the channel is busy either for carrier detect
// or when we are transmitting.
int activity; /* OCTYPE_PTT for my transmission start/end. */
/* OCTYPE_DCD if we hear someone else. */
int status; /* 1 for active or 0 for quiet. */
} dlq_item_t;
@ -75,7 +108,16 @@ void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num
void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client);
void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int clienti, int pid, char *xdata_ptr, int xdata_len);
void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len);
void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
void dlq_channel_busy (int chan, int activity, int status);
void dlq_client_cleanup (int client);
int dlq_wait_while_empty (double timeout_val);
@ -84,6 +126,15 @@ struct dlq_item_s *dlq_remove (void);
void dlq_delete (struct dlq_item_s *pitem);
cdata_t *cdata_new (int pid, char *data, int len);
void cdata_delete (cdata_t *txdata);
void cdata_check_leak (void);
#endif
/* end dlq.h */

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -141,7 +141,7 @@ static void send_packet (char *str)
}
#endif
hdlc_send_flags (c, 8, 0);
hdlc_send_frame (c, fbuf, flen);
hdlc_send_frame (c, fbuf, flen, 0);
hdlc_send_flags (c, 2, 1);
}
ax25_delete (pp);

View File

@ -260,8 +260,15 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
* with the special "flag" characters.
*
* Idle time of all zero bits (alternating tones at maximum rate)
* has also been observed rarely.
* Recognize zero(s) followed by a flag even though it vilolates the spec.
* has also been observed rarely. It is easy to understand the reasoning.
* The tones alternate at the maximum rate, making it symmetrical and providing
* the most opportunity for the PLL to lock on to the edges.
* It also violates the published protocol spec.
*
* Recognize zero(s) followed by a single flag even though it violates the spec.
*
* It has been reported that the TinyTrak4 does this.
* https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/1207
*/
/*
@ -304,6 +311,15 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
/*
* Loss of signal should result in lack of transitions.
* (all '1' bits) for at least a little while.
*
* When this was written, I was only concerned about 1200 baud.
* For 9600, added later, there is a (de)scrambling function.
* So if there is no change in the signal, we would get pseudo random bits here.
* Maybe we need to put in another check earlier so DCD is not held on too long
* after loss of signal for 9600.
* No, that would not be a good idea. part of a valid frame, when scrambled,
* could have seven or more "1" bits in a row.
* Needs more study.
*/

View File

@ -52,6 +52,8 @@ 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.
*
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
*
* Returns: Number of bits sent including "flags" and the
@ -73,8 +75,7 @@ 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 hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs)
{
int j, fcs;
@ -84,7 +85,7 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d )\n", chan, fbuf, flen);
dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs);
fflush (stdout);
#endif
@ -97,8 +98,15 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
fcs = fcs_calc (fbuf, flen);
send_data (chan, fcs & 0xff);
send_data (chan, (fcs >> 8) & 0xff);
if (bad_fcs) {
/* For testing only - Simulate a frame getting corrupted along the way. */
send_data (chan, (~fcs) & 0xff);
send_data (chan, ((~fcs) >> 8) & 0xff);
}
else {
send_data (chan, fcs & 0xff);
send_data (chan, (fcs >> 8) & 0xff);
}
send_control (chan, 0x7e); /* End frame */

View File

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

View File

@ -883,7 +883,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp)
if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) {
if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp) != 1) {
if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) {
// Is this useful troubleshooting information or just distracting noise?
// Originally this was always printed but there was a request to add a "quiet" option to suppress this.
@ -1542,7 +1542,7 @@ static void xmit_packet (char *message, int to_chan)
if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) {
if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3) != 1) {
if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3, 1) != 1) {
// Originally this was always printed but it's probably too much noise.
// Version 1.4, print only if debug option is specified.

83
log.c
View File

@ -341,6 +341,89 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_
/*------------------------------------------------------------------
*
* Function: log_rr_bits
*
* Purpose: Quick hack to look at the C and RR bits just to see what is there.
* This seems like a good place because it is a small subset of the function above.
*
* Inputs: A - Explode information from APRS packet.
*
* pp - Received packet object.
*
*------------------------------------------------------------------*/
void log_rr_bits (decode_aprs_t *A, packet_t pp)
{
if (1) {
char heard[AX25_MAX_ADDR_LEN+1];
char smfr[60];
char *p;
int src_c, dst_c;
int src_rr, dst_rr;
// Sanitize system type (manufacturer) changing any comma to period.
strlcpy (smfr, A->g_mfr, sizeof(smfr));
for (p=smfr; *p!='\0'; p++) {
if (*p == ',') *p = '.';
}
/* Who are we hearing? Original station or digipeater? */
/* Similar code in direwolf.c. Combine into one function? */
strlcpy(heard, "", sizeof(heard));
if (pp != NULL) {
int h;
if (ax25_get_num_addr(pp) == 0) {
/* Not AX.25. No station to display below. */
h = -1;
strlcpy (heard, "", sizeof(heard));
}
else {
h = ax25_get_heard(pp);
ax25_get_addr_with_ssid(pp, h, heard);
}
if (h >= AX25_REPEATER_2 &&
strncmp(heard, "WIDE", 4) == 0 &&
isdigit(heard[4]) &&
heard[5] == '\0') {
ax25_get_addr_with_ssid(pp, h-1, heard);
strlcat (heard, "?", sizeof(heard));
}
src_c = ax25_get_h (pp, AX25_SOURCE);
dst_c = ax25_get_h (pp, AX25_DESTINATION);
src_rr = ax25_get_rr (pp, AX25_SOURCE);
dst_rr = ax25_get_rr (pp, AX25_DESTINATION);
// C RR for source
// C RR for destination
// system type
// source
// station heard
text_color_set(DW_COLOR_INFO);
dw_printf ("%d %d%d %d %d%d,%s,%s,%s\n",
src_c, (src_rr >> 1) & 1, src_rr & 1,
dst_c, (dst_rr >> 1) & 1, dst_rr & 1,
smfr, A->g_src, heard);
}
}
} /* end log_rr_bits */
/*------------------------------------------------------------------
*
* Function: log_term

2
log.h
View File

@ -14,4 +14,6 @@ void log_init (char *path);
void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);
void log_rr_bits (decode_aprs_t *A, packet_t pp);
void log_term (void);

View File

@ -470,7 +470,27 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
if (save_audio_config_p->achan[chan].num_subchan == 1 &&
save_audio_config_p->achan[chan].num_slicers == 1) {
dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, "");
int drop_it = 0;
if (save_audio_config_p->recv_error_rate != 0) {
float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
//text_color_set(DW_COLOR_INFO);
//dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
if (save_audio_config_p->recv_error_rate / 100.0 > r) {
drop_it = 1;
text_color_set(DW_COLOR_INFO);
dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
}
}
if (drop_it ) {
ax25_delete (pp);
}
else {
dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, "");
}
return;
}
@ -649,14 +669,34 @@ static void pick_best_candidate (int chan)
j = subchan_from_n(best_n);
k = slice_from_n(best_n);
dlq_rec_frame (chan, j, k,
int drop_it = 0;
if (save_audio_config_p->recv_error_rate != 0) {
float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
//text_color_set(DW_COLOR_INFO);
//dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
if (save_audio_config_p->recv_error_rate / 100.0 > r) {
drop_it = 1;
text_color_set(DW_COLOR_INFO);
dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
}
}
if ( drop_it ) {
ax25_delete (candidate[chan][j][k].packet_p);
candidate[chan][j][k].packet_p = NULL;
}
else {
dlq_rec_frame (chan, j, k,
candidate[chan][j][k].packet_p,
candidate[chan][j][k].alevel,
(int)(candidate[chan][j][k].retries),
spectrum);
/* Someone else owns it now and will delete it later. */
candidate[chan][j][k].packet_p = NULL;
/* Someone else owns it now and will delete it later. */
candidate[chan][j][k].packet_p = NULL;
}
/* Clear in preparation for next time. */

View File

@ -81,9 +81,15 @@ typedef struct pfstate_s {
packet_t pp;
/*
* Packet split into separate parts.
* Are we processing APRS or connected mode?
* This determines whch types of filters are available.
*/
int is_aprs;
/*
* Packet split into separate parts if APRS.
* Most interesting fields are:
* g_src - source address
*
* g_symbol_table - / \ or overlay
* g_symbol_code
* g_lat, g_lon - Location
@ -133,6 +139,10 @@ static int filt_s (pfstate_t *pf);
*
* pp - Packet object handle.
*
* is_aprs - True for APRS, false for connected mode digipeater.
* Connected mode allows a subset of the filter types, only
* looking at the addresses, not information part contents.
*
* Returns: 1 = yes
* 0 = no
* -1 = error detected
@ -142,7 +152,7 @@ static int filt_s (pfstate_t *pf);
*
*--------------------------------------------------------------------*/
int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs)
{
pfstate_t pfstate;
char *p;
@ -151,6 +161,8 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
assert (from_chan >= 0 && from_chan <= MAX_CHANS);
assert (to_chan >= 0 && to_chan <= MAX_CHANS);
memset (&pfstate, 0, sizeof(pfstate));
if (pp == NULL) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n");
@ -165,10 +177,9 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
pfstate.from_chan = from_chan;
pfstate.to_chan = to_chan;
/* Copy filter string, removing any control characters. */
/* Copy filter string, changing any control characers to spaces. */
memset (pfstate.filter_str, 0, sizeof(pfstate.filter_str));
strncpy (pfstate.filter_str, filter, MAX_FILTER_LEN-1);
strlcpy (pfstate.filter_str, filter, sizeof(pfstate.filter_str));
pfstate.nexti = 0;
for (p = pfstate.filter_str; *p != '\0'; p++) {
@ -178,7 +189,11 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
}
pfstate.pp = pp;
decode_aprs (&pfstate.decoded, pp, 1);
pfstate.is_aprs = is_aprs;
if (is_aprs) {
decode_aprs (&pfstate.decoded, pp, 1);
}
next_token(&pfstate);
@ -415,6 +430,14 @@ static int parse_primary (pfstate_t *pf)
* 0 = no
* -1 = error detected
*
* Description: All filter specifications are allowed for APRS.
* Only those dealing with addresses are allowed for connected digipeater.
*
* b - budlist (source)
* d - digipeaters used
* v - digipeaters not used
* u - unproto (destination)
*
*--------------------------------------------------------------------*/
@ -424,6 +447,16 @@ static int parse_filter_spec (pfstate_t *pf)
int result = -1;
char addr[AX25_MAX_ADDR_LEN];
if ( ( ! pf->is_aprs) && strchr ("01bdvu", pf->token_str[0]) == NULL) {
print_error (pf, "Only b, d, v, and u specifications are allowed for connected mode digipeater filtering.");
result = -1;
next_token (pf);
return (result);
}
/* undocumented: can use 0 or 1 for testing. */
if (strcmp(pf->token_str, "0") == 0) {
@ -437,7 +470,8 @@ static int parse_filter_spec (pfstate_t *pf)
else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) {
/* Budlist - source address */
result = filt_bodgu (pf, pf->decoded.g_src);
ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr);
result = filt_bodgu (pf, addr);
}
else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) {
/* Object or item name */
@ -479,7 +513,7 @@ static int parse_filter_spec (pfstate_t *pf)
}
else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) {
/* Unproto (destination) - probably want to exclude mic-e types */
/* because destintation is used for part of location. */
/* because destination is used for part of location. */
if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') {
ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
@ -1120,7 +1154,7 @@ static void pftest (int test_num, char *filter, char *monitor, int expected)
pp = ax25_from_text (monitor, 1);
assert (pp != NULL);
result = pfilter (0, 0, filter, pp);
result = pfilter (0, 0, filter, pp, 1);
if (result != expected) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("Unexpected result for test number %d\n", test_num);

View File

@ -1,6 +1,6 @@
/* pfilter.h */
int pfilter (int from_chan, int to_chan, char *filter, packet_t pp);
int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs);
int is_telem_metadata (char *infop);

14
ptt.c
View File

@ -46,6 +46,8 @@
*
* Version 1.3: HAMLIB support.
*
* Version 1.4: The spare "future" indicator is now used when connected to another station.
*
* References: http://www.robbayer.com/files/serial-win.pdf
*
* https://www.kernel.org/doc/Documentation/gpio.txt
@ -127,6 +129,7 @@ typedef int HANDLE;
#include "textcolor.h"
#include "audio.h"
#include "ptt.h"
#include "dlq.h"
#if __WIN32__
@ -372,7 +375,7 @@ void ptt_init (struct audio_s *audio_config_p)
strlcpy (otnames[OCTYPE_PTT], "PTT", sizeof(otnames[OCTYPE_PTT]));
strlcpy (otnames[OCTYPE_DCD], "DCD", sizeof(otnames[OCTYPE_DCD]));
strlcpy (otnames[OCTYPE_FUTURE], "FUTURE", sizeof(otnames[OCTYPE_FUTURE]));
strlcpy (otnames[OCTYPE_CON], "CON", sizeof(otnames[OCTYPE_CON]));
for (ch = 0; ch < MAX_CHANS; ch++) {
@ -773,7 +776,7 @@ void ptt_init (struct audio_s *audio_config_p)
* probably be renamed something like octrl_set.
*
* Inputs: ot - Output control type:
* OCTYPE_PTT, OCTYPE_DCD, OCTYPE_HAMLIB, OCTYPE_FUTURE
* OCTYPE_PTT, OCTYPE_DCD, OCTYPE_FUTURE
*
* chan - channel, 0 .. (number of channels)-1
*
@ -810,6 +813,13 @@ void ptt_set (int ot, int chan, int ptt_signal)
return;
}
/*
* The data link state machine has an interest in activity on the radio channel.
* This is a very convenient place to get that information.
*/
dlq_channel_busy (chan, ot, ptt_signal);
/*
* Inverted output?
*/

46
recv.c
View File

@ -68,7 +68,7 @@
*
* The difference is that app_process_rec_frame
* is no longer called directly. Instead
* the frame is appended to a queue with dlq_append.
* the frame is appended to a queue with dlq_rec_frame.
*
* Received frames can now be processed one at
* a time and we don't need to worry about later
@ -107,9 +107,8 @@
#include "dtmf.h"
#include "aprs_tt.h"
#include "dtime_now.h"
#if NEW14
#include "ax25_link.h"
#endif
#if __WIN32__
static unsigned __stdcall recv_adev_thread (void *arg);
@ -266,7 +265,7 @@ static void * recv_adev_thread (void *arg)
}
/* When a complete frame is accumulated, */
/* dlq_append, is called. */
/* dlq_rec_frame, is called. */
/* recv_process, below, drains the queue. */
@ -293,15 +292,11 @@ void recv_process (void)
while (1) {
int timed_out;
#if NEW14
double timeout_value = ax25_link_get_next_timer_expiry();
timed_out = dlq_wait_while_empty (timeout_value);
#else
dlq_wait_while_empty (0.0);
timed_out = 0;
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
@ -315,9 +310,7 @@ void recv_process (void)
dw_printf ("recv_process: time waiting on dlq. call dl_timer_expiry.\n");
#endif
#if NEW14
dl_timer_expiry ();
#endif
}
else {
@ -345,17 +338,16 @@ void recv_process (void)
*/
app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->retries, pitem->spectrum);
pitem->pp = NULL; // Was consumed by above. Don't try to use or free again.
/*
* Link processing - Can ignore UI frames.
* Link processing.
*/
// TODO - ax25_link_rec_frame & delete & remove delete from above.
lm_data_indication(pitem);
break;
#if NEW14
case DLQ_CONNECT_REQUEST:
dl_connect_request (pitem);
@ -370,7 +362,27 @@ void recv_process (void)
dl_data_request (pitem);
break;
#endif
case DLQ_REGISTER_CALLSIGN:
dl_register_callsign (pitem);
break;
case DLQ_UNREGISTER_CALLSIGN:
dl_unregister_callsign (pitem);
break;
case DLQ_CHANNEL_BUSY:
lm_channel_busy (pitem);
break;
case DLQ_CLIENT_CLEANUP:
dl_client_cleanup (pitem);
break;
}
dlq_delete (pitem);

View File

@ -184,6 +184,10 @@ MYFDTYPE serial_port_open (char *devicename, int baud)
//text_color_set(DW_COLOR_INFO);
//dw_printf("Successful serial port open on %s.\n", devicename);
// Some devices, e.g. KPC-3+, can't turn off hardware flow control and need RTS.
EscapeCommFunction(fd,SETRTS);
EscapeCommFunction(fd,SETDTR);
#else
/* Linux version. */

211
server.c
View File

@ -179,23 +179,6 @@ static int enable_send_monitor_to_client[MAX_NET_CLIENTS];
/* the client app must send a command to enable this. */
/*
* Registered callsigns from 'X' command.
* For simplicity just use a fixed size array until there
* is evidence that a larger number would be needed.
*
* Also keep track of which client did the registration.
* For example client 0 might register the callsign ABC
* and client 1 register DEF. If something comes addressed
* to DEF, we would want it going only to client 1.
*/
#define MAX_REG_CALLSIGNS 20
static char registered_callsigns[MAX_REG_CALLSIGNS][AX25_MAX_ADDR_LEN];
static int registered_by_client[MAX_REG_CALLSIGNS];
// TODO: define in one place, use everywhere.
// TODO: Macro to terminate thread when no point to go on.
@ -464,8 +447,6 @@ void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc)
enable_send_monitor_to_client[client] = 0;
}
memset (registered_callsigns, 0, sizeof(registered_callsigns));
if (server_port == 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Disabled AGW network client port.\n");
@ -653,6 +634,7 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
// TODO: "attached" or some other term would be better because "connected" has a different meaning for AX.25.
dw_printf("\nConnected to AGW client application %d ...\n\n", client);
/*
@ -836,6 +818,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
closesocket (client_sock[client]);
client_sock[client] = -1;
WSACleanup();
dlq_client_cleanup (client);
}
#else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@ -845,6 +828,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
dw_printf ("\nError sending message to AGW client application. Closing connection.\n\n");
close (client_sock[client]);
client_sock[client] = -1;
dlq_client_cleanup (client);
}
#endif
}
@ -939,6 +923,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
closesocket (client_sock[client]);
client_sock[client] = -1;
WSACleanup();
dlq_client_cleanup (client);
}
#else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@ -948,6 +933,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
dw_printf ("\nError sending message to AGW client application %d. Closing connection.\n\n", client);
close (client_sock[client]);
client_sock[client] = -1;
dlq_client_cleanup (client);
}
#endif
}
@ -964,6 +950,8 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
* Purpose: Send notification to client app when a link has
* been established with another station.
*
* DL-CONNECT Confirm or DL-CONNECT Indication in the protocol spec.
*
* Inputs: chan - Which radio channel.
*
* client - Which one of potentially several clients.
@ -1017,6 +1005,8 @@ void server_link_established (int chan, int client, char *remote_call, char *own
* another station has been terminated or a connection
* attempt failed.
*
* DL-DISCONNECT Confirm or DL-DISCONNECT Indication in the protocol spec.
*
* Inputs: chan - Which radio channel.
*
* client - Which one of potentially several clients.
@ -1027,7 +1017,7 @@ void server_link_established (int chan, int client, char *remote_call, char *own
*
* timeout - true when no answer from other station.
* How do we distinguish who asked for the
* termination of an existing linkn?
* termination of an existing link?
*
*--------------------------------------------------------------------*/
@ -1060,6 +1050,66 @@ void server_link_terminated (int chan, int client, char *remote_call, char *own_
} /* end server_link_terminated */
/*-------------------------------------------------------------------
*
* Name: server_rec_conn_data
*
* Purpose: Send received connected data to the application.
*
* DL-DATA Indication in the protocol spec.
*
* Inputs: chan - Which radio channel.
*
* client - Which one of potentially several clients.
*
* remote_call - Callsign[-ssid] of remote station.
*
* own_call - Callsign[-ssid] of my end.
*
* pid - Protocol ID from I frame.
*
* data_ptr - Pointer to a block of bytes.
*
* data_len - Number of bytes. Could be zero.
*
*--------------------------------------------------------------------*/
void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len)
{
struct {
struct agwpe_s hdr;
char info[AX25_MAX_INFO_LEN]; // I suppose there is potential for something larger.
// We'll cross that bridge if we ever come to it.
} reply;
memset (&reply.hdr, 0, sizeof(reply.hdr));
reply.hdr.portx = chan;
reply.hdr.datakind = 'D';
reply.hdr.pid = pid;
strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from));
strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to));
if (data_len < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client);
data_len = 0;
}
else if (data_len > AX25_MAX_INFO_LEN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client);
data_len = AX25_MAX_INFO_LEN;
}
memcpy (reply.info, data_ptr, data_len);
reply.hdr.data_len_NETLE = host2netle(data_len);
send_to_client (client, &reply);
} /* end server_rec_conn_data */
/*-------------------------------------------------------------------
*
@ -1199,6 +1249,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]);
#endif
client_sock[client] = -1;
dlq_client_cleanup (client);
continue;
}
@ -1240,6 +1291,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]);
#endif
client_sock[client] = -1;
dlq_client_cleanup (client);
return (0);
}
@ -1258,6 +1310,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]);
#endif
client_sock[client] = -1;
dlq_client_cleanup (client);
return (0);
}
if (n >= 0) {
@ -1581,38 +1634,34 @@ static THREAD_F cmd_listen_thread (void *arg)
{
struct {
struct agwpe_s hdr;
char data;
char data; /* 1 = success, 0 = failure */
} reply;
int j, ok;
int ok = 1;
// The protocol spec says it is an error to register the same one more than once.
// Too much trouble. Report success if the channel is valid.
int chan = cmd.hdr.portx;
if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) {
ok = 1;
dlq_register_callsign (cmd.hdr.call_from, chan, client);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("AGW protocol error. Register callsign for invalid channel %d.\n", chan);
ok = 0;
}
memset (&reply, 0, sizeof(reply));
reply.hdr.datakind = 'X';
reply.hdr.portx = cmd.hdr.portx;
memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from));
reply.hdr.data_len_NETLE = host2netle(1);
// Version 1.0.
// Previously used sizeof(reply) but compiler rounded it up to next byte boundary.
// That's why more cumbersome size expression is used.
// The protocol spec says it is an error to register the same one more than once.
// First make sure is it not already in there. Add if space available.
if (server_callsign_registered_by_client(cmd.hdr.call_from) >= 0) {
ok = 0;
}
else {
ok = 0;
for (j = 0; j < MAX_REG_CALLSIGNS && ok == 0; j++) {
if (registered_callsigns[j][0] == '\0') {
strlcpy (registered_callsigns[j], cmd.hdr.call_from, sizeof(registered_callsigns[j]));
registered_by_client[j] = client;
ok = 1;
}
}
}
reply.data = ok; /* 1 = success, 0 = failure */
reply.data = ok;
send_to_client (client, &reply);
}
break;
@ -1620,13 +1669,15 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'x': /* Unregister CallSign */
{
int j;
for (j = 0; j < MAX_REG_CALLSIGNS; j++) {
if (strcmp(registered_callsigns[j], cmd.hdr.call_from) == 0) {
registered_callsigns[j][0] = '\0';
registered_by_client[j] = -1;
}
int chan = cmd.hdr.portx;
if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) {
dlq_unregister_callsign (cmd.hdr.call_from, chan, client);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("AGW protocol error. Unregister callsign for invalid channel %d.\n", chan);
}
}
/* No reponse is expected. */
@ -1648,7 +1699,7 @@ static THREAD_F cmd_listen_thread (void *arg)
int j;
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_DESTINATION]));
if (cmd.hdr.datakind == 'c') {
pid = cmd.hdr.pid; /* non standard for NETROM, TCP/IP, etc. */
@ -1675,15 +1726,9 @@ static THREAD_F cmd_listen_thread (void *arg)
}
}
#if NEW14
dlq_connect_request (callsigns, num_calls, cmd.hdr.portx, client, pid);
#else
(void)pid; // suppress unused variable message.
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
dw_printf ("Connected packet mode is not implemented.\n");
#endif
}
break;
@ -1696,15 +1741,9 @@ static THREAD_F cmd_listen_thread (void *arg)
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
#if NEW14
dlq_xmit_data_request (callsigns, num_calls, cmd.hdr.portx, client, cmd.hdr.pid, cmd.data, netle2host(cmd.hdr.data_len_NETLE));
#else
(void)num_calls; // suppress unused variable warning.
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
dw_printf ("Connected packet mode is not implemented.\n");
#endif
}
break;
@ -1716,15 +1755,9 @@ static THREAD_F cmd_listen_thread (void *arg)
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
#if NEW14
dlq_disconnect_request (callsigns, num_calls, cmd.hdr.portx, client);
#else
(void)num_calls; // suppress unused variable warning.
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
dw_printf ("Connected packet mode is not implemented.\n");
#endif
}
break;
@ -1839,30 +1872,4 @@ static THREAD_F cmd_listen_thread (void *arg)
} /* end send_to_client */
/*-------------------------------------------------------------------
*
* Name: server_callsign_registered_by_client
*
* Purpose: See if given callsign was registered.
*
* Inputs: callsign
*
* Returns: >= 0 for the client number.
* -1 for not found.
*
*--------------------------------------------------------------------*/
int server_callsign_registered_by_client (char *callsign)
{
int j;
for (j = 0; j < MAX_REG_CALLSIGNS; j++) {
if (strcmp(registered_callsigns[j], callsign) == 0) {
return (registered_by_client[j]);
}
}
return (-1);
} /* end server_callsign_registered_by_client */
/* end server.c */

View File

@ -22,5 +22,7 @@ void server_link_established (int chan, int client, char *remote_call, char *own
void server_link_terminated (int chan, int client, char *remote_call, char *own_call, int timeout);
void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len);
/* end server.h */

View File

@ -1,4 +1,4 @@
APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 22 Mar 2016
APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 18 Oct 2016
---------------------------------------------------------------------
BACKGROUND: This file addresses new additions proposals (OVERLAYS)
@ -7,6 +7,9 @@ document remains on the www.aprs.org/symbols/symbolsX.txt page, but
only has one line per symbol character. Since each of the symbols
can have up to 36 overlays, this gives us thousands of symbols codes.
18 Oct 2016: Added Green, Yellow, Red for Flood gaures using G,Y,R
overlays + N for Normal. (on existing H2O symbol)
Added DIGIPEATERS
22 Mar 2016: Added A0 overlay circle for ALSTAR nodes
and V0 for VOIP combined echolink and IRLP
and P& for PSKmail node
@ -226,6 +229,19 @@ BD = Bus Depot (new Aug 2014)
LD = LIght Rail or Subway (new Aug 2014)
SD = Seaport Depot (new Aug 2014)
DIGIPEATERS
/# - Generic digipeater
1# - WIDE1-1 digipeater
A# - Alternate input (i.e. 144.990MHz) digipeater
E# - Emergency powered (assumed full normal digi)
I# - I-gate equipped digipeater
L# - WIDEn-N with path length trapping
P# - PacComm
S# - SSn-N digipeater (includes WIDEn-N)
X# - eXperimental digipeater
V# - Viscous https://github.com/PhirePhly/aprx/blob/master/ViscousDigipeater.README
W# - WIDEn-N, SSn-N and Trapping
EMERGENCY: #!
/! = Police/Sheriff, etc
\! = Emergency!
@ -389,6 +405,18 @@ Tu = Tanker
Cu = Chlorine Tanker
Hu = Hazardous
WATER #w
/w = Water Station or other H2O
\w = flooding (or Avalanche/slides)
Aw = Avalanche
Gw = Green Flood Gauge
Mw = Mud slide
Nw = Normal flood gauge (blue)
Rw = Red flood gauge
Sw = Snow Blockage
Yw = Yellow flood gauge
Anyone can use any overlay on any of the overlayable symbols for any
special purpose. We are not trying to document all possible such

View File

@ -1,6 +1,10 @@
APRS TO-CALL VERSION NUMBERS 29 Apr 2016
APRS TO-CALL VERSION NUMBERS 14 Nov 2016
-------------------------------------------------------------------
WB4APR
14 Nov 16 Added APINxx for PinPoint by AB0WV
09 Nov 16 added APNICx for SQ5EKU http://sq5eku.blogspot.com/
24 Oct 16 added APTKPT TrackPoint N0LP, removed APZTKP
24 Aug 16 added APK004 for Kenwood THD-74
29 Apr 16 added APFPRS for FreeDV by Jeroen PE1RXQ
25 Feb 16 Added APCDS0 for Leon Lessing ZS6LMG's cell tracker
21 Jan 16 added APDNOx for APRSduino by DO3SWW
@ -12,12 +16,7 @@ APRS TO-CALL VERSION NUMBERS 29 Apr 2016
27 Apr 15 added APZMAJ for Martyn M1MAJ DeLorme inReach Tracker
21 Apr 15 added APB2MF & APR2MF DL2MF - MF2APRS Radiosonde
06 Apr 15 added APAVT5 SainSonic AP510 - a 1watt tracker
13 Mar 14 added APECAN Pecan Pico APRS Balloon Tracker
02 Sep 14 added APSTMx W7QO's Balloon trackers
21 Aug 14 added APSMSx Paul Defrusne's SMS gateway
11 Aug 14 added APCWP8 John GM7HHB, WinphoneAPRS
18 Dec 13 added APZWKR GM1WKR NetSked application
. . . . . ...
11 Jan 12 added APYTxx for YagTracker and updated Yaesu APY008/350
In APRS, the AX.25 Destination address is not used for packet
@ -86,6 +85,7 @@ a TOCALL number series:
APHTxx HMTracker by IU0AAC
API APICQx for ICQ
APICxx for HA9MCQ Pic IGate
APINxx for PinPoint by AB0WV
APJ APJAxx JavAPRS
APJExx JeAPRS
APJIxx jAPRSIgate
@ -93,6 +93,7 @@ a TOCALL number series:
APJYnn KA2DDO Yet another APRS system
APK APK0xx Kenwood TH-D7's
APK003 Kenwood TH-D72
APK004 Kenwood TH-D74
APK1xx Kenwood D700's
APK102 Kenwood D710
APKRAM KRAMstuff.com - Mark. G7LEU
@ -107,6 +108,7 @@ a TOCALL number series:
APN9xx Kantronics KPC-9612 Roms
APNAxx WB6ZSU's APRServe
APNDxx DIGI_NED
APNICx SQ5EKU http://sq5eku.blogspot.com/
APNK01 Kenwood D700 (APK101) type
APNK80 KAM version 8.0
APNKMP KAM+
@ -144,14 +146,15 @@ a TOCALL number series:
APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK)
APSK63 APRS Messenger -over-PSK63
APSK25 APRS Messenger GMSK-250
APSMSx Paul Defrusne's SMS gateway
APSMSx Paul Dufresne's SMSGTE - SMS Gateway
APSTMx for W7QO's Balloon trackers
APSTPO for N0AGI Satellite Tracking and Operations
APT APTIGR TigerTrack
APTTxx Tiny Track
APT2xx Tiny Track II
APT APT2xx Tiny Track II
APT3xx Tiny Track III
APTAxx K4ATM's tiny track
APTIGR TigerTrack
APTKPT TrackPoint N0LP
APTTxx Tiny Track
APTWxx Byons WXTrac
APTVxx for ATV/APRN and SSTV applications
APU APU1xx UIview 16 bit applications
@ -179,7 +182,7 @@ a TOCALL number series:
APZMAJ Martyn M1MAJ DeLorme inReach Tracker
APZMDR for HaMDR trackers - hessu * hes.iki.fi]
APZPAD Smart Palm
APZTKP TrackPoint, Nick N0LP (Balloon tracking)
APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated)
APZWIT MAP27 radio (Mountain Rescue) EI7IG
APZWKR GM1WKR NetSked application

419
tq.c
View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2014, 2015 John Langner, WB2OSZ
// Copyright (C) 2011, 2012, 2014, 2015, 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -35,6 +35,8 @@
*
*---------------------------------------------------------------*/
#define TQ_C 1
#include "direwolf.h"
#include <stdio.h>
@ -120,7 +122,7 @@ void tq_init (struct audio_s *audio_config_p)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tq_init ( %d )\n", nchan);
dw_printf ("tq_init ( )\n");
#endif
save_audio_config_p = audio_config_p;
@ -187,11 +189,14 @@ void tq_init (struct audio_s *audio_config_p)
*
* Name: tq_append
*
* Purpose: Add a packet to the end of the specified transmit queue.
* Purpose: Add an APRS packet to the end of the specified transmit queue.
*
* Connected mode is a little different. Use lm_data_request instead.
*
* Inputs: chan - Channel, 0 is first.
*
* prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
* prio - Priority, use TQ_PRIO_0_HI for digipeated or
* TQ_PRIO_1_LO for normal.
*
* pp - Address of packet object.
* Caller should NOT make any references to
@ -218,8 +223,11 @@ void tq_append (int chan, int prio, packet_t pp)
#if DEBUG
unsigned char *pinfo;
int info_len = ax25_get_info (pp, &pinfo);
if (info_len > 10) info_len = 10;
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tq_append (chan=%d, prio=%d, pp=%p)\n", chan, prio, pp);
dw_printf ("tq_append (chan=%d, prio=%d, pp=%p) \"%*s\"\n", chan, prio, pp, info_len, (char*)pinfo);
#endif
@ -242,6 +250,8 @@ void tq_append (int chan, int prio, packet_t pp)
if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
dw_printf ("This is probably a client application error, not a problem with direwolf.\n");
dw_printf ("AX.25 for Linux is known to transmit on channels 2 & 8 sometimes when it shouldn't.\n");
ax25_delete(pp);
return;
}
@ -286,14 +296,6 @@ void tq_append (int chan, int prio, packet_t pp)
dw_mutex_lock (&tq_mutex);
// was_empty = 1;
// for (c=0; c<tq_num_channels; c++) {
// for (p=0; p<TQ_NUM_PRIO; p++) {
// if (queue_head[c][p] != NULL)
// was_empty = 0;
// }
// }
if (queue_head[chan][prio] == NULL) {
queue_head[chan][prio] = pp;
}
@ -334,7 +336,340 @@ void tq_append (int chan, int prio, packet_t pp)
}
#endif
}
} /* end tq_append */
/*-------------------------------------------------------------------
*
* Name: lm_data_request
*
* Purpose: Add an AX.25 frame to the end of the specified transmit queue.
*
* Use tq_append instead for APRS.
*
* Inputs: chan - Channel, 0 is first.
*
* prio - Priority, use TQ_PRIO_0_HI for priority (expedited)
* or TQ_PRIO_1_LO for normal.
*
* pp - Address of packet object.
* Caller should NOT make any references to
* it after this point because it could
* be deleted at any time.
*
* Outputs: A packet object is added to transmit queue.
*
* Description: 5.4.
*
* LM-DATA Request. The Data-link State Machine uses this primitive to pass
* frames of any type (SABM, RR, UI, etc.) to the Link Multiplexer State Machine.
*
* LM-EXPEDITED-DATA Request. The data-link machine uses this primitive to
* request transmission of each digipeat or expedite data frame.
*
* C2a.1
*
* PH-DATA Request. This primitive from the Link Multiplexer State Machine
* provides an AX.25 frame of any type (UI, SABM, I, etc.) that is to be transmitted. An
* unlimited number of frames may be provided. If the transmission exceeds the 10-
* minute limit or the anti-hogging time limit, the half-duplex Physical State Machine
* automatically relinquishes the channel for use by the other stations. The
* transmission is automatically resumed at the next transmission opportunity
* indicated by the CSMA/p-persistence contention algorithm.
*
* PH-EXPEDITED-DATA Request. This primitive from the Link Multiplexer State
* Machine provides the AX.25 frame that is to be transmitted immediately. The
* simplex Physical State Machine gives preference to priority frames over normal
* frames, and will take advantage of the PRIACK window. Priority frames can be
* provided by the link multiplexer at any time; a PH-SEIZE Request and subsequent
* PH Release Request are not employed for priority frames.
*
* C3.1
*
* LM-DATA Request. This primitive from the Data-link State Machine provides a
* AX.25 frame of any type (UI, SABM, I, etc.) that is to be transmitted. An unlimited
* number of frames may be provided. The Link Multiplexer State Machine
* accumulates the frames in a first-in, first-out queue until it is time to transmit them.
*
* C4.2
*
* LM-DATA Request. This primitive is used by the Data link State Machines to pass
* frames of any type (SABM, RR, UI, etc.) to the Link Multiplexer State Machine.
*
* LM-EXPEDITED-DATA Request. This primitive is used by the Data link State
* Machine to pass expedited data to the link multiplexer.
*
*
* Implementation: Add packet to end of linked list.
* Signal the transmit thread if the queue was formerly empty.
*
* Note that we have a transmit thread each audio channel.
* Two channels can share one audio output device.
*
* IMPORTANT! Don't make an further references to the packet object after
* giving it to lm_data_request.
*
*--------------------------------------------------------------------*/
// TODO: FIXME: this is a copy of tq_append. Need to fine tune and explain why.
void lm_data_request (int chan, int prio, packet_t pp)
{
packet_t plast;
packet_t pnext;
#if DEBUG
unsigned char *pinfo;
int info_len = ax25_get_info (pp, &pinfo);
if (info_len > 10) info_len = 10;
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_request (chan=%d, prio=%d, pp=%p) \"%*s\"\n", chan, prio, pp, info_len, (char*)pinfo);
#endif
assert (prio >= 0 && prio < TQ_NUM_PRIO);
if (pp == NULL) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("INTERNAL ERROR: lm_data_request NULL packet pointer. Please report this!\n");
return;
}
#if AX25MEMDEBUG
if (ax25memdebug_get()) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_request (chan=%d, prio=%d, seq=%d)\n", chan, prio, ax25memdebug_seq(pp));
}
#endif
if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
ax25_delete(pp);
return;
}
/*
* Is transmit queue out of control?
*/
if (tq_count(chan,prio) > 250) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Warning: Transmit packet queue for channel %d is extremely long.\n", chan);
dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n");
}
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_request: enter critical section\n");
#endif
dw_mutex_lock (&tq_mutex);
if (queue_head[chan][prio] == NULL) {
queue_head[chan][prio] = pp;
}
else {
plast = queue_head[chan][prio];
while ((pnext = ax25_get_nextp(plast)) != NULL) {
plast = pnext;
}
ax25_set_nextp (plast, pp);
}
dw_mutex_unlock (&tq_mutex);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_request: left critical section\n");
#endif
// Appendix C2a, from the Ax.25 protocol spec, says that a priority frame
// will start transmission. If not already transmitting, normal frames
// will pile up until LM-SEIZE Request starts transmission.
// Erratum: It doesn't take long for that to fail.
// We send SABM(e) frames to the transmit queue and the transmitter doesn't get activated.
//NO! if (prio == TQ_PRIO_0_HI) {
#if DEBUG
dw_printf ("lm_data_request (): about to wake up xmit thread.\n");
#endif
#if __WIN32__
SetEvent (wake_up_event[chan]);
#else
if (xmit_thread_is_waiting[chan]) {
int err;
dw_mutex_lock (&(wake_up_mutex[chan]));
err = pthread_cond_signal (&(wake_up_cond[chan]));
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("lm_data_request: pthread_cond_signal err=%d", err);
perror ("");
exit (1);
}
dw_mutex_unlock (&(wake_up_mutex[chan]));
}
#endif
//NO! }
} /* end lm_data_request */
/*-------------------------------------------------------------------
*
* Name: lm_seize_request
*
* Purpose: Force start of transmit even if transmit queue is empty.
*
* Inputs: chan - Channel, 0 is first.
*
* Description: 5.4.
*
* LM-SEIZE Request. The Data-link State Machine uses this primitive to request the
* Link Multiplexer State Machine to arrange for transmission at the next available
* opportunity. The Data-link State Machine uses this primitive when an
* acknowledgement must be made; the exact frame in which the acknowledgement
* is sent will be chosen when the actual time for transmission arrives.
*
* C2a.1
*
* PH-SEIZE Request. This primitive requests the simplex state machine to begin
* transmitting at the next available opportunity. When that opportunity has been
* identified (according to the CSMA/p-persistence algorithm included within), the
* transmitter started, a parameterized window provided for the startup of a
* conventional repeater (if required), and a parameterized time allowed for the
* synchronization of the remote station's receiver (known as TXDELAY in most
* implementations), then a PH-SEIZE Confirm primitive is returned to the link
* multiplexer.
*
* C3.1
*
* LM-SEIZE Request. This primitive requests the Link Multiplexer State Machine to
* arrange for transmission at the next available opportunity. The Data-link State
* Machine uses this primitive when an acknowledgment must be made, but the exact
* frame in which the acknowledgment will be sent will be chosen when the actual
* time for transmission arrives. The Link Multiplexer State Machine uses the LMSEIZE
* Confirm primitive to indicate that the transmission opportunity has arrived.
* After the Data-link State Machine has provided the acknowledgment, the Data-link
* State Machine gives permission to stop transmission with the LM Release Request
* primitive.
*
* C4.2
*
* LM-SEIZE Request. This primitive is used by the Data link State Machine to
* request the Link Multiplexer State Machine to arrange for transmission at the next
* available opportunity. The Data link State Machine uses this primitive when an
* acknowledgment must be made, but the exact frame in which the acknowledgment
* is sent will be chosen when the actual time for transmission arrives.
*
*
* Implementation: Add a null frame (i.e. length of 0) to give the process a kick.
* xmit.c needs to be smart enough to discard it.
*
*--------------------------------------------------------------------*/
void lm_seize_request (int chan)
{
packet_t pp;
int prio = TQ_PRIO_1_LO;
packet_t plast;
packet_t pnext;
#if DEBUG
unsigned char *pinfo;
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_seize_request (chan=%d)\n", chan);
#endif
if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
return;
}
pp = ax25_new();
#if AX25MEMDEBUG
if (ax25memdebug_get()) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_seize_request (chan=%d, seq=%d)\n", chan, ax25memdebug_seq(pp));
}
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_seize_request: enter critical section\n");
#endif
dw_mutex_lock (&tq_mutex);
if (queue_head[chan][prio] == NULL) {
queue_head[chan][prio] = pp;
}
else {
plast = queue_head[chan][prio];
while ((pnext = ax25_get_nextp(plast)) != NULL) {
plast = pnext;
}
ax25_set_nextp (plast, pp);
}
dw_mutex_unlock (&tq_mutex);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_seize_request: left critical section\n");
#endif
#if DEBUG
dw_printf ("lm_seize_request (): about to wake up xmit thread.\n");
#endif
#if __WIN32__
SetEvent (wake_up_event[chan]);
#else
if (xmit_thread_is_waiting[chan]) {
int err;
dw_mutex_lock (&(wake_up_mutex[chan]));
err = pthread_cond_signal (&(wake_up_cond[chan]));
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("lm_seize_request: pthread_cond_signal err=%d", err);
perror ("");
exit (1);
}
dw_mutex_unlock (&(wake_up_mutex[chan]));
}
#endif
} /* end lm_seize_request */
/*-------------------------------------------------------------------
@ -484,7 +819,61 @@ packet_t tq_remove (int chan, int prio)
}
#endif
return (result_p);
}
} /* end tq_remove */
/*-------------------------------------------------------------------
*
* Name: tq_peek
*
* Purpose: Take a peek at the next frame in the queue but don't remove it.
*
* Inputs: chan - Channel, 0 is first.
*
* prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
*
* Returns: Pointer to packet object or NULL.
*
* Caller should NOT destroy it because it is still in the queue.
*
*--------------------------------------------------------------------*/
packet_t tq_peek (int chan, int prio)
{
packet_t result_p;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tq_peek(%d,%d) enter critical section\n", chan, prio);
#endif
// I don't think we need critical region here.
//dw_mutex_lock (&tq_mutex);
result_p = queue_head[chan][prio];
// Just take a peek at the head. Don't remove it.
//dw_mutex_unlock (&tq_mutex);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tq_remove(%d,%d) leave critical section, returns %p\n", chan, prio, result_p);
#endif
#if AX25MEMDEBUG
if (ax25memdebug_get() && result_p != NULL) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tq_remove (chan=%d, prio=%d) seq=%d\n", chan, prio, ax25memdebug_seq(result_p));
}
#endif
return (result_p);
} /* end tq_peek */
/*-------------------------------------------------------------------

6
tq.h
View File

@ -24,10 +24,16 @@ void tq_init (struct audio_s *audio_config_p);
void tq_append (int chan, int prio, packet_t pp);
void lm_data_request (int chan, int prio, packet_t pp);
void lm_seize_request (int chan);
void tq_wait_while_empty (int chan);
packet_t tq_remove (int chan, int prio);
packet_t tq_peek (int chan, int prio);
int tq_count (int chan, int prio);
#endif

View File

@ -502,7 +502,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt
* These would not be parsed properly:
*
* $PKWDWPL,150753,V,4237.14,N,07120.83,W,,,190316,,test3,/,*1B
* $PKWDWPL,150758,V,4237.14,N,07120.83,W,,,190316,,test4,/**3B
* $PKWDWPL,150758,V,4237.14,N,07120.83,W,,,190316,,test4,/ **3B
*
* We perform the usual substitution and the other end would
* need to change them back after extracting from NMEA sentence.

262
xid.c
View File

@ -2,7 +2,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2014 John Langner, WB2OSZ
// Copyright (C) 2014, 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -49,28 +49,10 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include "textcolor.h"
//#include "xid.h"
struct ax25_param_s {
int full_duplex;
// Order is important because negotiation keeps the lower.
enum rej_e {implicit_reject=1, selective_reject=2, selective_reject_reject=3 } rej;
enum modulo_e {modulo_8 = 8, modulo_128 = 128} modulo;
int i_field_length_rx; /* In bytes. XID has it in bits. */
int window_size_rx;
int ack_timer; /* "T1" in mSec. */
int retries; /* "N1" */
};
#include "xid.h"
@ -102,13 +84,20 @@ struct ax25_param_s {
/*-------------------------------------------------------------------
*
* Name: ...
* Name: xid_parse
*
* Purpose: ...
* Purpose: Decode information part of XID frame into individual values.
*
* Inputs: ...
* Inputs: info - pointer to information part of frame.
*
* Outputs: ...
* info_len - Number of bytes in information part of frame.
* Could be 0.
*
* desc_size - Size of desc. 100 is good.
*
* Outputs: result - Structure with extracted values.
*
* desc - Text description for troubleshooting.
*
* Returns: 1 for mostly successful (with possible error messages), 0 for failure.
*
@ -120,37 +109,44 @@ struct ax25_param_s {
*--------------------------------------------------------------------*/
int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, char *desc, int desc_size)
{
unsigned char *p;
int group_len;
char stemp[64];
char debug_msg[256];
result->full_duplex = 0;
strlcpy (desc, "", desc_size);
// Default is implicit reject for pre version 2.2 but we wouldn't be here in that case.
result->rej = selective_reject;
// What should we do when some fields are missing?
result->modulo = modulo_8;
// The AX.25 v2.2 protocol spec says, for most of these,
// "If this field is not present, the current values are retained."
result->i_field_length_rx = 256; // bytes here but converted to bits during encoding.
// We set the values to our usual G_UNKNOWN to mean undefined and let the caller deal with it.
// Default is 4 for pre version 2.2 but we wouldn't be here in that case.
result->window_size_rx = result->modulo == modulo_128 ? 32 : 7;
result->ack_timer = 3000;
result->retries = 10;
result->full_duplex = G_UNKNOWN;
result->rej = G_UNKNOWN;
result->modulo = G_UNKNOWN;
result->i_field_length_rx = G_UNKNOWN;
result->window_size_rx = G_UNKNOWN;
result->ack_timer = G_UNKNOWN;
result->retries = G_UNKNOWN;
/* Information field is optional but that seems pretty lame. */
if (info_len == 0) {
return (1);
}
p = info;
if (*p != FI_Format_Indicator) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: First byte of info field should be Format Indicator, %d.\n", FI_Format_Indicator);
dw_printf ("XID error: First byte of info field should be Format Indicator, %02x.\n", FI_Format_Indicator);
dw_printf ("XID info part: %02x %02x %02x %02x %02x ... length=%d\n", info[0], info[1], info[2], info[3], info[4], info_len);
return 0;
}
p++;
@ -192,38 +188,46 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
if (pval & PV_Classes_Procedures_Half_Duplex && ! (pval & PV_Classes_Procedures_Full_Duplex)) {
result->full_duplex = 0;
strlcat (desc, "Half-Duplex ", desc_size);
}
else if (pval & PV_Classes_Procedures_Full_Duplex && ! (pval & PV_Classes_Procedures_Half_Duplex)) {
result->full_duplex = 1;
strlcat (desc, "Full-Duplex ", desc_size);
}
else {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Expected one of Half or Full Duplex be set.\n");
result->full_duplex = 0;
}
break;
case PI_HDLC_Optional_Functions:
if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
if ((pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
result->rej = selective_reject_reject; /* Both bits set */
strlcat (desc, "SREJ-REJ ", desc_size);
}
else if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && ! (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
else if ((pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && ! (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
result->rej = implicit_reject; /* Only REJ is set */
strlcat (desc, "REJ ", desc_size);
}
else if ( ! (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
result->rej = selective_reject; /* Only SREJ is set */
strlcat (desc, "SREJ ", desc_size);
}
else {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Expected one or both of REJ, SREJ to be set.\n");
}
if (pval & PV_HDLC_Optional_Functions_Modulo_8 && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) {
if ((pval & PV_HDLC_Optional_Functions_Modulo_8) && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) {
result->modulo = modulo_8;
strlcat (desc, "modulo-8 ", desc_size);
}
else if (pval & PV_HDLC_Optional_Functions_Modulo_128 && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) {
else if ((pval & PV_HDLC_Optional_Functions_Modulo_128) && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) {
result->modulo = modulo_128;
strlcat (desc, "modulo-128 ", desc_size);
}
else {
text_color_set (DW_COLOR_ERROR);
@ -256,6 +260,9 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
result->i_field_length_rx = pval / 8;
snprintf (stemp, sizeof(stemp), "I-Field-Length-Rx=%d ", result->i_field_length_rx);
strlcat (desc, stemp, desc_size);
if (pval & 0x7) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: I Field Length Rx, %d, is not a whole number of bytes.\n", pval);
@ -267,22 +274,34 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
result->window_size_rx = pval;
if (pval < 1 || pval >= result->modulo) {
snprintf (stemp, sizeof(stemp), "Window-Size-Rx=%d ", result->window_size_rx);
strlcat (desc, stemp, desc_size);
if (pval < 1 || pval > 127) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru %d.\n", pval, result->modulo-1);
result->window_size_rx = result->modulo == modulo_128 ? 32 : 7;
dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru 127.\n", pval);
result->window_size_rx = 127;
// Let the caller deal with modulo 8 consideration.
}
//continue here.
//continue here with more error checking.
break;
case PI_Ack_Timer:
result->ack_timer = pval;
snprintf (stemp, sizeof(stemp), "Ack-Timer=%d ", result->ack_timer);
strlcat (desc, stemp, desc_size);
break;
case PI_Retries:
case PI_Retries: // Is it retrys or retries?
result->retries = pval;
snprintf (stemp, sizeof(stemp), "Retries=%d ", result->retries);
strlcat (desc, stemp, desc_size);
break;
default:
@ -338,9 +357,10 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
*
* Outputs: info - Information part of XID frame.
* Does not include the control byte.
* Supply 32 bytes to be safe.
* Use buffer of 40 bytes just to be safe.
*
* Returns: Number of bytes in the info part. Should be at most 27.
* Again, provide a larger space just to be safe in case this ever changes.
*
* Description: 6.3.2 "Parameter negotiation occurs at any time. It is accomplished by sending
* the XID command frame and receiving the XID response frame. Implementations of
@ -359,35 +379,57 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
* Both TNCs set themselves up based on the values used in the XID response. Negotiation
* is used by Classes of Procedures, HDLC Optional Functions, Acknowledge Timer and Retries."
*
* Comment: I have a problem with "... occurs at any time." What if we were in the middle
* of transferring a large file with k=32 then along comes XID which says switch to modulo 8?
*
*--------------------------------------------------------------------*/
int xid_encode (struct ax25_param_s *param, unsigned char *info)
int xid_encode (struct xid_param_s *param, unsigned char *info)
{
unsigned char *p;
int len;
int x;
int m = 0;
p = info;
*p++ = FI_Format_Indicator;
*p++ = GI_Group_Identifier;
*p++ = 0;
*p++ = 0x17;
m = 4; // classes of procedures
m += 5; // HDLC optional features
if (param->i_field_length_rx != G_UNKNOWN) m += 4;
if (param->window_size_rx != G_UNKNOWN) m += 3;
if (param->ack_timer != G_UNKNOWN) m += 4;
if (param->retries != G_UNKNOWN) m += 3;
*p++ = m; // 0x17 if all present.
// "Classes of Procedures" has half / full duplex.
// We always send this.
*p++ = PI_Classes_of_Procedures;
*p++ = 2;
x = PV_Classes_Procedures_Balanced_ABM;
if (param->full_duplex)
if (param->full_duplex == 1)
x |= PV_Classes_Procedures_Full_Duplex;
else
else // includes G_UNKNOWN
x |= PV_Classes_Procedures_Half_Duplex;
*p++ = (x >> 8) & 0xff;
*p++ = x & 0xff;
// "HDLC Optional Functions" contains REJ/SREJ & modulo 8/128.
// We always send this.
// Watch out for unknown values and do something reasonable.
*p++ = PI_HDLC_Optional_Functions;
*p++ = 3;
@ -396,7 +438,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
PV_HDLC_Optional_Functions_16_bit_FCS |
PV_HDLC_Optional_Functions_Synchronous_Tx;
if (param->rej == implicit_reject || param->rej == selective_reject_reject)
if (param->rej == implicit_reject || param->rej == selective_reject_reject || param->rej == G_UNKNOWN)
x |= PV_HDLC_Optional_Functions_REJ_cmd_resp;
if (param->rej == selective_reject || param->rej == selective_reject_reject)
@ -404,13 +446,18 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
if (param->modulo == modulo_128)
x |= PV_HDLC_Optional_Functions_Modulo_128;
else
else // includes G_UNKNOWN
x |= PV_HDLC_Optional_Functions_Modulo_8;
*p++ = (x >> 16) & 0xff;
*p++ = (x >> 8) & 0xff;
*p++ = x & 0xff;
// The rest are skipped if undefined values.
// "I Field Length Rx" - max I field length acceptable to me.
// This is in bits. 8191 would be max number of bytes to fit in field.
if (param->i_field_length_rx != G_UNKNOWN) {
*p++ = PI_I_Field_Length_Rx;
*p++ = 2;
@ -419,12 +466,16 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
*p++ = x & 0xff;
}
// "Window Size Rx"
if (param->window_size_rx != G_UNKNOWN) {
*p++ = PI_Window_Size_Rx;
*p++ = 1;
*p++ = param->window_size_rx;
}
// "Ack Timer" milliseconds. We could handle up to 65535 here.
if (param->ack_timer != G_UNKNOWN) {
*p++ = PI_Ack_Timer;
*p++ = 2;
@ -432,6 +483,8 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
*p++ = param->ack_timer & 0xff;
}
// "Retries."
if (param->retries != G_UNKNOWN) {
*p++ = PI_Retries;
*p++ = 1;
@ -439,7 +492,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
}
len = p - info;
assert (len <= 27);
return (len);
} /* end xid_encode */
@ -508,14 +561,19 @@ static unsigned char example[27] = {
int main (int argc, char *argv[]) {
struct ax25_param_s param;
struct ax25_param_s param2;
struct xid_param_s param;
struct xid_param_s param2;
int n;
unsigned char info[40];
unsigned char info[40]; // Currently max of 27 but things can change.
char desc[100]; // 80 is not adequate.
/* parse example. */
n = xid_parse (example, sizeof(example), &param);
n = xid_parse (example, sizeof(example), &param, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
text_color_set (DW_COLOR_ERROR);
@ -548,40 +606,96 @@ int main (int argc, char *argv[]) {
param.modulo = modulo_8;
param.i_field_length_rx = 2048;
param.window_size_rx = 3;
param.ack_timer = 3000;
param.retries = 10;
param.ack_timer = 1234;
param.retries = 12;
n = xid_encode (&param, info);
n = xid_parse (info, n, &param2);
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == 1);
assert (param2.rej == implicit_reject);
assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == 2048);
assert (param2.window_size_rx == 3);
assert (param2.ack_timer == 3000);
assert (param2.retries == 10);
assert (param2.ack_timer == 1234);
assert (param2.retries == 12);
/* Finally the third possbility for rej. */
/* The third possbility for rej. We don't use this. */
param.full_duplex = 0;
param.rej = selective_reject;
param.modulo = modulo_8;
param.i_field_length_rx = 256;
param.i_field_length_rx = 61;
param.window_size_rx = 4;
param.ack_timer = 3000;
param.retries = 10;
param.ack_timer = 5555;
param.retries = 9;
n = xid_encode (&param, info);
n = xid_parse (info, n, &param2);
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == 0);
assert (param2.rej == selective_reject);
assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == 256);
assert (param2.i_field_length_rx == 61);
assert (param2.window_size_rx == 4);
assert (param2.ack_timer == 3000);
assert (param2.retries == 10);
assert (param2.ack_timer == 5555);
assert (param2.retries == 9);
/* Specify some and not others. */
param.full_duplex = 0;
param.rej = selective_reject;
param.modulo = modulo_8;
param.i_field_length_rx = G_UNKNOWN;
param.window_size_rx = G_UNKNOWN;
param.ack_timer = 999;
param.retries = G_UNKNOWN;
n = xid_encode (&param, info);
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == 0);
assert (param2.rej == selective_reject);
assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == G_UNKNOWN);
assert (param2.window_size_rx == G_UNKNOWN);
assert (param2.ack_timer == 999);
assert (param2.retries == G_UNKNOWN);
/* Default values for empty info field. */
n = 0;
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == G_UNKNOWN);
assert (param2.rej == G_UNKNOWN);
assert (param2.modulo == G_UNKNOWN);
assert (param2.i_field_length_rx == G_UNKNOWN);
assert (param2.window_size_rx == G_UNKNOWN);
assert (param2.ack_timer == G_UNKNOWN);
assert (param2.retries == G_UNKNOWN);
text_color_set (DW_COLOR_REC);
dw_printf ("XID test: Success.\n");

31
xid.h Normal file
View File

@ -0,0 +1,31 @@
/* xid.h */
#include "ax25_pad.h" // for enum ax25_modulo_e
struct xid_param_s {
int full_duplex;
// Order is important because negotiation keeps the lower value.
// We will support only 1 & 2.
enum rej_e {implicit_reject=1, selective_reject=2, selective_reject_reject=3 } rej;
enum ax25_modulo_e modulo;
int i_field_length_rx; /* In bytes. XID has it in bits. */
int window_size_rx;
int ack_timer; /* "T1" in mSec. */
int retries; /* "N1" */
};
int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, char *desc, int desc_size);
int xid_encode (struct xid_param_s *param, unsigned char *info);

585
xmit.c
View File

@ -2,7 +2,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013, 2014, 2015 John Langner, WB2OSZ
// Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -72,6 +72,7 @@
#include "ptt.h"
#include "dtime_now.h"
#include "morse.h"
#include "xid.h"
@ -107,6 +108,9 @@ static int xmit_bits_per_sec[MAX_CHANS]; /* Data transmission rate. */
static int g_debug_xmit_packet; /* print packet in hexadecimal form for debugging. */
// TODO: When this was first written, bits/sec was same as baud.
// Need to revisit this for PSK modes where they are not the same.
#define BITS_TO_MS(b,ch) (((b)*1000)/xmit_bits_per_sec[(ch)])
@ -130,8 +134,9 @@ static dw_mutex_t audio_out_dev_mutex[MAX_ADEVS];
static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist);
static void xmit_ax25_frames (int c, int p, packet_t pp);
static int wait_for_clear_channel (int channel, int slotttime, int persist);
static void xmit_ax25_frames (int c, int p, packet_t pp, int max_bundle);
static int send_one_frame (int c, int p, packet_t pp);
static void xmit_speech (int c, packet_t pp);
static void xmit_morse (int c, packet_t pp, int wpm);
@ -346,6 +351,62 @@ void xmit_set_txtail (int channel, int value)
}
}
/*-------------------------------------------------------------------
*
* Name: frame_flavor
*
* Purpose: Separate frames into different flavors so we can decide
* which can be bundled into a single transmission and which should
* be sent separately.
*
* Inputs: pp - Packet object.
*
* Returns: Flavor, one of:
*
* FLAVOR_SPEECH - Destination address is SPEECH.
* FLAVOR_MORSE - Destination address is MORSE.
* FLAVOR_APRS_NEW - APRS original, i.e. not digipeating.
* FLAVOR_APRS_DIGI - APRS digipeating.
* FLAVOR_OTHER - Anything left over, i.e. connected mode.
*
*--------------------------------------------------------------------*/
typedef enum flavor_e { FLAVOR_APRS_NEW, FLAVOR_APRS_DIGI, FLAVOR_SPEECH, FLAVOR_MORSE, FLAVOR_OTHER } flavor_t;
static flavor_t frame_flavor (packet_t pp)
{
if (ax25_is_aprs (pp)) { // UI frame, PID 0xF0.
// It's unfortunate APRS did not use its own special PID.
char dest[AX25_MAX_ADDR_LEN];
ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest);
if (strcmp(dest, "SPEECH") == 0) {
return (FLAVOR_SPEECH);
}
if (strcmp(dest, "MORSE") == 0) {
return (FLAVOR_MORSE);
}
/* Is there at least one digipeater AND has first one been used? */
/* I could be the first in the list or later. Doesn't matter. */
if (ax25_get_num_repeaters(pp) >= 1 && ax25_get_h(pp,AX25_REPEATER_1)) {
return (FLAVOR_APRS_DIGI);
}
return (FLAVOR_APRS_NEW);
}
return (FLAVOR_OTHER);
} /* end frame_flavor */
/*-------------------------------------------------------------------
*
* Name: xmit_thread
@ -367,6 +428,9 @@ void xmit_set_txtail (int channel, int value)
* rather than waiting random times to avoid collisions.
* The KPC-3 configuration option for this is "UIDWAIT OFF". (?)
*
* AX.25 connected mode also has a couple cases
* where "expedited" frames are sent.
*
* Low Priority -
*
* Other packets are sent after a random wait time
@ -382,6 +446,11 @@ void xmit_set_txtail (int channel, int value)
* each channel has its own thread.
* Add speech capability.
*
* Version 1.4: Rearranged logic for bundling multiple frames into a single transmission.
*
* The rule is that Speech, Morse Code, and APRS digipeated frames
* are all sent separately. The rest can be bundled.
*
*--------------------------------------------------------------------*/
#if __WIN32__
@ -390,119 +459,126 @@ static unsigned __stdcall xmit_thread (void *arg)
static void * xmit_thread (void *arg)
#endif
{
int c = (int)(long)arg; // channel number.
int chan = (int)(long)arg; // channel number.
packet_t pp;
int p;
int prio;
int ok;
/*
* These are for timing of a transmission.
* All are in usual unix time (seconds since 1/1/1970) but higher resolution
*/
while (1) {
tq_wait_while_empty (c);
tq_wait_while_empty (chan);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread, channel %d: woke up\n", c);
#endif
for (p=0; p<TQ_NUM_PRIO; p++) {
pp = tq_remove (c, p);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp);
#endif
if (pp != NULL) {
// Does this extra loop offer any benefit?
while (tq_peek(chan, TQ_PRIO_0_HI) != NULL || tq_peek(chan, TQ_PRIO_1_LO) != NULL) {
/*
* Wait for the channel to be clear.
* For the high priority queue, begin transmitting immediately.
* For the low priority queue, wait a random amount of time, in hopes
* of minimizing collisions.
* If there is something in the high priority queue, begin transmitting immediately.
* Otherwise, wait a random amount of time, in hopes of minimizing collisions.
*/
ok = wait_for_clear_channel (c, (p==TQ_PRIO_0_HI), xmit_slottime[c], xmit_persist[c]);
ok = wait_for_clear_channel (chan, xmit_slottime[chan], xmit_persist[chan]);
if (ok) {
prio = TQ_PRIO_1_LO;
pp = tq_remove (chan, TQ_PRIO_0_HI);
if (pp != NULL) {
prio = TQ_PRIO_0_HI;
}
else {
pp = tq_remove (chan, TQ_PRIO_1_LO);
}
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, pp);
#endif
// Shouldn't have NULL here but be careful.
if (pp != NULL) {
if (ok) {
/*
* Channel is clear and we have lock on output device.
*
* If destination is "SPEECH" send info part to speech synthesizer.
* If destination is "MORSE" send as morse code.
*/
char dest[AX25_MAX_ADDR_LEN];
int ssid = 0;
int ssid, wpm;
if (ax25_is_aprs (pp)) {
switch (frame_flavor(pp)) {
ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest);
case FLAVOR_SPEECH:
xmit_speech (chan, pp);
break;
case FLAVOR_MORSE:
ssid = ax25_get_ssid(pp, AX25_DESTINATION);
}
else {
strlcpy (dest, "", sizeof(dest));
}
if (strcmp(dest, "SPEECH") == 0) {
xmit_speech (c, pp);
}
else if (strcmp(dest, "MORSE") == 0) {
int wpm = ssid * 2;
if (wpm == 0) wpm = MORSE_DEFAULT_WPM;
wpm = (ssid > 0) ? (ssid * 2) : MORSE_DEFAULT_WPM;
// This is a bit of a hack so we don't respond too quickly for APRStt.
// It will be sent in high priority queue while a beacon wouldn't.
// Add a little delay so user has time release PTT after sending #.
// This and default txdelay would give us a second.
if (p == TQ_PRIO_0_HI) {
if (prio == TQ_PRIO_0_HI) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("APRStt morse xmit delay hack...\n");
SLEEP_MS (700);
}
xmit_morse (chan, pp, wpm);
break;
xmit_morse (c, pp, wpm);
}
else {
xmit_ax25_frames (c, p, pp);
}
case FLAVOR_APRS_DIGI:
xmit_ax25_frames (chan, prio, pp, 1); /* 1 means don't bundle */
break;
dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(c)]));
case FLAVOR_APRS_NEW:
case FLAVOR_OTHER:
default:
xmit_ax25_frames (chan, prio, pp, 256);
break;
}
else {
// Corresponding lock is in wait_for_clear_channel.
dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(chan)]));
}
else {
/*
* Timeout waiting for clear channel.
* Discard the packet.
* Display with ERROR color rather than XMIT color.
*/
char stemp[1024]; /* max size needed? */
int info_len;
unsigned char *pinfo;
char stemp[1024]; /* max size needed? */
int info_len;
unsigned char *pinfo;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Waited too long for clear channel. Discarding packet below.\n");
text_color_set(DW_COLOR_ERROR);
dw_printf ("Waited too long for clear channel. Discarding packet below.\n");
ax25_format_addrs (pp, stemp);
ax25_format_addrs (pp, stemp);
info_len = ax25_get_info (pp, &pinfo);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_INFO);
dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
text_color_set(DW_COLOR_INFO);
dw_printf ("[%d%c] ", chan, (prio==TQ_PRIO_0_HI) ? 'H' : 'L');
dw_printf ("%s", stemp); /* stations followed by : */
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n");
ax25_delete (pp);
dw_printf ("%s", stemp); /* stations followed by : */
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n");
ax25_delete (pp);
} /* wait for clear channel. */
} /* for high priority then low priority */
}
}
} /* wait for clear channel error. */
} /* Have pp */
} /* while queue not empty */
} /* while 1 */
return 0; /* unreachable but quiet the warning. */
@ -517,28 +593,31 @@ static void * xmit_thread (void *arg)
* Purpose: After we have a clear channel, and possibly waited a random time,
* we transmit one or more frames.
*
* Inputs: c - Channel number.
* Inputs: chan - Channel number.
*
* p - Priority of the queue.
* prio - Priority of the first frame.
* Subsequent frames could be different.
*
* pp - Packet object pointer.
* It will be deleted so caller should not try
* to reference it after this.
*
* max_bundle - Max number of frames to bundle into one transmission.
*
* Description: Turn on transmitter.
* Send flags for TXDELAY time.
* Send the first packet, given by pp.
* Possibly send more packets from the same queue.
* Possibly send more packets from either queue.
* Send flags for TXTAIL time.
* Turn off transmitter.
*
*
* How many frames in one transmission?
* How many frames in one transmission? (for APRS)
*
* Should we send multiple frames in one transmission if we
* have more than one sitting in the queue? At first I was thinking
* this would help reduce channel congestion. I don't recall seeing
* anything in the specifications allowing or disallowing multiple
* anything in the APRS specifications allowing or disallowing multiple
* frames in one transmission. I can think of some scenarios
* where it might help. I can think of some where it would
* definitely be counter productive.
@ -558,21 +637,27 @@ static void * xmit_thread (void *arg)
* Version 0.9: Earlier versions always sent one frame per transmission.
* This was fine for APRS but more and more people are now
* using this as a KISS TNC for connected protocols.
* Rather than having a MAXFRAME configuration file item,
* Rather than having a configuration file item,
* we try setting the maximum number automatically.
* 1 for digipeated frames, 7 for others.
*
* Version 1.4: Lift the limit. We could theoretically have a window size up to 127.
* If another section pumps out that many quickly we shouldn't
* break it up here. Empty out both queues with some exceptions.
*
* Digipeated APRS, Speech, and Morse code should have
* their own separate transmissions.
* Everything else can be bundled together.
* Different priorities can share a single transmission.
* Once we have control of the channel, we might as well keep going.
* [High] Priority frames will always go to head of the line,
*
*--------------------------------------------------------------------*/
static void xmit_ax25_frames (int c, int p, packet_t pp)
static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle)
{
unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
int flen;
char stemp[1024]; /* max size needed? */
int info_len;
unsigned char *pinfo;
int pre_flags, post_flags;
int num_bits; /* Total number of bits in transmission */
/* including all flags and bit stuffing. */
@ -580,8 +665,7 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
int already;
int wait_more;
int maxframe; /* Maximum number of frames for one transmission. */
int numframe; /* Number of frames sent during this transmission. */
int numframe = 0; /* Number of frames sent during this transmission. */
/*
* These are for timing of a transmission.
@ -593,49 +677,6 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
int nb;
maxframe = (p == TQ_PRIO_0_HI) ? 1 : 7;
/*
* Print trasmitted packet. Prefix by channel and priority.
* Do this before we get into the time critical part.
*/
ax25_format_addrs (pp, stemp);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_XMIT);
dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
dw_printf ("%s", stemp); /* stations followed by : */
/* Demystify non-APRS. Use same format for received frames in direwolf.c. */
if ( ! ax25_is_aprs(pp)) {
ax25_frame_type_t ftype;
cmdres_t cr;
char desc[32];
int pf;
int nr;
int ns;
ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
(void)ftype;
dw_printf ("(%s)", desc);
}
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n");
(void)ax25_check_addresses (pp);
/* Optional hex dump of packet. */
if (g_debug_xmit_packet) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("------\n");
ax25_hex_dump (pp);
dw_printf ("------\n");
}
/*
* Turn on transmitter.
* Start sending leading flag bytes.
@ -647,26 +688,26 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]);
dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", chan, xmit_bits_per_sec[chan]);
#endif
ptt_set (OCTYPE_PTT, c, 1);
ptt_set (OCTYPE_PTT, chan, 1);
pre_flags = MS_TO_BITS(xmit_txdelay[c] * 10, c) / 8;
num_bits = hdlc_send_flags (c, pre_flags, 0);
pre_flags = MS_TO_BITS(xmit_txdelay[chan] * 10, chan) / 8;
num_bits = hdlc_send_flags (chan, pre_flags, 0);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[c], pre_flags, num_bits);
dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[chan], pre_flags, num_bits);
#endif
/*
* Transmit the frame.
*/
flen = ax25_pack (pp, fbuf);
assert (flen >= 1 && flen <= sizeof(fbuf));
nb = hdlc_send_frame (c, fbuf, flen);
nb = send_one_frame (chan, prio, pp);
num_bits += nb;
numframe = 1;
if (nb > 0) numframe++;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
@ -674,68 +715,84 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
ax25_delete (pp);
/*
* Additional packets if available and not exceeding max.
* See if we can bundle additional frames into this transmission.
*/
while (numframe < maxframe && tq_count (c,p) > 0) {
pp = tq_remove (c, p);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp);
#endif
ax25_format_addrs (pp, stemp);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_XMIT);
dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
dw_printf ("%s", stemp); /* stations followed by : */
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n");
(void)ax25_check_addresses (pp);
if (g_debug_xmit_packet) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("------\n");
ax25_hex_dump (pp);
dw_printf ("------\n");
}
int done = 0;
while (numframe < max_bundle && ! done) {
/*
* Transmit the frame.
* Peek at what is available.
* Don't remove from queue yet because it might not be eligible.
*/
flen = ax25_pack (pp, fbuf);
assert (flen >= 1 && flen <= sizeof(fbuf));
nb = hdlc_send_frame (c, fbuf, flen);
num_bits += nb;
numframe++;
prio = TQ_PRIO_1_LO;
pp = tq_peek (chan, TQ_PRIO_0_HI);
if (pp != NULL) {
prio = TQ_PRIO_0_HI;
}
else {
pp = tq_peek (chan, TQ_PRIO_1_LO);
}
if (pp != NULL) {
switch (frame_flavor(pp)) {
case FLAVOR_SPEECH:
case FLAVOR_MORSE:
case FLAVOR_APRS_DIGI:
default:
done = 1; // not eligible for bundling.
break;
case FLAVOR_APRS_NEW:
case FLAVOR_OTHER:
pp = tq_remove (chan, prio);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, pp);
#endif
ax25_delete (pp);
nb = send_one_frame (chan, prio, pp);
num_bits += nb;
if (nb > 0) numframe++;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
#endif
ax25_delete (pp);
break;
}
}
else {
done = 1;
}
}
/*
* Need TXTAIL because we don't know exactly when the sound is done.
*/
post_flags = MS_TO_BITS(xmit_txtail[c] * 10, c) / 8;
nb = hdlc_send_flags (c, post_flags, 1);
post_flags = MS_TO_BITS(xmit_txtail[chan] * 10, chan) / 8;
nb = hdlc_send_flags (chan, post_flags, 1);
num_bits += nb;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[c], post_flags, nb, num_bits);
dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[chan], post_flags, nb, num_bits);
#endif
/*
* While demodulating is CPU intensive, generating the tones is not.
* Example: on the RPi, with 50% of the CPU taken with two receive
* Example: on the RPi model 1, with 50% of the CPU taken with two receive
* channels, a transmission of more than a second is generated in
* about 40 mS of elapsed real time.
*/
audio_wait(ACHAN2ADEV(c));
audio_wait(ACHAN2ADEV(chan));
/*
* Ideally we should be here just about the time when the audio is ending.
@ -744,7 +801,7 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
* Calculate how long the frame(s) should take in milliseconds.
*/
duration = BITS_TO_MS(num_bits, c);
duration = BITS_TO_MS(num_bits, chan);
/*
* See how long it has been since PTT was turned on.
@ -786,12 +843,123 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
dw_printf ("xmit_thread: Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", (int) ((time_now - time_ptt) * 1000.), duration);
#endif
ptt_set (OCTYPE_PTT, c, 0);
ptt_set (OCTYPE_PTT, chan, 0);
} /* end xmit_ax25_frames */
/*-------------------------------------------------------------------
*
* Name: send_one_frame
*
* Purpose: Send one AX.25 frame.
*
* Inputs: c - Channel number.
*
* p - Priority.
*
* pp - Packet object pointer. Caller will delete it.
*
* Returns: Number of bits transmitted.
*
* Description: Caller is responsible for activiating PTT, TXDELAY,
* deciding how many frames can be in one transmission,
* deactivating PTT.
*
*--------------------------------------------------------------------*/
static int send_one_frame (int c, int p, packet_t pp)
{
unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
int flen;
char stemp[1024]; /* max size needed? */
int info_len;
unsigned char *pinfo;
int nb;
if (ax25_is_null_frame(pp)) {
return(0);
}
ax25_format_addrs (pp, stemp);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_XMIT);
dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
dw_printf ("%s", stemp); /* stations followed by : */
/* Demystify non-APRS. Use same format for received frames in direwolf.c. */
if ( ! ax25_is_aprs(pp)) {
ax25_frame_type_t ftype;
cmdres_t cr;
char desc[80];
int pf;
int nr;
int ns;
ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
dw_printf ("(%s)", desc);
if (ftype == frame_type_U_XID) {
struct xid_param_s param;
char info2text[100];
xid_parse (pinfo, info_len, &param, info2text, sizeof(info2text));
dw_printf (" %s\n", info2text);
}
else {
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n");
}
}
else {
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n");
}
(void)ax25_check_addresses (pp);
/* Optional hex dump of packet. */
if (g_debug_xmit_packet) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("------\n");
ax25_hex_dump (pp);
dw_printf ("------\n");
}
/*
* Transmit the frame.
*/
flen = ax25_pack (pp, fbuf);
assert (flen >= 1 && flen <= sizeof(fbuf));
int send_invalid_fcs2 = 0;
if (save_audio_config_p->xmit_error_rate != 0) {
float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
if (save_audio_config_p->xmit_error_rate / 100.0 > r) {
send_invalid_fcs2 = 1;
text_color_set(DW_COLOR_INFO);
dw_printf ("Intentionally sending invalid CRC for frame above. Xmit Error rate = %d per cent.\n", save_audio_config_p->xmit_error_rate);
}
}
nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2);
return (nb);
} /* end send_one_frame */
/*-------------------------------------------------------------------
*
* Name: xmit_speech
@ -814,8 +982,6 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
static void xmit_speech (int c, packet_t pp)
{
int info_len;
unsigned char *pinfo;
@ -932,8 +1098,6 @@ int xmit_speak_it (char *script, int c, char *orig_msg)
static void xmit_morse (int c, packet_t pp, int wpm)
{
int info_len;
unsigned char *pinfo;
@ -960,15 +1124,7 @@ static void xmit_morse (int c, packet_t pp, int wpm)
* Purpose: Wait for the radio channel to be clear and any
* additional time for collision avoidance.
*
*
*
* Inputs: channel - Radio channel number.
*
* nowait - Should be true for the high priority queue
* (packets being digipeated). This will
* allow transmission immediately when the
* channel is clear rather than waiting a
* random amount of time.
* Inputs: chan - Radio channel number.
*
* slottime - Amount of time to wait for each iteration
* of the waiting algorithm. 10 mSec units.
@ -977,14 +1133,12 @@ static void xmit_morse (int c, packet_t pp, int wpm)
*
* Returns: 1 for OK. 0 for timeout.
*
* Description:
*
* New in version 1.2: also obtain a lock on audio out device.
* Description: New in version 1.2: also obtain a lock on audio out device.
*
* Transmit delay algorithm:
*
* Wait for channel to be clear.
* Return if nowait is true.
* If anything in high priority queue, bail out of the following.
*
* Wait slottime * 10 milliseconds.
* Generate an 8 bit random number in range of 0 - 255.
@ -1015,17 +1169,13 @@ static void xmit_morse (int c, packet_t pp, int wpm)
#define WAIT_TIMEOUT_MS (60 * 1000)
#define WAIT_CHECK_EVERY_MS 10
static int wait_for_clear_channel (int channel, int nowait, int slottime, int persist)
static int wait_for_clear_channel (int chan, int slottime, int persist)
{
int r;
int n;
n = 0;
int n = 0;
start_over_again:
while (hdlc_rec_data_detect_any(channel)) {
while (hdlc_rec_data_detect_any(chan)) {
SLEEP_MS(WAIT_CHECK_EVERY_MS);
n++;
if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {
@ -1033,41 +1183,52 @@ start_over_again:
}
}
//TODO1.2: rethink dwait.
//TODO: rethink dwait.
/*
* Added in version 1.2 - for transceivers that can't
* turn around fast enough when using squelch and VOX.
*/
if (save_audio_config_p->achan[channel].dwait > 0) {
SLEEP_MS (save_audio_config_p->achan[channel].dwait * 10);
if (save_audio_config_p->achan[chan].dwait > 0) {
SLEEP_MS (save_audio_config_p->achan[chan].dwait * 10);
}
if (hdlc_rec_data_detect_any(channel)) {
if (hdlc_rec_data_detect_any(chan)) {
goto start_over_again;
}
if ( ! nowait) {
/*
* Wait random time.
* Proceed to transmit sooner if anything shows up in high priority queue.
*/
while (tq_peek(chan, TQ_PRIO_0_HI) == NULL) {
int r;
while (1) {
SLEEP_MS (slottime * 10);
SLEEP_MS (slottime * 10);
if (hdlc_rec_data_detect_any(channel)) {
goto start_over_again;
}
r = rand() & 0xff;
if (r <= persist) {
break;
}
if (hdlc_rec_data_detect_any(chan)) {
goto start_over_again;
}
r = rand() & 0xff;
if (r <= persist) {
break;
}
}
// TODO1.2
/*
* This is to prevent two channels from transmitting at the same time
* thru a stereo audio device.
* We are not clever enough to combine two audio streams.
* They must go out one at a time.
* Documentation recommends using separate audio device for each channel rather than stereo.
* That also allows better use of multiple cores for receiving.
*/
while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(channel)]))) {
// TODO: review this.
while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(chan)]))) {
SLEEP_MS(WAIT_CHECK_EVERY_MS);
n++;
if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {