diff --git a/CHANGES.md b/CHANGES.md index a2d091e..32f1fd3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,36 @@ # Revision History # +---------- + +## Version 1.4 -- Development snapshot G -- January 2017 ## + +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: ### + +- New client side packet filter to select "messages" only to stations that have been heard nearby recently. This is now the default if no IS to RF filter is specified. + +- Expanded debug options so you can understand what is going on with packet filtering. + +- Added new document ***Successful-APRS-IGate-Operation.pdf*** with IGate background, configuration, and troubleshooting tips. + + +---------- + +## Version 1.4 -- Development snapshot F -- December 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. + + +### Bugs Fixed: ### + +- -p command line option caused segmentation fault with glibc >= 2.24. + + + + ---------- diff --git a/Makefile.linux b/Makefile.linux index 173309d..5af2792 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -13,13 +13,25 @@ all : $(APPS) direwolf.desktop direwolf.conf CC := gcc +# Just for fun, let's see how clang compares to gcc. First install like this: +# sudo apt-get update +# apt-cache search clang +# sudo apt-get install clang-3.5 +# +# CC := clang-3.5 + # _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24. # Explanation here: https://github.com/wb2osz/direwolf/issues/62 # There are a few source files where it had been necessary to define __USE_XOPEN2KXSI, # __USE_XOPEN, or _POSIX_C_SOURCE. Doesn't seem to be needed after adding this. -CFLAGS := -O3 -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 +CFLAGS := -O3 -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 -Wall + +# That was fine for a recent Ubuntu and Raspbian Jessie. +# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set. + +CFLAGS += -D_BSD_SOURCE LDFLAGS := -lm -lpthread -lrt @@ -113,7 +125,26 @@ endif # One article said it was added with gcc 4.2 but I haven't verified that. # -# Add -ffastmath in only if compiler version recognizes it. +# ---------- How does clang compare? - Ubuntu x86_64 ---------- +# +# I keep hearing a lot about "clang." Let's see how it compares with gcc. +# Simply use different compiler and keep all options the same. +# +# test case: atest 02_Track_2.wav +# +# gcc 4.8.4: 988 packets decoded in 40.503 seconds. 38.3 x realtime +# 988 packets decoded in 40.403 seconds. 38.4 x realtime +# +# clang 3.8.0-2: 988 packets decoded in 77.454 seconds. 20.0 x realtime +# 988 packets decoded in 77.232 seconds. 20.1 x realtime +# +# I'm not impressed. Almost twice as long. Maybe we need to try some other compiler options. +# -march=native did not help. +# Makefile.macosx, which uses clang, has these: +# -fvectorize -fslp-vectorize -fslp-vectorize-aggressive +# Those did not help. +# + useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math) ifneq ($(useffast),) @@ -146,7 +177,7 @@ endif # # -# ---------- ARM - Raspberry Pi 2 ---------- +# ---------- ARM - Raspberry Pi 2 ---------- # # Besides the higher clock speed, the Raspberry Pi 2 has the NEON instruction set # which which should make things considerably faster. @@ -200,10 +231,27 @@ endif # cause compatibility issues for those with older computers. # +# ---------- How does clang compare? - ARM - Raspberry Pi 2 ---------- +# +# I keep hearing a lot about "clang." Let's see how it compares with gcc. +# Simply use different compiler and keep all options the same. +# +# test case: atest 02_Track_2.wav +# +# gcc 4.9.2-10: 988 packets decoded in 353.025 seconds. 4.4 x realtime +# 988 packets decoded in 352.752 seconds. 4.4 x realtime +# +# clang 3.5.0-10: 988 packets decoded in 825.879 seconds. 1.9 x realtime +# 988 packets decoded in 831.469 seconds. 1.9 x realtime +# +# There might be a different set of options which produce faster code +# but the initial test doesn't look good. About 2.3 times as slow. # If you want to use OSS (for FreeBSD, OpenBSD) instead of # ALSA (for Linux), comment out (or remove) the two lines below. +# TODO: Can we automate this somehow? + CFLAGS += -DUSE_ALSA LDFLAGS += -lasound @@ -220,6 +268,8 @@ endif # Uncomment following lines to enable hamlib support. +# TODO: automate this too. See if hamlib has been installed. + #CFLAGS += -DUSE_HAMLIB #LDFLAGS += -lhamlib diff --git a/Makefile.macosx b/Makefile.macosx index bcdab97..262ed45 100644 --- a/Makefile.macosx +++ b/Makefile.macosx @@ -77,6 +77,12 @@ CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN) CFLAGS := -Os -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 $(EXTRA_CFLAGS) +# That was fine for a recent Ubuntu and Raspbian Jessie. +# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set. + +CFLAGS += -D_BSD_SOURCE + + # $(info $$CC is [${CC}]) # diff --git a/Makefile.win b/Makefile.win index 156e291..240df0f 100644 --- a/Makefile.win +++ b/Makefile.win @@ -26,19 +26,30 @@ all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_pa # -Ofast was added in gcc 4.6 which was the MinGW version back in 2012. CC := gcc -CFLAGS := -Wall -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC -#CFLAGS := -Wall -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC +CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC -Wall -Wlogical-op AR := ar CFLAGS += -g -# For version 1.4, we upgrade from 4.6.2 to 4.9.3. +# For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3. # gcc 4.8 adds these. Try them just for fun. # No, it needs libasan which is not on Windows. #CFLAGS += -fsanitize=address -fno-omit-frame-pointer +# Helpful for the demodulators. Overkill for non-hot spots. +#CFLAGS += -Wdouble-promotion +# Don't have the patience for this right now. +#CFLAGS += -Wextra + +# Continue working on these. +CFLAGS += -Wsign-compare +CFLAGS += -Wuninitialized +CFLAGS += -Wold-style-declaration +# CFLAGS += -fdelete-null-pointer-checks -Wnull-dereference ---not recognized +#CFLAGS += -Wold-style-definition +#-Wmissing-prototypes # # Let's see impact of various optimization levels. @@ -178,7 +189,7 @@ log2gpx : log2gpx.c textcolor.o misc.a # Test application to generate sound. -gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o textcolor.o dsp.o misc.a regex.a +gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o dtmf.o textcolor.o dsp.o misc.a regex.a $(CC) $(CFLAGS) -o $@ $^ @@ -219,7 +230,7 @@ utm.o : geotranz/utm.c # functions supplied by the gnu C library. # For the native WIN32 version, we need to use our own copy. # These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm -# +# Consider upgrading from https://www.gnu.org/software/libc/sources.html regex.a : regex.o ar -cr $@ $^ @@ -228,7 +239,8 @@ regex.o : regex/regex.c $(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^ -# There are several string functios found in Linux + +# There are several string functions found in Linux # but not on Windows. Need to provide our own copy. misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o @@ -256,7 +268,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 xidtest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 +check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest 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. @@ -273,7 +285,15 @@ check-modem300 : gen_packets atest atest -B300 -F1 -L71 -G75 test3.wav rm test3.wav +#FIXME: test full amplitude. + check-modem9600 : gen_packets atest + gen_packets -B9600 -a 170 -o test96.wav + sleep 1 + atest -B9600 -F0 -L4 -G4 test96.wav + sleep 1 + rm test96.wav + sleep 1 gen_packets -B9600 -n 100 -o test96.wav sleep 1 atest -B9600 -F0 -L50 -G54 test96.wav @@ -282,6 +302,12 @@ check-modem9600 : gen_packets atest rm test96.wav check-modem19200 : gen_packets atest + gen_packets -r 96000 -B19200 -a 170 -o test19.wav + sleep 1 + atest -B19200 -F0 -L4 test19.wav + sleep 1 + rm test19.wav + sleep 1 gen_packets -r 96000 -B19200 -n 100 -o test19.wav sleep 1 atest -B19200 -F0 -L55 -G59 test19.wav @@ -333,8 +359,8 @@ atest9 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c # Unit test for inner digipeater algorithm .PHONY: dtest -dtest : digipeater.c dedupe.c \ - pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \ +dtest : digipeater.c dedupe.c pfilter.c \ + ax25_pad.o fcs_calc.o tq.o textcolor.o \ decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ ./dtest @@ -417,6 +443,13 @@ xidtest : xid.c textcolor.o misc.a ./xidtest rm xidtest.exe +# Unit Test for DTMF encode/decode. + +.PHONY: dtmftest +dtmftest : dtmf.c textcolor.o + $(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ + ./dtmftest + rm dtmftest.exe # ------------------------------ Other manual testing & experimenting ------------------------------- @@ -529,7 +562,7 @@ walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \ xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \ hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \ multi_modem.o demod.o demod_afsk.o demod_psk.c demod_9600.o rdq.o \ - server.o morse.o audio_stats.o dtime_now.o dlq.o \ + server.o morse.o dtmf.o audio_stats.o dtime_now.o dlq.o \ regex.a misc.a $(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32 diff --git a/ax25_pad.c b/ax25_pad.c index aceeee4..bbc5879 100644 --- a/ax25_pad.c +++ b/ax25_pad.c @@ -1820,6 +1820,64 @@ void ax25_format_addrs (packet_t this_p, char *result) } +/*------------------------------------------------------------------ + * + * Function: ax25_format_via_path + * + * Purpose: Format via path addresses suitable for printing. + * + * Inputs: Current packet. + * + * result_size - Number of bytes available for result. + * We can have up to 8 addresses x 9 characters + * plus 7 commas, possible *, and nul = 81 minimum. + * + * Outputs: result - Digipeater field addresses combined into a single string of the form: + * + * "repeater, repeater ..." + * + * An asterisk is displayed after the last digipeater + * with the "H" bit set. e.g. If we hear RPT2, + * + * RPT1,RPT2*,RPT3 + * + * No asterisk means the source is being heard directly. + * + *------------------------------------------------------------------*/ + +void ax25_format_via_path (packet_t this_p, char *result, size_t result_size) +{ + int i; + int heard; + char stemp[AX25_MAX_ADDR_LEN]; + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + *result = '\0'; + + /* Don't get upset if no addresses. */ + /* This will allow packets that do not comply to AX.25 format. */ + + if (this_p->num_addr == 0) { + return; + } + + heard = ax25_get_heard(this_p); + + for (i=(int)AX25_REPEATER_1; inum_addr; i++) { + if (i > (int)AX25_REPEATER_1) { + strlcat (result, ",", result_size); + } + ax25_get_addr_with_ssid (this_p, i, stemp); + strlcat (result, stemp, result_size); + if (i == heard) { + strlcat (result, "*", result_size); + } + } + +} /* end ax25_format_via_path */ + + /*------------------------------------------------------------------ * * Function: ax25_pack diff --git a/ax25_pad.h b/ax25_pad.h index 51d3a20..718fa9e 100644 --- a/ax25_pad.h +++ b/ax25_pad.h @@ -410,6 +410,7 @@ 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 void ax25_format_via_path (packet_t this_p, char *result, size_t result_size); extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]); diff --git a/beacon.c b/beacon.c index 1d0ca49..a5a2bad 100644 --- a/beacon.c +++ b/beacon.c @@ -954,7 +954,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) case SENDTO_RECV: - /* Simulated reception. */ + /* Simulated reception from radio. */ memset (&alevel, 0xff, sizeof(alevel)); dlq_rec_frame (g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, ""); diff --git a/cdigipeater.c b/cdigipeater.c index a28ab07..e531efd 100644 --- a/cdigipeater.c +++ b/cdigipeater.c @@ -241,14 +241,6 @@ static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, c 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); } } diff --git a/config.c b/config.c index 1293535..db9325f 100644 --- a/config.c +++ b/config.c @@ -138,7 +138,7 @@ static const struct units_s { { "league", 4828.032 }, { "lea", 4828.032 } }; -#define NUM_UNITS (sizeof(units) / sizeof(struct units_s)) +#define NUM_UNITS ((int)((sizeof(units) / sizeof(struct units_s)))) static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config); @@ -857,6 +857,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_igate_config->tx_chan = -1; /* IS->RF not enabled */ p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_DEFAULT; p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_DEFAULT; + p_igate_config->igmsp = 1; /* People find this confusing. */ @@ -2503,7 +2504,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTPOINT pattern must begin with upper case 'B'.\n", line); } - for (j=1; jttloc_len--; continue; } - for (j=1; j= 'x' && otemp[j] <= 'z') { d_count[otemp[j]-'x']++; } @@ -3903,7 +3904,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } /* - * IGFILTER - Filter for messages from IGate server + * IGFILTER - IGate Server side filters. * * IGFILTER filter-spec ... */ @@ -3973,6 +3974,37 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } + +/* + * IGMSP - Number of times to send position of message sender. + * + * IGMSP n + */ + + else if (strcasecmp(t, "IGMSP") == 0) { + + t = split(NULL,0); + if (t != NULL) { + + int n = atoi(t); + if (n >= 0 && n <= 10) { + p_igate_config->igmsp = n; + } + else { + p_igate_config->satgate_delay = 1; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable number of times for message sender position. Using default 1.\n", line); + } + } + else { + p_igate_config->satgate_delay = 1; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing number of times for message sender position. Using default 1.\n", line); + } + } + + + /* * SATGATE - Special SATgate mode to delay packets heard directly. * @@ -4576,6 +4608,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Config file: MYCALL must be set for receive channel %d before Rx IGate is allowed.\n", i); strlcpy (p_igate_config->t2_login, "", sizeof(p_igate_config->t2_login)); } + // Currently we can have only one transmit channel. + // This might be generalized someday to allow more. if (p_igate_config->tx_chan >= 0 && ( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 || strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "NOCALL") == 0 || @@ -4586,11 +4620,23 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_igate_config->tx_chan = -1; } } - } +// Apply default IS>RF IGate filter if none specified. New in 1.4. +// This will handle eventual case of multiple transmit channels. + + for (j=0; jachan[j].valid && strlen(p_igate_config->t2_login) > 0) { + if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) { + p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/30"); + } + } + } + +// Terrible hack. But what can we do? + if (p_misc_config->maxv22 < 0) { - p_misc_config->maxv22 = p_misc_config->retry / 2; + p_misc_config->maxv22 = p_misc_config->retry / 3; } } /* end config_init */ diff --git a/decode_aprs.c b/decode_aprs.c index 23d5e4b..64d8c02 100644 --- a/decode_aprs.c +++ b/decode_aprs.c @@ -1725,25 +1725,27 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) { struct aprs_item_s { - char dti; /* ) */ - char name[9]; /* Actually variable length 3 - 9 bytes. */ + char dti; /* ')' */ + char name[10]; /* Actually variable length 3 - 9 bytes. */ /* DON'T refer to the rest of this structure; */ /* the offsets will be wrong! */ + /* We make it 10 here so we don't get subscript out of bounds */ + /* warning when looking for following '!' or '_' character. */ - char live_killed; /* ! for live or _ for killed */ - position_t pos; - char comment[43]; /* First 7 bytes could be data extension. */ + char live_killed__; /* ! for live or _ for killed */ + position_t pos__; + char comment__[43]; /* First 7 bytes could be data extension. */ } *p; struct aprs_compressed_item_s { - char dti; /* ) */ - char name[9]; /* Actually variable length 3 - 9 bytes. */ + char dti; /* ')' */ + char name[10]; /* Actually variable length 3 - 9 bytes. */ /* DON'T refer to the rest of this structure; */ /* the offsets will be wrong! */ - char live_killed; /* ! for live or _ for killed */ - compressed_position_t cpos; - char comment[40]; /* No data extension in this case. */ + char live_killed__; /* ! for live or _ for killed */ + compressed_position_t cpos__; + char comment__[40]; /* No data extension in this case. */ } *q; diff --git a/digipeater.c b/digipeater.c index aa2f11e..9abbc2b 100644 --- a/digipeater.c +++ b/digipeater.c @@ -297,14 +297,6 @@ 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) != 1) { - -// TODO1.2: take out debug message -// Actually it turns out to be useful. -// Maybe add a quiet option to suppress it although no one has complained about it yet. -//#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Packet was rejected for digipeating from channel %d to %d by filter: %s\n", from_chan, to_chan, filter_str); -//#endif return(NULL); } } diff --git a/direwolf.c b/direwolf.c index 8a43b7d..0782c20 100644 --- a/direwolf.c +++ b/direwolf.c @@ -167,6 +167,7 @@ static struct tt_config_s tt_config; static const int audio_amplitude = 100; /* % of audio sample range. */ /* This translates to +-32k for 16 bit samples. */ + /* Currently no option to change this. */ static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */ static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */ @@ -204,6 +205,8 @@ int main (int argc, char *argv[]) int d_g_opt = 0; /* "-d g" option for GPS. Can be repeated for more detail. */ int d_o_opt = 0; /* "-d o" option for output control such as PTT and DCD. */ int d_i_opt = 0; /* "-d i" option for IGate. Repeat for more detail */ + int d_m_opt = 0; /* "-d m" option for mheard list. */ + int d_f_opt = 0; /* "-d f" option for filtering. Repeat for more detail. */ #if USE_HAMLIB int d_h_opt = 0; /* "-d h" option for hamlib debugging. Repeat for more detail */ #endif @@ -257,7 +260,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, "E", __DATE__); + dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "G", __DATE__); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); #if defined(ENABLE_GPSD) || defined(USE_HAMLIB) @@ -477,9 +480,11 @@ int main (int argc, char *argv[]) case 'p': d_p_opt = 1; break; // TODO: packet dump for xmit side. case 'o': d_o_opt++; ptt_set_debug(d_o_opt); break; case 'i': d_i_opt++; break; + case 'm': d_m_opt++; break; + case 'f': d_f_opt++; break; #if AX25MEMDEBUG - case 'm': ax25memdebug_set(); break; // Track down memory leak. Not documented. -#endif + case 'l': ax25memdebug_set(); break; // Track down memory Leak. Not documented. +#endif // Previously 'm' but that is now used for mheard. #if USE_HAMLIB case 'h': d_h_opt++; break; // Hamlib verbose level. #endif @@ -749,7 +754,7 @@ 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); - //FIXME//pfilter_init (&igate_config, 0); + pfilter_init (&igate_config, d_f_opt); ax25_link_init (&misc_config); /* @@ -778,7 +783,7 @@ int main (int argc, char *argv[]) */ log_init(misc_config.logdir); - mheard_init (0); // might add debug option someday. + mheard_init (d_m_opt); beacon_init (&audio_config, &misc_config, &igate_config); @@ -1057,8 +1062,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev // Add to list of stations heard over the radio. - mheard_save (chan, &A, pp, alevel, retries); - //FIXME//mheard_save_rf (chan, &A, pp, alevel, retries); + mheard_save_rf (chan, &A, pp, alevel, retries); // Convert to NMEA waypoint sentence if we have a location. @@ -1209,6 +1213,7 @@ static void usage (char **argv) dw_printf (" t t = Tracker beacon.\n"); dw_printf (" o o = output controls such as PTT and DCD.\n"); dw_printf (" i i = IGate.\n"); + dw_printf (" m m = Monitor heard station list.\n"); #if USE_HAMLIB dw_printf (" h h = hamlib increase verbose level.\n"); #endif diff --git a/doc/README.md b/doc/README.md index 3355f64..bcd0586 100644 --- a/doc/README.md +++ b/doc/README.md @@ -18,6 +18,11 @@ These dive into more detail for specialized topics or typical usage scenarios. +- [Successful APRS IGate Operation](Successful-APRS-IGate-Operation.pdf) + + Dire Wolf can serve as a gateway between the APRS radio network and APRS-IS servers on the Internet. + + This explains how it all works, proper configuration, and troubleshooting. - [APRStt Implementation Notes](APRStt-Implementation-Notes.pdf) diff --git a/doc/Successful-APRS-IGate-Operation.pdf b/doc/Successful-APRS-IGate-Operation.pdf new file mode 100644 index 0000000..ce8b3cb Binary files /dev/null and b/doc/Successful-APRS-IGate-Operation.pdf differ diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf index 717c5ac..5479d98 100644 Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ diff --git a/igate.c b/igate.c index 2d0cb0e..144a56b 100644 --- a/igate.c +++ b/igate.c @@ -99,6 +99,7 @@ #include "latlong.h" #include "pfilter.h" #include "dtime_now.h" +#include "mheard.h" #if __WIN32__ @@ -118,7 +119,7 @@ static packet_t dp_queue_head; static void satgate_delay_packet (packet_t pp, int chan); static void send_packet_to_server (packet_t pp, int chan); static void send_msg_to_server (const char *msg); -static void xmit_packet (char *message, int chan); +static void maybe_xmit_packet_from_igate (char *message, int chan); static void rx_to_ig_init (void); static void rx_to_ig_remember (packet_t pp); @@ -691,8 +692,11 @@ static void * connnect_thread (void *arg) // Try each address until we find one that is successful. for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); @@ -827,6 +831,9 @@ static void * connnect_thread (void *arg) } } + + exit(0); // Unreachable but stops compiler from complaining + // about function not returning a value. } /* end connnect_thread */ @@ -879,6 +886,10 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) /* * Check for filtering from specified channel to the IGate server. + * + * Should we do this after unwrapping the payload from a third party packet? + * In my experience, third party packets have only been seen coming from IGates. + * In that case, the payload will have TCPIP in the path and it will be dropped. */ if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { @@ -1066,7 +1077,12 @@ static void send_packet_to_server (packet_t pp, int chan) (void)(info_len); /* - * Do not relay if a duplicate of something sent recently. + * We will often see the same packet multiple times close together due to digipeating. + * The consensus seems to be that we should just send the first and drop the later duplicates. + * There is some dissent on this issue. http://www.tapr.org/pipermail/aprssig/2016-July/045907.html + * There could be some value to sending them all to provide information about digipeater paths. + * If you feel strongly about this issue, you could remove the following section. + * Currently rx_to_ig_allow only checks for recent duplicates. */ if ( ! rx_to_ig_allow(pp)) { @@ -1080,11 +1096,40 @@ static void send_packet_to_server (packet_t pp, int chan) /* * Finally, append ",qAR," and my call to the path. + */ + +/* + * It seems that the specification has changed recently. + * http://www.tapr.org/pipermail/aprssig/2016-December/046456.html + * + * We can see the history at the Internet Archive Wayback Machine. + * + * http://www.aprs-is.net/Connecting.aspx + * captured Oct 19, 2016: + * ... Only the qAR construct may be generated by a client (IGate) on APRS-IS. + * Captured Dec 1, 2016: + * ... Only the qAR and qAO constructs may be generated by a client (IGate) on APRS-IS. + * + * http://www.aprs-is.net/q.aspx + * Captured April 23, 2016: + * (no mention of client generating qAO.) + * Captured July 19, 2016: + * qAO - (letter O) Packet is placed on APRS-IS by a receive-only IGate from RF. + * The callSSID following the qAO is the callSSID of the IGate. Note that receive-only + * IGates are discouraged on standard APRS frequencies. Please consider a bidirectional + * IGate that only gates to RF messages for stations heard directly. */ ax25_format_addrs (pp, msg); msg[strlen(msg)-1] = '\0'; /* Remove trailing ":" */ - strlcat (msg, ",qAR,", sizeof(msg)); + + if (save_igate_config_p->tx_chan >= 0) { + strlcat (msg, ",qAR,", sizeof(msg)); + } + else { + strlcat (msg, ",qAO,", sizeof(msg)); // new for version 1.4. + } + strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg)); strlcat (msg, ":", sizeof(msg)); strlcat (msg, (char*)pinfo, sizeof(msg)); @@ -1268,7 +1313,7 @@ static void * igate_recv_thread (void *arg) ch = get1ch(); stats_downlink_bytes++; - if (len < sizeof(message)) + if (len < (int)(sizeof(message))) { message[len] = ch; } @@ -1339,12 +1384,20 @@ static void * igate_recv_thread (void *arg) ax25_safe_print ((char *)message, len, 0); dw_printf ("\n"); +/* + * Record that we heard from the source address. + */ + mheard_save_is ((char *)message); + stats_downlink_packets++; +/* + * Possibly transmit if so configured. + */ int to_chan = save_igate_config_p->tx_chan; if (to_chan >= 0) { - xmit_packet ((char*)message, to_chan); + maybe_xmit_packet_from_igate ((char*)message, to_chan); } } @@ -1476,10 +1529,10 @@ static void * satgate_delay_thread (void *arg) /*------------------------------------------------------------------- * - * Name: xmit_packet + * Name: maybe_xmit_packet_from_igate * * Purpose: Convert text string, from IGate server, to third party - * packet and send to transmit queue. + * packet and send to transmit queue if appropriate. * * Inputs: message - As sent by the server. * Any trailing CRLF should have been removed. @@ -1497,18 +1550,23 @@ static void * satgate_delay_thread (void *arg) * repackaging to go over the radio. * * The "q construct" ( http://www.aprs-is.net/q.aspx ) provides - * a clue about the journey taken but I don't think we care here. + * a clue about the journey taken. "qAX" means that the station sending + * the packet to the server did not login properly as a ham radio + * operator so we don't want to put this on to RF. * * to_chan - Radio channel for transmitting. * *--------------------------------------------------------------------*/ -static void xmit_packet (char *message, int to_chan) +static void maybe_xmit_packet_from_igate (char *message, int to_chan) { packet_t pp3; char payload[AX25_MAX_PACKET_LEN]; /* what is max len? */ + char src[AX25_MAX_ADDR_LEN]; /* Source address. */ + char *pinfo = NULL; int info_len; + int n; assert (to_chan >= 0 && to_chan < MAX_CHANS); @@ -1530,6 +1588,32 @@ static void xmit_packet (char *message, int to_chan) return; } + ax25_get_addr_with_ssid (pp3, AX25_SOURCE, src); + +/* + * Drop if path contains: + * NOGATE or RFONLY - means IGate should not pass them. + * TCPXX or qAX - means it came from somewhere that did not identify itself correctly. + */ + for (n = 0; n < ax25_get_num_repeaters(pp3); n++) { + char via[AX25_MAX_ADDR_LEN]; /* includes ssid. Do we want to ignore it? */ + + ax25_get_addr_with_ssid (pp3, n + AX25_REPEATER_1, via); + + if (strcmp(via, "qAX") == 0 || + strcmp(via, "TCPXX") == 0 || + strcmp(via, "RFONLY") == 0 || + strcmp(via, "NOGATE") == 0) { + + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Tx IGate: Do not transmit with %s in path.\n", via); + } + + ax25_delete (pp3); + return; + } + } /* * Apply our own packet filtering if configured. @@ -1540,20 +1624,58 @@ static void xmit_packet (char *message, int to_chan) assert (to_chan >= 0 && to_chan < MAX_CHANS); - 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) != 1) { +/* + * We have a rather strange special case here. + * If we recently transmitted a 'message' from some station, + * send the position of the message sender when it comes along later. + * + * If we have a position report, look up the sender and see if we should + * bypass the normal filtering. + */ - // Originally this was always printed but it's probably too much noise. - // Version 1.4, print only if debug option is specified. +// TODO: Not quite this simple. Should have a function to check for position. +// $ raw gps could be a position. @ could be weather data depending on symbol. + + info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo)); + + int msp_special_case = 0; + + if (info_len >= 1 && strchr("!=/@'`", *pinfo) != NULL) { + + int n = mheard_get_msp(src); + + if (n > 0) { + + msp_special_case = 1; if (s_debug >= 1) { text_color_set(DW_COLOR_INFO); - dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]); + dw_printf ("Special case, allow position from message sender %s, %d remaining.\n", src, n - 1); } - ax25_delete (pp3); - return; + mheard_set_msp (src, n - 1); + } + } + + if ( ! msp_special_case) { + + 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) != 1) { + + // Previously there was a debug message here about the packet being dropped by filtering. + // This is now handled better by the "-df" command line option for filtering details. + + // TODO: clean up - remove these lines. + //if (s_debug >= 1) { + // text_color_set(DW_COLOR_INFO); + // dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]); + //} + + ax25_delete (pp3); + return; + } } } @@ -1566,6 +1688,34 @@ static void xmit_packet (char *message, int to_chan) * * We want to reduce it to this before wrapping it as third party traffic. * K1USN-1>APWW10:T#479,100,048,002,500,000,10000000<0x0d><0x0a> + */ + +/* + * These are typical examples where we see TCPIP*,qAC, + * + * N3LLO-4>APRX28,TCPIP*,qAC,T2NUENGLD:T#474,21.4,0.3,114.0,4.0,0.0,00000000 + * N1WJO>APWW10,TCPIP*,qAC,T2MAINE:)147.120!4412.27N/07033.27WrW1OCA repeater136.5 Tone Norway Me + * AB1OC-10>APWW10,TCPIP*,qAC,T2IAD2:=4242.70N/07135.41W#(Time 0:00:00)!INSERVICE!!W60! + * + * But sometimes we get a different form: + * + * N1YG-1>T1SY9P,WIDE1-1,WIDE2-2,qAR,W2DAN-15:'c&<0x7f>l <0x1c>-/> + * W1HS-8>TSSP9T,WIDE1-1,WIDE2-1,qAR,N3LLO-2:`d^Vl"W>/'"85}|*&%_'[|!wLK!|3 + * N1RCW-1>APU25N,MA2-2,qAR,KA1VCQ-1:=4140.41N/07030.21W-Home Station/Fill-in Digi {UIV32N} + * N1IEJ>T4PY3U,W1EMA-1,WIDE1*,WIDE2-2,qAR,KD1KE:`a5"l!<0x7f>-/]"4f}Retired & Busy= + * + * Oh! They have qAR rather than qAC. What does that mean? + * From http://www.aprs-is.net/q.aspx + * + * qAC - Packet was received from the client directly via a verified connection (FROMCALL=login). + * The callSSID following the qAC is the server's callsign-SSID. + * + * qAR - Packet was received directly (via a verified connection) from an IGate using the ,I construct. + * The callSSID following the qAR it the callSSID of the IGate. + * + * What is the ",I" construct? + * Do we care here? + * Is is something new and improved that we should be using in the other direction? */ while (ax25_get_num_repeaters(pp3) > 0) { @@ -1598,6 +1748,16 @@ static void xmit_packet (char *message, int to_chan) /* * Encapsulate for sending over radio if no reason to drop it. + */ + +/* + * We don't want to suppress duplicate "messages" within a short time period. + * Suppose we transmitted a "message" for some station and it did not respond with an ack. + * 25 seconds later the sender retries. Wouldn't we want to pass along that retry? + * + * "Messages" get preferential treatment because they are high value and very rare. + * -> Bypass the duplicate suppression. + * -> Raise the rate limiting value. */ if (ig_to_tx_allow (pp3, to_chan)) { char radio [500]; @@ -1626,8 +1786,17 @@ static void xmit_packet (char *message, int to_chan) #endif stats_rf_xmit_packets++; // Any type of packet. + // TEMP TEST: metadata temporarily allowed during testing. + if (*pinfo == ':' && ! is_telem_metadata(pinfo)) { - stats_msg_cnt++; // "message" be sure to exclude telemetry metadata. + // temp test // if (*pinfo == ':') { + +// We transmitted a "message." Telemetry metadata is excluded. +// Remember to pass along address of the sender later. + + stats_msg_cnt++; // Update statistics. + + mheard_set_msp (src, save_igate_config_p->igmsp); } ig_to_tx_remember (pp3, save_igate_config_p->tx_chan, 0); // correct. version before encapsulating it. @@ -1644,7 +1813,7 @@ static void xmit_packet (char *message, int to_chan) ax25_delete (pp3); -} /* end xmit_packet */ +} /* end maybe_xmit_packet_from_igate */ @@ -1820,7 +1989,7 @@ static int rx_to_ig_allow (packet_t pp) * duplicate of another sent recently. * * This is the essentially the same as the pair of functions - * above with one addition restriction. + * above, for RF to IS, with one additional restriction. * * The typical residential Internet connection is around 10,000 * to 50,000 times faster than the radio links we are using. It would @@ -2058,14 +2227,35 @@ static int ig_to_tx_allow (packet_t pp, int chan) for (j=0; j= now - IG2TX_DEDUPE_TIME) { - if (s_debug >= 2) { - text_color_set(DW_COLOR_DEBUG); - // could be multiple entries and this might not be the most recent. - dw_printf ("ig_to_tx_allow? NO. Sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]); + + /* We have a duplicate within some time period. */ + + if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) { + + /* I think I want to avoid the duplicate suppression for "messages." */ + /* Suppose we transmit a message from station X and it doesn't get an ack back. */ + /* Station X then sends exactly the same thing 20 seconds later. */ + /* We don't want to suppress the retry. */ + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ig_to_tx_allow? Yes for duplicate message sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]); + } + } + else { + + /* Normal (non-message) case. */ + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + // could be multiple entries and this might not be the most recent. + dw_printf ("ig_to_tx_allow? NO. Duplicate sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]); + } + + text_color_set(DW_COLOR_INFO); + dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n"); + return 0; } - text_color_set(DW_COLOR_INFO); - dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n"); - return 0; } } diff --git a/igate.h b/igate.h index 369d01e..b98e5a9 100644 --- a/igate.h +++ b/igate.h @@ -45,6 +45,9 @@ struct igate_config_s { */ int tx_chan; /* Radio channel for transmitting. */ /* 0=first, etc. -1 for none. */ + /* Presently IGate can transmit on only a single channel. */ + /* A future version might generalize this. */ + /* Each transmit channel would have its own client side filtering. */ char tx_via[80]; /* VIA path for transmitting third party packets. */ /* Usual text representation. */ @@ -52,13 +55,20 @@ struct igate_config_s { /* simply be inserted after the destination address. */ int max_digi_hops; /* Maximum number of digipeater hops possible for via path. */ - /* e.g. "WIDE1-1,WDIE2-2" would be 3. */ + /* Derived from the SSID when last character of address is a digit. */ + /* e.g. "WIDE1-1,WIDE5-2" would be 3. */ /* This is useful to know so we can determine how many */ /* stations we might be able to reach. */ int tx_limit_1; /* Max. packets to transmit in 1 minute. */ int tx_limit_5; /* Max. packets to transmit in 5 minutes. */ + + int igmsp; /* Number of message sender position reports to allow. */ + /* Common practice is to default to 1. */ + /* We allow additional flexibility of 0 to disable feature */ + /* or a small number to allow more. */ + /* * Special SATgate mode to delay packets heard directly. */ diff --git a/kiss.c b/kiss.c index 5a66aa0..19c258d 100644 --- a/kiss.c +++ b/kiss.c @@ -124,6 +124,7 @@ #include #include #include +#include #include #include #ifdef __OpenBSD__ diff --git a/mheard.c b/mheard.c index 2d498d8..37163c5 100644 --- a/mheard.c +++ b/mheard.c @@ -24,12 +24,19 @@ * * Purpose: Maintain a list of all stations heard. * - * Description: This was added for IGate statistics but would also be - * useful for the AGW network protocol 'H' request. + * Description: This was added for IGate statistics and checking if a user is local + * but would also be useful for the AGW network protocol 'H' request. * * This application has no GUI and is not interactive so * I'm not sure what else we might do with the information. * + * Why mheard instead of just heard? The KPC-3+ has an MHEARD command + * to list stations heard. I guess that stuck in my mind. + * It should be noted that here "heard" refers to the AX.25 source station. + * Before printing the received packet, the "heard" line refers to who + * we heard over the radio. This would be the digipeater with "*" after + * its name. + * * Future Ideas: Someone suggested using SQLite to store the information * so other applications could access it. * @@ -49,21 +56,29 @@ #include "ax25_pad.h" #include "hdlc_rec2.h" // for retry_t #include "mheard.h" +#include "latlong.h" -// I think we can get away without a critical region if we follow certain rules. +// This is getting updated from two different threads so we need a critical region +// for adding new nodes. + +static dw_mutex_t mheard_mutex; + + +// I think we can get away without a critical region for reading if we follow these +// rules: // -// (1) All updates are from a single thread. Although there are multiple receive -// threads, all received packets go into a single queue for serial processing. -// (2) When adding a new node, make sure it is complete, including next ptr, +// (1) When adding a new node, make sure it is complete, including next ptr, // before adding it to the list. -// (3) Nothing gets deleted. -// -// It shouldn't be a problem if the data readers are from other threads. +// (2) Update the start of list pointer last. +// (2) Nothing gets deleted. + +// If we ever decide to start cleaning out very old data, all access would then +// need to use the mutex. /* - * Information for each station heard over the radio. + * Information for each station heard over the radio or from Internet Server. */ typedef struct mheard_s { @@ -72,16 +87,36 @@ typedef struct mheard_s { char callsign[AX25_MAX_ADDR_LEN]; // Callsign from the AX.25 source field. - int num_digi_hops; // Number of digipeater hops before we heard it. - // Zero when heard directly. + int count; // Number of times heard. + // We don't use this for anything. + // Just something potentially interesting when looking at data dump. - time_t last_heard; // Timestamp when last heard. + int chan; // Most recent channel where heard. + + int num_digi_hops; // Number of digipeater hops before we heard it. + // over radio. Zero when heard directly. + + time_t last_heard_rf; // Timestamp when last heard over the radio. + + time_t last_heard_is; // Timestamp when last heard from Internet Server. + + double dlat, dlon; // Last position. G_UNKNOWN for unknown. + + int msp; // Allow message sender positon report. + // When non zero, an IS>RF position report is allowed. + // Then decremented. // What else would be useful? // The AGW protocol is by channel and returns // first heard in addition to last heard. } mheard_t; + + + + + + /* * The list could be quite long and we hit this a lot so use a hash table. */ @@ -112,7 +147,7 @@ static mheard_t *mheard_ptr(char *callsign) { } -static int mheard_debug; +static int mheard_debug = 0; /*------------------------------------------------------------------ @@ -139,15 +174,130 @@ void mheard_init (int debug) mheard_hash[i] = NULL; } +/* + * Mutex to coordinate adding new nodes. + */ + dw_mutex_init(&mheard_mutex); + } /* end mheard_init */ /*------------------------------------------------------------------ * - * Function: mheard_save + * Function: mheard_dump * - * Purpose: Save information about station heard. + * Purpose: Print list of stations heard for debugging. + * + *------------------------------------------------------------------*/ + +/* convert some time in past to hours:minutes text format. */ + +static void age(char *result, time_t now, time_t t) +{ + int s, h, m; + + if (t == 0) { + strcpy (result, "- "); + return; + } + + s = (int)(now - t); + m = s / 60; + h = m / 60; + m -= h * 60; + + sprintf (result, "%4d:%02d", h, m); +} + +/* Convert latitude, longitude to text or - if not defined. */ + +static void latlon (char * result, double dlat, double dlon) +{ + if (dlat != G_UNKNOWN && dlon != G_UNKNOWN) { + sprintf (result, "%6.2f %7.2f", dlat, dlon); + } + else { + strcpy (result, " - - "); + } +} + +/* Compare last heard time for use with qsort. */ + +#define MAXX(x,y) (((x)>(y))?(x):(y)) +static int compar(const void *a, const void *b) +{ + mheard_t *ma = *((mheard_t **)a); + mheard_t *mb = *((mheard_t **)b); + + time_t ta = MAXX(ma->last_heard_rf, ma->last_heard_is); + time_t tb = MAXX(mb->last_heard_rf, mb->last_heard_is); + + return (tb - ta); +} + +#define MAXDUMP 1000 + +static void mheard_dump (void) +{ + int i; + mheard_t *mptr; + time_t now = time(NULL); + char stuff[80]; + char rf[16]; // hours:minutes + char is[16]; + char position[40]; + mheard_t *station[MAXDUMP]; + int num_stations = 0; + + +/* Get linear array of node pointers so they can be sorted easily. */ + + num_stations = 0; + + for (i = 0; i < MHEARD_HASH_SIZE; i++) { + for (mptr = mheard_hash[i]; mptr != NULL; mptr = mptr->pnext) { + + if (num_stations < MAXDUMP) { + station[num_stations] = mptr; + num_stations++; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("mheard_dump - max number of stations exceeded.\n"); + } + } + } + +/* Sort most recently heard to the top then print. */ + + qsort (station, num_stations, sizeof(mheard_t *), compar); + + text_color_set(DW_COLOR_DEBUG); + + dw_printf ("callsign cnt chan hops RF IS lat long msp\n"); + + for (i = 0; i < num_stations; i++) { + + mptr = station[i]; + + age (rf, now, mptr->last_heard_rf); + age (is, now, mptr->last_heard_is); + latlon (position, mptr->dlat, mptr->dlon); + + snprintf (stuff, sizeof(stuff), "%-9s %3d %d %d %7s %7s %s %d\n", + mptr->callsign, mptr->count, mptr->chan, mptr->num_digi_hops, rf, is, position, mptr->msp); + dw_printf ("%s", stuff); + } + +} /* end mheard_dump */ + + +/*------------------------------------------------------------------ + * + * Function: mheard_save_rf + * + * Purpose: Save information about station heard over the radio. * * Inputs: chan - Radio channel where heard. * @@ -166,7 +316,7 @@ void mheard_init (int debug) * *------------------------------------------------------------------*/ -void mheard_save (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries) +void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries) { time_t now = time(NULL); char source[AX25_MAX_ADDR_LEN]; @@ -196,18 +346,24 @@ void mheard_save (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retr if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("mheard_save: %s %d - added new\n", source, hops); + dw_printf ("mheard_save_rf: %s %d - added new\n", source, hops); } mptr = calloc(sizeof(mheard_t),1); strlcpy (mptr->callsign, source, sizeof(mptr->callsign)); + mptr->count = 1; + mptr->chan = chan; mptr->num_digi_hops = hops; - mptr->last_heard = now; + mptr->last_heard_rf = now; + mptr->dlat = G_UNKNOWN; + mptr->dlon = G_UNKNOWN; i = hash_index(source); + dw_mutex_lock (&mheard_mutex); mptr->pnext = mheard_hash[i]; // before inserting into list. mheard_hash[i] = mptr; + dw_mutex_unlock (&mheard_mutex); } else { @@ -218,32 +374,162 @@ void mheard_save (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retr * We are interested in the shortest path if heard very recently. */ - if (hops > mptr->num_digi_hops && (int)(now - mptr->last_heard) < 15) { + if (hops > mptr->num_digi_hops && (int)(now - mptr->last_heard_rf) < 15) { if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("mheard_save: %s %d - skip because hops was %d %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard) ); + dw_printf ("mheard_save_rf: %s %d - skip because hops was %d %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf) ); } } else { if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("mheard_save: %s %d - update time, was %d hops %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard)); + dw_printf ("mheard_save_rf: %s %d - update time, was %d hops %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf)); } + mptr->count++; + mptr->chan = chan; mptr->num_digi_hops = hops; - mptr->last_heard = now; + mptr->last_heard_rf = now; } } + if (A->g_lat != G_UNKNOWN && A->g_lon != G_UNKNOWN) { + mptr->dlat = A->g_lat; + mptr->dlon = A->g_lon; + } + + if (mheard_debug >= 2) { + int limit = 10; // normally 30 or 60. more frequent when debugging. + text_color_set(DW_COLOR_DEBUG); + dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit)); + } + + if (mheard_debug) { + mheard_dump (); + } + +} /* end mheard_save_rf */ + + + +/*------------------------------------------------------------------ + * + * Function: mheard_save_is + * + * Purpose: Save information about station heard via Internet Server. + * + * Inputs: ptext - Packet in monitoring text form as sent by the Internet server. + * + * Any trailing CRLF should have been removed. + * Typical examples: + * + * KA1BTK-5>APDR13,TCPIP*,qAC,T2IRELAND:=4237.62N/07040.68W$/A=-00054 http://aprsdroid.org/ + * N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmAT3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile + * + * Notice how the final address in the header might not + * be a valid AX.25 address. We see a 9 character address + * (with no ssid) and an ssid of two letters. + * + * The "q construct" ( http://www.aprs-is.net/q.aspx ) provides + * a clue about the journey taken but I don't think we care here. + * + * All we should care about here is the the source address. + * + * Description: + * + *------------------------------------------------------------------*/ + +void mheard_save_is (char *ptext) +{ + packet_t pp; + time_t now = time(NULL); + char source[AX25_MAX_ADDR_LEN]; + mheard_t *mptr; + +/* + * Try to parse it into a packet object. + * This will contain "q constructs" and we might see an address + * with two alphnumeric characters in the SSID so we must use + * the non-strict parsing. + * + * Bug: Up to 8 digipeaters are allowed in radio format. + * There is a potential of finding a larger number here. + */ + pp = ax25_from_text(ptext, 0); + + if (pp == NULL) { + if (mheard_debug) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("mheard_save_is: Could not parse message from server.\n"); + dw_printf ("%s\n", ptext); + } + return; + } + + ax25_get_addr_with_ssid (pp, AX25_SOURCE, source); + + mptr = mheard_ptr(source); + if (mptr == NULL) { + int i; +/* + * Not heard before. Add it. + */ + + if (mheard_debug) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("mheard_save_is: %s - added new\n", source); + } + + mptr = calloc(sizeof(mheard_t),1); + strlcpy (mptr->callsign, source, sizeof(mptr->callsign)); + mptr->count = 1; + mptr->last_heard_is = now; + mptr->dlat = G_UNKNOWN; + mptr->dlon = G_UNKNOWN; + + i = hash_index(source); + + dw_mutex_lock (&mheard_mutex); + mptr->pnext = mheard_hash[i]; // before inserting into list. + mheard_hash[i] = mptr; + dw_mutex_unlock (&mheard_mutex); + } + else { + +/* Already there. UPdate last heard from IS time. */ + + if (mheard_debug) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("mheard_save_is: %s - update time, was %d seconds ago.\n", source, (int)(now - mptr->last_heard_rf)); + } + mptr->count++; + mptr->last_heard_is = now; + } + + // Is is desirable to save any location in this case? + // I don't think it would help. + // The whole purpose of keeping the location is for message sending filter. + // We wouldn't want to try sending a message to the station if we didn't hear it over the radio. + // On the other hand, I don't think it would hurt. + // The filter always includes a time since last heard over the radi. + if (mheard_debug >= 2) { int limit = 10; // normally 30 or 60 text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit)); } -} /* end mheard_save */ + if (mheard_debug) { + mheard_dump (); + } + + ax25_delete (pp); + +} /* end mheard_save_is */ /*------------------------------------------------------------------ @@ -312,7 +598,7 @@ int mheard_count (int max_hops, int time_limit) for (i = 0; i < MHEARD_HASH_SIZE; i++) { for (p = mheard_hash[i]; p != NULL; p = p->pnext) { - if (p->last_heard >= since && p->num_digi_hops <= max_hops) { + if (p->last_heard_rf >= since && p->num_digi_hops <= max_hops) { count++; } } @@ -328,4 +614,184 @@ int mheard_count (int max_hops, int time_limit) } /* end mheard_count */ + +/*------------------------------------------------------------------ + * + * Function: mheard_was_recently_nearby + * + * Purpose: Determine whether given station was heard recently on the radio. + * + * Inputs: role - "addressee" or "source" if debug out is desired. + * Otherwise empty string. + * + * callsign - Callsign for station. + * + * time_limit - Include only stations heard within this many minutes. + * Typically 30 or 60. + * + * max_hops - Include only stations heard with this number of + * digipeater hops or less. For reporting, we might use: + * + * dlat, dlon, km - Include only stations within distance of location. + * Not used if G_UNKNOWN is supplied. + * + * Returns: 1 for true, 0 for false. + * + *------------------------------------------------------------------*/ + +int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km) +{ + mheard_t *mptr; + time_t now; + int heard_ago; + + + if (role != NULL && strlen(role) > 0) { + + text_color_set(DW_COLOR_INFO); + if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN) { + dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops, and within %.1f km of %.2f %.2f?\n", role, callsign, time_limit, max_hops, km, dlat, dlon); + } + else { + dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops?\n", role, callsign, time_limit, max_hops); + } + } + + mptr = mheard_ptr(callsign); + + if (mptr == NULL || mptr->last_heard_rf == 0) { + + if (role != NULL && strlen(role) > 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("No, we have not heard %s over the radio.\n", callsign); + } + return (0); + } + + now = time(NULL); + heard_ago = (int)(now - mptr->last_heard_rf) / 60; + + if (heard_ago > time_limit) { + + if (role != NULL && strlen(role) > 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("No, %s was last heard over the radio %d minutes ago with %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops); + } + return (0); + } + + if (mptr->num_digi_hops > max_hops) { + + if (role != NULL && strlen(role) > 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("No, %s was last heard over the radio with %d digipeater hops %d minutes ago.\n", callsign, mptr->num_digi_hops, heard_ago); + } + return (0); + } + +// Apply physical distance check? + + if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN && mptr->dlat != G_UNKNOWN && mptr->dlon != G_UNKNOWN) { + + double dist = ll_distance_km (mptr->dlat, mptr->dlon, dlat, dlon); + + if (dist > km) { + + if (role != NULL && strlen(role) > 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("No, %s was %.1f km away although it was %d digipeater hops %d minutes ago.\n", callsign, dist, mptr->num_digi_hops, heard_ago); + } + return (0); + } + else { + if (role != NULL && strlen(role) > 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops. Last location %.1f km away.\n", callsign, heard_ago, mptr->num_digi_hops, dist); + } + return (1); + } + } + +// Passed all the tests. + + if (role != NULL && strlen(role) > 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops); + } + + return (1); + +} /* end mheard_was_recently_nearby */ + + + +/*------------------------------------------------------------------ + * + * Function: mheard_set_msp + * + * Purpose: Set the "message sender position" count for specified station. + * + * Inputs: callsign - Callsign for station which sent the "message." + * + * num - Number of position reports to allow. Typically 1. + * + *------------------------------------------------------------------*/ + +void mheard_set_msp (char *callsign, int num) +{ + mheard_t *mptr; + + mptr = mheard_ptr(callsign); + + if (mptr != NULL) { + + mptr->msp = num; + + if (mheard_debug) { + text_color_set(DW_COLOR_INFO); + dw_printf ("MSP for %s set to %d\n", callsign, num); + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error: Can't find %s to set MSP.\n", callsign); + } + +} /* end mheard_set_msp */ + + +/*------------------------------------------------------------------ + * + * Function: mheard_get_msp + * + * Purpose: Get the "message sender position" count for specified station. + * + * Inputs: callsign - Callsign for station which sent the "message." + * + * Returns: The cound for the specified station. + * 0 if not found. + * + *------------------------------------------------------------------*/ + +int mheard_get_msp (char *callsign) +{ + mheard_t *mptr; + + mptr = mheard_ptr(callsign); + + if (mptr != NULL) { + + if (mheard_debug) { + text_color_set(DW_COLOR_INFO); + dw_printf ("MSP for %s is %d\n", callsign, mptr->msp); + } + return (mptr->msp); // Should we have a time limit? + } + + return (0); + +} /* end mheard_get_msp */ + + + /* end mheard.c */ diff --git a/mheard.h b/mheard.h index e4ab9bf..f8466ba 100644 --- a/mheard.h +++ b/mheard.h @@ -2,8 +2,19 @@ /* mheard.h */ +#include "decode_aprs.h" // for decode_aprs_t + + void mheard_init (int debug); -void mheard_save (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries); +void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries); -int mheard_count (int max_hops, int time_limit); \ No newline at end of file +void mheard_save_is (char *ptext); + +int mheard_count (int max_hops, int time_limit); + +int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km); + +void mheard_set_msp (char *callsign, int num); + +int mheard_get_msp (char *callsign); \ No newline at end of file diff --git a/pfilter.c b/pfilter.c index 19f232a..5360410 100644 --- a/pfilter.c +++ b/pfilter.c @@ -51,6 +51,43 @@ #include "decode_aprs.h" #include "latlong.h" #include "pfilter.h" +#include "mheard.h" + + + +/* + * Global stuff (to this file) + * + * These are set by init function. + */ + +static struct igate_config_s *save_igate_config_p; +static int s_debug = 0; + + + +/*------------------------------------------------------------------- + * + * Name: pfilter_init + * + * Purpose: One time initialization when main application starts up. + * + * Inputs: p_igate_config - IGate configuration. + * + * debug_level - 0 no debug output. + * 1 single summary line with final result. Indent by 1. + * 2 details from each filter specification. Indent by 3. + * 3 Logical operators. Indent by 2. + * + *--------------------------------------------------------------------*/ + + +void pfilter_init (struct igate_config_s *p_igate_config, int debug_level) +{ + s_debug = debug_level; + save_igate_config_p = p_igate_config; +} + @@ -65,9 +102,6 @@ typedef struct pfstate_s { int from_chan; /* From and to channels. MAX_CHANS is used for IGate. */ int to_chan; /* Used only for debug and error messages. */ - -// TODO: might want to put channels and packet here so we only pass one thing around. - /* * Original filter string from config file. * All control characters should be replaced by spaces. @@ -120,8 +154,17 @@ static void print_error (pfstate_t *pf, char *msg); static int filt_bodgu (pfstate_t *pf, char *pattern); static int filt_t (pfstate_t *pf); -static int filt_r (pfstate_t *pf); +static int filt_r (pfstate_t *pf, char *sdist); static int filt_s (pfstate_t *pf); +static int filt_i (pfstate_t *pf); + +static char *bool2text (int val) +{ + if (val == 1) return "TRUE"; + if (val == 0) return "FALSE"; + if (val == -1) return "ERROR"; + return "OOPS!"; +} /*------------------------------------------------------------------- @@ -164,12 +207,12 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs) memset (&pfstate, 0, sizeof(pfstate)); if (pp == NULL) { - text_color_set(DW_COLOR_DEBUG); + text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n"); return (-1); } if (filter == NULL) { - text_color_set(DW_COLOR_DEBUG); + text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR in pfilter: NULL filter string pointer. Please report this!\n"); return (-1); } @@ -212,6 +255,23 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs) result = -1; } } + + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + if (from_chan == MAX_CHANS) { + dw_printf (" Packet filter from IGate to radio channel %d returns %s\n", to_chan, bool2text(result)); + } + else if (to_chan == MAX_CHANS) { + dw_printf (" Packet filter from radio channel %d to IGate returns %s\n", from_chan, bool2text(result)); + } + else if (is_aprs) { + dw_printf (" Packet filter for APRS digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result)); + } + else { + dw_printf (" Packet filter for traditional digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result)); + } + } + return (result); } /* end pfilter */ @@ -350,6 +410,12 @@ static int parse_or_expr (pfstate_t *pf) next_token (pf); e = parse_and_expr (pf); + + if (s_debug >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s | %s\n", bool2text(result), bool2text(e)); + } + if (e < 0) return (-1); result |= e; } @@ -371,6 +437,12 @@ static int parse_and_expr (pfstate_t *pf) next_token (pf); e = parse_primary (pf); + + if (s_debug >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s & %s\n", bool2text(result), bool2text(e)); + } + if (e < 0) return (-1); result &= e; } @@ -404,6 +476,12 @@ static int parse_primary (pfstate_t *pf) next_token (pf); e = parse_primary (pf); + + if (s_debug >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" ! %s\n", bool2text(e)); + } + if (e < 0) result = -1; else result = ! e; } @@ -440,12 +518,9 @@ static int parse_primary (pfstate_t *pf) * *--------------------------------------------------------------------*/ - - 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) { @@ -468,15 +543,33 @@ static int parse_filter_spec (pfstate_t *pf) /* simple string matching */ +/* b - budlist */ + else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) { /* Budlist - source address */ + char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr); result = filt_bodgu (pf, addr); + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr); + } } + +/* o - object or item name */ + else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) { - /* Object or item name */ result = filt_bodgu (pf, pf->decoded.g_name); + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_name); + } } + +/* d - was digipeated by */ + else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) { int n; // loop on all digipeaters @@ -484,11 +577,26 @@ static int parse_filter_spec (pfstate_t *pf) for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { // Consider only those with the H (has-been-used) bit set. if (ax25_get_h (pf->pp, n)) { + char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, n, addr); result = filt_bodgu (pf, addr); } } + + if (s_debug >= 2) { + char path[100]; + + ax25_format_via_path (pf->pp, path, sizeof(path)); + if (strlen(path) == 0) { + strcpy (path, "no digipeater path"); + } + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path); + } } + +/* v - via not used */ + else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) { int n; // loop on all digipeaters (mnemonic Via) @@ -497,55 +605,140 @@ static int parse_filter_spec (pfstate_t *pf) // This is different than the previous "d" filter. // Consider only those where the the H (has-been-used) bit is NOT set. if ( ! ax25_get_h (pf->pp, n)) { + char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, n, addr); result = filt_bodgu (pf, addr); } } + + if (s_debug >= 2) { + char path[100]; + + ax25_format_via_path (pf->pp, path, sizeof(path)); + if (strlen(path) == 0) { + strcpy (path, "no digipeater path"); + } + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path); + } } + +/* g - Addressee of message. */ + else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) { - /* Addressee of message. */ if (ax25_get_dti(pf->pp) == ':') { result = filt_bodgu (pf, pf->decoded.g_addressee); + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee); + } } else { result = 0; + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "not a message"); + } } } + +/* u - unproto (destination) */ + else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) { - /* Unproto (destination) - probably want to exclude mic-e types */ + /* Probably want to exclude mic-e types */ /* because destination is used for part of location. */ if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') { + char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr); result = filt_bodgu (pf, addr); + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr); + } } else { result = 0; + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "MIC-E packet type"); + } } } -/* type: position, weather, etc. */ +/* t - type: position, weather, etc. */ else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) { - ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr); result = filt_t (pf); + + if (s_debug >= 2) { + char *infop = NULL; + (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); + + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %c data type indicator\n", pf->token_str, bool2text(result), *infop); + } } -/* range */ +/* r - range */ else if (pf->token_str[0] == 'r' && ispunct(pf->token_str[1])) { /* range */ - result = filt_r (pf); + char sdist[30]; + strcpy (sdist, "unknown distance"); + result = filt_r (pf, sdist); + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), sdist); + } } -/* symbol */ +/* s - symbol */ else if (pf->token_str[0] == 's' && ispunct(pf->token_str[1])) { /* symbol */ result = filt_s (pf); + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + if (pf->decoded.g_symbol_table == '/') { + dw_printf (" %s returns %s for symbol %c in primary table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code); + } + else if (pf->decoded.g_symbol_table == '\\') { + dw_printf (" %s returns %s for symbol %c in alternate table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code); + } + else { + dw_printf (" %s returns %s for symbol %c with overlay %c\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code, pf->decoded.g_symbol_table); + } + } } +/* i - IGate messaging default */ + + else if (pf->token_str[0] == 'i' && ispunct(pf->token_str[1])) { + /* IGatge messaging */ + result = filt_i (pf); + + if (s_debug >= 2) { + char *infop = NULL; + (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); + + text_color_set(DW_COLOR_DEBUG); + if (*infop == ':' && ! is_telem_metadata(infop)) { + dw_printf (" %s returns %s for message to %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee); + } + else { + dw_printf (" %s returns %s for not an APRS 'message'\n", pf->token_str, bool2text(result)); + } + } + } + +/* unrecognized filter type */ + else { char stemp[80]; snprintf (stemp, sizeof(stemp), "Unrecognized filter type '%c'", pf->token_str[0]); @@ -573,7 +766,7 @@ static int parse_filter_spec (pfstate_t *pf) * Digipeater d/digi1/digi2... * Group Msg g/call1/call2... * Unproto u/unproto1/unproto2... - * Via-not-yet v/digi1/digi2... + * Via-not-yet v/digi1/digi2...noteapd * * arg - Value to match from source addr, destination, * used digipeater, object name, etc. @@ -610,7 +803,7 @@ static int filt_bodgu (pfstate_t *pf, char *arg) /* Wildcarding. Should have single * on end. */ mlen = w - v; - if (mlen != strlen(v) - 1) { + if (mlen != (int)(strlen(v) - 1)) { print_error (pf, "Any wildcard * must be at the end of pattern.\n"); return (-1); } @@ -787,6 +980,8 @@ static int filt_t (pfstate_t *pf) * * decoded.g_lat & decoded.g_lon * + * Outputs: sdist - Distance as a string for troubleshooting. + * * Returns: 1 = yes * 0 = no * -1 = error detected @@ -795,7 +990,7 @@ static int filt_t (pfstate_t *pf) * *------------------------------------------------------------------------------*/ -static int filt_r (pfstate_t *pf) +static int filt_r (pfstate_t *pf, char *sdist) { char str[MAX_TOKEN_LEN]; char *cp; @@ -836,10 +1031,7 @@ static int filt_r (pfstate_t *pf) km = ll_distance_km (dlat, dlon, pf->decoded.g_lat, pf->decoded.g_lon); - - text_color_set (DW_COLOR_DEBUG); - - dw_printf ("Calculated distance = %.3f km\n", km); + sprintf (sdist, "%.2f km", km); if (km <= ddist) { return (1); @@ -892,7 +1084,10 @@ static int filt_s (pfstate_t *pf) sep[1] = '\0'; cp = str + 2; +// TODO: check here. + pri = strsep (&cp, sep); + if (pri == NULL) { print_error (pf, "Missing arguments for Symbol filter."); return (-1); @@ -936,6 +1131,212 @@ static int filt_s (pfstate_t *pf) } +/*------------------------------------------------------------------------------ + * + * Name: filt_i + * + * Purpose: IGate messaging default behavior. + * + * Inputs: pf - Pointer to current state information. + * token_str should contain something of format: + * + * i/time/hops/lat/lon/km + * + * Returns: 1 = yes + * 0 = no + * -1 = error detected + * + * Description: Selection is based on time since last heard on RF, and distance + * in terms of digipeater hops and/or phyiscal location. + * + * i/time + * i/time/hops + * i/time/hops/lat/lon/km + * + * + * "time" is maximum number of minutes since message addressee was last heard. + * This is required. + * + * "hops" is maximum number of digpeater hops. (i.e. 0 for heard directly). + * If hops is not specified, the maximum transmit digipeater hop count, + * from the IGTXVIA configuration will be used. + + * The rest is distanced, in kilometers, from given point. + * + * Examples: + * i/60/0 Heard in past 60 minutes directly. + * i/45 Past 45 minutes, default max digi hops. + * i/30/3 Default time, max 3 digi hops. + * i/30/8/42.6/-71.3/50. + * + * + * It only makes sense to use this for the IS>RF direction. + * The basic idea is that we want to transmit a "message" only if the + * addressee has been heard recently and is not too far away. + * + * After passing along a "message" we will also allow the next + * position report from the sender of the "message." + * That is done somewhere else. We are not concerned with it here. + * + *------------------------------------------------------------------------------*/ + +static int filt_i (pfstate_t *pf) +{ + char str[MAX_TOKEN_LEN]; + char *cp; + char sep[2]; + char *v; + int heardtime = 30; +#if PFTEST + int maxhops = 2; +#else + int maxhops = save_igate_config_p->max_digi_hops; // from IGTXVIA config. +#endif + double dlat = G_UNKNOWN; + double dlon = G_UNKNOWN; + double km = G_UNKNOWN; + + + char src[AX25_MAX_ADDR_LEN]; + char *infop = NULL; + int info_len; + //char *f; + //char addressee[AX25_MAX_ADDR_LEN]; + + + strlcpy (str, pf->token_str, sizeof(str)); + sep[0] = str[1]; + sep[1] = '\0'; + cp = str + 2; + +// Get parameters or defaults. + + v = strsep (&cp, sep); + + if (v != NULL && strlen(v) > 0) { + heardtime = atoi(v); + } + else { + print_error (pf, "Missing time limit for IGate message filter."); + return (-1); + } + + v = strsep (&cp, sep); + + if (v != NULL) { + if (strlen(v) > 0) { + maxhops = atoi(v); + } + else { + print_error (pf, "Missing max digipeater hops for IGate message filter."); + return (-1); + } + + v = strsep (&cp, sep); + if (v != NULL && strlen(v) > 0) { + dlat = atof(v); + + v = strsep (&cp, sep); + if (v != NULL && strlen(v) > 0) { + dlon = atof(v); + } + else { + print_error (pf, "Missing longitude for IGate message filter."); + return (-1); + } + + v = strsep (&cp, sep); + if (v != NULL && strlen(v) > 0) { + km = atof(v); + } + else { + print_error (pf, "Missing distance, in km, for IGate message filter."); + return (-1); + } + } + + v = strsep (&cp, sep); + if (v != NULL) { + print_error (pf, "Something unexpected after distance for IGate message filter."); + return (-1); + } + } + +#if PFTEST + text_color_set(DW_COLOR_DEBUG); + dw_printf ("debug: IGate message filter, %d minutes, %d hops, %.2f %.2f %.2f km\n", + heardtime, maxhops, dlat, dlon, km); +#endif + + +/* + * Get source address and info part. + * Addressee has already been extracted into pf->decoded.g_addressee. + */ + + memset (src, 0, sizeof(src)); + ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src); + info_len = ax25_get_info (pf->pp, (unsigned char **)(&infop)); + + if (infop == NULL) return (0); + if (info_len < 1) return (0); + +// Determine packet type. We are interested only in "message." +// Telemetry metadata is not considered a message. + + if (*infop != ':') return (0); + if (is_telem_metadata(infop)) return (0); + + +#if defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax. + + (void)dlat; // Suppress set and not used warning. + (void)dlon; + (void)km; + (void)maxhops; + (void)heardtime; + + return (1); +#else + +/* + * Condition 1: + * "the receiving station has been heard within range within a predefined time + * period (range defined as digi hops, distance, or both)." + */ + + int was_heard = mheard_was_recently_nearby ("addressee", pf->decoded.g_addressee, heardtime, maxhops, dlat, dlon, km); + + if ( ! was_heard) return (0); + +/* + * Condition 2: + * "the sending station has not been heard via RF within a predefined time period + * (packets gated from the Internet by other stations are excluded from this test)." + * + * This is the part I'm not so sure about. + * I guess the intention is that if the sender can be heard over RF, then the addressee + * might hear the sender without the help of Igate stations. + * Suppose the sender was 1 digipeater hop to the west and the addressee was 1 digipeater hop to the east. + * I can communicate with each of them with 1 digipeater hop but for them to reach each other, they + * might need 3 hops and using that many is generally frowned upon and rare. + * + * Maybe we could compromise here and say the sender must have been heard directly. + * It sent the message currently being processed so we must have heard it very recently, i.e. in + * the past minute, rather than the usual 30 or 60 minutes for the addressee. + */ + + was_heard = mheard_was_recently_nearby ("source", src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN); + + if (was_heard) return (0); + + return (1); + +#endif + +} /* end filt_i */ + + /*------------------------------------------------------------------- * * Name: print_error @@ -1131,6 +1532,21 @@ int main () pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1); + pftest (220, "i/30/8/42.6/-71.3/50", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); + pftest (222, "i/30/8/42.6/-71.3/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); + pftest (223, "i/30/8/42.6/-71.3", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); + pftest (224, "i/30/8/42.6/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); + pftest (225, "i/30/8/42.6", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); + pftest (226, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); + pftest (227, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); + + // FIXME: behaves differently on Windows and Linux + //pftest (228, "i/30/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); + + pftest (229, "i/30", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); + pftest (230, "i/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); + +// TODO: to be continued... if (error_count > 0) { text_color_set (DW_COLOR_ERROR); diff --git a/pfilter.h b/pfilter.h index 18b4f61..d54e056 100644 --- a/pfilter.h +++ b/pfilter.h @@ -1,6 +1,13 @@ /* pfilter.h */ + +#include "igate.h" // for igate_config_s + + + +void pfilter_init (struct igate_config_s *p_igate_config, int debug_level); + int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs); int is_telem_metadata (char *infop); \ No newline at end of file diff --git a/xmit.c b/xmit.c index ba996b7..ee82ee1 100644 --- a/xmit.c +++ b/xmit.c @@ -72,6 +72,7 @@ #include "ptt.h" #include "dtime_now.h" #include "morse.h" +#include "dtmf.h" #include "xid.h" @@ -116,6 +117,8 @@ static int g_debug_xmit_packet; /* print packet in hexadecimal form for debuggi #define MS_TO_BITS(ms,ch) (((ms)*xmit_bits_per_sec[(ch)])/1000) +#define MAXX(a,b) (((a)>(b)) ? (a) : (b)) + #if __WIN32__ static unsigned __stdcall xmit_thread (void *arg); @@ -139,6 +142,7 @@ 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); +static void xmit_dtmf (int c, packet_t pp, int speed); /*------------------------------------------------------------------- @@ -366,13 +370,14 @@ void xmit_set_txtail (int channel, int value) * * FLAVOR_SPEECH - Destination address is SPEECH. * FLAVOR_MORSE - Destination address is MORSE. + * FLAVOR_DTMF - Destination address is DTMF. * 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; +typedef enum flavor_e { FLAVOR_APRS_NEW, FLAVOR_APRS_DIGI, FLAVOR_SPEECH, FLAVOR_MORSE, FLAVOR_DTMF, FLAVOR_OTHER } flavor_t; static flavor_t frame_flavor (packet_t pp) { @@ -392,6 +397,10 @@ static flavor_t frame_flavor (packet_t pp) return (FLAVOR_MORSE); } + if (strcmp(dest, "DTMF") == 0) { + return (FLAVOR_DTMF); + } + /* 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. */ @@ -448,7 +457,7 @@ static flavor_t frame_flavor (packet_t pp) * * Version 1.4: Rearranged logic for bundling multiple frames into a single transmission. * - * The rule is that Speech, Morse Code, and APRS digipeated frames + * The rule is that Speech, Morse Code, DTMF, and APRS digipeated frames * are all sent separately. The rest can be bundled. * *--------------------------------------------------------------------*/ @@ -507,9 +516,10 @@ static void * xmit_thread (void *arg) * * If destination is "SPEECH" send info part to speech synthesizer. * If destination is "MORSE" send as morse code. + * If destination is "DTMF" send as Touch Tones. */ - int ssid, wpm; + int ssid, wpm, speed; switch (frame_flavor(pp)) { @@ -534,6 +544,14 @@ static void * xmit_thread (void *arg) xmit_morse (chan, pp, wpm); break; + case FLAVOR_DTMF: + speed = ax25_get_ssid(pp, AX25_DESTINATION); + if (speed == 0) speed = 5; // default half of maximum + if (speed > 10) speed = 10; + + xmit_dtmf (chan, pp, speed); + break; + case FLAVOR_APRS_DIGI: xmit_ax25_frames (chan, prio, pp, 1); /* 1 means don't bundle */ break; @@ -740,6 +758,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) case FLAVOR_SPEECH: case FLAVOR_MORSE: + case FLAVOR_DTMF: case FLAVOR_APRS_DIGI: default: done = 1; // not eligible for bundling. @@ -938,7 +957,7 @@ static int send_one_frame (int c, int p, packet_t pp) * Transmit the frame. */ flen = ax25_pack (pp, fbuf); - assert (flen >= 1 && flen <= sizeof(fbuf)); + assert (flen >= 1 && flen <= (int)(sizeof(fbuf))); int send_invalid_fcs2 = 0; @@ -1091,6 +1110,7 @@ int xmit_speak_it (char *script, int c, char *orig_msg) * * Description: Turn on transmitter. * Send text as Morse code. + * A small amount of quiet padding will appear at start and end. * Turn off transmitter. * *--------------------------------------------------------------------*/ @@ -1100,6 +1120,8 @@ static void xmit_morse (int c, packet_t pp, int wpm) { int info_len; unsigned char *pinfo; + int length_ms, wait_ms; + double start_ptt, wait_until, now; info_len = ax25_get_info (pp, &pinfo); @@ -1108,8 +1130,22 @@ static void xmit_morse (int c, packet_t pp, int wpm) dw_printf ("[%d.morse] \"%s\"\n", c, pinfo); ptt_set (OCTYPE_PTT, c, 1); + start_ptt = dtime_now(); - morse_send (c, (char*)pinfo, wpm, xmit_txdelay[c] * 10, xmit_txtail[c] * 10); + // make txdelay at least 300 and txtail at least 250 ms. + + length_ms = morse_send (c, (char*)pinfo, wpm, MAXX(xmit_txdelay[c] * 10, 300), MAXX(xmit_txtail[c] * 10, 250)); + + // there is probably still sound queued up in the output buffers. + + wait_until = start_ptt + length_ms * 0.001; + + now = dtime_now(); + + wait_ms = (int) ( ( wait_until - now ) * 1000 ); + if (wait_ms > 0) { + SLEEP_MS(wait_ms); + } ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); @@ -1117,6 +1153,72 @@ static void xmit_morse (int c, packet_t pp, int wpm) } /* end xmit_morse */ + +/*------------------------------------------------------------------- + * + * Name: xmit_dtmf + * + * Purpose: After we have a clear channel, and possibly waited a random time, + * we transmit information part of frame as DTMF tones. + * + * Inputs: c - Channel number. + * + * pp - Packet object pointer. + * It will be deleted so caller should not try + * to reference it after this. + * + * speed - Button presses per second. + * + * Description: Turn on transmitter. + * Send text as touch tones. + * A small amount of quiet padding will appear at start and end. + * Turn off transmitter. + * + *--------------------------------------------------------------------*/ + + +static void xmit_dtmf (int c, packet_t pp, int speed) +{ + int info_len; + unsigned char *pinfo; + int length_ms, wait_ms; + double start_ptt, wait_until, now; + + + info_len = ax25_get_info (pp, &pinfo); + (void)info_len; + text_color_set(DW_COLOR_XMIT); + dw_printf ("[%d.dtmf] \"%s\"\n", c, pinfo); + + ptt_set (OCTYPE_PTT, c, 1); + start_ptt = dtime_now(); + + // make txdelay at least 300 and txtail at least 250 ms. + + length_ms = dtmf_send (c, (char*)pinfo, speed, MAXX(xmit_txdelay[c] * 10, 300), MAXX(xmit_txtail[c] * 10, 250)); + + // there is probably still sound queued up in the output buffers. + + wait_until = start_ptt + length_ms * 0.001; + + now = dtime_now(); + + wait_ms = (int) ( ( wait_until - now ) * 1000 ); + if (wait_ms > 0) { + SLEEP_MS(wait_ms); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Oops. CPU too slow to keep up with DTMF generation.\n"); + } + + ptt_set (OCTYPE_PTT, c, 0); + ax25_delete (pp); + +} /* end xmit_dtmf */ + + + /*------------------------------------------------------------------- * * Name: wait_for_clear_channel