diff --git a/CHANGES.md b/CHANGES.md index 9b31ec35..0102c2eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,82 @@ # Revision History # + +## Version 1.4 -- April 2017 ## + + +### New Features: ### + +- AX.25 v2.2 connected mode. See chapter 10 of User Guide for details. + +- 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. + +- New beacon type, IBEACON, for sending IGate statistics. + +- 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. +- 2400 & 4800 bps PSK modems. See ***2400-4800-PSK-for-APRS-Packet-Radio.pdf*** in the doc directory for discussion. + +- The top speed of 9600 bps has been increased to 38400. You will need a sound card capable of 96k or 192k samples per second for the higher rates. Radios must also have adequate bandwidth. See ***Going-beyond-9600-baud.pdf*** in the doc directory for more details. + +- Better decoder performance for 9600 and higher especially for low audio sample rate to baud ratios. + +- Generate waypoint sentences for use by AvMap G5 / G6 or other mapping devices or applications. Formats include + - $GPWPL - NMEA generic with only location and name. + - $PGRMW - Garmin, adds altitude, symbol, and comment to previously named waypoint. + - $PMGNWPL - Magellan, more complete for stationary objects. + - $PKWDWPL - Kenwood with APRS style symbol but missing comment. + + +- DTMF tones can be sent by putting "DTMF" in the destination address, similar to the way that Morse Code is sent. + +- Take advantage of new 'gpio' group and new /sys/class/gpio ownership in Raspbian Jessie. + +- Handle more complicated gpio naming for CubieBoard, etc. + +- More flexible dw-start.sh start up script for both GUI and CLI environments. + + + +### Bugs Fixed: ### + +- The transmitter (PTT control) was being turned off too soon when sending Morse Code. + +- The -qd (quiet decode) command line option now suppresses errors about improperly formed Telemetry packets. + +- Longer tocall.txt files can now be handled. + +- Sometimes kissattach would have an issue with the Dire Wolf pseudo terminal. This showed up most often on Raspbian but sometimes occurred with other versions of Linux. + + *kissattach: Error setting line discipline: TIOCSETD: Device or resource busy + Are you sure you have enabled MKISS support in the kernel + or, if you made it a module, that the module is loaded?* + + +- Sometimes writes to a pseudo terminal would block causing the received +frame processing thread to hang. The first thing you will notice is that +received frames are not being printed. After a while this message will appear: + + *Received frame queue is out of control. Length=... Reader thread is probably + frozen. This can be caused by using a pseudo terminal (direwolf -p) where + another application is not reading the frames from the other side.* + +- -p command line option caused segmentation fault with glibc >= 2.24. + + +- The Windows version 1.3 would crash when starting to transmit on Windows XP. There have also been some other reports of erratic behavior on Windows. The crashing problem was fixed in in the 1.3.1 patch release. Linux version was not affected. + +- IGate did not retain nul characters in the information part of a packet. This should never happen with a valid APRS packet but there are a couple cases where it has. If we encounter these malformed packets, pass them along as-is, rather than truncating. + +- Don't digipeat packets when the source is my call. + + + ---------- ## Version 1.3 -- May 2016 ## -This is the same as the 1.3 beta test version with a few minor documentation updates. If you are already using 1.3 beta test, there is no need to install this. - ### New Features: ### - Support for Mac OS X. @@ -63,7 +133,7 @@ such as PowerPC or MIPS. - Improved decoder performance. Over 1000 error-free frames decoded from WA8LMF TNC Test CD. -See "A-Better-APRS-Packet-Demodulator.pdf" for details. +See ***A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf*** for details. - Up to 3 soundcards and 6 radio channels can be handled at the same time. @@ -274,8 +344,7 @@ to rebuild it from source. ### New Features: ### -- Added APRStt gateway capability. For details, see: -**APRStt-Implementation-Notes.pdf** +- Added APRStt gateway capability. For details, see ***APRStt-Implementation-Notes.pdf*** ----------- diff --git a/Makefile.linux b/Makefile.linux index be9bd593..55777d04 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -12,9 +12,32 @@ all : $(APPS) direwolf.desktop direwolf.conf @echo " " CC := gcc -CFLAGS := -O3 -pthread -Igeotranz -LDFLAGS := -lm -lpthread -lrt +# 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. + +# The first assignment to CFLAGS and LDFLAGS uses +=, rather than :=, so +# we will inherit options already set in build environment. +# Explanation - https://github.com/wb2osz/direwolf/pull/38 + +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 @@ -91,7 +114,7 @@ endif # 245 # 75 -01 # 72 -02 -# 71 -03 +# 71 -03 # 73 -O3 -march=native # 42 -O3 -ffast-math # 42 -Ofast (note below) @@ -106,7 +129,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),) @@ -139,7 +181,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. @@ -193,10 +235,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 @@ -213,6 +272,8 @@ endif # Uncomment following lines to enable hamlib support. +# TODO: automate this too. See if hamlib has been installed. + #CFLAGS += -DUSE_HAMLIB #LDFLAGS += -lhamlib @@ -228,14 +289,14 @@ z := $(notdir ${CURDIR}) -direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \ - hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \ - fcs_calc.o ax25_pad.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 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 nmea.o serial_port.o log.o telemetry.o \ - dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.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 ax25_link.o \ misc.a geotranz.a $(CC) -o $@ $^ $(LDFLAGS) ifneq ($(enable_gpsd),) @@ -248,15 +309,18 @@ endif # Optimization for slow processors. -demod.o : fsk_fast_filter.h +demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h -fsk_fast_filter.h : demod_afsk.c - $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c $(LDFLAGS) +fsk_fast_filter.h : gen_fff ./gen_fff > fsk_fast_filter.h +gen_fff : demod_afsk.c dsp.c textcolor.c + echo " " > tune.h + $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS) + # # The destination field is often used to identify the manufacturer/model. @@ -322,15 +386,15 @@ log2gpx : log2gpx.c textcolor.o misc.a # Test application to generate sound. -gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c misc.a +gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) # Unit test for AFSK demodulator -atest : atest.c demod.o demod_afsk.o demod_9600.o \ +atest : atest.c demod.o demod_afsk.o demod_psk.o demod_9600.o \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.o ax25_pad.o decode_aprs.o dwgpsnmea.o \ - dwgps.o dwgpsd.o serial_port.o telemetry.o latlong.o symbols.o tt_text.o textcolor.o \ + dwgps.o dwgpsd.o serial_port.o telemetry.o dtime_now.o latlong.o symbols.o tt_text.o textcolor.o \ misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) @@ -522,7 +586,7 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon # the home directory or other desired location. # $(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/examples/direwolf.conf - $(INSTALL) -D --mode=644 dw-start.sh $(INSTALLDIR)/share/doc/direwolf/examples/dw-start.sh + $(INSTALL) -D --mode=755 dw-start.sh $(INSTALLDIR)/share/doc/direwolf/examples/dw-start.sh $(INSTALL) -D --mode=644 sdr.conf $(INSTALLDIR)/share/doc/direwolf/examples/sdr.conf $(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt $(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/examples/telem-balloon.conf @@ -549,19 +613,22 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon @echo " " +# Put sample configuration files in home directory. # These would be done as ordinary user. # The Raspberry Pi has ~/Desktop but Ubuntu does not. # TODO: Handle Linux variations correctly. +# Version 1.4 - Add "-n" option to avoid clobbering existing, probably customized, config files. + .PHONY: install-conf install-conf : direwolf.conf - cp direwolf.conf ~ - cp sdr.conf ~ - cp telemetry-toolkit/telem-m0xer-3.txt ~ - cp telemetry-toolkit/telem-*.conf ~ + cp -n direwolf.conf ~ + cp -n sdr.conf ~ + cp -n telemetry-toolkit/telem-m0xer-3.txt ~ + cp -n telemetry-toolkit/telem-*.conf ~ ifneq ($(wildcard $(HOME)/Desktop),) @echo " " @echo "This will add a desktop icon on some systems:" @@ -571,9 +638,13 @@ ifneq ($(wildcard $(HOME)/Desktop),) endif +# dw-start.sh is greatly improved in version 1.4. +# It should probably be part of install-conf because it is not just for the RPi. + .PHONY: install-rpi install-rpi : dw-start.sh - cp dw-start.sh ~ + chmod +x dw-start.sh + cp -n dw-start.sh ~ ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop @@ -585,15 +656,15 @@ install-rpi : dw-start.sh # Combine some unit tests into a single regression sanity check. -check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-modem1200 check-modem300 check-modem9600 +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? check-modem1200 : gen_packets atest - ./gen_packets -n 100 -o /tmp/test1.wav - ./atest -F0 -PE -L70 -G71 /tmp/test1.wav - ./atest -F1 -PE -L73 -G75 /tmp/test1.wav - #rm /tmp/test1.wav + ./gen_packets -n 100 -o /tmp/test12.wav + ./atest -F0 -PE -L63 -G71 /tmp/test12.wav + ./atest -F1 -PE -L70 -G75 /tmp/test12.wav + rm /tmp/test12.wav check-modem300 : gen_packets atest ./gen_packets -B300 -n 100 -o /tmp/test3.wav @@ -602,18 +673,35 @@ check-modem300 : gen_packets atest rm /tmp/test3.wav check-modem9600 : gen_packets atest - ./gen_packets -B9600 -n 100 -o /tmp/test9.wav - ./atest -B9600 -F0 -L57 -G59 /tmp/test9.wav - ./atest -B9600 -F1 -L66 -G67 /tmp/test9.wav - rm /tmp/test9.wav - + ./gen_packets -B9600 -n 100 -o /tmp/test96.wav + ./atest -B9600 -F0 -L50 -G54 /tmp/test96.wav + ./atest -B9600 -F1 -L55 -G59 /tmp/test96.wav + rm /tmp/test96.wav + +check-modem19200 : gen_packets atest + ./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav + ./atest -B19200 -F0 -L55 -G59 /tmp/test19.wav + ./atest -B19200 -F1 -L60 -G64 /tmp/test19.wav + rm /tmp/test19.wav + +check-modem2400 : gen_packets atest + ./gen_packets -B2400 -n 100 -o /tmp/test24.wav + ./atest -B2400 -F0 -L70 -G78 /tmp/test24.wav + ./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav + rm /tmp/test24.wav + +check-modem4800 : gen_packets atest + ./gen_packets -B2400 -n 100 -o /tmp/test48.wav + ./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav + ./atest -B2400 -F1 -L80 -G90 /tmp/test48.wav + rm /tmp/test48.wav # 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 dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS) ./dtest @@ -679,6 +767,32 @@ kisstest : kiss_frame.c ./kisstest rm kisstest +# Unit test for constructing frames besides UI. + +.PHONY: pad2test +pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o misc.a + $(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ $(LDFLAGS) + ./pad2test + 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 + + +# Unit Test for DTMF encode/decode. + +.PHONY: dtmftest +dtmftest : dtmf.c textcolor.o + $(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ $(LDFLAGS) + ./dtmftest + rm dtmftest + # ----------------------------- Manual tests and experiments --------------------------- @@ -694,7 +808,7 @@ itest : igate.c textcolor.c ax25_pad.c fcs_calc.c textcolor.o misc.a # Unit test for UDP reception with AFSK demodulator. # Temporary during development. Might not be useful anymore. -udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ +udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.o ax25_pad.o decode_aprs.o symbols.o textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) ./udptest @@ -702,16 +816,39 @@ udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o hdlc_rec # For demodulator tweaking experiments. # Dependencies of demod*.c, rather than .o, are intentional. -demod.o : tune.h +demod.o : tune.h + demod_afsk.o : tune.h + demod_9600.o : tune.h -testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o latlong.o symbols.o tune.h textcolor.o misc.a +demod_psk.o : tune.h + +tune.h : + echo " " > tune.h + + +testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ + fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o dtime_now.o latlong.o symbols.o tune.h textcolor.o misc.a $(CC) $(CFLAGS) -o atest $^ $(LDFLAGS) ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out +testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ + dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ + rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ + dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o \ + symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ + misc.a + rm -f atest96 + $(CC) $(CFLAGS) -o atest96 $^ $(LDFLAGS) + ./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out + #./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out + echo " " > tune.h @@ -757,8 +894,7 @@ dist-src : README.md CHANGES.md .PHONY: clean clean : - rm -f $(APPS) fsk_fast_filter.h *.o *.a direwolf.desktop - echo " " > tune.h + rm -f $(APPS) gen_fff tune.h fsk_fast_filter.h *.o *.a direwolf.desktop depend : $(wildcard *.c) diff --git a/Makefile.macosx b/Makefile.macosx index c0fc4e77..262ed453 100644 --- a/Makefile.macosx +++ b/Makefile.macosx @@ -24,8 +24,9 @@ # 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having # a hissy fit. Not check with GCC. -all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.conf - @echo " " +APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc + +all : $(APPS) direwolf.desktop direwolf.conf @echo " " @echo "Next step install with: " @echo " " @echo " sudo make install" @@ -70,7 +71,18 @@ endif #CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN) CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN) -CFLAGS := -Os -pthread -Igeotranz $(EXTRA_CFLAGS) + +# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24. +# Explanation here: https://github.com/wb2osz/direwolf/issues/62 + +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}]) # @@ -220,29 +232,34 @@ z := $(notdir ${CURDIR}) # Main application. -direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beacon.o \ - config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o \ - demod.o digipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.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 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 \ - nmea.o serial_port.o pfilter.o ptt.o rdq.o recv.o redecode.o rrbb.o server.o \ - symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xmit.o \ - dwgps.o dwgpsnmea.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 xid.o xmit.o \ + dwgps.o dwgpsnmea.o mheard.o $(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm # Optimization for slow processors. -demod.o : fsk_fast_filter.h +demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h -fsk_fast_filter.h : demod_afsk.c - $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm +fsk_fast_filter.h : gen_fff ./gen_fff > fsk_fast_filter.h +gen_fff : demod_afsk.c dsp.c textcolor.c + echo " " > tune.h + $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS) + + + # UTM, USNG, MGRS conversions. geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o @@ -392,10 +409,6 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon # These would be done as ordinary user. -# The Raspberry Pi has ~/Desktop but Ubuntu does not. - -# TODO: Handle Linux variations correctly. - .PHONY: install-conf install-conf : direwolf.conf @@ -406,7 +419,7 @@ install-conf : direwolf.conf # Separate application to decode raw data. -decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o +decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o $(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ -lm # Convert between text and touch tone representation. @@ -435,25 +448,33 @@ log2gpx : log2gpx.c # Test application to generate sound. -gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c +gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm -demod.o : tune.h +demod.o : tune.h + demod_afsk.o : tune.h + demod_9600.o : tune.h +demod_psk.o : tune.h + +tune.h : + echo " " > tune.h + + testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c tune.h textcolor.c + fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c dtime_now.o latlong.c symbols.c tune.h textcolor.c $(CC) $(CFLAGS) -o atest $^ -lm ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out -# Unit test for AFSK demodulator +# Unit test for demodulators -atest : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c +atest : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ + fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c dtest_now.o latlong.c symbols.c textcolor.c tt_text.c $(CC) $(CFLAGS) -o $@ $^ -lm -#atest : atest.c fsk_fast_filter.h demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ +#atest : atest.c fsk_fast_filter.h demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ # fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c # $(CC) $(CFLAGS) -o $@ $^ -lm @@ -513,7 +534,7 @@ depend : $(wildcard *.c) .PHONY: clean clean : - rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc \ + rm -f $(APPS) gen_fff \ fsk_fast_filter.h *.o *.a use_this_sdk echo " " > tune.h diff --git a/Makefile.win b/Makefile.win index 3a9d9f6f..3e1cefc1 100644 --- a/Makefile.win +++ b/Makefile.win @@ -16,18 +16,41 @@ # -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. # They also say it creates a dependency on mingwm10.dll but I'm not seeing that. +# Maybe that is for pthreads. We are using the Windows threads. + +# -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 := -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 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. # Benchmark results with MinGW gcc version 4.6.2. @@ -61,19 +84,25 @@ CFLAGS += -g # -------------------------------------- Main application -------------------------------- -demod.o : fsk_demod_state.h +# Not sure why this is here. + +demod.o : fsk_demod_state.h + demod_9600.o : fsk_demod_state.h + demod_afsk.o : fsk_demod_state.h +demod_psk.o : fsk_demod_state.h -direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \ - hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \ - fcs_calc.o ax25_pad.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 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 nmea.o serial_port.o log.o telemetry.o \ - dwgps.o dwgpsnmea.o dtime_now.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 ax25_link.o \ dw-icon.o regex.a misc.a geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 @@ -88,10 +117,13 @@ demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h -fsk_fast_filter.h : demod_afsk.c - $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c +fsk_fast_filter.h : gen_fff ./gen_fff > fsk_fast_filter.h +gen_fff : demod_afsk.c dsp.c textcolor.c + echo " " > tune.h + $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ + # # The destination field is often used to identify the manufacturer/model. @@ -157,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 $@ $^ @@ -198,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 $@ $^ @@ -207,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 @@ -235,36 +268,78 @@ strlcat.o : misc/strlcat.c # Combine some unit tests into a single regression sanity check. -check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-modem1200 check-modem300 check-modem9600 +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. check-modem1200 : gen_packets atest - gen_packets -n 100 -o test1.wav - atest -F0 -PE -L70 -G71 test1.wav - atest -F1 -PE -L73 -G75 test1.wav - #rm test1.wav + gen_packets -n 100 -o test12.wav + atest -F0 -PE -L64 -G72 test12.wav + atest -F1 -PE -L70 -G75 test12.wav + rm test12.wav check-modem300 : gen_packets atest gen_packets -B300 -n 100 -o test3.wav atest -B300 -F0 -L68 -G69 test3.wav - atest -B300 -F1 -L73 -G75 test3.wav + atest -B300 -F1 -L71 -G75 test3.wav rm test3.wav -check-modem9600 : gen_packets atest - gen_packets -B9600 -n 100 -o test9.wav - atest -B9600 -F0 -L57 -G59 test9.wav - atest -B9600 -F1 -L66 -G67 test9.wav - rm test9.wav - -# Unit test for AFSK demodulator +#FIXME: test full amplitude. -atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_9600.c \ +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 + atest -B9600 -F1 -L55 -G59 test96.wav + sleep 1 + 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 + atest -B19200 -F1 -L60 -G64 test19.wav + sleep 1 + rm test19.wav + +check-modem2400 : gen_packets atest + gen_packets -B2400 -n 100 -o test24.wav + sleep 1 + atest -B2400 -F0 -L70 -G78 test24.wav + atest -B2400 -F1 -L80 -G87 test24.wav + sleep 1 + rm test24.wav + +check-modem4800 : gen_packets atest + gen_packets -B4800 -n 100 -o test48.wav + sleep 1 + atest -B4800 -F0 -L70 -G74 test48.wav + atest -B4800 -F1 -L79 -G84 test48.wav + sleep 1 + rm test48.wav + + +# Unit test for demodulators + +atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ 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 \ + 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 $@ $^ @@ -272,8 +347,8 @@ atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_9600.c \ #atest -B 9600 z9.wav #atest za100.wav -atest9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ - rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c misc.a regex.a \ +atest9 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ + rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o misc.a regex.a \ fsk_fast_filter.h echo " " > tune.h $(CC) $(CFLAGS) -o $@ $^ @@ -284,8 +359,8 @@ atest9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.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 @@ -352,20 +427,49 @@ kisstest : kiss_frame.c rm kisstest.exe +# Unit test for constructing frames besides UI. + +.PHONY: pad2test +pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o regex.a misc.a + $(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ + ./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 + +# 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 ------------------------------- +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 +demod.o : tune.h demod_9600.o : tune.h demod_afsk.o : tune.h +demod_psk.o : tune.h - -testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.o fsk_demod_agc.h \ +testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.o fsk_demod_agc.h \ hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \ - dwgpsnmea.o dwgps.o serial_port.o tt_text.o regex.a misc.a + dwgpsnmea.o dwgps.o serial_port.o tt_text.o dtime_now.o regex.a misc.a rm -f atest.exe $(CC) $(CFLAGS) -o atest $^ ./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out @@ -375,27 +479,60 @@ testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.o fsk_demod_agc.h \ noisy3.wav : gen_packets ./gen_packets -B 300 -n 100 -o noisy3.wav -testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ - rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \ +testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ + rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o regex.a misc.a \ tune.h - rm -f atest.exe - $(CC) $(CFLAGS) -o atest $^ - ./atest -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out + rm -f atest3.exe + $(CC) $(CFLAGS) -o atest3 $^ + ./atest3 -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out echo " " > tune.h noisy96.wav : gen_packets ./gen_packets -B 9600 -n 100 -o noisy96.wav -testagc9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ - rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \ - tune.h - rm -f atest.exe - $(CC) $(CFLAGS) -o atest $^ - ./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out - #./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out +testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ + dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ + rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ + dwgpsnmea.o dwgps.o serial_port.o latlong.o \ + symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ + misc.a regex.a + rm -f atest96.exe + $(CC) $(CFLAGS) -o atest96 $^ + ./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out + #./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out + echo " " > tune.h + +testagc24 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ + dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ + rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ + dwgpsnmea.o dwgps.o serial_port.o latlong.o \ + symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ + misc.a regex.a + rm -f atest24.exe + sleep 1 + $(CC) $(CFLAGS) -o atest24 $^ + ./atest24 -B 2400 test2400.wav | grep "packets decoded in" >atest.out echo " " > tune.h +testagc48 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ + dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ + rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ + dwgpsnmea.o dwgps.o serial_port.o latlong.o \ + symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ + misc.a regex.a + rm -f atest48.exe + sleep 1 + $(CC) $(CFLAGS) -o atest48 $^ + ./atest48 -B 4800 test4800.wav | grep "packets decoded in" >atest.out + #./atest48 -B 4800 test4800.wav + echo " " > tune.h + + # Unit test for IGate itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a @@ -424,8 +561,8 @@ walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \ ax25_pad.o fcs_calc.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_9600.o rdq.o \ - server.o morse.o audio_stats.o dtime_now.o dlq.o \ + multi_modem.o demod.o demod_afsk.o demod_psk.c demod_9600.o rdq.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 @@ -494,67 +631,6 @@ dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2l dwespeak.bat \ telemetry-toolkit/* -.PHONY: dist-src -dist-src : README.md CHANGES.md \ - doc/User-Guide.pdf \ - doc/Raspberry-Pi-APRS.pdf \ - doc/Raspberry-Pi-APRS-Tracker.pdf \ - doc/APRStt-Implementation-Notes.pdf \ - doc/APRS-Telemetry-Toolkit.pdf \ - dw-start.sh dwespeak.bat dwespeak.sh \ - tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec - rm -f fsk_fast_filter.h - echo " " > tune.h - rm -f ../$z-src.zip - dos2unix generic.conf - dos2unix Makefile - dos2unix Makefile.linux - dos2unix Makefile.macosx - dos2unix telemetry-toolkit/telem-balloon.conf - dos2unix telemetry-toolkit/telem-balloon.pl - dos2unix telemetry-toolkit/telem-bits.pl - dos2unix telemetry-toolkit/telem-data.pl - dos2unix telemetry-toolkit/telem-data91.pl - dos2unix telemetry-toolkit/telem-eqns.pl - dos2unix telemetry-toolkit/telem-m0xer-3.txt - dos2unix telemetry-toolkit/telem-parm.pl - dos2unix telemetry-toolkit/telem-unit.pl - dos2unix telemetry-toolkit/telem-volts.py - dos2unix telemetry-toolkit/telem-volts.conf - (cd .. ; zip $z-src.zip \ - $z/README.md \ - $z/CHANGES.md \ - $z/LICENSE* \ - $z/doc/User-Guide.pdf \ - $z/doc/Raspberry-Pi-APRS.pdf \ - $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ - $z/doc/APRStt-Implementation-Notes.pdf \ - $z/doc/APRS-Telemetry-Toolkit.pdf \ - $z/Makefile.win $z/Makefile.linux $z/Makefile.macosx $z/Makefile \ - $z/*.c $z/*.h \ - $z/regex/* $z/misc/* $z/geotranz/* \ - $z/man1/* \ - $z/generic.conf \ - $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ - $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ - $z/dw-start.sh $z/direwolf.spec \ - $z/dwespeak.bat $z/dwespeak.sh \ - $z/telemetry-toolkit/* ) - unix2dos Makefile - unix2dos Makefile.linux - unix2dos Makefile.macosx - unix2dos telemetry-toolkit/telem-balloon.conf - unix2dos telemetry-toolkit/telem-balloon.pl - dos2unix telemetry-toolkit/telem-bits.pl - unix2dos telemetry-toolkit/telem-data.pl - unix2dos telemetry-toolkit/telem-data91.pl - unix2dos telemetry-toolkit/telem-eqns.pl - unix2dos telemetry-toolkit/telem-m0xer-3.txt - unix2dos telemetry-toolkit/telem-parm.pl - unix2dos telemetry-toolkit/telem-unit.pl - unix2dos telemetry-toolkit/telem-volts.py - unix2dos telemetry-toolkit/telem-volts.conf - # Reminders if pdf files are not up to date. diff --git a/README.md b/README.md index 69b9ad10..7d91dd3f 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,24 @@ ### Decoded Information from Radio Emissions for Windows Or Linux Fans ### -In the early days of Amateur Packet Radio, it was necessary to use a “Terminal Node Controller” (TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the “soundcard” interface of a computer and using software to decode the signals. +In the early days of Amateur Packet Radio, it was necessary to use an expensive “Terminal Node Controller” (TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the “soundcard” interface of a computer and using software to decode the signals. Dire Wolf is a software "soundcard" modem/TNC and [APRS](http://www.aprs.org/) encoder/decoder. It can be used stand-alone to observe APRS traffic, as a digipeater, [APRStt](http://www.aprs.org/aprstt.html) gateway, or Internet Gateway (IGate). It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [RMS Express](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/) and many others. -## Features ## +## Features & Benefits ## - Lower cost, higher performance alternative to hardware TNC. Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf.net/TNCtest/). - Ideal for building a Raspberry Pi digipeater & IGate. -- Data rates: 300 AFSK, 1200 AFSK, 2400 QPSK, 4800 8PSK, and 9600/19200/38400 K9NG/G3RUH. +- Data rates: 300 AFSK, 1200 AFSK, 2400 QPSK, 4800 8PSK, and 9600/19200/38400 bps K9NG/G3RUH. - Interface with applications by - [AGW](http://uz7.ho.ua/includes/agwpeapi.htm) network protocol - [KISS](http://www.ax25.net/kiss.aspx) serial port - - [KISS](http://www.ax25.net/kiss.aspx) network protocol + - [KISS](http://www.ax25.net/kiss.aspx) TCP network protocol - Decoding of received information for troubleshooting. @@ -48,45 +48,72 @@ Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf - Runs in 3 different environments: - Microsoft Windows XP or later - - Linux, regular PC or single board computer such as Raspberry Pi, BeagleBone Black, or cubieboard 2 + - Linux, regular PC/laptop or single board computer such as Raspberry Pi, BeagleBone Black, cubieboard 2, or C.H.I.P. - Mac OS X - + +## Documentation ## + +[Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc) + +[Latest Development Version](https://github.com/wb2osz/direwolf/tree/dev/doc) + + ## Installation ## ### Windows ### -Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window. +Go to the [**releases** page](https://github.com/wb2osz/direwolf/releases). Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window. -For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc). +For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). -### Linux - Download with web browser ### -Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose desired release and download the source as zip or compressed tar file. Unpack the files, with "unzip" or "tar xfz," and then: - cd direwolf-* +### Linux - Using git clone (recommended) ### + + cd ~ + git clone https://www.github.com/wb2osz/direwolf + cd direwolf make sudo make install make install-conf -For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** +This should give you the most recent stable release. If you want the latest (possibly unstable) development version, use "git checkout dev" before the first "make" command. -### Linux - Using git clone ### +For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** - cd ~ - git clone https://www.github.com/wb2osz/direwolf - cd direwolf + +### Linux - Using apt-get (Debian flavor operating systems) ### + +Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. + + sudo apt-get update + apt-cache showpkg direwolf + sudo apt-get install direwolf + + +### Linux - Using yum (Red Hat flavor operating systems) ### + +Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. + + sudo yum check-update + sudo yum list direwolf + sudo yum install direwolf + +### Linux - Download source in tar or zip file ### + +Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose desired release and download the source as zip or compressed tar file. Unpack the files, with "unzip" or "tar xfz," and then: + + cd direwolf-* make sudo make install make install-conf -This should give you the most recent stable release. If you want the latest (unstable) development version, use "git checkout dev" instead before the first "make" command. - -For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** +For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** ## Join the conversation ## -Here are some good places to share information: +Here are some good places to ask questions and share your experience: - [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) diff --git a/aclients.c b/aclients.c index fe5e93c6..28b7cf32 100644 --- a/aclients.c +++ b/aclients.c @@ -51,17 +51,14 @@ * Linux: Use the BSD socket interface. */ +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include -// default is 0x0400 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ -#include +#include // _WIN32_WINNT must be set to 0x0501 before including this + #else -//#define __USE_XOPEN2KXSI 1 -//#define __USE_XOPEN 1 #include #include #include @@ -83,7 +80,6 @@ #include -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "version.h" @@ -468,7 +464,11 @@ static void * client_thread_net (void *arg) // Try each address until we find one that is successful. for (n=0; n= 0 && mon_cmd.data_len < sizeof(data)); + assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data))); if (mon_cmd.data_len > 0) { #if __WIN32__ diff --git a/aprs_tt.c b/aprs_tt.c index cf4e1c74..cf00cc8b 100644 --- a/aprs_tt.c +++ b/aprs_tt.c @@ -39,6 +39,8 @@ #define APRS_TT_C 1 +#include "direwolf.h" + // TODO: clean up terminolgy. // "Message" has a specific meaning in APRS and this is not it. @@ -56,7 +58,6 @@ #include #include -#include "direwolf.h" #include "version.h" #include "ax25_pad.h" #include "hdlc_rec2.h" /* for process_rec_frame */ @@ -105,7 +106,9 @@ static int parse_aprstt3_call (char *e); static int parse_location (char *e); static int parse_comment (char *e); static int expand_macro (char *e); +#ifndef TT_MAIN static void raw_tt_data_to_app (int chan, char *msg); +#endif static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize); #if TT_MAIN @@ -378,6 +381,7 @@ void aprs_tt_sequence (int chan, char *msg) #endif #if TT_MAIN + (void)err; // suppress variable set but not used warning. check_result (); // for unit testing. #else @@ -1443,7 +1447,7 @@ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char * len = strlen(tt_config.ttloc_ptr[ipat].pattern); - if (strlen(e) == len) { + if ((int)(strlen(e)) == len) { match = 1; strlcpy (xstr, "", valstrsize); @@ -1658,6 +1662,7 @@ static int parse_comment (char *e) * *----------------------------------------------------------------*/ +#ifndef TT_MAIN static void raw_tt_data_to_app (int chan, char *msg) { @@ -1668,9 +1673,6 @@ static void raw_tt_data_to_app (int chan, char *msg) char src[10], dest[10]; char raw_tt_msg[256]; packet_t pp; - char *c, *s; - int i; - int err; alevel_t alevel; @@ -1706,7 +1708,7 @@ static void raw_tt_data_to_app (int chan, char *msg) alevel.mark = -2; alevel.space = -2; - dlq_append (DLQ_REC_FRAME, chan, -1, 0, pp, alevel, RETRY_NONE, "tt"); + dlq_rec_frame (chan, -1, 0, pp, alevel, RETRY_NONE, "tt"); } else { text_color_set(DW_COLOR_ERROR); @@ -1716,6 +1718,7 @@ static void raw_tt_data_to_app (int chan, char *msg) #endif } +#endif /*------------------------------------------------------------------ diff --git a/atest.c b/atest.c index 7ab60a89..c78094a7 100644 --- a/atest.c +++ b/atest.c @@ -2,7 +2,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 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 @@ -59,6 +59,7 @@ // #define X 1 +#include "direwolf.h" #include #include @@ -79,6 +80,7 @@ #include "hdlc_rec2.h" #include "dlq.h" #include "ptt.h" +#include "dtime_now.h" @@ -178,7 +180,10 @@ int main (int argc, char *argv[]) int err; int c; int channel; - time_t start_time; + + double start_time; // Time when we started so we can measure elapsed time. + double duration; // Length of the audio file in seconds. + double elapsed; // Time it took us to process it. #if defined(EXPERIMENT_G) || defined(EXPERIMENT_H) @@ -291,35 +296,55 @@ int main (int argc, char *argv[]) case 'B': /* -B for data Bit rate */ /* 300 implies 1600/1800 AFSK. */ /* 1200 implies 1200/2200 AFSK. */ + /* 2400 implies V.26 */ /* 9600 implies scrambled. */ my_audio_config.achan[0].baud = atoi(optarg); dw_printf ("Data rate set to %d bits / second.\n", my_audio_config.achan[0].baud); - if (my_audio_config.achan[0].baud < 100 || my_audio_config.achan[0].baud > 10000) { + if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); + dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } + + /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ + /* that need to be kept in sync. Maybe it could be a common function someday. */ + if (my_audio_config.achan[0].baud < 600) { my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = 1600; my_audio_config.achan[0].space_freq = 1800; strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); } - else if (my_audio_config.achan[0].baud > 2400) { + else if (my_audio_config.achan[0].baud < 1800) { + my_audio_config.achan[0].modem_type = MODEM_AFSK; + my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; + my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; + // Should default to E+ or something similar later. + } + else if (my_audio_config.achan[0].baud < 3600) { + my_audio_config.achan[0].modem_type = MODEM_QPSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + dw_printf ("Using V.26 QPSK rather than AFSK.\n"); + } + else if (my_audio_config.achan[0].baud < 7200) { + my_audio_config.achan[0].modem_type = MODEM_8PSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + dw_printf ("Using V.27 8PSK rather than AFSK.\n"); + } + else { my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; my_audio_config.achan[0].mark_freq = 0; my_audio_config.achan[0].space_freq = 0; strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); } - else { - my_audio_config.achan[0].modem_type = MODEM_AFSK; - my_audio_config.achan[0].mark_freq = 1200; - my_audio_config.achan[0].space_freq = 2200; - } break; case 'P': /* -P for modem profile. */ @@ -336,7 +361,7 @@ int main (int argc, char *argv[]) if (decimate < 1 || decimate > 8) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unreasonable value for -D.\n"); - exit (1); + exit (EXIT_FAILURE); } dw_printf ("Divide audio sample rate by %d\n", decimate); my_audio_config.achan[0].decimate = decimate; @@ -349,7 +374,7 @@ int main (int argc, char *argv[]) if (my_audio_config.achan[0].fix_bits < RETRY_NONE || my_audio_config.achan[0].fix_bits >= RETRY_MAX) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid Fix Bits level.\n"); - exit (1); + exit (EXIT_FAILURE); } break; @@ -407,11 +432,10 @@ int main (int argc, char *argv[]) text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't open file for read: %s\n", argv[optind]); //perror ("more info?"); - exit (1); + exit (EXIT_FAILURE); } - start_time = time(NULL); - + start_time = dtime_now(); /* * Read the file header. @@ -437,12 +461,12 @@ int main (int argc, char *argv[]) if (strncmp(chunk.id, "fmt ", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id); - exit(1); + exit(EXIT_FAILURE); } if (chunk.datasize != 16 && chunk.datasize != 18) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18. Found %d.\n", chunk.datasize); - exit(1); + exit(EXIT_FAILURE); } err = fread (&format, (size_t)chunk.datasize, (size_t)1, fp); @@ -452,12 +476,26 @@ int main (int argc, char *argv[]) if (strncmp(wav_data.data, "data", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Found \"%4.4s\" where \"data\" was expected.\n", wav_data.data); - exit(1); + exit(EXIT_FAILURE); } - // TODO: Should have proper message, not abort. - assert (format.nchannels == 1 || format.nchannels == 2); - assert (format.wbitspersample == 8 || format.wbitspersample == 16); + if (format.wformattag != 1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Sorry, I only understand audio format 1 (PCM). This file has %d.\n", format.wformattag); + exit (EXIT_FAILURE); + } + + if (format.nchannels != 1 && format.nchannels != 2) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Sorry, I only understand 1 or 2 channels. This file has %d.\n", format.nchannels); + exit (EXIT_FAILURE); + } + + if (format.wbitspersample != 8 && format.wbitspersample != 16) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Sorry, I only understand 8 or 16 bits per sample. This file has %d.\n", format.wbitspersample); + exit (EXIT_FAILURE); + } my_audio_config.adev[0].samples_per_sec = format.nsamplespersec; my_audio_config.adev[0].bits_per_sample = format.wbitspersample; @@ -467,12 +505,16 @@ int main (int argc, char *argv[]) if (format.nchannels == 2) my_audio_config.achan[1].valid = 1; text_color_set(DW_COLOR_INFO); - dw_printf ("%d samples per second\n", my_audio_config.adev[0].samples_per_sec); - dw_printf ("%d bits per sample\n", my_audio_config.adev[0].bits_per_sample); - dw_printf ("%d audio channels\n", my_audio_config.adev[0].num_channels); - dw_printf ("%d audio bytes in file\n", (int)(wav_data.datasize)); + dw_printf ("%d samples per second. %d bits per sample. %d audio channels.\n", + my_audio_config.adev[0].samples_per_sec, + my_audio_config.adev[0].bits_per_sample, + my_audio_config.adev[0].num_channels); + duration = (double) wav_data.datasize / + ((my_audio_config.adev[0].bits_per_sample / 8) * my_audio_config.adev[0].num_channels * my_audio_config.adev[0].samples_per_sec); + dw_printf ("%d audio bytes in file. Duration = %.1f seconds.\n", + (int)(wav_data.datasize), + duration); dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits); - /* * Initialize the AFSK demodulator and HDLC decoder. @@ -496,8 +538,10 @@ int main (int argc, char *argv[]) audio_sample = demod_get_sample (ACHAN2ADEV(c)); - if (audio_sample >= 256 * 256) + if (audio_sample >= 256 * 256) { e_o_f = 1; + continue; + } if (c == 0) sample_number++; @@ -527,20 +571,24 @@ int main (int argc, char *argv[]) dw_printf ("%d\n", count[j]); } #endif - dw_printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time)); + + + elapsed = dtime_now() - start_time; + + dw_printf ("%d packets decoded in %.3f seconds. %.1f x realtime\n", packets_decoded, elapsed, duration/elapsed); if (error_if_less_than != -1 && packets_decoded < error_if_less_than) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n * * * TEST FAILED: number decoded is less than %d * * * \n", error_if_less_than); - exit (1); + exit (EXIT_FAILURE); } if (error_if_greater_than != -1 && packets_decoded > error_if_greater_than) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than); - exit (1); + exit (EXIT_FAILURE); } - exit (0); + exit (EXIT_SUCCESS); } @@ -596,7 +644,7 @@ void rdq_append (rrbb_t rrbb) * This is called when we have a good frame. */ -void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { char stemp[500]; @@ -695,6 +743,24 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, 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 */ @@ -740,7 +806,7 @@ static void usage (void) { dw_printf ("\n"); dw_printf (" -0 Use channel 0 (left) of stereo audio (default).\n"); dw_printf (" -1 use channel 1 (right) of stereo audio.\n"); - dw_printf (" -1 decode both channels of stereo audio.\n"); + dw_printf (" -2 decode both channels of stereo audio.\n"); dw_printf ("\n"); dw_printf (" wav-file-in is a WAV format audio file.\n"); dw_printf ("\n"); diff --git a/audio.c b/audio.c index 84aa854a..a27bc393 100644 --- a/audio.c +++ b/audio.c @@ -60,6 +60,7 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" #include #include @@ -88,7 +89,6 @@ #endif -#include "direwolf.h" #include "audio.h" #include "audio_stats.h" #include "textcolor.h" @@ -375,8 +375,8 @@ int audio_open (struct audio_s *pa) { struct sockaddr_in si_me; - int slen=sizeof(si_me); - int data_size = 0; + //int slen=sizeof(si_me); + //int data_size = 0; //Create UDP Socket if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { @@ -1003,7 +1003,7 @@ int audio_get (int a) case AUDIO_IN_TYPE_SDR_UDP: while (adev[a].inbuf_next >= adev[a].inbuf_len) { - int ch, res,i; + int res; assert (adev[a].udp_sock > 0); res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); @@ -1038,7 +1038,8 @@ int audio_get (int a) case AUDIO_IN_TYPE_STDIN: while (adev[a].inbuf_next >= adev[a].inbuf_len) { - int ch, res,i; + //int ch, res,i; + int res; res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes); if (res <= 0) { diff --git a/audio.h b/audio.h index 674c27ed..71d37517 100644 --- a/audio.h +++ b/audio.h @@ -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. */ @@ -101,11 +109,12 @@ struct audio_s { /* Could all be the same or different. */ - enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_OFF } modem_type; + enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF } modem_type; /* Usual AFSK. */ /* Baseband signal. Not used yet. */ /* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */ + /* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */ /* No modem. Might want this for DTMF only channel. */ @@ -130,8 +139,9 @@ struct audio_s { int mark_freq; /* Two tones for AFSK modulation, in Hz. */ int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */ - int baud; /* Data bits (more accurately, symbols) per second. */ + int baud; /* Data bits per second. */ /* Standard rates are 1200 for VHF and 300 for HF. */ + /* This should really be called bits per second. */ /* Next 3 come from config file or command line. */ @@ -164,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 3 /* number of values above. i.e. last value +1. */ struct { @@ -187,8 +199,19 @@ struct audio_s { ptt_line_t ptt_line; /* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */ ptt_line_t ptt_line2; /* Optional second one: PTT_LINE_NONE when not used. */ - int ptt_gpio; /* GPIO number. */ - + int out_gpio_num; /* GPIO number. Originally this was only for PTT. */ + /* It is now more general. */ + /* octrl array is indexed by PTT, DCD, or CONnected indicator. */ + +#define MAX_GPIO_NAME_LEN 16 // 12 would cover any case I've seen so this should be safe + + char out_gpio_name[MAX_GPIO_NAME_LEN]; + /* orginally, gpio number NN was assumed to simply */ + /* have the name gpioNN but this turned out not to be */ + /* the case for CubieBoard where it was longer. */ + /* This is filled in by ptt_init so we don't have to */ + /* recalculate it each time we access it. */ + int ptt_lpt_bit; /* Bit number for parallel printer port. */ /* Bit 0 = pin 2, ..., bit 7 = pin 9. */ @@ -202,13 +225,26 @@ struct audio_s { } octrl[NUM_OCTYPES]; + + /* Each channel can also have associated input lines. */ + /* So far, we just have one for transmit inhibit. */ + #define ICTYPE_TXINH 0 -#define NUM_ICTYPES 1 +#define NUM_ICTYPES 1 /* number of values above. i.e. last value +1. */ struct { ptt_method_t method; /* none, serial port, GPIO, LPT. */ - int gpio; /* GPIO number */ + + int in_gpio_num; /* GPIO number */ + + char in_gpio_name[MAX_GPIO_NAME_LEN]; + /* orginally, gpio number NN was assumed to simply */ + /* have the name gpioNN but this turned out not to be */ + /* the case for CubieBoard where it was longer. */ + /* This is filled in by ptt_init so we don't have to */ + /* recalculate it each time we access it. */ + int invert; /* 1 = active low */ } ictrl[NUM_ICTYPES]; @@ -278,8 +314,12 @@ struct audio_s { /* 44100 works a little better than 22050. */ /* If you have a reasonable machine, use the highest rate. */ #define MIN_SAMPLES_PER_SEC 8000 -#define MAX_SAMPLES_PER_SEC 48000 /* Formerly 44100. */ - /* Software defined radio often uses 48000. */ +//#define MAX_SAMPLES_PER_SEC 48000 /* Originally 44100. Later increased because */ + /* Software Defined Radio often uses 48000. */ + +#define MAX_SAMPLES_PER_SEC 192000 /* The cheap USB-audio adapters (e.g. CM108) can handle 44100 and 48000. */ + /* The "soundcard" in my desktop PC can do 96kHz or even 192kHz. */ + /* We will probably need to increase the sample rate to go much above 9600 baud. */ #define DEFAULT_BITS_PER_SAMPLE 16 @@ -301,12 +341,12 @@ struct audio_s { #define DEFAULT_BAUD 1200 /* Used for sanity checking in config file and command line options. */ -/* 9600 is known to work. */ -/* TODO: Is 19200 possible with a soundcard at 44100 samples/sec? */ +/* 9600 baud is known to work. */ +/* TODO: Is 19200 possible with a soundcard at 44100 samples/sec or do we need a higher sample rate? */ #define MIN_BAUD 100 -#define MAX_BAUD 10000 - +//#define MAX_BAUD 10000 +#define MAX_BAUD 40000 // Anyone want to try 38.4 k baud? /* * Typical transmit timings for VHF. diff --git a/audio_portaudio.c b/audio_portaudio.c index ead1ffca..6d53f6af 100644 --- a/audio_portaudio.c +++ b/audio_portaudio.c @@ -37,6 +37,8 @@ #if defined(USE_PORTAUDIO) +#include "direwolf.h" + #include #include #include @@ -54,7 +56,6 @@ #include #include -#include "direwolf.h" #include "audio.h" #include "audio_stats.h" #include "textcolor.h" diff --git a/audio_stats.c b/audio_stats.c index 9c67769c..b6549cab 100644 --- a/audio_stats.c +++ b/audio_stats.c @@ -50,6 +50,9 @@ *---------------------------------------------------------------*/ +#include "direwolf.h" + + #include #include #include @@ -57,8 +60,9 @@ #include #include #include +#include + -#include "direwolf.h" #include "audio_stats.h" #include "textcolor.h" #include "dtime_now.h" diff --git a/audio_win.c b/audio_win.c index f4313634..5cbb39b0 100644 --- a/audio_win.c +++ b/audio_win.c @@ -39,6 +39,10 @@ *---------------------------------------------------------------*/ +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h + // Also includes windows.h. + + #include #include #include @@ -48,7 +52,6 @@ #include #include -#include #include #ifndef WAVE_FORMAT_96M16 @@ -57,11 +60,9 @@ #endif #include -#define _WIN32_WINNT 0x0501 -#include +#include // _WIN32_WINNT must be set to 0x0501 before including this -#include "direwolf.h" #include "audio.h" #include "audio_stats.h" #include "textcolor.h" @@ -286,7 +287,7 @@ int audio_open (struct audio_s *pa) A->udp_sock = INVALID_SOCKET; - in_dev_no[a] = WAVE_MAPPER; /* = -1 */ + in_dev_no[a] = WAVE_MAPPER; /* = ((UINT)-1) in mmsystem.h */ out_dev_no[a] = WAVE_MAPPER; /* @@ -319,16 +320,16 @@ int audio_open (struct audio_s *pa) /* Otherwise, does it have search string? */ - if (in_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) { + if ((UINT)(in_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) { num_devices = waveInGetNumDevs(); - for (n=0 ; nadev[a].adevice_in) != NULL) { in_dev_no[a] = n; } } } - if (in_dev_no[a] == WAVE_MAPPER) { + if ((UINT)(in_dev_no[a]) == WAVE_MAPPER) { text_color_set(DW_COLOR_ERROR); dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adev[a].adevice_in); } @@ -344,16 +345,16 @@ int audio_open (struct audio_s *pa) out_dev_no[a] = atoi(pa->adev[a].adevice_out); } - if (out_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { + if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { num_devices = waveOutGetNumDevs(); - for (n=0 ; nadev[a].adevice_out) != NULL) { out_dev_no[a] = n; } } } - if (out_dev_no[a] == WAVE_MAPPER) { + if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) { text_color_set(DW_COLOR_ERROR); dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out); } @@ -798,7 +799,7 @@ int audio_get (int a) p = (WAVEHDR*)(A->in_headp); /* no need to be volatile at this point */ - if (p->dwUser == -1) { + if (p->dwUser == (DWORD)(-1)) { waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); p->dwUser = 0; /* Index for next byte. */ @@ -959,11 +960,11 @@ int audio_put (int a, int c) /* Should never be full at this point. */ assert (p->dwBufferLength >= 0); - assert (p->dwBufferLength < A->outbuf_size); + assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); p->lpData[p->dwBufferLength++] = c; - if (p->dwBufferLength == A->outbuf_size) { + if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { return (audio_flush(a)); } diff --git a/ax25_link.c b/ax25_link.c new file mode 100644 index 00000000..324334fa --- /dev/null +++ b/ax25_link.c @@ -0,0 +1,6523 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * Name: ax25_link + * + * Purpose: Data Link State Machine. + * Establish connections and transfer data in the proper + * order with retries. + * + * Description: + * + * Typical sequence for establishing a connection + * initiated by a client application. Try version 2.2, + * get refused, and fall back to trying version 2.0. + * + * + * State Client App State Mach. Peer + * ----- ---------- ----------- ---- + * + * 0 disc + * Conn. Req ---> + * SABME ---> + * 5 await 2.2 + * <--- FRMR or DM *note + * SABM ---> + * 1 await 2.0 + * <--- UA + * <--- CONN Ind. + * 3 conn + * + * + * Typical sequence when other end initiates connection. + * + * + * State Client App State Mach. Peer + * ----- ---------- ----------- ---- + * + * 0 disc + * <--- SABME or SABM + * UA ---> + * <--- CONN Ind. + * 3 conn + * + * + * *note: + * + * By reading the v2.2 spec, I expected a 2.0 implementation to send + * FRMR in response to SABME. In testing, I found that the KPC-3+ sent DM. + * + * After consulting the 2.0 spec, it says, that when in disconnected + * mode, it will respond to any command other than SABM or UI frame with a DM + * response with P/F set to 1. I can see where they might get that idea. + * + * I think the rule about sending FRMR for any unrecognized command should take precedence. + * + * + * References: + * * AX.25 Amateur Packet-Radio Link-Layer Protocol Version 2.0, October 1984 + * + * https://www.tapr.org/pub_ax25.html + * + * * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 + * + * https://www.tapr.org/pdf/AX25.2.2.pdf + * + * * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 + * + * http://www.ax25.net/AX25.2.2-Jul%2098-2.pdf + * + * I accidentally stumbled across this one when searching for some sort of errata + * list for the original protocol specification. + * + * "This is a new version of the 1998 standard. It has had all figures + * redone using Microsoft Visio. Errors in the SDL have been corrected." + * + * The SDL diagrams are dated 2006. I wish I knew about this version earlier + * before doing most of the implementation. :-( + * + * + * The functions here are based on the SDL diagrams but turned inside out. + * It seems more intuitive to have a function for each type of input and then decide + * what to do depending on the state. This also reduces duplicate code because we + * often see the same flow chart segments, for the same input, appearing in multiple states. + * + * Erratum: The protocol spec has many places that appear to be errors or are ambiguous so I wasn't + * sure what to do. These should be annotated with Errata comments so we can easily go + * back and revisit them. + * + * X.25: The AX.25 protocol is based on, but does not necessarily adhere to, the X.25 protocol. + * Consulting this might provide some insights where the AX.25 spec is not clear. + * + * http://www.itu.int/rec/T-REC-X.25-199610-I/en/ + * + * Current Status: This is still under development. + * + * Features partially tested: + * + * Connect to/from a KPC-3+ and send I frames in both directions. + * v2.2 connect between two instances of direwolf. (Can't find another v2.2 for testing.) + * Modulo 8 & 128 sequence numbers. + * Recovery from simulated transmission errors using either REJ or SREJ. + * XID frame for parameter negotiation. + * Segments to allow data larger than max info part size. + * + * Implemented but not tested properly: + * + * Connecting thru digipeater(s). + * Acting as a digipeater. + * T3 timer. + * Compatibility with additional types of TNC. + * + *------------------------------------------------------------------*/ + +#include "direwolf.h" + + +#include +#include +#include +#include +#include +#include + + +#include "ax25_pad.h" +#include "ax25_pad2.h" +#include "xid.h" +#include "textcolor.h" +#include "dlq.h" +#include "tq.h" +#include "ax25_link.h" +#include "dtime_now.h" +#include "server.h" +#include "ptt.h" + + +#define MIN(a,b) ((a)<(b)?(a):(b)) +#define MAX(a,b) ((a)>(b)?(a):(b)) + +// Debug switches for different types of information. + +static int s_debug_protocol_errors = 1; // Less serious Protocol errors. + // Useful for debugging but unnecessarily alarming other times. + +static int s_debug_client_app = 0; // Interaction with client application. + // dl_connect_request, dl_data_request, dl_data_indication, etc. + +static int s_debug_radio = 0; // Received frames and channel busy status. + // lm_data_indication, lm_channel_busy + +static int s_debug_variables = 0; // Variables, state changes. + +static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending. + +static int s_debug_timers = 0; // Timer details. + +static int s_debug_link_handle = 0; // Create data link state machine or pick existing one, + // based on my address, peer address, client app index, and radio channel. + +static int s_debug_stats = 0; // Statistics when connection is closed. + +static int s_debug_misc = 0; // Anything left over that might be interesting. + + +/* + * AX.25 data link state machine. + * + * One instance for each link identified by + * [ client, channel, owncall, peercall ] + */ + +enum dlsm_state_e { + state_0_disconnected = 0, + state_1_awaiting_connection = 1, + state_2_awaiting_release = 2, + state_3_connected = 3, + state_4_timer_recovery = 4, + state_5_awaiting_v22_connection = 5 }; + + +typedef struct ax25_dlsm_s { + + int magic1; // Look out for bad pointer or corruption. +#define MAGIC1 0x11592201 + + struct ax25_dlsm_s *next; // Next in linked list. + + int stream_id; // Unique number for each stream. + // Internally we use a pointer but this is more user-friendly. + + int chan; // Radio channel being used. + + int client; // We have have multiple client applications, + // each with their own links. We need to know + // which client should receive the data or + // notifications about state changes. + + + char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN]; + // Up to 10 addresses, same order as in frame. + + int num_addr; // Number of addresses. Should be in range 2 .. 10. + +#define OWNCALL AX25_SOURCE + // addrs[OWNCALL] is owncall for this end of link. + // Note that we are acting on behalf of + // a client application so the APRS mycall + // might not be relevent. + +#define PEERCALL AX25_DESTINATION + // addrs[PEERCALL] is call for other end. + + + double start_time; // Clock time when this was allocated. Used only for + // debug output for timestamps relative to start. + + enum dlsm_state_e state; // Current state. + + int modulo; // 8 or 128. + // Determines whether we have one or two control + // octets. 128 allows a much larger window size. + + int srej_enabled; // Is other end capable of processing SREJ? (Am I allowed to send it?) + // Starts out as false for v2.0 or true for v2.2. + // Can be changed when exchanging capabilities. + // Can be used only with modulo 128. + + int n1_paclen; // Maximum length of information field, in bytes. + // Starts out as 256 but can be negotiated higher. + // (Protocol Spec has this in bits. It is in bytes here.) + // "PACLEN" in configuration file. + + int n2_retry; // Maximum number of retries permitted. + // Typically 10. + // "RETRY" parameter in configuration file. + + + int k_maxframe; // Window size. Defaults to 4 (mod 8) or 32 (mod 128). + // Maximum number of unacknowledged information + // frames that can be outstanding. + // "MAXFRAME" parameter in configuration file. + + int rc; // Retry count. Give up after n2. + + int vs; // 4.2.4.1. Send State Variable V(S) + // The send state variable exists within the TNC and is never sent. + // It contains the next sequential number to be assigned to the next + // transmitted I frame. + // This variable is updated with the transmission of each I frame. + + int va; // 4.2.4.5. Acknowledge State Variable V(A) + // The acknowledge state variable exists within the TNC and is never sent. + // It contains the sequence number of the last frame acknowledged by + // its peer [V(A)-1 equals the N(S) of the last acknowledged I frame]. + + int vr; // 4.2.4.3. Receive State Variable V(R) + // The receive state variable exists within the TNC. + // It contains the sequence number of the next expected received I frame + // This variable is updated upon the reception of an error-free I frame + // whose send sequence number equals the present received state variable value. + + int layer_3_initiated; // SABM(E) was sent by request of Layer 3; i.e. DL-CONNECT request primitive. + // I think this means that it is set only if we initiated the connection. + // It would not be set if we are in the middle of accepting a connection from the other station. + +// Next 5 are called exception conditions. + + int peer_receiver_busy; // Remote station is busy and can't receive I frames. + + int reject_exception; // A REJ frame has been sent to the remote station. (boolean) + + int own_receiver_busy; // Layer 3 is busy and can't receive I frames. + // We have no API to convey this information so it should always be 0. + + int acknowledge_pending; // I frames have been successfully received but not yet + // acknowledged to the remote station. + // Set when receiving the next expected I frame and P=0. + // This gets cleared by sending any I, RR, RNR, REJ. + // Cleared when sending SREJ with F=1. + +// Timing. + + float srt; // Smoothed roundtrip time in seconds. + // This is used to dynamically adjust t1v. + // Sometimes the flow chart has SAT instead of SRT. + // I think that is a typographical error. + + float t1v; // How long to wait for an acknowlegement before resending. + // Value used when starting timer T1, in seconds. + // "FRACK" parameter in some implementations. + // Typically it might be 3 seconds after frame has been + // sent. Add more for each digipeater in path. + // Here it is dynamically adjusted. + +// Set initial value for T1V. +// Multiply FRACK by 2*m+1, where m is number of digipeaters. + +#define INIT_T1V_SRT \ + S->t1v = g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1); \ + S->srt = S->t1v / 2.0; + + + int radio_channel_busy; // Either due to DCD or PTT. + + +// Timer T1. + +// Timer values all use the usual unix time() value but double precision +// so we can have fractions of seconds. + +// T1 is used for retries along with the retry counter, "rc." +// When timer T1 is started, the value is obtained from t1v plus the current time. + + +// Appropriate functions should be used rather than accessing the values directly. + + + +// This gets a little tricky because we need to pause the timers when the radio +// channel is busy. Suppose we sent an I frame and set T1 to 4 seconds so we could +// take corrective action if there is no response in a reasonable amount of time. +// What if some other station has the channel tied up for 10 seconds? We don't want +// T1 to timeout and start a retry sequence. The solution is to pause the timers while +// the channel is busy. We don't want to get a timer expiry event when t1_exp is in +// the past if it is currently paused. When it is un-paused, the expiration time is adjusted +// for the amount of time it was paused. + + + double t1_exp; // This is the time when T1 will expire or 0 if not running. + + double t1_paused_at; // Time when it was paused or 0 if not paused. + + float t1_remaining_when_last_stopped; // Number of seconds that were left on T1 when it was stopped. + // This is used to fine tune t1v. + // Set to negative initially to mean invalid, don't use in calculation. + + int t1_had_expired; // Set when T1 expires. + // Cleared for start & stop. + + +// Timer T3. + +// T3 is used to terminate connection after extended inactivity. + + +// Similar to T1 except there is not mechanism to capture the remaining time when it is stopped +// and it is not paused when the channel is busy. + + + double t3_exp; // When it expires or 0 if not running. + +#define T3_DEFAULT 300.0 // Copied 5 minutes from Ax.25 for Linux. + // http://www.linux-ax25.org/wiki/Run_time_configurable_parameters + // D710A also defaults to 30*10 = 300 seconds. + // Should it be user-configurable? + // KPC-3+ and TM-D710A have "CHECK" command for this purpose. + +// Statistics for testing purposes. + +// Count how many frames of each type we received. +// This is easy to do because they all come in thru lm_data_indication. +// Counting outgoing could probably be done in lm_data_request so +// it would not have to be scattered all over the place. TBD + + int count_recv_frame_type[frame_not_AX25+1]; + + int peak_rc_value; // Peak value of retry count (rc). + + +// For sending data. + + cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet. + // Linked list. + + cdata_t *txdata_by_ns[128]; // Data which has already been transmitted. + // Indexed by N(S) in case it gets lost and needs to be sent again. + // Cleared out when we get ACK for it. + + int magic3; // Look out for out of bounds for above. +#define MAGIC3 0x03331301 + + cdata_t *rxdata_by_ns[128]; // "Receive buffer" + // Data which has been received out of sequence. + // Indexed by N(S). + + int magic2; // Look out for out of bounds for above. +#define MAGIC2 0x02221201 + + + +// "Management Data Link" (MDL) state machine for XID exchange. + + + enum mdl_state_e { mdl_state_0_ready=0, mdl_state_1_negotiating=1 } mdl_state; + + int mdl_rc; // Retry count, waiting to get XID response. + // The spec has provision for a separate maximum, NM201, but we + // just use the regular N2 same as other retries. + + double tm201_exp; // Timer. Similar to T1. + // The spec mentions a separate timeout value but + // we will just use the same as T1. + + double tm201_paused_at; // Time when it was paused or 0 if not paused. + +// Segment reassembler. + + cdata_t *ra_buff; // Reassembler buffer. NULL when in ready state. + + int ra_following; // Most recent number following to predict next expected. + + +} ax25_dlsm_t; + + +/* + * List of current state machines for each link. + * There is potential many client apps, each with multiple links + * connected all at the same time. + * + * Everything coming thru here should be from a single thread. + * The Data Link Queue should serialize all processing. + * Therefore, we don't have to worry about critical regions. + */ + +static ax25_dlsm_t *list_head = NULL; + + +/* + * Registered callsigns for incoming connections. + */ + +#define RC_MAGIC 0x08291951 + +typedef struct reg_callsign_s { + char callsign[AX25_MAX_ADDR_LEN]; + int chan; + int client; + struct reg_callsign_s *next; + int magic; +} reg_callsign_t; + +static reg_callsign_t *reg_callsign_list = NULL; + + +// Use these, rather than setting variables directly, to make debug out easier. + +#define SET_VS(n) { S->vs = (n); \ + if (s_debug_variables) { \ + text_color_set(DW_COLOR_DEBUG); \ + dw_printf ("V(S) = %d at %s %d\n", S->vs, __func__, __LINE__); \ + } \ + } + +// If other guy acks reception of an I frame, we should never get an REJ or SREJ +// asking for it again. When we update V(A), we should be able to remove the saved +// transmitted data, and everything preceding it, from S->txdata_by_ns[]. + +#define SET_VA(n) { S->va = (n); \ + if (s_debug_variables) { \ + text_color_set(DW_COLOR_DEBUG); \ + dw_printf ("V(A) = %d at %s %d\n", S->va, __func__, __LINE__); \ + } \ + int x = AX25MODULO(n-1, S->modulo, __FILE__, __func__, __LINE__); \ + while (S->txdata_by_ns[x] != NULL) { \ + cdata_delete (S->txdata_by_ns[x]); \ + S->txdata_by_ns[x] = NULL; \ + x = AX25MODULO(x-1, S->modulo, __FILE__, __func__, __LINE__); \ + } \ + } + +#define SET_VR(n) { S->vr = (n); \ + if (s_debug_variables) { \ + text_color_set(DW_COLOR_DEBUG); \ + dw_printf ("V(R) = %d at %s %d\n", S->vr, __func__, __LINE__); \ + } \ + } + + +//TODO: Make this a macro so we can simplify calls yet keep debug output if something goes wrong. + +static int AX25MODULO(int n, int m, const char *file, const char *func, int line) +{ + if (m != 8 && m != 128) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR: %d modulo %d, %s, %s, %d\n", n, m, file, func, line); + m = 8; + } + // Use masking, rather than % operator, so negative numbers are handled properly. + return (n & (m-1)); +} + + +// Timer macros to provide debug output with location from where they are called. + +#define START_T1 start_t1(S, __func__, __LINE__) +#define IS_T1_RUNNING is_t1_running(S, __func__, __LINE__) +#define STOP_T1 stop_t1(S, __func__, __LINE__) +#define PAUSE_T1 pause_t1(S, __func__, __LINE__) +#define RESUME_T1 resume_t1(S, __func__, __LINE__) + +#define START_T3 start_t3(S, __func__, __LINE__) +#define STOP_T3 stop_t3(S, __func__, __LINE__) + +#define START_TM201 start_tm201(S, __func__, __LINE__) +#define STOP_TM201 stop_tm201(S, __func__, __LINE__) +#define PAUSE_TM201 pause_tm201(S, __func__, __LINE__) +#define RESUME_TM201 resume_tm201(S, __func__, __LINE__) + + +static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len); + +static void lm_seize_confirm (ax25_dlsm_t *S); + +static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len); +static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len); +static int is_ns_in_window (ax25_dlsm_t *S, int ns); +static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1); +static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr); +static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr); +static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr); + +static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p); +static void disc_frame (ax25_dlsm_t *S, int f); +static void dm_frame (ax25_dlsm_t *S, int f); +static void ua_frame (ax25_dlsm_t *S, int f); +static void frmr_frame (ax25_dlsm_t *S); +static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf); +static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); +static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); + +static void t1_expiry (ax25_dlsm_t *S); +static void t3_expiry (ax25_dlsm_t *S); +static void tm201_expiry (ax25_dlsm_t *S); + +static void nr_error_recovery (ax25_dlsm_t *S); +static void clear_exception_conditions (ax25_dlsm_t *S); +static void transmit_enquiry (ax25_dlsm_t *S); +static void select_t1_value (ax25_dlsm_t *S); +static void establish_data_link (ax25_dlsm_t *S); +static void set_version_2_0 (ax25_dlsm_t *S); +static void set_version_2_2 (ax25_dlsm_t *S); +static int is_good_nr (ax25_dlsm_t *S, int nr); +static void i_frame_pop_off_queue (ax25_dlsm_t *S); +static void discard_i_queue (ax25_dlsm_t *S); +static void invoke_retransmission (ax25_dlsm_t *S, int nr_input); +static void check_i_frame_ackd (ax25_dlsm_t *S, int nr); +static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf); +static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f); + +static void enter_new_state(ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line); + +static void mdl_negotiate_request (ax25_dlsm_t *S); +static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); +static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param); +static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); + + +// Use macros above rather than calling these directly. + +static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); +static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); +static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line); +static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); +static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); + +static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); +static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); + +static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); +static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); +static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); +static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); + + + +/* + * Configuration settings from file or command line. + */ + +static struct misc_config_s *g_misc_config_p; + + +/*------------------------------------------------------------------- + * + * Name: ax25_link_init + * + * Purpose: Initialize the ax25_link module. + * + * Inputs: pconfig - misc. configuration from config file or command line. + * Beacon stuff ended up here. + * + * Outputs: Remember required information for future use. That's all. + * + *--------------------------------------------------------------------*/ + +void ax25_link_init (struct misc_config_s *pconfig) +{ + +/* + * Save parameters for later use. + */ + g_misc_config_p = pconfig; + +} /* end ax25_link_init */ + + + + +/*------------------------------------------------------------------------------ + * + * Name: get_link_handle + * + * Purpose: Find existing (or possibly create) state machine for a given link. + * It should be possible to have a large number of links active at the + * same time. They are uniquely identified by + * (owncall, peercall, client id, radio channel) + * Note that we could have multiple client applications, all sharing one + * TNC, on the same or different radio channels, completely unware of each other. + * + * Inputs: addrs - Owncall, peercall, and optional digipeaters. + * For ease of passing this around, it is an array in the + * same order as in the frame. + * + * num_addr - Number of addresses, 2 thru 10. + * + * chan - Radio channel number. + * + * client - Client app number. + * We allow multiple concurrent applications with the + * AGW network protocol. These are identified as 0, 1, ... + * We don't know this for an incoming frame from the radio + * so it is -1 at this point. At a later time will will + * associate the stream with the right client. + * + * create - True if OK to create a new one. + * Otherwise, return only one already existing. + * + * This should always be true for outgoing frames. + * For incoming frames this would be true only for SABM(e) + * with all digipeater fields marked as used. + * + * Here, we will also check to see if it is in our + * registered callsign list. + * + * Returns: Handle for data link state machine. + * NULL if not found and 'create' is false. + * + * Description: Try to find an existing entry matching owncall, peercall, channel, + * and client. If not found create a new one. + * + *------------------------------------------------------------------------------*/ + +static int next_stream_id = 0; + +static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int create) +{ + + ax25_dlsm_t *p; + + + if (s_debug_link_handle) { + text_color_set(DW_COLOR_DECODED); + dw_printf ("get_link_handle (%s>%s, chan=%d, client=%d, create=%d)\n", + addrs[AX25_SOURCE], addrs[AX25_DESTINATION], chan, client, create); + } + + +// Look for existing. + + if (client == -1) { // from the radio. + // address order is reversed for compare. + for (p = list_head; p != NULL; p = p->next) { + + if (p->chan == chan && + strcmp(addrs[AX25_DESTINATION], p->addrs[OWNCALL]) == 0 && + strcmp(addrs[AX25_SOURCE], p->addrs[PEERCALL]) == 0) { + + if (s_debug_link_handle) { + text_color_set(DW_COLOR_DECODED); + dw_printf ("get_link_handle returns existing stream id %d for incoming.\n", p->stream_id); + } + return (p); + } + } + } + else { // from client app + for (p = list_head; p != NULL; p = p->next) { + + if (p->chan == chan && + p->client == client && + strcmp(addrs[AX25_SOURCE], p->addrs[OWNCALL]) == 0 && + strcmp(addrs[AX25_DESTINATION], p->addrs[PEERCALL]) == 0) { + + if (s_debug_link_handle) { + text_color_set(DW_COLOR_DECODED); + dw_printf ("get_link_handle returns existing stream id %d for outgoing.\n", p->stream_id); + } + return (p); + } + } + } + + +// Could not find existing. Should we create a new one? + + if ( ! create) { + if (s_debug_link_handle) { + text_color_set(DW_COLOR_DECODED); + dw_printf ("get_link_handle: Search failed. Do not create new.\n"); + } + return (NULL); + } + + +// If it came from the radio, search for destination our registered callsign list. + + int incoming_for_client = -1; // which client app registered the callsign? + + + if (client == -1) { // from the radio. + + reg_callsign_t *r, *found; + + found = NULL; + for (r = reg_callsign_list; r != NULL && found == NULL; r = r->next) { + + if (strcmp(addrs[AX25_DESTINATION], r->callsign) == 0 && chan == r->chan) { + found = r; + incoming_for_client = r->client; + } + } + + if (found == NULL) { + if (s_debug_link_handle) { + text_color_set(DW_COLOR_DECODED); + dw_printf ("get_link_handle: not for me. Ignore it.\n"); + } + return (NULL); + } + } + +// Create new data link state machine. + + p = calloc (sizeof(ax25_dlsm_t), 1); + p->magic1 = MAGIC1; + p->start_time = dtime_now(); + p->stream_id = next_stream_id++; + p->modulo = 8; + + p->chan = chan; + p->num_addr = num_addr; + +// If it came in over the radio, we need to swap source/destination and reverse any digi path. + + if (incoming_for_client >= 0) { + strlcpy (p->addrs[AX25_SOURCE], addrs[AX25_DESTINATION], sizeof(p->addrs[AX25_SOURCE])); + strlcpy (p->addrs[AX25_DESTINATION], addrs[AX25_SOURCE], sizeof(p->addrs[AX25_DESTINATION])); + + int j = AX25_REPEATER_1; + int k = num_addr - 1; + while (k >= AX25_REPEATER_1) { + strlcpy (p->addrs[j], addrs[k], sizeof(p->addrs[j])); + j++; + k--; + } + + p->client = incoming_for_client; + } + else { + memcpy (p->addrs, addrs, sizeof(p->addrs)); + p->client = client; + } + + p->state = state_0_disconnected; + p->t1_remaining_when_last_stopped = -999; // Invalid, don't use. + + p->magic2 = MAGIC2; + p->magic3 = MAGIC3; + + // No need for critical region because this should all be in one thread. + p->next = list_head; + list_head = p; + + if (s_debug_link_handle) { + text_color_set(DW_COLOR_DECODED); + dw_printf ("get_link_handle returns NEW stream id %d\n", p->stream_id); + } + + return (p); +} + + + + + + +//################################################################################### +//################################################################################### +// +// Data Link state machine for sending data in connected mode. +// +// Incoming: +// +// Requests from the client application. Set s_debug_client_app for debugging. +// +// dl_connect_request +// dl_disconnect_request +// dl_data_request - send connected data +// dl_unit_data_request - not implemented. APRS & KISS bypass this +// dl_flow_off - not implemented. Not in AGW API. +// dl_flow_on - not implemented. Not in AGW API. +// dl_register_callsign - Register callsigns(s) for incoming connection requests. +// dl_unregister_callsign - Unregister callsigns(s) ... +// dl_client_cleanup - Clean up after client which has disappeared. +// +// Stuff from the radio channel. Set s_debug_radio for debugging. +// +// lm_data_indication - Received frame. +// lm_channel_busy - Change in PTT or DCD. +// lm_seize_confirm - We have started to transmit. +// +// Timer expiration. Set s_debug_timers for debugging. +// +// dl_timer_expiry +// +// Outgoing: +// +// To the client application: +// +// dl_data_indication - received connected data. +// +// To the transmitter: +// +// lm_data_request - Queue up a frame for transmission. +// +// lm_seize_request - Start transmitter when possible. +// lm_seize_confirm will be called when it has. +// +// +// It is important that all requests come thru the data link queue so +// everything is serialized. +// We don't have to worry about being reentrant or critical regions. +// Nothing here should consume a significant amount of time. +// i.e. There should be no sleep delay or anything that would block waiting on someone else. +// +//################################################################################### +//################################################################################### + + + +/*------------------------------------------------------------------------------ + * + * Name: dl_connect_request + * + * Purpose: Client app wants to connect to another station. + * + * Inputs: E - Event from the queue. + * The caller will free it. + * + * Description: + * + *------------------------------------------------------------------------------*/ + +void dl_connect_request (dlq_item_t *E) +{ + ax25_dlsm_t *S; + int ok_to_create = 1; + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dl_connect_request ()\n"); + } + + text_color_set(DW_COLOR_INFO); + dw_printf ("Attempting connect to %s ...\n", E->addrs[PEERCALL]); + + S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); + + switch (S->state) { + + case state_0_disconnected: + + INIT_T1V_SRT; + + if (g_misc_config_p->maxv22 == 0) { // Don't attempt v2.2. + + set_version_2_0 (S); + + establish_data_link (S); + S->layer_3_initiated = 1; + enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); + } + else { // Try v2.2 first, then fall back if appropriate. + + set_version_2_2 (S); + + establish_data_link (S); + S->layer_3_initiated = 1; + enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__); + } + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + + discard_i_queue(S); + S->layer_3_initiated = 1; + // Keep current state. + break; + + case state_2_awaiting_release: + + // Keep current state. + break; + + case state_3_connected: + case state_4_timer_recovery: + + discard_i_queue(S); + establish_data_link(S); + S->layer_3_initiated = 1; + // My enhancement. Original always sent SABM and went to state 1. + // If we were using v2.2, why not reestablish with that? + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + break; + } + +} /* end dl_connect_request */ + + +/*------------------------------------------------------------------------------ + * + * Name: dl_disconnect_request + * + * Purpose: Client app wants to terminate connection with another station. + * + * Inputs: E - Event from the queue. + * The caller will free it. + * + * Outputs: + * + * Description: + * + *------------------------------------------------------------------------------*/ + +void dl_disconnect_request (dlq_item_t *E) +{ + ax25_dlsm_t *S; + int ok_to_create = 1; + + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dl_disconnect_request ()\n"); + } + + text_color_set(DW_COLOR_INFO); + dw_printf ("Disconnect from %s ...\n", E->addrs[PEERCALL]); + + S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); + + switch (S->state) { + + case state_0_disconnected: + + // DL-DISCONNECT *confirm* + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + +// TODO: FIXME requeue. Not sure what to do here. +// If we put it back in the queue we will get it back again probably still in same state. +// Need a way to defer it until the next state change. + + break; + + case state_2_awaiting_release: + { + // Erratum. Flow chart simply says "DM (expedited)." + // This is the only place we have expedited. Is this correct? + + cmdres_t cr = cr_res; // DM can only be response. + int p = 0; + int nopid = 0; // PID applies only to I and UI frames. + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, p, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // HI means expedited. + + STOP_T1; + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + break; + + case state_3_connected: + case state_4_timer_recovery: + + discard_i_queue (S); + S->rc = 0; // I think this should be 1 but I'm not that worried about it. + + cmdres_t cmd = cr_cmd; + int p = 1; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + STOP_T3; + START_T1; + enter_new_state (S, state_2_awaiting_release, __func__, __LINE__); + + break; + } + +} /* end dl_disconnect_request */ + + +/*------------------------------------------------------------------------------ + * + * Name: dl_data_request + * + * Purpose: Client app wants to send data to another station. + * + * Inputs: E - Event from the queue. + * The caller will free it. + * + * Description: Append the transmit data block to the I frame queue for later processing. + * + * We also perform the segmentation handling here. + * + * C6.1 Segmenter State Machine + * Only the following DL primitives will be candidates for modification by the segmented + * state machine: + + * * DL-DATA Request. The user employs this primitive to provide information to be + * transmitted using connection-oriented procedures; i.e., using I frames. The + * segmenter state machine examines the quantity of data to be transmitted. If the + * quantity of data to be transmitted is less than or equal to the data link parameter + * N1, the segmenter state machine passes the primitive through transparently. If the + * quantity of data to be transmitted exceeds the data link parameter N1, the + * segmenter chops up the data into segments of length N1-2 octets. Each segment is + * prepended with a two octet header. (See Figures 3.1 and 3.2.) The segments are + * then turned over to the Data-link State Machine for transmission, using multiple DL + * Data Request primitives. All segments are turned over immediately; therefore the + * Data-link State Machine will transmit them consecutively on the data link. + * + * Erratum: Not sure how to interpret that. See example below for how it was implemented. + * + *------------------------------------------------------------------------------*/ + +static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata); + + +void dl_data_request (dlq_item_t *E) +{ + ax25_dlsm_t *S; + int ok_to_create = 1; + int nseg_to_follow; + int orig_offset, remaining_len; + + + S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dl_data_request (\""); + ax25_safe_print (E->txdata->data, E->txdata->len, 1); + dw_printf ("\") state=%d\n", S->state); + } + + if (E->txdata->len <= S->n1_paclen) { + data_request_good_size (S, E->txdata); + E->txdata = NULL; // Now part of transmit I frame queue. + return; + } + +// More interesting case. +// It is too large to fit in one frame so we segment it. + +// As an example, suppose we had 6 bytes of data "ABCDEF". + +// If N1 >= 6, it would be sent normally. + +// (addresses) +// (control bytes) +// PID, typically 0xF0 +// 'A' - first byte of information field +// 'B' +// 'C' +// 'D' +// 'E' +// 'F' + +// Now consider the case where it would not fit. +// We would change the PID to 0x08 meaning a segment. +// The information part is the segment identifier of this format: +// +// x xxxxxxx +// | ---+--- +// | | +// | +- Number of additional segments to follow. +// | +// +- '1' means it is the first segment. + +// If N1 = 4, it would be split up like this: + +// (addresses) +// (control bytes) +// PID = 0x08 means segment +// 0x82 - Start of info field. +// MSB set indicates FIRST segment. +// 2, in lower 7 bits, means 2 more segments to follow. +// 0xF0 - original PID, typical value. +// 'A' - For the FIRST segment, we have PID and N1-2 data bytes. +// 'B' + +// (addresses) +// (control bytes) +// PID = 0x08 means segment +// 0x01 - Means 1 more segment follows. +// 'C' - For subsequent (not first) segments, we have up to N1-1 data bytes. +// 'D' +// 'E' + +// (addresses) +// (control bytes) +// PID = 0x08 +// 0x00 - 0 means no more to follow. i.e. This is the last. +// 'E' + + +// Number of segments is ceiling( (datalen + 1 ) / (N1 - 1)) + +// we add one to datalen for the original PID. +// We subtract one from N1 for the segment identifier header. + +#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b)) + +// Compute number of segments. +// We will decrement this before putting it in the frame so the first +// will have one less than this number. + + nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1); + + if (nseg_to_follow < 2 || nseg_to_follow > 128) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, number of segments = %d\n", + __LINE__, E->txdata->len, S->n1_paclen, nseg_to_follow); + cdata_delete (E->txdata); + E->txdata = NULL; + return; + } + + orig_offset = 0; + remaining_len = E->txdata->len; + +// First segment. + + int seglen; + struct { + char header; // 0x80 + number of segments to follow. + char original_pid; + char segdata[AX25_N1_PACLEN_MAX]; + } first_segment; + cdata_t *new_txdata; + + nseg_to_follow--; + + first_segment.header = 0x80 | nseg_to_follow; + first_segment.original_pid = E->txdata->pid; + seglen = MIN(S->n1_paclen - 2, remaining_len); + + if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > (int)(sizeof(first_segment.segdata))) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", + __LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); + cdata_delete (E->txdata); + E->txdata = NULL; + return; + } + + memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen); + + new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2); + + data_request_good_size (S, new_txdata); + + orig_offset += seglen; + remaining_len -= seglen; + + +// Subsequent segments. + + do { + struct { + char header; // Number of segments to follow. + char segdata[AX25_N1_PACLEN_MAX]; + } subsequent_segment; + + nseg_to_follow--; + + subsequent_segment.header = nseg_to_follow; + seglen = MIN(S->n1_paclen - 1, remaining_len); + + if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > (int)(sizeof(subsequent_segment.segdata))) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", + __LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); + cdata_delete (E->txdata); + E->txdata = NULL; + return; + } + + memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen); + + new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1); + + data_request_good_size (S, new_txdata); + + orig_offset += seglen; + remaining_len -= seglen; + + } while (nseg_to_follow > 0); + + if (remaining_len != 0 || orig_offset != E->txdata->len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n", + __LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len); + } + + cdata_delete (E->txdata); + E->txdata = NULL; + +} /* end dl_data_request */ + + +static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata) +{ + switch (S->state) { + + case state_0_disconnected: + case state_2_awaiting_release: +/* + * Discard it. + */ + cdata_delete (txdata); + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: +/* + * Erratum? + * The flow chart shows "push on I frame queue" if layer 3 initiated + * is NOT set. This seems backwards but I don't understand enough yet + * to make a compelling argument that it is wrong. + * Implemented as in flow chart. + * TODO: Get better understanding of what'layer_3_initiated' means. + */ + if (S->layer_3_initiated) { + cdata_delete (txdata); + break; + } + // otherwise fall thru. + + case state_3_connected: + case state_4_timer_recovery: +/* + * "push on I frame queue" + * Append to the end would have been a better description because push implies a stack. + */ + + if (S->i_frame_queue == NULL) { + txdata->next = NULL; + S->i_frame_queue = txdata; + } + else { + cdata_t *plast = S->i_frame_queue; + while (plast->next != NULL) { + plast = plast->next; + } + txdata->next = NULL; + plast->next = txdata; + } + break; + } + + i_frame_pop_off_queue (S); + +} /* end data_request_good_size */ + + +/*------------------------------------------------------------------------------ + * + * Name: dl_register_callsign + * dl_unregister_callsign + * + * Purpose: Register / Unregister callsigns that we will accept connections for. + * + * Inputs: E - Event from the queue. + * The caller will free it. + * + * Outputs: New item is pushed on the head of the reg_callsign_list. + * We don't bother checking for duplicates so the most recent wins. + * + * 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 dl_register_callsign (dlq_item_t *E) +{ + reg_callsign_t *r; + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dl_register_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); + } + + r = calloc(sizeof(reg_callsign_t),1); + strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign)); + r->chan = E->chan; + r->client = E->client; + r->next = reg_callsign_list; + r->magic = RC_MAGIC; + + reg_callsign_list = r; + +} /* end dl_register_callsign */ + + +void dl_unregister_callsign (dlq_item_t *E) +{ + reg_callsign_t *r, *prev; + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dl_unregister_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); + } + + prev = NULL; + r = reg_callsign_list; + while (r != NULL) { + + assert (r->magic == RC_MAGIC); + + if (strcmp(r->callsign,E->addrs[0]) == 0 && r->chan == E->chan && r->client == E->client) { + + if (r == reg_callsign_list) { + + reg_callsign_list = r->next; + memset (r, 0, sizeof(reg_callsign_t)); + free (r); + r = reg_callsign_list; + } + else { + + prev->next = r->next; + memset (r, 0, sizeof(reg_callsign_t)); + free (r); + r = prev->next; + } + } + else { + prev = r; + r = r->next; + } + } + +} /* end dl_unregister_callsign */ + + + +/*------------------------------------------------------------------------------ + * + * Name: dl_client_cleanup + * + * Purpose: Client app has gone away. Clean up any data associated with it. + * + * Inputs: E - Event from the queue. + * The caller will free it. + * + + * Description: By client application we mean something that attached with the + * AGW network protocol. + * + * Clean out anything related to the specfied client application. + * This would include state machines and registered callsigns. + * + *------------------------------------------------------------------------------*/ + +void dl_client_cleanup (dlq_item_t *E) +{ + ax25_dlsm_t *S; + ax25_dlsm_t *dlprev; + reg_callsign_t *r, *rcprev; + + + if (s_debug_client_app) { + text_color_set(DW_COLOR_INFO); + dw_printf ("dl_client_cleanup (%d)\n", E->client); + } + + + dlprev = NULL; + S = list_head; + while (S != NULL) { + + // Look for corruption or double freeing. + + assert (S->magic1 == MAGIC1); + assert (S->magic2 == MAGIC2); + assert (S->magic3 == MAGIC3); + + if (S->client == E->client ) { + + int n; + + if (s_debug_stats) { + text_color_set(DW_COLOR_INFO); + dw_printf ("%d I frames received\n", S->count_recv_frame_type[frame_type_I]); + + dw_printf ("%d RR frames received\n", S->count_recv_frame_type[frame_type_S_RR]); + dw_printf ("%d RNR frames received\n", S->count_recv_frame_type[frame_type_S_RNR]); + dw_printf ("%d REJ frames received\n", S->count_recv_frame_type[frame_type_S_REJ]); + dw_printf ("%d SREJ frames received\n", S->count_recv_frame_type[frame_type_S_SREJ]); + + dw_printf ("%d SABME frames received\n", S->count_recv_frame_type[frame_type_U_SABME]); + dw_printf ("%d SABM frames received\n", S->count_recv_frame_type[frame_type_U_SABM]); + dw_printf ("%d DISC frames received\n", S->count_recv_frame_type[frame_type_U_DISC]); + dw_printf ("%d DM frames received\n", S->count_recv_frame_type[frame_type_U_DM]); + dw_printf ("%d UA frames received\n", S->count_recv_frame_type[frame_type_U_UA]); + dw_printf ("%d FRMR frames received\n", S->count_recv_frame_type[frame_type_U_FRMR]); + dw_printf ("%d UI frames received\n", S->count_recv_frame_type[frame_type_U_UI]); + dw_printf ("%d XID frames received\n", S->count_recv_frame_type[frame_type_U_XID]); + dw_printf ("%d TEST frames received\n", S->count_recv_frame_type[frame_type_U_TEST]); + + dw_printf ("%d peak retry count\n", S->peak_rc_value); + } + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dl_client_cleanup: remove %s>%s\n", S->addrs[AX25_SOURCE], S->addrs[AX25_DESTINATION]); + } + + discard_i_queue (S); + + for (n = 0; n < 128; n++) { + if (S->txdata_by_ns[n] != NULL) { + cdata_delete (S->txdata_by_ns[n]); + S->txdata_by_ns[n] = NULL; + } + } + + for (n = 0; n < 128; n++) { + if (S->rxdata_by_ns[n] != NULL) { + cdata_delete (S->rxdata_by_ns[n]); + S->rxdata_by_ns[n] = NULL; + } + } + + if (S->ra_buff != NULL) { + cdata_delete (S->ra_buff); + S->ra_buff = NULL; + } + + // Put into disconnected state. + // If "connected" indicator (e.g. LED) was on, this will turn it off. + + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + + // Take S out of list. + + S->magic1 = 0; + S->magic2 = 0; + S->magic3 = 0; + + if (S == list_head) { // first one on list. + + list_head = S->next; + free (S); + S = list_head; + } + else { // not the first one. + dlprev->next = S->next; + free (S); + S = dlprev->next; + } + } + else { + dlprev = S; + S = S->next; + } + } + +/* + * If there are no link state machines (streams) remaining, there should be no txdata items still allocated. + */ + if (list_head == NULL) { + cdata_check_leak(); + } + +/* + * Remove registered callsigns for this client. + */ + + rcprev = NULL; + r = reg_callsign_list; + while (r != NULL) { + + assert (r->magic == RC_MAGIC); + + if (r->client == E->client) { + + if (r == reg_callsign_list) { + + reg_callsign_list = r->next; + memset (r, 0, sizeof(reg_callsign_t)); + free (r); + r = reg_callsign_list; + } + else { + + rcprev->next = r->next; + memset (r, 0, sizeof(reg_callsign_t)); + free (r); + r = rcprev->next; + } + } + else { + rcprev = r; + r = r->next; + } + } + +} /* end dl_client_cleanup */ + + + +/*------------------------------------------------------------------------------ + * + * Name: dl_data_indication + * + * Purpose: send connected data to client application. + * + * Inputs: pid - Protocol ID. + * + * data - Pointer to array of bytes. + * + * len - Number of bytes in data. + * + * + * Description: TODO: We perform reassembly of segments here if necessary. + * + *------------------------------------------------------------------------------*/ + +static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len) +{ + + + +// Now it gets more interesting. We need to combine segments before passing it along. + +// See example in dl_data_request. + + if (S->ra_buff == NULL) { + +// Ready state. + + if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { + server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); + return; + } + else if (data[0] & 0x80) { + +// Ready state, First segment. + + S->ra_following = data[0] & 0x7f; + int total = (S->ra_following + 1) * (len - 1) - 1; // len should be other side's N1 + S->ra_buff = cdata_new(data[1], NULL, total); + S->ra_buff->size = total; // max that we are expecting. + S->ra_buff->len = len - 2; // how much accumulated so far. + memcpy (S->ra_buff->data, data + 2, len - 2); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not first segment in ready state.\n", S->stream_id); + } + } + else { + +// Reassembling data state + + if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { + + server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not segment in reassembling state.\n", S->stream_id); + cdata_delete(S->ra_buff); + S->ra_buff = NULL; + return; + } + else if (data[0] & 0x80) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: First segment in reassembling state.\n", S->stream_id); + cdata_delete(S->ra_buff); + S->ra_buff = NULL; + return; + } + else if ((data[0] & 0x7f) != S->ra_following - 1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments out of sequence.\n", S->stream_id); + cdata_delete(S->ra_buff); + S->ra_buff = NULL; + return; + } + else { + +// Reassembling data state, Not first segment. + + S->ra_following = data[0] & 0x7f; + if (S->ra_buff->len + len - 1 <= S->ra_buff->size) { + memcpy (S->ra_buff->data + S->ra_buff->len, data + 1, len - 1); + S->ra_buff->len += len - 1; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments exceed buffer space.\n", S->stream_id); + cdata_delete(S->ra_buff); + S->ra_buff = NULL; + return; + } + + if (S->ra_following == 0) { +// Last one. + server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], S->ra_buff->pid, S->ra_buff->data, S->ra_buff->len); + cdata_delete(S->ra_buff); + S->ra_buff = NULL; + } + } + } + + +} /* end dl_data_indication */ + + + +/*------------------------------------------------------------------------------ + * + * Name: lm_channel_busy + * + * Purpose: Change in DCD or PTT status for channel so we know when it is busy. + * + * Inputs: E - Event from the queue. + * + * E->chan - Radio channel number. + * + * E->activity - OCTYPE_PTT for my transmission start/end. + * - OCTYPE_DCD if we hear someone else. + * + * E->status - 1 for active or 0 for quiet. + * + * Outputs: S->radio_channel_busy + * + * T1 & TM201 paused/resumed if running. + * + * Description: We need to pause the timers when the channel is busy. + * + * Signal lm_seize_confirm when we have started to transmit. + * + *------------------------------------------------------------------------------*/ + +static int dcd_status[MAX_CHANS]; +static int ptt_status[MAX_CHANS]; + +void lm_channel_busy (dlq_item_t *E) +{ + int busy; + + assert (E->chan >= 0 && E->chan < MAX_CHANS); + assert (E->activity == OCTYPE_PTT || E->activity == OCTYPE_DCD); + assert (E->status == 1 || E->status == 0); + + switch (E->activity) { + + case OCTYPE_DCD: + + if (s_debug_radio) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("lm_channel_busy: DCD chan %d = %d\n", E->chan, E->status); + } + + dcd_status[E->chan] = E->status; + break; + + case OCTYPE_PTT: + + if (s_debug_radio) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("lm_channel_busy: PTT chan %d = %d\n", E->chan, E->status); + } + + ptt_status[E->chan] = E->status; + break; + + default: + break; + } + + busy = dcd_status[E->chan] | ptt_status[E->chan]; + +/* + * We know if the given radio channel is busy or not. + * This must be applied to all data link state machines associated with that radio channel. + */ + + ax25_dlsm_t *S; + + for (S = list_head; S != NULL; S = S->next) { + + if (E->chan == S->chan) { + + if (busy && ! S->radio_channel_busy) { + S->radio_channel_busy = 1; + PAUSE_T1; + PAUSE_TM201; + + // Did channel become busy due to PTT turning on? + + if ( E->activity == OCTYPE_PTT && E->status == 1) { + + lm_seize_confirm (S); // C4.2. "This primitive indicates, to the Data-link State + // machine, that the transmission opportunity has arrived." + } + } + else if ( ! busy && S->radio_channel_busy) { + S->radio_channel_busy = 0; + RESUME_T1; + RESUME_TM201; + } + } + } + +} /* end lm_channel_busy */ + + + + +/*------------------------------------------------------------------------------ + * + * Name: lm_seize_confirm + * + * Purpose: Notification the the channel is clear. + * + * Description: C4.2. This primitive indicates to the Data-link State Machine that + * the transmission opportunity has arrived. + * + *------------------------------------------------------------------------------*/ + +static void lm_seize_confirm (ax25_dlsm_t *S) +{ + + switch (S->state) { + + case state_0_disconnected: + case state_1_awaiting_connection: + case state_2_awaiting_release: + case state_5_awaiting_v22_connection: + + break; + + case state_3_connected: + case state_4_timer_recovery: + + if (S->acknowledge_pending) { + S->acknowledge_pending = 0; + enquiry_response (S, frame_not_AX25, 0); + } + +// Implemenation difference: The flow chart for state 3 has LM-RELEASE Request here. +// I don't think I need it because the transmitter will turn off +// automatically once the queue is empty. + +// Erratum: The original spec had LM-SEIZE request here, for state 4, which didn't seem right. +// The 2006 revision has LM-RELEASE Request so states 3 & 4 are the same. + + break; + } + +} /* lm_seize_confirm */ + + + +/*------------------------------------------------------------------------------ + * + * Name: lm_data_indication + * + * Purpose: We received some sort of frame over the radio. + * + * Inputs: E - Event from the queue. + * Caller is responsible for freeing it. + * + * Description: First determine if is of interest to me. Two cases: + * + * (1) We already have a link handle for (from-addr, to-addr, channel). + * This could have been set up by an outgoing connect request. + * + * (2) It is addressed to one of the registered callsigns. This would + * catch the case of incoming connect requests. The APRS MYCALL + * is not involved at all. The attached client app might have + * much different ideas about what the station is called or + * aliases it might respond to. + * + *------------------------------------------------------------------------------*/ + +void lm_data_indication (dlq_item_t *E) +{ + ax25_frame_type_t ftype; + char desc[80]; + cmdres_t cr; + int pf; + int nr; + int ns; + ax25_dlsm_t *S; + int client_not_applicable = -1; + int n; + int any_unused_digi; + + + if (E->pp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal Error, packet pointer is null. %s %s %d\n", __FILE__, __func__, __LINE__); + return; + } + + E->num_addr = ax25_get_num_addr(E->pp); + +// Digipeating is not done here so consider only those with no unused digipeater addresses. + + any_unused_digi = 0; + + for (n = AX25_REPEATER_1; n < E->num_addr; n++) { + if ( ! ax25_get_h(E->pp, n)) { + any_unused_digi = 1; + } + } + + if (any_unused_digi) { + if (s_debug_radio) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("lm_data_indication (%d, %s>%s) - ignore due to unused digi address.\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); + } + return; + } + +// Copy addresses from frame into event structure. + + for (n = 0; n < E->num_addr; n++) { + ax25_get_addr_with_ssid (E->pp, n, E->addrs[n]); + } + + if (s_debug_radio) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("lm_data_indication (%d, %s>%s)\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); + } + +// Look for existing, or possibly create new, link state matching addresses and channel. + +// In most cases, we can ignore the frame if we don't have a corresponding +// data link state machine. However, we might want to create a new one for SABM or SABME. +// get_link_handle will check to see if the destination matches my address. + +// TODO: This won't work right because we don't know the modulo yet. +// Maybe we should have a shorter form that only returns the frame type. +// That is all we need at this point. + + ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); + + S = get_link_handle (E->addrs, E->num_addr, E->chan, client_not_applicable, + (ftype == frame_type_U_SABM) | (ftype == frame_type_U_SABME)); + + if (S == NULL) { + return; + } + +/* + * There is not a reliable way to tell if a frame, out of context, has modulo 8 or 128 + * sequence numbers. This needs to be supplied from the data link state machine. + * + * We can't do this until we get the link handle. + */ + + ax25_set_modulo (E->pp, S->modulo); + +/* + * Now we need to use ax25_frame_type again because the previous results, for nr and ns, might be wrong. + */ + + ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); + +// Gather statistics useful for testing. + + if (ftype <= frame_not_AX25) { + S->count_recv_frame_type[ftype]++; + } + + switch (ftype) { + + case frame_type_I: + if (cr != cr_cmd) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error S: %s must be COMMAND.\n", S->stream_id, desc); + } + break; + + case frame_type_S_RR: + case frame_type_S_RNR: + case frame_type_S_REJ: + if (cr != cr_cmd && cr != cr_res) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); + } + break; + + case frame_type_U_SABME: + case frame_type_U_SABM: + case frame_type_U_DISC: + if (cr != cr_cmd) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND.\n", S->stream_id, desc); + } + break; + +// Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both. +// The underlying X.25 spec clearly says it is reponse only. Let's go with that. + + case frame_type_S_SREJ: + case frame_type_U_DM: + case frame_type_U_UA: + case frame_type_U_FRMR: + if (cr != cr_res) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error: %s must be RESPONSE.\n", S->stream_id, desc); + } + break; + + case frame_type_U_XID: + case frame_type_U_TEST: + if (cr != cr_cmd && cr != cr_res) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); + } + break; + + case frame_type_U_UI: + // Don't test at this point in case an APRS frame gets thru. + // APRS doesn't specify what to put in the Source and Dest C bits. + // In practice we see all 4 possble combinations. + // I have an opinion about what would be "correct" (discussed elsewhere) + // but in practice no one seems to care. + break; + + case frame_type_U: + case frame_not_AX25: + // not expected. + break; + } + + + switch (ftype) { + + case frame_type_I: // Information + { + int pid; + unsigned char *info_ptr; + int info_len; + + pid = ax25_get_pid (E->pp); + info_len = ax25_get_info (E->pp, &info_ptr); + + i_frame (S, cr, pf, nr, ns, pid, (char *)info_ptr, info_len); + } + break; + + case frame_type_S_RR: // Receive Ready - System Ready To Receive + rr_rnr_frame (S, 1, cr, pf, nr); + break; + + case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full + rr_rnr_frame (S, 0, cr, pf, nr); + break; + + case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate + rej_frame (S, cr, pf, nr); + break; + + case frame_type_S_SREJ: // Selective Reject - Ask for single frame repeat + srej_frame (S, cr, pf, nr); + break; + + case frame_type_U_SABME: // Set Async Balanced Mode, Extended + sabm_e_frame (S, 1, pf); + break; + + case frame_type_U_SABM: // Set Async Balanced Mode + sabm_e_frame (S, 0, pf); + break; + + case frame_type_U_DISC: // Disconnect + disc_frame (S, pf); + break; + + case frame_type_U_DM: // Disconnect Mode + dm_frame (S, pf); + break; + + case frame_type_U_UA: // Unnumbered Acknowledge + ua_frame (S, pf); + break; + + case frame_type_U_FRMR: // Frame Reject + frmr_frame (S); + break; + + case frame_type_U_UI: // Unnumbered Information + ui_frame (S, cr, pf); + break; + + case frame_type_U_XID: // Exchange Identification + { + unsigned char *info_ptr; + int info_len; + + info_len = ax25_get_info (E->pp, &info_ptr); + + xid_frame (S, cr, pf, info_ptr, info_len); + } + break; + + case frame_type_U_TEST: // Test + { + unsigned char *info_ptr; + int info_len; + + info_len = ax25_get_info (E->pp, &info_ptr); + + test_frame (S, cr, pf, info_ptr, info_len); + } + break; + + case frame_type_U: // other Unnumbered, not used by AX.25. + break; + + case frame_not_AX25: // Could not get control byte from frame. + break; + } + + i_frame_pop_off_queue (S); + +} /* end lm_data_indication */ + + + +/*------------------------------------------------------------------------------ + * + * Name: i_frame + * + * Purpose: Process I Frame. + * + * Inputs: S - Data Link State Machine. + * cr - Command or Response. We have already issued an error if not command. + * p - Poll bit. Assuming we checked earlier that it was a command. + * The meaning is described below. + * nr - N(R) from the frame. Next expected seq. for other end. + * ns - N(S) from the frame. Seq. number of this incoming frame. + * pid - protocol id. + * info_ptr - pointer to information part of frame. + * info_len - Number of bytes in information part of frame. + * Should be in range of 0 thru n1_paclen. + * + * Description: + * 6.4.2. Receiving I Frames + * + * The reception of I frames that contain zero-length information fields is reported to the next layer; no information + * field will be transferred. + * + * 6.4.2.1. Not Busy + * + * If a TNC receives a valid I frame (one with a correct FCS and whose send sequence number equals the + * receiver's receive state variable) and is not in the busy condition, it accepts the received I frame, increments its + * receive state variable, and acts in one of the following manners: + * + * a) If it has an I frame to send, that I frame may be sent with the transmitted N(R) equal to its receive state + * variable V(R) (thus acknowledging the received frame). Alternately, the TNC may send an RR frame with N(R) + * equal to V(R), and then send the I frame. + * + * or b) If there are no outstanding I frames, the receiving TNC sends an RR frame with N(R) equal to V(R). The + * receiving TNC may wait a small period of time before sending the RR frame to be sure additional I frames are + * not being transmitted. + * + * 6.4.2.2. Busy + * + * If the TNC is in a busy condition, it ignores any received I frames without reporting this condition, other than + * repeating the indication of the busy condition. + * If a busy condition exists, the TNC receiving the busy condition indication polls the sending TNC periodically + * until the busy condition disappears. + * A TNC may poll the busy TNC periodically with RR or RNR frames with the P bit set to "1". + * + * 6.4.6. Receiving Acknowledgement + * + * Whenever an I or S frame is correctly received, even in a busy condition, the N(R) of the received frame is + * checked to see if it includes an acknowledgement of outstanding sent I frames. The T1 timer is canceled if the + * received frame actually acknowledges previously unacknowledged frames. If the T1 timer is canceled and there + * are still some frames that have been sent that are not acknowledged, T1 is started again. If the T1 timer expires + * before an acknowledgement is received, the TNC proceeds with the retransmission procedure outlined in Section + * 6.4.11. + * + * + * 6.2. Poll/Final (P/F) Bit Procedures + * + * The next response frame returned to an I frame with the P bit set to "1", received during the information + * transfer state, is an RR, RNR or REJ response with the F bit set to "1". + * + * The next response frame returned to a S or I command frame with the P bit set to "1", received in the + * disconnected state, is a DM response frame with the F bit set to "1". + * + *------------------------------------------------------------------------------*/ + +static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len) +{ + switch (S->state) { + + case state_0_disconnected: + + // Logic from flow chart for "all other commands." + + if (cr == cr_cmd) { + cmdres_t r = cr_res; // DM response with F taken from P. + int f = p; + int nopid = 0; // PID applies only for I and UI frames. + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + + // Ignore it. Keep same state. + break; + + case state_2_awaiting_release: + + // Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." + + if (cr == cr_cmd && p == 1) { + cmdres_t r = cr_res; // DM response with F = 1. + int f = 1; + int nopid = 0; // PID applies only for I and UI frames. + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + break; + + case state_3_connected: + case state_4_timer_recovery: + + // Look carefully. The original had two tiny differences between the two states. + // In the 2006 version, these differences no longer exist. + + if (info_len >= 0 && info_len <= S->n1_paclen) { + + if (is_good_nr(S,nr)) { + + // Erratum? + // I wonder if this difference is intentional or if only one place was + // was modified after a cut-n-paste of the flow chart segment. + + // Erratum: Discrepancy between original and 2006 version. + + // Pattern noticed: Anytime we have "is_good_nr" which tests for V(A) <= N(R) <= V(S), + // we should always call "check_i_frame_acked" or at least set V(A) from N(R). + +#if 0 // Erratum: original - states 3 & 4 differ here. + + if (S->state == state_3_connected) { + // This sets "S->va = nr" and also does some timer stuff. + check_i_frame_ackd (S,nr); + } + else { + SET_VA(nr); + } + +#else // 2006 version - states 3 & 4 same here. + + // This sets "S->va = nr" and also does some timer stuff. + + check_i_frame_ackd (S,nr); +#endif + + if (S->own_receiver_busy) { + // This should be unreachable because we currently don't have a way to set own_receiver_busy. + // But we might the capability someday so implement this while we are here. + + if (p == 1) { + cmdres_t cr = cr_res; // Erratum: The use of "F" in the flow chart implies that RNR is a response + // in this case, but I'm not confident about that. The text says frame. + int f = 1; + int nr = S->vr; + packet_t pp; + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f); + + // I wonder if this difference is intentional or if only one place was + // was modified after a cut-n-paste of the flow chart segment. + +#if 0 // Erratum: Original - state 4 has expedited. + + if (S->state == state_4_timer_recovery) { + lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // "expedited" + } + else { + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } +#else // 2006 version - states 3 & 4 the same. + + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + +#endif + S->acknowledge_pending = 0; + } + } + else { // N(R) out of expected range. + + i_frame_continued (S, p, ns, pid, info_ptr, info_len); + } + } + else { + nr_error_recovery (S); + // my enhancement. See below. + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + } + else { // Bad information length. + // Wouldn't even get to CRC check if not octet aligned. + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error O: Information part length, %d, not in range of 0 thru %d.\n", S->stream_id, info_len, S->n1_paclen); + + establish_data_link (S); + S->layer_3_initiated = 0; + + // The original spec always sent SABM and went to state 1. + // I was thinking, why not use v2.2 instead of we were already connected with v2.2? + // My version of establish_data_link combined the two original functions and + // already uses SABME or SABM based on S->modulo. + + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + break; + } + +} /* end i_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: i_frame_continued + * + * Purpose: The I frame processing logic gets pretty complicated. + * Some of it has been split out into a separate function to make + * things more readable. + * We have already done some error checking and processed N(R). + * This is used for both states 3 & 4. + * + * Inputs: S - Data Link State Machine. We are in state 3. + * p - Poll bit. + * ns - N(S) from the frame. Seq. number of this incoming frame. + * pid - protocol id. + * info_ptr - pointer to information part of frame. + * info_len - Number of bytes in information part of frame. Already verified. + * + * Description: + * + * 4.3.2.3. Reject (REJ) Command and Response + * + * The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence + * number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the + * retransmission of the N(R) frame. + * Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the + * proper reception of I frames up to the I frame that caused the reject condition to be initiated. + * The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit + * set to one. + * + * 4.3.2.4. Selective Reject (SREJ) Command and Response + * + * The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame + * numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are + * considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ + * frame does not indicate acknowledgement of I frames. + * + * Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) + * of the SREJ frame. + * + * A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set + * to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not + * transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so + * would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ + * frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in + * + * Section 4.5.4. + * + * I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of + * receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the + * retransmission of the specific I frame requested by the SREJ frame. + * + * + * 6.4.4. Reception of Out-of-Sequence Frames + * + * 6.4.4.1. Implicit Reject (REJ) + * + * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current + * receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number + * equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not + * been previously established. The received state variable and poll bit of the discarded frame is checked and acted + * upon, if necessary. + * This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode + * requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This + * mode is ineffective on systems with long round-trip delays and high data rates. + * + * 6.4.4.2. Selective Reject (SREJ) + * + * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current + * receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number + * equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously + * established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable + * and poll bit of the received frame are checked and acted upon, if necessary. + * This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can + * consume precious buffer space, especially if the user device has limited memory available and several active + * links are operational. + * + * 6.4.4.3. Selective Reject-Reject (SREJ/REJ) + * + * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current + * receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. + * All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is + * missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The + * received state variable and poll bit of the received frame are checked and acted upon. If another frame error + * occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame + * and discards frames received after the second errored frame until the first errored frame is recovered. Then, a + * REJ is issued to recover the second errored frame and all subsequent discarded frames. + * + *------------------------------------------------------------------------------*/ + +static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len) +{ + + if (ns == S->vr) { + +// The receive sequence number, N(S), is the same as what we were expecting, V(R). +// Send it to the application and increment the next expected. +// It is possible that this was resent and we tucked away others with the following +// sequence numbers. If so, process them too. + + + SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); + S->reject_exception = 0; + + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); + ax25_safe_print (info_ptr, info_len, 1); + dw_printf ("\"\n"); + } + + dl_data_indication (S, pid, info_ptr, info_len); + + if (S->rxdata_by_ns[ns] != NULL) { + // There is a possibility that we might have another received frame stashed + // away from 8 or 128 (modulo) frames back. Remove it so it doesn't accidently + // show up at some future inopportune time. + + cdata_delete (S->rxdata_by_ns[ns]); + S->rxdata_by_ns[ns] = NULL; + + } + + + while (S->rxdata_by_ns[S->vr] != NULL) { + + // dl_data_indication - send connected data to client application. + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, data=\"", __func__, __LINE__, ns, S->vr); + ax25_safe_print (S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len, 1); + dw_printf ("\"\n"); + } + + dl_data_indication (S, S->rxdata_by_ns[S->vr]->pid, S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len); + + // Don't keep around anymore after sending it to client app. + + cdata_delete (S->rxdata_by_ns[S->vr]); + S->rxdata_by_ns[S->vr] = NULL; + + SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); + } + + if (p) { + +// Mentioned in section 6.2. +// The next response frame returned to an I frame with the P bit set to "1", received during the information +// transfer state, is an RR, RNR or REJ response with the F bit set to "1". + + int f = 1; + int nr = S->vr; // Next expected sequence number. + cmdres_t cr = cr_res; // response with F set to 1. + packet_t pp; + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + S->acknowledge_pending = 0; + } + else if ( ! S->acknowledge_pending) { + + S->acknowledge_pending = 1; // Probably want to set this before the LM-SEIZE Request + // in case the LM-SEIZE Confirm gets processed before we + // return from it. + + // Force start of transmission even if the transmit frame queue is empty. + // Notify me, with lm_seize_confirm, when transmission has started. + // When that event arrives, we check acknowledge_pending and send something + // to be determined later. + + lm_seize_request (S->chan); + } + } + else if (S->reject_exception) { + +// This is not the sequence we were expecting. +// We previously sent REJ, asking for a resend so don't send another. +// In this case, send RR only if the Poll bit is set. +// Again, reference section 6.2. + + if (p) { + int f = 1; + int nr = S->vr; // Next expected sequence number. + cmdres_t cr = cr_res; // response with F set to 1. + packet_t pp; + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + S->acknowledge_pending = 0; + } + } + else if ( ! S->srej_enabled) { + +// The received sequence number is not the expected one and we can't use SREJ. +// The old v2.0 approach is to send and REJ with the number we are expecting. +// This can be very inefficient. For example if we received 1,3,4,5,6 in one transmission, +// we discard 3,4,5,6, and tell the other end to resend everything starting with 2. + +// At one time, I had some doubts about when to use command or response for REJ. +// I now believe that reponse, as implied by setting F in the flow chart, is correct. + + int f = p; + int nr = S->vr; // Next expected sequence number. + cmdres_t cr = cr_res; // response with F copied from P in I frame. + packet_t pp; + + S->reject_exception = 1; + + if (s_debug_retry) { + text_color_set(DW_COLOR_ERROR); // make it more noticable. + dw_printf ("sending REJ, at %s %d, SREJ not enabled case, V(R)=%d", __func__, __LINE__, S->vr); + } + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_REJ, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->acknowledge_pending = 0; + } + else { + +// Selective reject is enabled so we can use the more efficient method. +// This is normally enabled for v2.2 but XID can be used to change that. +// First we save the current frame so we can retrieve it later after getting the fill in. + + if (S->modulo != 128) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR: Should not be sending SREJ in basic (modulo 8) mode.\n"); + } + +#if 1 + +// Erratum: AX.25 protocol spec did not handle SREJ very well. +// Based on X.25 section 2.4.6.4. + + + if (is_ns_in_window(S, ns)) { + +// X.25 2.4.6.4 (b) +// v(R) < N(S) < V(R)+k so it is in the expected range. +// Save it in the receive buffer. + + if (S->rxdata_by_ns[ns] != NULL) { + cdata_delete (S->rxdata_by_ns[ns]); + S->rxdata_by_ns[ns] = NULL; + } + S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); + + if (s_debug_misc) { + dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); + ax25_safe_print (info_ptr, info_len, 1); + dw_printf ("\"\n"); + } + + if (p == 1) { + int f = 1; + enquiry_response (S, frame_type_I, f); + } + else if (S->own_receiver_busy) { + cmdres_t cr = cr_res; // send RNR response + int f = 0; // we know p=0 here. + int nr = S->vr; + packet_t pp; + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + else if (S->rxdata_by_ns[ AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) { + +// Ask for missing frames when we don't have N(S)-1 in the receive buffer. + +// I would think that we would want to set F when sending N(R) = V(R) but it says use F=0 here. +// Don't understand why yet. + +// I like my method better. It does not generate duplicate requests for gaps in the same transmission. +// This creates a cummulative list each time and would cause repeats to be sent more often than necessary. + + int resend[128]; + int count = 0; + int x; + int allow_f1 = 0; // F=1 from X.25 2.4.6.4 b) 3) + + for (x = S->vr; x != ns; x = AX25MODULO(x + 1, S->modulo, __FILE__, __func__, __LINE__)) { + if (S->rxdata_by_ns[x] == NULL) { + resend[count++] = AX25MODULO(x, S->modulo, __FILE__, __func__, __LINE__); + } + } + + send_srej_frames (S, resend, count, allow_f1); + } + } + else { + +// X.25 2.4.6.4 a) +// N(S) is not in expected range. Discard it. Send response if P=1. + + if (p == 1) { + int f = 1; + enquiry_response (S, frame_type_I, f); + } + + } + +#else // my earlier attempt before taking a close look at X.25 spec. + // Keeping it around for a little while because I might want to + // use earlier technique of sending only needed SREJ for any second + // and later gaps in a single multiframe transmission. + + + if (S->rxdata_by_ns[ns] != NULL) { + cdata_delete (S->rxdata_by_ns[ns]); + S->rxdata_by_ns[ns] = NULL; + } + S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); + + S->outstanding_srej[ns] = 0; // Don't care if it was previously set or not. + // We have this one so there is no outstanding SREJ for it. + + if (s_debug_misc) { + dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); + ax25_safe_print (info_ptr, info_len, 1); + dw_printf ("\"\n"); + } + + + + + if (selective_reject_exception(S) == 0) { + +// Erratum: This is vastly different than the SDL in the AX.25 protocol spec. +// That would use SREJ if only one was missing and REJ instead. +// Here we do not mix the them. +// This agrees with the X.25 protocol spec that says use one or the other. Not both. + +// Suppose we had incoming I frames 0, 3, 7. +// 0 was already processed and V(R)=1 meaning that is the next expected. +// At this point we area processing N(S)=3. +// In this case, we need to ask for a resend of 1 & 2. +// More generally, the range of V(R) thru N(S)-1. + + int resend[128]; + int count = 0; + int i; + int allow_f1 = 1; + +text_color_set(DW_COLOR_ERROR); +dw_printf ("%s:%d, zero exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, S->vr, ns); + + for (i = S->vr; i != ns; i = AX25MODULO(i+1, S->modulo, __FILE__, __func__, __LINE__)) { + resend[count++] = i; + } + + send_srej_frames (S, resend, count, allow_f1); + } + else { + +// Erratum: The SDL says ask for N(S) which is clearly wrong because that's what we just received. +// Instead we want to ask for any missing frames up to but not including N(S). + +// Let's continue with the example above. I frames with N(S) of 0, 3, 7. +// selective_reject_exception is non zero meaning there are outstanding requests to resend specified I frames. +// V(R) is still 1 because 0 is the last one received with contiguous N(S) values. +// 3 has been saved into S->rxdata_by_ns. +// We now have N(S)=7. We want to ask for a resend of 4, 5, 6. +// This can be achieved by searching S->rxdata_by_ns, starting with N(S)-1, and counting +// how many empty slots we have before finding a saved frame. + + int count = 0; + int first; + +text_color_set(DW_COLOR_ERROR); +dw_printf ("%s:%d, %d srej exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, selective_reject_exception(S), S->vr, ns); + + first = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__); + while (S->rxdata_by_ns[first] == NULL) { + if (first == AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__)) { + // Oops! Went too far. This I frame was already processed. + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR calulating what to put in SREJ, %s line %d\n", __func__, __LINE__); + dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, count=%d\n", S->vr, ns, selective_reject_exception(S), first, count); + int k; + for (k=0; k<128; k++) { + if (S->rxdata_by_ns[k] != NULL) { + dw_printf ("rxdata_by_ns[%d] has data\n", k); + } + } + break; + } + count++; + first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__); + } + + // Go beyond the slot where we already have an I frame. + first = AX25MODULO(first + 1, S->modulo, __FILE__, __func__, __LINE__); + + // The count could be 0. e.g. We got 4 rather than 7 in this example. + + if (count > 0) { + int resend[128]; + int n; + int allow_f1 = 1; + + for (n = 0; n < count; n++) { + resend[n] = AX25MODULO(first + n, S->modulo, __FILE__, __func__, __LINE__);; + } + + send_srej_frames (S, resend, count, allow_f1); + } + + } /* end SREJ exception */ + + + +#endif // my earlier attempt. + + + + +// Erratum: original has following but 2006 rev does not. +// I think the 2006 version is correct. +// SREJ does not always satisfy the need for ack. +// There is a special case where F=1. We take care of that inside of send_srej_frames. + +#if 0 + S->acknowledge_pending = 0; +#endif + + } /* end srej_enabled */ + + +} /* end i_frame_continued */ + + + +/*------------------------------------------------------------------------------ + * + * Name: is_ns_in_window + * + * Purpose: Is the N(S) value of the incoming I frame in the expected range? + * + * Inputs: ns - Sequence from I frame. + * + * Description: With selective reject, it is possible that we could receive a repeat of + * an I frame with N(S) less than V(R). In this case, we just want to + * discard it rather than getting upset and reestablishing the connection. + * + * The X.25 spec,section 2.4.6.4 (b) asks whether V(R) < N(S) < V(R)+k. + * + * The problem here is that it depends on the value of k for the other end. + * X.25 says that both sides need to agree on a common value of k ahead of time. + * We might have k=8 for our sending but the other side could have k=32 so + * this test could fail. + * + * As a hack, we could use the value 63 here. If too small we would discard + * I frames that are in the acceptable range because they would be >= V(R)+k. + * On the other hand, if this value is too big, the range < V(R) would not be + * large enough and we would accept frame we shouldn't. + * As a practical matter, using a window size that large is pretty unlikely. + * Maybe I could put a limit of 63, rather than 127 in the configuration. + * + *------------------------------------------------------------------------------*/ + +#define GENEROUS_K 63 + +static int is_ns_in_window (ax25_dlsm_t *S, int ns) +{ + int adjusted_vr, adjusted_ns, adjusted_vrpk; + int result; + +/* Shift all values relative to V(R) before comparing so we won't have wrap around. */ + +#define adjust_by_vr(x) (AX25MODULO((x) - S->vr, S->modulo, __FILE__, __func__, __LINE__)) + + adjusted_vr = adjust_by_vr(S->vr); // A clever compiler would know it is zero. + adjusted_ns = adjust_by_vr(ns); + adjusted_vrpk = adjust_by_vr(S->vr + GENEROUS_K); + + result = adjusted_vr < adjusted_ns && adjusted_ns < adjusted_vrpk; + + if (s_debug_retry) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("is_ns_in_window, V(R) %d < N(S) %d < V(R)+k %d, returns %d\n", S->vr, ns, S->vr + GENEROUS_K, result); + } + + return (result); +} + + +/*------------------------------------------------------------------------------ + * + * Name: send_srej_frames + * + * Purpose: Ask for a resend of I frames with specified sequence numbers. + * + * Inputs: resend - Array of N(S) values for missing I frames. + * count - Number of items in array. + * allow_f1 - When true, set F=1 when asking for V(R). + * X.25 section 2.4.6.4 b) 3) says F should be set to 0 + * when receiving I frame out of sequence. + * X.25 sections 2.4.6.11 & 2.3.5.2.2 say set F to 1 when + * responding to command with P=1. (our enquiry_response function). + * + * Future? The X.25 protocol spec allows additional sequence numbers in one frame + * by using the INFO part. + * It would be easy but we haven't done that here to remain compatible with AX.25. + * + *------------------------------------------------------------------------------*/ + + +static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1) +{ + int f; // Set if we are ack-ing one before. + int nr; + cmdres_t cr = cr_res; // SREJ is always response. + int i; + + packet_t pp; + + if (count <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR, count=%d, %s line %d\n", count, __func__, __LINE__); + return; + } + + if (s_debug_retry) { + text_color_set(DW_COLOR_INFO); + dw_printf ("%s line %d\n", __func__, __LINE__); + //dw_printf ("state=%d, count=%d, k=%d, V(R)=%d, SREJ exeception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S)); + dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); + + dw_printf ("resend[]="); + for (i = 0; i < count; i++) { + dw_printf (" %d", resend[i]); + } + dw_printf ("\n"); + + dw_printf ("rxdata_by_ns[]="); + for (i = 0; i < 128; i++) { + if (S->rxdata_by_ns[i] != NULL) { + dw_printf (" %d", i); + } + } + dw_printf ("\n"); + } + + +// Something is wrong! We ask for more than the window size. + + if (count > S->k_maxframe) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR - Extreme number of SREJ, %s line %d\n", __func__, __LINE__); + dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); + + dw_printf ("resend[]="); + for (i = 0; i < count; i++) { + dw_printf (" %d", resend[i]); + } + dw_printf ("\n"); + + dw_printf ("rxdata_by_ns[]="); + for (i = 0; i < 128; i++) { + if (S->rxdata_by_ns[i] != NULL) { + dw_printf (" %d", i); + } + } + dw_printf ("\n"); + } + + for (i = 0; i < count; i++) { + + nr = resend[i]; + f = allow_f1 && (nr == S->vr); + // Possibly set if we are asking for the next after + // the last one received in contiguous order. + + if (f) { + // In this case the other end is being + // informed of my V(R) so no additional + // RR etc. is needed. + S->acknowledge_pending = 0; + } + + if (nr < 0 || nr >= S->modulo) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR, nr=%d, modulo=%d, %s line %d\n", nr, S->modulo, __func__, __LINE__); + nr = AX25MODULO(nr, S->modulo, __FILE__, __func__, __LINE__); + } + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_SREJ, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + +} /* end send_srej_frames */ + + + +/*------------------------------------------------------------------------------ + * + * Name: rr_rnr_frame + * + * Purpose: Process RR or RNR Frame. + * Processing is the essentially the same so they are handled by a single function. + * + * Inputs: S - Data Link State Machine. + * ready - True for RR, false for RNR + * cr - Is this command or response? + * pf - Poll/Final bit. + * nr - N(R) from the frame. + * + * Description: 4.3.2.1. Receive Ready (RR) Command and Response + * + * Receive Ready accomplishes the following: + * a) indicates that the sender of the RR is now able to receive more I frames; + * b) acknowledges properly received I frames up to, and including N(R)-1;and + * c) clears a previously-set busy condition created by an RNR command having been sent. + * The status of the TNC at the other end of the link can be requested by sending an RR command frame with the + * P-bit set to one. + * + * 4.3.2.2. Receive Not Ready (RNR) Command and Response + * + * Receive Not Ready indicates to the sender of I frames that the receiving TNC is temporarily busy and cannot + * accept any more I frames. Frames up to N(R)-1 are acknowledged. Frames N(R) and above that may have been + * transmitted are discarded and must be retransmitted when the busy condition clears. + * The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. + * The status of the TNC at the other end of the link is requested by sending an RNR command frame with the + * P bit set to one. + * + *------------------------------------------------------------------------------*/ + + +static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr) +{ + + // dw_printf ("rr_rnr_frame (ready=%d, cr=%d, pf=%d, nr=%d) state=%d\n", ready, cr, pf, nr, S->state); + + switch (S->state) { + + case state_0_disconnected: + + if (cr == cr_cmd) { + cmdres_t r = cr_res; // DM response with F taken from P. + int f = pf; + int nopid = 0; // PID only for I and UI frames. + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + + // do nothing. + break; + + case state_2_awaiting_release: + + // Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." + + if (cr == cr_cmd && pf == 1) { + cmdres_t r = cr_res; // DM response with F = 1. + int f = 1; + int nopid = 0; // PID applies only for I and UI frames. + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + +// Erratum: We have a disagreement here between original and 2006 version. +// RR, RNR, REJ, SREJ responses would fall under "all other primitives." +// In the original, we simply ignore it and stay in state 2. +// The 2006 version, page 94, says go into "1 awaiting connection" state. +// That makes no sense to me. + + break; + + case state_3_connected: + + S->peer_receiver_busy = ! ready; + +// Erratum: the flow charts have unconditional check_need_for_response here. +// I don't recall exactly why I added the extra test for command and P=1. +// It might have been because we were reporting error A for response with F=1. +// Other than avoiding that error message, this is functionally equivalent. + + if (cr == cr_cmd && pf) { + check_need_for_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, cr, pf); + } + + if (is_good_nr(S,nr)) { + // dw_printf ("rr_rnr_frame (), line %d, state=%d, good nr=%d, calling check_i_frame_ackd\n", __LINE__, S->state, nr); + + check_i_frame_ackd (S, nr); + // keep current state. + } + else { + if (s_debug_retry) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("rr_rnr_frame (), line %d, state=%d, bad nr, calling nr_error_recovery\n", __LINE__, S->state); + } + + nr_error_recovery (S); + // My enhancement. Original always sent SABM and went to state 1. + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + + break; + + case state_4_timer_recovery: + + S->peer_receiver_busy = ! ready; + + if (cr == cr_res && pf == 1) { + + if (s_debug_retry) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("rr_rnr_frame (), Response, pf=1, line %d, state=%d, good nr, calling check_i_frame_ackd\n", __LINE__, S->state); + } + + STOP_T1; + select_t1_value(S); + + if (is_good_nr(S,nr)) { + + SET_VA(nr); + if (S->vs == S->va) { // all caught up with ack from other guy. + START_T3; + S->rc =0; // My enhancement. See Erratum note in select_t1_value. + enter_new_state (S, state_3_connected, __func__, __LINE__); + } + else { + invoke_retransmission (S, nr); +// my addition + +// Erratum: We sent I frame(s) and want to timeout if no ack comes back. +// We also sent N(R) so no need for extra RR at the end only for that. + + STOP_T3; + START_T1; + S->acknowledge_pending = 0; + +// end of my addition + } + } + else { + nr_error_recovery (S); + +// Erratum: Another case of my enhancement. +// The flow charts go into state 1 after nr_error_recovery. +// I use state 5 instead if we were oprating in extended (modulo 128) mode. + + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + } + else { + if (cr == cr_cmd && pf == 1) { + int f = 1; + enquiry_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, f); + } + + if (is_good_nr(S,nr)) { + SET_VA(nr); + // REJ state 4 is identical but has conditional invoke_retransmission here. + } + else { + nr_error_recovery (S); + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + } + + break; + } + +} /* end rr_rnr_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: rej_frame + * + * Purpose: Process REJ Frame. + * + * Inputs: S - Data Link State Machine. + * cr - Is this command or response? + * pf - Poll/Final bit. + * nr - N(R) from the frame. + * + * Description: 4.3.2.2. Receive Not Ready (RNR) Command and Response + * + * ... The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. ... + * + * + * 4.3.2.3. Reject (REJ) Command and Response + * + * The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence + * number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the + * retransmission of the N(R) frame. + * Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the + * proper reception of I frames up to the I frame that caused the reject condition to be initiated. + * The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit + * set to one. + * + * 4.4.3. Reject (REJ) Recovery + * + * The REJ frame requests a retransmission of I frames following the detection of a N(S) sequence error. Only + * one outstanding "sent REJ" condition is allowed at a time. This condition is cleared when the requested I frame + * has been received. + * A TNC receiving the REJ command clears the condition by resending all outstanding I frames (up to the + * window size), starting with the frame indicated in N(R) of the REJ frame. + * + * + * 4.4.5.1. T1 Timer Recovery + * + * If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I + * frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently + * does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion + * of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described + * in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent + * frame(s), or by the link being reset. + * + * 6.2. Poll/Final (P/F) Bit Procedures + * + * The response frame returned by a TNC depends on the previous command received, as described in the + * following paragraphs. + * ... + * + * The next response frame returned to an I frame with the P bit set to "1", received during the information5 + * transfer state, is an RR, RNR or REJ response with the F bit set to "1". + * + * The next response frame returned to a supervisory command frame with the P bit set to "1", received during + * the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1". + * ... + * + * The P bit is used in conjunction with the timeout recovery condition discussed in Section 4.5.5. + * When not used, the P/F bit is set to "0". + * + * 6.4.4.1. Implicit Reject (REJ) + * + * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current + * receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number + * equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not + * been previously established. The received state variable and poll bit of the discarded frame is checked and acted + * upon, if necessary. + * This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode + * requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This + * mode is ineffective on systems with long round-trip delays and high data rates. + * + * 6.4.7. Receiving REJ + * + * After receiving a REJ frame, the transmitting TNC sets its send state variable to the same value as the REJ + * frame's received sequence number in the control field. The TNC then retransmits any I frame(s) outstanding at + * the next available opportunity in accordance with the following: + * + * a) If the TNC is not transmitting at the time and the channel is open, the TNC may begin retransmission of the + * I frame(s) immediately. + * b) If the TNC is operating on a full-duplex channel transmitting a UI or S frame when it receives a REJ frame, + * it may finish sending the UI or S frame and then retransmit the I frame(s). + * c) If the TNC is operating in a full-duplex channel transmitting another I frame when it receives a REJ frame, + * it may abort the I frame it was sending and start retransmission of the requested I frames immediately. + * d) The TNC may send just the one I frame outstanding, or it may send more than the one indicated if more I + * frames followed the first unacknowledged frame, provided that the total to be sent does not exceed the flowcontrol + * window (k frames). + * If the TNC receives a REJ frame with the poll bit set, it responds with either an RR or RNR frame with the + * final bit set before retransmitting the outstanding I frame(s). + * + *------------------------------------------------------------------------------*/ + +static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr) +{ + + switch (S->state) { + + case state_0_disconnected: + + // states 0 and 2 are very similar with one tiny little difference. + + if (cr == cr_cmd) { + cmdres_t r = cr_res; // DM response with F taken from P. + int f = pf; + int nopid = 0; // PID is only for I and UI. + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + // Do nothing. + break; + + case state_2_awaiting_release: + + if (cr == cr_cmd && pf == 1) { + cmdres_t r = cr_res; // DM response with F = 1. + int f = 1; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + +// Erratum: We have a disagreement here between original and 2006 version. +// RR, RNR, REJ, SREJ responses would fall under "all other primitives." +// In the original, we simply ignore it and stay in state 2. +// The 2006 version, page 94, says go into "1 awaiting connection" state. +// That makes no sense to me. + + break; + + case state_3_connected: + + S->peer_receiver_busy = 0; + +// Receipt of the REJ "frame" (either command or response) causes us to +// start resending I frames at the specified number. + +// I think there are 3 possibilities here: +// Response is used when incoming I frame processing detects one is missing. +// In this case, F is copied from the I frame P bit. I don't think we care here. +// Command with P=1 is used during timeout recovery. +// The rule is that we are supposed to send a response with F=1 for I, RR, RNR, or REJ with P=1. + + check_need_for_response (S, frame_type_S_REJ, cr, pf); + + if (is_good_nr(S,nr)) { + SET_VA(nr); + STOP_T1; + STOP_T3; + select_t1_value(S); + + invoke_retransmission (S, nr); + +// my addition +// Erratum: We sent I frame(s) and want to timeout if no ack comes back. +// We also sent N(R) so no need for extra RR at the end only for that. + +// We ran into cases where I frame(s) would be resent but lost. +// T1 was stopped so we just waited and waited and waited instead of trying again. +// I added the following after each invoke_retransmission. +// This seems clearer than hiding the timer stuff inside of it. + + // T3 is already stopped. + START_T1; + S->acknowledge_pending = 0; + } + else { + nr_error_recovery (S); + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + break; + + case state_4_timer_recovery: + + S->peer_receiver_busy = 0; + + if (cr == cr_res && pf == 1) { + + STOP_T1; + select_t1_value(S); + + if (is_good_nr(S,nr)) { + + SET_VA(nr); + if (S->vs == S->va) { + START_T3; + S->rc =0; // My enhancement. See Erratum note in select_t1_value. + enter_new_state (S, state_3_connected, __func__, __LINE__); + } + else { + invoke_retransmission (S, nr); +// my addition. +// Erratum: We sent I frame(s) and want to timeout if no ack comes back. +// We also sent N(R) so no need for extra RR at the end only for that. + + STOP_T3; + START_T1; + S->acknowledge_pending = 0; + } + } + else { + nr_error_recovery (S); + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + } + else { + if (cr == cr_cmd && pf == 1) { + int f = 1; + enquiry_response (S, frame_type_S_REJ, f); + } + + if (is_good_nr(S,nr)) { + + SET_VA(nr); + + if (S->vs != S->va) { + // Observation: RR/RNR state 4 is identical but it doesn't have invoke_retransmission here. + invoke_retransmission (S, nr); +// my addition. +// Erratum: We sent I frame(s) and want to timeout if no ack comes back. +// We also sent N(R) so no need for extra RR at the end only for that. + + STOP_T3; + START_T1; + S->acknowledge_pending = 0; + } + + } + else { + nr_error_recovery (S); + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + } + break; + } + +} /* end rej_frame */ + + +/*------------------------------------------------------------------------------ + * + * Name: srej_frame + * + * Purpose: Process SREJ Response. + * + * Inputs: S - Data Link State Machine. + * cr - Is this command or response? + * f - Final bit. When set, it is ack-ing up thru N(R)-1 + * nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S). + * + * Description: 4.3.2.4. Selective Reject (SREJ) Command and Response + * + * The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame + * numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are + * considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ + * frame does not indicate acknowledgement of I frames. + * + * Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) + * of the SREJ frame. + * + * A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set + * to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not + * transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so + * would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ + * frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in + * Section 4.5.4. + * + * I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of + * receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the + * retransmission of the specific I frame requested by the SREJ frame. + * + * Erratum: The section above always refers to SREJ "frames." There doesn't seem to be any clue about when + * command vs. response would be used. When we look in the flow charts, we see that we generate only + * responses but the code is there to process command and response slightly differently. + * + * Description: 4.4.4. Selective Reject (SREJ) Recovery + * + * The SREJ command/response initiates more-efficient error recovery by requesting the retransmission of a + * single I frame following the detection of a sequence error. This is an advancement over the earlier versions in + * which the requested I frame was retransmitted togther with all additional I frames subsequently transmitted and + * successfully received. + * + * When a TNC sends one or more SREJ commands, each with the P bit set to "0" or "1", or one or more SREJ + * responses, each with the F bit set to "0", and the "sent SREJ" conditions are not cleared when the TNC is ready + * to issue the next response frame with the F bit set to "1", the TNC sends a SREJ response with the F bit set to "1", + * with the same N(R) as the oldest unresolved SREJ frame. + * + * Because an I or S format frame with the F bit set to "1" can cause checkpoint retransmission, a TNC does not + * send SREJ frames until it receives at least one in-sequence I frame, or it perceives by timeout that the checkpoint + * retransmission will not be initiated at the remote TNC. + * + * With respect to each direction of transmission on the data link, one or more "sent SREJ" exception conditions + * from a TNC to another TNC may be established at a time. A "sent SREJ" exception condition is cleared when + * the requested I frame is received. + * + * The SREJ frame may be repeated when a TNC perceives by timeout that a requested I frame will not be + * received, because either the requested I frame or the SREJ frame was in error or lost. + * + * When appropriate, a TNC receiving one or more SREJ frames initiates retransmission of the individual I + * frames indicated by the N(R) contained in each SREJ frame. After having retransmitted the above frames, new + * I frames are transmitted later if they become available. + * + * When a TNC receives and acts on one or more SREJ commands, each with the P bit set to "0", or an SREJ + * command with the P bit set to "1", or one or more SREJ responses each with the F bit set to "0", it disables any + * action on the next SREJ response frame if that SREJ frame has the F bit set to "1" and has the same N(R) (i.e., + * the same value and the same numbering cycle) as a previously actioned SREJ frame, and if the resultant + * retransmission was made following the transmission of the P bit set to a "1". + * When the SREJ mechanism is used, the receiving station retains correctly-received I frames and delivers + * them to the higher layer in sequence number order. + * + * + * 6.4.4.2. Selective Reject (SREJ) + * + * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current + * receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number + * equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously + * established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable + * and poll bit of the received frame are checked and acted upon, if necessary. + * + * This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can + * consume precious buffer space, especially if the user device has limited memory available and several active + * links are operational. + * + * + * 6.4.4.3. Selective Reject-Reject (SREJ/REJ) + * + * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current + * receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. + * All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is + * missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The + * received state variable and poll bit of the received frame are checked and acted upon. If another frame error + * occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame + * and discards frames received after the second errored frame until the first errored frame is recovered. Then, a + * REJ is issued to recover the second errored frame and all subsequent discarded frames. + * + * X.25: States that SREJ is only response. I'm following that and it simplifies matters. + * + * X.25 2.4.6.6.1 & 2.4.6.6.2 make a distinction between F being 0 or 1 besides copying N(R) into V(A). + * They talk about sending a poll under some conditions. + * We don't do that here. It seems to work reliably so leave well enough alone. + * + *------------------------------------------------------------------------------*/ + +static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int f, int nr) +{ + + switch (S->state) { + + case state_0_disconnected: + + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + // Do nothing. + + // Erratum: The original spec said stay in same state. + // 2006 revision shows state 5 transitioning into 1. I think that is wrong. + // probably a cut-n-paste from state 1 to 5 and that part not updated. + break; + + case state_2_awaiting_release: + + // Erratum: Flow chart says send DM(F=1) for "I, RR, RNR, REJ, SREJ commands" and P=1. + // It is wrong for two reasons. + // If SREJ was a command, the P flag has a different meaning than the other Supervisory commands. + // It means ack reception of frames up thru N(R)-1; it is not a poll to get a response. + + // Based on X.25, I don't think SREJ can be a command. + // It should say, "I, RR, RNR, REJ commands" + + // Erratum: We have a disagreement here between original and 2006 version. + // RR, RNR, REJ, SREJ responses would fall under "all other primitives." + // In the original, we simply ignore it and stay in state 2. + // The 2006 version, page 94, says go into "1 awaiting connection" state. + // That makes no sense to me. + + break; + + case state_3_connected: + + S->peer_receiver_busy = 0; + + // Erratum: Flow chart has "check need for response here." + + // check_need_for_response() does the following: + // - for command & P=1, send RR or RNR. + // - for response & F=1, error A. + + // SREJ can only be a response. We don't want to produce an error when F=1. + + if (is_good_nr(S,nr)) { + + if (f) { + SET_VA(nr); + } + STOP_T1; + START_T3; + select_t1_value(S); + + // Resend I frame with N(S) equal to the N(R) in the SREJ. + // Note: X.25 says info part can contain additional sequence numbers. + // Here we stay with Ax.25 and don't consider the info part. + + cdata_t *txdata = S->txdata_by_ns[nr]; + + if (txdata != NULL) { + + cmdres_t cr = cr_cmd; + int i_frame_ns = nr; + int i_frame_nr = S->vr; + int p = 0; + + packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); + + // dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); + + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + +// my addition +// Erratum: We sent I frame(s) and want to timeout if no ack comes back. +// We also sent N(R) so no need for extra RR at the end only for that. + +// We would sometimes end up in a situation where T1 was stopped on +// both ends and everyone would wait for the other guy to timeout and do something. +// My solution was to Start T1 after every place we send an I frame if not already there. + + STOP_T3; + START_T1; + S->acknowledge_pending = 0; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, nr); + } + // keep same state. + } + else { + nr_error_recovery (S); + // Erratum? Flow chart shows state 1 but that would not be appropriate if modulo is 128. + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + break; + + case state_4_timer_recovery: + + S->peer_receiver_busy = 0; + + // Erratum: Original Flow chart has "check need for response here." + // The 2006 version correctly removed it. + + // check_need_for_response() does the following: + // - for command & P=1, send RR or RNR. + // - for response & F=1, error A. + + // SREJ can only be a response. We don't want to produce an error when F=1. + + + // The flow chart splits into two paths for command/response with a lot of duplication. + // Command path has been omitted because SREJ can only be response. + + STOP_T1; + select_t1_value(S); + + if (is_good_nr(S,nr)) { + + if (f) { // f=1 means ack up thru previous sequence. + // Erratum: 2006 version tests "P". Original has "F." + SET_VA(nr); + } + + if (S->vs == S->va) { // ACKs all caught up. Back to state 3. + + START_T3; + S->rc =0; // My enhancement. See Erratum note in select_t1_value. + enter_new_state (S, state_3_connected, __func__, __LINE__); + + } + else { + +// Erratum: Difference between two AX.25 revisions. + +#if 1 // This is from the original protocol spec. + // Resend I frame with N(S) equal to the N(R) in the SREJ. + + cdata_t *txdata = S->txdata_by_ns[nr]; + + if (txdata != NULL) { + + cmdres_t cr = cr_cmd; + int i_frame_ns = nr; + int i_frame_nr = S->vr; + int p = 0; + + packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); + + // dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); + + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + +// my addition +// Erratum: We sent I frame(s) and want to timeout if no ack comes back. +// We also sent N(R), from V(R), so no need for extra RR at the end only for that. + +// We would sometimes end up in a situation where T1 was stopped on +// both ends and everyone would wait for the other guy to timeout and do something. +// My solution was to Start T1 after every place we send an I frame if not already there. + + STOP_T3; + START_T1; + S->acknowledge_pending = 0; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, nr); + } + +#else // Erratum! This is from the 2006 revision. + // We should resend only the single requested I frame. + // I think there was a cut-n-paste from the REJ flow chart and this particular place did not get changed. + + invoke_retransmission(S); +#endif + } + } + else { + nr_error_recovery (S); + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + } + break; + } + +} /* end srej_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: sabm_e_frame + * + * Purpose: Process SABM or SABME Frame. + * + * Inputs: S - Data Link State Machine. + * + * extended - True for SABME. False for SABM. + * + * p - Poll bit. TODO: What does it mean in this case? + * + * Description: This is a request, from the other end, to establish a connection. + * + * 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command + * + * The SABM command places two Terminal Node Comtrollers (TNC) in the asynchronous balanced mode + * (modulo 8). This a balanced mode of operation in which both devices are treated as equals or peers. + * + * Information fields are not allowed in SABM commands. Any outstanding I frames left when the SABM + * command is issued remain unacknowledged. + * + * The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the + * earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if + * possible. + * + * 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command + * + * The SABME command places two TNCs in the asynchronous balanced mode extended (modulo 128). This + * is a balanced mode of operation in which both devices are treated as equals or peers. + * Information fields are not allowed in SABME commands. Any outstanding I frames left when the SABME + * command is issued remains unacknowledged. + * + * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the + * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. + * + * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. ** (see note below) + * + * + * Note: The KPC-3+, which does not appear to support v2.2, responds with a DM. + * The 2.0 spec, section 2.3.4.3.5, states, "While a DXE is in the disconnected mode, it will respond + * to any command other than a SABM or UI frame with a DM response with the P/F bit set to 1." + * I think it is a bug in the KPC but I can see how someone might implement it that way. + * However, another place says FRMR is sent for any unrecognized frame type. That would seem to take priority. + * + *------------------------------------------------------------------------------*/ + +static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p) +{ + + switch (S->state) { + + case state_0_disconnected: + + // Flow chart has decision: "Able to establish?" + // I think this means, are we willing to accept connection requests? + // We are always willing to accept connections. + // Of course, we wouldn't get this far if local callsigns were not "registered." + + if (extended) { + set_version_2_2 (S); + } + else { + set_version_2_0 (S); + } + + cmdres_t res = cr_res; + int f = p; // I don't understand the purpose of "P" in SABM/SABME + // but we dutifully copy it into "F" for the UA response. + int nopid = 0; // PID is only for I and UI. + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + clear_exception_conditions (S); + + SET_VS(0); + SET_VA(0); + SET_VR(0); + + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], extended ? "v2.2" : "v2.0"); + + // dl connect indication - inform the client app. + int incoming = 1; + server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); + + INIT_T1V_SRT; + + START_T3; + S->rc =0; // My enhancement. See Erratum note in select_t1_value. + enter_new_state (S, state_3_connected, __func__, __LINE__); + break; + + case state_1_awaiting_connection: + + // Don't combine with state 5. They are slightly different. + + if (extended) { // SABME - respond with DM, enter state 5. + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__); + } + else { // SABM - respond with UA. + + // Erratum! 2006 version shows SAMBE twice for state 1. + // First one should be SABM in last page of Figure C4.2 + // Original appears to be correct. + + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + // stay in state 1. + } + break; + + case state_5_awaiting_v22_connection: + + if (extended) { // SABME - respond with UA + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + // stay in state 5 + } + else { // SABM, respond with UA, enter state 1 + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); + } + break; + + case state_2_awaiting_release: + + // Erratum! Flow charts don't list SABME for state 2. + // Probably just want to treat it the same as SABM here. + + { + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited + // stay in state 2. + } + break; + + case state_3_connected: + case state_4_timer_recovery: + + { + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + // State 3 & 4 handling are the same except for this one difference. + if (S->state == state_4_timer_recovery) { + if (extended) { + set_version_2_2 (S); + } + else { + set_version_2_0 (S); + } + } + + clear_exception_conditions (S); + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error F: Data Link reset; i.e. SABM(e) received in state %d.\n", S->stream_id, S->state); + } + if (S->vs != S->va) { + discard_i_queue (S); + // dl connect indication + int incoming = 1; + server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); + } + STOP_T1; + START_T3; + SET_VS(0); + SET_VA(0); + SET_VR(0); + S->rc =0; // My enhancement. See Erratum note in select_t1_value. + enter_new_state (S, state_3_connected, __func__, __LINE__); + } + break; + } + +} /* end sabm_e_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: disc_frame + * + * Purpose: Process DISC command. + * + * Inputs: S - Data Link State Machine. + * p - Poll bit. + * + * Description: 4.3.3.3. Disconnect (DISC) Command + * + * The DISC command terminates a link session between two stations. An information field is not permitted in + * a DISC command frame. + * + * Prior to acting on the DISC frame, the receiving TNC confirms acceptance of the DISC by issuing a UA + * response frame at its earliest opportunity. The TNC sending the DISC enters the disconnected state when it + * receives the UA response. + * + * Any unacknowledged I frames left when this command is acted upon remain unacknowledged. + * + * + * 6.3.4. Link Disconnection + * + * While in the information-transfer state, either TNC may indicate a request to disconnect the link by transmitting + * a DISC command frame and starting timer T1. + * + * After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected + * state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the + * disconnected state. + * + * If a UA or DM response is not correctly received before T1 times out, the DISC frame is sent again and T1 is + * restarted. If this happens N2 times, the TNC enters the disconnected state. + * + *------------------------------------------------------------------------------*/ + +static void disc_frame (ax25_dlsm_t *S, int p) +{ + + switch (S->state) { + + case state_0_disconnected: + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + + { + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + // keep current state, 0, 1, or 5. + break; + + case state_2_awaiting_release: + + { + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited + } + // keep current state, 2. + break; + + case state_3_connected: + case state_4_timer_recovery: + + { + discard_i_queue (S); + + cmdres_t res = cr_res; + int f = p; + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + // dl disconnect *indication* + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + + STOP_T1; + STOP_T3; + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + break; + } + +} /* end disc_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: dm_frame + * + * Purpose: Process DM Response Frame. + * + * Inputs: S - Data Link State Machine. + * f - Final bit. + * + * Description: 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command + * + * The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the + * earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if + * possible. + * + * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the + * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. + * + * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. + * ( I think the KPC-3+ has a bug - it replys with DM - WB2OSZ ) + * + * 4.3.3.5. Disconnected Mode (DM) Response + * + * The disconnected mode response is sent whenever a TNC receives a frame other than a SABM(E) or UI + * frame while in a disconnected mode. The disconnected mode response also indicates that the TNC cannot + * accept a connection at the moment. The DM response does not have an information field. + * Whenever a SABM(E) frame is received and it is determined that a connection is not possible, a DM frame is + * sent. This indicates that the called station cannot accept a connection at that time. + * While a TNC is in the disconnected mode, it responds to any command other than a SABM(E) or UI frame + * with a DM response with the P/F bit set to "1". + * + * 4.3.3.6. Unnumbered Information (UI) Frame + * + * A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when + * in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state. + * + * 6.3.1. AX.25 Link Connection Establishment + * + * If the distant TNC receives a SABM command and cannot enter the indicated state, it sends a DM frame. + * When the originating TNC receives a DM response to its SABM(E) frame, it cancels its T1 timer and does + * not enter the information-transfer state. + * + * 6.3.4. Link Disconnection + * + * After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected + * state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the + * disconnected state. + * + * 6.5. Resetting Procedure + * + * If a DM response is received, the TNC enters the disconnected state and stops timer T1. If timer T1 expires + * before a UA or DM response frame is received, the SABM(E) is retransmitted and timer T1 restarted. If timer T1 + * expires N2 times, the TNC enters the disconnected state. Any previously existing link conditions are cleared. + * Other commands or responses received by the TNC before completion of the reset procedure are discarded. + * + * Erratum: The flow chart shows the same behavior for states 1 and 5. + * For state 5, I think we should treat DM the same as FRMR. + * + *------------------------------------------------------------------------------*/ + + +static void dm_frame (ax25_dlsm_t *S, int f) +{ + switch (S->state) { + + case state_0_disconnected: + // Do nothing. + break; + + case state_1_awaiting_connection: + + if (f == 1) { + discard_i_queue (S); + // dl disconnect *indication* + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + STOP_T1; + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + else { + // keep current state. + } + break; + + case state_2_awaiting_release: + + if (f == 1) { + + // Erratum! Original flow chart, page 91, shows DL-CONNECT confirm. + // It should clearly be DISconnect rather than Connect. + + // 2006 has DISCONNECT *Indication*. + // Should it be indication or confirm? Not sure. + + // dl disconnect *confirm* + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + STOP_T1; + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + else { + // keep current state. + } + break; + + case state_3_connected: + case state_4_timer_recovery: + + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error E: DM received in state %d.\n", S->stream_id, S->state); + } + // dl disconnect *indication* + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + discard_i_queue (S); + STOP_T1; + STOP_T3; + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + break; + + case state_5_awaiting_v22_connection: + +#if 0 + // Erratum: The flow chart says we should do this. + // I'm not saying it is wrong. I just found it necessary to change this + // to work around an apparent bug in a popular hardware TNC. + + if (f == 1) { + discard_i_queue (S); + // dl disconnect *indication* + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + STOP_T1; + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + else { + // keep current state. + } +#else + // Erratum: This is not in original spec. It's copied from the FRMR case. + + // I was expecting FRMR to mean the other end did not understand v2.2. + // Experimentation, with KPC-3+, revealed that we get DM instead. + // One part of the the 2.0 spec sort of indicates this might be intentional. + // But another part more clearly states it should be FRMR. + + // At first I thought it was an error in the protocol spec. + // Later, I tend to believe it was just implemented wrong in the KPC-3+. + + if (f == 1) { + text_color_set(DW_COLOR_INFO); + dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); + + INIT_T1V_SRT; + + // Erratum: page 105. We are in state 5 so I think that means modulo is 128, + // k is probably something > 7, and selective reject is enabled. + // At the end of this we go to state 1. + // It seems to me, that we really want to set version 2.0 in here so we have + // compatible settings. + + set_version_2_0 (S); + + establish_data_link (S); + S->layer_3_initiated = 1; + enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); + } +#endif + break; + } + +} /* end dm_frame */ + + + + +/*------------------------------------------------------------------------------ + * + * Name: UA_frame + * + * Purpose: Process UA Response Frame. + * + * Inputs: S - Data Link State Machine. + * f - Final bit. + * + * Description: 4.3.3.4. Unnumbered Acknowledge (UA) Response + * + * The UA response frame acknowledges the reception and acceptance of a SABM(E) or DISC command + * frame. A received command is not actually processed until the UA response frame is sent. Information fields are + * not permitted in a UA frame. + * + * 4.4.1. TNC Busy Condition + * + * When a TNC is temporarily unable to receive I frames (e.g., when receive buffers are full), it sends a Receive + * Not Ready (RNR) frame. This informs the sending TNC that the receiving TNC cannot handle any more I + * frames at the moment. This receiving TNC clears this condition by the sending a UA, RR, REJ or SABM(E) + * command frame. + * + * 6.2. Poll/Final (P/F) Bit Procedures + * + * The response frame returned by a TNC depends on the previous command received, as described in the + * following paragraphs. + * The next response frame returned by the TNC to a SABM(E) or DISC command with the P bit set to "1" is a + * UA or DM response with the F bit set to "1". + * + * 6.3.1. AX.25 Link Connection Establishment + * + * To connect to a distant TNC, the originating TNC sends a SABM command frame to the distant TNC and + * starts its T1 timer. If the distant TNC exists and accepts the connect request, it responds with a UA response + * frame and resets all of its internal state variables (V(S), V(A) and V(R)). Reception of the UA response frame by + * the originating TNC causes it to cancel the T1 timer and set its internal state variables to "0". + * + * 6.5. Resetting Procedure + * + * A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of + * a FRMR frame from a TNC using an older version of the protocol. + * + *------------------------------------------------------------------------------*/ + +static void ua_frame (ax25_dlsm_t *S, int f) +{ + switch (S->state) { + + case state_0_disconnected: + + // Erratum: flow chart says errors C and D. Neither one really makes sense. + // "Unexpected UA in states 3, 4, or 5." We are in state 0 here. + // "UA received without F=1 when SABM or DISC was sent P=1." + + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state); + } + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + + if (f == 1) { + if (S->layer_3_initiated) { + text_color_set(DW_COLOR_INFO); + // TODO: add via if apppropriate. + dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0"); + // There is a subtle difference here between connect confirm and indication. + // connect *confirm* means "has been made" + // The AGW API distinguishes between incoming (initiated by other station) and + // outgoing (initiated by me) connections. + int incoming = 0; + server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); + } + else if (S->vs != S->va) { +#if 1 + // Erratum: 2006 version has this. + + INIT_T1V_SRT; + + START_T3; // Erratum: Rather pointless because we immediately stop it below. + // In the original flow chart, that is. + // I think there is an error as explained below. + // In my version this is still pointless because we start T3 later. + +#else + // Erratum: Original version has this. + // I think this could be harmful. + // The client app might have been impatient and started sending + // information already. I don't see why we would want to discard it. + + discard_i_queue (S); +#endif + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0"); + + // Erratum: 2006 version says DL-CONNECT *confirm* but original has *indication*. + + // connect *indication* means "has been requested". + // *confirm* seems right because we got a reply from the other side. + + int incoming = 0; + server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); + } + + STOP_T1; +#if 1 // My version. + START_T3; +#else // As shown in flow chart. + STOP_T3; // Erratum? I think this is wrong. + // We are about to enter state 3. When in state 3 either T1 or T3 should be + // running. In state 3, we always see start one / stop the other pairs except where + // we are about to enter a different state. + // Since there is nothing outstanding where we expect a response, T1 would + // not be started. +#endif + SET_VS(0); + SET_VA(0); + SET_VR(0); + select_t1_value (S); + +// Erratum: mdl_negotiate_request does not appear in the SDL flow chart. +// It is mentioned here: +// +// C5.3 Internal Operation of the Machine +// +// The Management Data link State Machine handles the negotiation/notification of +// operational parameters. It uses a single command/response exchange to negotiate the +// final values of negotiable parameters. +// +// The station initiating the AX.25 connection will send an XID command after it receives +// the UA frame. If the other station is using a version of AX.25 earlier than 2.2, it will +// respond with an FRMR of the XID command and the default version 2.0 parameters will +// be used. If the other station is using version 2.2 or better, it will respond with an XID +// response. + + if (S->state == state_5_awaiting_v22_connection) { + mdl_negotiate_request (S); + } + + S->rc =0; // My enhancement. See Erratum note in select_t1_value. + enter_new_state (S, state_3_connected, __func__, __LINE__); + } + else { + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id); + } + // stay in current state, either 1 or 5. + } + break; + + case state_2_awaiting_release: + + // Erratum: 2006 version is missing yes/no labels on this test. + // DL-ERROR Indication does not mention error D. + + if (f == 1) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + STOP_T1; + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + else { + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id); + } + // stay in same state. + } + break; + + case state_3_connected: + case state_4_timer_recovery: + + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state); + } + establish_data_link (S); + S->layer_3_initiated = 0; + + // Erratum? Flow chart goes to state 1. Wouldn't we want this to be state 5 if modulo is 128? + enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); + break; + } + +} /* end ua_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: frmr_frame + * + * Purpose: Process FRMR Response Frame. + * + * Inputs: S - Data Link State Machine. + * + * Description: 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command + * ... + * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the + * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. + * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. + * + * 4.3.3.9. FRMR Response Frame + * + * The FRMR response is removed from the standard for the following reasons: + * a) UI frame transmission was not allowed during FRMR recovery; + * b) During FRMR recovery, the link could not be reestablished by the station that sent the FRMR; + * c) The above functions are better handled by simply resetting the link with a SABM(E) + UA exchange; + * d) An implementation that receives and process FRMRs but does not transmit them is compatible with older + * versions of the standard; and + * e) SDL is simplified and removes the need for one state. + * This version of AX.25 operates with previous versions of AX.25. It does not generate a FRMR Response + * frame, but handles error conditions by resetting the link. + * + * 6.3.2. Parameter Negotiation Phase + * + * Parameter negotiation occurs at any time. It is accomplished by sending the XID command frame and + * receiving the XID response frame. Implementations of AX.25 prior to version 2.2 respond to an XID command + * frame with a FRMR response frame. The TNC receiving the FRMR uses a default set of parameters compatible + * with previous versions of AX.25. + * + * 6.5. Resetting Procedure + * + * The link resetting procedure initializes both directions of data flow after a unrecoverable error has occurred. + * This resetting procedure is used only in the information-transfer state of an AX.25 link. + * A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of + * a FRMR frame from a TNC using an older version of the protocol. + * + *------------------------------------------------------------------------------*/ + + +static void frmr_frame (ax25_dlsm_t *S) +{ + switch (S->state) { + + case state_0_disconnected: + case state_1_awaiting_connection: + case state_2_awaiting_release: + // Ignore it. Keep current state. + break; + + case state_3_connected: + case state_4_timer_recovery: + + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error K: FRMR not expected in state %d.\n", S->stream_id, S->state); + } + + set_version_2_0 (S); // Erratum: FRMR can only be sent by v2.0. + // Need to force v2.0. Should be added to flow chart. + establish_data_link (S); + S->layer_3_initiated = 0; + enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); + break; + + case state_5_awaiting_v22_connection: + + text_color_set(DW_COLOR_INFO); + dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); + + INIT_T1V_SRT; + + set_version_2_0 (S); // Erratum: Need to force v2.0. This is not in flow chart. + + establish_data_link (S); + S->layer_3_initiated = 1; // Erratum? I don't understand the difference here. + // State 1 clears it. State 5 sets it. Why not the same? + + enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); + break; + } + +// part of state machine for the XID negotiation. + +// I would not expect this to happen. +// To get here: +// We sent SABME. (not SABM) +// Other side responded with UA so it understands v2.2. +// We sent XID command which puts us int the negotiating state. +// Presumably this is in response to the XID and not something else. + +// Anyhow, we will fall back to v2.0 parameters. + + switch (S->mdl_state) { + + case mdl_state_0_ready: + break; + + case mdl_state_1_negotiating: + + set_version_2_0 (S); + S->mdl_state = mdl_state_0_ready; + break; + } + +} /* end frmr_frame */ + + +/*------------------------------------------------------------------------------ + * + * Name: ui_frame + * + * Purpose: Process XID frame for negotiating protocol parameters. + * + * Inputs: S - Data Link State Machine. + * + * cr - Is it command or response? + * + * pf - Poll/Final bit. + * + * Description: 4.3.3.6. Unnumbered Information (UI) Frame + * + * The Unnumbered Information frame contains PID and information fields and passes information along the + * link outside the normal information controls. This allows information fields to be exchanged on the link, bypassing + * flow control. + * + * Because these frames cannot be acknowledged, if one such frame is obliterated, it cannot be recovered. + * A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when + * in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state. + * + * Reality: The data link state machine was an add-on after APRS and client APIs were already done. + * UI frames don't go thru here for normal operation. + * The only reason we have this function is so that we can send a response to a UI command with P=1. + * + *------------------------------------------------------------------------------*/ + +static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf) +{ + if (cr == cr_cmd && pf == 1) { + + switch (S->state) { + + case state_0_disconnected: + case state_1_awaiting_connection: + case state_2_awaiting_release: + case state_5_awaiting_v22_connection: + { + cmdres_t r = cr_res; // DM response with F taken from P. + int nopid = 0; // PID applies only for I and UI frames. + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, pf, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + break; + + case state_3_connected: + case state_4_timer_recovery: + + enquiry_response (S, frame_type_U_UI, pf); + break; + } + } + +} /* end ui_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: xid_frame + * + * Purpose: Process XID frame for negotiating protocol parameters. + * + * Inputs: S - Data Link State Machine. + * + * cr - Is it command or response? + * + * pf - Poll/Final bit. + * + * Description: 4.3.3.7 Exchange Identification (XID) Frame + * + * The Exchange Identification frame causes the addressed station to identify itself, and + * to provide its characteristics to the sending station. An information field is optional within + * the XID frame. A station receiving an XID command returns an XID response unless a UA + * response to a mode setting command is awaiting transmission, or a FRMR condition + * exists. + * + * The XID frame complies with ISO 8885. Only those fields applicable to AX.25 are + * described. All other fields are set to an appropriate value. This implementation is + * compatible with any implementation which follows ISO 8885. Only the general-purpose + * XID information field identifier is required in this version of AX.25. + * + * The information field consists of zero or more information elements. The information + * elements start with a Format Identifier (FI) octet. The second octet is the Group Identifier + * (GI). The third and forth octets form the Group Length (GL). The rest of the information + * field contains parameter fields. + * + * The FI takes the value 82 hex for the general-purpose XID information. The GI takes + * the value 80 hex for the parameter-negotiation identifier. The GL indicates the length of + * the associated parameter field. This length is expressed as a two-octet binary number + * representing the length of the associated parameter field in octets. The high-order bits of + * length value are in the first of the two octets. A group length of zero indicates the lack of + * an associated parameter field and that all parameters assume their default values. The GL + * does not include its own length or the length of the GI. + * + * The parameter field contains a series of Parameter Identifier (PI), Parameter Length + * (PL), and Parameter Value (PV) set structures, in that order. Each PI identifies a + * parameter and is one octet in length. Each PL indicates the length of the associated PV in + * octets, and is one octet in length. Each PV contains the parameter value and is PL octets + * in length. The PL does not include its own length or the length of its associated PI. A PL + * value of zero indicates that the associated PV is absent; the parameter assumes the + * default value. A PI/PL/PV set may be omitted if it is not required to convey information, or + * if present values for the parameter are to be used. The PI/PL/PV fields are placed into the + * information field of the XID frame in ascending order. There is only one entry for each + * PI/PL/PV field used. A parameter field containing an unrecognized PI is ignored. An + * omitted parameter field assumes the currently negotiated value. + * + *------------------------------------------------------------------------------*/ + + +static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len) +{ + struct xid_param_s param; + char desc[120]; + int ok; + unsigned char xinfo[40]; + int xlen; + cmdres_t res = cr_res; + int f = 1; + int nopid = 0; + packet_t pp; + + + switch (S->mdl_state) { + + case mdl_state_0_ready: + + if (cr == cr_cmd) { + + if (pf == 1) { + +// Take parameters sent by other station. +// Generally we take minimum of what he wants and what I can do. +// Adjust my working configuration and send it back. + + ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc)); + + if (ok) { + negotiation_response (S, ¶m); + + xlen = xid_encode (¶m, xinfo); + + pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_XID, f, nopid, xinfo, xlen); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error MDL-A: XID command without P=1.\n", S->stream_id); + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error MDL-B: Unexpected XID response.\n", S->stream_id); + } + break; + + case mdl_state_1_negotiating: + + if (cr == cr_res) { + + if (pf == 1) { + +// Got expected response. Copy into my working parameters. + + ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc)); + + if (ok) { + complete_negotiation (S, ¶m); + } + + S->mdl_state = mdl_state_0_ready; + STOP_TM201; + +//#define TEST_TEST 1 + +#if TEST_TEST // Send TEST command to see how it responds. + // We currently have no Client API for sending this or reporting result. + { + char info[80] = "The quick brown fox jumps over the lazy dog."; + cmdres_t cmd = cr_cmd; + int p = 0; + int nopid = 0; + packet_t pp; + + pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_TEST, p, nopid, (unsigned char *)info, (int)strlen(info)); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } +#endif + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error MDL-D: XID response without F=1.\n", S->stream_id); + } + } + else { + // Not expecting to receive a command when I sent one. + // Flow chart says requeue but I just drop it. + // The other end can retry and maybe I will be back to ready state by then. + } + break; + } + +} /* end xid_frame */ + + +/*------------------------------------------------------------------------------ + * + * Name: test_frame + * + * Purpose: Process TEST command for checking link. + * + * Inputs: S - Data Link State Machine. + * + * cr - Is it command or response? + * + * pf - Poll/Final bit. + * + * Description: 4.3.3.8. Test (TEST) Frame + * + * The Test command causes the addressed station to respond with the TEST response at the first respond + * opportunity; this performs a basic test of the data-link control. An information field is optional with the TEST + * command. If present, the received information field is returned, if possible, by the addressed station, with the + * TEST response. The TEST command has no effect on the mode or sequence variables maintained by the station. + * + * A FRMR condition may be established if the received TEST command information field exceeds the maximum + * defined storage capability of the station. If a FRMR response is not returned for this condition, a TEST response + * without an information field is returned. + * + * The station considers the data-link layer test terminated on receipt of the TEST response, or when a time-out + * period has expired. The results of the TEST command/response exchange are made available for interrogation + * by a higher layer. + * + * Erratum: TEST frame is not mentioned in the SDL flow charts. + * Don't know how P/F is supposed to be used. + * Here, the response sends back what was received in the command. + * + *------------------------------------------------------------------------------*/ + + +static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len) +{ + cmdres_t res = cr_res; + int f = pf; + int nopid = 0; + packet_t pp; + + if (cr == cr_cmd) { + pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_TEST, f, nopid, info_ptr, info_len); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + } + +} /* end test_frame */ + + + +/*------------------------------------------------------------------------------ + * + * Name: dl_timer_expiry + * + * Purpose: Some timer expired. Figure out which one and act accordingly. + * + * Inputs: none. + * + *------------------------------------------------------------------------------*/ + +void dl_timer_expiry (void) +{ + ax25_dlsm_t *p; + double now = dtime_now(); + +// Examine all of the data link state machines. +// Process only those where timer: +// - is running. +// - is not paused. +// - expiration time has arrived or passed. + + for (p = list_head; p != NULL; p = p->next) { + if (p->t1_exp != 0 && p->t1_paused_at == 0 && p->t1_exp <= now) { + p->t1_exp = 0; + p->t1_paused_at = 0; + p->t1_had_expired = 1; + t1_expiry (p); + } + } + + for (p = list_head; p != NULL; p = p->next) { + if (p->t3_exp != 0 && p->t3_exp <= now) { + p->t3_exp = 0; + t3_expiry (p); + } + } + + for (p = list_head; p != NULL; p = p->next) { + if (p->tm201_exp != 0 && p->tm201_paused_at == 0 && p->tm201_exp <= now) { + p->tm201_exp = 0; + p->tm201_paused_at = 0; + tm201_expiry (p); + } + } + +} /* end dl_timer_expiry */ + + +/*------------------------------------------------------------------------------ + * + * Name: t1_expiry + * + * Purpose: Handle T1 timer expiration for outstanding I frame or P-bit. + * + * Inputs: S - Data Link State Machine. + * + * Description: 4.4.5.1. T1 Timer Recovery + * + * If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I + * frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently + * does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion + * of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described + * in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent + * frame(s), or by the link being reset. + * + * 6.7.1.1. Acknowledgment Timer T1 + * + * T1, the Acknowledgement Timer, ensures that a TNC does not wait indefinitely for a response to a frame it + * sends. This timer cannot be expressed in absolute time; the time required to send frames varies greatly with the + * signaling rate used at Layer 1. T1 should take at least twice the amount of time it would take to send maximum + * length frame to the distant TNC and get the proper response frame back from the distant TNC. This allows time + * for the distant TNC to do some processing before responding. + * If Layer 2 repeaters are used, the value of T1 should be adjusted according to the number of repeaters through + * which the frame is being transferred. + * + *------------------------------------------------------------------------------*/ + +// Make timer start, stop, expiry a different color to stand out. + +#define DW_COLOR_DEBUG_TIMER DW_COLOR_ERROR + + +static void t1_expiry (ax25_dlsm_t *S) +{ + + if (s_debug_timers) { + double now = dtime_now(); + + text_color_set(DW_COLOR_DEBUG_TIMER); + dw_printf ("t1_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc); + } + + switch (S->state) { + + case state_0_disconnected: + + // Ignore it. + break; + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + + // MAXV22 hack. + // If we already sent the maximum number of SABME, fall back to v2.0 SABM. + + if (S->state == state_5_awaiting_v22_connection && S->rc == g_misc_config_p->maxv22) { + set_version_2_0 (S); + enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); + } + + if (S->rc == S->n2_retry) { + discard_i_queue(S); + text_color_set(DW_COLOR_INFO); + dw_printf ("Failed to connect to %s after %d tries.\n", S->addrs[PEERCALL], S->n2_retry); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1); + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + else { + cmdres_t cmd = cr_cmd; + int p = 1; + int nopid = 0; + + packet_t pp; + + S->rc++; + if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // Keep statistics. + + pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->state == state_5_awaiting_v22_connection) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + select_t1_value(S); + START_T1; + // Keep same state. + } + break; + + case state_2_awaiting_release: + + if (S->rc == S->n2_retry) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + else { + cmdres_t cmd = cr_cmd; + int p = 1; + int nopid = 0; + + packet_t pp; + + S->rc++; + if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; + + pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + select_t1_value(S); + START_T1; + // stay in same state + } + break; + + case state_3_connected: + + S->rc = 1; + transmit_enquiry (S); + enter_new_state (S, state_4_timer_recovery, __func__, __LINE__); + break; + + case state_4_timer_recovery: + + if (S->rc == S->n2_retry) { + +// Erratum: 2006 version, page 103, is missing yes/no labels on decision blocks. + + if (S->va != S->vr) { + + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error I: N2 timeouts: unacknowledged data.\n", S->stream_id); + } + } + else if (S->peer_receiver_busy) { + + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error U: N2 timeouts: extended peer busy condition.\n", S->stream_id); + } + } + else { + + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error T: N2 timeouts: no response to enquiry.\n", S->stream_id); + } + } + + // Erratum: Flow chart says DL-DISCONNECT "request" in both original and 2006 revision. + // That is clearly wrong because a "request" would come FROM the higher level protocol/client app. + // I think it should be "indication" rather than "confirm" because the peer condition is unknown. + + // dl disconnect *indication* + text_color_set(DW_COLOR_INFO); + dw_printf ("Stream %d: Disconnected from %s due to timeouts.\n", S->stream_id, S->addrs[PEERCALL]); + server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1); + + discard_i_queue (S); + + cmdres_t cr = cr_res; // DM can only be response. + int f = 0; // Erratum: Assuming F=0 because it is not response to P=1 + int nopid = 0; + + packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, f, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + enter_new_state (S, state_0_disconnected, __func__, __LINE__); + } + else { + S->rc++; + if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // gather statistics. + + transmit_enquiry (S); + // Keep same state. + } + break; + } + +} /* end t1_expiry */ + + +/*------------------------------------------------------------------------------ + * + * Name: t3_expiry + * + * Purpose: Handle T3 timer expiration. + * + * Inputs: S - Data Link State Machine. + * + * Description: TODO: still don't understand this. + * + * 4.4.5.2. Timer T3 Recovery + * + * Timer T3 ensures that the link is still functional during periods of low information transfer. When T1 is not + * running (no outstanding I frames), T3 periodically causes the TNC to poll the other TNC of a link. When T3 + * times out, an RR or RNR frame is transmitted as a command with the P bit set, and then T1 is started. When a + * response to this command is received, T1 is stopped and T3 is started. If T1 expires before a response is + * received, then the waiting acknowledgement procedure (Section 6.4.11) is executed. + * + * 6.7.1.3. Inactive Link Timer T3 + * + * T3, the Inactive Link Timer, maintains link integrity whenever T1 is not running. It is recommended that + * whenever there are no outstanding unacknowledged I frames or P-bit frames (during the information-transfer + * state), an RR or RNR frame with the P bit set to "1" be sent every T3 time units to query the status of the other + * TNC. The period of T3 is locally defined, and depends greatly on Layer 1 operation. T3 should be greater than + * T1; it may be very large on channels of high integrity. + * + *------------------------------------------------------------------------------*/ + +static void t3_expiry (ax25_dlsm_t *S) +{ + + if (s_debug_timers) { + double now = dtime_now(); + + text_color_set(DW_COLOR_DEBUG_TIMER); + dw_printf ("t3_expiry (), [now=%.3f]\n", now - S->start_time); + } + + switch (S->state) { + + case state_0_disconnected: + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + case state_2_awaiting_release: + case state_4_timer_recovery: + + break; + + case state_3_connected: + +// Erratum: Original sets RC to 0, 2006 revision sets RC to 1 which makes more sense. + + S->rc = 1; + transmit_enquiry (S); + enter_new_state (S, state_4_timer_recovery, __func__, __LINE__); + break; + } + +} /* end t3_expiry */ + + + +/*------------------------------------------------------------------------------ + * + * Name: tm201_expiry + * + * Purpose: Handle TM201 timer expiration. + * + * Inputs: S - Data Link State Machine. + * + * Description: This is used when waiting for a response to an XID command. + * + *------------------------------------------------------------------------------*/ + + +static void tm201_expiry (ax25_dlsm_t *S) +{ + + struct xid_param_s param; + unsigned char xinfo[40]; + int xlen; + cmdres_t cmd = cr_cmd; + int p = 1; + int nopid = 0; + packet_t pp; + + + if (s_debug_timers) { + double now = dtime_now(); + + text_color_set(DW_COLOR_DEBUG_TIMER); + dw_printf ("tm201_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc); + } + + switch (S->mdl_state) { + + case mdl_state_0_ready: + +// Timer shouldn't be running when in this state. + + break; + + case mdl_state_1_negotiating: + + S->mdl_rc++; + if (S->mdl_rc > S->n2_retry) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error MDL-C: Management retry limit exceeded.\n", S->stream_id); + S->mdl_state = mdl_state_0_ready; + } + else { + // No response. Ask again. + + initiate_negotiation (S, ¶m); + + xlen = xid_encode (¶m, xinfo); + + pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + START_TM201; + } + break; + } + +} /* end tm201_expiry */ + + +//################################################################################### +//################################################################################### +// +// Subroutines from protocol spec, pages 106 - 109 +// +//################################################################################### +//################################################################################### + +// FIXME: continue review here. + + +/*------------------------------------------------------------------------------ + * + * Name: nr_error_recovery + * + * Purpose: Try to recover after receiving an expected N(r) value. + * + *------------------------------------------------------------------------------*/ + +static void nr_error_recovery (ax25_dlsm_t *S) +{ + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error J: N(r) sequence error.\n", S->stream_id); + } + establish_data_link (S); + S->layer_3_initiated = 0; + +} /* end nr_error_recovery */ + + +/*------------------------------------------------------------------------------ + * + * Name: establish_data_link + * (Combined with "establish extended data link") + * + * Purpose: Send SABM or SABME to other station. + * + * Inputs: S-> + * addrs destination, source, and optional digi addresses. + * num_addr Number of addresses. Should be 2 .. 10. + * modulo Determines if we send SABME or SABM. + * + * Description: Original spec had two different functions that differed + * only by sending SABM or SABME. Here they are combined into one. + * + *------------------------------------------------------------------------------*/ + +static void establish_data_link (ax25_dlsm_t *S) +{ + cmdres_t cmd = cr_cmd; + int p = 1; + packet_t pp; + int nopid = 0; + + clear_exception_conditions (S); + +// Erratum: We have an off-by-one error here. +// Flow chart shows setting RC to 0 and we end up sending SAMB(e) 11 times when N2 (RETRY) is 10. +// It should be 1 rather than 0. + + S->rc = 1; + pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->modulo == 128) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + STOP_T3; + START_T1; + +} /* end establish_data_link */ + + + +/*------------------------------------------------------------------------------ + * + * Name: clear_exception_conditions + * + *------------------------------------------------------------------------------*/ + +static void clear_exception_conditions (ax25_dlsm_t *S) +{ + S->peer_receiver_busy = 0; + S->reject_exception = 0; + S->own_receiver_busy = 0; + S->acknowledge_pending = 0; + +// My enhancement. If we are establishing a new connection, we should discard any saved out of sequence incoming I frames. + + int n; + + for (n = 0; n < 128; n++) { + if (S->rxdata_by_ns[n] != NULL) { + cdata_delete (S->rxdata_by_ns[n]); + S->rxdata_by_ns[n] = NULL; + } + } + +// We retain the transmit I frame queue so we can continue after establishing a new connection. + +} /* end clear_exception_conditions */ + + +/*------------------------------------------------------------------------------ + * + * Name: transmit_enquiry, page 106 + * + * Purpose: This is called only when a timer expires. + * + * T1: We sent I frames and timed out waiting for the ack. + * Poke the other end to determine how much it got so far + * so we know where to continue. + * + * T3: Not activity for substantial amount of time. + * Poke the other end to see if it is still there. + * + * + * Observation: This is the only place where we send RR command with P=1. + * + * Sequence of events: + * + * We send some I frames to the other guy. + * There are outstanding sent I frames for which we did not receive ACK. + * + * Timer 1 expires when we are in state 3: send RR/RNR command P=1 (here). Enter state 4. + * Timer 1 expires when we are in state 4: same until max retry count is exceeded. + * + * Other guy gets RR/RNR command P=1. + * Same action for either state 3 or 4. + * Whether he has outstanding un-ack'ed sent I frames is irrelevent. + * He calls "enquiry response" which sends RR/RNR response F=1. + * (Read about detour 1 below and in enquiry_response.) + * + * I get back RR/RNR response F=1. Still in state 4. + * Of course, N(R) gets copied into V(A). + * Now here is the interesting part. + * If the ACKs are caught up, i.e. V(A) == V(S), stop T1 and enter state 3. + * Otherwise, "invoke retransmission" to resend everything after N(R). + * + * + * Detour 1: You were probably thinking, "Suppose SREJ is enabled and the other guy + * had a record of the SREJ frames sent which were not answered by filled in + * I frames. Why not send the SREJ again instead of backing up and resending + * stuff which already got there OK?" + * + * The code to handle incoming SREJ in state 4 is there but stop T1 is in the + * wrong place as mentioned above. + * + *------------------------------------------------------------------------------*/ + +static void transmit_enquiry (ax25_dlsm_t *S) +{ + int p = 1; + int nr = S->vr; + cmdres_t cmd = cr_cmd; + packet_t pp; + + if (s_debug_retry) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\n****** TRANSMIT ENQUIRY ******\n\n"); + } + +// This is the ONLY place that we send RR/RNR *command* with P=1. +// Everywhere else should be response. +// I don't think we ever use RR/RNR command P=0 but need to check on that. + + pp = ax25_s_frame (S->addrs, S->num_addr, cmd, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, p); + + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->acknowledge_pending = 0; + START_T1; + +} /* end transmit_enquiry */ + + + +/*------------------------------------------------------------------------------ + * + * Name: enquiry_response + * + * Inputs: frame_type - Type of frame received or frame_not_AX25 for LM seize confirm. + * I think that this function is being called from too many + * different contexts where it really needs to react differently. + * So pass in more information about where we are coming from. + * + * F - Always specified as parameter in the references. + * + * Description: This is called for: + * - UI command with P=1 then F=1. + * - LM seize confirm with ack pending then F=0. (TODO: not clear on this yet.) + * TODO: I think we want to ensure that this function is called ONLY + * for RR/RNR/I command with P=1. LM Seize confirm can do its own thing and + * not get involved in this complication. + * - check_need_for_response(), command & P=1, then F=1 + * - RR/RNR/REJ command & P=1, then F=1 + * + * In all cases, we see that F has been specified, usually 1 because it is + * a response to a command with P=1. + * Specifying F would imply response when the flow chart says RR/RNR command. + * The documentation says: + * + * 6.2. Poll/Final (P/F) Bit Procedures + * + * The next response frame returned to an I frame with the P bit set to "1", received during the information + * transfer state, is an RR, RNR or REJ response with the F bit set to "1". + * + * The next response frame returned to a supervisory command frame with the P bit set to "1", received during + * the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1". + * + * Erattum! The flow chart says RR/RNR *command* but I'm confident should be response. + * + * Erratum: Ax.25 spec has nothing here for SREJ. See X.25 2.4.6.11 for explanation. + * + *------------------------------------------------------------------------------*/ + +static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f) +{ + cmdres_t cr = cr_res; // Response, not command as seen in flow chart. + int nr = S->vr; + packet_t pp; + + + if (s_debug_retry) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\n****** ENQUIRY RESPONSE F=%d ******\n\n", f); + } + +#if 1 // Detour 1 + + // My addition, Based on X.25 2.4.6.11. + // Only for RR, RNR, I. + // See sequence of events in transmit_enquiry comments. + + if (f == 1 && (frame_type == frame_type_S_RR || frame_type == frame_type_S_RNR || frame_type == frame_type_I)) { + + if (S->own_receiver_busy) { + +// I'm busy. + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->acknowledge_pending = 0; + } + + else if (S->srej_enabled) { + +// SREJ is enabled. This is based on X.25 2.4.6.11. + + if (S->modulo != 128) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR: enquiry response should not be sending SREJ for modulo 8.\n"); + } + +// Suppose we received I frames with N(S) of 0, 3, 7. +// V(R) is still 1 because 0 is the last one received with contiguous N(S) values. +// 3 and 7 have been saved into S->rxdata_by_ns. +// We have outstanding requests to resend 1, 2, 4, 5, 6. +// Either those requests or the replies got lost. +// The other end timed out and asked us what is happening by sending RR/RNR command P=1. + +// First see if we have any out of sequence frames in the receive buffer. + + int last; + last = AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__); + while (last != S->vr && S->rxdata_by_ns[last] == NULL) { + last = AX25MODULO(last - 1, S->modulo, __FILE__, __func__, __LINE__); + } + + if (last != S->vr) { + +// Ask for missing frames to be sent again. X.25 2.4.6.11 b) & 2.3.5.2.2 + + int resend[128]; + int count = 0; + int j; + int allow_f1 = 1; + + j = S->vr; + while (j != last) { + if (S->rxdata_by_ns[j] == NULL) { + resend[count++] = j; + } + j = AX25MODULO(j + 1, S->modulo, __FILE__, __func__, __LINE__); + } + + send_srej_frames (S, resend, count, allow_f1); + } + else { + +// Not waiting for fill in of missing frames. X.25 2.4.6.11 c) + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->acknowledge_pending = 0; + } + + } else { + +// SREJ not enabled. +// One might get the idea that it would make sense send REJ here if the reject exception is set. +// However, I can't seem to find that buried in X.25 2.4.5.9. +// And when we look at what happens when RR response, F=1 is received in state 4, it is +// effectively REJ when N(R) is not the same as V(S). + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->acknowledge_pending = 0; + } + + } // end of RR,RNR,I cmd with P=1 + + else { + +// For cases other than (RR, RNR, I) command, P=1. + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->acknowledge_pending = 0; + } + +#else + +// As found in AX.25 spec. +// Erratum: This is woefully inadequate when SREJ is enabled. +// Erratum: Flow chart says RR/RNR command but I'm confident it should be response. + + pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->acknowledge_pending = 0; + +# endif + +} /* end enquiry_response */ + + + + +/*------------------------------------------------------------------------------ + * + * Name: invoke_retransmission + * + * Inputs: nr_input - Resend starting with this. + * Continue will all up to and including current V(S) value. + * + * Description: Resend one or more frames that have already been sent. + * + * This is probably the result of getting REJ asking for a resend. + * + *------------------------------------------------------------------------------*/ + +static void invoke_retransmission (ax25_dlsm_t *S, int nr_input) +{ + +// Original flow chart showed saving V(S) into temp variable x, +// using V(S) as loop control variable, and finally restoring it +// to original value before returning. +// Here we just a local variable instead of messing with it. +// This should be equivalent but safer. + + int local_vs; + int sent_count = 0; + + if (S->txdata_by_ns[nr_input] == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal Error, Can't resend starting with N(S) = %d. It is not available. %s %s %d\n", nr_input, __FILE__, __func__, __LINE__); + return; + } + + + local_vs = nr_input; + do { + + if (S->txdata_by_ns[local_vs] != NULL) { + + cmdres_t cr = cr_cmd; + int ns = local_vs; + int nr = S->vr; + int p = 0; + + if (s_debug_misc) { + text_color_set(DW_COLOR_INFO); + dw_printf ("invoke_retransmission(): state=%d, Resending N(S) = %d, probably as result of REJ N(R) = %d\n", S->state, ns, nr_input); + } + + packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, + S->txdata_by_ns[ns]->pid, (unsigned char *)(S->txdata_by_ns[ns]->data), S->txdata_by_ns[ns]->len); + + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + // Keep it around in case we need to send again. + + sent_count++; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal Error, state=%d, need to retransmit N(S) = %d for REJ but it is not available. %s %s %d\n", S->state, local_vs, __FILE__, __func__, __LINE__); + } + local_vs = AX25MODULO(local_vs + 1, S->modulo, __FILE__, __func__, __LINE__); + + } while (local_vs != S->vs); + + if (sent_count == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal Error, Nothing to retransmit. N(R)=%d, %s %s %d\n", nr_input, __FILE__, __func__, __LINE__); + } + +} /* end invoke_retransmission */ + + + +/*------------------------------------------------------------------------------ + * + * Name: check_i_frame_ackd + * + * Purpose: + * + * Inputs: nr - N(R) from I or S frame, acknowledging receipt thru N(R)-1. + * i.e. The next one expected by the peer is N(R). + * + * Outputs: S->va - updated from nr. + * + * Description: TBD... Document when this is used. + * + *------------------------------------------------------------------------------*/ + +static void check_i_frame_ackd (ax25_dlsm_t *S, int nr) +{ + if (S->peer_receiver_busy) { + SET_VA(nr); + + // Erratum? This looks odd to me. + // It doesn't seem right that we would have T3 and T1 running at the same time. + // Normally we stop one when starting the other. + // Should this be Stop T3 instead? + + START_T3; + if ( ! IS_T1_RUNNING) { + START_T1; + } + } + else if (nr == S->vs) { + SET_VA(nr); + STOP_T1; + START_T3; + select_t1_value (S); + } + else if (nr != S->va) { + + if (s_debug_misc) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("check_i_frame_ackd n(r)=%d, v(a)=%d, Set v(a) to new value %d\n", nr, S->va, nr); + } + + SET_VA(nr); + START_T1; // Erratum? Flow chart says "restart" rather than "start." + // Is this intentional, what is the difference? + } + +} /* check_i_frame_ackd */ + + + +/*------------------------------------------------------------------------------ + * + * Name: check_need_for_response + * + * Inputs: frame_type - frame_type_S_RR, etc. + * + * cr - Is it a command or response? + * + * pf - P/F from the frame. + * + * Description: This is called for RR, RNR, and REJ frames. + * If it is a command with P=1, we reply with RR or RNR with F=1. + * + *------------------------------------------------------------------------------*/ + +static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf) +{ + if (cr == cr_cmd && pf == 1) { + int f = 1; + enquiry_response (S, frame_type, f); + } + else if (cr == cr_res && pf == 1) { + if (s_debug_protocol_errors) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Stream %d: AX.25 Protocol Error A: F=1 received but P=1 not outstanding.\n", S->stream_id); + } + } + +} /* end check_need_for_response */ + + + +/*------------------------------------------------------------------------------ + * + * Name: ui_check + * + * Description: I don't think we need this because UI frames are processed + * without going thru the data link state machine. + * + *------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------ + * + * Name: select_t1_value + * + * Purpose: Dynamically adjust the T1 timeout value, commonly a fixed time known as FRACK. + * + * Inputs: S->rc Retry counter. + * + * S->srt Smoothed roundtrip time in seconds. + * + * S->t1_remaining_when_last_stopped + * Seconds left on T1 when it is stopped. + * + * Outputs: S->srt New smoothed roundtrip time. + * + * S->t1v How long to wait for an acknowlegement before resending. + * Value used when starting timer T1, in seconds. + * Here it is dynamically adjusted. + * + * Description: How long should we wait for an ACK before sending again or giving up? + * some implementations have a fixed length time. This is usually the FRACK parameter, + * typically 3 seconds (D710A) or 4 seconds (KPC-3+). + * + * This should be increased for each digipeater in the path. + * Here it is dynamically adjusted by taking the average time it takes to get a response + * and then we double it. + * + * Rambling: It seems like a good idea to adapt to channel conditions, such as digipeater delays, + * but it is fraught with peril if you are not careful. + * + * For example, if we accept an incoming connection and only receive some I frames and + * send no I frames, T1 never gets started. In my earlier attempt, 't1_remaining_when_last_stopped' + * had the initial value of 0 lacking any reason to set it differently. The calculation here + * then kept pushing t1v up up up. After receiving 20 I frames and sending none, + * t1v was over 300 seconds!!! + * + * We need some way to indicate that 't1_remaining_when_last_stopped' is not valid and + * not to use it. Rather than adding a new variable, it is set to a negative value + * initially to mean it has not been set yet. That solves one problem. + * + * T1 is paused whenever the channel is busy, either transmitting or receiving, + * so the measured time could turn out to be a tiny fraction of a second, much less than + * the frame transmission time. + * If this gets too low, an unusually long random delay, before the sender's transmission, + * could exceed this. I put in a lower limit for t1v, currently 1 second. + * + * What happens if we get multiple timeouts because we don't get a response? + * For example, when we try to connect to a station which is not there, a KPC-3+ will give + * up and report failure after 10 tries x 4 sec = 40 seconds. + * + * The algorithm in the AX.25 protocol spec shows increasing timeout values. + * It might seem like a good idea but either it was not thought out very well + * or I am not understanding it. If it is doubled each time, it gets awful large + * very quickly. If we try to connect to a station which is not there, + * we want to know within a minute, not an hour later. + * + * Keeping with the spirit of increasing the time but keeping it sane, + * I increase the time linearly by a fraction of a second. + * + *------------------------------------------------------------------------------*/ + + +static void select_t1_value (ax25_dlsm_t *S) +{ + float old_srt = S->srt; + + +// Erratum: I don't think this test for RC == 0 is valid. +// We would need to set RC to 0 whenever we enter state 3 and we don't do that. +// I think a more appropriate test would be to check if we are in state 3. +// When things are going smoothly, it makes sense to fine tune timeout based on smoothed round trip time. +// When in some other state, we might want to slowly increase the time to minimize collisions. +// Maybe the solution is to set RC=0 when we enter state 3. + +// TODO: come back and revisit this. + + if (S->rc == 0) { + + if (S->t1_remaining_when_last_stopped >= 0) { // Negative means invalid, don't use it. + + // This is an IIR low pass filter. + // Algebraically equivalent to version in AX.25 protocol spec but I think the + // original intent is clearer by having 1/8 appear only once. + + S->srt = 7./8. * S->srt + 1./8. * ( S->t1v - S->t1_remaining_when_last_stopped ); + } + + // We pause T1 when the channel is busy. + // This includes both receiving someone else and us transmitting. + // This can result in the round trip time going down to almost nothing. + // My enhancement is to prevent srt from going below one second so + // t1v should never be less than 2 seconds. + // When t1v was allowed to go down to 1, we got occastional timeouts + // even under ideal conditions, probably due to random CSMA delay time. + + if (S->srt < 1) { + + S->srt = 1; + + // Add another 2 seconds for each digipeater in path. + + if (S->num_addr > 2) { + S->srt += 2 * (S->num_addr - 2); + } + } + + S->t1v = S->srt * 2; + } + else { + + if (S->t1_had_expired) { + + // This goes up exponentially if implemented as documented! + // For example, if we were trying to connect to a station which is not there, we + // would retry after 3, the 8, 16, 32, ... and not time out for over an hour. + // That's ridiculous. Let's try increasing it by a quarter second each time. + // We now give up after about a minute. + + // NO! S->t1v = powf(2, S->rc+1) * S->srt; + + S->t1v = S->rc * 0.25 + S->srt * 2; + } + } + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, new t1v = %.3f\n", + S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v); + } + + + if (S->t1v < 0.99 || S->t1v > 30) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR? Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, Extreme new t1v = %.3f\n", + S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v); + } + +} /* end select_t1_value */ + + +/*------------------------------------------------------------------------------ + * + * Name: set_version_2_0 + * + * Erratum: Flow chart refers to T2 which doesn't appear anywhere else. + * + *------------------------------------------------------------------------------*/ + +static void set_version_2_0 (ax25_dlsm_t *S) +{ + S->srej_enabled = 0; + S->modulo = 8; + S->n1_paclen = g_misc_config_p->paclen; + S->k_maxframe = g_misc_config_p->maxframe_basic; + S->n2_retry = g_misc_config_p->retry; + +} /* end set_version_2_0 */ + + +/*------------------------------------------------------------------------------ + * + * Name: set_version_2_2 + * + *------------------------------------------------------------------------------*/ + +static void set_version_2_2 (ax25_dlsm_t *S) +{ + S->srej_enabled = 1; + //S->srej_enabled = 0; // temporarily disable for testing of REJ only with modulo 128 + S->modulo = 128; + S->n1_paclen = g_misc_config_p->paclen; + S->k_maxframe = g_misc_config_p->maxframe_extended; + S->n2_retry = g_misc_config_p->retry; + +} /* end set_version_2_2 */ + + + + +/*------------------------------------------------------------------------------ + * + * Name: is_good_nr + * + * Purpose: Evaluate condition "V(a) <= N(r) <= V(s)" which appears in flow charts + * for incoming I, RR, RNR, REJ, and SREJ frames. + * + * Inputs: S - state machine. Contains V(a) and V(s). + * + * nr - N(r) found in the incoming frame. + * + * Description: This determines whether the Received Sequence Number, N(R), is in + * the expected range for normal processing or if we have an error + * condition that needs recovery. + * + * This gets tricky due to the wrap around of sequence numbers. + * + * 4.2.4.4. Received Sequence Number N(R) + * + * The received sequence number exists in both I and S frames. + * Prior to sending an I or S frame, this variable is updated to equal that + * of the received state variable, thus implicitly acknowledging the proper + * reception of all frames up to and including N(R)-1. + * + * Pattern noticed: Anytime we have "is_good_nr" returning true, we should always + * - set V(A) from N(R) or + * - call "check_i_frame_acked" which does the same and some timer stuff. + * + *------------------------------------------------------------------------------*/ + + +static int is_good_nr (ax25_dlsm_t *S, int nr) +{ + int adjusted_va, adjusted_nr, adjusted_vs; + int result; + +/* Adjust all values relative to V(a) before comparing so we won't have wrap around. */ + +#define adjust_by_va(x) (AX25MODULO((x) - S->va, S->modulo, __FILE__, __func__, __LINE__)) + + adjusted_va = adjust_by_va(S->va); // A clever compiler would know it is zero. + adjusted_nr = adjust_by_va(nr); + adjusted_vs = adjust_by_va(S->vs); + + result = adjusted_va <= adjusted_nr && adjusted_nr <= adjusted_vs; + + if (s_debug_misc) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("is_good_nr, V(a) %d <= nr %d <= V(s) %d, returns %d\n", S->va, nr, S->vs, result); + } + + return (result); + +} /* end is_good_nr */ + + + +/*------------------------------------------------------------------------------ + * + * Name: i_frame_pop_off_queue + * + * Purpose: Transmit an I frame if we have one in the queue and conditions are right. + * This appears two slightly different ways in the flow charts: + * "frame pop off queue" + * "I frame pops off queue" + * + * Inputs: i_frame_queue - Remove items from here. + * peer_receiver_busy - If other end not busy. + * V(s) - and we haven't reached window size. + * V(a) + * k + * + * Outputs: v(s) is incremented for each processed. + * ack_pending = 0 + * + *------------------------------------------------------------------------------*/ + +static void i_frame_pop_off_queue (ax25_dlsm_t *S) +{ + + + if (s_debug_misc) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("i_frame_pop_off_queue () state=%d\n", S->state); + } + +// TODO: Were we expecting something in the queue? +// or is empty an expected situation? + + if (S->i_frame_queue == NULL) { + + if (s_debug_misc) { + // TODO: add different switch for I frame queue. + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("i_frame_pop_off_queue () queue is empty get out, line %d\n", __LINE__); + } + + // I Frame queue is empty. + // Nothing to see here, folks. Move along. + return; + } + + switch (S->state) { + + case state_1_awaiting_connection: + case state_5_awaiting_v22_connection: + + if (s_debug_misc) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("i_frame_pop_off_queue () line %d\n", __LINE__); + } + + // This seems to say remove the I Frame from the queue and discard it if "layer 3 initiated" is set. + + // For the case of removing it from the queue and putting it back in we just leave it there. + + // Erratum? The flow chart seems to be backwards. + // It would seem like we want to keep it if we are further along in the connection process. + // I don't understand the intention here, and can't make a compelling argument on why it + // is backwards, so it is implemented as documented. + + if (S->layer_3_initiated) { + cdata_t *txdata; + + if (s_debug_misc) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("i_frame_pop_off_queue () discarding due to L3 init. line %d\n", __LINE__); + } + txdata = S->i_frame_queue; // Remove from head of list. + S->i_frame_queue = txdata->next; + cdata_delete (txdata); + } + break; + + case state_3_connected: + case state_4_timer_recovery: + + if (s_debug_misc) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("i_frame_pop_off_queue () state %d, line %d\n", S->state, __LINE__); + } + + while ( ( ! S->peer_receiver_busy ) && + S->i_frame_queue != NULL && + S->vs != AX25MODULO(S->va + S->k_maxframe, S->modulo, __FILE__, __func__, __LINE__) ) { + + cdata_t *txdata; + + txdata = S->i_frame_queue; // Remove from head of list. + S->i_frame_queue = txdata->next; + txdata->next = NULL; + + cmdres_t cr = cr_cmd; + int ns = S->vs; + int nr = S->vr; + int p = 0; + + if (s_debug_misc || s_debug_radio) { + //dw_printf ("i_frame_pop_off_queue () ns=%d, queue for transmit \"", ns); + //ax25_safe_print (txdata->data, txdata->len, 1); + //dw_printf ("\"\n"); + } + packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); + + if (s_debug_misc) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); + } + + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + // Stash in sent array in case it gets lost and needs to be sent again. + + if (S->txdata_by_ns[ns] != NULL) { + cdata_delete (S->txdata_by_ns[ns]); + } + S->txdata_by_ns[ns] = txdata; + + SET_VS(AX25MODULO(S->vs + 1, S->modulo, __FILE__, __func__, __LINE__)); // increment sequence of last sent. + + S->acknowledge_pending = 0; + +// Erratum: I think we always want to restart T1 when an I frame is sent. +// Otherwise we could time out too soon. +#if 1 + STOP_T3; + START_T1; +#else + if ( ! IS_T1_RUNNING) { + STOP_T3; + START_T1; + } +#endif + } + break; + + case state_0_disconnected: + case state_2_awaiting_release: + + // Do nothing. + break; + } + +} /* end i_frame_pop_off_queue */ + + + + +/*------------------------------------------------------------------------------ + * + * Name: discard_i_queue + * + * Purpose: Discard any data chunks waiting to be sent. + * + *------------------------------------------------------------------------------*/ + + +static void discard_i_queue (ax25_dlsm_t *S) +{ + cdata_t *t; + + while (S->i_frame_queue != NULL) { + + t = S->i_frame_queue; + S->i_frame_queue = S->i_frame_queue->next; + cdata_delete (t); + } + +} /* end discard_i_queue */ + + + +/*------------------------------------------------------------------------------ + * + * Name: enter_new_state + * + * Purpose: Switch to new state. + * + * Description: Use a function, rather than setting variable directly, so we have + * one common point for debug output and possibly other things we + * might want to do at a state change. + * + *------------------------------------------------------------------------------*/ + +// TODO: requeuing... + +static void enter_new_state (ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line) +{ + + if (s_debug_variables) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\n"); + dw_printf (">>> NEW STATE = %d, previously %d, called from %s %d <<<\n", new_state, S->state, from_func, from_line); + dw_printf ("\n"); + } + + assert (new_state >= 0 && new_state <= 5); + + + if (( new_state == state_3_connected || new_state == state_4_timer_recovery) && + S->state != state_3_connected && S->state != state_4_timer_recovery ) { + + ptt_set (OCTYPE_CON, S->chan, 1); // Turn on connected indicator if configured. + } + else if (( new_state != state_3_connected && new_state != state_4_timer_recovery) && + ( S->state == state_3_connected || S->state == state_4_timer_recovery ) ) { + + ptt_set (OCTYPE_CON, S->chan, 0); // Turn off connected indicator if configured. + // Ideally we should look at any other link state machines + // for this channel and leave the indicator on if any + // are connected. I'm not that worried about it. + } + + S->state = new_state; + +} /* end enter_new_state */ + + + +/*------------------------------------------------------------------------------ + * + * Name: mdl_negotiate_request + * + * Purpose: After receiving UA, in response to SABME, this starts up the XID exchange. + * + * Description: Send XID command. + * Start timer TM201 so we can retry if timeout waiting for response. + * Enter MDL negotiating state. + * + *------------------------------------------------------------------------------*/ + +static void mdl_negotiate_request (ax25_dlsm_t *S) +{ + struct xid_param_s param; + unsigned char xinfo[40]; + int xlen; + cmdres_t cmd = cr_cmd; + int p = 1; + int nopid = 0; + packet_t pp; + + + switch (S->mdl_state) { + + case mdl_state_0_ready: + + initiate_negotiation (S, ¶m); + + xlen = xid_encode (¶m, xinfo); + + pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen); + lm_data_request (S->chan, TQ_PRIO_1_LO, pp); + + S->mdl_rc = 0; + START_TM201; + S->mdl_state = mdl_state_1_negotiating; + + break; + + case mdl_state_1_negotiating: + + // SDL says "requeue" but I don't understand how it would be useful or how to do it. + break; + } + +} /* end mdl_negotiate_request */ + + +/*------------------------------------------------------------------------------ + * + * Name: initiate_negotiation + * + * Purpose: Used when preparing the XID *command*. + * + * Description: Prepare set of parameters to request from the other station. + * + *------------------------------------------------------------------------------*/ + +static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param) +{ + param->full_duplex = 0; + param->rej = S->srej_enabled ? selective_reject : implicit_reject; + param->modulo = S->modulo; + param->i_field_length_rx = S->n1_paclen; // Hmmmm. Should we ask for what the user + // specified for PACLEN or offer the maximum + // that we can handle, AX25_N1_PACLEN_MAX? + param->window_size_rx = S->k_maxframe; + param->ack_timer = (int)(g_misc_config_p->frack * 1000); + param->retries = S->n2_retry; +} + + +/*------------------------------------------------------------------------------ + * + * Name: negotiation_response + * + * Purpose: Used when receiving the XID command and preparing the XID response. + * + * Description: Take what other station has asked for and reduce if we have lesser capabilities. + * For example if other end wants 8k information part we reduce it to 2k. + * Ack time and retries are the opposite, we take the maximum. + * + * Question: If the other send leaves anything undefined should we leave it + * undefined or fill in what we would like before sending it back? + * + * The original version of the protocol spec left this open. + * The 2006 revision, in red, says we should fill in defaults for anything + * not specified. This makes sense. We send back a complete set of parameters + * so both ends should agree. + * + *------------------------------------------------------------------------------*/ + +static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param) +{ + +// Full duplex would not be that difficult. +// Just ignore the Carrier Detect when transmitting. +// But we haven't done that yet. + + param->full_duplex = 0; + +// Other end might want 8. +// Seems unlikely. If it implements XID it should have modulo 128. + + if (param->modulo == modulo_unknown) { + param->modulo = 8; // Not specified. Set default. + } + else { + param->modulo = MIN(param->modulo, 128); + } + +// We can do REJ or SREJ but won't combine them. +// Erratum: 2006 version, section, 4.3.3.7 says default selective reject - reject. +// We can't do that. + + if (param->rej == unknown_reject) { + param->rej = (param->modulo == 128) ? selective_reject : implicit_reject; // not specified, set default + } + else { + param->rej = MIN(param->rej, selective_reject); + } + +// We can currently do up to 2k. +// Take minimum of that and what other guy asks for. + + if (param->i_field_length_rx == G_UNKNOWN) { + param->i_field_length_rx = 256; // Not specified, take default. + } + else { + param->i_field_length_rx = MIN(param->i_field_length_rx, AX25_N1_PACLEN_MAX); + } + +// In theory extended mode can have window size of 127 but +// I'm limiting it to 63 for the reason mentioned in the SREJ logic. + + if (param->window_size_rx == G_UNKNOWN) { + param->window_size_rx = (param->modulo == 128) ? 32 : 4; // not specified, set default. + } + else { + if (param->modulo == 128) + param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_EXTENDED_MAX); + else + param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_BASIC_MAX); + } + +// Erratum: Unclear. Is the Acknowledgement Timer before or after compensating for digipeaters +// in the path? e.g. Typically TNCs use the FRACK parameter for this and it often defaults to 3. +// However, the actual timeout value might be something like FRACK*(2*m+1) where m is the number of +// digipeaters in the path. I'm assuming this is the FRACK value and any additional time, for +// digipeaters will be added in locally at each end on top of this exchanged value. + + if (param->ack_timer == G_UNKNOWN) { + param->ack_timer = 3000; // not specified, set default. + } + else { + param->ack_timer = MAX(param->ack_timer, (int)(g_misc_config_p->frack * 1000)); + } + + if (param->retries == G_UNKNOWN) { + param->retries = 10; // not specified, set default. + } + else { + param->retries = MAX(param->retries, S->n2_retry); + } + +// IMPORTANT: Take values we have agreed upon and put into my running configuration. + + complete_negotiation(S, param); +} + + +/*------------------------------------------------------------------------------ + * + * Name: complete_negotiation + * + * Purpose: Used when preparing or receiving the XID *response*. + * + * Description: Take set of parameters which we have agreed upon and apply + * to the running configuration. + * + * TODO: Should do some checking here in case other station + * sends something crazy. + * + *------------------------------------------------------------------------------*/ + +static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param) +{ + if (param->rej != unknown_reject) { + S->srej_enabled = param->rej >= selective_reject; + } + + if (param->modulo != modulo_unknown) { + // Disaster if aren't agreeing on this. + S->modulo = param->modulo; + } + + if (param->i_field_length_rx != G_UNKNOWN) { + S->n1_paclen = param->i_field_length_rx; + } + + if (param->window_size_rx != G_UNKNOWN) { + S->k_maxframe = param->window_size_rx; + } + + if (param->ack_timer != G_UNKNOWN) { + S->t1v = param->ack_timer * 0.001; + } + + if (param->retries != G_UNKNOWN) { + S->n2_retry = param->retries; + } +} + + + + + +//################################################################################### +//################################################################################### +// +// Timers. +// +// Start. +// Stop. +// Pause (when channel busy) & resume. +// Is it running? +// Did it expire before being stopped? +// When will next one expire? +// +//################################################################################### +//################################################################################### + + +static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + double now = dtime_now(); + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG_TIMER); + dw_printf ("Start T1 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line); + } + + S->t1_exp = now + S->t1v; + if (S->radio_channel_busy) { + S->t1_paused_at = now; + } + else { + S->t1_paused_at = 0; + } + S->t1_had_expired = 0; + +} /* end start_t1 */ + + +static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + double now = dtime_now(); + + RESUME_T1; // adjust expire time if paused. + + if (S->t1_exp == 0.0) { + // Was already stopped. + } + else { + S->t1_remaining_when_last_stopped = S->t1_exp - now; + if (S->t1_remaining_when_last_stopped < 0) S->t1_remaining_when_last_stopped = 0; + } + +// Normally this would be at the top but we don't know time remaining at that point. + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG_TIMER); + if (S->t1_exp == 0.0) { + dw_printf ("Stop T1. Wasn't running, [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line); + } + else { + dw_printf ("Stop T1, %.3f remaining, [now=%.3f] from %s %d\n", S->t1_remaining_when_last_stopped, now - S->start_time, from_func, from_line); + } + } + + S->t1_exp = 0.0; // now stopped. + S->t1_had_expired = 0; // remember that it did not expire. + +} /* end stop_t1 */ + + +static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + int result = S->t1_exp != 0.0; + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("is_t1_running? returns %d\n", result); + } + + return (result); + +} /* end is_t1_running */ + + +static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + + if (S->t1_exp == 0.0) { + // Stopped so there is nothing to do. + } + else if (S->t1_paused_at == 0.0) { + // Running and not paused. + + double now = dtime_now(); + + S->t1_paused_at = now; + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Paused T1 with %.3f still remaining, [now=%.3f] from %s %d\n", S->t1_exp - now, now - S->start_time, from_func, from_line); + } + } + else { + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("T1 error: Didn't expect pause when already paused.\n"); + } + } + +} /* end pause_t1 */ + + +static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + if (S->t1_exp == 0.0) { + // Stopped so there is nothing to do. + } + else if (S->t1_paused_at == 0.0) { + // Running but not paused. + } + else { + double now = dtime_now(); + double paused_for_sec = now - S->t1_paused_at; + + S->t1_exp += paused_for_sec; + S->t1_paused_at = 0.0; + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Resumed T1 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->t1_exp - now, now - S->start_time); + } + } + +} /* end resume_t1 */ + + + + +// T3 is a lot simpler. +// Here we are talking about minutes of inactivity with the peer +// rather than expecting a response within seconds where timing is more critical. +// We don't need to capture remaining time when stopped. +// I don't think there is a need to pause it due to the large time frame. + + +static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + double now = dtime_now(); + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG_TIMER); + dw_printf ("Start T3 for %.3f sec, [now=%.3f] from %s %d\n", T3_DEFAULT, now - S->start_time, from_func, from_line); + } + + S->t3_exp = now + T3_DEFAULT; +} + +static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + if (s_debug_timers) { + double now = dtime_now(); + + text_color_set(DW_COLOR_DEBUG_TIMER); + if (S->t3_exp == 0.0) { + dw_printf ("Stop T3. Wasn't running.\n"); + } + else { + dw_printf ("Stop T3, %.3f remaining, [now=%.3f] from %s %d\n", S->t3_exp - now, now - S->start_time, from_func, from_line); + } + } + S->t3_exp = 0.0; +} + + + +// TM201 is similar to T1. +// It needs to be paused whent the channel is busy. +// Simpler because we don't need to keep track of time remaining when stopped. + + + +static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + double now = dtime_now(); + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG_TIMER); + dw_printf ("Start TM201 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line); + } + + S->tm201_exp = now + S->t1v; + if (S->radio_channel_busy) { + S->tm201_paused_at = now; + } + else { + S->tm201_paused_at = 0; + } + +} /* end start_tm201 */ + + +static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + double now = dtime_now(); + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG_TIMER); + dw_printf ("Stop TM201. [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line); + } + + S->tm201_exp = 0.0; // now stopped. + +} /* end stop_tm201 */ + + + +static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + + if (S->tm201_exp == 0.0) { + // Stopped so there is nothing to do. + } + else if (S->tm201_paused_at == 0.0) { + // Running and not paused. + + double now = dtime_now(); + + S->tm201_paused_at = now; + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Paused TM201 with %.3f still remaining, [now=%.3f] from %s %d\n", S->tm201_exp - now, now - S->start_time, from_func, from_line); + } + } + else { + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("TM201 error: Didn't expect pause when already paused.\n"); + } + } + +} /* end pause_tm201 */ + + +static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) +{ + if (S->tm201_exp == 0.0) { + // Stopped so there is nothing to do. + } + else if (S->tm201_paused_at == 0.0) { + // Running but not paused. + } + else { + double now = dtime_now(); + double paused_for_sec = now - S->tm201_paused_at; + + S->tm201_exp += paused_for_sec; + S->tm201_paused_at = 0.0; + + if (s_debug_timers) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Resumed TM201 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->tm201_exp - now, now - S->start_time); + } + } + +} /* end resume_tm201 */ + + + + + +double ax25_link_get_next_timer_expiry (void) +{ + double tnext = 0; + ax25_dlsm_t *p; + + for (p = list_head; p != NULL; p = p->next) { + + // Consider if running and not paused. + + if (p->t1_exp != 0 && p->t1_paused_at == 0) { + if (tnext == 0) { + tnext = p->t1_exp; + } + else if (p->t1_exp < tnext) { + tnext = p->t1_exp; + } + } + + if (p->t3_exp != 0) { + if (tnext == 0) { + tnext = p->t3_exp; + } + else if (p->t3_exp < tnext) { + tnext = p->t3_exp; + } + } + + if (p->tm201_exp != 0 && p->tm201_paused_at == 0) { + if (tnext == 0) { + tnext = p->tm201_exp; + } + else if (p->tm201_exp < tnext) { + tnext = p->tm201_exp; + } + } + + } + + if (s_debug_timers > 1) { + text_color_set(DW_COLOR_DEBUG); + if (tnext == 0.0) { + dw_printf ("ax25_link_get_next_timer_expiry returns none.\n"); + } + else { + dw_printf ("ax25_link_get_next_timer_expiry returns %.3f sec from now.\n", + tnext - dtime_now()); + } + } + + return (tnext); + +} /* end ax25_link_get_next_timer_expiry */ + + +/* end ax25_link.c */ diff --git a/ax25_link.h b/ax25_link.h new file mode 100644 index 00000000..060b6ab4 --- /dev/null +++ b/ax25_link.h @@ -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 */ \ No newline at end of file diff --git a/ax25_pad.c b/ax25_pad.c index 37a9508e..37bc3166 100644 --- a/ax25_pad.c +++ b/ax25_pad.c @@ -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) @@ -146,16 +163,13 @@ #define AX25_PAD_C /* this will affect behavior of ax25_pad.h */ +#include "direwolf.h" #include #include #include #include #include -#ifndef _POSIX_C_SOURCE - -#define _POSIX_C_SOURCE 1 -#endif #include "regex.h" @@ -163,9 +177,9 @@ char *strtok_r(char *str, const char *delim, char **saveptr); #endif -#include "direwolf.h" -#include "ax25_pad.h" + #include "textcolor.h" +#include "ax25_pad.h" #include "fcs_calc.h" /* @@ -235,7 +249,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 @@ -315,9 +335,18 @@ void ax25_delete (packet_t this_p) * Purpose: Parse a frame in human-readable monitoring format and change * to internal representation. * - * Input: monitor - "TNC-2" format of a monitored packet. i.e. + * Input: monitor - "TNC-2" monitor format for packet. i.e. * source>dest[,repeater1,repeater2,...]:information * + * The information part can have non-printable characters + * in the form of <0xff>. This will be converted to single + * bytes. e.g. <0x0d> is carriage return. + * In version 1.4H we will allow nul characters which means + * we have to maintain a length rather than using strlen(). + * I maintain that it violates the spec but want to handle it + * because it does happen and we want to preserve it when + * acting as an IGate rather than corrupting it. + * * strict - True to enforce rules for packets sent over the air. * False to be more lenient for packets from IGate server. * @@ -349,17 +378,11 @@ packet_t ax25_from_text (char *monitor, int strict) char *pa; char *saveptr; /* Used with strtok_r because strtok is not thread safe. */ - static int first_time = 1; - static regex_t unhex_re; - int e; - char emsg[100]; -#define MAXMATCH 1 - regmatch_t match[MAXMATCH]; - int keep_going; - char temp[512]; int ssid_temp, heard_temp; char atemp[AX25_MAX_ADDR_LEN]; + char info_part[AX25_MAX_INFO_LEN+1]; + int info_len; packet_t this_p = ax25_new (); @@ -372,58 +395,15 @@ packet_t ax25_from_text (char *monitor, int strict) /* Is it possible to have a nul character (zero byte) in the */ /* information field of an AX.25 frame? */ - /* Yes, but it would be difficult in the from-text case. */ + /* At this point, we have a normal C string. */ + /* It is possible that will convert <0x00> to a nul character later. */ + /* There we need to maintain a separate length and not use normal C string functions. */ strlcpy (stuff, monitor, sizeof(stuff)); -/* - * Translate hexadecimal values like <0xff> to non-printing characters. - * MIC-E message type uses 5 different non-printing characters. - */ - - if (first_time) - { - e = regcomp (&unhex_re, "<0x[0-9a-fA-F][0-9a-fA-F]>", 0); - if (e) { - regerror (e, &unhex_re, emsg, sizeof(emsg)); - text_color_set(DW_COLOR_ERROR); - dw_printf ("%s:%d: %s\n", __FILE__, __LINE__, emsg); - } - - first_time = 0; - } - -#if 0 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("BEFORE: %s\n", stuff); - ax25_safe_print (stuff, -1, 0); - dw_printf ("\n"); -#endif - keep_going = 1; - while (keep_going) { - if (regexec (&unhex_re, stuff, MAXMATCH, match, 0) == 0) { - int n; - char *p; - - stuff[match[0].rm_so + 5] = '\0'; - n = strtol (stuff + match[0].rm_so + 3, &p, 16); - stuff[match[0].rm_so] = n; - strlcpy (temp, stuff + match[0].rm_eo, sizeof(temp)); - strlcpy (stuff + match[0].rm_so + 1, temp, sizeof(stuff)-match[0].rm_so-1); - } - else { - keep_going = 0; - } - } -#if 0 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("AFTER: %s\n", stuff); - ax25_safe_print (stuff, -1, 0); - dw_printf ("\n"); -#endif /* - * Initialize the packet with two addresses and control/pid + * Initialize the packet structure with two addresses and control/pid * for APRS. */ memset (this_p->frame_data + AX25_DESTINATION*7, ' ' << 1, 6); @@ -433,7 +413,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); @@ -453,12 +433,6 @@ packet_t ax25_from_text (char *monitor, int strict) *pinfo = '\0'; pinfo++; - if (strlen(pinfo) > AX25_MAX_INFO_LEN) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Warning: Information part truncated to %d characters.\n", AX25_MAX_INFO_LEN); - pinfo[AX25_MAX_INFO_LEN] = '\0'; - } - /* * Separate the addresses. * Note that source and destination order is swappped. @@ -515,7 +489,6 @@ packet_t ax25_from_text (char *monitor, int strict) */ while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) { - //char *last; int k; k = this_p->num_addr; @@ -541,11 +514,61 @@ packet_t ax25_from_text (char *monitor, int strict) } } + +/* + * Finally, process the information part. + * + * Translate hexadecimal values like <0xff> to single bytes. + * MIC-E format uses 5 different non-printing characters. + * We might want to manually generate UTF-8 characters such as degree. + */ + +//#define DEBUG14H 1 + +#if DEBUG14H + text_color_set(DW_COLOR_DEBUG); + dw_printf ("BEFORE: %s\nSAFE: ", pinfo); + ax25_safe_print (pinfo, -1, 0); + dw_printf ("\n"); +#endif + + info_len = 0; + while (*pinfo != '\0' && info_len < AX25_MAX_INFO_LEN) { + + if (strlen(pinfo) >= 6 && + pinfo[0] == '<' && + pinfo[1] == '0' && + pinfo[2] == 'x' && + isxdigit(pinfo[3]) && + isxdigit(pinfo[4]) && + pinfo[5] == '>') { + + char *p; + + info_part[info_len] = strtol (pinfo + 3, &p, 16); + info_len++; + pinfo += 6; + } + else { + info_part[info_len] = *pinfo; + info_len++; + pinfo++; + } + } + info_part[info_len] = '\0'; + +#if DEBUG14H + text_color_set(DW_COLOR_DEBUG); + dw_printf ("AFTER: %s\nSAFE: ", info_part); + ax25_safe_print (info_part, info_len, 0); + dw_printf ("\n"); +#endif + /* * Append the info part. */ - strlcpy ((char*)(this_p->frame_data+this_p->frame_len), pinfo, sizeof(this_p->frame_data)-this_p->frame_len); - this_p->frame_len += strlen(pinfo); + memcpy ((char*)(this_p->frame_data+this_p->frame_len), info_part, info_len); + this_p->frame_len += info_len; return (this_p); } @@ -891,7 +914,10 @@ packet_t ax25_unwrap_third_party (packet_t from_pp) (void) ax25_get_info (from_pp, &info_p); - result_pp = ax25_from_text((char *)info_p + 1, 0); + // Want strict because addresses should conform to AX.25 here. + // That's not the case for something from an Internet Server. + + result_pp = ax25_from_text((char *)info_p + 1, 1); return (result_pp); } @@ -1513,15 +1539,48 @@ 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 * * Purpose: Obtain Information part of current packet. * - * Inputs: None. + * Inputs: this_p - Packet object pointer. * - * Outputs: paddr - Starting address is returned here. + * Outputs: paddr - Starting address of information part is returned here. * * Assumption: ax25_from_text or ax25_from_frame was called first. * @@ -1562,6 +1621,56 @@ int ax25_get_info (packet_t this_p, unsigned char **paddr) *paddr = info_ptr; return (info_len); + +} /* end ax25_get_info */ + + +/*------------------------------------------------------------------------------ + * + * Name: ax25_cut_at_crlf + * + * Purpose: Truncate the information part at the first CR or LF. + * This is used for the RF>IS IGate function. + * CR/LF is used as record separator so we must remove it + * before packaging up packet to sending to server. + * + * Inputs: this_p - Packet object pointer. + * + * Outputs: Packet is modified in place. + * + * Returns: Number of characters removed from the end. + * 0 if not changed. + * + * Assumption: ax25_from_text or ax25_from_frame was called first. + * + *------------------------------------------------------------------------------*/ + +int ax25_cut_at_crlf (packet_t this_p) +{ + unsigned char *info_ptr; + int info_len; + int j; + + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + + info_len = ax25_get_info (this_p, &info_ptr); + + // Can't use strchr because there is potential of nul character. + + for (j = 0; j < info_len; j++) { + + if (info_ptr[j] == '\r' || info_ptr[j] == '\n') { + + int chop = info_len - j; + + this_p->frame_len -= chop; + return (chop); + } + } + + return (0); } @@ -1674,6 +1783,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; +} + + + + /*------------------------------------------------------------------ * @@ -1745,6 +1873,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 @@ -1789,12 +1975,11 @@ 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 - * 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? * * pf - P/F - Poll/Final or -1 if not applicable * @@ -1807,17 +1992,20 @@ 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, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns) +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); strlcpy (desc, "????", DESC_SIZ); + *cr = cr_11; *pf = -1; *nr = -1; *ns = -1; @@ -1827,16 +2015,65 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * strlcpy (desc, "Not AX.25", DESC_SIZ); return (frame_not_AX25); } - if (modulo == modulo_128) { + +/* + * 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); } + int dst_c = this_p->frame_data[AX25_DESTINATION * 7 + 6] & SSID_H_MASK; + int src_c = this_p->frame_data[AX25_SOURCE * 7 + 6] & SSID_H_MASK; + + char cr_text[8]; + char pf_text[8]; + + if (dst_c) { + if (src_c) { *cr = cr_11; strcpy(cr_text,"cc=11"); strcpy(pf_text,"p/f"); } + else { *cr = cr_cmd; strcpy(cr_text,"cmd"); strcpy(pf_text,"p"); } + } + else { + if (src_c) { *cr = cr_res; strcpy(cr_text,"res"); strcpy(pf_text,"f"); } + else { *cr = cr_00; strcpy(cr_text,"cc=00"); strcpy(pf_text,"p/f"); } + } if ((c & 1) == 0) { // Information rrr p sss 0 or sssssss 0 rrrrrrr p - if (modulo == modulo_128) { + if (this_p->modulo == modulo_128) { *ns = (c >> 1) & 0x7f; *pf = c2 & 1; *nr = (c2 >> 1) & 0x7f; @@ -1846,14 +2083,16 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * *pf = (c >> 4) & 1; *nr = (c >> 5) & 7; } - snprintf (desc, DESC_SIZ, "I frame, n(s)= %d, n(r)=%d, p=%d", *ns, *nr, *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) { // Supervisory rrr p/f ss 0 1 or 0000 ss 0 1 rrrrrrr p/f - if (modulo == modulo_128) { + if (this_p->modulo == modulo_128) { *pf = c2 & 1; *nr = (c2 >> 1) & 0x7f; } @@ -1861,12 +2100,13 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * *pf = (c >> 4) & 1; *nr = (c >> 5) & 7; } - + + switch ((c >> 2) & 3) { - case 0: snprintf (desc, DESC_SIZ, "S frame RR, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_RR); break; - case 1: snprintf (desc, DESC_SIZ, "S frame RNR, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_RNR); break; - case 2: snprintf (desc, DESC_SIZ, "S frame REJ, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_REJ); break; - case 3: snprintf (desc, DESC_SIZ, "S frame SREJ, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_SREJ); break; + case 0: snprintf (desc, DESC_SIZ, "RR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RR); break; + case 1: snprintf (desc, DESC_SIZ, "RNR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RNR); break; + case 2: snprintf (desc, DESC_SIZ, "REJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_REJ); break; + case 3: snprintf (desc, DESC_SIZ, "SREJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_SREJ); break; } } else { @@ -1877,16 +2117,16 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * switch (c & 0xef) { - case 0x6f: snprintf (desc, DESC_SIZ, "U frame SABME, p=%d", *pf); return (frame_type_U_SABME); break; - case 0x2f: snprintf (desc, DESC_SIZ, "U frame SABM, p=%d", *pf); return (frame_type_U_SABM); break; - case 0x43: snprintf (desc, DESC_SIZ, "U frame DISC, p=%d", *pf); return (frame_type_U_DISC); break; - case 0x0f: snprintf (desc, DESC_SIZ, "U frame DM, f=%d", *pf); return (frame_type_U_DM); break; - case 0x63: snprintf (desc, DESC_SIZ, "U frame UA, f=%d", *pf); return (frame_type_U_UA); break; - case 0x87: snprintf (desc, DESC_SIZ, "U frame FRMR, f=%d", *pf); return (frame_type_U_FRMR); break; - case 0x03: snprintf (desc, DESC_SIZ, "U frame UI, pf=%d", *pf); return (frame_type_U_UI); break; - case 0xaf: snprintf (desc, DESC_SIZ, "U frame XID, pf=%d", *pf); return (frame_type_U_XID); break; - case 0xe3: snprintf (desc, DESC_SIZ, "U frame TEST, pf=%d", *pf); return (frame_type_U_TEST); break; - default: snprintf (desc, DESC_SIZ, "U frame ???"); return (frame_type_U); break; + case 0x6f: snprintf (desc, DESC_SIZ, "SABME %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABME); break; + case 0x2f: snprintf (desc, DESC_SIZ, "SABM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABM); break; + case 0x43: snprintf (desc, DESC_SIZ, "DISC %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DISC); break; + case 0x0f: snprintf (desc, DESC_SIZ, "DM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DM); break; + case 0x63: snprintf (desc, DESC_SIZ, "UA %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UA); break; + case 0x87: snprintf (desc, DESC_SIZ, "FRMR %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_FRMR); break; + case 0x03: snprintf (desc, DESC_SIZ, "UI %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UI); break; + case 0xaf: snprintf (desc, DESC_SIZ, "XID %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_XID); break; + case 0xe3: snprintf (desc, DESC_SIZ, "TEST %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_TEST); break; + default: snprintf (desc, DESC_SIZ, "U other???"); return (frame_type_U); break; } } @@ -1897,6 +2137,8 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * } /* end ax25_frame_type */ + + /*------------------------------------------------------------------ * * Function: ax25_hex_dump @@ -1935,6 +2177,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. @@ -2083,10 +2326,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); @@ -2095,6 +2340,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 @@ -2115,6 +2395,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)]); } @@ -2126,10 +2408,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 */ } @@ -2159,6 +2450,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)]); } @@ -2296,7 +2589,7 @@ unsigned short ax25_m_m_crc (packet_t pp) * * Inputs: pstr - Pointer to string. * - * len - Maximum length if not -1. + * len - Number of bytes. If < 0 we use strlen(). * * ascii_only - Restrict output to only ASCII. * Normally we allow UTF-8. @@ -2347,7 +2640,7 @@ void ax25_safe_print (char *pstr, int len, int ascii_only) if (len > MAXSAFE) len = MAXSAFE; - while (len > 0 && *pstr != '\0') + while (len > 0) { ch = *((unsigned char *)pstr); @@ -2437,7 +2730,11 @@ int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]) snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space); } - else if (alevel.mark == -2 && alevel.space == -2) { /* DTMF */ + else if (alevel.mark == -1 && alevel.space == -1) { /* PSK - single number. */ + + snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); + } + else if (alevel.mark == -2 && alevel.space == -2) { /* DTMF - single number. */ snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); } diff --git a/ax25_pad.h b/ax25_pad.h index 75ede36e..6e859b19 100644 --- a/ax25_pad.h +++ b/ax25_pad.h @@ -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. */ @@ -100,7 +102,8 @@ struct packet_s { * Changed to 1 when position has been used. * * for source & destination it is called - * command/response and is normally 1. + * command/response. Normally both 1 for APRS. + * They should be opposites for connected mode. * * R R Reserved. Normally set to 1 1. * @@ -109,6 +112,7 @@ struct packet_s { * 0 Usually 0 but 1 for last address. */ + #define SSID_H_MASK 0x80 #define SSID_H_SHIFT 7 @@ -123,6 +127,15 @@ struct packet_s { int frame_len; /* Frame length without CRC. */ + int modulo; /* I & S frames have sequence numbers of either 3 bits (modulo 8) */ + /* or 7 bits (modulo 128). This is conveyed by either 1 or 2 */ + /* control bytes. Unfortunately, we can't determine this by looking */ + /* at an isolated frame. We need to know about the context. If we */ + /* are part of the conversation, we would know. But if we are */ + /* just listening to others, this would be more difficult to determine. */ + + /* For U frames: set to 0 - not applicable */ + /* For I & S frames: 8 or 128 if known. 0 if unknown. */ unsigned char frame_data[AX25_MAX_PACKET_LEN+1]; /* Raw frame contents, without the CRC. */ @@ -145,30 +158,53 @@ struct packet_s { 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 * general AX.25 case is one or two control bytes depending on - * "modulo 128 operation" is in effect. Unfortunately, it seems - * this can be determined only by examining the XID frames and - * keeping this information for each connection. - * We can assume 1 for our current purposes. + * whether "modulo 128 operation" is in effect. */ +//#define DEBUGX 1 + static inline int ax25_get_control_offset (packet_t this_p) { - //return (0); return (this_p->num_addr*7); } static inline int ax25_get_num_control (packet_t this_p) { - return (1); // TODO: always be 1 for U frame. More complicated for I and S. + 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 */ } @@ -194,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); } @@ -217,10 +259,14 @@ 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 int ax25_get_num_info (packet_t this_p) +static inline int ax25_get_num_info (packet_t this_p) { int len; @@ -237,11 +283,11 @@ static int ax25_get_num_info (packet_t this_p) #endif -typedef enum ax25_modulo_e { modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t; +typedef enum ax25_modulo_e { modulo_unknown = 0, 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 @@ -260,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; @@ -346,7 +394,10 @@ 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 int ax25_cut_at_crlf (packet_t this_p); extern void ax25_set_nextp (packet_t this_p, packet_t next_p); @@ -357,15 +408,19 @@ 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 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]); -extern ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns); +extern ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns); 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); diff --git a/ax25_pad2.c b/ax25_pad2.c new file mode 100644 index 00000000..7c37dbf5 --- /dev/null +++ b/ax25_pad2.c @@ -0,0 +1,889 @@ +// +// 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 . +// + + + +/*------------------------------------------------------------------ + * + * Name: ax25_pad2.c + * + * Purpose: Packet assembler and disasembler, part 2. + * + * Description: + * + * The original ax25_pad.c was written with APRS in mind. + * It handles UI frames and transparency for a KISS TNC. + * Here we add new functions that can handle the + * more general cases of AX.25 frames. + * + * + * * Destination Address (note: opposite order in printed format) + * + * * Source Address + * + * * 0-8 Digipeater Addresses + * (The AX.25 v2.2 spec reduced this number to + * a maximum of 2 but I allow the original 8.) + * + * Each address is composed of: + * + * * 6 upper case letters or digits, blank padded. + * These are shifted left one bit, leaving the LSB always 0. + * + * * a 7th octet containing the SSID and flags. + * The LSB is always 0 except for the last octet of the address field. + * + * The final octet of the Destination has the form: + * + * C R R SSID 0, where, + * + * C = command/response. Set to 1 for command. + * R R = Reserved = 1 1 (See RR note, below) + * SSID = substation ID + * 0 = zero + * + * The final octet of the Source has the form: + * + * C R R SSID 0, where, + * + * C = command/response. Must be inverse of destination C bit. + * R R = Reserved = 1 1 (See RR note, below) + * SSID = substation ID + * 0 = zero (or 1 if no repeaters) + * + * The final octet of each repeater has the form: + * + * H R R SSID 0, where, + * + * H = has-been-repeated = 0 initially. + * Set to 1 after this address has been used. + * R R = Reserved = 1 1 + * SSID = substation ID + * 0 = zero (or 1 if last repeater in list) + * + * A digipeater would repeat this frame if it finds its address + * with the "H" bit set to 0 and all earlier repeater addresses + * have the "H" bit set to 1. + * The "H" bit would be set to 1 in the repeated frame. + * + * In standard monitoring format, an asterisk is displayed after the last + * digipeater with the "H" bit set. That indicates who you are hearing + * over the radio. + * + * + * Next we have: + * + * * One or two byte Control Field - A U frame always has one control byte. + * When using modulo 128 sequence numbers, the + * I and S frames can have a second byte allowing + * 7 bit fields instead of 3 bit fields. + * Unfortunately, we can't tell which we have by looking + * at a frame out of context. :-( + * If we are one end of the link, we would know this + * from SABM/SABME and possible later negotiation + * with XID. But if we start monitoring two other + * stations that are already conversing, we don't know. + * + * RR note: It seems that some implementations put a hint + * in the "RR" reserved bits. + * http://www.tapr.org/pipermail/ax25-layer2/2005-October/000297.html + * The RR bits can also be used for "DAMA" which is + * some sort of channel access coordination scheme. + * http://internet.freepage.de/cgi-bin/feets/freepage_ext/41030x030A/rewrite/hennig/afu/afudoc/afudama.html + * Neither is part of the official protocol spec. + * + * * One byte Protocol ID - Only for I and UI frames. + * Normally we would use 0xf0 for no layer 3. + * + * Finally the Information Field. The initial max size is 256 but it + * can be negotiated higher if both ends agree. + * + * Only these types of frames can have an information part: + * - I + * - UI + * - XID + * - TEST + * - FRMR + * + * The 2 byte CRC is not stored here. + * + * + * Constructors: + * ax25_u_frame - Construct a U frame. + * ax25_s_frame - Construct a S frame. + * ax25_i_frame - Construct a I frame. + * + * Get methods: .... ??? + * + *------------------------------------------------------------------*/ + +#define AX25_PAD_C /* this will affect behavior of ax25_pad.h */ + + +#include "direwolf.h" + +#include +#include +#include +#include +#include + + +#include "textcolor.h" +#include "ax25_pad.h" +#include "ax25_pad2.h" + + + +extern int ax25memdebug; + +static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr); + +//#if AX25MEMDEBUG +//#undef AX25MEMDEBUG +//#endif + + +/*------------------------------------------------------------------------------ + * + * Name: ax25_u_frame + * + * Purpose: Construct a U frame. + * + * Input: addrs - Array of addresses. + * + * num_addr - Number of addresses, range 2 .. 10. + * + * cr - cr_cmd command frame, cr_res for a response frame. + * + * ftype - One of: + * frame_type_U_SABME // Set Async Balanced Mode, Extended + * frame_type_U_SABM // Set Async Balanced Mode + * frame_type_U_DISC // Disconnect + * frame_type_U_DM // Disconnect Mode + * frame_type_U_UA // Unnumbered Acknowledge + * frame_type_U_FRMR // Frame Reject + * frame_type_U_UI // Unnumbered Information + * frame_type_U_XID // Exchange Identification + * frame_type_U_TEST // Test + * + * pf - Poll/Final flag. + * + * pid - Protocol ID. >>> Used ONLY for the UI type. <<< + * Normally 0xf0 meaning no level 3. + * Could be other values for NET/ROM, etc. + * + * pinfo - Pointer to data for Info field. Allowed only for UI, XID, TEST, FRMR. + * + * info_len - Length for Info field. + * + * + * Returns: Pointer to new packet object. + * + *------------------------------------------------------------------------------*/ + +#if AX25MEMDEBUG +packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line) +#else +packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len) +#endif +{ + packet_t this_p; + unsigned char *p; + int ctrl = 0; + unsigned int t = 999; // 1 = must be cmd, 0 = must be response, 2 = can be either. + int i = 0; // Is Info part allowed? + + this_p = ax25_new (); + +#if AX25MEMDEBUG + if (ax25memdebug) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ax25_u_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); + } +#endif + + if (this_p == NULL) return (NULL); + + this_p->modulo = 0; + + if ( ! set_addrs (this_p, addrs, num_addr, cr)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Could not set addresses for U frame.\n", __func__); + ax25_delete (this_p); + return (NULL); + } + + switch (ftype) { + // 1 = cmd only, 0 = res only, 2 = either + case frame_type_U_SABME: ctrl = 0x6f; t = 1; break; + case frame_type_U_SABM: ctrl = 0x2f; t = 1; break; + case frame_type_U_DISC: ctrl = 0x43; t = 1; break; + case frame_type_U_DM: ctrl = 0x0f; t = 0; break; + case frame_type_U_UA: ctrl = 0x63; t = 0; break; + case frame_type_U_FRMR: ctrl = 0x87; t = 0; i = 1; break; + case frame_type_U_UI: ctrl = 0x03; t = 2; i = 1; break; + case frame_type_U_XID: ctrl = 0xaf; t = 2; i = 1; break; + case frame_type_U_TEST: ctrl = 0xe3; t = 2; i = 1; break; + + default: + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Invalid ftype %d for U frame.\n", __func__, ftype); + ax25_delete (this_p); + return (NULL); + break; + } + if (pf) ctrl |= 0x10; + + 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. ftype=%d\n", __func__, cr, t, ftype); + } + } + + p = this_p->frame_data + this_p->frame_len; + *p++ = ctrl; + this_p->frame_len++; + + if (ftype == frame_type_U_UI) { + + // Definitely don't want pid value of 0 (not in valid list) + // 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: U frame, Invalid pid value 0x%02x.\n", __func__, pid); + pid = AX25_PID_NO_LAYER_3; + } + *p++ = pid; + this_p->frame_len++; + } + + if (i) { + if (pinfo != NULL && info_len > 0) { + if (info_len > AX25_MAX_INFO_LEN) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: U frame, Invalid information field length %d.\n", __func__, info_len); + info_len = AX25_MAX_INFO_LEN; + } + memcpy (p, pinfo, info_len); + p += info_len; + this_p->frame_len += info_len; + } + } + else { + if (pinfo != NULL && info_len > 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Info part not allowed for U frame type.\n", __func__); + } + } + *p = '\0'; + + assert (p == this_p->frame_data + this_p->frame_len); + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + +#if PAD2TEST + ax25_frame_type_t check_ftype; + cmdres_t check_cr; + char check_desc[80]; + int check_pf; + int check_nr; + int check_ns; + + check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns); + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d\n", check_ftype, check_desc, check_pf); + + assert (check_cr == cr); + assert (check_ftype == ftype); + assert (check_pf == pf); + assert (check_nr == -1); + assert (check_ns == -1); + +#endif + + return (this_p); + +} /* end ax25_u_frame */ + + + + + + + +/*------------------------------------------------------------------------------ + * + * Name: ax25_s_frame + * + * Purpose: Construct an S frame. + * + * Input: addrs - Array of addresses. + * + * num_addr - Number of addresses, range 2 .. 10. + * + * cr - cr_cmd command frame, cr_res for a response frame. + * + * ftype - One of: + * frame_type_S_RR, // Receive Ready - System Ready To Receive + * frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full + * frame_type_S_REJ, // Reject Frame - Out of Sequence or Duplicate + * frame_type_S_SREJ, // Selective Reject - Request single frame repeat + * + * modulo - 8 or 128. Determines if we have 1 or 2 control bytes. + * + * nr - N(R) field --- describe. + * + * pf - Poll/Final flag. + * + * + * Returns: Pointer to new packet object. + * + *------------------------------------------------------------------------------*/ + +#if AX25MEMDEBUG +packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, char *src_file, int src_line) +#else +packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf) +#endif +{ + packet_t this_p; + unsigned char *p; + int ctrl = 0; + + this_p = ax25_new (); + +#if AX25MEMDEBUG + if (ax25memdebug) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ax25_s_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); + } +#endif + + if (this_p == NULL) return (NULL); + + if ( ! set_addrs (this_p, addrs, num_addr, cr)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Could not set addresses for U frame.\n", __func__); + ax25_delete (this_p); + return (NULL); + } + + if (modulo != 8 && modulo != 128) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Invalid modulo %d for S frame.\n", __func__, modulo); + modulo = 8; + } + this_p->modulo = modulo; + + if (nr < 0 || nr >= modulo) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Invalid N(R) %d for S frame.\n", __func__, nr); + nr &= (modulo - 1); + } + + switch (ftype) { + + case frame_type_S_RR: ctrl = 0x01; break; + case frame_type_S_RNR: ctrl = 0x05; break; + case frame_type_S_REJ: ctrl = 0x09; break; + case frame_type_S_SREJ: ctrl = 0x0d; break; + + default: + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Invalid ftype %d for S frame.\n", __func__, ftype); + ax25_delete (this_p); + return (NULL); + break; + } + + p = this_p->frame_data + this_p->frame_len; + + if (modulo == 8) { + if (pf) ctrl |= 0x10; + ctrl |= nr << 5; + *p++ = ctrl; + this_p->frame_len++; + } + else { + *p++ = ctrl; + this_p->frame_len++; + + ctrl = pf & 1; + ctrl |= nr << 1; + *p++ = ctrl; + this_p->frame_len++; + } + + *p = '\0'; + + assert (p == this_p->frame_data + this_p->frame_len); + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + +#if PAD2TEST + + ax25_frame_type_t check_ftype; + cmdres_t check_cr; + char check_desc[80]; + int check_pf; + int check_nr; + int check_ns; + + // todo modulo must be input. + check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns); + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d\n", check_ftype, check_desc, check_pf, check_nr); + + assert (check_cr == cr); + assert (check_ftype == ftype); + assert (check_pf == pf); + assert (check_nr == nr); + assert (check_ns == -1); + +#endif + return (this_p); + +} /* end ax25_s_frame */ + + + + + +/*------------------------------------------------------------------------------ + * + * Name: ax25_i_frame + * + * Purpose: Construct an I frame. + * + * Input: addrs - Array of addresses. + * + * num_addr - Number of addresses, range 2 .. 10. + * + * cr - cr_cmd command frame, cr_res for a response frame. + * + * modulo - 8 or 128. + * + * nr - N(R) field --- describe. + * + * ns - N(S) field --- describe. + * + * pf - Poll/Final flag. + * + * pid - Protocol ID. + * Normally 0xf0 meaning no level 3. + * Could be other values for NET/ROM, etc. + * + * pinfo - Pointer to data for Info field. + * + * info_len - Length for Info field. + * + * + * Returns: Pointer to new packet object. + * + *------------------------------------------------------------------------------*/ + +#if AX25MEMDEBUG +packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line) +#else +packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len) +#endif +{ + packet_t this_p; + unsigned char *p; + int ctrl = 0; + + this_p = ax25_new (); + +#if AX25MEMDEBUG + if (ax25memdebug) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ax25_i_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); + } +#endif + + if (this_p == NULL) return (NULL); + + if ( ! set_addrs (this_p, addrs, num_addr, cr)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Could not set addresses for I frame.\n", __func__); + ax25_delete (this_p); + return (NULL); + } + + if (modulo != 8 && modulo != 128) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Invalid modulo %d for I frame.\n", __func__, modulo); + modulo = 8; + } + this_p->modulo = modulo; + + if (nr < 0 || nr >= modulo) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Invalid N(R) %d for I frame.\n", __func__, nr); + nr &= (modulo - 1); + } + + if (ns < 0 || ns >= modulo) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: Invalid N(S) %d for I frame.\n", __func__, ns); + ns &= (modulo - 1); + } + + p = this_p->frame_data + this_p->frame_len; + + if (modulo == 8) { + ctrl = (nr << 5) | (ns << 1); + if (pf) ctrl |= 0x10; + *p++ = ctrl; + this_p->frame_len++; + } + else { + ctrl = ns << 1; + *p++ = ctrl; + this_p->frame_len++; + + ctrl = nr << 1; + if (pf) ctrl |= 0x01; + *p++ = ctrl; + this_p->frame_len++; + } + + // Definitely don't want pid value of 0 (not in valid list) + // or 0xff (which means more bytes follow). + + if (pid < 0 || pid == 0 || pid == 0xff) { + 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++; + + if (pinfo != NULL && info_len > 0) { + if (info_len > AX25_MAX_INFO_LEN) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error in %s: I frame, Invalid information field length %d.\n", __func__, info_len); + info_len = AX25_MAX_INFO_LEN; + } + memcpy (p, pinfo, info_len); + p += info_len; + this_p->frame_len += info_len; + } + + *p = '\0'; + + assert (p == this_p->frame_data + this_p->frame_len); + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + +#if PAD2TEST + + ax25_frame_type_t check_ftype; + cmdres_t check_cr; + char check_desc[80]; + int check_pf; + int check_nr; + int check_ns; + unsigned char *check_pinfo; + int check_info_len; + + check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns); + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d, ns=%d\n", check_ftype, check_desc, check_pf, check_nr, check_ns); + + check_info_len = ax25_get_info (this_p, &check_pinfo); + + assert (check_cr == cr); + assert (check_ftype == frame_type_I); + assert (check_pf == pf); + assert (check_nr == nr); + assert (check_ns == ns); + + assert (check_info_len == info_len); + assert (strcmp((char*)check_pinfo,(char*)pinfo) == 0); +#endif + + return (this_p); + +} /* end ax25_i_frame */ + + + + + +/*------------------------------------------------------------------------------ + * + * Name: set_addrs + * + * Purpose: Set address fields + * + * Input: pp - Packet object. + * + * addrs - Array of addresses. Same order as in frame. + * + * num_addr - Number of addresses, range 2 .. 10. + * + * cr - cr_cmd command frame, cr_res for a response frame. + * + * Output: pp->frame_data - 7 bytes for each address. + * + * pp->frame_len - num_addr * 7 + * + * p->num_addr - num_addr + * + * Returns: 1 for success. 0 for failure. + * + *------------------------------------------------------------------------------*/ + + +static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr) +{ + int n; + + assert (pp->frame_len == 0); + assert (cr == cr_cmd || cr == cr_res); + + if (num_addr < AX25_MIN_ADDRS || num_addr > AX25_MAX_ADDRS) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("INTERNAL ERROR: %s %s %d, num_addr = %d\n", __FILE__, __func__, __LINE__, num_addr); + return (0); + } + + for (n = 0; n < num_addr; n++) { + + unsigned char *pa = pp->frame_data + n * 7; + int ok; + int strict = 1; + char oaddr[AX25_MAX_ADDR_LEN]; + int ssid; + int heard; + int j; + + ok = ax25_parse_addr (n, addrs[n], strict, oaddr, &ssid, &heard); + + if (! ok) return (0); + + // Fill in address. + + memset (pa, ' ' << 1, 6); + for (j = 0; oaddr[j]; j++) { + pa[j] = oaddr[j] << 1; + } + pa += 6; + + // Fill in SSID. + + *pa = 0x60 | ((ssid & 0xf) << 1); + + // Command / response flag. + + switch (n) { + case AX25_DESTINATION: + if (cr == cr_cmd) *pa |= 0x80; + break; + case AX25_SOURCE: + if (cr == cr_res) *pa |= 0x80; + break; + default: + break; + } + + // Is this the end of address field? + + if (n == num_addr - 1) { + *pa |= 1; + } + + pp->frame_len += 7; + } + + pp->num_addr = num_addr; + return (1); + +} /* end set_addrs */ + + + + +/*------------------------------------------------------------------------------ + * + * Name: main + * + * Purpose: Quick unit test for this file. + * + * Description: Generate a variety of frames. + * Each function calls ax25_frame_type to verify results. + * + * $ gcc -DPAD2TEST -DUSE_REGEX_STATIC -Iregex ax25_pad.c ax25_pad2.c fcs_calc.o textcolor.o regex.a misc.a + * + *------------------------------------------------------------------------------*/ + +#if PAD2TEST + +int main () +{ + char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; + int num_addr = 2; + cmdres_t cr; + ax25_frame_type_t ftype; + int pf = 0; + int pid = 0xf0; + int modulo; + int nr, ns; + unsigned char *pinfo = NULL; + int info_len = 0; + packet_t pp; + + strcpy (addrs[0], "W2UB"); + strcpy (addrs[1], "WB2OSZ-15"); + num_addr = 2; + +/* U frame */ + + for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) { + + for (pf = 0; pf <= 1; pf++) { + + int cmin, cmax; + + switch (ftype) { + // 0 = response, 1 = command + case frame_type_U_SABME: cmin = 1; cmax = 1; break; + case frame_type_U_SABM: cmin = 1; cmax = 1; break; + case frame_type_U_DISC: cmin = 1; cmax = 1; break; + case frame_type_U_DM: cmin = 0; cmax = 0; break; + case frame_type_U_UA: cmin = 0; cmax = 0; break; + case frame_type_U_FRMR: cmin = 0; cmax = 0; break; + case frame_type_U_UI: cmin = 0; cmax = 1; break; + case frame_type_U_XID: cmin = 0; cmax = 1; break; + case frame_type_U_TEST: cmin = 0; cmax = 1; break; + default: break; // avoid compiler warning. + } + + for (cr = cmin; cr <= cmax; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct U frame, cr=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_u_frame (addrs, num_addr, cr, ftype, pf, pid, pinfo, info_len); + ax25_hex_dump (pp); + ax25_delete (pp); + } + } + } + + dw_printf ("\n----------\n\n"); + +/* S frame */ + + strcpy (addrs[2], "DIGI1-1"); + num_addr = 3; + + for (ftype = frame_type_S_RR; ftype <= frame_type_S_SREJ; ftype++) { + + for (pf = 0; pf <= 1; pf++) { + + modulo = 8; + nr = modulo / 2 + 1; + + for (cr = 0; cr <= 1; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf); + + ax25_hex_dump (pp); + ax25_delete (pp); + } + + modulo = 128; + nr = modulo / 2 + 1; + + for (cr = 0; cr <= 1; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf); + + ax25_hex_dump (pp); + ax25_delete (pp); + } + } + } + + dw_printf ("\n----------\n\n"); + +/* I frame */ + + pinfo = (unsigned char*)"The rain in Spain stays mainly on the plain."; + info_len = strlen((char*)pinfo); + + for (pf = 0; pf <= 1; pf++) { + + modulo = 8; + nr = 0x55 & (modulo - 1); + ns = 0xaa & (modulo - 1); + + for (cr = 0; cr <= 1; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); + + ax25_hex_dump (pp); + ax25_delete (pp); + } + + modulo = 128; + nr = 0x55 & (modulo - 1); + ns = 0xaa & (modulo - 1); + + for (cr = 0; cr <= 1; cr++) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); + + pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); + + ax25_hex_dump (pp); + ax25_delete (pp); + } + } + + text_color_set(DW_COLOR_REC); + dw_printf ("\n----------\n\n"); + dw_printf ("\nSUCCESS!\n"); + + exit (EXIT_SUCCESS); + +} /* end main */ + +#endif + + +/* end ax25_pad2.c */ diff --git a/ax25_pad2.h b/ax25_pad2.h new file mode 100644 index 00000000..48609417 --- /dev/null +++ b/ax25_pad2.h @@ -0,0 +1,55 @@ +/*------------------------------------------------------------------- + * + * Name: ax25_pad2.h + * + * Purpose: Header file for using ax25_pad2.c + * ax25_pad dealt only with UI frames. + * This adds a facility for the other types: U, s, I. + * + *------------------------------------------------------------------*/ + +#ifndef AX25_PAD2_H +#define AX25_PAD2_H 1 + +#include "ax25_pad.h" + + + + +#if AX25MEMDEBUG // to investigate a memory leak problem + + + +packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line); + +packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, char *src_file, int src_line); + +packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line); + + +#define ax25_u_frame(a,n,c,f,p,q,i,l) ax25_u_frame_debug(a,n,c,f,p,q,i,l,__FILE__,__LINE__) + +#define ax25_s_frame(a,n,c,f,m,r,p) ax25_s_frame_debug(a,n,c,f,m,r,p,__FILE__,__LINE__) + +#define ax25_i_frame(a,n,c,m,r,s,p,q,i,l) ax25_i_frame_debug(a,n,c,m,r,s,p,q,i,l,__FILE__,__LINE__) + + +#else + +packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len); + +packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf); + +packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len); + + +#endif + + + + +#endif /* AX25_PAD2_H */ + +/* end ax25_pad2.h */ + + diff --git a/beacon.c b/beacon.c index 89ef30d3..a5a2bade 100644 --- a/beacon.c +++ b/beacon.c @@ -1,7 +1,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 @@ -30,6 +30,8 @@ //#define DEBUG 1 +#include "direwolf.h" + #include #include @@ -40,7 +42,6 @@ #include -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" @@ -55,6 +56,7 @@ #include "log.h" #include "dlq.h" #include "aprs_tt.h" // for dw_run_cmd - should relocate someday. +#include "mheard.h" #if __WIN32__ @@ -83,6 +85,7 @@ struct tm *localtime_r(time_t *clock, struct tm *res) static struct audio_s *g_modem_config_p; static struct misc_config_s *g_misc_config_p; +static struct igate_config_s *g_igate_config_p; #if __WIN32__ @@ -118,6 +121,10 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo); * Used only to find valid channels. * * pconfig - misc. configuration from config file. + * Beacon stuff ended up here. + * + * pigate - IGate configuration. + * Need this for calculating IGate statistics. * * * Outputs: Remember required information for future use. @@ -131,7 +138,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo); -void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig) +void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate) { time_t now; int j; @@ -156,6 +163,7 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig) */ g_modem_config_p = pmodem; g_misc_config_p = pconfig; + g_igate_config_p = pigate; /* * Precompute the packet contents so any errors are @@ -228,6 +236,22 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig) } break; + case BEACON_IGATE: + + /* Doesn't make sense if IGate is not configured. */ + + if (strlen(g_igate_config_p->t2_server_name) == 0 || + strlen(g_igate_config_p->t2_login) == 0 || + strlen(g_igate_config_p->t2_passcode) == 0) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: Doesn't make sense to use IBEACON without IGate Configured.\n", g_misc_config_p->beacon[j].lineno); + dw_printf ("IBEACON has been disabled.\n"); + g_misc_config_p->beacon[j].btype = BEACON_IGNORE; + continue; + } + break; + case BEACON_IGNORE: break; } @@ -870,6 +894,25 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) } break; + case BEACON_IGATE: + + { + int last_minutes = 30; + char stuff[256]; + + snprintf (stuff, sizeof(stuff), "max_digi_hops,last_minutes), + mheard_count(8,last_minutes), + igate_get_upl_cnt(), + igate_get_dnl_cnt()); + + strlcat (beacon_text, stuff, sizeof(beacon_text)); + } + break; + case BEACON_IGNORE: default: break; @@ -911,10 +954,10 @@ 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_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, ""); + dlq_rec_frame (g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, ""); break; } } diff --git a/beacon.h b/beacon.h index 1dce32be..f7d2a561 100644 --- a/beacon.h +++ b/beacon.h @@ -1,6 +1,6 @@ /* beacon.h */ -void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig); +void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate); void beacon_tracker_set_debug (int level); diff --git a/cdigipeater.c b/cdigipeater.c new file mode 100644 index 00000000..e531efd7 --- /dev/null +++ b/cdigipeater.c @@ -0,0 +1,317 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include /* for isdigit, isupper */ +#include "regex.h" +#include + +#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_chanenabled[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_chanenabled[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) { + 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 */ diff --git a/cdigipeater.h b/cdigipeater.h new file mode 100644 index 00000000..5a50b872 --- /dev/null +++ b/cdigipeater.h @@ -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 */ + diff --git a/config.c b/config.c index 8090c859..455a0d31 100644 --- a/config.c +++ b/config.c @@ -17,7 +17,7 @@ // along with this program. If not, see . // -#define CONFIG_C 1 +#define CONFIG_C 1 // influences behavior of aprs_tt.h // #define DEBUG 1 @@ -34,6 +34,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -51,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" @@ -58,6 +61,7 @@ #include "symbols.h" #include "xmit.h" #include "tt_text.h" +#include "ax25_link.h" // geotranz @@ -134,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); @@ -451,6 +455,110 @@ static int parse_interval (char *str, int line) +/*------------------------------------------------------------------ + * + * Name: check_via_path + * + * Purpose: Check for valid path in beacons, IGate, and APRStt configuration. + * + * Inputs: via_path - Zero or more comma separated stations. + * + * Returns: Maximum number of digipeater hops or -1 for error. + * + * Description: Beacons and IGate can use via paths such as: + * + * WIDE1-1,MA3-3 + * N2GH,RARA-7 + * + * Each part could be a specific station, an alias, or a path + * from the "New n-N Paradigm." + * In the first example above, the maximum number of digipeater + * hops would be 4. In the second example, 2. + * + *----------------------------------------------------------------*/ + +// Put something like this in the config file as a quick test. +// Not worth adding to "make check" regression tests. +// +// IBEACON via= +// IBEACON via=W2UB +// IBEACON via=W2UB-7 +// IBEACON via=WIDE1-1,WIDE2-2,WIDE3-3 +// IBEACON via=Lower +// IBEACON via=T00LONG +// IBEACON via=W2UB-16 +// IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8 +// IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8,D9 +// +// Define below and visually check results. + +//#define DEBUG8 1 + + +static int check_via_path (char *via_path) +{ + char stemp[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN + 1)]; + int num_digi = 0; + int max_digi_hops = 0; + char *r; + char *a; + +#if DEBUG8 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("check_via_path %s\n", via_path); +#endif + if (strlen(via_path) == 0) { + return (0); + } + + strlcpy (stemp, via_path, sizeof(stemp)); + + r = stemp; + while (( a = strsep(&r,",")) != NULL) { + int strict = 1; + int ok; + char addr[AX25_MAX_ADDR_LEN]; + int ssid; + int heard; + + num_digi++; + ok = ax25_parse_addr (AX25_REPEATER_1 - 1 + num_digi, a, strict, addr, &ssid, &heard); + if ( ! ok) { +#if DEBUG8 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("check_via_path bad address\n"); +#endif + return (-1); + } + + /* Based on assumption that a callsign can't end with a digit. */ + /* For something of the form xxx9-9, we take the ssid as max hop count. */ + + if (ssid > 0 && strlen(addr) >= 2 && isdigit(addr[strlen(addr)-1])) { + max_digi_hops += ssid; + } + else { + max_digi_hops++; + } + } + + if (num_digi > AX25_MAX_REPEATERS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Maximum of 8 digipeaters has been exceeded.\n"); + return (-1); + } + +#if DEBUG8 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("check_via_path %d addresses, %d max digi hops\n", num_digi, max_digi_hops); +#endif + + return (max_digi_hops); + +} /* end check_via_path */ + + + /*------------------------------------------------------------------- * * Name: split @@ -574,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. * @@ -597,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) @@ -637,7 +748,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_audio_config->adev[0].defined = 1; for (channel=0; channelachan[channel].valid = 0; /* One or both channels will be */ /* set to valid when corresponding */ @@ -662,12 +773,18 @@ void config_init (char *fname, struct audio_s *p_audio_config, strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "", sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_NONE; p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_NONE; - p_audio_config->achan[channel].octrl[ot].ptt_gpio = 0; + p_audio_config->achan[channel].octrl[ot].out_gpio_num = 0; p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = 0; p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0; } + for (it = 0; it < NUM_ICTYPES; it++) { + p_audio_config->achan[channel].ictrl[it].method = PTT_METHOD_NONE; + p_audio_config->achan[channel].ictrl[it].in_gpio_num = 0; + p_audio_config->achan[channel].ictrl[it].invert = 0; + } + p_audio_config->achan[channel].dwait = DEFAULT_DWAIT; p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME; p_audio_config->achan[channel].persist = DEFAULT_PERSIST; @@ -683,6 +800,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; @@ -745,6 +863,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. */ @@ -753,10 +872,25 @@ void config_init (char *fname, struct audio_s *p_audio_config, //strlcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM, sizeof(p_misc_config->nullmodem)); strlcpy (p_misc_config->nullmodem, "", sizeof(p_misc_config->nullmodem)); + strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port)); - strlcpy (p_misc_config->nmea_port, "", sizeof(p_misc_config->nmea_port)); + strlcpy (p_misc_config->waypoint_port, "", sizeof(p_misc_config->waypoint_port)); + 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. @@ -1114,15 +1248,15 @@ void config_init (char *fname, struct audio_s *p_audio_config, n = atoi(t); if (n >= MIN_BAUD && n <= MAX_BAUD) { p_audio_config->achan[channel].baud = n; - if (n != 300 && n != 1200 && n != 9600) { + if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Warning: Non-standard baud rate. Are you sure?\n", line); + dw_printf ("Line %d: Warning: Non-standard data rate of %d bits per second. Are you sure?\n", line, n); } } else { p_audio_config->achan[channel].baud = DEFAULT_BAUD; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", + dw_printf ("Line %d: Unreasonable data rate. Using %d bits per second.\n", line, p_audio_config->achan[channel].baud); } @@ -1130,20 +1264,33 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Set defaults based on speed. */ /* Should be same as -B command line option in direwolf.c. */ + /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ + /* that need to be kept in sync. Maybe it could be a common function someday. */ + if (p_audio_config->achan[channel].baud < 600) { p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].mark_freq = 1600; p_audio_config->achan[channel].space_freq = 1800; } - else if (p_audio_config->achan[channel].baud > 2400) { - p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; + else if (p_audio_config->achan[channel].baud < 1800) { + p_audio_config->achan[channel].modem_type = MODEM_AFSK; + p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; + p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; + } + else if (p_audio_config->achan[channel].baud < 3600) { + p_audio_config->achan[channel].modem_type = MODEM_QPSK; + p_audio_config->achan[channel].mark_freq = 0; + p_audio_config->achan[channel].space_freq = 0; + } + else if (p_audio_config->achan[channel].baud < 7200) { + p_audio_config->achan[channel].modem_type = MODEM_8PSK; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } else { - p_audio_config->achan[channel].modem_type = MODEM_AFSK; - p_audio_config->achan[channel].mark_freq = 1200; - p_audio_config->achan[channel].space_freq = 2200; + p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; + p_audio_config->achan[channel].mark_freq = 0; + p_audio_config->achan[channel].space_freq = 0; } /* Get mark frequency. */ @@ -1447,6 +1594,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 @@ -1461,7 +1609,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]; @@ -1474,8 +1622,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); @@ -1502,11 +1650,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (*t == '-') { - p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t+1); + p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1); p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { - p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t); + p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO; @@ -1663,7 +1811,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 @@ -1674,10 +1822,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "TXINH") == 0) { - int it; char itname[8]; - it = ICTYPE_TXINH; strlcpy (itname, "TXINH", sizeof(itname)); t = split(NULL,0); @@ -1701,14 +1847,14 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (*t == '-') { - p_audio_config->achan[channel].ictrl[it].gpio = atoi(t+1); - p_audio_config->achan[channel].ictrl[it].invert = 1; + p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t+1); + p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 1; } else { - p_audio_config->achan[channel].ictrl[it].gpio = atoi(t); - p_audio_config->achan[channel].ictrl[it].invert = 0; + p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t); + p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 0; } - p_audio_config->achan[channel].ictrl[it].method = PTT_METHOD_GPIO; + p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].method = PTT_METHOD_GPIO; #endif } } @@ -1865,14 +2011,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]; @@ -2056,7 +2202,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 ==================== */ /* @@ -2126,8 +2347,84 @@ void config_init (char *fname, struct audio_s *p_audio_config, t = " "; /* Empty means permit nothing. */ } + if (p_digi_config->filter_str[from_chan][to_chan] != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: Replacing previous filter for same from/to pair:\n %s\n", + line, p_digi_config->filter_str[from_chan][to_chan]); + free (p_digi_config->filter_str[from_chan][to_chan]); + p_digi_config->filter_str[from_chan][to_chan] = NULL; + } + p_digi_config->filter_str[from_chan][to_chan] = strdup(t); +//TODO: Do a test run to see errors now instead of waiting. + + } + + +/* + * ==================== 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. } @@ -2221,7 +2518,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']++; } @@ -3335,8 +3632,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, t = split(NULL,0); if (t != NULL) { - // TODO: Should do some validity checking on the path. - strlcpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via)); + if (check_via_path(t) >= 0) { + strlcpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: invalid via path.\n", line); + } } } @@ -3587,6 +3889,22 @@ void config_init (char *fname, struct audio_s *p_audio_config, t = split(NULL,0); if (t != NULL) { + +#if 1 // proper checking + + n = check_via_path(t); + if (n >= 0) { + p_igate_config->max_digi_hops = n; + p_igate_config->tx_via[0] = ','; + strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: invalid via path.\n", line); + } + +#else // previously + char *p; p_igate_config->tx_via[0] = ','; strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1); @@ -3595,11 +3913,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, *p = toupper(*p); /* silently force upper case. */ } } +#endif } } /* - * IGFILTER - Filter for messages from IGate server + * IGFILTER - IGate Server side filters. * * IGFILTER filter-spec ... */ @@ -3669,6 +3988,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. * @@ -3828,18 +4178,46 @@ void config_init (char *fname, struct audio_s *p_audio_config, } /* - * NMEA - Device name for communication with NMEA device. - * Wasn't documented will probably use WAYPOINT instead. + * WAYPOINT - Generate WPL NMEA sentences for display on map. + * + * WAYPOINT serial-device [ formats ] + * */ - else if (strcasecmp(t, "nmea") == 0) { + else if (strcasecmp(t, "waypoint") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: Missing device name for NMEA port on line %d.\n", line); + dw_printf ("Config file: Missing device name for WAYPOINT on line %d.\n", line); continue; } else { - strlcpy (p_misc_config->nmea_port, t, sizeof(p_misc_config->nmea_port)); + strlcpy (p_misc_config->waypoint_port, t, sizeof(p_misc_config->waypoint_port)); + } + t = split(NULL,1); + if (t != NULL) { + for ( ; *t != '\0' ; t++ ) { + switch (toupper(*t)) { + case 'N': + p_misc_config->waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; + break; + case 'G': + p_misc_config->waypoint_formats |= WPT_FORMAT_GARMIN; + break; + case 'M': + p_misc_config->waypoint_formats |= WPT_FORMAT_MAGELLAN; + break; + case 'K': + p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD; + break; + case ' ': + case ',': + break; + default: + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Invalid output format '%c' for WAYPOINT on line %d.\n", *t, line); + break; + } + } } } @@ -3883,6 +4261,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, * OBEACON keyword=value ... * TBEACON keyword=value ... * CBEACON keyword=value ... + * IBEACON keyword=value ... * * New style with keywords for options. */ @@ -3890,7 +4269,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "PBEACON") == 0 || strcasecmp(t, "OBEACON") == 0 || strcasecmp(t, "TBEACON") == 0 || - strcasecmp(t, "CBEACON") == 0) { + strcasecmp(t, "CBEACON") == 0 || + strcasecmp(t, "IBEACON") == 0) { if (p_misc_config->num_beacons < MAX_BEACONS) { @@ -3904,6 +4284,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "TBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_TRACKER; } + else if (strcasecmp(t, "IBEACON") == 0) { + p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_IGATE; + } else { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_CUSTOM; } @@ -3989,6 +4372,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. */ @@ -4015,6 +4546,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, for (i=0; ienabled[i][j]) { if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || @@ -4047,6 +4580,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; knum_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) { @@ -4056,6 +4622,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 || @@ -4066,7 +4634,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 / 3; } } /* end config_init */ @@ -4177,12 +4761,27 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } } else if (strcasecmp(keyword, "VIA") == 0) { + +#if 1 // proper checking + + if (check_via_path(value) >= 0) { + b->via = strdup(value); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: invalid via path.\n", line); + } + + +#else // previously + b->via = strdup(value); for (p = b->via; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } } +#endif } else if (strcasecmp(keyword, "INFO") == 0) { b->custom_info = strdup(value); diff --git a/config.h b/config.h index 4ea2eda7..c895e290 100644 --- a/config.h +++ b/config.h @@ -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 */ @@ -23,7 +24,7 @@ * This wasn't thought out. It just happened. */ -enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM }; +enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM, BEACON_IGATE }; enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV }; @@ -43,6 +44,7 @@ struct misc_config_s { char gpsnmea_port[20]; /* Serial port name for reading NMEA sentences from GPS. */ /* e.g. COM22, /dev/ttyACM0 */ + /* Currently no option for setting non-standard speed. */ char gpsd_host[20]; /* Host for gpsd server. */ /* e.g. localhost, 192.168.1.2 */ @@ -50,9 +52,19 @@ struct misc_config_s { int gpsd_port; /* Port number for gpsd server. */ /* Default is 2947. */ + + char waypoint_port[20]; /* Serial port name for sending NMEA waypoint sentences */ + /* to a GPS map display or other mapping application. */ /* e.g. COM22, /dev/ttyACM0 */ - char nmea_port[20]; /* Serial port name for NMEA communication with GPS */ - /* receiver and/or mapping application. Change this. */ + /* Currently no option for setting non-standard speed. */ + + int waypoint_formats; /* Which sentence formats should be generated? */ + +#define WPT_FORMAT_NMEA_GENERIC 0x01 /* N $GPWPT */ +#define WPT_FORMAT_GARMIN 0x02 /* G $PGRMW */ +#define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */ +#define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */ + char logdir[80]; /* Directory for saving activity logs. */ @@ -65,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. */ @@ -155,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); diff --git a/decode_aprs.c b/decode_aprs.c index b259aa47..00f8e80d 100644 --- a/decode_aprs.c +++ b/decode_aprs.c @@ -33,6 +33,8 @@ * *------------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -43,12 +45,8 @@ #include /* for isdigit */ #include -#ifndef _POSIX_C_SOURCE -#define _POSIX_C_SOURCE 1 -#endif #include "regex.h" -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "symbols.h" @@ -110,7 +108,7 @@ static void aprs_station_capabilities (decode_aprs_t *A, char *, int); static void aprs_status_report (decode_aprs_t *A, char *, int); static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet); static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet); -static void aprs_telemetry (decode_aprs_t *A, char *, int, int quiet); +static void aprs_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet); static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int); static void aprs_morse_code (decode_aprs_t *A, char *, int); static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int); @@ -166,7 +164,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) A->g_quiet = quiet; - snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown message type %c", *pinfo); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown APRS Data Type Indicator \"%c\"", *pinfo); A->g_symbol_table = '/'; /* Default to primary table. */ A->g_symbol_code = ' '; /* What should we have for default symbol? */ @@ -201,6 +199,33 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src); ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest); +/* + * Report error if the information part contains a nul character. + * There are two known cases where this can happen. + * + * - The Kenwood TM-D710A sometimes sends packets like this: + * + * VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast= + * K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV = + * + * Notice that the data type indicator of "4" is not valid. If we remove + * 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format. + * This same thing has been observed from others and is intermittent. + * + * - AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes. + * This is wrong, it should be using UTF-8. + */ + + if ( ( ! A->g_quiet ) && ( (int)strlen((char*)pinfo) != info_len) ) { + + text_color_set(DW_COLOR_ERROR); + dw_printf("'nul' character found in Information part. This should never happen.\n"); + dw_printf("It seems that %s is transmitting with defective software.\n", A->g_src); + + if (strcmp((char*)pinfo, "4P") == 0) { + dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n"); + } + } switch (*pinfo) { /* "DTI" data type identifier. */ @@ -254,8 +279,9 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) break; - case ':': /* Message */ + case ':': /* Message: for one person, a group, or a bulletin. */ /* Directed Station Query */ + /* Telemetry metadata. */ aprs_message (A, pinfo, info_len, quiet); break; @@ -638,14 +664,14 @@ void decode_aprs_print (decode_aprs_t *A) { if ( ! A->g_quiet) { for (j=0; jg_comment[j] == (char)0xb0 && (j == 0 || ! (A->g_comment[j-1] & 0x80))) { + if ((unsigned char)(A->g_comment[j]) == 0xb0 && (j == 0 || ! (A->g_comment[j-1] & 0x80))) { text_color_set(DW_COLOR_ERROR); dw_printf("Character code 0xb0 is probably an attempt at a degree symbol.\n"); dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n"); } } for (j=0; jg_comment[j] == (char)0xf8 && (j == n-1 || (A->g_comment[j+1] & 0xc0) != 0xc0)) { + if ((unsigned char)(A->g_comment[j]) == 0xf8 && (j == n-1 || (A->g_comment[j+1] & 0xc0) != 0xc0)) { text_color_set(DW_COLOR_ERROR); dw_printf("Character code 0xf8 is probably an attempt at a degree symbol.\n"); dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n"); @@ -974,6 +1000,7 @@ N1ZZN-9>T2SP0W:`c_Vm6hk/ "49}Originl Mic-E (leading space) N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D7A walkie Talkie N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D72 walkie Talkie= +W6GPS>S4PT3R:`p(1oR0K\>TH-D74A^ N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D700 MObile Radio N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D710 Mobile Radio= @@ -1328,38 +1355,44 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int #define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'') +// Last updated Sept. 2016 for TH-D74A if (isT(*pfirst)) { - if (*pfirst == ' ') { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } + if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } + + else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; } + else if (*pfirst == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; } + else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } + + else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } + else if (*pfirst == ']' ) { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; } - else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; } - else if (*pfirst == '>') { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } - else if (*pfirst == ']') { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; } + else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; } - else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*(plast-1) == '\\') { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*(plast-1) == '/') { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*(plast-1) == '^') { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*(plast-1) == '*') { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*(plast-1) == '~') { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } - // Should Original Mic-E and Kenwood be moved down to here? + else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; } - else if (*pfirst == '`') { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'') { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if ( *(plast-1) == '^' ) { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if ( *(plast-1) == '*' ) { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; } } /* @@ -1409,22 +1442,45 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * * Function: aprs_message * - * Purpose: Decode "Message Format" + * Purpose: Decode "Message Format." + * The word message is used loosely all over the place, but it has a very specific meaning here. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * quiet - supress error messages. * - * Outputs: ??? TBD + * Outputs: A->g_msg_type Text description for screen display. + * + * A->g_addressee To whom is it addressed. + * Could be a specific station, alias, bulletin, etc. + * For telemetry metadata is is about this station, + * not being sent to it. + * + * A->g_message_subtype Subtype so caller might avoid replicating + * all the code to distinguish them. + * + * A->g_message_number Message number if any. Required for ack/rej. * * Description: An APRS message is a text string with a specifed addressee. * * It's a lot more complicated with different types of addressees * and replies with acknowledgement or rejection. * + * There is even a special case for telemetry metadata. * - * Examples: ... - * + * + * Cases: :xxxxxxxxx:PARM. Telemetry metadata, parameter name + * :xxxxxxxxx:UNIT. Telemetry metadata, unit/label + * :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficents + * :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name + * :xxxxxxxxx:? Directed Station Query + * :xxxxxxxxx:ack Message acknowledged (received) + * :xxxxxxxxx:rej Message rejected (unable to accept) + * + * :xxxxxxxxx: ... Message with no message number. + * (Text may not contain the { character because + * it indicates beginning of optional message number.) + * :xxxxxxxxx: ... {num Message with message number. * *------------------------------------------------------------------*/ @@ -1436,7 +1492,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q char addressee[9]; char colon; /* : */ char message[73]; /* 0-67 characters for message */ - /* { followed by 1-5 characters for message number */ + /* Optional { followed by 1-5 characters for message number */ /* If the first chracter is '?' it is a Directed Station Query. */ } *p; @@ -1447,20 +1503,23 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q p = (struct aprs_message_s *)info; strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type)); + A->g_message_subtype = message_subtype_message; /* until found otherwise */ if (ilen < 11) { if (! quiet) { text_color_set(DW_COLOR_ERROR); - dw_printf("Message must have a minimum of 11 characters for : addressee :\n"); + dw_printf("APRS Message must have a minimum of 11 characters for : 9 character addressee :\n"); } + A->g_message_subtype = message_subtype_invalid; return; } if (p->colon != ':') { if (! quiet) { text_color_set(DW_COLOR_ERROR); - dw_printf("Message must begin with : addressee :\n"); + dw_printf("APRS Message must begin with : 9 character addressee :\n"); } + A->g_message_subtype = message_subtype_invalid; return; } @@ -1475,6 +1534,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee)); + /* * Special message formats contain telemetry metadata. * It applies to the addressee, not the sender. @@ -1488,18 +1548,22 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q if (strncmp(p->message,"PARM.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Parameter Name Message for \"%s\"", addressee); + A->g_message_subtype = message_subtype_telem_parm; telemetry_name_message (addressee, p->message+5); } else if (strncmp(p->message,"UNIT.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Unit/Label Message for \"%s\"", addressee); + A->g_message_subtype = message_subtype_telem_unit; telemetry_unit_label_message (addressee, p->message+5); } else if (strncmp(p->message,"EQNS.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee); + A->g_message_subtype = message_subtype_telem_eqns; telemetry_coefficents_message (addressee, p->message+5, quiet); } else if (strncmp(p->message,"BITS.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee); + A->g_message_subtype = message_subtype_telem_bits; telemetry_bit_sense_message (addressee, p->message+5, quiet); } @@ -1510,11 +1574,33 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q else if (p->message[0] == '?') { strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type)); + A->g_message_subtype = message_subtype_directed_query; aprs_directed_station_query (A, addressee, p->message+1, quiet); } + +/* ack or rej? Message number is required for these. */ + + else if (strncmp(p->message,"ack",3) == 0) { + strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number)); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "ACK message %s for \"%s\"", A->g_message_number, addressee); + A->g_message_subtype = message_subtype_ack; + } + else if (strncmp(p->message,"rej",3) == 0) { + strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number)); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "REJ message %s for \"%s\"", A->g_message_number, addressee); + A->g_message_subtype = message_subtype_ack; + } + +/* message number is optional here. */ + else { - snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message for \"%s\"", addressee); + char *pno = strchr(p->message, '{'); + if (pno != NULL) { + strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number)); + } + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message %s for \"%s\"", A->g_message_number, addressee); + A->g_message_subtype = message_subtype_message; /* No location so don't use process_comment () */ @@ -1666,25 +1752,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; @@ -3014,7 +3102,7 @@ double get_latitude_8 (char *p, int quiet) return (G_UNKNOWN); } - if (plat->minn[0] >= '0' || plat->minn[0] <= '5') + if (plat->minn[0] >= '0' && plat->minn[0] <= '5') result += ((plat->minn[0]) - '0') * (10. / 60.); else if (plat->minn[0] == ' ') ; @@ -3177,7 +3265,7 @@ double get_longitude_9 (char *p, int quiet) return (G_UNKNOWN); } - if (plon->minn[0] >= '0' || plon->minn[0] <= '5') + if (plon->minn[0] >= '0' && plon->minn[0] <= '5') result += ((plon->minn[0]) - '0') * (10. / 60.); else if (plon->minn[0] == ' ') ; @@ -3621,7 +3709,15 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext) * *------------------------------------------------------------------*/ -#define MAX_TOCALLS 150 +// If I was more ambitious, this would dynamically allocate enough +// storage based on the file contents. Just stick in a constant for +// now. This takes an insignificant amount of space and +// I don't anticipate tocalls.txt growing that quickly. +// Version 1.4 - add message if too small instead of silently ignoring the rest. + +// Dec. 2016 tocalls.txt has 153 destination addresses. + +#define MAX_TOCALLS 200 static struct tocalls_s { unsigned char len; @@ -3718,7 +3814,7 @@ static void decode_tocall (decode_aprs_t *A, char *dest) if (strlen(tocalls[num_tocalls].prefix) > 2) { tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); - // dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); + // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); num_tocalls++; } @@ -3742,11 +3838,15 @@ static void decode_tocall (decode_aprs_t *A, char *dest) if (strlen(tocalls[num_tocalls].prefix) > 2) { tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); - // dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); + // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); num_tocalls++; } } + if (num_tocalls == MAX_TOCALLS) { // oops. might have discarded some. + text_color_set(DW_COLOR_ERROR); + dw_printf("MAX_TOCALLS needs to be larger than %d to handle contents of 'tocalls.txt'.\n", MAX_TOCALLS); + } } fclose(fp); @@ -3770,9 +3870,12 @@ static void decode_tocall (decode_aprs_t *A, char *dest) dw_printf("System types in the destination field will not be decoded.\n"); } } - first_time = 0; + + //for (n=0; n '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description); + //} } @@ -3831,7 +3934,7 @@ static void substr_se (char *dest, const char *src, int start, int endp1) * clen - Length of comment or -1 to take it all. * * Outputs: A->g_telemetry - Base 91 telemetry |ss1122| - * A->g_altitude_ft - from /A=123456 + * A->g_altitude_ft - from /A=123456 * A->g_lat - Might be adjusted from !DAO! * A->g_lon - Might be adjusted from !DAO! * A->g_aprstt_loc - Private extension to !DAO! @@ -3862,6 +3965,49 @@ static void substr_se (char *dest, const char *src, int start, int endp1) * * /A=123456 Altitude * + * What can appear in a comment? + * + * Chapter 5 of the APRS spec ( http://www.aprs.org/doc/APRS101.PDF ) says: + * + * "The comment may contain any printable ASCII characters (except | and ~, + * which are reserved for TNC channel switching)." + * + * "Printable" would exclude character values less than space (00100000), e.g. + * tab, carriage return, line feed, nul. Sometimes we see carriage return + * (00001010) at the end of APRS packets. This would be in violation of the + * specification. + * + * The base 91 telemetry format (http://he.fi/doc/aprs-base91-comment-telemetry.txt ), + * which is not part of the APRS spec, uses the | character in the comment to delimit encoded + * telemetry data. This would be in violation of the original spec. + * + * The APRS Spec Addendum 1.2 Proposals ( http://www.aprs.org/aprs12/datum.txt) + * adds use of UTF-8 (https://en.wikipedia.org/wiki/UTF-8 )for the free form text in + * messages and comments. It can't be used in the fixed width fields. + * + * Non-ASCII characters are represented by multi-byte sequences. All bytes in these + * multi-byte sequences have the most significant bit set to 1. Using UTF-8 would not + * add any nul (00000000) bytes to the stream. + * + * There are two known cases where we can have a nul character value. + * + * * The Kenwood TM-D710A sometimes sends packets like this: + * + * VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast= + * K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV = + * + * Notice that the data type indicator of "4" is not valid. If we remove + * 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format. + * This same thing has been observed from others and is intermittent. + * + * * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes. + * This is wrong. It should be using UTF-8 and I'm not going to accomodate it here. + * + * + * The digipeater and IGate functions should pass along anything exactly the + * we received it, even if it is invalid. If different implementations try to fix it up + * somehow, like changing unprintable characters to spaces, we will only make things + * worse and thwart the duplicate detection. * *------------------------------------------------------------------*/ @@ -4026,7 +4172,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) * digipeated, causing the comment to be hundreds of characters long. */ - if (clen > sizeof(A->g_comment) - 1) { + if (clen > (int)(sizeof(A->g_comment) - 1)) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Comment is extremely long, %d characters.\n", clen); diff --git a/decode_aprs.h b/decode_aprs.h index a767ed2b..cdbb1678 100644 --- a/decode_aprs.h +++ b/decode_aprs.h @@ -26,7 +26,9 @@ typedef struct decode_aprs_s { char g_src[AX25_MAX_ADDR_LEN]; - char g_msg_type[60]; /* Message type. Telemetry descriptions get pretty long. */ + char g_msg_type[60]; /* APRS data type. Telemetry descriptions get pretty long. */ + /* Putting msg in the name was a poor choice because */ + /* "message" has a specific meaning. Rename it someday. */ char g_symbol_table; /* The Symbol Table Identifier character selects one */ /* of the two Symbol Tables, or it may be used as */ @@ -64,6 +66,19 @@ typedef struct decode_aprs_s { /* Also for Directed Station Query which is a */ /* special case of message. */ + enum message_subtype_e { message_subtype_invalid = 0, + message_subtype_message, + message_subtype_ack, + message_subtype_rej, + message_subtype_telem_parm, + message_subtype_telem_unit, + message_subtype_telem_eqns, + message_subtype_telem_bits, + message_subtype_directed_query + } g_message_subtype; /* Various cases of the overloaded "message." */ + + char g_message_number[8]; /* Message number. Should be 1 - 5 characters if used. */ + float g_speed_mph; /* Speed in MPH. */ float g_course; /* 0 = North, 90 = East, etc. */ diff --git a/dedupe.c b/dedupe.c index 66a035f3..cb012ebb 100644 --- a/dedupe.c +++ b/dedupe.c @@ -96,6 +96,7 @@ #define DEDUPE_C +#include "direwolf.h" #include #include diff --git a/demod.c b/demod.c index a4944afe..0022254f 100644 --- a/demod.c +++ b/demod.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 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 @@ -18,14 +18,6 @@ // -// #define DEBUG1 1 /* display debugging info */ - -// #define DEBUG3 1 /* print carrier detect changes. */ - -// #define DEBUG4 1 /* capture AFSK demodulator output to log files */ - -// #define DEBUG5 1 /* capture 9600 output to log files */ - /*------------------------------------------------------------------ * @@ -39,6 +31,7 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" #include #include @@ -49,7 +42,6 @@ #include #include -#include "direwolf.h" #include "audio.h" #include "demod.h" #include "tune.h" @@ -60,6 +52,7 @@ #include "textcolor.h" #include "demod_9600.h" #include "demod_afsk.h" +#include "demod_psk.h" @@ -68,13 +61,16 @@ static struct audio_s *save_audio_config_p; +// TODO: temp experiment. + +static int upsample = 2; // temp experiment. +static int zerostuff = 1; // temp experiment. + // Current state of all the decoders. static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS]; -#define UPSAMPLE 2 - static int sample_sum[MAX_CHANS][MAX_SUBCHANS]; static int sample_count[MAX_CHANS][MAX_SUBCHANS]; @@ -188,7 +184,7 @@ int demod_init (struct audio_s *pa) } } - assert (num_letters == strlen(just_letters)); + assert (num_letters == (int)(strlen(just_letters))); /* * Pick a good default demodulator if none specified. @@ -225,7 +221,8 @@ int demod_init (struct audio_s *pa) num_letters = 1; } - assert (num_letters == strlen(just_letters)); + + assert (num_letters == (int)(strlen(just_letters))); /* * Put it back together again. @@ -403,11 +400,11 @@ int demod_init (struct audio_s *pa) D->num_slicers = MAX_SLICERS; } - /* For siginal level reporting, we want a longer term view. */ + /* For signal level reporting, we want a longer term view. */ // TODO: Should probably move this into the init functions. - D->quick_attack = D->agc_fast_attack * 0.2; - D->sluggish_decay = D->agc_slow_decay * 0.2; + D->quick_attack = D->agc_fast_attack * 0.2f; + D->sluggish_decay = D->agc_slow_decay * 0.2f; } } else if (have_plus) { @@ -459,10 +456,10 @@ int demod_init (struct audio_s *pa) D->num_slicers = MAX_SLICERS; } - /* For siginal level reporting, we want a longer term view. */ + /* For signal level reporting, we want a longer term view. */ - D->quick_attack = D->agc_fast_attack * 0.2; - D->sluggish_decay = D->agc_slow_decay * 0.2; + D->quick_attack = D->agc_fast_attack * 0.2f; + D->sluggish_decay = D->agc_slow_decay * 0.2f; } else { int d; @@ -512,15 +509,123 @@ int demod_init (struct audio_s *pa) D->num_slicers = MAX_SLICERS; } - /* For siginal level reporting, we want a longer term view. */ + /* For signal level reporting, we want a longer term view. */ - D->quick_attack = D->agc_fast_attack * 0.2; - D->sluggish_decay = D->agc_slow_decay * 0.2; + D->quick_attack = D->agc_fast_attack * 0.2f; + D->sluggish_decay = D->agc_slow_decay * 0.2f; } /* for each freq pair */ } break; + case MODEM_QPSK: // New for 1.4 + +// TODO: See how much CPU this takes on ARM and decide if we should have different defaults. + + if (strlen(save_audio_config_p->achan[chan].profiles) == 0) { +//#if __arm__ +// strlcpy (save_audio_config_p->achan[chan].profiles, "R", sizeof(save_audio_config_p->achan[chan].profiles)); +//#else + strlcpy (save_audio_config_p->achan[chan].profiles, "PQRS", sizeof(save_audio_config_p->achan[chan].profiles)); +//#endif + } + save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles); + + save_audio_config_p->achan[chan].decimate = 1; // think about this later. + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Channel %d: %d bps, QPSK, %s, %d sample rate", + chan, save_audio_config_p->achan[chan].baud, + save_audio_config_p->achan[chan].profiles, + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); + if (save_audio_config_p->achan[chan].decimate != 1) + dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); + if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) + dw_printf (", DTMF decoder enabled"); + dw_printf (".\n"); + + int d; + for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { + + assert (d >= 0 && d < MAX_SUBCHANS); + struct demodulator_state_s *D; + D = &demodulator_state[chan][d]; + profile = save_audio_config_p->achan[chan].profiles[d]; + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("About to call demod_psk_init for Q-PSK case, modem_type=%d, profile='%c'\n", + // save_audio_config_p->achan[chan].modem_type, profile); + + demod_psk_init (save_audio_config_p->achan[chan].modem_type, + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, + save_audio_config_p->achan[chan].baud, + profile, + D); + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Returned from demod_psk_init\n"); + + /* For signal level reporting, we want a longer term view. */ + /* Guesses based on 9600. Maybe revisit someday. */ + + D->quick_attack = 0.080 * 0.2; + D->sluggish_decay = 0.00012 * 0.2; + } + break; + + case MODEM_8PSK: // New for 1.4 + +// TODO: See how much CPU this takes on ARM and decide if we should have different defaults. + + if (strlen(save_audio_config_p->achan[chan].profiles) == 0) { +//#if __arm__ +// strlcpy (save_audio_config_p->achan[chan].profiles, "V", sizeof(save_audio_config_p->achan[chan].profiles)); +//#else + strlcpy (save_audio_config_p->achan[chan].profiles, "TUVW", sizeof(save_audio_config_p->achan[chan].profiles)); +//#endif + } + save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles); + + save_audio_config_p->achan[chan].decimate = 1; // think about this later + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Channel %d: %d bps, 8PSK, %s, %d sample rate", + chan, save_audio_config_p->achan[chan].baud, + save_audio_config_p->achan[chan].profiles, + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); + if (save_audio_config_p->achan[chan].decimate != 1) + dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); + if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) + dw_printf (", DTMF decoder enabled"); + dw_printf (".\n"); + + //int d; + for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { + + assert (d >= 0 && d < MAX_SUBCHANS); + struct demodulator_state_s *D; + D = &demodulator_state[chan][d]; + profile = save_audio_config_p->achan[chan].profiles[d]; + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("About to call demod_psk_init for 8-PSK case, modem_type=%d, profile='%c'\n", + // save_audio_config_p->achan[chan].modem_type, profile); + + demod_psk_init (save_audio_config_p->achan[chan].modem_type, + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, + save_audio_config_p->achan[chan].baud, + profile, + D); + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Returned from demod_psk_init\n"); + + /* For signal level reporting, we want a longer term view. */ + /* Guesses based on 9600. Maybe revisit someday. */ + + D->quick_attack = 0.080 * 0.2; + D->sluggish_decay = 0.00012 * 0.2; + } + break; + //TODO: how about MODEM_OFF case? case MODEM_BASEBAND: @@ -539,11 +644,20 @@ int demod_init (struct audio_s *pa) #endif } +#ifdef TUNE_UPSAMPLE + upsample = TUNE_UPSAMPLE; +#endif + + +#ifdef TUNE_ZEROSTUFF + zerostuff = TUNE_ZEROSTUFF; +#endif + text_color_set(DW_COLOR_DEBUG); dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d", chan, save_audio_config_p->achan[chan].baud, save_audio_config_p->achan[chan].profiles, - save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, UPSAMPLE); + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, upsample); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); @@ -562,7 +676,36 @@ int demod_init (struct audio_s *pa) save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; } - demod_9600_init (UPSAMPLE * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D); + + /* We need a minimum number of audio samples per bit time for good performance. */ + /* Easier to check here because demod_9600_init might have an adjusted sample rate. */ + + float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec) + / (float)(save_audio_config_p->achan[chan].baud); + + text_color_set(DW_COLOR_INFO); + dw_printf ("The ratio of audio samples per sec (%d) to data rate in baud (%d) is %.1f\n", + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, + save_audio_config_p->achan[chan].baud, + (double)ratio); + if (ratio < 3) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("There is little hope of success with such a low ratio. Use a higher sample rate.\n"); + } + else if (ratio < 5) { + dw_printf ("This is on the low side for best performance. Can you use a higher sample rate?\n"); + } + else if (ratio < 6) { + dw_printf ("Increasing the sample rate should improve decoder performance.\n"); + } + else if (ratio > 15) { + dw_printf ("Sample rate is more than adequate. You might lower it if CPU load is a concern.\n"); + } + else { + dw_printf ("This is a suitable ratio for good performance.\n"); + } + + demod_9600_init (upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D); if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) { @@ -573,10 +716,10 @@ int demod_init (struct audio_s *pa) D->num_slicers = MAX_SLICERS; } - /* For siginal level reporting, we want a longer term view. */ + /* For signal level reporting, we want a longer term view. */ - D->quick_attack = D->agc_fast_attack * 0.2; - D->sluggish_decay = D->agc_slow_decay * 0.2; + D->quick_attack = D->agc_fast_attack * 0.2f; + D->sluggish_decay = D->agc_slow_decay * 0.2f; } break; @@ -699,17 +842,9 @@ __attribute__((hot)) void demod_process_sample (int chan, int subchan, int sam) { float fsam; - //float abs_fsam; int k; -#if DEBUG4 - static FILE *demod_log_fp = NULL; - static int seq = 0; /* for log file name */ -#endif - - //int j; - //int demod_data; struct demodulator_state_s *D; assert (chan >= 0 && chan < MAX_CHANS); @@ -774,58 +909,73 @@ void demod_process_sample (int chan, int subchan, int sam) } } else { - demod_afsk_process_sample (chan, subchan, sam, D); + demod_afsk_process_sample (chan, subchan, sam, D); + } + break; + + case MODEM_QPSK: + case MODEM_8PSK: + + if (save_audio_config_p->achan[chan].decimate > 1) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid combination of options. Exiting.\n"); + // Would probably work but haven't thought about it or tested yet. + exit (1); + } + else { + demod_psk_process_sample (chan, subchan, sam, D); } break; case MODEM_BASEBAND: case MODEM_SCRAMBLE: default: - -#define ZEROSTUFF 1 - -#if ZEROSTUFF - /* Literature says this is better if followed */ - /* by appropriate low pass filter. */ - /* So far, both are same in tests with different */ - /* optimal low pass filter parameters. */ - - for (k=1; kalevel_mark_peak ) * 100.0f + 0.5f); alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f); - - //alevel.ms_ratio = D->alevel_mark_peak / D->alevel_space_peak; // TODO: remove after temp test + } + else if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK || + save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) { + alevel.mark = -1; + alevel.space = -1; } else { diff --git a/demod_9600.c b/demod_9600.c index 62faa01a..58c4c047 100644 --- a/demod_9600.c +++ b/demod_9600.c @@ -18,7 +18,7 @@ // -// #define DEBUG5 1 /* capture 9600 output to log files */ +//#define DEBUG4 1 /* capture 9600 output to log files */ /*------------------------------------------------------------------ @@ -33,6 +33,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -42,7 +44,6 @@ #include #include -#include "direwolf.h" #include "tune.h" #include "fsk_demod_state.h" #include "hdlc_rec.h" @@ -72,25 +73,10 @@ static inline float convolve (const float *__restrict__ data, const float *__res float sum = 0.0f; int j; -#if 0 - // As suggested here, http://locklessinc.com/articles/vectorize/ - // Unfortunately, older compilers don't recognize it. - - // Get more information by using -ftree-vectorizer-verbose=5 - - float *d = __builtin_assume_aligned(data, 16); - float *f = __builtin_assume_aligned(filter, 16); - -#pragma GCC ivdep - for (j=0; j= *ppeak) { - *ppeak = in * fast_attack + *ppeak * (1. - fast_attack); + *ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack); } else { - *ppeak = in * slow_decay + *ppeak * (1. - slow_decay); + *ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay); } if (in <= *pvalley) { - *pvalley = in * fast_attack + *pvalley * (1. - fast_attack); + *pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack); } else { - *pvalley = in * slow_decay + *pvalley * (1. - slow_decay); + *pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay); } if (*ppeak > *pvalley) { - return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); + return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); } return (0.0); } @@ -125,11 +111,11 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p * * Name: demod_9600_init * - * Purpose: Initialize the 9600 baud demodulator. + * Purpose: Initialize the 9600 (or higher) baud demodulator. * * Inputs: samples_per_sec - Number of samples per second. - * Might be upsampled in hopes of - * reducing the PLL jitter. + * Might be upsampled in hopes of + * reducing the PLL jitter. * * baud - Data rate in bits per second. * @@ -147,21 +133,33 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s memset (D, 0, sizeof(struct demodulator_state_s)); D->num_slicers = 1; - //dw_printf ("demod_9600_init(rate=%d, baud=%d, D ptr)\n", samples_per_sec, baud); +// Multiple profiles in future? - D->pll_step_per_sample = - (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); - - D->lp_filter_len_bits = 72 * 9600.0 / (44100.0 * 2.0); - D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5); - D->lp_window = BP_WINDOW_HAMMING; - D->lpf_baud = 0.59; +// switch (profile) { + +// case 'J': // upsample x2 with filtering. +// case 'K': // upsample x3 with filtering. +// case 'L': // upsample x4 with filtering. + + D->lp_filter_len_bits = 76 * 9600.0 / (44100.0 * 2.0); - D->agc_fast_attack = 0.080; - D->agc_slow_decay = 0.00012; + // Works best with odd number in some tests. Even is better in others. + //D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ))) * 2 + 1; + D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f); - D->pll_locked_inertia = 0.88; - D->pll_searching_inertia = 0.67; + D->lp_window = BP_WINDOW_HAMMING; + D->lpf_baud = 0.62; + + D->agc_fast_attack = 0.080; + D->agc_slow_decay = 0.00012; + + D->pll_locked_inertia = 0.89; + D->pll_searching_inertia = 0.67; +// break; +// } + + D->pll_step_per_sample = + (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); #ifdef TUNE_LP_WINDOW @@ -184,8 +182,11 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s D->agc_slow_decay = TUNE_AGC_SLOW; #endif -#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING) +#if defined(TUNE_PLL_LOCKED) D->pll_locked_inertia = TUNE_PLL_LOCKED; +#endif + +#if defined(TUNE_PLL_SEARCHING) D->pll_searching_inertia = TUNE_PLL_SEARCHING; #endif @@ -198,7 +199,7 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s /* Version 1.2: Experiment with different slicing levels. */ for (j = 0; j < MAX_SUBCHANS; j++) { - slice_point[j] = 0.02 * (j - 0.5 * (MAX_SUBCHANS-1)); + slice_point[j] = 0.02f * (j - 0.5f * (MAX_SUBCHANS-1)); //dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]); } @@ -260,23 +261,22 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s * *--------------------------------------------------------------------*/ -static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D); +inline static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D); __attribute__((hot)) void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D) { float fsam; - //float abs_fsam; float amp; float demod_out; -#if DEBUG5 +#if DEBUG4 static FILE *demod_log_fp = NULL; - static int seq = 0; /* for log file name */ + static int log_file_seq = 0; /* Part of log file name */ #endif - //int j; + int subchan = 0; int demod_data; /* Still scrambled. */ @@ -306,6 +306,12 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D fsam = sam / 16384.0; +#if defined(TUNE_ZEROSTUFF) && TUNE_ZEROSTUFF == 0 +// experiment - no filtering. + + amp = fsam; + +#else push_sample (fsam, D->raw_cb, D->lp_filter_size); /* @@ -313,7 +319,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D */ amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size); - +#endif /* * Version 1.2: Capture the post-filtering amplitude for display. @@ -324,18 +330,20 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * Here we keep + and - peaks because there could be a DC bias. */ +// TODO: probably no need for this. Just use D->m_peak, D->m_valley + if (amp >= D->alevel_mark_peak) { - D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1. - D->quick_attack); + D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack); } else { - D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1. - D->sluggish_decay); + D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay); } if (amp <= D->alevel_space_peak) { - D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1. - D->quick_attack); + D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack); } else { - D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1. - D->sluggish_decay); + D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay); } /* @@ -349,28 +357,10 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); - // TODO: There is potential for multiple decoders with one filter. //dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm); - /* Throw in a little Hysteresis??? */ - /* (Not to be confused with Hysteria.) */ - /* Doesn't seem to have any value. */ - /* Using a level of .02 makes things worse. */ - /* Might want to experiment with this again someday. */ - - -// if (demod_out > 0.03) { -// demod_data = 1; -// } -// else if (demod_out < -0.03) { -// demod_data = 0; -// } -// else { -// demod_data = D->slicer[subchan].prev_demod_data; -// } - if (D->num_slicers <= 1) { /* Normal case of one demodulator to one HDLC decoder. */ @@ -378,7 +368,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D /* AGC should generally keep this around -1 to +1 range. */ demod_data = demod_out > 0; - nudge_pll (chan, subchan, 0, demod_data, D); + nudge_pll (chan, subchan, 0, demod_out, D); } else { int slice; @@ -386,76 +376,147 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D /* Multiple slicers each feeding its own HDLC decoder. */ for (slice=0; slicenum_slicers; slice++) { - demod_data = demod_out > slice_point[slice]; - nudge_pll (chan, subchan, slice, demod_data, D); + demod_data = demod_out - slice_point[slice] > 0; + nudge_pll (chan, subchan, slice, demod_out - slice_point[slice], D); } } -} /* end demod_9600_process_sample */ + // demod_data is used only for debug out. + // suppress compiler warning about it not being used. + (void) demod_data; + +#if DEBUG4 + + if (chan == 0) { + + if (1) { + //if (hdlc_rec_gathering (chan, subchan, slice)) { + char fname[30]; + int slice = 0; + + if (demod_log_fp == NULL) { + log_file_seq++; + snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq); + //if (log_file_seq == 1) mkdir ("demod", 0777); + if (log_file_seq == 1) mkdir ("demod"); + + demod_log_fp = fopen (fname, "w"); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Starting demodulator log file %s\n", fname); + fprintf (demod_log_fp, "Audio, Filtered, Max, Min, Normalized, Sliced, Clock\n"); + } + + fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %d, %.2f\n", + fsam + 6, + amp + 4, + D->m_peak + 4, + D->m_valley + 4, + demod_out + 2, + demod_data + 2, + (D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0); + + fflush (demod_log_fp); + } + else { + if (demod_log_fp != NULL) { + fclose (demod_log_fp); + demod_log_fp = NULL; + } + } + } +#endif +} /* end demod_9600_process_sample */ -__attribute__((hot)) -static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) -{ -/* - * Next, a PLL is used to sample near the centers of the data bits. +/*------------------------------------------------------------------- + * + * Name: nudge_pll + * + * Purpose: Update the PLL state for each audio sample. + * + * (2) Descramble it. + * (2) Recover clock and data. + * + * Inputs: chan - Audio channel. 0 for left, 1 for right. + * + * subchan - Which demodulator. We could have several running in parallel. + * + * slice - Determines which Slicing level & HDLC decoder to use. + * + * demod_out_f - Demodulator output, possibly shifted by slicing level + * It will be compared with 0.0 to bit binary value out. + * + * D - Demodulator state for this channel / subchannel. + * + * Returns: None * - * D->data_clock_pll is a SIGNED 32 bit variable. - * When it overflows from a large positive value to a negative value, we - * sample a data bit from the demodulated signal. + * Descripton: A PLL is used to sample near the centers of the data bits. * - * Ideally, the the demodulated signal transitions should be near - * zero we we sample mid way between the transitions. + * D->data_clock_pll is a SIGNED 32 bit variable. + * When it overflows from a large positive value to a negative value, we + * sample a data bit from the demodulated signal. * - * Nudge the PLL by removing some small fraction from the value of - * data_clock_pll, pushing it closer to zero. - * - * This adjustment will never change the sign so it won't cause - * any erratic data bit sampling. + * Ideally, the the demodulated signal transitions should be near + * zero we we sample mid way between the transitions. * - * If we adjust it too quickly, the clock will have too much jitter. - * If we adjust it too slowly, it will take too long to lock on to a new signal. + * Nudge the PLL by removing some small fraction from the value of + * data_clock_pll, pushing it closer to zero. * - * I don't think the optimal value will depend on the audio sample rate - * because this happens for each transition from the demodulator. + * This adjustment will never change the sign so it won't cause + * any erratic data bit sampling. * - * This was optimized for 1200 baud AFSK. There might be some opportunity - * for improvement here. + * If we adjust it too quickly, the clock will have too much jitter. + * If we adjust it too slowly, it will take too long to lock on to a new signal. + * + * I don't think the optimal value will depend on the audio sample rate + * because this happens for each transition from the demodulator. + * + * Version 1.4: Previously, we would always pull the PLL phase toward 0 after + * after a zero crossing was detetected. This adds extra jitter, + * especially when the ratio of audio sample rate to baud is low. + * Now, we interpolate between the two samples to get an estimate + * on when the zero crossing happened. The PLL is pulled toward + * this point. + * + * Results??? TBD + * + *--------------------------------------------------------------------*/ + +__attribute__((hot)) +inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D) +{ + +/* */ D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; D->slicer[slice].data_clock_pll += D->pll_step_per_sample; - if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) { + if ( D->slicer[slice].prev_d_c_pll > 1000000000 && D->slicer[slice].data_clock_pll < -1000000000) { - /* Overflow. */ + /* Overflow. Was large positive, wrapped around, now large negative. */ + + hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr); + } /* - * At this point, we need to descramble the data as - * in hardware based designs by G3RUH and K9NG. - * - * Future Idea: allow unscrambled baseband data. - * - * http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif + * Zero crossing? */ - // Warning: 'descram' set but not used. - // It's used in conditional debug code below. - // descram = - descramble (demod_data, &(D->slicer[slice].lfsr)); + if ((D->slicer[slice].prev_demod_out_f < 0 && demod_out_f > 0) || + (D->slicer[slice].prev_demod_out_f > 0 && demod_out_f < 0)) { - hdlc_rec_bit (chan, subchan, slice, demod_data, 1, D->slicer[slice].lfsr); - } + // Note: Test for this demodulator, not overall for channel. - if (demod_data != D->slicer[slice].prev_demod_data) { + float target = 0; - // Note: Test for this demodulator, not overall for channel. + target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f); if (hdlc_rec_gathering (chan, subchan, slice)) { - D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); + D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia + target * (1.0f - D->pll_locked_inertia) ); } else { - D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); + D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia + target * (1.0f - D->pll_searching_inertia) ); } } @@ -483,10 +544,10 @@ static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, fprintf (demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n"); } fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", - 0.5 * fsam + 3.5, - 0.5 * D->m_peak + 3.5, - 0.5 * D->m_valley + 3.5, - 0.5 * demod_out + 2.0, + 0.5f * fsam + 3.5, + 0.5f * D->m_peak + 3.5, + 0.5f * D->m_valley + 3.5, + 0.5f * demod_out + 2.0, demod_data ? 1.35 : 1.0, descram ? .9 : .55, (D->data_clock_pll & 0x80000000) ? .1 : .45); @@ -506,12 +567,9 @@ static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, * Remember demodulator output (pre-descrambling) so we can compare next time * for the DPLL sync. */ - D->slicer[slice].prev_demod_data = demod_data; + D->slicer[slice].prev_demod_out_f = demod_out_f; } /* end nudge_pll */ - - - /* end demod_9600.c */ diff --git a/demod_afsk.c b/demod_afsk.c index b769e216..e8252db9 100644 --- a/demod_afsk.c +++ b/demod_afsk.c @@ -39,7 +39,7 @@ * *---------------------------------------------------------------*/ - +#include "direwolf.h" #include #include @@ -50,9 +50,7 @@ #include #include -#include "direwolf.h" #include "audio.h" - #include "tune.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" @@ -65,7 +63,7 @@ #define MAX(a,b) ((a)>(b)?(a):(b)) - +#ifndef GEN_FFF /* Quick approximation to sqrt(x*x+y*y) */ /* No benefit for regular PC. */ @@ -105,7 +103,7 @@ static inline float convolve (const float *__restrict__ data, const float *__res int j; -#pragma GCC ivdep // ignored until gcc 4.9 +//#pragma GCC ivdep // ignored until gcc 4.9 for (j=0; jms_filter_size; j++) { float am; float center; - float shape = 1; /* Shape is an attempt to smooth out the */ + float shape = 1.0f; /* Shape is an attempt to smooth out the */ /* abrupt edges in hopes of reducing */ /* overshoot and ringing. */ /* My first thought was to use a cosine shape. */ @@ -538,16 +538,16 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, /* windows mentioned in the literature. */ /* http://en.wikipedia.org/wiki/Window_function */ - center = 0.5 * (D->ms_filter_size - 1); - am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2 * M_PI); + center = 0.5f * (D->ms_filter_size - 1); + am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2.0f * (float)M_PI); shape = window (D->ms_window, D->ms_filter_size, j); - D->m_sin_table[j] = sin(am) * shape; - D->m_cos_table[j] = cos(am) * shape; + D->m_sin_table[j] = sinf(am) * shape; + D->m_cos_table[j] = cosf(am) * shape; - Gs += D->m_sin_table[j] * sin(am); - Gc += D->m_cos_table[j] * cos(am); + Gs += D->m_sin_table[j] * sinf(am); + Gc += D->m_cos_table[j] * cosf(am); #if DEBUG1 dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ; @@ -578,18 +578,18 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, for (j=0; jms_filter_size; j++) { float as; float center; - float shape = 1; + float shape = 1.0f; center = 0.5 * (D->ms_filter_size - 1); - as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2 * M_PI); + as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2.0f * (float)M_PI); shape = window (D->ms_window, D->ms_filter_size, j); - D->s_sin_table[j] = sin(as) * shape; - D->s_cos_table[j] = cos(as) * shape; + D->s_sin_table[j] = sinf(as) * shape; + D->s_cos_table[j] = cosf(as) * shape; - Gs += D->s_sin_table[j] * sin(as); - Gc += D->s_cos_table[j] * cos(as); + Gs += D->s_sin_table[j] * sinf(as); + Gc += D->s_cos_table[j] * cosf(as); #if DEBUG1 dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ; @@ -756,7 +756,7 @@ int main (void) emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table); emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table); - exit(0); + exit(EXIT_SUCCESS); } #endif @@ -808,7 +808,7 @@ int main (void) * *--------------------------------------------------------------------*/ -static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D); +inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D); __attribute__((hot)) void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D) @@ -1088,7 +1088,7 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat __attribute__((hot)) -static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) +inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) { /* diff --git a/demod_psk.c b/demod_psk.c new file mode 100644 index 00000000..db9407aa --- /dev/null +++ b/demod_psk.c @@ -0,0 +1,856 @@ +// +// 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 . +// + + +//#define DEBUG1 1 /* display debugging info */ + +//#define DEBUG3 1 /* print carrier detect changes. */ + +//#define DEBUG4 1 /* capture PSK demodulator output to log files */ + + + +/*------------------------------------------------------------------ + * + * Module: demod_psk.c + * + * Purpose: Demodulator for Phase Shift Keying (PSK). + * + * This is my initial attempt at implementing a 2400 bps mode. + * The MFJ-2400 & AEA PK232-2400 used V.26 / Bell 201 so I will follow that precedent. + * + * + * Input: Audio samples from either a file or the "sound card." + * + * Outputs: Calls hdlc_rec_bit() for each bit demodulated. + * + * Current Status: New for Version 1.4. + * + * Don't know if this is correct and/or compatible with + * other implementations. + * There is a lot of stuff going on here with phase + * shifting, gray code, bit order for the dibit, NRZI and + * bit-stuffing for HDLC. Plenty of opportunity for + * misinterpreting a protocol spec or just stupid mistakes. + * + * References: MFJ-2400 Product description and manual: + * + * http://www.mfjenterprises.com/Product.php?productid=MFJ-2400 + * http://www.mfjenterprises.com/Downloads/index.php?productid=MFJ-2400&filename=MFJ-2400.pdf&company=mfj + * + * AEA had a 2400 bps packet modem, PK232-2400. + * + * http://www.repeater-builder.com/aea/pk232/pk232-2400-baud-dpsk-modem.pdf + * + * There was also a Kantronics KPC-2400 that had 2400 bps. + * + * http://www.brazoriacountyares.org/winlink-collection/TNC%20manuals/Kantronics/2400_modem_operators_guide@rgf.pdf + * + * + * The MFJ and AEA both use the EXAR XR-2123 PSK modem chip. + * The Kantronics has a P423 ??? + * + * Can't find the chip specs on the EXAR website so Google it. + * + * http://www.komponenten.es.aau.dk/fileadmin/komponenten/Data_Sheet/Linear/XR2123.pdf + * + * The XR-2123 implements the V.26 / Bell 201 standard: + * + * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26-198811-I!!PDF-E&type=items + * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26bis-198811-I!!PDF-E&type=items + * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26ter-198811-I!!PDF-E&type=items + * + * "bis" and "ter" are from Latin for second and third. + * I used the "ter" version which has phase shifts of 0, 90, 180, and 270 degrees. + * + * There are other references to an alternative B which uses other multiples of 45. + * The XR-2123 data sheet mentions only multiples of 90. That's what I went with. + * + * The XR-2123 does not perform the scrambling as specified in V.26 so I wonder if + * the vendors implemented it in software or just left it out. + * I left out scrambling for now. Eventually, I'd like to get my hands on an old + * 2400 bps TNC for compatibility testing. + * + * After getting QPSK working, it was not much more effort to add V.27 with 8 phases. + * + * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27bis-198811-I!!PDF-E&type=items + * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27ter-198811-I!!PDF-E&type=items + * + *---------------------------------------------------------------*/ + +#include "direwolf.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "audio.h" +#include "tune.h" +#include "fsk_demod_state.h" +#include "fsk_gen_filter.h" +#include "hdlc_rec.h" +#include "textcolor.h" +#include "demod_psk.h" +#include "dsp.h" + + +/* Add sample to buffer and shift the rest down. */ + +__attribute__((hot)) __attribute__((always_inline)) +static inline void push_sample (float val, float *buff, int size) +{ + memmove(buff+1,buff,(size-1)*sizeof(float)); + buff[0] = val; +} + + +/* FIR filter kernel. */ + +__attribute__((hot)) __attribute__((always_inline)) +static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size) +{ + float sum = 0.0; + int j; + + for (j=0; jms_filter_size + * + * Returns: None. + * + * Bugs: This doesn't do much error checking so don't give it + * anything crazy. + * + *----------------------------------------------------------------*/ + +void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D) +{ + int correct_baud; // baud is not same as bits/sec here! + int carrier_freq; + int j; + + + memset (D, 0, sizeof(struct demodulator_state_s)); + + D->modem_type = modem_type; + D->num_slicers = 1; // Haven't thought about this yet. Is it even applicable? + + + + +#ifdef TUNE_PROFILE + profile = TUNE_PROFILE; +#endif + + if (modem_type == MODEM_QPSK) { + + correct_baud = bps / 2; + // Originally I thought of scaling it to the data rate, + // e.g. 2400 bps -> 1800 Hz, but decided to make it a + // constant since it is the same for V.26 and V.27. + carrier_freq = 1800; + +#if DEBUG1 + dw_printf ("demod_psk_init QPSK (sample rate=%d, bps=%d, baud=%d, carrier=%d, profile=%c\n", + samples_per_sec, bps, correct_baud, carrier_freq, profile); +#endif + + switch (toupper(profile)) { + + case 'P': /* Self correlation technique. */ + + D->use_prefilter = 0; /* No bandpass filter. */ + + D->lpf_baud = 0.60; + D->lp_filter_len_bits = 39. * 1200. / 44100.; + D->lp_window = BP_WINDOW_COSINE; + + D->pll_locked_inertia = 0.95; + D->pll_searching_inertia = 0.50; + + break; + + case 'Q': /* Self correlation technique. */ + + D->use_prefilter = 1; /* Add a bandpass filter. */ + D->prefilter_baud = 1.3; + D->pre_filter_len_bits = 55. * 1200. / 44100.; + D->pre_window = BP_WINDOW_COSINE; + + D->lpf_baud = 0.60; + D->lp_filter_len_bits = 39. * 1200. / 44100.; + D->lp_window = BP_WINDOW_COSINE; + + D->pll_locked_inertia = 0.87; + D->pll_searching_inertia = 0.50; + + break; + + default: + text_color_set (DW_COLOR_ERROR); + dw_printf ("Invalid demodulator profile %c for v.26 QPSK. Valid choices are P, Q, R, S. Using default.\n", profile); + // fall thru. + + case 'R': /* Mix with local oscillator. */ + + D->psk_use_lo = 1; + + D->use_prefilter = 0; /* No bandpass filter. */ + + D->lpf_baud = 0.70; + D->lp_filter_len_bits = 37. * 1200. / 44100.; + D->lp_window = BP_WINDOW_TRUNCATED; + + D->pll_locked_inertia = 0.925; + D->pll_searching_inertia = 0.50; + + break; + + case 'S': /* Mix with local oscillator. */ + + D->psk_use_lo = 1; + + D->use_prefilter = 1; /* Add a bandpass filter. */ + D->prefilter_baud = 0.55; + D->pre_filter_len_bits = 74. * 1200. / 44100.; + D->pre_window = BP_WINDOW_FLATTOP; + + D->lpf_baud = 0.60; + D->lp_filter_len_bits = 39. * 1200. / 44100.; + D->lp_window = BP_WINDOW_COSINE; + + D->pll_locked_inertia = 0.925; + D->pll_searching_inertia = 0.50; + + break; + } + + D->ms_filter_len_bits = 1.25; // Delay line > 13/12 * symbol period + + D->coffs = (int) round( (11.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); + D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); + D->soffs = (int) round( (13.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); + } + else { + + correct_baud = bps / 3; + carrier_freq = 1800; + +#if DEBUG1 + dw_printf ("demod_psk_init 8-PSK (sample rate=%d, bps=%d, baud=%d, carrier=%d, profile=%c\n", + samples_per_sec, bps, correct_baud, carrier_freq, profile); +#endif + + switch (toupper(profile)) { + + + case 'T': /* Self correlation technique. */ + + D->use_prefilter = 0; /* No bandpass filter. */ + + D->lpf_baud = 1.15; + D->lp_filter_len_bits = 32. * 1200. / 44100.; + D->lp_window = BP_WINDOW_COSINE; + + D->pll_locked_inertia = 0.95; + D->pll_searching_inertia = 0.50; + + break; + + case 'U': /* Self correlation technique. */ + + D->use_prefilter = 1; /* Add a bandpass filter. */ + D->prefilter_baud = 0.9; + D->pre_filter_len_bits = 21. * 1200. / 44100.; + D->pre_window = BP_WINDOW_FLATTOP; + + D->lpf_baud = 1.15; + D->lp_filter_len_bits = 32. * 1200. / 44100.; + D->lp_window = BP_WINDOW_COSINE; + + D->pll_locked_inertia = 0.87; + D->pll_searching_inertia = 0.50; + + break; + + default: + text_color_set (DW_COLOR_ERROR); + dw_printf ("Invalid demodulator profile %c for v.27 8PSK. Valid choices are T, U, V, W. Using default.\n", profile); + // fall thru. + + case 'V': /* Mix with local oscillator. */ + + D->psk_use_lo = 1; + + D->use_prefilter = 0; /* No bandpass filter. */ + + D->lpf_baud = 0.85; + D->lp_filter_len_bits = 31. * 1200. / 44100.; + D->lp_window = BP_WINDOW_COSINE; + + D->pll_locked_inertia = 0.925; + D->pll_searching_inertia = 0.50; + + break; + + case 'W': /* Mix with local oscillator. */ + + D->psk_use_lo = 1; + + D->use_prefilter = 1; /* Add a bandpass filter. */ + D->prefilter_baud = 0.85; + D->pre_filter_len_bits = 31. * 1200. / 44100.; + D->pre_window = BP_WINDOW_COSINE; + + D->lpf_baud = 0.85; + D->lp_filter_len_bits = 31. * 1200. / 44100.; + D->lp_window = BP_WINDOW_COSINE; + + D->pll_locked_inertia = 0.925; + D->pll_searching_inertia = 0.50; + + break; + } + + D->ms_filter_len_bits = 1.25; // Delay line > 10/9 * symbol period + + D->coffs = (int) round( (8.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); + D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); + D->soffs = (int) round( (10.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); + } + + + if (D->psk_use_lo) { + D->lo_step = (int) round( 256. * 256. * 256. * 256. * carrier_freq / (double)samples_per_sec); + + assert (MAX_FILTER_SIZE >= 256); + for (j = 0; j < 256; j++) { + D->m_sin_table[j] = sinf(2.f * (float)M_PI * j / 256.f); + } + } + +#ifdef TUNE_PRE_BAUD + D->prefilter_baud = TUNE_PRE_BAUD; +#endif +#ifdef TUNE_PRE_WINDOW + D->pre_window = TUNE_PRE_WINDOW; +#endif + + + +#ifdef TUNE_LPF_BAUD + D->lpf_baud = TUNE_LPF_BAUD; +#endif +#ifdef TUNE_LP_WINDOW + D->lp_window = TUNE_LP_WINDOW; +#endif + + +#ifdef TUNE_HYST + D->hysteresis = TUNE_HYST; +#endif + +#if defined(TUNE_PLL_SEARCHING) + D->pll_searching_inertia = TUNE_PLL_SEARCHING; +#endif +#if defined(TUNE_PLL_LOCKED) + D->pll_locked_inertia = TUNE_PLL_LOCKED; +#endif + + +/* + * Calculate constants used for timing. + * The audio sample rate must be at least a few times the data rate. + */ + + D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)correct_baud) / ((double)samples_per_sec)); + +/* + * Convert number of symbol times to number of taps. + */ + + D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); + D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); + D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); + + +#ifdef TUNE_PRE_FILTER_SIZE + D->pre_filter_size = TUNE_PRE_FILTER_SIZE; +#endif + +#ifdef TUNE_LP_FILTER_SIZE + D->lp_filter_size = TUNE_LP_FILTER_SIZE; +#endif + + + if (D->pre_filter_size > MAX_FILTER_SIZE) + { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); + dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", + MAX_FILTER_SIZE); + exit (1); + } + + if (D->ms_filter_size > MAX_FILTER_SIZE) + { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size); + dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", + MAX_FILTER_SIZE); + exit (1); + } + + if (D->lp_filter_size > MAX_FILTER_SIZE) + { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); + dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", + MAX_FILTER_SIZE); + exit (1); + } + +/* + * Optionally apply a bandpass ("pre") filter to attenuate + * frequencies outside the range of interest. + */ + + if (D->use_prefilter) { + float f1, f2; + + f1 = carrier_freq - D->prefilter_baud * correct_baud; + f2 = carrier_freq + D->prefilter_baud * correct_baud; +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", (double)f1, (double)f2); +#endif + if (f1 <= 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Prefilter of %.0f to %.0f Hz doesn't make sense.\n", (double)f1, (double)f2); + f1 = 10; + } + + f1 = f1 / (float)samples_per_sec; + f2 = f2 / (float)samples_per_sec; + + gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window); + } + +/* + * Now the lowpass filter. + */ + + float fc = correct_baud * D->lpf_baud / (float)samples_per_sec; + gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); + +/* + * No point in having multiple numbers for signal level. + */ + + D->alevel_mark_peak = -1; + D->alevel_space_peak = -1; + +} /* demod_psk_init */ + + + + +/*------------------------------------------------------------------- + * + * Name: demod_psk_process_sample + * + * Purpose: (1) Demodulate the psk signal into I & Q components. + * (2) Recover clock and sample data at the right time. + * (3) Produce two bits per symbol based on phase change from previous. + * + * Inputs: chan - Audio channel. 0 for left, 1 for right. + * subchan - modem of the channel. + * sam - One sample of audio. + * Should be in range of -32768 .. 32767. + * + * Outputs: For each recovered data bit, we call: + * + * hdlc_rec (channel, demodulated_bit); + * + * to decode HDLC frames from the stream of bits. + * + * Returns: None + * + * Descripion: All the literature, that I could find, described mixing + * with a local oscillator. First we multiply the input by + * cos and sin then low pass filter each. This gives us + * correlation to the different phases. The signs of these two + * results produces two data bits per symbol period. + * + * An 1800 Hz local oscillator was derived from the 1200 Hz + * PLL used to sample the data. + * This worked wonderfully for the ideal condition where + * we start off with the proper phase and all the timing + * is perfect. However, when random delays were added + * before the frame, the PLL would lock on only about + * half the time. + * + * Late one night, it dawned on me that there is no + * need for a local oscillator (LO) at the carrier frequency. + * Simply correlate the signal with the previous symbol, + * phase shifted by + and - 45 degrees. + * The code is much simpler and very reliable. + * + * Later, I realized it was not necessary to synchronize the LO + * because we only care about the phase shift between symbols. + * + * This works better under noisy conditions because we are + * including the noise from only the current symbol and not + * the previous one. + * + * Finally, once we know how to distinguish 4 different phases, + * it is not much effort to use 8 phases to double the bit rate. + * + *--------------------------------------------------------------------*/ + + + +inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D); + +__attribute__((hot)) +void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D) +{ + float fsam; + float sam_x_cos, sam_x_sin; + float I, Q; + int demod_phase_shift; // Phase shift relative to previous symbol. + // range 0-3, 1 unit for each 90 degrees. + int slice = 0; + +#if DEBUG4 + static FILE *demod_log_fp = NULL; + static int log_file_seq = 0; /* Part of log file name */ +#endif + + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + + /* Scale to nice number for plotting during debug. */ + + fsam = sam / 16384.0f; + + +/* + * Optional bandpass filter before the phase detector. + */ + + if (D->use_prefilter) { + push_sample (fsam, D->raw_cb, D->pre_filter_size); + fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size); + } + + if (D->psk_use_lo) { + float a, delta; + int id; +/* + * Mix with local oscillator to obtain phase. + * The absolute phase doesn't matter. + * We are just concerned with the change since the previous symbol. + */ + + sam_x_cos = fsam * D->m_sin_table[((D->lo_phase >> 24) + 64) & 0xff]; + + sam_x_sin = fsam * D->m_sin_table[(D->lo_phase >> 24) & 0xff]; + + push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size); + I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); + + push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size); + Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); + + a = my_atan2f(I,Q); + push_sample (a, D->ms_in_cb, D->ms_filter_size); + + delta = a - D->ms_in_cb[D->boffs]; + + /* 256 units/cycle makes modulo processing easier. */ + /* Make sure it is positive before truncating to integer. */ + + id = ((int)((delta / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; + + if (D->modem_type == MODEM_QPSK) { + demod_phase_shift = ((id + 32) >> 6) & 0x3; + } + else { + demod_phase_shift = ((id + 16) >> 5) & 0x7; + } + nudge_pll (chan, subchan, slice, demod_phase_shift, D); + + D->lo_phase += D->lo_step; + } + else { +/* + * Correlate with previous symbol. We are looking for the phase shift. + */ + push_sample (fsam, D->ms_in_cb, D->ms_filter_size); + + sam_x_cos = fsam * D->ms_in_cb[D->coffs]; + sam_x_sin = fsam * D->ms_in_cb[D->soffs]; + + push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size); + I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); + + push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size); + Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); + + if (D->modem_type == MODEM_QPSK) { + +#if 1 // Speed up special case. + if (I > 0) { + if (Q > 0) + demod_phase_shift = 0; /* 0 to 90 degrees, etc. */ + else + demod_phase_shift = 1; + } + else { + if (Q > 0) + demod_phase_shift = 3; + else + demod_phase_shift = 2; + } +#else + a = my_atan2f(I,Q); + int id = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; + // 128 compensates for 180 degree phase shift due + // to 1 1/2 carrier cycles per symbol period. + demod_phase_shift = ((id + 128) >> 6) & 0x3; +#endif + } + else { + float a; + int idelta; + + a = my_atan2f(I,Q); + idelta = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; + // 32 (90 degrees) compensates for 1800 carrier vs. 1800 baud. + // 16 is to set threshold between constellation points. + demod_phase_shift = ((idelta - 32 - 16) >> 5) & 0x7; + } + + nudge_pll (chan, subchan, slice, demod_phase_shift, D); + } + +#if DEBUG4 + + if (chan == 0) { + + if (1) { + //if (hdlc_rec_gathering (chan, subchan, slice)) { + char fname[30]; + + + if (demod_log_fp == NULL) { + log_file_seq++; + snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq); + //if (log_file_seq == 1) mkdir ("demod", 0777); + if (log_file_seq == 1) mkdir ("demod"); + + demod_log_fp = fopen (fname, "w"); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Starting demodulator log file %s\n", fname); + fprintf (demod_log_fp, "Audio, sin, cos, *cos, *sin, I, Q, phase, Clock\n"); + } + + fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", + fsam + 2, + - D->ms_in_cb[D->soffs] + 6, + - D->ms_in_cb[D->coffs] + 6, + sam_x_cos + 8, + sam_x_sin + 10, + 2 * I + 12, + 2 * Q + 12, + demod_phase_shift * 2. / 3. + 14., + (D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0); + + fflush (demod_log_fp); + } + else { + if (demod_log_fp != NULL) { + fclose (demod_log_fp); + demod_log_fp = NULL; + } + } + } +#endif + + +} /* end demod_psk_process_sample */ + +static const int phase_to_gray_v26[4] = {0, 1, 3, 2}; +static const int phase_to_gray_v27[8] = {1, 0, 2, 3, 7, 6, 4, 5}; + + + +__attribute__((hot)) +inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D) +{ + +/* + * Finally, a PLL is used to sample near the centers of the data bits. + * + * D points to a demodulator for a channel/subchannel pair so we don't + * have to keep recalculating it. + * + * D->data_clock_pll is a SIGNED 32 bit variable. + * When it overflows from a large positive value to a negative value, we + * sample a data bit from the demodulated signal. + * + * Ideally, the the demodulated signal transitions should be near + * zero we we sample mid way between the transitions. + * + * Nudge the PLL by removing some small fraction from the value of + * data_clock_pll, pushing it closer to zero. + * + * This adjustment will never change the sign so it won't cause + * any erratic data bit sampling. + * + * If we adjust it too quickly, the clock will have too much jitter. + * If we adjust it too slowly, it will take too long to lock on to a new signal. + * + * Be a little more agressive about adjusting the PLL + * phase when searching for a signal. + * Don't change it as much when locked on to a signal. + * + * I don't think the optimal value will depend on the audio sample rate + * because this happens for each transition from the demodulator. + */ + + + D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; + + D->slicer[slice].data_clock_pll += D->pll_step_per_sample; + + if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll >= 0) { + + /* Overflow of PLL counter. */ + /* This is where we sample the data. */ + + if (D->modem_type == MODEM_QPSK) { + + int gray = phase_to_gray_v26[ demod_bits ]; + +#if DEBUG4 + text_color_set(DW_COLOR_DEBUG); + + dw_printf ("a=%.2f deg, delta=%.2f deg, phaseshift=%d, bits= %d %d \n", + a * 360 / (2*M_PI), delta * 360 / (2*M_PI), demod_bits, (gray >> 1) & 1, gray & 1); + + //dw_printf ("phaseshift=%d, bits= %d %d \n", demod_bits, (gray >> 1) & 1, gray & 1); +#endif + hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1); + hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1); + } + else { + int gray = phase_to_gray_v27[ demod_bits ]; + + hdlc_rec_bit (chan, subchan, slice, (gray >> 2) & 1, 0, -1); + hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1); + hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1); + } + } + +/* + * If demodulated data has changed, + * Pull the PLL phase closer to zero. + * Use "floor" instead of simply casting so the sign won't flip. + * For example if we had -0.7 we want to end up with -1 rather than 0. + */ + +// TODO: demod_9600 has an improved technique. Would it help us here? + + if (demod_bits != D->slicer[slice].prev_demod_data) { + + if (hdlc_rec_gathering (chan, subchan, slice)) { + D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_locked_inertia); + } + else { + D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_searching_inertia); + } + } + +/* + * Remember demodulator output so we can compare next time. + */ + D->slicer[slice].prev_demod_data = demod_bits; + +} /* end nudge_pll */ + + + +/* end demod_psk.c */ diff --git a/demod_psk.h b/demod_psk.h new file mode 100644 index 00000000..0f5830d8 --- /dev/null +++ b/demod_psk.h @@ -0,0 +1,7 @@ + +/* demod_psk.h */ + + +void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D); + +void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D); diff --git a/digipeater.c b/digipeater.c index 7e90d916..1900d784 100644 --- a/digipeater.c +++ b/digipeater.c @@ -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 @@ -53,6 +54,7 @@ #define DIGIPEATER_C +#include "direwolf.h" #include #include @@ -62,7 +64,6 @@ #include "regex.h" #include -#include "direwolf.h" #include "ax25_pad.h" #include "digipeater.h" #include "textcolor.h" @@ -74,8 +75,6 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter); -//static int filter_by_type (char *source, char *infop, char *type_filter); - /* * Keep pointer to configuration options. @@ -87,6 +86,18 @@ static struct audio_s *save_audio_config_p; static struct digi_config_s *save_digi_config_p; +/* + * Maintain count of packets digipeated for each combination of from/to channel. + */ + +static int digi_count[MAX_CHANS][MAX_CHANS]; + +int digipeater_get_count (int from_chan, int to_chan) { + return (digi_count[from_chan][to_chan]); +} + + + /*------------------------------------------------------------------------------ * * Name: digipeater_init @@ -165,6 +176,7 @@ void digipeater (int from_chan, packet_t pp) if (result != NULL) { dedupe_remember (pp, to_chan); tq_append (to_chan, TQ_PRIO_0_HI, result); + digi_count[from_chan][to_chan]++; } } } @@ -190,6 +202,7 @@ void digipeater (int from_chan, packet_t pp) if (result != NULL) { dedupe_remember (pp, to_chan); tq_append (to_chan, TQ_PRIO_1_LO, result); + digi_count[from_chan][to_chan]++; } } } @@ -243,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", @@ -260,11 +277,13 @@ 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, regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str) { + char source[AX25_MAX_ADDR_LEN]; int ssid; int r; char repeater[AX25_MAX_ADDR_LEN]; @@ -274,19 +293,9 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch /* * First check if filtering has been configured. */ - - if (filter_str != NULL) { - if (pfilter(from_chan, to_chan, filter_str, pp) != 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 + if (pfilter(from_chan, to_chan, filter_str, pp, 1) != 1) { return(NULL); } } @@ -310,11 +319,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. @@ -337,10 +350,13 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch /* - * First check for explicit use of my call. + * First check for explicit use of my call, including SSID. + * Someone might explicitly specify a particular path for testing purposes. + * This will bypass the usual checks for duplicates and my call in the source. + * * In this case, we don't check the history so it would be possible * to have a loop (of limited size) if someone constructed the digipeater paths - * correctly. + * correctly. I would expect it only for testing purposes. */ if (strcmp(repeater, mycall_rec) == 0) { @@ -356,13 +372,24 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch return (result); } +/* + * Don't digipeat my own. Fixed in 1.4 dev H. + * Alternatively we might feed everything transmitted into + * dedupe_remember rather than only frames out of digipeater. + */ + ax25_get_addr_with_ssid(pp, AX25_SOURCE, source); + if (strcmp(source, mycall_rec) == 0) { + return (NULL); + } + + /* * Next try to avoid retransmitting redundant information. * Duplicates are detected by comparing only: * - source * - destination * - info part - * - but none of the digipeaters + * - but not the via path. (digipeater addresses) * A history is kept for some amount of time, typically 30 seconds. * For efficiency, only a checksum, rather than the complete fields * might be kept but the result is the same. @@ -371,7 +398,6 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch * */ - if (dedupe_check(pp, to_chan)) { //#if DEBUG /* Might be useful if people are wondering why */ @@ -385,7 +411,10 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch /* * For the alias pattern, we unconditionally digipeat it once. - * i.e. Just replace it with MYCALL don't even look at the ssid. + * i.e. Just replace it with MYCALL. + * + * My call should be an implied member of this set. + * In this implementation, we already caught it further up. */ err = regexec(alias,repeater,0,NULL,0); if (err == 0) { @@ -584,13 +613,11 @@ static int failed; static enum preempt_e preempt = PREEMPT_OFF; -static char typefilter[20] = ""; static void test (char *in, char *out) { packet_t pp, result; - //int should_repeat; char rec[256]; char xmit[256]; unsigned char *pinfo; @@ -610,6 +637,7 @@ static void test (char *in, char *out) ax25_format_addrs (pp, rec); info_len = ax25_get_info (pp, &pinfo); + (void)info_len; strlcat (rec, (char*)pinfo, sizeof(rec)); if (strcmp(in, rec) != 0) { @@ -784,17 +812,22 @@ 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:", +#ifndef OBSOLETE14 "W1ABC>TEST,WB2OSZ-9*,WIDE3-2:"); - +#else + ""); +#endif test ( "W1DEF>TEST-3,WIDE2-2:", "W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:"); /* * Drop duplicates within specified time interval. * Only the first 1 of 3 should be retransmitted. + * The 4th case might be controversial. */ test ( "W1XYZ>TEST,R1*,WIDE3-2:info1", @@ -806,6 +839,10 @@ int main (int argc, char *argv[]) test ( "W1XYZ>TEST,R3*,WIDE3-2:info1", ""); + test ( "W1XYZ>TEST,R1*,WB2OSZ-9:has explicit routing", + "W1XYZ>TEST,R1,WB2OSZ-9*:has explicit routing"); + + /* * Allow same thing after adequate time. */ @@ -863,72 +900,24 @@ int main (int argc, char *argv[]) ""); -#if 0 /* changed strategy */ -/* - * New in version 1.2. - */ - - - // no filter. - if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "") != 1) - { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 1\n"); failed++; } - - // message should not match psqt - if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "pqst") != 0) - { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 2\n"); failed++; } - - // This should match position - if (filter_by_type ("N3LEE-7", "`cHDl <0x1c>[/\"5j}", "qstp") != 1) - { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 3\n"); failed++; } - - // This should match nws - if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 1) - { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 4\n"); failed++; } - - // But not this. - if (filter_by_type ("CWAPID", ":zzz-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 0) - { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 5\n"); failed++; } - - // This should match nws - if (filter_by_type ("CWAPID", ";CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 1) - { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 6\n"); failed++; } - - // But not this due do addressee prefix mismatch - if (filter_by_type ("CWAPID", ";NWSttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 0) - { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 7\n"); failed++; } - - -/* - * Filtering integrated with rest of process... +/* + * Did I miss any cases? + * Yes. Don't retransmit my own. 1.4H */ - strlcpy (typefilter, "w", sizeof(typefilter)); - - test ( "N8VIM>APN391,WIDE2-1:$ULTW00000000010E097D2884FFF389DC000102430002033400000000", - "N8VIM>APN391,WB2OSZ-9*:$ULTW00000000010E097D2884FFF389DC000102430002033400000000"); + test ( "WB2OSZ-7>TEST14,WIDE1-1,WIDE1-1:stuff", + "WB2OSZ-7>TEST14,WB2OSZ-9*,WIDE1-1:stuff"); - test ( "AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational", + test ( "WB2OSZ-9>TEST14,WIDE1-1,WIDE1-1:from myself", ""); - - strlcpy (typefilter, "s", sizeof(typefilter)); - test ( "AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational", - "AB1OC-10>APWW10,WB2OSZ-9*,WIDE2-1:>FN42er/# Hollis, NH iGate Operational"); + test ( "WB2OSZ-9>TEST14,WIDE1-1*,WB2OSZ-9:from myself but explicit routing", + "WB2OSZ-9>TEST14,WIDE1-1,WB2OSZ-9*:from myself but explicit routing"); - test ( "K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%", - ""); - - strlcpy (typefilter, "up", sizeof(typefilter)); + test ( "WB2OSZ-15>TEST14,WIDE1-1,WIDE1-1:stuff", + "WB2OSZ-15>TEST14,WB2OSZ-9*,WIDE1-1:stuff"); - test ( "K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%", - "K1ABC-9>TR4R8R,WB2OSZ-9*:`c6LlIb>/`\"4K}_%"); - - strlcpy (typefilter, "", sizeof(typefilter)); -#endif -/* - * Did I miss any cases? - */ if (failed == 0) { dw_printf ("SUCCESS -- All digipeater tests passed.\n"); diff --git a/digipeater.h b/digipeater.h index d1b26b8c..d1ad3b6c 100644 --- a/digipeater.h +++ b/digipeater.h @@ -65,6 +65,11 @@ extern void digipeater (int from_chan, packet_t pp); void digi_regen (int from_chan, packet_t pp); +/* Make statistics available. */ + +int digipeater_get_count (int from_chan, int to_chan); + + #endif /* end digipeater.h */ diff --git a/direwolf.c b/direwolf.c index 3ea54c78..1a562b87 100644 --- a/direwolf.c +++ b/direwolf.c @@ -35,6 +35,13 @@ * *---------------------------------------------------------------*/ + +#define DIREWOLF_C 1 + +#include "direwolf.h" + + + #include #include #include @@ -72,9 +79,7 @@ #endif -#define DIREWOLF_C 1 -#include "direwolf.h" #include "version.h" #include "audio.h" #include "config.h" @@ -83,29 +88,34 @@ #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" #include "kiss.h" #include "kissnet.h" #include "kiss_frame.h" -#include "nmea.h" +#include "waypoint.h" #include "gen_tone.h" #include "digipeater.h" +#include "cdigipeater.h" #include "tq.h" #include "xmit.h" #include "ptt.h" #include "beacon.h" -#include "redecode.h" #include "dtmf.h" #include "aprs_tt.h" #include "tt_user.h" #include "igate.h" +#include "pfilter.h" #include "symbols.h" #include "dwgps.h" +#include "waypoint.h" #include "log.h" #include "recv.h" #include "morse.h" +#include "mheard.h" +#include "ax25_link.h" //static int idx_decoded = 0; @@ -152,14 +162,19 @@ 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 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. */ static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */ -static int q_d_opt = 0; /* "-q d" Quiet, suppress the decoding of APRS packets. */ +static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */ + static struct misc_config_s misc_config; @@ -174,6 +189,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]; @@ -189,9 +205,13 @@ 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 + 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)); @@ -234,10 +254,13 @@ int main (int argc, char *argv[]) // TODO: control development/beta/release by version.h instead of changing here. // Print platform. This will provide more information when people send a copy the information displayed. + // Might want to print OS version here. For Windows, see: + // https://msdn.microsoft.com/en-us/library/ms724451(v=VS.85).aspx + 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, "K", __DATE__); + //dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "H", __DATE__); dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); #if defined(ENABLE_GPSD) || defined(USE_HAMLIB) @@ -322,7 +345,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; @@ -367,9 +390,9 @@ int main (int argc, char *argv[]) case 'B': /* -B baud rate and modem properties. */ B_opt = atoi(optarg); - if (B_opt < 100 || B_opt > 10000) { + if (B_opt < MIN_BAUD || B_opt > MAX_BAUD) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable data baud rate in range of 100 - 10000.\n"); + dw_printf ("Use a more reasonable data baud rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } break; @@ -451,15 +474,17 @@ int main (int argc, char *argv[]) // separate out gps & waypoints. case 'g': d_g_opt++; break; + case 'w': waypoint_set_debug (1); break; // not documented yet. case 't': d_t_opt++; beacon_tracker_set_debug (d_t_opt); break; - case 'w': nmea_set_debug (1); break; // not documented yet. 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 @@ -508,6 +533,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. */ @@ -542,7 +588,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; @@ -559,21 +605,42 @@ int main (int argc, char *argv[]) if (B_opt != 0) { audio_config.achan[0].baud = B_opt; + /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ + /* that need to be kept in sync. Maybe it could be a common function someday. */ + if (audio_config.achan[0].baud < 600) { audio_config.achan[0].modem_type = MODEM_AFSK; - audio_config.achan[0].mark_freq = 1600; + audio_config.achan[0].mark_freq = 1600; // Typical for HF SSB. audio_config.achan[0].space_freq = 1800; - audio_config.achan[0].decimate = 3; + audio_config.achan[0].decimate = 3; // Reduce CPU load. } - else if (audio_config.achan[0].baud > 2400) { - audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + else if (audio_config.achan[0].baud < 1800) { + audio_config.achan[0].modem_type = MODEM_AFSK; + audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; + audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; + } + else if (audio_config.achan[0].baud < 3600) { + audio_config.achan[0].modem_type = MODEM_QPSK; audio_config.achan[0].mark_freq = 0; audio_config.achan[0].space_freq = 0; + if (audio_config.achan[0].baud != 2400) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Bit rate should be standard 2400 rather than specified %d.\n", audio_config.achan[0].baud); + } + } + else if (audio_config.achan[0].baud < 7200) { + audio_config.achan[0].modem_type = MODEM_8PSK; + audio_config.achan[0].mark_freq = 0; + audio_config.achan[0].space_freq = 0; + if (audio_config.achan[0].baud != 4800) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", audio_config.achan[0].baud); + } } else { - audio_config.achan[0].modem_type = MODEM_AFSK; - audio_config.achan[0].mark_freq = 1200; - audio_config.achan[0].space_freq = 2200; + audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + audio_config.achan[0].mark_freq = 0; + audio_config.achan[0].space_freq = 0; } } @@ -590,6 +657,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)); } @@ -621,14 +694,14 @@ int main (int argc, char *argv[]) } /* - * Initialize the AFSK demodulator and HDLC decoder. + * Initialize the demodulator(s) and HDLC decoder. */ multi_modem_init (&audio_config); /* * Initialize the touch tone decoder & APRStt gateway. */ - dtmf_init (&audio_config); + dtmf_init (&audio_config, audio_amplitude); aprs_tt_init (&tt_config); tt_user_init (&audio_config, &tt_config); @@ -637,8 +710,8 @@ int main (int argc, char *argv[]) * Note: This is not the same as a volume control you would see on the screen. * It is the range of the digital sound representation. */ - gen_tone_init (&audio_config, 100); - morse_init (&audio_config, 100); + gen_tone_init (&audio_config, audio_amplitude, 0); + morse_init (&audio_config, audio_amplitude); assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16); assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2); @@ -680,6 +753,9 @@ 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); + pfilter_init (&igate_config, d_f_opt); + ax25_link_init (&misc_config); /* * Provide the AGW & KISS socket interfaces for use by a client application. @@ -698,12 +774,7 @@ int main (int argc, char *argv[]) */ dwgps_init (&misc_config, d_g_opt); - nmea_init (&misc_config); // TODO: revisit. - -/* - * Create thread for trying to salvage frames with bad FCS. - */ - redecode_init (&audio_config); + waypoint_init (&misc_config); /* * Enable beaconing. @@ -712,7 +783,8 @@ int main (int argc, char *argv[]) */ log_init(misc_config.logdir); - beacon_init (&audio_config, &misc_config); + mheard_init (d_m_opt); + beacon_init (&audio_config, &misc_config, &igate_config); /* @@ -863,7 +935,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) { @@ -882,14 +954,46 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev dw_printf ("%s", stemp); /* stations followed by : */ - // 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. +/* Demystify non-APRS. Use same format for transmitted frames in xmit.c. */ - // TODO: Might want to use d_u_opt for transmitted frames too. + 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); + + /* 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, ¶m, 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"); + } - 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. @@ -920,18 +1024,28 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev } -/* Decode the contents of APRS frames and display in human-readable form. */ -/* Suppress decoding if "-q d" option used. */ +/* + * Decode the contents of UI frames and display in human-readable form. + * Could be APRS or anything random for old fashioned packet beacons. + * + * Suppress printed decoding if "-q d" option used. + */ - if ( ( ! q_d_opt ) && ax25_is_aprs(pp)) { + if (ax25_is_aprs(pp)) { decode_aprs_t A; - decode_aprs (&A, pp, 0); + // we still want to decode it for logging and other processing. + // Just be quiet about errors if "-qd" is set. + + decode_aprs (&A, pp, q_d_opt); - //Print it all out in human readable format. + if ( ! q_d_opt ) { - decode_aprs_print (&A); + // Print it all out in human readable format unless "-q d" option used. + + decode_aprs_print (&A); + } /* * Perform validity check on each address. @@ -943,10 +1057,18 @@ 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 over the radio. + + mheard_save_rf (chan, &A, pp, alevel, retries); + + // Convert to NMEA waypoint sentence if we have a location. if (A.g_lat != G_UNKNOWN && A.g_lon != G_UNKNOWN) { - nmea_send_waypoint (strlen(A.g_name) > 0 ? A.g_name : A.g_src, + waypoint_send_sentence (strlen(A.g_name) > 0 ? A.g_name : A.g_src, A.g_lat, A.g_lon, A.g_symbol_table, A.g_symbol_code, DW_FEET_TO_METERS(A.g_altitude_ft), A.g_course, DW_MPH_TO_KNOTS(A.g_speed_mph), A.g_comment); @@ -955,7 +1077,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev /* Send to another application if connected. */ -// TODO1.3: Put a wrapper around this so we only call one function to send by all methods. +// TODO: Put a wrapper around this so we only call one function to send by all methods. int flen; unsigned char fbuf[AX25_MAX_PACKET_LEN]; @@ -997,24 +1119,31 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev digi_regen (chan, pp); -/* - * 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__ @@ -1026,6 +1155,7 @@ static BOOL cleanup_win (int ctrltype) dw_printf ("\nQRT\n"); log_term (); ptt_term (); + waypoint_term (); dwgps_term (); SLEEP_SEC(1); ExitProcess (0); @@ -1065,10 +1195,12 @@ static void usage (char **argv) dw_printf (" -r n Audio sample rate, per sec.\n"); dw_printf (" -n n Number of audio channels, 1 or 2.\n"); dw_printf (" -b n Bits per audio sample, 8 or 16.\n"); - dw_printf (" -B n Data rate in bits/sec for channel 0. Standard values are 300, 1200, 9600.\n"); - dw_printf (" If < 600, AFSK tones are set to 1600 & 1800.\n"); - dw_printf (" If > 2400, K9NG/G3RUH style encoding is used.\n"); - dw_printf (" Otherwise, AFSK tones are set to 1200 & 2200.\n"); + dw_printf (" -B n Data rate in bits/sec for channel 0. Standard values are 300, 1200, 2400, 4800, 9600.\n"); + dw_printf (" 300 bps defaults to AFSK tones of 1600 & 1800.\n"); + dw_printf (" 1200 bps uses AFSK tones of 1200 & 2200.\n"); + dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n"); + dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n"); + dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n"); dw_printf (" -D n Divide audio sample rate by n for channel 0.\n"); dw_printf (" -d Debug options:\n"); dw_printf (" a a = AGWPE network protocol client.\n"); @@ -1077,9 +1209,12 @@ static void usage (char **argv) dw_printf (" u u = Display non-ASCII text in hexadecimal.\n"); dw_printf (" p p = dump Packets in hexadecimal.\n"); dw_printf (" g g = GPS interface.\n"); + dw_printf (" w w = Waypoints for Position or Object Reports.\n"); 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"); + dw_printf (" f f = packet Filtering.\n"); #if USE_HAMLIB dw_printf (" h h = hamlib increase verbose level.\n"); #endif diff --git a/direwolf.h b/direwolf.h index 958e0932..a647b600 100644 --- a/direwolf.h +++ b/direwolf.h @@ -1,7 +1,37 @@ +/* direwolf.h - Common stuff used many places. */ + +// TODO: include this file first before anything else in each .c file. + + #ifndef DIREWOLF_H #define DIREWOLF_H 1 +/* + * Support Windows XP and later. + * + * We need this before "#include ". + * + * Don't know what other impact it might have on others. + */ + +#if __WIN32__ + +#ifdef _WIN32_WINNT +#error Include "direwolf.h" before any windows system files. +#endif +#ifdef WINVER +#error Include "direwolf.h" before any windows system files. +#endif + +#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ +#define WINVER 0x0501 /* Minimum OS version is XP. */ + +#include + +#endif + + /* * Previously, we could handle only a single audio device. @@ -9,6 +39,8 @@ * In version 1.2, we relax this restriction and allow more audio devices. * Three is probably adequate for standard version. * Larger reasonable numbers should also be fine. + * + * For example, if you wanted to use 4 audio devices at once, change this to 4. */ #define MAX_ADEVS 3 @@ -69,7 +101,6 @@ #if __WIN32__ -#include #define SLEEP_SEC(n) Sleep((n)*1000) #define SLEEP_MS(n) Sleep(n) #else @@ -191,9 +222,8 @@ char *strsep(char **stringp, const char *delim); char *strtok_r(char *str, const char *delim, char **saveptr); #endif -//#if __WIN32__ +// Don't recall why for everyone. char *strcasestr(const char *S, const char *FIND); -//#endif #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) @@ -203,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 diff --git a/dlq.c b/dlq.c index 06157c84..6a4236f6 100644 --- a/dlq.c +++ b/dlq.c @@ -1,8 +1,7 @@ - // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 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 @@ -25,57 +24,41 @@ * * Purpose: Received frame queue. * - * Description: In previous versions, the main thread read from the + * Description: In earlier versions, the main thread read from the * audio device and performed the receive demodulation/decoding. - * In version 1.2 we now have a seprate receive thread + * + * Since version 1.2 we have a separate receive thread * for each audio device. This queue is used to collect * received frames from all channels and process them * serially. * + * In version 1.4, other types of events also go into this + * queue and we use it to drive the data link state machine. + * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include #include #include +#if __WIN32__ +#else +#include +#endif -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "dlq.h" #include "dedupe.h" +#include "dtime_now.h" /* The queue is a linked list of these. */ -struct dlq_item_s { - - struct dlq_item_s *nextp; /* Next item in queue. */ - - dlq_type_t type; /* Type of item. */ - /* Only received frames at this time. */ - - int chan; /* Radio channel of origin. */ - - int subchan; /* Winning "subchannel" when using multiple */ - /* decoders on one channel. */ - /* Special case, -1 means DTMF decoder. */ - /* Maybe we should have a different type in this case? */ - - int slice; /* Winning slicer. */ - - packet_t pp; /* Pointer to frame structure. */ - - alevel_t alevel; /* Audio level. */ - - retry_t retries; /* Effort expended to get a valid CRC. */ - - char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */ -}; - - static struct dlq_item_s *queue_head = NULL; /* Head of linked list for queue. */ #if __WIN32__ @@ -94,14 +77,22 @@ static pthread_cond_t wake_up_cond; /* Notify received packet processing thread static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */ -static int recv_thread_is_waiting = 0; +static volatile int recv_thread_is_waiting = 0; #endif -static int dlq_is_empty (void); - 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 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. + + /*------------------------------------------------------------------- * @@ -121,9 +112,6 @@ static int was_init = 0; /* was initialization performed? */ void dlq_init (void) { - int c, p; - int err; - #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_init ( )\n"); @@ -140,6 +128,7 @@ void dlq_init (void) #if __WIN32__ InitializeCriticalSection (&dlq_cs); #else + int err; err = pthread_mutex_init (&wake_up_mutex, NULL); err = pthread_mutex_init (&dlq_mutex, NULL); if (err != 0) { @@ -192,17 +181,18 @@ void dlq_init (void) } /* end dlq_init */ + /*------------------------------------------------------------------- * - * Name: dlq_append + * Name: dlq_rec_frame * - * Purpose: Add a packet to the end of the specified receive queue. + * Purpose: Add a received packet to the end of the queue. + * Normally this was received over the radio but we can create + * our own from APRStt or beaconing. * - * Inputs: type - One of the following: + * This would correspond to PH-DATA Indication in the AX.25 protocol spec. * - * DLQ_REC_FRAME - Frame received from radio. - * - * chan - Channel, 0 is first. + * Inputs: chan - Channel, 0 is first. * * subchan - Which modem caught it. * Special case -1 for APRStt gateway. @@ -224,38 +214,27 @@ void dlq_init (void) * spectrum - Display of how well multiple decoders did. * * - * Outputs: Information is appended to queue. - * - * Description: Add item to end of linked list. - * Signal the receive processing thread if the queue was formerly empty. - * * IMPORTANT! Don't make an further references to the packet object after * giving it to dlq_append. * *--------------------------------------------------------------------*/ -void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { struct dlq_item_s *pnew; - struct dlq_item_s *plast; - int err; - int queue_length = 0; + #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_append (type=%d, chan=%d, pp=%p, ...)\n", type, chan, pp); + dw_printf ("dlq_rec_frame (chan=%d, pp=%p, ...)\n", chan, pp); #endif - if ( ! was_init) { - dlq_init (); - } - assert (chan >= 0 && chan < MAX_CHANS); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("INTERNAL ERROR: dlq_append NULL packet pointer. Please report this!\n"); + dw_printf ("INTERNAL ERROR: dlq_rec_frame NULL packet pointer. Please report this!\n"); return; } @@ -263,16 +242,23 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, if (ax25memdebug_get()) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_append (type=%d, chan=%d.%d, seq=%d, ...)\n", type, chan, subchan, ax25memdebug_seq(pp)); + dw_printf ("dlq_rec_frame (chan=%d.%d, seq=%d, ...)\n", chan, subchan, ax25memdebug_seq(pp)); } #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 = type; + pnew->type = DLQ_REC_FRAME; pnew->chan = chan; pnew->slice = slice; pnew->subchan = subchan; @@ -284,17 +270,56 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, else strlcpy(pnew->spectrum, spectrum, sizeof(pnew->spectrum)); +/* Put it into queue. */ + + append_to_queue (pnew); + +} /* end dlq_rec_frame */ + + + +/*------------------------------------------------------------------- + * + * Name: append_to_queue + * + * Purpose: Append some type of event to queue. + * This includes frames received over the radio, + * requests from client applications, and notifications + * from the frame transmission process. + * + * + * Inputs: pnew - Pointer to queue element structure. + * + * Outputs: Information is appended to queue. + * + * Description: Add item to end of linked list. + * Signal the receive processing thread if the queue was formerly empty. + * + *--------------------------------------------------------------------*/ + +static void append_to_queue (struct dlq_item_s *pnew) +{ + struct dlq_item_s *plast; + int queue_length = 0; + + if ( ! was_init) { + dlq_init (); + } + + pnew->nextp = NULL; + #if DEBUG1 text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_append: enter critical section\n"); + dw_printf ("dlq append_to_queue: enter critical section\n"); #endif #if __WIN32__ EnterCriticalSection (&dlq_cs); #else + int err; err = pthread_mutex_lock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_append: pthread_mutex_lock err=%d", err); + dw_printf ("dlq append_to_queue: pthread_mutex_lock err=%d", err); perror (""); exit (1); } @@ -321,15 +346,15 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, err = pthread_mutex_unlock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_append: pthread_mutex_unlock err=%d", err); + dw_printf ("dlq append_to_queue: pthread_mutex_unlock err=%d", err); perror (""); exit (1); } #endif #if DEBUG1 text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_append: left critical section\n"); - dw_printf ("dlq_append (): about to wake up recv processing thread.\n"); + dw_printf ("dlq append_to_queue: left critical section\n"); + dw_printf ("dlq append_to_queue (): about to wake up recv processing thread.\n"); #endif @@ -394,7 +419,7 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, err = pthread_mutex_lock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_append: pthread_mutex_lock wu err=%d", err); + dw_printf ("dlq append_to_queue: pthread_mutex_lock wu err=%d", err); perror (""); exit (1); } @@ -402,7 +427,7 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, err = pthread_cond_signal (&wake_up_cond); if (err != 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_append: pthread_cond_signal err=%d", err); + dw_printf ("dlq append_to_queue: pthread_cond_signal err=%d", err); perror (""); exit (1); } @@ -410,14 +435,381 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, err = pthread_mutex_unlock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_append: pthread_mutex_unlock wu err=%d", err); + dw_printf ("dlq append_to_queue: pthread_mutex_unlock wu err=%d", err); perror (""); exit (1); } } #endif -} +} /* end append_to_queue */ + + +/*------------------------------------------------------------------- + * + * Name: dlq_connect_request + * + * Purpose: Client application has requested connection to another station. + * + * Inputs: addrs - Source (owncall), destination (peercall), + * and possibly digipeaters. + * + * num_addr - Number of addresses. 2 to 10. + * + * chan - Channel, 0 is first. + * + * client - Client application instance. We could have multiple + * applications, all on the same channel, connecting + * to different stations. We need to know which one + * should get the results. + * + * 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. + * + *--------------------------------------------------------------------*/ + +void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid) +{ + struct dlq_item_s *pnew; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dlq_connect_request (...)\n"); +#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_CONNECT_REQUEST; + pnew->chan = chan; + memcpy (pnew->addrs, addrs, sizeof(pnew->addrs)); + pnew->num_addr = num_addr; + pnew->client = client; + +/* Put it into queue. */ + + append_to_queue (pnew); + +} /* end dlq_connect_request */ + + + +/*------------------------------------------------------------------- + * + * Name: dlq_disconnect_request + * + * Purpose: Client application has requested to disconnect. + * + * Inputs: addrs - Source (owncall), destination (peercall), + * and possibly digipeaters. + * + * num_addr - Number of addresses. 2 to 10. + * Only first two matter in this case. + * + * chan - Channel, 0 is first. + * + * client - Client application instance. We could have multiple + * applications, all on the same channel, connecting + * to different stations. We need to know which one + * should get the results. + * + * Outputs: Request is appended to queue for processing by + * the data link state machine. + * + *--------------------------------------------------------------------*/ + +void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client) +{ + struct dlq_item_s *pnew; +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dlq_disconnect_request (...)\n"); +#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_DISCONNECT_REQUEST; + pnew->chan = chan; + memcpy (pnew->addrs, addrs, sizeof(pnew->addrs)); + pnew->num_addr = num_addr; + pnew->client = client; + +/* Put it into queue. */ + + append_to_queue (pnew); + +} /* end dlq_connect_request */ + + +/*------------------------------------------------------------------- + * + * Name: dlq_xmit_data_request + * + * Purpose: Client application has requested transmission of connected + * data over an established link. + * + * Inputs: addrs - Source (owncall), destination (peercall), + * and possibly digipeaters. + * + * num_addr - Number of addresses. 2 to 10. + * First two are used to uniquely identify link. + * Any digipeaters involved are remembered + * from when the link was established. + * + * chan - Channel, 0 is first. + * + * client - Client application instance. + * + * pid - Protocol ID for data. Normally 0xf0 but the API + * allows the client app to use something non-standard + * for special situations. + * + * xdata_ptr - Pointer to block of data. + * + * xdata_len - Length of data in bytes. + * + * Outputs: Request is appended to queue for processing by + * the data link state machine. + * + *--------------------------------------------------------------------*/ + + +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) +{ + struct dlq_item_s *pnew; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dlq_xmit_data_request (...)\n"); +#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_XMIT_DATA_REQUEST; + pnew->chan = chan; + memcpy (pnew->addrs, addrs, sizeof(pnew->addrs)); + pnew->num_addr = num_addr; + pnew->client = client; + +/* Attach the transmit data. */ + + pnew->txdata = cdata_new(pid,xdata_ptr,xdata_len); + +/* Put it into queue. */ + + append_to_queue (pnew); + +} /* 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 */ + /*------------------------------------------------------------------- @@ -427,19 +819,24 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, * Purpose: Sleep while the received data queue is empty rather than * polling periodically. * - * Inputs: None. + * Inputs: timeout - Return at this time even if queue is empty. + * Zero for no timeout. + * + * Returns: True if timed out before any event arrived. + * + * Description: In version 1.4, we add timeout option so we can continue after + * some amount of time even if no events are in the queue. * *--------------------------------------------------------------------*/ -void dlq_wait_while_empty (void) +int dlq_wait_while_empty (double timeout) { - int err; - + int timed_out_result = 0; #if DEBUG1 text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty () \n"); + dw_printf ("dlq_wait_while_empty (%.3f)\n", timeout); #endif if ( ! was_init) { @@ -448,21 +845,34 @@ void dlq_wait_while_empty (void) if (queue_head == NULL) { + #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty (): prepare to SLEEP - about to call cond wait\n"); + dw_printf ("dlq_wait_while_empty (): prepare to SLEEP...\n"); #endif #if __WIN32__ - WaitForSingleObject (wake_up_event, INFINITE); + if (timeout != 0.0) { + + DWORD ms = (timeout - dtime_now()) * 1000; + if (ms <= 0) ms = 1; #if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty (): returned from wait\n"); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("WaitForSingleObject: timeout after %d ms\n", ms); #endif + if (WaitForSingleObject (wake_up_event, ms) == WAIT_TIMEOUT) { + timed_out_result = 1; + } + } + else { + WaitForSingleObject (wake_up_event, INFINITE); + } #else + int err; + err = pthread_mutex_lock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); @@ -472,20 +882,21 @@ void dlq_wait_while_empty (void) } recv_thread_is_waiting = 1; - err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); - recv_thread_is_waiting = 0; + if (timeout != 0.0) { + struct timespec abstime; -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err); -#endif + abstime.tv_sec = (time_t)(long)timeout; + abstime.tv_nsec = (long)((timeout - (long)abstime.tv_sec) * 1000000000.0); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_wait_while_empty: pthread_cond_wait err=%d", err); - perror (""); - exit (1); + err = pthread_cond_timedwait (&wake_up_cond, &wake_up_mutex, &abstime); + if (err == ETIMEDOUT) { + timed_out_result = 1; + } + } + else { + err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); } + recv_thread_is_waiting = 0; err = pthread_mutex_unlock (&wake_up_mutex); if (err != 0) { @@ -494,17 +905,18 @@ void dlq_wait_while_empty (void) perror (""); exit (1); } - #endif } #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty () returns\n"); + dw_printf ("dlq_wait_while_empty () returns timedout=%d\n", timed_out_result); #endif + return (timed_out_result); + +} /* end dlq_wait_while_empty */ -} /*------------------------------------------------------------------- @@ -515,27 +927,17 @@ void dlq_wait_while_empty (void) * * Inputs: None. * - * Outputs: type - type of queue entry. - * - * chan - channel of received frame. - * subchan - which demodulator caught it. - * slice - which slicer caught it. - * - * pp - pointer to packet object when type is DLQ_REC_FRAME. - * Caller should destroy it with ax25_delete when finished with it. - * - * Returns: 1 for success. - * 0 if queue is empty. + * Returns: Pointer to a queue item. Caller is responsible for deleting it. + * NULL if queue is empty. * *--------------------------------------------------------------------*/ -int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize) +struct dlq_item_s *dlq_remove (void) { - struct dlq_item_s *phead; - int result; - int err; + struct dlq_item_s *result = NULL; + //int err; #if DEBUG1 text_color_set(DW_COLOR_DEBUG); @@ -549,6 +951,8 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t #if __WIN32__ EnterCriticalSection (&dlq_cs); #else + int err; + err = pthread_mutex_lock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); @@ -558,34 +962,9 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t } #endif - if (queue_head == NULL) { - - *type = -1; - *chan = -1; - *subchan = -1; - *slice = -1; - *pp = NULL; - - memset (alevel, 0xff, sizeof(*alevel)); - - *retries = -1; - strlcpy(spectrum, "", spectrumsize); - result = 0; - } - else { - - phead = queue_head; + if (queue_head != NULL) { + result = queue_head; queue_head = queue_head->nextp; - - *type = phead->type; - *chan = phead->chan; - *subchan = phead->subchan; - *slice = phead->slice; - *pp = phead->pp; - *alevel = phead->alevel; - *retries = phead->retries; - strlcpy (spectrum, phead->spectrum, spectrumsize); - result = 1; } #if __WIN32__ @@ -602,19 +981,22 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_remove() returns type=%d, chan=%d\n", (int)(*type), *chan); + dw_printf ("dlq_remove() returns \n"); #endif #if AX25MEMDEBUG - if (ax25memdebug_get() && result) { + if (ax25memdebug_get() && result != NULL) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_remove (type=%d, chan=%d.%d, seq=%d, ...)\n", *type, *chan, *subchan, ax25memdebug_seq(*pp)); + if (result->pp != NULL) { +// TODO: mnemonics for type. + dw_printf ("dlq_remove (type=%d, chan=%d.%d, seq=%d, ...)\n", result->type, result->chan, result->subchan, ax25memdebug_seq(result->pp)); + } + else { + dw_printf ("dlq_remove (type=%d, chan=%d, ...)\n", result->type, result->chan); + } } #endif - if (result) { - free (phead); - } return (result); } @@ -622,25 +1004,156 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t /*------------------------------------------------------------------- * - * Name: dlq_is_empty + * Name: dlq_delete * - * Purpose: Test whether queue is empty. + * Purpose: Release storage used by a queue item. * - * Inputs: None + * Inputs: pitem - Pointer to a queue item. * - * Returns: True if nothing in the queue. + *--------------------------------------------------------------------*/ + + +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); + 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. * *--------------------------------------------------------------------*/ -#if 0 -static int dlq_is_empty (void) + +cdata_t *cdata_new (int pid, char *data, int len) { - if (queue_head == NULL) { - return (1); + 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); } - return (0); + 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_is_empty */ -#endif /* end dlq.c */ diff --git a/dlq.h b/dlq.h index acfbce36..6874fb06 100644 --- a/dlq.h +++ b/dlq.h @@ -12,17 +12,128 @@ #include "audio.h" -void dlq_init (void); +/* 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_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. */ + +// TODO: call this event rather than item. +// TODO: should add fences. + +typedef struct dlq_item_s { + + struct dlq_item_s *nextp; /* Next item in queue. */ + + dlq_type_t type; /* Type of item. */ + /* 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 */ + /* decoders on one channel. */ + /* Special case, -1 means DTMF decoder. */ + /* Maybe we should have a different type in this case? */ + + int slice; /* Winning slicer. */ + + packet_t pp; /* Pointer to frame structure. */ + + alevel_t alevel; /* Audio level. */ + + retry_t retries; /* Effort expended to get a valid CRC. */ + + char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */ + +// Used by requests from a client application, connect, etc. + + char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; + + int num_addr; /* Range 2 .. 10. */ + + int client; + + +// 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; + + + +void dlq_init (void); + + + +void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum); + +void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid); + +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 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); + +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 dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum); +void cdata_delete (cdata_t *txdata); -void dlq_wait_while_empty (void); +void cdata_check_leak (void); -int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize); #endif diff --git a/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf b/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf new file mode 100644 index 00000000..077c5ff3 Binary files /dev/null and b/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf differ diff --git a/doc/APRS-Telemetry-Toolkit.pdf b/doc/APRS-Telemetry-Toolkit.pdf index 68568bf5..b88f8f40 100644 Binary files a/doc/APRS-Telemetry-Toolkit.pdf and b/doc/APRS-Telemetry-Toolkit.pdf differ diff --git a/doc/Going-beyond-9600-baud.pdf b/doc/Going-beyond-9600-baud.pdf new file mode 100644 index 00000000..18e0aa6b Binary files /dev/null and b/doc/Going-beyond-9600-baud.pdf differ diff --git a/doc/README.md b/doc/README.md index 67527f20..734f3a01 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,13 +1,14 @@ # Documentation for Dire Wolf # +Click on the document name to view in your web browser or the link following to download the PDF file. ## Essential Reading ## -- [User Guide](User-Guide.pdf) +- [**User Guide**](User-Guide.pdf) [ [*download*](../../../raw/dev/doc/User-Guide.pdf) ] This is your primary source of information about installation, operation, and configuration. -- [Raspberry Pi APRS](Raspberry-Pi-APRS.pdf) +- [**Raspberry Pi APRS**](Raspberry-Pi-APRS.pdf) [ [*download*](../../../raw/dev/doc/Raspberry-Pi-APRS.pdf) ] The Raspberry Pi has some special considerations that make it different from other generic Linux systems. @@ -18,37 +19,60 @@ These dive into more detail for specialized topics or typical usage scenarios. +- [**Successful APRS IGate Operation**](Successful-APRS-IGate-Operation.pdf) [ [*download*](../../../raw/dev/doc/Successful-APRS-IGate-Operation.pdf) ] -- [APRStt Implementation Notes](APRStt-Implementation-Notes.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) [ [*download*](../../../raw/dev/doc/APRStt-Implementation-Notes.pdf) ] Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones. APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network. This document explains how the APRStt concept was implemented in the Dire Wolf application. -- [APRStt Interface for SARTrack](APRStt-interface-for-SARTrack.pdf) +- [**APRStt Interface for SARTrack**](APRStt-interface-for-SARTrack.pdf) [ [*download*](../../../raw/dev/doc/APRStt-interface-for-SARTrack.pdf) ] This example illustrates how APRStt can be integrated with other applications such as SARTrack, APRSISCE/32, YAAC, or Xastir. -- [APRStt Listening Example](APRStt-Listening-Example.pdf) +- [**APRStt Listening Example**](APRStt-Listening-Example.pdf) [ [*download*](../../../raw/dev/doc/APRStt-Listening-Example.pdf) ] + WB4APR described a useful application for the [QIKCOM-2 Satallite Transponder](http://www.tapr.org/pipermail/aprssig/2015-November/045035.html). Don’t have your own QIKCOM-2 Satellite Transponder? No Problem. You can do the same thing with an ordinary computer and the APRStt gateway built into Dire Wolf. Here’s how. -- [Raspberry Pi SDR IGate](Raspberry-Pi-SDR-IGate.pdf) +- [**Raspberry Pi APRS Tracker**](Raspberry-Pi-APRS-Tracker.pdf) [ [*download*](../../../raw/dev/doc/Raspberry-Pi-APRS-Tracker.pdf) ] + + Build a tracking device which transmits position from a GPS receiver. + +- [**Raspberry Pi SDR IGate**](Raspberry-Pi-SDR-IGate.pdf) [ [*download*](../../../raw/dev/doc/Raspberry-Pi-SDR-IGate.pdf) ] It's easy to build a receive-only APRS Internet Gateway (IGate) with only a Raspberry Pi and a software defined radio (RTL-SDR) dongle. Here’s how. -- [APRS Telemetry Toolkit](APRS-Telemetry-Toolkit.pdf) +- [**APRS Telemetry Toolkit**](APRS-Telemetry-Toolkit.pdf) [ [*download*](../../../raw/dev/doc/APRS-Telemetry-Toolkit.pdf) ] Describes scripts and methods to generate telemetry. Includes a complete example of attaching an analog to digital converter to a Raspberry Pi and transmitting a measured voltage. + + +- [**2400 & 4800 bps PSK for APRS / Packet Radio**](2400-4800-PSK-for-APRS-Packet-Radio.pdf) [ [*download*](../../../raw/dev/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf) ] + + + Double or quadruple your data rate by sending multiple bits at the same time. + +- [**Going beyond 9600 baud**](Going-beyond-9600-baud.pdf) [ [*download*](../../../raw/dev/doc/Going-beyond-9600-baud.pdf) ] + + + Why stop at 9600 baud? Go faster if your soundcard and radio can handle it. + + ## Miscellaneous ## -- [A Better APRS Packet Demodulator, part 1, 1200 baud](A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) +- [**A Better APRS Packet Demodulator, part 1, 1200 baud**](A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) [ [*download*](../../../raw/dev/doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) ] Sometimes it's a little mystifying why an APRS / AX.25 Packet TNC will decode some signals @@ -60,7 +84,7 @@ and a couple things that can be done about it. -- [A Better APRS Packet Demodulator, part 2, 9600 baud](A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) +- [**A Better APRS Packet Demodulator, part 2, 9600 baud**](A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) [ [*download*](../../../raw/dev/doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) ] In the first part of this series we discussed 1200 baud audio frequency shift keying (AFSK). The mismatch between FM transmitter pre-emphasis and the @@ -69,13 +93,28 @@ and a couple things that can be done about it. This makes it more difficult to demodulate them accurately. 9600 baud operation is an entirely different animal. ... -- [WA8LMF TNC Test CD Results a.k.a. Battle of the TNCs](WA8LMF-TNC-Test-CD-Results.pdf) +- [**WA8LMF TNC Test CD Results a.k.a. Battle of the TNCs**](WA8LMF-TNC-Test-CD-Results.pdf) [ [*download*](../../../raw/dev/doc/WA8LMF-TNC-Test-CD-Results.pdf) ] How can we compare how well the TNCs perform under real world conditions? The de facto standard of measurement is the number of packets decoded from [WA8LMF’s TNC Test CD](http://wa8lmf.net/TNCtest/index.htm). Many have published the number of packets they have been able to decode from this test. Here they are, all gathered in one place, for your reading pleasure. -- [A Closer Look at the WA8LMF TNC Test CD](A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) +- [**A Closer Look at the WA8LMF TNC Test CD**](A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) [ [*download*](../../../raw/dev/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) ] Here, we take a closer look at some of the frames on the TNC Test CD in hopes of gaining some insights into why some are easily decoded and others are more difficult. - There are a lot of ugly signals out there. Many can be improved by decreasing the transmit volume. Others are just plain weird and you have to wonder how they are being generated. \ No newline at end of file + There are a lot of ugly signals out there. Many can be improved by decreasing the transmit volume. Others are just plain weird and you have to wonder how they are being generated. + +## Questions? Experiences to share? ## + +Here are some good places to ask questions and share your experiences: + +- [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) + +- [Raspberry Pi 4 Ham Radio](https://groups.yahoo.com/neo/groups/Raspberry_Pi_4-Ham_RADIO/info) + +- [linuxham](https://groups.yahoo.com/neo/groups/linuxham/info) + +- [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. diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf index dc6120f8..86b266cb 100644 Binary files a/doc/Raspberry-Pi-APRS.pdf and b/doc/Raspberry-Pi-APRS.pdf differ diff --git a/doc/Raspberry-Pi-SDR-IGate.pdf b/doc/Raspberry-Pi-SDR-IGate.pdf index 68037bd6..5f0d302f 100644 Binary files a/doc/Raspberry-Pi-SDR-IGate.pdf and b/doc/Raspberry-Pi-SDR-IGate.pdf differ diff --git a/doc/Successful-APRS-IGate-Operation.pdf b/doc/Successful-APRS-IGate-Operation.pdf new file mode 100644 index 00000000..ce8b3cb1 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 2015e9ae..848bcc11 100644 Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ diff --git a/doc/WA8LMF-TNC-Test-CD-Results.pdf b/doc/WA8LMF-TNC-Test-CD-Results.pdf index cf7c523d..7f883acd 100644 Binary files a/doc/WA8LMF-TNC-Test-CD-Results.pdf and b/doc/WA8LMF-TNC-Test-CD-Results.pdf differ diff --git a/dsp.c b/dsp.c index f2a4343a..fa163053 100644 --- a/dsp.c +++ b/dsp.c @@ -26,6 +26,8 @@ * *----------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -34,7 +36,6 @@ #include #include -#include "direwolf.h" #include "audio.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" @@ -51,7 +52,7 @@ // Don't remove this. It serves as a reminder that an experiment is underway. #if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE) -#define DEBUG1 1 +#define DEBUG1 1 // Don't remove this. #endif diff --git a/dtmf.c b/dtmf.c index 34792444..031946e4 100644 --- a/dtmf.c +++ b/dtmf.c @@ -5,7 +5,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 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 @@ -32,23 +32,28 @@ * References: http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm * http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt * + * Revisions: 1.4 - Added transmit capability. + * *---------------------------------------------------------------*/ +#include "direwolf.h" #include #include #include #include +#include -#include "direwolf.h" #include "dtmf.h" #include "hdlc_rec.h" // for dcd_change +#include "textcolor.h" +#include "gen_tone.h" #if DTMF_TEST #define TIMEOUT_SEC 1 /* short for unit test below. */ -#define DEBUG 1 +#define DEBUG 1 // Don't remove this. We want more output for test. #else #define TIMEOUT_SEC 5 /* for normal operation. */ #endif @@ -78,8 +83,11 @@ static struct dd_s { /* Separate for each audio channel. */ } dd[MAX_CHANS]; +static int s_amplitude = 100; // range of 0 .. 100 +static void push_button (int chan, char button, int ms); + /*------------------------------------------------------------------ * @@ -99,17 +107,23 @@ static struct dd_s { /* Separate for each audio channel. */ * In version 1.2, we can have multiple soundcards * with potentially different sample rates. * + * amp - Signal amplitude, for transmit, on scale of 0 .. 100. + * + * 100 will produce maximum amplitude of +-32k samples. + * * Returns: None. * *----------------------------------------------------------------*/ -void dtmf_init (struct audio_s *p_audio_config) +void dtmf_init (struct audio_s *p_audio_config, int amp) { int j; /* Loop over all tones frequencies. */ int c; /* Loop over all audio channels. */ + s_amplitude = amp; + /* * Pick a suitable processing block size. * Larger = narrower bandwidth, slower response. @@ -118,15 +132,16 @@ void dtmf_init (struct audio_s *p_audio_config) for (c=0; csample_rate = p_audio_config->adev[a].samples_per_sec; + if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) { #if DEBUG + text_color_set(DW_COLOR_DEBUG); dw_printf ("channel %d:\n", c); #endif - - D->sample_rate = p_audio_config->adev[a].samples_per_sec; D->block_size = (205 * D->sample_rate) / 8000; - #if DEBUG dw_printf (" freq k coef \n"); @@ -142,9 +157,9 @@ void dtmf_init (struct audio_s *p_audio_config) k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate); - D->coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D->block_size)); + D->coef[j] = 2.0f * cosf(2.0f * (float)M_PI * (float)k / (float)(D->block_size)); - assert (D->coef[j] > 0 && D->coef[j] < 2.0); + assert (D->coef[j] > 0.0f && D->coef[j] < 2.0f); #if DEBUG dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]); #endif @@ -240,7 +255,7 @@ char dtmf_sample (int c, float input) */ -#define THRESHOLD 1.74 +#define THRESHOLD 1.74f if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0; else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1; @@ -273,7 +288,9 @@ char dtmf_sample (int c, float input) // Update Data Carrier Detect Indicator. +#ifndef DTMF_TEST dcd_change (c, MAX_SUBCHANS, 0, decoded != ' '); +#endif /* Reset timeout timer. */ if (decoded != ' ') { @@ -312,149 +329,262 @@ char dtmf_sample (int c, float input) } -/*------------------------------------------------------------------ + +/*------------------------------------------------------------------- * - * Name: main + * Name: dtmf_send * - * Purpose: Unit test for functions above. + * Purpose: Generate DTMF tones from text string. * - * Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe + * Inputs: chan - Radio channel number. + * str - Character string to send. 0-9, A-D, *, # + * speed - Number of tones per second. Range 1 to 10. + * txdelay - Delay (ms) from PTT to start. + * txtail - Delay (ms) from end to PTT off. * - *----------------------------------------------------------------*/ + * Returns: Total number of milliseconds to activate PTT. + * This includes delays before the first tone + * and after the last to avoid chopping off part of it. + * + * Description: xmit_thread calls this instead of the usual hdlc_send + * when we have a special packet that means send DTMF. + * + *--------------------------------------------------------------------*/ +int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail) +{ + char *p; + int len_ms; // Length of tone or gap between. -#if DTMF_TEST + len_ms = (int) ( ( 500.0f / (float)speed ) + 0.5f); + + push_button (chan, ' ', txdelay); + + for (p = str; *p != '\0'; p++) { + + push_button (chan, *p, len_ms); + push_button (chan, ' ', len_ms); + } + + push_button (chan, ' ', txtail); + +#ifndef DTMF_TEST + audio_flush(ACHAN2ADEV(chan)); +#endif + return (txdelay + + (int) (1000.0f * (float)strlen(str) / (float)speed + 0.5f) + + txtail); + +} /* end dtmf_send */ + + + +/*------------------------------------------------------------------ + * + * Name: push_button + * + * Purpose: Generate DTMF tone for a button push. + * + * Inputs: chan - Radio channel number. + * + * button - One of 0-9, A-D, *, #. Others result in silence. + * '?' is a special case used only for unit testing. + * + * ms - Duration in milliseconds. + * Use 50 ms for tone and 50 ms of silence for max rate of 10 per second. + * + * Outputs: Audio is sent to radio. + * + *----------------------------------------------------------------*/ -push_button (char button, int ms) +static void push_button (int chan, char button, int ms) { - static float phasea = 0; - static float phaseb = 0; - float fa, fb; + float phasea = 0; + float phaseb = 0; + float fa = 0; + float fb = 0; int i; - float input; + float dtmf; // Audio. Sum of two sine waves. +#if DTMF_TEST char x; static char result[100]; static int result_len = 0; - int c = 0; // fake channel number. - +#endif switch (button) { case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break; case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break; case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break; + case 'a': case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break; + case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break; case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break; case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break; + case 'b': case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break; + case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break; case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break; case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break; + case 'c': case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break; + case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break; case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break; case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break; + case 'd': case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break; - case '?': -// TODO: why are timeouts failing. Do we care? +#if DTMF_TEST + + case '?': /* check result */ if (strcmp(result, "123A456B789C*0#D123$789$") == 0) { + text_color_set(DW_COLOR_REC); dw_printf ("\nSuccess!\n"); } else if (strcmp(result, "123A456B789C*0#D123789") == 0) { + text_color_set(DW_COLOR_ERROR); dw_printf ("\n * Time-out failed, otherwise OK *\n"); dw_printf ("\"%s\"\n", result); + exit (EXIT_FAILURE); } else { + text_color_set(DW_COLOR_ERROR); dw_printf ("\n *** TEST FAILED ***\n"); dw_printf ("\"%s\"\n", result); + exit (EXIT_FAILURE); } break; - - default: fa = 0; fb = 0; +#endif } - for (i = 0; i < (ms*dd[c].sample_rate)/1000; i++) { + //dw_printf ("push_button (%d, '%c', %d), fa=%.0f, fb=%.0f. %d samples\n", chan, button, ms, fa, fb, (ms*dd[chan].sample_rate)/1000); + + for (i = 0; i < (ms*dd[chan].sample_rate)/1000; i++) { + + // This could be more efficient with a precomputed sine wave table + // but I'm not that worried about it. + // With a Raspberry Pi, model 2, default 1200 receiving takes about 14% of one CPU core. + // When transmitting tones, it briefly shoots up to about 33%. - input = sin(phasea) + sin(phaseb); - phasea += 2 * M_PI * fa / dd[c].sample_rate; - phaseb += 2 * M_PI * fb / dd[c].sample_rate; + if (fa > 0 && fb > 0) { + dtmf = sinf(phasea) + sinf(phaseb); + phasea += 2.0f * (float)M_PI * fa / dd[chan].sample_rate; + phaseb += 2.0f * (float)M_PI * fb / dd[chan].sample_rate; + } + else { + dtmf = 0; + } + +#if DTMF_TEST /* Make sure it is insensitive to signal amplitude. */ + /* (Uncomment each of below when testing.) */ - x = dtmf_sample (0, input); - //x = dtmf_sample (0, input * 1000); - //x = dtmf_sample (0, input * 0.001); + x = dtmf_sample (0, dtmf); + //x = dtmf_sample (0, dtmf * 1000); + //x = dtmf_sample (0, dtmf * 0.001); if (x != ' ' && x != '.') { result[result_len] = x; result_len++; result[result_len] = '\0'; } +#else + + // 'dtmf' can be in range of +-2.0 because it is sum of two sine waves. + // Amplitude of 100 would use full +-32k range. + + int sam = (int)(dtmf * 16383.0f * (float)s_amplitude / 100.0f); + gen_tone_put_sample (chan, ACHAN2ADEV(chan), sam); + +#endif } } + +/*------------------------------------------------------------------ + * + * Name: main + * + * Purpose: Unit test for functions above. + * + * Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe + * or + * make dtmftest + * + *----------------------------------------------------------------*/ + +#if DTMF_TEST + static struct audio_s my_audio_config; -main () +int main () { + int c = 0; // radio channel. memset (&my_audio_config, 0, sizeof(my_audio_config)); - my_audio_config.adev[0].defined = 1; - my_audio_config.adev[0].samples_per_sec = 44100; - my_audio_config.achan[0].valid = 1; - my_audio_config.achan[0].dtmf_decode = DTMF_DECODE_ON; + my_audio_config.adev[ACHAN2ADEV(c)].defined = 1; + my_audio_config.adev[ACHAN2ADEV(c)].samples_per_sec = 44100; + my_audio_config.achan[c].valid = 1; + my_audio_config.achan[c].dtmf_decode = DTMF_DECODE_ON; - dtmf_init(&my_audio_config); + dtmf_init(&my_audio_config, 50); + text_color_set(DW_COLOR_INFO); dw_printf ("\nFirst, check all button tone pairs. \n\n"); /* Max auto dialing rate is 10 per second. */ - push_button ('1', 50); push_button (' ', 50); - push_button ('2', 50); push_button (' ', 50); - push_button ('3', 50); push_button (' ', 50); - push_button ('A', 50); push_button (' ', 50); + push_button (c, '1', 50); push_button (c, ' ', 50); + push_button (c, '2', 50); push_button (c, ' ', 50); + push_button (c, '3', 50); push_button (c, ' ', 50); + push_button (c, 'A', 50); push_button (c, ' ', 50); - push_button ('4', 50); push_button (' ', 50); - push_button ('5', 50); push_button (' ', 50); - push_button ('6', 50); push_button (' ', 50); - push_button ('B', 50); push_button (' ', 50); + push_button (c, '4', 50); push_button (c, ' ', 50); + push_button (c, '5', 50); push_button (c, ' ', 50); + push_button (c, '6', 50); push_button (c, ' ', 50); + push_button (c, 'B', 50); push_button (c, ' ', 50); - push_button ('7', 50); push_button (' ', 50); - push_button ('8', 50); push_button (' ', 50); - push_button ('9', 50); push_button (' ', 50); - push_button ('C', 50); push_button (' ', 50); + push_button (c, '7', 50); push_button (c, ' ', 50); + push_button (c, '8', 50); push_button (c, ' ', 50); + push_button (c, '9', 50); push_button (c, ' ', 50); + push_button (c, 'C', 50); push_button (c, ' ', 50); - push_button ('*', 50); push_button (' ', 50); - push_button ('0', 50); push_button (' ', 50); - push_button ('#', 50); push_button (' ', 50); - push_button ('D', 50); push_button (' ', 50); + push_button (c, '*', 50); push_button (c, ' ', 50); + push_button (c, '0', 50); push_button (c, ' ', 50); + push_button (c, '#', 50); push_button (c, ' ', 50); + push_button (c, 'D', 50); push_button (c, ' ', 50); + text_color_set(DW_COLOR_INFO); dw_printf ("\nShould reject very short pulses.\n\n"); - push_button ('1', 20); push_button (' ', 50); - push_button ('1', 20); push_button (' ', 50); - push_button ('1', 20); push_button (' ', 50); - push_button ('1', 20); push_button (' ', 50); - push_button ('1', 20); push_button (' ', 50); + push_button (c, '1', 20); push_button (c, ' ', 50); + push_button (c, '1', 20); push_button (c, ' ', 50); + push_button (c, '1', 20); push_button (c, ' ', 50); + push_button (c, '1', 20); push_button (c, ' ', 50); + push_button (c, '1', 20); push_button (c, ' ', 50); + text_color_set(DW_COLOR_INFO); dw_printf ("\nTest timeout after inactivity.\n\n"); /* For this test we use 1 second. */ /* In practice, it will probably more like 5. */ - push_button ('1', 250); push_button (' ', 500); - push_button ('2', 250); push_button (' ', 500); - push_button ('3', 250); push_button (' ', 1200); + push_button (c, '1', 250); push_button (c, ' ', 500); + push_button (c, '2', 250); push_button (c, ' ', 500); + push_button (c, '3', 250); push_button (c, ' ', 1200); - push_button ('7', 250); push_button (' ', 500); - push_button ('8', 250); push_button (' ', 500); - push_button ('9', 250); push_button (' ', 1200); + push_button (c, '7', 250); push_button (c, ' ', 500); + push_button (c, '8', 250); push_button (c, ' ', 500); + push_button (c, '9', 250); push_button (c, ' ', 1200); /* Check for expected results. */ - push_button ('?', 0); + push_button (c, '?', 0); + + exit (EXIT_SUCCESS); } /* end main */ diff --git a/dtmf.h b/dtmf.h index dea09df7..c1b52b91 100644 --- a/dtmf.h +++ b/dtmf.h @@ -3,10 +3,12 @@ #include "audio.h" -void dtmf_init (struct audio_s *p_audio_config); +void dtmf_init (struct audio_s *p_audio_config, int amp); char dtmf_sample (int c, float input); +int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail); + /* end dtmf.h */ diff --git a/dw-start.sh b/dw-start.sh old mode 100644 new mode 100755 index ac863964..360b9c73 --- a/dw-start.sh +++ b/dw-start.sh @@ -1,100 +1,185 @@ #!/bin/bash -# # Run this from crontab periodically to start up # Dire Wolf automatically. -# -# I prefer this method instead of putting something -# in ~/.config/autostart. That would start an application -# only when the desktop first starts up. -# -# This method will restart the application if it -# crashes or stops for any other reason. -# -# This script has some specifics the Raspberry Pi. -# Some adjustments might be needed for other Linux variations. -# -# -# When running from cron, we have a very minimal environment -# including PATH=/usr/bin:/bin. -# - -export PATH=/usr/local/bin:$PATH +# See User Guide for more discussion. +# For release 1.4 it is section 5.7 "Automatic Start Up After Reboot" +# but it could change in the future as more information is added. -# First wait a little while in case we just rebooted -# and the desktop hasn't started up yet. -# -sleep 30 -LOGFILE=/tmp/dw-start.log +# Versioning (this file, not direwolf version) +#----------- +# v1.3 - KI6ZHD - added variable support for direwolf binary location +# v1.2 - KI6ZHD - support different versions of VNC +# v1.1 - KI6ZHD - expanded version to support running on text-only displays with +# auto support; log placement change +# v1.0 - WB2OSZ - original version for Xwindow displays only -# -# Nothing to do if it is already running. -# -a=`pgrep direwolf` -if [ "$a" != "" ] -then - #date >> /tmp/dw-start.log - #echo "Already running." >> $LOGFILE - exit -fi +#How are you running Direwolf : within a GUI (Xwindows / VNC) or CLI mode # -# In my case, the Raspberry Pi is not connected to a monitor. -# I access it remotely using VNC as described here: -# http://learn.adafruit.com/adafruit-raspberry-pi-lesson-7-remote-control-with-vnc +# AUTO mode is design to try starting direwolf with GUI support and then +# if no GUI environment is available, it reverts to CLI support with screen # -# If VNC server is running, use its display number. -# Otherwise default to :0. +# GUI mode is suited for users with the machine running LXDE/Gnome/KDE or VNC +# which auto-logs on (sitting at a login prompt won't work) # +# CLI mode is suited for say a Raspberry Pi running the Jessie LITE version +# where it will run from the CLI w/o requiring Xwindows - uses screen -date >> /tmp/dw-start.log +RUNMODE=AUTO -export DISPLAY=":0" +# Location of the direwolf binary. Depends on $PATH as shown. +# change this if you want to use some other specific location. +# e.g. DIREWOLF="/usr/local/bin/direwolf" -v=`ps -ef | grep Xtightvnc | grep -v grep` -if [ "$v" != "" ] -then - d=`echo "$v" | sed 's/.*tightvnc *\(:[0-9]\).*/\1/'` - export DISPLAY="$d" -fi +DIREWOLF="direwolf" -echo "DISPLAY=$DISPLAY" >> $LOGFILE +#Direwolf start up command :: two examples where example one is enabled +# +# 1. For normal operation as TNC, digipeater, IGate, etc. +# Print audio statistics each 100 seconds for troubleshooting. +# Change this command to however you wish to start Direwolf -echo "Start up application." >> $LOGFILE +DWCMD="$DIREWOLF -a 100" +#--------------------------------------------------------------- # -# For normal operation as TNC, digipeater, IGate, etc. -# Print audio statistics each 100 seconds for troubleshooting. -# +# 2. Alternative for running with SDR receiver. +# Piping one application into another makes it a little more complicated. +# We need to use bash for the | to be recognized. -DWCMD="direwolf -a 100" +#DWCMD="bash -c 'rtl_fm -f 144.39M - | direwolf -c sdr.conf -r 24000 -D 1 -'" -# Alternative for running with SDR receiver. -# Piping one application into another makes it a little more complicated. -# We need to use bash for the | to be recognized. -#DWCMD="bash -c 'rtl_fm -f 144.39M - | direwolf -c sdr.conf -r 24000 -D 1 -'" +#Where will logs go - needs to be writable by non-root users +LOGFILE=/var/tmp/dw-start.log + + +#------------------------------------- +# Main functions of the script +#------------------------------------- + +#Status variables +SUCCESS=0 + +function CLI { + SCREEN=`which screen` + if [ $? -ne 0 ]; then + echo -e "Error: screen is not installed but is required for CLI mode. Aborting" + exit 1 + fi + + echo "Direwolf in CLI mode start up" + echo "Direwolf in CLI mode start up" >> $LOGFILE + + # Screen commands + # -d m :: starts the command in detached mode + # -S :: name the session + $SCREEN -d -m -S direwolf $DWCMD >> $LOGFILE + SUCCESS=1 + + $SCREEN -list direwolf + $SCREEN -list direwolf >> $LOGFILE + + echo "-----------------------" + echo "-----------------------" >> $LOGFILE +} + +function GUI { + # In this case + # In my case, the Raspberry Pi is not connected to a monitor. + # I access it remotely using VNC as described here: + # http://learn.adafruit.com/adafruit-raspberry-pi-lesson-7-remote-control-with-vnc + # + # If VNC server is running, use its display number. + # Otherwise default to :0 (the Xwindows on the HDMI display) + # + export DISPLAY=":0" + + #Reviewing for RealVNC sessions (stock in Raspbian Pixel) + if [ -n "`ps -ef | grep vncserver-x11-serviced | grep -v grep`" ]; then + sleep 0.1 + echo -e "\nRealVNC found - defaults to connecting to the :0 root window" + elif [ -n "`ps -ef | grep Xtightvnc | grep -v grep`" ]; then + #Reviewing for TightVNC sessions + echo -e "\nTightVNC found - defaults to connecting to the :1 root window" + v=`ps -ef | grep Xtightvnc | grep -v grep` + d=`echo "$v" | sed 's/.*tightvnc *\(:[0-9]\).*/\1/'` + export DISPLAY="$d" + fi + + echo "Direwolf in GUI mode start up" + echo "Direwolf in GUI mode start up" >> $LOGFILE + echo "DISPLAY=$DISPLAY" + echo "DISPLAY=$DISPLAY" >> $LOGFILE + + # + # Auto adjust the startup for your particular environment: gnome-terminal, xterm, etc. + # + + if [ -x /usr/bin/lxterminal ]; then + /usr/bin/lxterminal -t "Dire Wolf" -e "$DWCMD" & + SUCCESS=1 + elif [ -x /usr/bin/xterm ]; then + /usr/bin/xterm -bg white -fg black -e "$DWCMD" & + SUCCESS=1 + elif [ -x /usr/bin/x-terminal-emulator ]; then + /usr/bin/x-terminal-emulator -e "$DWCMD" & + SUCCESS=1 + else + echo "Did not find an X terminal emulator. Reverting to CLI mode" + SUCCESS=0 + fi + echo "-----------------------" + echo "-----------------------" >> $LOGFILE +} + +# ----------------------------------------------------------- +# Main Script start +# ----------------------------------------------------------- -# -# Adjust for your particular situation: gnome-terminal, xterm, etc. +# When running from cron, we have a very minimal environment +# including PATH=/usr/bin:/bin. # +export PATH=/usr/local/bin:$PATH +#Log the start of the script run and re-run +date >> $LOGFILE -if [ -x /usr/bin/lxterminal ] -then - /usr/bin/lxterminal -t "Dire Wolf" -e "$DWCMD" & -elif [ -x /usr/bin/xterm ] -then - /usr/bin/xterm -bg white -fg black -e "$DWCMD" & -elif [ -x /usr/bin/x-terminal-emulator ] +# First wait a little while in case we just rebooted +# and the desktop hasn't started up yet. +# +sleep 30 + + +# +# Nothing to do if Direwolf is already running. +# + +a=`ps ax | grep direwolf | grep -vi -e bash -e screen -e grep | awk '{print $1}'` +if [ -n "$a" ] then - /usr/bin/x-terminal-emulator -e "$DWCMD" & -else - echo "Did not find an X terminal emulator." + #date >> /tmp/dw-start.log + #echo "Direwolf already running." >> $LOGFILE + exit fi -echo "-----------------------" >> $LOGFILE +# Main execution of the script + +if [ $RUNMODE == "AUTO" ];then + GUI + if [ $SUCCESS -eq 0 ]; then + CLI + fi + elif [ $RUNMODE == "GUI" ];then + GUI + elif [ $RUNMODE == "CLI" ];then + CLI + else + echo -e "ERROR: illegal run mode given. Giving up" + exit 1 +fi diff --git a/dwgps.c b/dwgps.c index f8cc37ea..83e92444 100644 --- a/dwgps.c +++ b/dwgps.c @@ -48,13 +48,14 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include #include #include -#include "direwolf.h" #include "textcolor.h" #include "dwgps.h" #include "dwgpsnmea.h" diff --git a/dwgpsd.c b/dwgpsd.c index 37688ff9..57094978 100644 --- a/dwgpsd.c +++ b/dwgpsd.c @@ -34,6 +34,9 @@ *---------------------------------------------------------------*/ +#include "direwolf.h" + + #include #include #include @@ -48,6 +51,7 @@ #endif #if ENABLE_GPSD + #include // Debian bug report: direwolf (1.2-1) FTBFS with libgps22 as part of the gpsd transition (#803605): @@ -57,15 +61,21 @@ #error libgps API version might be incompatible. #endif -#endif +/* + * Information for interface to gpsd daemon. + */ + +static struct gps_data_t gpsdata; + +#endif /* ENABLE_GPSD */ -#include "direwolf.h" #include "textcolor.h" #include "dwgps.h" #include "dwgpsd.h" +#if ENABLE_GPSD static int s_debug = 0; /* Enable debug output. */ /* >= 1 show results from dwgps_read. */ @@ -73,11 +83,8 @@ static int s_debug = 0; /* Enable debug output. */ static void * read_gpsd_thread (void *arg); -/* - * Information for interface to gpsd daemon. - */ +#endif -static struct gps_data_t gpsdata; /*------------------------------------------------------------------- @@ -136,7 +143,7 @@ static struct gps_data_t gpsdata; * * Update: January 2016. * - * I'm told that it might work in Raspian, Jessie version. + * I'm told that the shared memory interface might work in Raspian, Jessie version. * Haven't tried it yet. */ @@ -152,7 +159,6 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug) int err; int arg = 0; char sport[12]; - dwgps_info_t info; s_debug = debug; @@ -174,7 +180,6 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug) snprintf (sport, sizeof(sport), "%d", pconfig->gpsd_port); err = gps_open (pconfig->gpsd_host, sport, &gpsdata); if (err != 0) { - dwgps_info_t info; text_color_set(DW_COLOR_ERROR); dw_printf ("Unable to connect to GPSD stream at %s:%s.\n", pconfig->gpsd_host, sport); @@ -330,7 +335,7 @@ static void * read_gpsd_thread (void *arg) return(0); // Terminate thread on serious error. -} /* end read_gps_thread */ +} /* end read_gpsd_thread */ #endif diff --git a/dwgpsnmea.c b/dwgpsnmea.c index 0de05122..6ce6963b 100644 --- a/dwgpsnmea.c +++ b/dwgpsnmea.c @@ -29,6 +29,9 @@ *---------------------------------------------------------------*/ +#include "direwolf.h" + + #include #include #include @@ -38,7 +41,6 @@ #include -#include "direwolf.h" #include "textcolor.h" #include "dwgps.h" #include "dwgpsnmea.h" @@ -47,7 +49,8 @@ static int s_debug = 0; /* Enable debug output. */ /* See dwgpsnmea_init description for values. */ - + +static struct misc_config_s *s_save_configp; @@ -105,10 +108,12 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug) HANDLE read_gps_th; #else pthread_t read_gps_tid; - int e; + //int e; #endif s_debug = debug; + s_save_configp = pconfig; + if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); @@ -160,6 +165,19 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug) } /* end dwgpsnmea_init */ +/* Return fd to share if waypoint wants same device. */ +/* Currently both are fixed speed at 4800. */ +/* If that ever becomes configurable, that needs to be compared too. */ + +MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed) +{ + if (strcmp(s_save_configp->gpsnmea_port, wp_port_name) == 0 && speed == 4800) { + return (s_gpsnmea_port_fd); + } + return (MYFDERROR); +} + + /*------------------------------------------------------------------- * * Name: read_gpsnmea_thread diff --git a/dwgpsnmea.h b/dwgpsnmea.h index a126a50c..ffe5a12b 100644 --- a/dwgpsnmea.h +++ b/dwgpsnmea.h @@ -1,5 +1,5 @@ -/* dwgpsnmea.h - For NMEA sentences over serial port */ +/* dwgpsnmea.h - For reading NMEA sentences over serial port */ @@ -8,10 +8,13 @@ #include "dwgps.h" /* for dwfix_t */ #include "config.h" +#include "serial_port.h" /* for MYFDTYPE */ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug); +MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed); + void dwgpsnmea_term (void); @@ -19,6 +22,7 @@ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat); + #endif diff --git a/encode_aprs.c b/encode_aprs.c index 67476f46..f46bca89 100644 --- a/encode_aprs.c +++ b/encode_aprs.c @@ -33,6 +33,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -42,7 +44,6 @@ #include #include -#include "direwolf.h" #include "encode_aprs.h" #include "latlong.h" #include "textcolor.h" @@ -598,7 +599,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int result_len += strlen(comment); } - if (result_len >= result_size) { + if (result_len >= (int)result_size) { text_color_set(DW_COLOR_ERROR); dw_printf ("encode_position result of %d characters won't fit into space provided.\n", result_len); } @@ -682,7 +683,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double memset (p->o.name, ' ', sizeof(p->o.name)); n = strlen(name); - if (n > sizeof(p->o.name)) n = sizeof(p->o.name); + if (n > (int)(sizeof(p->o.name))) n = sizeof(p->o.name); memcpy (p->o.name, name, n); p->o.live_killed = '*'; @@ -692,7 +693,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double #define XMIT_UTC 1 #if XMIT_UTC - gmtime_r (&thyme, &tm); + (void)gmtime_r (&thyme, &tm); #else /* Using local time, for this application, would make more sense to me. */ /* On Windows, localtime_r produces UTC. */ @@ -748,7 +749,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double result_len += strlen(comment); } - if (result_len >= result_size) { + if (result_len >= (int)result_size) { text_color_set(DW_COLOR_ERROR); dw_printf ("encode_object result of %d characters won't fit into space provided.\n", result_len); } diff --git a/fcs_calc.c b/fcs_calc.c index eff88d3a..97e34a5c 100644 --- a/fcs_calc.c +++ b/fcs_calc.c @@ -18,6 +18,8 @@ // +#include "direwolf.h" + #include /* diff --git a/fsk_demod_state.h b/fsk_demod_state.h index 74a723ce..a37b24d2 100644 --- a/fsk_demod_state.h +++ b/fsk_demod_state.h @@ -4,9 +4,11 @@ #include "rpack.h" +#include "audio.h" // for enum modem_t /* * Demodulator state. + * The name of the file is from we only had FSK. Now we have other techniques. * Different copy is required for each channel & subchannel being processed concurrently. */ @@ -24,6 +26,7 @@ struct demodulator_state_s /* * These are set once during initialization. */ + enum modem_t modem_type; // MODEM_AFSK, MODEM_8PSK, etc. char profile; // 'A', 'B', etc. Upper case. // Only needed to see if we are using 'F' to take fast path. @@ -132,10 +135,28 @@ struct demodulator_state_s float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); +/* + * These are for PSK only. + * They are number of delay line taps into previous symbol. + * They are one symbol period and + or - 45 degrees of the carrier frequency. + */ + int boffs; /* symbol length based on sample rate and baud. */ + int coffs; /* to get cos component of previous symbol. */ + int soffs; /* to get sin component of previous symbol. */ + + unsigned int lo_step; /* How much to advance the local oscillator */ + /* phase for each audio sample. */ + + int psk_use_lo; /* Use local oscillator rather than self correlation. */ + + /* * The rest are continuously updated. */ + unsigned int lo_phase; /* Local oscillator for PSK. */ + + /* * Most recent raw audio samples, before/after prefiltering. */ @@ -213,6 +234,7 @@ struct demodulator_state_s int prev_demod_data; // Previous data bit detected. // Used to look for transitions. + float prev_demod_out_f; /* This is used only for "9600" baud data. */ diff --git a/gen_packets.c b/gen_packets.c index 78ad964f..c2390107 100644 --- a/gen_packets.c +++ b/gen_packets.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2013, 2014, 2015 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 @@ -60,7 +60,7 @@ *------------------------------------------------------------------*/ - +#include "direwolf.h" #include #include @@ -74,7 +74,19 @@ #include "gen_tone.h" #include "textcolor.h" #include "morse.h" +#include "dtmf.h" + + +/* Own random number generator so we can get */ +/* same results on Windows and Linux. */ + +#define MY_RAND_MAX 0x7fffffff +static int seed = 1; +static int my_rand (void) { + seed = ((seed * 1103515245) + 12345) & MY_RAND_MAX; + return (seed); +} static void usage (char **argv); static int audio_file_open (char *fname, struct audio_s *pa); @@ -95,9 +107,10 @@ static void send_packet (char *str) int flen; int c; - if (g_morse_wpm > 0) { + // TODO: Why not use the destination field instead of command line option? + morse_send (0, str, g_morse_wpm, 100, 100); } else { @@ -105,8 +118,33 @@ static void send_packet (char *str) flen = ax25_pack (pp, fbuf); for (c=0; c 10000) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); + if (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } break; @@ -208,29 +248,55 @@ int main(int argc, char **argv) /* 1200 implies 1200/2200 AFSK. */ /* 9600 implies scrambled. */ + /* If you want something else, specify -B first */ + /* then anything to override these defaults. */ + modem.achan[0].baud = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud); - if (modem.achan[0].baud < 100 || modem.achan[0].baud > 10000) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); + if (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } - switch (modem.achan[0].baud) { - case 300: - modem.achan[0].mark_freq = 1600; + /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ + /* that need to be kept in sync. Maybe it could be a common function someday. */ + + if (modem.achan[0].baud < 600) { + modem.achan[0].modem_type = MODEM_AFSK; + modem.achan[0].mark_freq = 1600; // Typical for HF SSB modem.achan[0].space_freq = 1800; - break; - case 1200: - modem.achan[0].mark_freq = 1200; - modem.achan[0].space_freq = 2200; - break; - case 9600: + } + else if (modem.achan[0].baud < 1800) { + modem.achan[0].modem_type = MODEM_AFSK; + modem.achan[0].mark_freq = DEFAULT_MARK_FREQ; + modem.achan[0].space_freq = DEFAULT_SPACE_FREQ; + } + else if (modem.achan[0].baud < 3600) { + modem.achan[0].modem_type = MODEM_QPSK; + modem.achan[0].mark_freq = 0; + modem.achan[0].space_freq = 0; + dw_printf ("Using V.26 QPSK rather than AFSK.\n"); + if (modem.achan[0].baud != 2400) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Bit rate should be standard 2400 rather than specified %d.\n", modem.achan[0].baud); + } + } + else if (modem.achan[0].baud < 7200) { + modem.achan[0].modem_type = MODEM_8PSK; + modem.achan[0].mark_freq = 0; + modem.achan[0].space_freq = 0; + dw_printf ("Using V.27 8PSK rather than AFSK.\n"); + if (modem.achan[0].baud != 4800) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", modem.achan[0].baud); + } + } + else { modem.achan[0].modem_type = MODEM_SCRAMBLE; text_color_set(DW_COLOR_INFO); dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); - break; } break; @@ -341,6 +407,7 @@ int main(int argc, char **argv) case 'M': /* -M for morse code speed */ //TODO: document this. +// Why not base it on the destination field instead? g_morse_wpm = atoi(optarg); text_color_set(DW_COLOR_INFO); @@ -352,6 +419,11 @@ int main(int argc, char **argv) } break; + case 'X': + + experiment = 1; + break; + case '?': /* Unknown option message was already printed. */ @@ -389,16 +461,99 @@ int main(int argc, char **argv) } + if (experiment) { + modem.achan[0].modem_type = MODEM_QPSK; + modem.achan[0].baud = 2400; // really bps not baud. + amplitude = 100; + } - - gen_tone_init (&modem, amplitude/2); + gen_tone_init (&modem, amplitude/2, 1); morse_init (&modem, amplitude/2); + dtmf_init (&modem, amplitude/2); assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16); assert (modem.adev[0].num_channels == 1 || modem.adev[0].num_channels == 2); assert (modem.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC); + + if (experiment) { + int chan = 0; + int n; + + // 6 cycles of 1800 Hz. + for (n=0; n<8; n++) { + tone_gen_put_bit (chan, 0); + } + + // Shift 90 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 1); + + // Shift 90 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 1); + + // Shift 90 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 1); + + // Shift 90 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 1); + + // Shift 180 + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + + // Shift 270 + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 0); + + // Shift 0 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 0); + + // Shift 0 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 0); + + + // HDLC flag - six 1 in a row. + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 0); + + tone_gen_put_bit (chan, 0); // reverse even/odd position + + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 1); + tone_gen_put_bit (chan, 0); + + tone_gen_put_bit (chan, 0); + + // Shift 0 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 0); + + // Shift 0 + tone_gen_put_bit (chan, 0); + tone_gen_put_bit (chan, 0); + + audio_file_close (); + return (EXIT_SUCCESS); + } + /* * Get user packets(s) from file or stdin if specified. * "-n" option is ignored in this case. @@ -466,17 +621,28 @@ int main(int argc, char **argv) char stemp[80]; - if (modem.achan[0].modem_type == MODEM_SCRAMBLE) { - g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count); - } - else if (modem.achan[0].baud < 600) { - /* About 2/3 should be decoded properly. */ + if (modem.achan[0].baud < 600) { + /* e.g. 300 bps AFSK - About 2/3 should be decoded properly. */ g_noise_level = amplitude *.0048 * ((float)i / packet_count); } - else { - /* About 2/3 should be decoded properly. */ + else if (modem.achan[0].baud < 1800) { + /* e.g. 1200 bps AFSK - About 2/3 should be decoded properly. */ g_noise_level = amplitude *.0023 * ((float)i / packet_count); } + else if (modem.achan[0].baud < 3600) { + /* e.g. 2400 bps QPSK - T.B.D. */ + g_noise_level = amplitude *.0015 * ((float)i / packet_count); + } + else if (modem.achan[0].baud < 7200) { + /* e.g. 4800 bps - T.B.D. */ + g_noise_level = amplitude *.0007 * ((float)i / packet_count); + } + else { + /* e.g. 9600 */ + g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count); + // temp test + //g_noise_level = 0.20 * (amplitude / 200.0) * ((float)i / packet_count); + } snprintf (stemp, sizeof(stemp), "WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! %04d of %04d", i, packet_count); @@ -511,15 +677,15 @@ static void usage (char **argv) dw_printf ("Options:\n"); dw_printf (" -a Signal amplitude in range of 0 - 200%%. Default 50.\n"); dw_printf (" -b Bits / second for data. Default is %d.\n", DEFAULT_BAUD); - dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 9600.\n"); + dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600.\n"); dw_printf (" -g Scrambled baseband rather than AFSK.\n"); dw_printf (" -m Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ); dw_printf (" -s Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ); dw_printf (" -r Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC); dw_printf (" -n Generate specified number of frames with increasing noise.\n"); dw_printf (" -o Send output to .wav file.\n"); -// dw_printf (" -8 8 bit audio rather than 16.\n"); -// dw_printf (" -2 2 channels of audio rather than 1.\n"); + dw_printf (" -8 8 bit audio rather than 16.\n"); + dw_printf (" -2 2 channels (stereo) audio rather than one channel.\n"); // dw_printf (" -z Number of leading zero bits before frame.\n"); // dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\n"); @@ -690,14 +856,6 @@ static int audio_file_open (char *fname, struct audio_s *pa) * *----------------------------------------------------------------*/ -#define MY_RAND_MAX 0x7fffffff - -static int seed = 1; - -static int my_rand (void) { - seed = ((seed * 1103515245) + 12345) & MY_RAND_MAX; - return (seed); -} int audio_put (int a, int c) { @@ -803,3 +961,11 @@ static int audio_file_close (void) } /* end audio_close */ + +// To keep dtmf.c happy. + +#include "hdlc_rec.h" // for dcd_change + +void dcd_change (int chan, int subchan, int slice, int state) +{ +} \ No newline at end of file diff --git a/gen_tone.c b/gen_tone.c index 63f36f9a..0d7c255b 100644 --- a/gen_tone.c +++ b/gen_tone.c @@ -1,7 +1,10 @@ +//#define DEBUG 1 +//#define DEBUG2 1 + // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 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 @@ -28,6 +31,9 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + + #include #include #include @@ -35,7 +41,7 @@ #include #include -#include "direwolf.h" + #include "audio.h" #include "gen_tone.h" #include "textcolor.h" @@ -47,7 +53,7 @@ // Properties of the digitized sound stream & modem. -static struct audio_s *save_audio_config_p; +static struct audio_s *save_audio_config_p = NULL; /* * 8 bit samples are unsigned bytes in range of 0 .. 255. @@ -68,18 +74,37 @@ static int ticks_per_bit[MAX_CHANS]; static int f1_change_per_sample[MAX_CHANS]; static int f2_change_per_sample[MAX_CHANS]; + static short sine_table[256]; /* Accumulators. */ static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation. - // Upper bits are used as index into sine table. + // Upper bits are used as index into sine table. + +#define PHASE_SHIFT_180 ( 128u << 24 ) +#define PHASE_SHIFT_90 ( 64u << 24 ) +#define PHASE_SHIFT_45 ( 32u << 24 ) + static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit. static int lfsr[MAX_CHANS]; // Shift register for scrambler. +static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted + // on the channel. This is only used for QPSK. + // The LSB determines if we save the bit until + // next time, or send this one with the previously saved. + // The LSB+1 position determines if we add an + // extra 180 degrees to the phase to compensate + // for having 1.5 carrier cycles per symbol time. + + // For 8PSK, it has a different meaning. It is the + // number of bits in 'save_bit' so we can accumulate + // three for each symbol. +static int save_bit[MAX_CHANS]; + /* * The K9NG/G3RUH output originally took a very simple and lazy approach. @@ -155,6 +180,9 @@ static int resample[MAX_CHANS]; * * amp - Signal amplitude on scale of 0 .. 100. * + * gen_packets - True if being called from "gen_packets" utility + * rather than the "direwolf" application. + * * Returns: 0 for success. * -1 for failure. * @@ -166,10 +194,16 @@ static int resample[MAX_CHANS]; static int amp16bit; /* for 9600 baud */ -int gen_tone_init (struct audio_s *audio_config_p, int amp) +int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) { int j; int chan = 0; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("gen_tone_init ( audio_config_p=%p, amp=%d, gen_packets=%d )\n", + audio_config_p, amp, gen_packets); +#endif /* * Save away modem parameters for later use. @@ -187,19 +221,58 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) int a = ACHAN2ADEV(chan); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("gen_tone_init: chan=%d, modem_type=%d, bps=%d, samples_per_sec=%d\n", + chan, + save_audio_config_p->achan[chan].modem_type, + audio_config_p->achan[chan].baud, + audio_config_p->adev[a].samples_per_sec); +#endif + + tone_phase[chan] = 0; + bit_len_acc[chan] = 0; + lfsr[chan] = 0; + ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); - ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); + // The terminology is all wrong here. Didn't matter with 1200 and 9600. + // The config speed should be bits per second rather than baud. + // ticks_per_bit should be ticks_per_symbol. - f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + switch (save_audio_config_p->achan[chan].modem_type) { - f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + case MODEM_QPSK: - tone_phase[chan] = 0; - - bit_len_acc[chan] = 0; + audio_config_p->achan[chan].mark_freq = 1800; + audio_config_p->achan[chan].space_freq = audio_config_p->achan[chan].mark_freq; // Not Used. - lfsr[chan] = 0; + // symbol time is 1 / (half of bps) + ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud * 0.5)) + 0.5); + f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. + + tone_phase[chan] = PHASE_SHIFT_45; // Just to mimic first attempt. + break; + + case MODEM_8PSK: + + audio_config_p->achan[chan].mark_freq = 1800; + audio_config_p->achan[chan].space_freq = audio_config_p->achan[chan].mark_freq; // Not Used. + + // symbol time is 1 / (third of bps) + ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud / 3.)) + 0.5); + f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. + break; + + default: + + ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); + f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + break; + } } } @@ -210,10 +283,18 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) a = ((double)(j) / 256.0) * (2 * M_PI); s = (int) (sin(a) * 32767 * amp / 100.0); - /* 16 bit sound sample is in range of -32768 .. +32767. */ - - assert (s >= -32768 && s <= 32767); + /* 16 bit sound sample must fit in range of -32768 .. +32767. */ + if (s < -32768) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("gen_tone_init: Excessive amplitude is being clipped.\n"); + s = -32768; + } + else if (s > 32767) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("gen_tone_init: Excessive amplitude is being clipped.\n"); + s = 32767; + } sine_table[j] = s; } @@ -235,12 +316,27 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) /* These numbers were by trial and error. Need more investigation here. */ float filter_len_bits = 88 * 9600.0 / (44100.0 * 2.0); - /* Filter length in number of data bits. */ + /* Filter length in number of data bits. */ + /* Currently 9.58 */ float lpf_baud = 0.8; /* Lowpass cutoff freq as fraction of baud rate */ float fc; /* Cutoff frequency as fraction of sampling frequency. */ +/* + * Normally, we want to generate the same thing whether sending over the air + * or putting it into a file for other testing. + * (There is an important exception. gen_packets can introduce random noise.) + * In this case, we want more aggressive low pass filtering so it looks more like + * what we see coming out of a receiver. + * Specifically, single bits of the same state have considerably reduced amplitude + * below several same values in a row. + */ + + if (gen_packets) { + filter_len_bits = 4; + lpf_baud = 0.55; /* Lowpass cutoff freq as fraction of baud rate */ + } samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE; baud = audio_config_p->achan[chan].baud; @@ -250,10 +346,15 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5); - if (lp_filter_size[chan] < 10 || lp_filter_size[chan] > MAX_FILTER_SIZE) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("gen_tone_init: INTERNAL ERROR, chan %d, lp_filter_size %d\n", chan, lp_filter_size[chan]); - lp_filter_size[chan] = MAX_FILTER_SIZE / 2; + if (lp_filter_size[chan] < 10) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d < 10\n", chan, lp_filter_size[chan]); + lp_filter_size[chan] = 10; + } + else if (lp_filter_size[chan] > MAX_FILTER_SIZE) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d > %d\n", chan, lp_filter_size[chan], MAX_FILTER_SIZE); + lp_filter_size[chan] = MAX_FILTER_SIZE; } fc = (float)baud * lpf_baud / (float)samples_per_sec; @@ -273,7 +374,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) /*------------------------------------------------------------------- * - * Name: gen_tone_put_bit + * Name: tone_gen_put_bit * * Purpose: Generate tone of proper duration for one data bit. * @@ -286,14 +387,19 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) * * Assumption: fp is open to a file for write. * + * Version 1.4: Attempt to implement 2400 and 4800 bps PSK modes. + * *--------------------------------------------------------------------*/ +static const int gray2phase_v26[4] = {0, 1, 3, 2}; +static const int gray2phase_v27[8] = {1, 0, 2, 3, 6, 7, 5, 4}; + void tone_gen_put_bit (int chan, int dat) { int a = ACHAN2ADEV(chan); /* device for channel. */ - + assert (save_audio_config_p != NULL); assert (save_audio_config_p->achan[chan].valid); @@ -303,6 +409,55 @@ void tone_gen_put_bit (int chan, int dat) dat = 0; } + if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) { + + int dibit; + int symbol; + + dat &= 1; // Keep only LSB to be extra safe. + + if ( ! (bit_count[chan] & 1)) { + save_bit[chan] = dat; + bit_count[chan]++; + return; + } + + // All zero bits should give us steady 1800 Hz. + // All one bits should flip phase by 180 degrees each time. + + dibit = (save_bit[chan] << 1) | dat; + + symbol = gray2phase_v26[dibit]; + tone_phase[chan] += symbol * PHASE_SHIFT_90; + + bit_count[chan]++; + } + + if (save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) { + + int tribit; + int symbol; + + dat &= 1; // Keep only LSB to be extra safe. + + if (bit_count[chan] < 2) { + save_bit[chan] = (save_bit[chan] << 1) | dat; + bit_count[chan]++; + return; + } + + // The bit pattern 001 should give us steady 1800 Hz. + // All one bits should flip phase by 180 degrees each time. + + tribit = (save_bit[chan] << 1) | dat; + + symbol = gray2phase_v27[tribit]; + tone_phase[chan] += symbol * PHASE_SHIFT_45; + + save_bit[chan] = 0; + bit_count[chan] = 0; + } + if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE) { int x; @@ -311,31 +466,63 @@ void tone_gen_put_bit (int chan, int dat) dat = x; } - do { + do { /* until enough audio samples for this symbol. */ - if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) { - int sam; + int sam; + float fsam; - tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan]; - sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; - gen_tone_put_sample (chan, a, sam); - } - else { - - float fsam = dat ? amp16bit : (-amp16bit); + switch (save_audio_config_p->achan[chan].modem_type) { - /* version 1.2 - added a low pass filter instead of square wave out. */ + case MODEM_AFSK: - push_sample (fsam, raw[chan], lp_filter_size[chan]); - - resample[chan]++; - if (resample[chan] >= UPSAMPLE) { - int sam; +#if DEBUG2 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tone_gen_put_bit %d AFSK\n", __LINE__); +#endif + tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan]; + sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; + gen_tone_put_sample (chan, a, sam); + break; + + case MODEM_QPSK: + case MODEM_8PSK: - sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]); - resample[chan] = 0; +#if DEBUG2 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__); +#endif + tone_phase[chan] += f1_change_per_sample[chan]; + sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; gen_tone_put_sample (chan, a, sam); - } + break; + + case MODEM_BASEBAND: + case MODEM_SCRAMBLE: + +#if DEBUG2 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tone_gen_put_bit %d SCR\n", __LINE__); +#endif + fsam = dat ? amp16bit : (-amp16bit); + + /* version 1.2 - added a low pass filter instead of square wave out. */ + + push_sample (fsam, raw[chan], lp_filter_size[chan]); + + resample[chan]++; + if (resample[chan] >= UPSAMPLE) { + + sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]); + resample[chan] = 0; + gen_tone_put_sample (chan, a, sam); + } + break; + + default: + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR: %s %d achan[%d].modem_type = %d\n", + __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].modem_type); + exit (EXIT_FAILURE); } /* Enough for the bit time? */ @@ -351,12 +538,16 @@ void tone_gen_put_bit (int chan, int dat) void gen_tone_put_sample (int chan, int a, int sam) { /* Ship out an audio sample. */ + /* 16 bit is signed, little endian, range -32768 .. +32767 */ + /* 8 bit is unsigned, range 0 .. 255 */ + + assert (save_audio_config_p != NULL); assert (save_audio_config_p->adev[a].num_channels == 1 || save_audio_config_p->adev[a].num_channels == 2); - /* Generalize to allow 8 bits someday? */ + assert (save_audio_config_p->adev[a].bits_per_sample == 16 || save_audio_config_p->adev[a].bits_per_sample == 8); - assert (save_audio_config_p->adev[a].bits_per_sample == 16); + // TODO: Should print message telling user to reduce output level. if (sam < -32767) sam = -32767; else if (sam > 32767) sam = 32767; @@ -365,8 +556,13 @@ void gen_tone_put_sample (int chan, int a, int sam) { /* Mono */ - audio_put (a, sam & 0xff); - audio_put (a, (sam >> 8) & 0xff); + if (save_audio_config_p->adev[a].bits_per_sample == 8) { + audio_put (a, ((sam+32768) >> 8) & 0xff); + } + else { + audio_put (a, sam & 0xff); + audio_put (a, (sam >> 8) & 0xff); + } } else { @@ -374,21 +570,33 @@ void gen_tone_put_sample (int chan, int a, int sam) { /* Stereo, left channel. */ - audio_put (a, sam & 0xff); - audio_put (a, (sam >> 8) & 0xff); + if (save_audio_config_p->adev[a].bits_per_sample == 8) { + audio_put (a, ((sam+32768) >> 8) & 0xff); + audio_put (a, 0); + } + else { + audio_put (a, sam & 0xff); + audio_put (a, (sam >> 8) & 0xff); - audio_put (a, 0); - audio_put (a, 0); + audio_put (a, 0); + audio_put (a, 0); + } } else { /* Stereo, right channel. */ - audio_put (a, 0); - audio_put (a, 0); + if (save_audio_config_p->adev[a].bits_per_sample == 8) { + audio_put (a, 0); + audio_put (a, ((sam+32768) >> 8) & 0xff); + } + else { + audio_put (a, 0); + audio_put (a, 0); - audio_put (a, sam & 0xff); - audio_put (a, (sam >> 8) & 0xff); + audio_put (a, sam & 0xff); + audio_put (a, (sam >> 8) & 0xff); + } } } } diff --git a/gen_tone.h b/gen_tone.h index 1937c4a8..bbe23b5c 100644 --- a/gen_tone.h +++ b/gen_tone.h @@ -3,7 +3,7 @@ */ -int gen_tone_init (struct audio_s *pp, int amp); +int gen_tone_init (struct audio_s *pp, int amp, int gen_packets); //int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname); diff --git a/geotranz/usng.c b/geotranz/usng.c index dbce1468..3db24b32 100644 --- a/geotranz/usng.c +++ b/geotranz/usng.c @@ -577,6 +577,9 @@ long UTM_To_USNG (long Zone, Northing = 0.0; } + ltr2_low_value = LETTER_A; // Make compiler shut up about possibly uninitialized value. + // It should be set by the following but compiler doesn't know. + USNG_Get_Grid_Values(Zone, <r2_low_value, <r2_high_value, &pattern_offset); error_code = USNG_Get_Latitude_Letter(Latitude, &letters[0]); @@ -960,6 +963,9 @@ long Convert_USNG_To_UTM (char *USNG, else *Hemisphere = 'N'; + ltr2_low_value = LETTER_A; // Make compiler shut up about possibly uninitialized values. + ltr2_high_value = LETTER_Z; // They should be set by the following but compiler doesn't know. + USNG_Get_Grid_Values(*Zone, <r2_low_value, <r2_high_value, &pattern_offset); /* Check that the second letter of the USNG string is within diff --git a/hdlc_rec.c b/hdlc_rec.c index 06d1c4bc..15b72c2a 100644 --- a/hdlc_rec.c +++ b/hdlc_rec.c @@ -27,11 +27,12 @@ * *******************************************************************************/ +#include "direwolf.h" + #include #include #include -#include "direwolf.h" #include "demod.h" #include "hdlc_rec.h" #include "hdlc_rec2.h" @@ -46,6 +47,7 @@ //#define TEST 1 /* Define for unit testing. */ + //#define DEBUG3 1 /* monitor the data detect signal. */ @@ -258,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 */ /* @@ -278,6 +287,7 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, * when listening to live signals. Let's try 3 and see how that works out. */ + //if (H->flag4_det == 0x7e7e7e7e) { if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) { //if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) { @@ -301,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. */ diff --git a/hdlc_rec2.c b/hdlc_rec2.c index 374fa980..a4764f50 100644 --- a/hdlc_rec2.c +++ b/hdlc_rec2.c @@ -84,13 +84,15 @@ * *******************************************************************************/ +#include "direwolf.h" + #include #include #include +#include //Optimize processing by accessing directly to decoded bits #define RRBB_C 1 -#include "direwolf.h" #include "hdlc_rec2.h" #include "fcs_calc.h" #include "textcolor.h" @@ -242,6 +244,8 @@ void hdlc_rec2_block (rrbb_t block) /* Create an empty retry configuration */ retry_conf_t retry_cfg; + memset (&retry_cfg, 0, sizeof(retry_cfg)); + /* * For our first attempt we don't try to alter any bits. * Still let it thru if passall AND no retries are desired. @@ -327,7 +331,7 @@ void hdlc_rec2_block (rrbb_t block) static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel) { int ok; - int len, i,j; + int len, i; retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; //int passall = save_audio_config_p->achan[chan].passall; @@ -335,6 +339,9 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, len = rrbb_get_len(block); /* Prepare the retry configuration */ retry_conf_t retry_cfg; + + memset (&retry_cfg, 0, sizeof(retry_cfg)); + /* Will modify only contiguous bits*/ retry_cfg.mode = RETRY_MODE_CONTIGUOUS; /* @@ -464,14 +471,17 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel) { int ok; - int len, i, j; - retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; + //int len; + //retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; int passall = save_audio_config_p->achan[chan].passall; #if DEBUG_LATER double tstart, tend; #endif retry_conf_t retry_cfg; - len = rrbb_get_len(block); + + memset (&retry_cfg, 0, sizeof(retry_cfg)); + + //len = rrbb_get_len(block); /* @@ -575,7 +585,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t struct hdlc_state_s H; int blen; /* Block length in bits. */ int i; - unsigned int raw; /* From demodulator. */ + int raw; /* From demodulator. Should be 0 or 1. */ #if DEBUGx int crc_failed = 1; #endif diff --git a/hdlc_send.c b/hdlc_send.c index 35114881..4a09cc82 100644 --- a/hdlc_send.c +++ b/hdlc_send.c @@ -1,3 +1,4 @@ + // // This file is part of Dire Wolf, an amateur radio packet TNC. // @@ -17,10 +18,10 @@ // along with this program. If not, see . // +#include "direwolf.h" #include -#include "direwolf.h" #include "hdlc_send.h" #include "audio.h" #include "gen_tone.h" @@ -33,7 +34,9 @@ static void send_bit (int, int); -static int number_of_bits_sent[MAX_CHANS]; +static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags" + + @@ -49,6 +52,8 @@ static int number_of_bits_sent[MAX_CHANS]; * * 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 @@ -70,8 +75,7 @@ static int number_of_bits_sent[MAX_CHANS]; * *--------------------------------------------------------------*/ - -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; @@ -81,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 @@ -94,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 */ @@ -162,7 +173,10 @@ int hdlc_send_flags (int chan, int nflags, int finish) -static int stuff = 0; +static int stuff[MAX_CHANS]; // Count number of "1" bits to keep track of when we + // need to break up a long run by "bit stuffing." + // Needs to be array because we could be transmitting + // on multiple channels at the same time. static void send_control (int chan, int x) { @@ -173,7 +187,7 @@ static void send_control (int chan, int x) x >>= 1; } - stuff = 0; + stuff[chan] = 0; } static void send_data (int chan, int x) @@ -183,13 +197,13 @@ static void send_data (int chan, int x) for (i=0; i<8; i++) { send_bit (chan, x & 1); if (x & 1) { - stuff++; - if (stuff == 5) { + stuff[chan]++; + if (stuff[chan] == 5) { send_bit (chan, 0); - stuff = 0; + stuff[chan] = 0; } } else { - stuff = 0; + stuff[chan] = 0; } x >>= 1; } @@ -203,13 +217,13 @@ static void send_data (int chan, int x) static void send_bit (int chan, int b) { - static int output; + static int output[MAX_CHANS]; if (b == 0) { - output = ! output; + output[chan] = ! output[chan]; } - tone_gen_put_bit (chan, output); + tone_gen_put_bit (chan, output[chan]); number_of_bits_sent[chan]++; } diff --git a/hdlc_send.h b/hdlc_send.h index 41d44b19..10d200c1 100644 --- a/hdlc_send.h +++ b/hdlc_send.h @@ -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); diff --git a/igate.c b/igate.c index 16041b61..5e0037de 100644 --- a/igate.c +++ b/igate.c @@ -63,18 +63,15 @@ * Cygwin: Can use either one. */ +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ /* The goal is to support Windows XP and later. */ #include -// default is 0x0400 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ -//#define _WIN32_WINNT 0x0502 /* Minimum OS version is XP with SP2. */ -//#define _WIN32_WINNT 0x0600 /* Minimum OS version is Vista. */ -#include +#include // _WIN32_WINNT must be set to 0x0501 before including this + #else #include #include @@ -102,6 +99,7 @@ #include "latlong.h" #include "pfilter.h" #include "dtime_now.h" +#include "mheard.h" #if __WIN32__ @@ -120,8 +118,8 @@ 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 send_msg_to_server (const char *msg, int msg_len); +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); @@ -268,7 +266,7 @@ int main (int argc, char *argv[]) SLEEP_SEC (20); text_color_set(DW_COLOR_INFO); dw_printf ("Send received packet\n"); - send_msg_to_server ("W1ABC>APRS:?"); + send_msg_to_server ("W1ABC>APRS:?", strlen("W1ABC>APRS:?"); } #endif return 0; @@ -293,8 +291,10 @@ static int s_debug; /* - * Statistics. - * TODO: need print function. + * Statistics for IGate function. + * Note that the RF related counters are just a subset of what is happening on radio channels. + * + * TODO: should have debug option to print these occasionally. */ static int stats_failed_connect; /* Number of times we tried to connect to */ @@ -312,8 +312,10 @@ static time_t stats_connect_at; /* Most recent time connection was established. /* can be used to determine elapsed connect time. */ static int stats_rf_recv_packets; /* Number of candidate packets from the radio. */ + /* This is not the total number of AX.25 frames received */ + /* over the radio; only APRS packets get this far. */ -static int stats_rx_igate_packets; /* Number of packets passed along to the IGate */ +static int stats_uplink_packets; /* Number of packets passed along to the IGate */ /* server after filtering. */ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */ @@ -322,40 +324,44 @@ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */ static int stats_downlink_bytes; /* Total number of bytes from IGate server including */ /* packets, heartbeats, other messages. */ -static int stats_tx_igate_packets; /* Number of packets from IGate server. */ - -static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ - /* after rate limiting or other restrictions. */ +static int stats_downlink_packets; /* Number of packets from IGate server for possible transmission. */ + /* Fewer might be transmitted due to filtering or rate limiting. */ -/* We have some statistics. What do we do with them? +static int stats_rf_xmit_packets; /* Number of packets passed along to radio, for the IGate function, */ + /* after filtering, rate limiting, or other restrictions. */ + /* Number of packets transmitted for beacons, digipeating, */ + /* or client applications are not included here. */ +static int stats_msg_cnt; /* Number of "messages" transmitted. Subset of above. */ + /* A "message" has the data type indicator of ":" and it is */ + /* not the special case of telemetry metadata. */ - IGate stations often send packets like this: - APDW14,WIDE1-1:ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); @@ -787,7 +797,7 @@ static void * connnect_thread (void *arg) strlcat (stemp, " filter ", sizeof(stemp)); strlcat (stemp, save_igate_config_p->t2_filter, sizeof(stemp)); } - send_msg_to_server (stemp); + send_msg_to_server (stemp, strlen(stemp)); /* Delay until it is ok to start sending packets. */ @@ -817,10 +827,13 @@ static void * connnect_thread (void *arg) strlcpy (heartbeat, "#", sizeof(heartbeat)); /* This will close the socket if any error. */ - send_msg_to_server (heartbeat); + send_msg_to_server (heartbeat, strlen(heartbeat)); } } + + exit(0); // Unreachable but stops compiler from complaining + // about function not returning a value. } /* end connnect_thread */ @@ -848,14 +861,15 @@ static void * connnect_thread (void *arg) * *--------------------------------------------------------------------*/ -#define IGATE_MAX_MSG 520 /* Message to IGate max 512 characters. */ +#define IGATE_MAX_MSG 512 /* "All 'packets' sent to APRS-IS must be in the TNC2 format terminated */ + /* by a carriage return, line feed sequence. No line may exceed 512 bytes */ + /* including the CR/LF sequence." */ void igate_send_rec_packet (int chan, packet_t recv_pp) { packet_t pp; int n; unsigned char *pinfo; - char *p; int info_len; @@ -867,26 +881,34 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) return; /* Login not complete. */ } + /* Gather statistics. */ + + stats_rf_recv_packets++; + /* * 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) { - 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) { - text_color_set(DW_COLOR_INFO); - dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]); + // 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. + // version 1.4: Instead, make the default off and activate it only with the debug igate option. + if (s_debug >= 1) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]); + } return; } } - - /* Gather statistics. */ - - stats_rf_recv_packets++; - /* * First make a copy of it because it might be modified in place. */ @@ -972,32 +994,25 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) /* * Cut the information part at the first CR or LF. + * This is required because CR/LF is used as record separator when sending to server. + * Do NOT trim trailing spaces. + * Starting in 1.4 we preserve any nul characters in the information part. */ - info_len = ax25_get_info (pp, &pinfo); - (void)(info_len); - - if ((p = strchr ((char*)pinfo, '\r')) != NULL) { + if (ax25_cut_at_crlf (pp) > 0) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Truncated information part at CR.\n"); } - *p = '\0'; } - if ((p = strchr ((char*)pinfo, '\n')) != NULL) { - if (s_debug >= 1) { - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Truncated information part at LF.\n"); - } - *p = '\0'; - } + info_len = ax25_get_info (pp, &pinfo); /* * Someone around here occasionally sends a packet with no information part. */ - if (strlen((char*)pinfo) == 0) { + if (info_len == 0) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); @@ -1053,10 +1068,15 @@ static void send_packet_to_server (packet_t pp, int chan) info_len = ax25_get_info (pp, &pinfo); - (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. + * However, the servers should drop all duplicates so we wasting everyone's time but sending duplicates. + * 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)) { @@ -1070,17 +1090,111 @@ 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)); - send_msg_to_server (msg); - stats_rx_igate_packets++; + + +// It was reported that APRS packets, containing a nul byte in the information part, +// are being truncated. https://github.com/wb2osz/direwolf/issues/84 +// +// One might argue that the packets are invalid and the proper behavior would be +// to simply discard them, the same way we do if the CRC is bad. One might argue +// that we should simply pass along whatever we receive even if we don't like it. +// We really shouldn't modify it and make the situation even worse. +// +// Chapter 5 of the APRS spec ( http://www.aprs.org/doc/APRS101.PDF ) says: +// +// "The comment may contain any printable ASCII characters (except | and ~, +// which are reserved for TNC channel switching)." +// +// "Printable" would exclude character values less than space (00100000), e.g. +// tab, carriage return, line feed, nul. Sometimes we see carriage return +// (00001010) at the end of APRS packets. This would be in violation of the +// specification. +// +// The MIC-E position format can have non printable characters (0x1c ... 0x1f, 0x7f) +// in the information part. An unfortunate decision, but it is not in the comment part. +// +// The base 91 telemetry format (http://he.fi/doc/aprs-base91-comment-telemetry.txt ), +// which is not part of the APRS spec, uses the | character in the comment to delimit encoded +// telemetry data. This would be in violation of the original spec. No one cares. +// +// The APRS Spec Addendum 1.2 Proposals ( http://www.aprs.org/aprs12/datum.txt) +// adds use of UTF-8 (https://en.wikipedia.org/wiki/UTF-8 )for the free form text in +// messages and comments. It can't be used in the fixed width fields. +// +// Non-ASCII characters are represented by multi-byte sequences. All bytes in these +// multi-byte sequences have the most significant bit set to 1. Using UTF-8 would not +// add any nul (00000000) bytes to the stream. +// +// Based on all of that, we would not expect to see a nul character in the information part. +// +// There are two known cases where we can have a nul character value. +// +// * The Kenwood TM-D710A sometimes sends packets like this: +// +// VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast= +// K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV = +// +// Notice that the data type indicator of "4" is not valid. If we remove +// 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format. +// This same thing has been observed from others and is intermittent. +// +// * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes. +// This is wrong, it should be using UTF-8. +// +// Rather than using strlcat here, we need to use memcpy and maintain our +// own lengths, being careful to avoid buffer overflow. + + int msg_len = strlen(msg); // What we have so far before info part. + + if (info_len > IGATE_MAX_MSG - msg_len - 2) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Rx IGate: Too long. Truncating.\n"); + info_len = IGATE_MAX_MSG - msg_len - 2; + } + if (info_len > 0) { + memcpy (msg + msg_len, pinfo, info_len); + msg_len += info_len; + } + + send_msg_to_server (msg, msg_len); + stats_uplink_packets++; /* * Remember what was sent to avoid duplicates in near future. @@ -1097,58 +1211,74 @@ static void send_packet_to_server (packet_t pp, int chan) * * Name: send_msg_to_server * - * Purpose: Send to the IGate server. + * Purpose: Send something to the IGate server. * This one function should be used for login, hearbeats, * and packets. * - * Inputs: imsg - Message. We will add CR/LF. + * Inputs: imsg - Message. We will add CR/LF here. * + * imsg_len - Length of imsg in bytes. + * It could contain nul characters so we can't + * use the normal C string functions. * * Description: Send message to IGate Server if connected. * Disconnect from server, and notify user, if any error. + * Should use a word other than message because that has + * a specific meaning for APRS. * *--------------------------------------------------------------------*/ -static void send_msg_to_server (const char *imsg) +static void send_msg_to_server (const char *imsg, int imsg_len) { int err; - char stemp[IGATE_MAX_MSG]; + char stemp[IGATE_MAX_MSG+1]; + int stemp_len; if (igate_sock == -1) { return; /* Silently discard if not connected. */ } - strlcpy(stemp, imsg, sizeof(stemp)); + stemp_len = imsg_len; + if (stemp_len + 2 > IGATE_MAX_MSG) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Rx IGate: Too long. Truncating.\n"); + stemp_len = IGATE_MAX_MSG - 2; + } + + memcpy (stemp, imsg, stemp_len); if (s_debug >= 1) { text_color_set(DW_COLOR_XMIT); dw_printf ("[rx>ig] "); - ax25_safe_print (stemp, strlen(stemp), 0); + ax25_safe_print (stemp, stemp_len, 0); dw_printf ("\n"); } - strlcat (stemp, "\r\n", sizeof(stemp)); + stemp[stemp_len++] = '\r'; + stemp[stemp_len++] = '\n'; + stemp[stemp_len] = '\0'; + + stats_uplink_bytes += stemp_len; - stats_uplink_bytes += strlen(stemp); #if __WIN32__ - err = send (igate_sock, stemp, strlen(stemp), 0); + err = send (igate_sock, stemp, stemp_len, 0); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); - dw_printf ("\nError %d sending message to IGate server. Closing connection.\n\n", WSAGetLastError()); + dw_printf ("\nError %d sending to IGate server. Closing connection.\n\n", WSAGetLastError()); //dw_printf ("DEBUG: igate_sock=%d, line=%d\n", igate_sock, __LINE__); closesocket (igate_sock); igate_sock = -1; WSACleanup(); } #else - err = write (igate_sock, stemp, strlen(stemp)); + err = write (igate_sock, stemp, stemp_len); if (err <= 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("\nError sending message to IGate server. Closing connection.\n\n"); + dw_printf ("\nError sending to IGate server. Closing connection.\n\n"); close (igate_sock); igate_sock = -1; } @@ -1240,7 +1370,7 @@ static void * igate_recv_thread (void *arg) #endif { unsigned char ch; - unsigned char message[1000]; // Spec says max 500 or so. + unsigned char message[1000]; // Spec says max 512. int len; @@ -1258,14 +1388,27 @@ static void * igate_recv_thread (void *arg) ch = get1ch(); stats_downlink_bytes++; - if (len < sizeof(message)) + // I never expected to see a nul character but it can happen. + // If found, change it to <0x00> and ax25_from_text will change it back to a single byte. + // Along the way we can use the normal C string handling. + + if (ch == 0 && len < (int)(sizeof(message)) - 5) { + message[len++] = '<'; + message[len++] = '0'; + message[len++] = 'x'; + message[len++] = '0'; + message[len++] = '0'; + message[len++] = '>'; + } + else if (len < (int)(sizeof(message))) { - message[len] = ch; + message[len++] = ch; } - len++; } while (ch != '\n'); + message[sizeof(message)-1] = '\0'; + /* * We have a complete message terminated by LF. * @@ -1280,10 +1423,13 @@ static void * igate_recv_thread (void *arg) * I've seen a case where the original RF packet had a trailing CR but * after someone else sent it to the server and it came back to me, that * CR was now a trailing space. + * * At first I was tempted to trim a trailing space as well. * By fixing this one case it might corrupt the data in other cases. * We compensate for this by ignoring trailing spaces when performing * the duplicate detection and removal. + * + * We need to transmit exactly as we get it. */ /* @@ -1329,10 +1475,33 @@ static void * igate_recv_thread (void *arg) ax25_safe_print ((char *)message, len, 0); dw_printf ("\n"); + if ((int)strlen((char*)message) != len) { + + // Invalid. Either drop it or pass it along as-is. Don't change. + + text_color_set(DW_COLOR_ERROR); + dw_printf("'nul' character found in packet from IS. This should never happen.\n"); + dw_printf("The source station is probably transmitting with defective software.\n"); + + //if (strcmp((char*)pinfo, "4P") == 0) { + // dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\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); } } @@ -1464,10 +1633,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. @@ -1485,18 +1654,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); @@ -1518,6 +1692,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. @@ -1528,15 +1728,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) { +/* + * 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. + */ - 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]); +// 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. - ax25_delete (pp3); - return; + 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 ("Special case, allow position from message sender %s, %d remaining.\n", src, n - 1); + } + + 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; + } } } @@ -1549,6 +1792,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) { @@ -1581,6 +1852,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]; @@ -1599,8 +1880,6 @@ static void xmit_packet (char *message, int to_chan) if (pradio != NULL) { - stats_tx_igate_packets++; - #if ITEST text_color_set(DW_COLOR_XMIT); dw_printf ("Xmit: %s\n", radio); @@ -1609,7 +1888,21 @@ static void xmit_packet (char *message, int to_chan) /* This consumes packet so don't reference it again! */ tq_append (to_chan, TQ_PRIO_1_LO, pradio); #endif - stats_rf_xmit_packets++; + stats_rf_xmit_packets++; // Any type of packet. + + // TEMP TEST: metadata temporarily allowed during testing. + + if (*pinfo == ':' && ! is_telem_metadata(pinfo)) { + // 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. } else { @@ -1624,7 +1917,7 @@ static void xmit_packet (char *message, int to_chan) ax25_delete (pp3); -} /* end xmit_packet */ +} /* end maybe_xmit_packet_from_igate */ @@ -1701,6 +1994,7 @@ static void rx_to_ig_remember (packet_t pp) ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); info_len = ax25_get_info (pp, &pinfo); + (void)info_len; text_color_set(DW_COLOR_DEBUG); dw_printf ("rx_to_ig_remember [%d] = %d %d \"%s>%s:%s\"\n", @@ -1731,6 +2025,7 @@ static int rx_to_ig_allow (packet_t pp) ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); info_len = ax25_get_info (pp, &pinfo); + (void)info_len; text_color_set(DW_COLOR_DEBUG); dw_printf ("rx_to_ig_allow? %d \"%s>%s:%s\"\n", crc, src, dest, pinfo); @@ -1798,7 +2093,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 @@ -1847,10 +2142,12 @@ static int rx_to_ig_allow (packet_t pp) * At first I thought duplicate removal was broken but it turns out they * are not exactly the same. * - * The receive IGate spec says a packet should be cut at a CR. + * >>> The receive IGate spec says a packet should be cut at a CR. <<< + * * In one case it is removed as expected In another case, it is replaced by a trailing * space character. Maybe someone thought non printable characters should be - * replaced by spaces??? + * replaced by spaces??? (I have since been told someone thought it would be a good + * idea to replace unprintable characters with spaces. How's that working out for MIC-E position???) * * At first I was tempted to remove any trailing spaces to make up for the other * IGate adding it. Two wrongs don't make a right. Trailing spaces are not that @@ -1986,6 +2283,7 @@ void ig_to_tx_remember (packet_t pp, int chan, int bydigi) ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); info_len = ax25_get_info (pp, &pinfo); + (void)info_len; text_color_set(DW_COLOR_DEBUG); dw_printf ("ig_to_tx_remember [%d] = ch%d d%d %d %d \"%s>%s:%s\"\n", @@ -2012,16 +2310,20 @@ static int ig_to_tx_allow (packet_t pp, int chan) time_t now = time(NULL); int j; int count_1, count_5; + int increase_limit; + + unsigned char *pinfo; + int info_len; + + info_len = ax25_get_info (pp, &pinfo); + (void)info_len; if (s_debug >= 2) { char src[AX25_MAX_ADDR_LEN]; char dest[AX25_MAX_ADDR_LEN]; - unsigned char *pinfo; - int info_len; ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); - info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_DEBUG); dw_printf ("ig_to_tx_allow? ch%d %d \"%s>%s:%s\"\n", chan, crc, src, dest, pinfo); @@ -2031,14 +2333,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; } } @@ -2053,12 +2376,25 @@ static int ig_to_tx_allow (packet_t pp, int chan) } } - if (count_1 >= save_igate_config_p->tx_limit_1) { + /* "Messages" (special APRS data type ":") are intentional and more */ + /* important than all of the other mostly repetitive useless junk */ + /* flowing thru here. */ + /* It would be unfortunate to discard a message because we already */ + /* hit our limit. I don't want to completely eliminate limiting for */ + /* messages, in case something goes terribly wrong, but we can triple */ + /* the normal limit for them. */ + + increase_limit = 1; + if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) { + increase_limit = 3; + } + + if (count_1 >= save_igate_config_p->tx_limit_1 * increase_limit) { text_color_set(DW_COLOR_ERROR); dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 1 minute.\n", save_igate_config_p->tx_limit_1); return 0; } - if (count_5 >= save_igate_config_p->tx_limit_5) { + if (count_5 >= save_igate_config_p->tx_limit_5 * increase_limit) { text_color_set(DW_COLOR_ERROR); dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", save_igate_config_p->tx_limit_5); return 0; diff --git a/igate.h b/igate.h index 3e2d0030..b98e5a9a 100644 --- a/igate.h +++ b/igate.h @@ -45,15 +45,30 @@ 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. */ /* Must start with "," if not empty so it can */ /* simply be inserted after the destination address. */ + int max_digi_hops; /* Maximum number of digipeater hops possible for via path. */ + /* 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. */ @@ -84,4 +99,18 @@ void igate_send_rec_packet (int chan, packet_t recv_pp); void ig_to_tx_remember (packet_t pp, int chan, int bydigi); + + +/* Get statistics for IGATE status beacon. */ + +int igate_get_msg_cnt (void); + +int igate_get_pkt_cnt (void); + +int igate_get_upl_cnt (void); + +int igate_get_dnl_cnt (void); + + + #endif diff --git a/kiss.c b/kiss.c index db68d692..19c258d5 100644 --- a/kiss.c +++ b/kiss.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2013, 2014 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 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 @@ -24,14 +24,13 @@ * Module: kiss.c * * Purpose: Act as a virtual KISS TNC for use by other packet radio applications. + * On Windows, it is a serial port. On Linux, a pseudo terminal. * * Input: * * Outputs: * - * Description: This provides a pseudo terminal for communication with a client application. - * - * It implements the KISS TNC protocol as described in: + * Description: It implements the KISS TNC protocol as described in: * http://www.ka9q.net/papers/kiss.html * * Briefly, a frame is composed of @@ -113,20 +112,19 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #if __WIN32__ #include -#include #else -#define __USE_XOPEN2KXSI 1 -#define __USE_XOPEN 1 -//#define __USE_POSIX 1 #include #include #include #include +#include #include #include #ifdef __OpenBSD__ @@ -139,7 +137,7 @@ #include #include -#include "direwolf.h" + #include "tq.h" #include "ax25_pad.h" #include "textcolor.h" @@ -242,17 +240,22 @@ void hex_dump (unsigned char *p, int len); * *--------------------------------------------------------------------*/ -static MYFDTYPE kiss_open_pt (void); +#if __WIN32__ static MYFDTYPE kiss_open_nullmodem (char *device); +#else +static MYFDTYPE kiss_open_pt (void); +#endif + void kiss_init (struct misc_config_s *mc) { - int e; + #if __WIN32__ HANDLE kiss_nullmodem_listen_th; #else pthread_t kiss_pterm_listen_tid; - pthread_t kiss_nullmodem_listen_tid; + //pthread_t kiss_nullmodem_listen_tid; + int e; #endif memset (&kf, 0, sizeof(kf)); @@ -355,7 +358,7 @@ static MYFDTYPE kiss_open_pt (void) char *pts; struct termios ts; int e; - //int flags; + #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -397,39 +400,31 @@ static MYFDTYPE kiss_open_pt (void) } /* - * After running for a while on Linux, the write eventually - * blocks if no one is reading from the other side of - * the pseudo terminal. We get stuck on the kiss data - * write and reception stops. - * - * I tried using ioctl(,TIOCOUTQ,) to see how much was in - * the queue but that always returned zero. (Ubuntu) - * - * Let's try using non-blocking writes and see if we get - * the EWOULDBLOCK status instead of hanging. + * We had a problem here since the beginning. + * If no one was reading from the other end of the pseudo + * terminal, the buffer space would eventually fill up, + * the write here would block, and the receive decode + * thread would get stuck. + * + * March 2016 - A "select" was put before the read to + * solve a different problem. With that in place, we can + * now use non-blocking I/O and detect the buffer full + * condition here. */ -#if 0 // this is worse. all writes fail. errno = 0 bad file descriptor - flags = fcntl(fd, F_GETFL, 0); + // text_color_set(DW_COLOR_DEBUG); + // dw_printf("Debug: Try using non-blocking mode for pseudo terminal.\n"); + + int flags = fcntl(fd, F_GETFL, 0); e = fcntl (fd, F_SETFL, flags | O_NONBLOCK); if (e != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno); perror ("pt fcntl"); } -#endif -#if 0 // same - flags = 1; - e = ioctl (fd, FIONBIO, &flags); - if (e != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno); - perror ("pt ioctl"); - } -#endif + text_color_set(DW_COLOR_INFO); dw_printf("Virtual KISS TNC is available on %s\n", pt_slave_name); - dw_printf("WARNING - Dire Wolf will hang eventually if nothing is reading from it.\n"); #if 1 @@ -632,13 +627,11 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename) * * flen - Length of raw received frame not including the FCS * or -1 for a text string. - * * * Description: Send message to client. * We really don't care if anyone is listening or not. * I don't even know if we can find out. * - * *--------------------------------------------------------------------*/ @@ -646,7 +639,6 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen) { unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2]; int kiss_len; - int j; int err; #if ! __WIN32__ @@ -675,7 +667,7 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen) unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; - assert (flen < sizeof(stemp)); + assert (flen < (int)(sizeof(stemp))); stemp[0] = (chan << 4) + 0; memcpy (stemp+1, fbuf, flen); @@ -702,14 +694,12 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen) /* Pseudo terminal for Cygwin and Linux. */ - err = write (pt_master_fd, kiss_buff, (size_t)kiss_len); if (err == -1 && errno == EWOULDBLOCK) { -#if DEBUG text_color_set (DW_COLOR_INFO); - dw_printf ("KISS SEND - discarding message because write would block.\n"); -#endif + dw_printf ("KISS SEND - Discarding message because no one is listening.\n"); + dw_printf ("This happens when you use the -p option and don't read from the pseudo terminal.\n"); } else if (err != kiss_len) { @@ -764,7 +754,7 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen) //nullmodem_fd = MYFDERROR; } } - else if (nwritten != kiss_len) + else if ((int)nwritten != kiss_len) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending KISS message to client application thru null modem. Only %d of %d written.\n\n", (int)nwritten, kiss_len); @@ -772,13 +762,6 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen) //nullmodem_fd = MYFDERROR; } -#if DEBUG - /* Could wait with GetOverlappedResult but we never */ - /* have an issues in this direction. */ - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("KISS SEND completed. wrote %d / %d\n", nwritten, kiss_len); -#endif - #else err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len); if (err != len) @@ -796,20 +779,25 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen) + /*------------------------------------------------------------------- * - * Name: kiss_listen_thread + * Name: kiss_get + * + * Purpose: Read one byte from the KISS client app. * - * Purpose: Wait for messages from an application. + * Global In: nullmodem_fd (Windows) or pt_master_fd (Linux) * - * Global In: nullmodem_fd or pt_master_fd + * Returns: one byte (value 0 - 255) or terminate thread on error. * - * Description: Process messages from the client application. + * Description: There is room for improvment here. Reading one byte + * at a time is inefficient. We could read a large block + * into a local buffer and return a byte from that most of the time. + * Is it worth the effort? I don't know. With GHz processors and + * the low data rate here it might not make a noticable difference. * *--------------------------------------------------------------------*/ -/* Return one byte (value 0 - 255) or terminate thread on error. */ - static int kiss_get (/* MYFDTYPE fd*/ void ) { @@ -884,24 +872,75 @@ static int kiss_get (/* MYFDTYPE fd*/ void ) #else /* Linux/Cygwin version */ int n = 0; + fd_set fd_in, fd_ex; + int rc; while ( n == 0 ) { - n = read(pt_master_fd, &ch, (size_t)1); +/* + * Since the beginning we've always had a couple annoying problems with + * the pseudo terminal KISS interface. + * When using "kissattach" we would sometimes get the error message: + * + * kissattach: Error setting line discipline: TIOCSETD: Device or resource busy + * Are you sure you have enabled MKISS support in the kernel + * or, if you made it a module, that the module is loaded? + * + * martinhpedersen came up with the interesting idea of putting in a "select" + * before the "read" and explained it like this: + * + * "Reading from master fd of the pty before the client has connected leads + * to trouble with kissattach. Use select to check if the slave has sent + * any data before trying to read from it." + * + * "This fix resolves the issue by not reading from the pty's master fd, until + * kissattach has opened and configured the slave. This is implemented using + * select() to wait for data before reading from the master fd." + * + * The submitted code looked like this: + * + * FD_ZERO(&fd_in); + * rc = select(pt_master_fd + 1, &fd_in, NULL, &fd_in, NULL); + * + * That doesn't look right to me for a couple reasons. + * First, I would expect to use FD_SET for the fd. + * Second, using the same bit mask for two arguments doesn't seem + * like a good idea because select modifies them. + * When I tried running it, we don't get the failure message + * anymore but the select never returns so we can't read data from + * the KISS client app. + * + * I think this is what we want. + * + * Tested on Raspian (ARM) and Ubuntu (x86_64). + * We don't get the error from kissattach anymore. + */ - if (n != 1) { + FD_ZERO(&fd_in); + FD_SET(pt_master_fd, &fd_in); - text_color_set(DW_COLOR_ERROR); - dw_printf ("\nError receiving kiss message from client application. Closing %s.\n\n", pt_slave_name); - perror (""); + FD_ZERO(&fd_ex); + FD_SET(pt_master_fd, &fd_ex); - /* Message added between 1.1 beta test and final version 1.1 */ + rc = select(pt_master_fd + 1, &fd_in, NULL, &fd_ex, NULL); - /* TODO: Determine root cause and find proper solution. */ +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("select returns %d, errno=%d, fd=%d, fd_in=%08x, fd_ex=%08x\n", rc, errno, pt_master_fd, *((int*)(&fd_in)), *((int*)(&fd_in))); +#endif + + if (rc == 0) + { + continue; // When could we get a 0? + } - dw_printf ("This is a known problem that sometimes shows up when using with kissattach.\n"); - dw_printf ("There are a couple work-arounds described in the Dire Wolf User Guide\n"); - dw_printf ("and the Raspberry Pi APRS documents.\n"); + if (rc == MYFDERROR + || (n = read(pt_master_fd, &ch, (size_t)1)) != 1) + { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError receiving KISS message from client application. Closing %s.\n\n", pt_slave_name); + perror (""); close (pt_master_fd); @@ -935,6 +974,18 @@ static int kiss_get (/* MYFDTYPE fd*/ void ) } +/*------------------------------------------------------------------- + * + * Name: kiss_listen_thread + * + * Purpose: Read messages from serial port KISS client application. + * + * Global In: nullmodem_fd (Windows) or pt_master_fd (Linux) + * + * Description: Reads bytes from the KISS client app and + * sends them to kiss_rec_byte for processing. + * + *--------------------------------------------------------------------*/ static THREAD_F kiss_listen_thread (void *arg) diff --git a/kiss_frame.c b/kiss_frame.c index f988412c..3e9c45a8 100644 --- a/kiss_frame.c +++ b/kiss_frame.c @@ -68,6 +68,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -75,7 +77,6 @@ #include #include -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "kiss_frame.h" @@ -86,7 +87,7 @@ void hex_dump (unsigned char *p, int len); -static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug); + #if KISSTEST @@ -98,6 +99,10 @@ void text_color_set (dw_color_t c) return; } +#else + +static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug); + #endif @@ -561,9 +566,15 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug) break; default: - text_color_set(DW_COLOR_DEBUG); + text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Invalid command %d\n", cmd); kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len); + + text_color_set(DW_COLOR_INFO); + dw_printf ("Troubleshooting tip:\n"); + dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n"); + dw_printf ("all communication with the client application.\n"); + break; } @@ -630,7 +641,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int #if KISSTEST -main () +int main () { unsigned char din[512]; unsigned char kissed[520]; diff --git a/kissnet.c b/kissnet.c index e3776579..ccdbd0a3 100644 --- a/kissnet.c +++ b/kissnet.c @@ -95,10 +95,12 @@ */ +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h + + #if __WIN32__ #include -#define _WIN32_WINNT 0x0501 -#include +#include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include @@ -115,11 +117,9 @@ #include #include #include - #include -#include "direwolf.h" #include "tq.h" #include "ax25_pad.h" #include "textcolor.h" @@ -128,6 +128,8 @@ #include "kiss_frame.h" #include "xmit.h" +void hex_dump (unsigned char *p, int len); // This should be in a .h file. + static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */ // TODO: multiple instances if multiple KISS network clients! @@ -139,8 +141,15 @@ static int client_sock; /* File descriptor for socket for */ /* (Don't use SOCKET type because it is unsigned.) */ -static void * connect_listen_thread (void *arg); -static void * kissnet_listen_thread (void *arg); +// TODO: define in one place, use everywhere. +#if __WIN32__ +#define THREAD_F unsigned __stdcall +#else +#define THREAD_F void * +#endif + +static THREAD_F connect_listen_thread (void *arg); +static THREAD_F kissnet_listen_thread (void *arg); @@ -184,8 +193,8 @@ void kissnet_init (struct misc_config_s *mc) #else pthread_t connect_listen_tid; pthread_t cmd_listen_tid; -#endif int e; +#endif int kiss_port = mc->kiss_port; @@ -208,7 +217,7 @@ void kissnet_init (struct misc_config_s *mc) * This waits for a client to connect and sets client_sock. */ #if __WIN32__ - connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL); + connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL); if (connect_listen_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create KISS socket connect listening thread\n"); @@ -227,7 +236,7 @@ void kissnet_init (struct misc_config_s *mc) * This reads messages from client when client_sock is valid. */ #if __WIN32__ - cmd_listen_th = _beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL); + cmd_listen_th = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL); if (cmd_listen_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create KISS socket command listening thread\n"); @@ -263,7 +272,7 @@ void kissnet_init (struct misc_config_s *mc) * *--------------------------------------------------------------------*/ -static void * connect_listen_thread (void *arg) +static THREAD_F connect_listen_thread (void *arg) { #if __WIN32__ @@ -284,7 +293,7 @@ static void * connect_listen_thread (void *arg) if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf("WSAStartup failed: %d\n", err); - return (NULL); + return (0); } if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { @@ -292,7 +301,7 @@ static void * connect_listen_thread (void *arg) dw_printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); //sleep (1); - return (NULL); + return (0); } memset (&hints, 0, sizeof(hints)); @@ -307,14 +316,14 @@ static void * connect_listen_thread (void *arg) dw_printf("getaddrinfo failed: %d\n", err); //sleep (1); WSACleanup(); - return (NULL); + return (0); } listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (listen_sock == INVALID_SOCKET) { text_color_set(DW_COLOR_ERROR); dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError()); - return (NULL); + return (0); } #if DEBUG @@ -331,7 +340,7 @@ static void * connect_listen_thread (void *arg) freeaddrinfo(ai); closesocket(listen_sock); WSACleanup(); - return (NULL); + return (0); } freeaddrinfo(ai); @@ -353,7 +362,7 @@ static void * connect_listen_thread (void *arg) { text_color_set(DW_COLOR_ERROR); dw_printf("Listen failed with error: %d\n", WSAGetLastError()); - return (NULL); + return (0); } text_color_set(DW_COLOR_INFO); @@ -366,7 +375,7 @@ static void * connect_listen_thread (void *arg) dw_printf("Accept failed with error: %d\n", WSAGetLastError()); closesocket(listen_sock); WSACleanup(); - return (NULL); + return (0); } text_color_set(DW_COLOR_INFO); @@ -481,7 +490,6 @@ void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen) { unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; int kiss_len; - int j; int err; @@ -501,7 +509,7 @@ void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen) unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; - assert (flen < sizeof(stemp)); + assert (flen < (int)(sizeof(stemp))); stemp[0] = (chan << 4) + 0; memcpy (stemp+1, fbuf, flen); @@ -580,6 +588,7 @@ static int read_from_socket (int fd, char *ptr, int len) #if __WIN32__ //TODO: any flags for send/recv? +//TODO: Would be useful to have more detailed explanation from the error code. n = recv (fd, ptr + got_bytes, len - got_bytes, 0); #else @@ -659,7 +668,7 @@ static int kiss_get (void) } text_color_set(DW_COLOR_ERROR); - dw_printf ("\nError reading KISS byte from clent application. Closing connection.\n\n"); + dw_printf ("\nError reading KISS byte from client application. Closing connection.\n\n"); #if __WIN32__ closesocket (client_sock); #else @@ -671,7 +680,7 @@ static int kiss_get (void) -static void * kissnet_listen_thread (void *arg) +static THREAD_F kissnet_listen_thread (void *arg) { unsigned char ch; @@ -685,7 +694,11 @@ static void * kissnet_listen_thread (void *arg) kiss_rec_byte (&kf, ch, kiss_debug, kissnet_send_rec_packet); } - return (NULL); /* to suppress compiler warning. */ +#if __WIN32__ + return(0); +#else + return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */ +#endif } /* end kissnet_listen_thread */ diff --git a/latlong.c b/latlong.c index c984ef14..8e3a9998 100644 --- a/latlong.c +++ b/latlong.c @@ -30,6 +30,7 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" #include #include @@ -40,7 +41,6 @@ #include #include -#include "direwolf.h" #include "latlong.h" #include "textcolor.h" diff --git a/ll2utm.c b/ll2utm.c index 3ff663de..e06cd563 100644 --- a/ll2utm.c +++ b/ll2utm.c @@ -1,5 +1,7 @@ /* Latitude / Longitude to UTM conversion */ +#include "direwolf.h" + #include #include #include diff --git a/log.c b/log.c index f5448e75..fa86c3e2 100644 --- a/log.c +++ b/log.c @@ -31,6 +31,8 @@ * *------------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -42,8 +44,6 @@ #include #include - -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "decode_aprs.h" @@ -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 diff --git a/log.h b/log.h index 5cc02642..fc6ca441 100644 --- a/log.h +++ b/log.h @@ -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); \ No newline at end of file diff --git a/log2gpx.c b/log2gpx.c index c8f47e07..15b9835e 100644 --- a/log2gpx.c +++ b/log2gpx.c @@ -18,17 +18,12 @@ // along with this program. If not, see . // +#include "direwolf.h" #include #include #include -#if __WIN32__ -char *strsep(char **stringp, const char *delim); -#endif - -#include "direwolf.h" - /* * Information we gather for each thing. diff --git a/man1/direwolf.1 b/man1/direwolf.1 index b81b8a2a..36beb648 100644 --- a/man1/direwolf.1 +++ b/man1/direwolf.1 @@ -79,6 +79,8 @@ p = Packet dump in hexadecimal. .P g = GPS interface. .P +W = Waypoints for position or object reports. +.P t = Tracker beacon. .P o = Output controls such as PTT and DCD. diff --git a/mgn_icon.h b/mgn_icon.h index 3c96aabc..4563cce1 100644 --- a/mgn_icon.h +++ b/mgn_icon.h @@ -5,9 +5,18 @@ * * Waypoint icon codes for use in the $PMGNWPL sentence. * - * Derived from Data Transmission Protocol For Magellan Products - version 2.11 + * Derived from Data Transmission Protocol For Magellan Products - version 2.11, March 2003 + * + * http://www.gpsinformation.org/mag-proto-2-11.pdf + * + * + * That's 13 years ago. There should be something newer available but I can't find it. + * + * The is based on the newer models at the time. Earlier models had shorter incompatible icon lists. */ + + #define MGN_crossed_square "a" #define MGN_box "b" #define MGN_house "c" @@ -110,7 +119,7 @@ static const char mgn_primary_symtab[SYMTAB_SIZE][3] = { MGN_default, // E 37 EYEBALL (Eye catcher!) MGN_default, // F 38 Farm Vehicle (tractor) MGN_default, // G 39 Grid Square (6 digit) - MGN_default, // H 40 HOTEL (blue bed symbol) + MGN_hotel, // H 40 HOTEL (blue bed symbol) MGN_aerial, // I 41 TcpIp on air network stn MGN_default, // J 42 MGN_default, // K 43 School @@ -150,7 +159,7 @@ static const char mgn_primary_symtab[SYMTAB_SIZE][3] = { MGN_aerial, // m 77 Mic-E Repeater MGN_default, // n 78 Node (black bulls-eye) MGN_default, // o 79 EOC - MGN_default, // p 80 ROVER (puppy, or dog) + MGN_zoo, // p 80 ROVER (puppy, or dog) MGN_default, // q 81 GRID SQ shown above 128 m MGN_aerial, // r 82 Repeater MGN_default, // s 83 SHIP (pwr boat) @@ -202,7 +211,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = { MGN_default, // > 30 OVERLAYED CAR MGN_tourist_info, // ? 31 INFO Kiosk (Blue box with ?) MGN_default, // @ 32 HURICANE/Trop-Storm - MGN_default, // A 33 overlayBOX DTMF & RFID & XO + MGN_box, // A 33 overlayBOX DTMF & RFID & XO MGN_default, // B 34 Blwng Snow (& future codes) MGN_boating, // C 35 Coast Guard MGN_default, // D 36 Drizzle (proposed APRStt) @@ -215,7 +224,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = { MGN_default, // K 43 Kenwood HT (W) MGN_lighthouse, // L 44 Lighthouse MGN_default, // M 45 MARS (A=Army,N=Navy,F=AF) - MGN_nav_aid, // N 46 Navigation Buoy + MGN_buoy, // N 46 Navigation Buoy MGN_airport, // O 47 Rocket MGN_default, // P 48 Parking MGN_default, // Q 49 QUAKE @@ -223,7 +232,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = { MGN_aerial, // S 51 Satellite/Pacsat MGN_default, // T 52 Thunderstorm MGN_default, // U 53 SUNNY - MGN_default, // V 54 VORTAC Nav Aid + MGN_nav_aid, // V 54 VORTAC Nav Aid MGN_default, // W 55 # NWS site (NWS options) MGN_default, // X 56 Pharmacy Rx (Apothicary) MGN_aerial, // Y 57 Radios and devices @@ -242,7 +251,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = { MGN_default, // f 70 Funnel Cloud MGN_default, // g 71 Gale Flags MGN_default, // h 72 Store. or HAMFST Hh=HAM store - MGN_default, // i 73 BOX or points of Interest + MGN_box, // i 73 BOX or points of Interest MGN_default, // j 74 WorkZone (Steam Shovel) MGN_default, // k 75 Special Vehicle SUV,ATV,4x4 MGN_default, // l 76 Areas (box,circles,etc) diff --git a/mheard.c b/mheard.c new file mode 100644 index 00000000..37163c52 --- /dev/null +++ b/mheard.c @@ -0,0 +1,797 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * File: mheard.c + * + * Purpose: Maintain a list of all stations heard. + * + * 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. + * + *------------------------------------------------------------------*/ + +#include "direwolf.h" + +#include +#include +#include +#include +#include +#include + +#include "textcolor.h" +#include "decode_aprs.h" +#include "ax25_pad.h" +#include "hdlc_rec2.h" // for retry_t +#include "mheard.h" +#include "latlong.h" + + +// 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) When adding a new node, make sure it is complete, including next ptr, +// before adding it to the list. +// (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 or from Internet Server. + */ + +typedef struct mheard_s { + + struct mheard_s *pnext; // Pointer to next in list. + + char callsign[AX25_MAX_ADDR_LEN]; // Callsign from the AX.25 source field. + + int count; // Number of times heard. + // We don't use this for anything. + // Just something potentially interesting when looking at data dump. + + 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. + */ + +#define MHEARD_HASH_SIZE 73 // Best if prime number. + +static mheard_t *mheard_hash[MHEARD_HASH_SIZE]; + +static inline int hash_index(char *callsign) { + int n = 0; + char *p = callsign; + + while (*p != '\0') { + n += *p++; + } + return (n % MHEARD_HASH_SIZE); +} + +static mheard_t *mheard_ptr(char *callsign) { + int n = hash_index(callsign); + mheard_t *p = mheard_hash[n]; + + while (p != NULL) { + if (strcmp(callsign,p->callsign) == 0) return (p); + p = p->pnext; + } + return (NULL); +} + + +static int mheard_debug = 0; + + +/*------------------------------------------------------------------ + * + * Function: mheard_init + * + * Purpose: Initialization at start of application. + * + * Inputs: debug - Debug level. + * + * Description: Clear pointer table. + * Save debug level for later use. + * + *------------------------------------------------------------------*/ + + +void mheard_init (int debug) +{ + int i; + + mheard_debug = debug; + + for (i = 0; i < MHEARD_HASH_SIZE; i++) { + mheard_hash[i] = NULL; + } + +/* + * Mutex to coordinate adding new nodes. + */ + dw_mutex_init(&mheard_mutex); + +} /* end mheard_init */ + + + +/*------------------------------------------------------------------ + * + * Function: mheard_dump + * + * 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. + * + * A - Exploded information from APRS packet. + * + * pp - Received packet object. + * + * alevel - audio level. + * + * retries - Amount of effort to get a good CRC. + * + * Description: Calling sequence was copied from "log_write." + * It has a lot more than what we currently keep but the + * hooks are there so it will be easy to capture additional + * information when the need arises. + * + *------------------------------------------------------------------*/ + +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]; + int hops; + mheard_t *mptr; + + ax25_get_addr_with_ssid (pp, AX25_SOURCE, source); + +/* + * How many digipeaters has it gone thru before we hear it? + * We can count the number of digi addresses that are marked as "has been used." + * This is not always accurate because there is inconsistency in digipeater behavior. + * The base AX.25 spec seems clear in this regard. The used digipeaters should + * should accurately reflict the path taken by the packet. Sometimes we see excess + * stuff in there. Even when you understand what is going on, it is still an ambiguous + * situation. Look for my rant in the User Guide. + */ + + hops = ax25_get_heard(pp) - AX25_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_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_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 { + +/* + * Update existing entry. + * The only tricky part here is that we might hear the same transmission + * several times. First direct, then thru various digipeater paths. + * We are interested in the shortest path if heard very recently. + */ + + 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_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_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_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)); + } + + if (mheard_debug) { + mheard_dump (); + } + + ax25_delete (pp); + +} /* end mheard_save_is */ + + +/*------------------------------------------------------------------ + * + * Function: mheard_count + * + * Purpose: Count local stations for IGate statistics report like this: + * + * pnext) { + if (p->last_heard_rf >= since && p->num_digi_hops <= max_hops) { + count++; + } + } + } + + if (mheard_debug == 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("mheard_count(<= %d digi hops, last %d minutes) returns %d\n", max_hops, time_limit, count); + } + + return (count); + +} /* 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 new file mode 100644 index 00000000..f8466bab --- /dev/null +++ b/mheard.h @@ -0,0 +1,20 @@ + + +/* mheard.h */ + +#include "decode_aprs.h" // for decode_aprs_t + + +void mheard_init (int debug); + +void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries); + +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/misc/strcasestr.c b/misc/strcasestr.c index a418549d..c684db85 100644 --- a/misc/strcasestr.c +++ b/misc/strcasestr.c @@ -43,8 +43,7 @@ * Find the first occurrence of find in s, ignore case. */ char * -strcasestr(s, find) - const char *s, *find; +strcasestr(const char *s, const char *find) { char c, sc; size_t len; diff --git a/morse.c b/morse.c index 8598fcc6..df0038d8 100644 --- a/morse.c +++ b/morse.c @@ -30,6 +30,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -39,8 +41,6 @@ #include #include - -#include "direwolf.h" #include "textcolor.h" #include "audio.h" #include "ptt.h" @@ -122,7 +122,7 @@ static const struct morse_s { }; -#define NUM_MORSE (sizeof(morse) / sizeof(struct morse_s)) +#define NUM_MORSE ((int)(sizeof(morse) / sizeof(struct morse_s))) static void morse_tone (int chan, int tu, int wpm); static void morse_quiet (int chan, int tu, int wpm); @@ -272,6 +272,8 @@ int morse_send (int chan, char *str, int wpm, int txdelay, int txtail) time_units, morse_units_str(str)); } + audio_flush(ACHAN2ADEV(chan)); + return (txdelay + (int) (TIME_UNITS_TO_MS(time_units, wpm) + 0.5) + txtail); diff --git a/multi_modem.c b/multi_modem.c index f9d57fd6..fd53b48a 100644 --- a/multi_modem.c +++ b/multi_modem.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 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 @@ -70,9 +70,11 @@ * Set limit on number of packets in fix up later queue. * *------------------------------------------------------------------*/ + //#define DEBUG 1 #define DIGIPEATER_C +#include "direwolf.h" #include #include @@ -80,7 +82,6 @@ #include #include -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "multi_modem.h" @@ -108,7 +109,10 @@ static struct { -#define PROCESS_AFTER_BITS 2 +//#define PROCESS_AFTER_BITS 2 // version 1.4. Was a little short for skew of PSK with different modem types, optional pre-filter + +#define PROCESS_AFTER_BITS 3 + static int process_age[MAX_CHANS]; @@ -154,7 +158,11 @@ void multi_modem_init (struct audio_s *pa) dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__); save_audio_config_p->achan[chan].baud = DEFAULT_BAUD; } - process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].baud; + int real_baud = save_audio_config_p->achan[chan].baud; + if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) real_baud = save_audio_config_p->achan[chan].baud / 2; + if (save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) real_baud = save_audio_config_p->achan[chan].baud / 3; + + process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / real_baud ; //crc_queue_of_last_to_app[chan] = NULL; } } @@ -462,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_append (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; } @@ -641,14 +669,34 @@ static void pick_best_candidate (int chan) j = subchan_from_n(best_n); k = slice_from_n(best_n); - dlq_append (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. */ diff --git a/nmea.c b/nmea.c deleted file mode 100644 index 32e487ec..00000000 --- a/nmea.c +++ /dev/null @@ -1,465 +0,0 @@ -// -// This file is part of Dire Wolf, an amateur radio packet TNC. -// -// Copyright (C) 2014, 2015 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 . -// - - -//#define DEBUG 1 - - -// TODO: rename this to waypoint & integrate. - -/*------------------------------------------------------------------ - * - * Module: nmea.c - * - * Purpose: Receive NMEA sentences from a GPS receiver. - * Send NMEA waypoint sentences to GPS display or mapping application. - * - *---------------------------------------------------------------*/ - -#include -#include - -#if __WIN32__ -#include -#include -#else -#define __USE_XOPEN2KXSI 1 -#define __USE_XOPEN 1 -//#define __USE_POSIX 1 -#include -#include -#include -#include -#include -#include -#include -#endif - -#include -#include - -#if __WIN32__ -char *strsep(char **stringp, const char *delim); -#endif - -#include "direwolf.h" -#include "config.h" -#include "ax25_pad.h" -#include "textcolor.h" -//#include "xmit.h" -#include "latlong.h" -#include "nmea.h" -#include "grm_sym.h" /* Garmin symbols */ -#include "mgn_icon.h" /* Magellan icons */ -#include "serial_port.h" - - -// TODO: receive buffer... static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */ - -static MYFDTYPE nmea_port_fd = MYFDERROR; - -static void nmea_send_sentence (char *sent); - - - -//static void nmea_parse_gps (char *sentence); - - -static int nmea_debug = 0; /* Print information flowing from and to attached device. */ - -void nmea_set_debug (int n) -{ - nmea_debug = n; -} - - -/*------------------------------------------------------------------- - * - * Name: nmea_init - * - * Purpose: Initialization for NMEA communication port. - * - * Inputs: mc->nmea_port - name of serial port. - * - * Global output: nmea_port_fd - * - * - * Description: (1) Open serial port device. - * - *---------------------------------------------------------------*/ - - -void nmea_init (struct misc_config_s *mc) -{ - -/* - * Open serial port connection. - * 4800 baud is standard for GPS. - * Should add an option to allow changing someday. - */ - if (strlen(mc->nmea_port) > 0) { - - nmea_port_fd = serial_port_open (mc->nmea_port, 4800); - - - } - - -#if DEBUG - text_color_set (DW_COLOR_DEBUG); - - dw_printf ("end of nmea_init: nmea_port_fd = %d\n", nmea_port_fd); -#endif -} - - - -/*------------------------------------------------------------------- - * - * Name: append_checksum - * - * Purpose: Append checksum to the sentence. - * - * In/out: sentence - NMEA sentence beginning with '$'. - * - * Description: Checksum is exclusive of characters except leading '$'. - * We append '*' and an upper case two hexadecimal value. - * - *--------------------------------------------------------------------*/ - -static void append_checksum (char *sentence) -{ - char *p; - int cs; - - assert (sentence[0] == '$'); - - cs = 0; - for (p = sentence+1; *p != '\0'; p++) { - cs ^= *p; - } - - sprintf (p, "*%02X", cs & 0xff); - -// Add crlf too? - -} /* end append_checksum */ - - - -/*------------------------------------------------------------------- - * - * Name: nema_send_waypoint - * - * Purpose: Convert APRS position or object into NMEA waypoint sentence - * for use by a GPS display or other mapping application. - * - * Inputs: wname_in - Name of waypoint. - * dlat - Latitude. - * dlong - Longitude. - * symtab - Symbol table or overlay character. - * symbol - Symbol code. - * alt - Altitude in meters or G_UNKOWN. - * course - Course in degrees or ??? for unknown. - * speed - Speed in knots ?? or ?? - * comment - Description or message. - * - * - * Description: Currently we send multiple styles. Maybe someday there might - * be an option to send a selected subset. - * - * $GPWPL - Generic with only location and name. - * $PGRMW - Garmin, adds altitude, symbol, and comment - * to previously named waypoint. - * $PMGNWPL - Magellan, more complete for stationary objects. - * $PKWDWPL - Kenwood with APRS style symbol but missing comment. - * - *--------------------------------------------------------------------*/ - - -void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol, - float alt, float course, float speed, char *comment) -{ - char wname[12]; /* Waypoint name. Any , or * removed. */ - char slat[12]; /* DDMM.mmmm */ - char slat_ns[2]; /* N or S */ - char slong[12]; /* DDDMM.mmmm */ - char slong_ew[2]; /* E or W */ - char sentence[500]; - char salt[12]; /* altitude as string, empty if unknown */ - char sspeed[12]; /* speed as string, empty if unknown */ - char scourse[12]; /* course as string, empty if unknown */ - int grm_sym; /* Garmin symbol code. */ - char sicon[5]; /* Magellan icon string */ - char stime[8]; - char sdate[8]; - char *p; - - - -// Remove any comma from name. - -// TODO: remove any , or * from comment. - - strcpy (wname, wname_in); - for (p=wname; *p != '\0'; p++) { - if (*p == ',') *p = ' '; - if (*p == '*') *p = ' '; - } - -// Convert position to character form. - - latitude_to_nmea (dlat, slat, slat_ns); - longitude_to_nmea (dlong, slong, slong_ew); - -/* - * Generic. - * - * $GPWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,wname*99 - * - * Where, - * ddmm.mmmm,ns is latitude - * dddmm.mmmm,ew is longitude - * wname is the waypoint name - * *99 is checksum - */ - - snprintf (sentence, sizeof(sentence), "$GPWPL,%s,%s,%s,%s,%s", slat, slat_ns, slong, slong_ew, wname); - append_checksum (sentence); - nmea_send_sentence (sentence); - - - -/* - * Garmin - https://www8.garmin.com/support/pdf/NMEA_0183.pdf - * http://gpsinformation.net/mag-proto.htm - * - * $PGRMW,wname,alt,symbol,comment*99 - * - * Where, - * - * wname is waypoint name. Must match existing waypoint. - * alt is altitude in meters. - * symbol is symbol code. Hexadecimal up to FFFF. - * See Garmin Device Interface Specification 001-0063-00. - * comment is comment for the waypoint. - * *99 is checksum - */ - - if (alt == G_UNKNOWN) { - strcpy (salt, ""); - } - else { - snprintf (salt, sizeof(salt), "%.1f", alt); - } - grm_sym = 0x1234; // TODO - - snprintf (sentence, sizeof(sentence), "$PGRMW,%s,%s,%04X,%s", wname, salt, grm_sym, comment); - append_checksum (sentence); - nmea_send_sentence (sentence); - -/* - * Magellan - http://www.gpsinformation.org/mag-proto-2-11.pdf - * - * $PMGNWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,alt,unit,wname,comment,icon,xx*99 - * - * Where, - * ddmm.mmmm,ns is latitude - * dddmm.mmmm,ew is longitude - * alt is altitude - * unit is M for meters or F for feet - * wname is the waypoint name - * comment is message or comment - * icon is one or two letters for icon code - * xx is waypoint type which is optional, not well - * defined, and not used in their example. - * *99 is checksum - */ - -// TODO: icon - - snprintf (sicon, sizeof(sicon), "??"); - snprintf (sentence, sizeof(sentence), "$PMGNWPL,%s,%s,%s,%s,%s,M,%s,%s,%s", - slat, slat_ns, slong, slong_ew, salt, wname, comment, sicon); - append_checksum (sentence); - nmea_send_sentence (sentence); - -/* - * Kenwood - Speculation due to no official spec found so far. - * - * $PKWDWPL,hhmmss,v,ddmm.mmmm,ns,dddmm.mmmm,ew,speed,course,ddmmyy,alt,wname,ts*99 - * - * Where, - * hhmmss is time in UTC. Should we supply current - * time or only pass along time from - * received signal? - * v indicates valid data ????????????????? - * Why would we send if not valid? - * ddmm.mmmm,ns is latitude - * dddmm.mmmm,ew is longitude - * speed is speed in UNITS ??? knots ????? - * course is course in degrees - * ddmmyy is date. Same question as time. - * alt is altitude. in UNITS ??? meters ??? - * wname is the waypoint name - * ts are the table and symbol. - * Non-standard parsing would be required - * to deal with these for symbol: - * , Boy Scouts / Girl Scouts - * * SnowMobile / Snow - * *99 is checksum - * - * Oddly, there is not place for comment. - */ - - if (speed == G_UNKNOWN) { - strcpy (sspeed, ""); - } - else { - snprintf (sspeed, sizeof(sspeed), "%.1f", speed); - } - if (course == G_UNKNOWN) { - strcpy (scourse, ""); - } - else { - snprintf (scourse, sizeof(scourse), "%.1f", course); - } - -// TODO: how to handle time & date ??? - - strcpy (stime, "123456"); - strcpy (sdate, "123456"); - - snprintf (sentence, sizeof(sentence), "$PKWDWPL,%s,V,%s,%s,%s,%s,%s,%s,%s,%s,%s,%c%c", - stime, slat, slat_ns, slong, slong_ew, - sspeed, scourse, sdate, salt, wname, symtab, symbol); - append_checksum (sentence); - nmea_send_sentence (sentence); - -/* - * One application recognizes these. Not implemented at this time. - * - * $GPTLL,01,ddmm.mmmm,ns,dddmm.mmmm,ew,tname,000000.00,T,R*99 - * - * Where, - * ddmm.mmmm,ns is latitude - * dddmm.mmmm,ew is longitude - * tname is the target name - * 000000.00 is timestamp ??? - * T is target status (S for need help) - * R is reference target ??? - * *99 is checksum - * - * - * $GPTXT,01,01,tname,message*99 - * - * Where, - * - * 01 is total number of messages in transmission - * 01 is message number in this transmission - * tname is target name. Should match name in WPL or TTL. - * message is the message. - * *99 is checksum - * - */ - -} /* end nmea_send_waypoint */ - - -#if 0 - -* d710a menu 603 $GPWPL $PMGNWPL $PKWDWPL - -symbol mapping - -https://freepository.com:444/50lItuLQ7fW6s-web/browser/Tracker2/trunk/sources/waypoint.c?rev=108 - - - -Data Transmission Protocol For Magellan Products - version 2.11 - - - - - - -$PMGNWPL,4651.529,N,07111.425,W,0000000,M,GC5A5F, GC. The straight line,a*13 - $PMGNWPL,3549.499,N,08650.827,W,0000257,M,HOME,HOME,c*4D - -http://gpsbabel.sourcearchive.com/documentation/1.3.7~cvs1/magproto_8c-source.html - - sscanf(trkmsg,"$PMGNWPL,%lf,%c,%lf,%c,%d,%c,%[^,],%[^,]", - &latdeg,&latdir, - &lngdeg,&lngdir, - &alt,&altunits,shortname,descr); then icon - - snprintf(obuf, sizeof(), "PMGNWPL,%4.3f,%c,%09.3f,%c,%07.0f,M,%-.*s,%-.46s,%s", - lat, ilat < 0 ? 'S' : 'N', - lon, ilon < 0 ? 'W' : 'E', - waypointp->altitude == unknown_alt ? - 0 : waypointp->altitude, - wpt_len, - owpt, - odesc, - icon_token); - -https://freepository.com:444/50lItuLQ7fW6s-web/changeset/108/Tracker2/trunk/sources/waypoint.c - -$PMGNWPL,4106.003,S,14640.214,E,0000069,M,KISSING SPOT,,a*3C - - - - - - * -https://freepository.com:444/50lItuLQ7fW6s-web/changeset/325 - - -#endif - - -static void nmea_send_sentence (char *sent) -{ - - int err; - int len = strlen(sent); - - - if (nmea_port_fd == MYFDERROR) { - return; - } - - text_color_set(DW_COLOR_XMIT); - dw_printf ("%s\n", sent); - - if (nmea_debug) { - // TODO: debugg out... nmea_debug_print (TO_CLIENT, NULL, nmea_buff+1, nmea_len-2); - } - -// TODO: need to append CR LF. - - serial_port_write (nmea_port_fd, sent, len); - -} /* nmea_send_sentence */ - - - - - -/* end nmea.c */ diff --git a/nmea.h b/nmea.h deleted file mode 100644 index 9ff85537..00000000 --- a/nmea.h +++ /dev/null @@ -1,19 +0,0 @@ - -/* - * Name: nmea.h - */ - - -#include "ax25_pad.h" /* for packet_t */ - -#include "config.h" /* for struct misc_config_s */ - - -void nmea_init (struct misc_config_s *misc_config); - -void nmea_set_debug (int n); - -void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol, - float alt, float course, float speed, char *comment); - -/* end nmea.h */ diff --git a/pfilter.c b/pfilter.c index 33059498..53604101 100644 --- a/pfilter.c +++ b/pfilter.c @@ -37,6 +37,7 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" #include #include @@ -45,16 +46,48 @@ #include #include -#if __WIN32__ -char *strsep(char **stringp, const char *delim); -#endif - -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #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; +} + @@ -69,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. @@ -85,9 +115,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 @@ -118,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!"; +} /*------------------------------------------------------------------- @@ -137,6 +182,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 @@ -146,7 +195,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; @@ -155,13 +204,15 @@ 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); + 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); } @@ -169,10 +220,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++) { @@ -182,7 +232,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); @@ -201,6 +255,23 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp) 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 */ @@ -339,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; } @@ -360,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; } @@ -393,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; } @@ -419,14 +508,29 @@ 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) + * *--------------------------------------------------------------------*/ - - 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. */ @@ -439,14 +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 */ - result = filt_bodgu (pf, pf->decoded.g_src); + 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 @@ -454,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) @@ -467,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 */ - /* because destintation is used for part of location. */ + /* 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]); @@ -543,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. @@ -580,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); } @@ -622,7 +845,7 @@ static int filt_bodgu (pfstate_t *pf, char *arg) /* Telemetry metadata is a special case of message. */ /* We want to categorize it as telemetry rather than message. */ -static int is_telem_metadata (char *infop) +int is_telem_metadata (char *infop) { if (*infop != ':') return (0); if (strlen(infop) < 16) return (0); @@ -757,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 @@ -765,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; @@ -806,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); @@ -862,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); @@ -906,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 @@ -1101,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); @@ -1124,7 +1570,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); diff --git a/pfilter.h b/pfilter.h index 1171280c..d54e0564 100644 --- a/pfilter.h +++ b/pfilter.h @@ -1,4 +1,13 @@ /* pfilter.h */ -int pfilter (int from_chan, int to_chan, char *filter, packet_t pp); \ No newline at end of file + +#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/ptt.c b/ptt.c index 749ada61..da5429a9 100644 --- a/ptt.c +++ b/ptt.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 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 @@ -46,6 +46,12 @@ * * Version 1.3: HAMLIB support. * + * Version 1.4: The spare "future" indicator is now used when connected to another station. + * + * Take advantage of the new 'gpio' group and new /sys/class/gpio protections in Raspbian Jessie. + * + * Handle more complicated gpio node names for CubieBoard, etc. + * * References: http://www.robbayer.com/files/serial-win.pdf * * https://www.kernel.org/doc/Documentation/gpio.txt @@ -94,6 +100,9 @@ Maybe even for Windows. ;-) */ + +#include "direwolf.h" // should be first. This includes windows.h. + #include #include #include @@ -102,7 +111,6 @@ #include #if __WIN32__ -#include #else #include #include @@ -111,6 +119,8 @@ #include #include #include +#include +#include #ifdef USE_HAMLIB #include @@ -122,10 +132,10 @@ typedef int HANDLE; #endif -#include "direwolf.h" #include "textcolor.h" #include "audio.h" #include "ptt.h" +#include "dlq.h" #if __WIN32__ @@ -152,6 +162,8 @@ typedef int HANDLE; #endif +static struct audio_s *save_audio_config_p; /* Save config information for later use. */ + static int ptt_debug_level = 0; void ptt_set_debug(int debug) @@ -159,46 +171,262 @@ void ptt_set_debug(int debug) ptt_debug_level = debug; } + +/*------------------------------------------------------------------- + * + * Name: get_access_to_gpio + * + * Purpose: Try to get access to the GPIO device. + * + * Inputs: path - Path to device node. + * /sys/class/gpio/export + * /sys/class/gpio/unexport + * /sys/class/gpio/gpio??/direction + * /sys/class/gpio/gpio??/value + * + * Description: First see if we have access thru the usual uid/gid/mode method. + * If that fails, we try a hack where we use "sudo chmod ..." to open up access. + * That requires that sudo be configured to work without a password. + * That's the case for 'pi' user in Raspbian but not not be for other boards / operating systems. + * + * Debug: Use the "-doo" command line option. + * + *------------------------------------------------------------------*/ + +#ifndef __WIN32__ + +#define MAX_GROUPS 50 + + +static void get_access_to_gpio (const char *path) +{ + + static int my_uid = -1; + static int my_gid = -1; + static gid_t my_groups[MAX_GROUPS]; + static int num_groups = 0; + static int first_time = 1; + + struct stat finfo; + int i; + char cmd[80]; + int err; + +/* + * Does path even exist? + */ + + if (stat(path, &finfo) < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't get properties of %s.\n", path); + dw_printf ("This system is not configured with the GPIO user interface.\n"); + dw_printf ("Use a different method for PTT control.\n"); + exit (1); + } + + if (first_time) { + + // No need to fetch same information each time. Cache it. + my_uid = geteuid(); + my_gid = getegid(); + num_groups = getgroups (MAX_GROUPS, my_groups); + + if (num_groups < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("getgroups() failed to get supplementary groups, errno=%d\n", errno); + num_groups = 0; + } + first_time = 0; + } + + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("%s: uid=%d, gid=%d, mode=o%o\n", path, finfo.st_uid, finfo.st_gid, finfo.st_mode); + dw_printf ("my uid=%d, gid=%d, supplementary groups=", my_uid, my_gid); + for (i = 0; i < num_groups; i++) { + dw_printf (" %d", my_groups[i]); + } + dw_printf ("\n"); + } + +/* + * Do we have permission to access it? + * + * On Debian 7 (Wheezy) we see this: + * + * $ ls -l /sys/class/gpio/export + * --w------- 1 root root 4096 Feb 27 12:31 /sys/class/gpio/export + * + * + * Only root can write to it. + * Our work-around is change the protection so that everyone can write. + * This requires that the current user can use sudo without a password. + * This has been the case for the predefined "pi" user but can be a problem + * when people add new user names. + * Other operating systems could have different default configurations. + * + * A better solution is available in Debian 8 (Jessie). The group is now "gpio" + * so anyone in that group can now write to it. + * + * $ ls -l /sys/class/gpio/export + * -rwxrwx--- 1 root gpio 4096 Mar 4 21:12 /sys/class/gpio/export + * + * + * First see if we can access it by the usual file protection rules. + * If not, we will try the "sudo chmod go+rw ..." hack. + * + */ + + +/* + * Do I have access? + * We could just try to open for write but this gives us more debugging information. + */ + + if ((my_uid == finfo.st_uid) && (finfo.st_mode & S_IWUSR)) { // user write 00200 + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("My uid matches and we have user write permission.\n"); + } + return; + } + + if ((my_gid == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020 + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("My primary gid matches and we have group write permission.\n"); + } + return; + } + + for (i = 0; i < num_groups; i++) { + if ((my_groups[i] == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020 + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("My supplemental group %d matches and we have group write permission.\n", my_groups[i]); + } + return; + } + } + + if (finfo.st_mode & S_IWOTH) { // other write 00002 + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("We have other write permission.\n"); + } + return; + } + +/* + * We don't have permission. + * Try a hack which requires that the user be set up to use sudo without a password. + */ + + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_ERROR); // debug message but different color so it stands out. + dw_printf ("Trying 'sudo chmod go+rw %s' hack.\n", path); + } + + snprintf (cmd, sizeof(cmd), "sudo chmod go+rw %s", path); + err = system (cmd); + (void)err; // suppress warning about not using result. + +/* + * I don't trust status coming back from system() so we will check the mode again. + */ + + if (stat(path, &finfo) < 0) { + /* Unexpected because we could do it before. */ + text_color_set(DW_COLOR_ERROR); + dw_printf ("This system is not configured with the GPIO user interface.\n"); + dw_printf ("Use a different method for PTT control.\n"); + exit (1); + } + + /* Did we succeed in changing the protection? */ + + if ( (finfo.st_mode & 0266) != 0266) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("You don't have the necessary permission to access GPIO.\n"); + dw_printf ("There are three different solutions: \n"); + dw_printf (" 1. Run as root. (not recommended)\n"); + dw_printf (" 2. If operating system has 'gpio' group, add your user id to it.\n"); + dw_printf (" 3. Configure your user id for sudo without a password.\n"); + dw_printf ("\n"); + dw_printf ("Read the documentation and try -doo command line option for debugging details.\n"); + exit (1); + } + +} + +#endif + + + /*------------------------------------------------------------------- * * Name: export_gpio * * Purpose: Tell the GPIO subsystem to export a GPIO line for - * us to use, and set the initial state of the GPIO. + * us to use, and set the initial state of the GPIO. * - * Inputs: gpio - GPIO line to export + * Inputs: ch - Radio Channel. + * ot - Output type. * invert: - Is the GPIO active low? * direction: - 0 for input, 1 for output * - * Outputs: None. + * Outputs: out_gpio_name - in the audio configuration structure. + * in_gpio_name * *------------------------------------------------------------------*/ #ifndef __WIN32__ -void export_gpio(int gpio, int invert, int direction) + +void export_gpio(int ch, int ot, int invert, int direction) { HANDLE fd; - char stemp[80]; - struct stat finfo; - int err; + const char gpio_export_path[] = "/sys/class/gpio/export"; + char gpio_direction_path[80]; + char gpio_value_path[80]; + char stemp[16]; + int gpio_num; + char *gpio_name; + +// Raspberry Pi was easy. GPIO 24 has the name gpio24. +// Others, such as the Cubieboard, take a little more effort. +// The name might be gpio24_ph11 meaning connector H, pin 11. +// When we "export" GPIO number, we will store the corresponding +// device name for future use when we want to access it. + + if (direction) { + gpio_num = save_audio_config_p->achan[ch].octrl[ot].out_gpio_num; + gpio_name = save_audio_config_p->achan[ch].octrl[ot].out_gpio_name; + } + else { + gpio_num = save_audio_config_p->achan[ch].ictrl[ot].in_gpio_num; + gpio_name = save_audio_config_p->achan[ch].ictrl[ot].in_gpio_name; + } - fd = open("/sys/class/gpio/export", O_WRONLY); + + get_access_to_gpio (gpio_export_path); + + fd = open(gpio_export_path, O_WRONLY); if (fd < 0) { + // Not expected. Above should have obtained permission or exited. text_color_set(DW_COLOR_ERROR); - dw_printf ("Permissions do not allow ordinary users to access GPIO.\n"); - dw_printf ("Log in as root and type this command:\n"); - dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n"); + dw_printf ("Permissions do not allow access to GPIO.\n"); exit (1); } - snprintf (stemp, sizeof(stemp), "%d", gpio); + + snprintf (stemp, sizeof(stemp), "%d", gpio_num); if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) { int e = errno; /* Ignore EBUSY error which seems to mean */ /* the device node already exists. */ if (e != EBUSY) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e); + dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e); dw_printf ("%s\n", strerror(e)); exit (1); } @@ -206,62 +434,118 @@ void export_gpio(int gpio, int invert, int direction) close (fd); /* - Idea for future: + * Added in release 1.4. + * + * On the RPi, the device path for GPIO number XX is simply /sys/class/gpio/gpioXX. + * + * There was a report that it is different for the CubieBoard. For instance + * GPIO 61 has gpio61_pi13 in the path. This indicates connector "i" pin 13. + * https://github.com/cubieplayer/Cubian/wiki/GPIO-Introduction + * + * For another similar single board computer, we find the same thing: + * https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux + * + * How should we deal with this? Some possibilities: + * + * (1) The user might explicitly mention the name in direwolf.conf. + * (2) We might be able to find the names in some system device config file. + * (3) Get a directory listing of /sys/class/gpio then search for a + * matching name. Suppose we wanted GPIO 61. First look for an exact + * match to "gpio61". If that is not found, look for something + * matching the pattern "gpio61_*". + * + * We are finally implementing the third choice. + */ + + struct dirent **file_list; + int num_files; + int i; + int ok = 0; - On the RPi, the device path for GPIO number XX is /sys/class/gpio/gpioXX. - There was a report that it is different for the Cubieboard. For instance - GPIO 61 has gpio61_pi13 in the path. This indicates connector "i" pin 13. + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Contents of /sys/class/gpio:\n"); + } - For another similar single board computer, we find the same thing: - https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux + num_files = scandir ("/sys/class/gpio", &file_list, NULL, alphasort); - How should we deal with this? Some possibilities: + if (num_files < 0) { + // Something went wrong. Fill in the simple expected name and keep going. - (1) The user might explicitly mention the name in direwolf.conf. - (2) We might be able to find the names in some system device config file. - (3) Get a directory listing of /sys/class/gpio then search for a - matching name. Suppose we wanted GPIO 61. First look for an exact - match to "gpio61". If that is not found, look for something - matching the pattern "gpio61_*". -*/ + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR! Could not get directory listing for /sys/class/gpio\n"); + + snprintf (gpio_name, MAX_GPIO_NAME_LEN, "gpio%d", gpio_num); + num_files = 0; + ok = 1; + } + else { + + if (ptt_debug_level >= 2) { + + text_color_set(DW_COLOR_DEBUG); + + for (i = 0; i < num_files; i++) { + dw_printf("\t%s\n", file_list[i]->d_name); + } + } + + // Look for exact name gpioNN + + char lookfor[16]; + snprintf (lookfor, sizeof(lookfor), "gpio%d", gpio_num); + + for (i = 0; i < num_files && ! ok; i++) { + if (strcmp(lookfor, file_list[i]->d_name) == 0) { + strlcpy (gpio_name, file_list[i]->d_name, MAX_GPIO_NAME_LEN); + ok = 1; + } + } + + // If not found, Look for gpioNN_* + + snprintf (lookfor, sizeof(lookfor), "gpio%d_", gpio_num); + + for (i = 0; i < num_files && ! ok; i++) { + if (strncmp(lookfor, file_list[i]->d_name, strlen(lookfor)) == 0) { + strlcpy (gpio_name, file_list[i]->d_name, MAX_GPIO_NAME_LEN); + ok = 1; + } + } + + // Free the storage allocated by scandir(). + + for (i = 0; i < num_files; i++) { + free (file_list[i]); + } + free (file_list); + } /* - * We will have the same permission problem if not root. - * We only care about "direction" and "value". + * We should now have the corresponding node name. */ - snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", gpio); - err = system (stemp); - snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/value", gpio); - err = system (stemp); - (void)err; + if (ok) { - snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", gpio); + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Path for gpio number %d is /sys/class/gpio/%s\n", gpio_num, gpio_name); + } + } + else { - if (stat(stemp, &finfo) < 0) { - int e = errno; text_color_set(DW_COLOR_ERROR); - dw_printf ("Failed to get status for %s \n", stemp); - dw_printf ("%s\n", strerror(e)); + dw_printf ("ERROR! Could not find Path for gpio number %d.n", gpio_num); exit (1); } - if (geteuid() != 0) { - if ( ! (finfo.st_mode & S_IWOTH)) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Permissions do not allow ordinary users to access GPIO.\n"); - dw_printf ("Log in as root and type these commands:\n"); - dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/direction", gpio); - dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", gpio); - exit (1); - } - } - /* * Set output direction and initial state */ - snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/direction", gpio); - fd = open(stemp, O_WRONLY); + snprintf (gpio_direction_path, sizeof(gpio_direction_path), "/sys/class/gpio/%s/direction", gpio_name); + get_access_to_gpio (gpio_direction_path); + + fd = open(gpio_direction_path, O_WRONLY); if (fd < 0) { int e = errno; text_color_set(DW_COLOR_ERROR); @@ -290,6 +574,14 @@ void export_gpio(int gpio, int invert, int direction) exit (1); } close (fd); + +/* + * Make sure that we have access to 'value'. + * Do it once here, rather than each time we want to use it. + */ + + snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", gpio_name); + get_access_to_gpio (gpio_value_path); } #endif /* not __WIN32__ */ @@ -318,7 +610,7 @@ void export_gpio(int gpio, int invert, int direction) * * ptt_line RTS or DTR when using serial port. * - * ptt_gpio GPIO number. Only used for Linux. + * out_gpio_num GPIO number. Only used for Linux. * Valid only when ptt_method is PTT_METHOD_GPIO. * * ptt_lpt_bit Bit number for parallel printer port. @@ -341,7 +633,6 @@ void export_gpio(int gpio, int invert, int direction) -static struct audio_s *save_audio_config_p; /* Save config information for later use. */ static HANDLE ptt_fd[MAX_CHANS][NUM_OCTYPES]; /* Serial port handle or fd. */ @@ -371,7 +662,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++) { @@ -392,7 +683,7 @@ void ptt_init (struct audio_s *audio_config_p) audio_config_p->achan[ch].octrl[ot].ptt_method, audio_config_p->achan[ch].octrl[ot].ptt_device, audio_config_p->achan[ch].octrl[ot].ptt_line, - audio_config_p->achan[ch].octrl[ot].ptt_gpio, + audio_config_p->achan[ch].octrl[ot].out_gpio_num, audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit, audio_config_p->achan[ch].octrl[ot].ptt_invert); } @@ -535,58 +826,9 @@ void ptt_init (struct audio_s *audio_config_p) } if (using_gpio) { - - struct stat finfo; -/* - * Normally the device nodes are set up for access - * only by root. Try to change it here so we don't - * burden user with another configuration step. - * - * Does /sys/class/gpio/export even exist? - */ - - if (stat("/sys/class/gpio/export", &finfo) < 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("This system is not configured with the GPIO user interface.\n"); - dw_printf ("Use a different method for PTT control.\n"); - exit (1); - } - -/* - * Do we have permission to access it? - * - * pi@raspberrypi /sys/class/gpio $ ls -l - * total 0 - * --w------- 1 root root 4096 Aug 20 07:59 export - * lrwxrwxrwx 1 root root 0 Aug 20 07:59 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0 - * --w------- 1 root root 4096 Aug 20 07:59 unexport - */ - if (geteuid() != 0) { - if ( ! (finfo.st_mode & S_IWOTH)) { - int err; - - /* Try to change protection. */ - err = system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport"); - - if (stat("/sys/class/gpio/export", &finfo) < 0) { - /* Unexpected because we could do it before. */ - text_color_set(DW_COLOR_ERROR); - dw_printf ("This system is not configured with the GPIO user interface.\n"); - dw_printf ("Use a different method for PTT control.\n"); - exit (1); - } - - /* Did we succeed in changing the protection? */ - if ( ! (finfo.st_mode & S_IWOTH)) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Permissions do not allow ordinary users to access GPIO.\n"); - dw_printf ("Log in as root and type this command:\n"); - dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n"); - exit (1); - } - } - } + get_access_to_gpio ("/sys/class/gpio/export"); } + /* * We should now be able to create the device nodes for * the pins we want to use. @@ -594,15 +836,18 @@ void ptt_init (struct audio_s *audio_config_p) for (ch = 0; ch < MAX_CHANS; ch++) { if (save_audio_config_p->achan[ch].valid) { - int ot; + + int ot; // output control type, PTT, DCD, CON, ... + int it; // input control type + for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) { - export_gpio(audio_config_p->achan[ch].octrl[ot].ptt_gpio, audio_config_p->achan[ch].octrl[ot].ptt_invert, 1); + export_gpio(ch, ot, audio_config_p->achan[ch].octrl[ot].ptt_invert, 1); } } - for (ot = 0; ot < NUM_ICTYPES; ot++) { - if (audio_config_p->achan[ch].ictrl[ot].method == PTT_METHOD_GPIO) { - export_gpio(audio_config_p->achan[ch].ictrl[ot].gpio, audio_config_p->achan[ch].ictrl[ot].invert, 0); + for (it = 0; it < NUM_ICTYPES; it++) { + if (audio_config_p->achan[ch].ictrl[it].method == PTT_METHOD_GPIO) { + export_gpio(ch, it, audio_config_p->achan[ch].ictrl[it].invert, 0); } } } @@ -772,7 +1017,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 * @@ -809,6 +1054,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? */ @@ -880,11 +1132,12 @@ void ptt_set (int ot, int chan, int ptt_signal) if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) { int fd; - char stemp[80]; + char gpio_value_path[80]; + char stemp[16]; - snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio); + snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", save_audio_config_p->achan[chan].octrl[ot].out_gpio_name); - fd = open(stemp, O_WRONLY); + fd = open(gpio_value_path, O_WRONLY); if (fd < 0) { int e = errno; text_color_set(DW_COLOR_ERROR); @@ -898,7 +1151,7 @@ void ptt_set (int ot, int chan, int ptt_signal) if (write (fd, stemp, 1) != 1) { int e = errno; text_color_set(DW_COLOR_ERROR); - dw_printf ("Error setting GPIO %d for %s\n", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio, otnames[ot]); + dw_printf ("Error setting GPIO %d for %s\n", save_audio_config_p->achan[chan].octrl[ot].out_gpio_num, otnames[ot]); dw_printf ("%s\n", strerror(e)); } close (fd); @@ -999,15 +1252,17 @@ int get_input (int it, int chan) #else if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) { int fd; - char stemp[80]; + char gpio_value_path[80]; + + snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", save_audio_config_p->achan[chan].ictrl[it].in_gpio_name); - snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].ictrl[it].gpio); + get_access_to_gpio (gpio_value_path); - fd = open(stemp, O_RDONLY); + fd = open(gpio_value_path, O_RDONLY); if (fd < 0) { int e = errno; text_color_set(DW_COLOR_ERROR); - dw_printf ("Error opening %s to check input.\n", stemp); + dw_printf ("Error opening %s to check input.\n", gpio_value_path); dw_printf ("%s\n", strerror(e)); return -1; } @@ -1016,11 +1271,12 @@ int get_input (int it, int chan) if (read (fd, vtemp, 1) != 1) { int e = errno; text_color_set(DW_COLOR_ERROR); - dw_printf ("Error getting GPIO %d value\n", save_audio_config_p->achan[chan].ictrl[it].gpio); + dw_printf ("Error getting GPIO %d value\n", save_audio_config_p->achan[chan].ictrl[it].in_gpio_num); dw_printf ("%s\n", strerror(e)); } close (fd); + vtemp[1] = '\0'; if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) { return 1; } @@ -1201,9 +1457,9 @@ main () my_audio_config.adev[0].num_channels = 1; my_audio_config.valid[0] = 1; my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_GPIO; - my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_gpio = 25; + my_audio_config.adev[0].octrl[OCTYPE_PTT].out_gpio_num = 25; - dw_printf ("Try GPIO %d a few times...\n", my_audio_config.ptt_gpio[0]); + dw_printf ("Try GPIO %d a few times...\n", my_audio_config.out_gpio_num[0]); ptt_init (&my_audio_config); diff --git a/rdq.c b/rdq.c index cbfc2cf0..6a47cb83 100644 --- a/rdq.c +++ b/rdq.c @@ -29,13 +29,14 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include #include #include -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" @@ -336,9 +337,6 @@ rrbb_t rdq_remove (void) { rrbb_t result_p; -#ifndef __WIN32__ - int err; -#endif #if DEBUG diff --git a/recv.c b/recv.c index a2dba4d5..8d7826a1 100644 --- a/recv.c +++ b/recv.c @@ -2,7 +2,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 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 @@ -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 @@ -80,6 +80,9 @@ * *---------------------------------------------------------------*/ +//#define DEBUG 1 + +#include "direwolf.h" #include #include @@ -95,7 +98,6 @@ #include #endif -#include "direwolf.h" #include "audio.h" #include "demod.h" #include "multi_modem.h" @@ -104,6 +106,8 @@ #include "recv.h" #include "dtmf.h" #include "aprs_tt.h" +#include "dtime_now.h" +#include "ax25_link.h" #if __WIN32__ @@ -261,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. */ @@ -283,40 +287,114 @@ static void * recv_adev_thread (void *arg) void recv_process (void) { - int ok; - dlq_type_t type; - int chan; - int subchan; - int slice; - packet_t pp; - alevel_t alevel; - retry_t retries; - char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; + struct dlq_item_s *pitem; while (1) { - dlq_wait_while_empty (); + int timed_out; + + double timeout_value = ax25_link_get_next_timer_expiry(); + + timed_out = dlq_wait_while_empty (timeout_value); + + #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("recv_process: woke up\n"); + dw_printf ("recv_process: woke up, timed_out=%d\n", timed_out); #endif - ok = dlq_remove (&type, &chan, &subchan, &slice, &pp, &alevel, &retries, spectrum, sizeof(spectrum)); + if (timed_out) { #if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("recv_process: dlq_remove() returned ok=%d, type=%d, chan=%d, pp=%p\n", - ok, (int)type, chan, pp); + text_color_set(DW_COLOR_ERROR); + dw_printf ("recv_process: time waiting on dlq. call dl_timer_expiry.\n"); #endif - if (ok) { - app_process_rec_packet (chan, subchan, slice, pp, alevel, retries, spectrum); + + dl_timer_expiry (); } -#if DEBUG else { + + pitem = dlq_remove (); + +#if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("recv_process: spurious wakeup. (Temp debugging message - not a problem if only occasional.)\n"); - } + dw_printf ("recv_process: dlq_remove() returned pitem=%p\n", pitem); +#endif + + if (pitem != NULL) { + switch (pitem->type) { + + case DLQ_REC_FRAME: +/* + * This is the traditional processing. + * For all frames: + * - Print in standard monitoring format. + * - Send to KISS client applications. + * - Send to AGw client applications in raw mode. + * For APRS frames: + * - Explain what it means. + * - Send to Igate. + * - Digipeater. + */ + + app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->retries, pitem->spectrum); + + +/* + * Link processing. + */ + lm_data_indication(pitem); + + break; + + + case DLQ_CONNECT_REQUEST: + + dl_connect_request (pitem); + break; + + case DLQ_DISCONNECT_REQUEST: + + dl_disconnect_request (pitem); + break; + + case DLQ_XMIT_DATA_REQUEST: + + dl_data_request (pitem); + break; + + 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); + } +#if DEBUG + else { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("recv_process: spurious wakeup. (Temp debugging message - not a problem if only occasional.)\n"); + } #endif + } + } } /* end recv_process */ diff --git a/redecode.c b/redecode.c deleted file mode 100644 index 7c779ad6..00000000 --- a/redecode.c +++ /dev/null @@ -1,255 +0,0 @@ -// -// This file is part of Dire Wolf, an amateur radio packet TNC. -// -// Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - - -/*------------------------------------------------------------------ - * - * Module: redecode.c - * - * Purpose: Retry decoding frames that have a bad FCS. - * - * Description: - * - * - * Usage: (1) The main application calls redecode_init. - * - * This will initialize the retry decoding queue - * and create a thread to work on contents of the queue. - * - * (2) The application queues up frames by calling rdq_append. - * - * - * (3) redecode_thread removes raw frames from the queue and - * tries to recover from errors. - * - *---------------------------------------------------------------*/ - -#include -#include -#include -#include -#include - -#include - -#if __WIN32__ -#include -#endif - -#include "direwolf.h" -#include "ax25_pad.h" -#include "textcolor.h" -#include "audio.h" -#include "rdq.h" -#include "redecode.h" -#include "hdlc_send.h" -#include "hdlc_rec2.h" -#include "ptt.h" - - -/* Audio configuration for the fix_bits / passall optiions. */ - -static struct audio_s *save_audio_config_p; - - - -#if __WIN32__ -static unsigned redecode_thread (void *arg); -#else -static void * redecode_thread (void *arg); -#endif - - -/*------------------------------------------------------------------- - * - * Name: redecode_init - * - * Purpose: Initialize the process to try fixing bits in frames with bad FCS. - * - * Inputs: none. - * - * Outputs: none. - * - * Description: Initialize the queue to be empty and set up other - * mechanisms for sharing it between different threads. - * - * Start up redecode_thread to actually process the - * raw frames from the queue. - * - *--------------------------------------------------------------------*/ - - -void redecode_init (struct audio_s *p_audio_config) -{ - -#if 0 - -#if __WIN32__ - HANDLE redecode_th; -#else - pthread_t redecode_tid; - int e; -#endif - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_init ( ... )\n"); -#endif - - save_audio_config_p = p_audio_config; - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_init: about to call rdq_init \n"); -#endif - rdq_init (); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_init: about to create thread \n"); -#endif - - -#if __WIN32__ - redecode_th = _beginthreadex (NULL, 0, redecode_thread, NULL, 0, NULL); - if (redecode_th == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not create redecode thread\n"); - return; - } -#else - -//TODO: Give thread lower priority. - - e = pthread_create (&redecode_tid, NULL, redecode_thread, (void *)0); - if (e != 0) { - text_color_set(DW_COLOR_ERROR); - perror("Could not create redecode thread"); - return; - } -#endif - - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_init: finished \n"); -#endif -#endif - -} /* end redecode_init */ - - - - - -/*------------------------------------------------------------------- - * - * Name: redecode_thread - * - * Purpose: Try to decode frames with a bad FCS. - * - * Inputs: None. - * - * Outputs: - * - * Description: Initialize the queue to be empty and set up other - * mechanisms for sharing it between different threads. - * - * - *--------------------------------------------------------------------*/ - -#if 0 - -#if __WIN32__ -static unsigned redecode_thread (void *arg) -#else -static void * redecode_thread (void *arg) -#endif -{ -#if __WIN32__ - HANDLE tid = GetCurrentThread(); - //int tp; - - //tp = GetThreadPriority (tid); - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("Starting redecode thread priority=%d\n", tp); - SetThreadPriority (tid, THREAD_PRIORITY_LOWEST); - //tp = GetThreadPriority (tid); - //dw_printf ("New redecode thread priority=%d\n", tp); -#endif - - while (1) { - rrbb_t block; - - rdq_wait_while_empty (); -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_thread: woke up\n"); -#endif - - block = rdq_remove (); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_thread: rdq_remove() returned %p\n", block); -#endif - -/* Don't expect null ever but be safe. */ - - if (block != NULL) { - - int chan = rrbb_get_chan(block); - int subchan = rrbb_get_subchan(block); - int blen = rrbb_get_len(block); - alevel_t alevel = rrbb_get_audio_level(block); - //retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; - //int passall = save_audio_config_p->achan[chan].passall; - - int ok; - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_thread: begin processing %p, from channel %d, blen=%d\n", block, chan, blen); -#endif - - ok = hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("redecode_thread: finished processing %p\n", block); -#endif - rrbb_delete (block); - } - - } - - return 0; - -} /* end redecode_thread */ - -#endif - - - -/* end redecode.c */ - - - diff --git a/regex/README-dire-wolf.txt b/regex/README-dire-wolf.txt index cc081c49..f8170840 100644 --- a/regex/README-dire-wolf.txt +++ b/regex/README-dire-wolf.txt @@ -4,4 +4,8 @@ For the Windows version, we need to include our own version. The source was obtained from: - http://gnuwin32.sourceforge.net/packages/regex.htm \ No newline at end of file + http://gnuwin32.sourceforge.net/packages/regex.htm + +That is very old with loads of compile warnings. Should we upgrade from here? + + https://www.gnu.org/software/libc/sources.html \ No newline at end of file diff --git a/regex/regcomp.c b/regex/regcomp.c index 4cf16882..006fe5cf 100644 --- a/regex/regcomp.c +++ b/regex/regcomp.c @@ -3036,7 +3036,9 @@ parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, while (1) { - bracket_elem_t start_elem, end_elem; + // Got warnings about being used uninitialized. + // bracket_elem_t start_elem, end_elem; + bracket_elem_t start_elem = {.type=0, .opr.name=NULL}, end_elem = {.type=0, .opr.name=NULL}; unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE]; unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE]; reg_errcode_t ret; diff --git a/regex/regex_internal.c b/regex/regex_internal.c index 66154e0c..0c11b88f 100644 --- a/regex/regex_internal.c +++ b/regex/regex_internal.c @@ -675,9 +675,9 @@ re_string_reconstruct (re_string_t *pstr, int idx, int eflags) else { /* No, skip all characters until IDX. */ +#ifdef RE_ENABLE_I18N int prev_valid_len = pstr->valid_len; -#ifdef RE_ENABLE_I18N if (BE (pstr->offsets_needed, 0)) { pstr->len = pstr->raw_len - idx + offset; @@ -1396,7 +1396,9 @@ static int internal_function re_dfa_add_node (re_dfa_t *dfa, re_token_t token) { +#ifdef RE_ENABLE_I18N int type = token.type; +#endif if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0)) { size_t new_nodes_alloc = dfa->nodes_alloc * 2; diff --git a/regex/regexec.c b/regex/regexec.c index 135efe74..a5debc9d 100644 --- a/regex/regexec.c +++ b/regex/regexec.c @@ -1,3 +1,9 @@ + +/* this is very old and has massive numbers of compiler warnings. */ +/* Maybe try upgrading to a newer version such as */ +/* https://fossies.org/dox/glibc-2.24/regexec_8c_source.html */ + + /* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2004, 2005, 2007 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -18,6 +24,14 @@ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ +/* Added 12/2016 to remove warning: */ +/* incompatible implicit declaration of built-in function 'alloca' */ +#if __WIN32__ +#include +#else +#include +#endif + static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags, int n) internal_function; static void match_ctx_clean (re_match_context_t *mctx) internal_function; @@ -228,6 +242,7 @@ regexec (preg, string, nmatch, pmatch, eflags) reg_errcode_t err; int start, length; re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + (void)dfa; if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND)) return REG_BADPAT; @@ -419,6 +434,7 @@ re_search_stub (bufp, string, length, start, range, stop, regs, ret_len) int nregs, rval; int eflags = 0; re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; + (void)dfa; /* Check for out-of-range. */ if (BE (start < 0 || start > length, 0)) @@ -2894,7 +2910,10 @@ check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node, sizeof (re_dfastate_t *) * (path->alloc - old_alloc)); } - str_idx = path->next_idx ?: top_str; + // Original: + // str_idx = path->next_idx ?: top_str; + // Copied following from another version when cleaning up compiler warnings. + str_idx = path->next_idx ? path->next_idx : top_str; /* Temporary modify MCTX. */ backup_state_log = mctx->state_log; @@ -3032,7 +3051,9 @@ check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx, const re_dfa_t *const dfa = mctx->dfa; int result; int cur_idx; +#ifdef RE_ENABLE_I18N reg_errcode_t err = REG_NOERROR; +#endif re_node_set union_set; re_node_set_init_empty (&union_set); for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx) diff --git a/rrbb.c b/rrbb.c index 5651de74..82d8aaea 100644 --- a/rrbb.c +++ b/rrbb.c @@ -35,12 +35,13 @@ #define RRBB_C +#include "direwolf.h" + #include #include #include #include -#include "direwolf.h" #include "textcolor.h" #include "ax25_pad.h" #include "rrbb.h" diff --git a/serial_port.c b/serial_port.c index 2a710100..3ccf43c8 100644 --- a/serial_port.c +++ b/serial_port.c @@ -34,17 +34,16 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" // should be first + #include #if __WIN32__ #include -#include #else -#define __USE_XOPEN2KXSI 1 -#define __USE_XOPEN 1 #include #include #include @@ -58,7 +57,7 @@ #include #include -#include "direwolf.h" + #include "textcolor.h" #include "serial_port.h" @@ -183,6 +182,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. */ @@ -300,7 +303,7 @@ int serial_port_write (MYFDTYPE fd, char *str, int len) dw_printf ("Error writing to serial port. Error %d.\n\n", err); } } - else if (nwritten != len) + else if ((int)nwritten != len) { text_color_set(DW_COLOR_ERROR); dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len); diff --git a/serial_port.h b/serial_port.h index 6c3287ac..8a65a0b4 100644 --- a/serial_port.h +++ b/serial_port.h @@ -1,11 +1,13 @@ /* serial_port.h */ +#ifndef SERIAL_PORT_H +#define SERIAL_PORT_H 1 + #if __WIN32__ #include -#include typedef HANDLE MYFDTYPE; #define MYFDERROR INVALID_HANDLE_VALUE @@ -24,4 +26,7 @@ extern int serial_port_write (MYFDTYPE fd, char *str, int len); extern int serial_port_get1 (MYFDTYPE fd); -extern void serial_port_close (MYFDTYPE fd); \ No newline at end of file +extern void serial_port_close (MYFDTYPE fd); + + +#endif \ No newline at end of file diff --git a/server.c b/server.c index 13e7980a..2a1ef781 100644 --- a/server.c +++ b/server.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 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 @@ -58,6 +58,17 @@ * 'x' Unregister CallSign * * 'y' Ask Outstanding frames waiting on a Port (new in 1.2) + * + * 'C' Connect, Start an AX.25 Connection (new in 1.4) + * + * 'v' Connect VIA, Start an AX.25 circuit thru digipeaters (new in 1.4) + * + * 'c' Connection with non-standard PID (new in 1.4) + * + * 'D' Send Connected Data (new in 1.4) + * + * 'd' Disconnect, Terminate an AX.25 Connection (new in 1.4) + * * * A message is printed if any others are received. * @@ -80,7 +91,13 @@ * (Enabled with 'm' command.) * * 'y' Outstanding frames waiting on a Port (new in 1.2) - * + * + * 'C' AX.25 Connection Received (new in 1.4) + * + * 'D' Connected AX.25 Data (new in 1.4) + * + * 'd' Disconnected (new in 1.4) + * * * * References: AGWPE TCP/IP API Tutorial @@ -104,11 +121,11 @@ * Cygwin: Can use either one. */ +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include -#define _WIN32_WINNT 0x0501 -#include +#include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include @@ -129,18 +146,21 @@ #include #include -#include "direwolf.h" #include "tq.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "server.h" +#include "dlq.h" /* * Previously, we allowed only one network connection at a time to each port. - * In version 1.1, we allow multiple concurrent client apps to connect. + * In version 1.1, we allow multiple concurrent client apps to attach with the AGW network protocol. + * The default is a limit of 3 client applications at the same time. + * You can increase the limit by changing the line below. + * A larger number consumes more resources so don't go crazy by making it larger than needed. */ #define MAX_NET_CLIENTS 3 @@ -162,23 +182,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. @@ -226,7 +229,7 @@ static THREAD_F cmd_listen_thread (void *arg); #if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -// gcc >= 4.2 has __builtin_swap32() but be compatible with older versions. +// gcc >= 4.2 has __builtin_swap32() but might not be compatible with older versions or other compilers. #define host2netle(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) #define netle2host(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) @@ -447,8 +450,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"); @@ -636,6 +637,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); /* @@ -819,6 +821,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)); @@ -828,6 +831,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 } @@ -843,6 +847,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl time_t clock; struct tm *tm; + int num_digi; clock = time(NULL); tm = localtime(&clock); // TODO: should use localtime_r @@ -867,13 +872,46 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl /* The documentation example includes these 3 extra in the Len= value */ /* but actual observed data uses only the packet info length. */ - snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s [%02d:%02d:%02d]\r%s\r\r", + // Documentation doesn't mention anything about including the via path. + // In version 1.4, we add that to match observed behaviour. + + // This inconsistency was reported: + // Direwolf: + // [AGWE-IN] 1:Fm ZL4FOX-8 To Q7P2U2 [08:25:07]`I1*l V>/"9<}[:Barts Tracker 3.83V X + // AGWPE: + // [AGWE-IN] 1:Fm ZL4FOX-8 To Q7P2U2 Via WIDE3-3 [08:32:14]`I0*l V>/"98}[:Barts Tracker 3.83V X + + num_digi = ax25_get_num_repeaters(pp); + + if (num_digi > 0) { + + char via[AX25_MAX_REPEATERS*(AX25_MAX_ADDR_LEN+1)]; + char stemp[AX25_MAX_ADDR_LEN+1]; + int j; + + ax25_get_addr_with_ssid (pp, AX25_REPEATER_1, via); + for (j = 1; j < num_digi; j++) { + ax25_get_addr_with_ssid (pp, AX25_REPEATER_1 + j, stemp); + strlcat (via, ",", sizeof(via)); + strlcat (via, stemp, sizeof(via)); + } + + snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s Via %s [%02d:%02d:%02d]\r%s\r\r", + chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, via, + ax25_get_pid(pp), info_len, + tm->tm_hour, tm->tm_min, tm->tm_sec, + pinfo); + } + else { + + snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s [%02d:%02d:%02d]\r%s\r\r", chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, ax25_get_pid(pp), info_len, tm->tm_hour, tm->tm_min, tm->tm_sec, pinfo); + } - agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* include null */ ; + agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* +1 to include terminating null */ ; if (debug_client) { debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); @@ -888,6 +926,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)); @@ -896,7 +935,8 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending message to AGW client application %d. Closing connection.\n\n", client); close (client_sock[client]); - client_sock[client] = -1; + client_sock[client] = -1; + dlq_client_cleanup (client); } #endif } @@ -913,6 +953,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. @@ -942,6 +984,8 @@ void server_link_established (int chan, int client, char *remote_call, char *own strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from)); strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to)); + // Question: Should the via path be provided too? + if (incoming) { // Other end initiated the connection. snprintf (reply.info, sizeof(reply.info), "*** CONNECTED To Station %s\r", remote_call); @@ -966,6 +1010,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. @@ -976,7 +1022,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? * *--------------------------------------------------------------------*/ @@ -1009,6 +1055,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 */ + /*------------------------------------------------------------------- * @@ -1086,7 +1192,7 @@ static void send_to_client (int client, void *reply_p) { struct agwpe_s *ph; int len; -#if __WIN32__ +#if __WIN32__ #else int err; #endif @@ -1111,6 +1217,7 @@ static void send_to_client (int client, void *reply_p) send (client_sock[client], (char*)(ph), len, 0); #else err = write (client_sock[client], ph, len); + (void)err; #endif } @@ -1148,6 +1255,7 @@ static THREAD_F cmd_listen_thread (void *arg) close (client_sock[client]); #endif client_sock[client] = -1; + dlq_client_cleanup (client); continue; } @@ -1172,7 +1280,7 @@ static THREAD_F cmd_listen_thread (void *arg) int data_len = netle2host(cmd.hdr.data_len_NETLE); - if (data_len < 0 || data_len > sizeof(cmd.data) - 1) { + if (data_len < 0 || data_len > (int)(sizeof(cmd.data) - 1)) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nInvalid message from AGW client application %d.\n", client); @@ -1189,6 +1297,7 @@ static THREAD_F cmd_listen_thread (void *arg) close (client_sock[client]); #endif client_sock[client] = -1; + dlq_client_cleanup (client); return (0); } @@ -1207,6 +1316,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) { @@ -1326,7 +1436,7 @@ static THREAD_F cmd_listen_thread (void *arg) { struct { struct agwpe_s hdr; - unsigned char on_air_baud_rate; /* 0=1200, 3=9600 */ + unsigned char on_air_baud_rate; /* 0=1200, 1=2400, 2=4800, 3=9600, ... */ unsigned char traffic_level; /* 0xff if not in autoupdate mode */ unsigned char tx_delay; unsigned char tx_tail; @@ -1365,10 +1475,14 @@ static THREAD_F cmd_listen_thread (void *arg) break; - case 'H': /* Ask about recently heard stations. */ + case 'H': /* Ask about recently heard stations on given port. */ + + /* This should send back 20 'H' frames for the most recently heard stations. */ + /* If there are less available, empty frames are sent to make a total of 20. */ + /* Each contains the first and last heard times. */ { -#if 0 /* This information is not being collected. */ +#if 0 /* Currently, this information is not being collected. */ struct { struct agwpe_s hdr; char info[100]; @@ -1382,7 +1496,8 @@ static THREAD_F cmd_listen_thread (void *arg) reply.hdr.portx = cmd.hdr.portx - strlcpy (reply.hdr.call_from, "WB2OSZ-15", sizeof(reply.hdr.call_from)); + strlcpy (reply.hdr.call_from, "WB2OSZ-15 Mon,01Jan2000 01:02:03 Tue,31Dec2099 23:45:56", sizeof(reply.hdr.call_from)); + // or 00:00:00 00:00:00 strlcpy (agwpe_msg.data, ..., sizeof(agwpe_msg.data)); @@ -1525,38 +1640,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; - - memset (&reply, 0, sizeof(reply)); - reply.hdr.datakind = 'X'; - 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. + int ok = 1; // 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. + // Too much trouble. Report success if the channel is valid. - if (server_callsign_registered_by_client(cmd.hdr.call_from) >= 0) { - ok = 0; + + 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; - 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 */ + + 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); + reply.data = ok; send_to_client (client, &reply); } break; @@ -1564,13 +1675,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. */ @@ -1590,10 +1703,9 @@ static THREAD_F cmd_listen_thread (void *arg) int num_calls = 2; /* 2 plus any digipeaters. */ int pid = 0xf0; /* normal for AX.25 I frames. */ int j; - char stemp[256]; 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. */ @@ -1620,28 +1732,38 @@ static THREAD_F cmd_listen_thread (void *arg) } } - 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"); + + dlq_connect_request (callsigns, num_calls, cmd.hdr.portx, client, pid); + } break; + case 'D': /* Send Connected Data */ - 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"); + { + char callsigns[2][AX25_MAX_ADDR_LEN]; + const int num_calls = 2; + + strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); + strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); + + dlq_xmit_data_request (callsigns, num_calls, cmd.hdr.portx, client, cmd.hdr.pid, cmd.data, netle2host(cmd.hdr.data_len_NETLE)); + + } break; case 'd': /* Disconnect, Terminate an AX.25 Connection */ { - 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"); + char callsigns[2][AX25_MAX_ADDR_LEN]; + const int num_calls = 2; + + strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); + strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); + + dlq_disconnect_request (callsigns, num_calls, cmd.hdr.portx, client); + } break; @@ -1756,30 +1878,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 */ diff --git a/server.h b/server.h index 94ad0686..08db5713 100644 --- a/server.h +++ b/server.h @@ -18,5 +18,11 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl int server_callsign_registered_by_client (char *callsign); +void server_link_established (int chan, int client, char *remote_call, char *own_call, int incoming); + +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 */ diff --git a/symbols-new.txt b/symbols-new.txt index d9f50b46..44e2cba0 100644 --- a/symbols-new.txt +++ b/symbols-new.txt @@ -1,12 +1,22 @@ -APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 29 Oct 2015 +APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 3 Apr 2017 --------------------------------------------------------------------- -BACKGROUND: This file addresses new additions proposals (OVERLAYS) -to the APRS symbol set after 1 October 2007. The master symbol -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. +BACKGROUND: Since 1 October 2007, overlay characters (36 per symbol) +are allowed on all symbols. Since the master symbol document, +http://aprs.org/symbols/symbolsX.txt page only has one line per symbol +character this overlay list gives us thousands of new symbol codes. +03 Apr17: Added Methane Hazard symbol "MH" +13 Feb17: Added Ez = Emergency Power (shelter), Cars: P> = Plugin + S> = Solar powered. Moved Ham club C- to Buildings Ch. + Added C- to house for combined renewables and added R% + Renewable to power plants + +18 Oct16: Added G,Y,R for Flood gauges + N for Normal.(on H2O symbol) + Added DIGIPEATERS +22 Mar16: Added A0 overlay circle for ALSTAR nodes and V0 for VOIP. + Combined echolink and IRLP. P& for PSKmail node. W& for + Wires-X (was W0 for WiresII). Ya for Yaesu C4FM repeaters Update 29 Oct 2015: Reorgainized list to Alphabetical Order. + Added many new Balloons (due to lost DoD radar Blimp yesterday) @@ -18,27 +28,20 @@ Update 29 Oct 2015: Reorgainized list to Alphabetical Order. UPDATES/REVISIONS/CORRECTIONS: -28 Aug 14 Added numerous OpenAPRS symbols see "(new Aug 2014)" -20 May 14 Changed Da to DSTAR (2700 of them) from Dutch Ares -19 May 14 Added Submarine&torpedo to ships and lots of Aircraft - search for "(new may 2014)" -07 Oct 13 Added new overlays to ships such as Jet Ski, Js - Added Ham Club symbol as a C overlay on House, C- -19 Sep 11 Added T and 2 overlays for TX 1 and 2 hop IGates - Added overlays to (;) portable, to show event types -23 Mar 11 Added Radiation Detector (RH) -20 Apr 10 Byonics requested (BY) -04 Jan 10 added #A to the table (correcting earlier omission) -12 Oct 09 Added W0 for Yaesu WIRES nodes -09 Apr 09 Changed APRStt symbol to overlayed BOX (#A) -21 Aug 08 Added RFID R=, Babystroller B], Radio#Y, skull&Xbones XH - - -25 Mar 08 Modified these Alternate Symbol codes for expanded Overlays. -Prior to this, each Alternate Table basic symbol had a unique defini- -tion, but this was every restrictive. SO the following alternate -base symbols were redefined so that the basic symbol could take on -dozens of unique overlay definitions: +2014 Added numerous OpenAPRS symbol. Changed Da to DSTAR from Dutch + ARES. Added Subs to ships and lots of Aircraft overlays. +2013 Added ship overlay Jet Ski. Ham Club as C overlay on House, C- +2011 Added T and 2 overlays for TX 1 and 2 hop IGates + Added overlays to (;) Portables Added Radiation Detector (RH) +2010 Byonics requested (BY) and added #A to the table +2009 Added W0 for Yaesu WIRES and APRStt symbol to overlayed BOX (#A) +2008 Added RFID R=, Babystroller B], Radio#Y, skull&Xbones XH + + +DEPRICATION AND MAJOR REVISION: 25 Mar 08 Modified several Alternate +Symbol codes for expanded Overlays. The following alternate base +symbols were redefined so that the basic symbol could take on dozens +of unique overlay definitions: \= - Had been undefined \0 - Several overlays for the numbered Circle @@ -54,13 +57,14 @@ dozens of unique overlay definitions: \[ - \[ is wall cloud, but overlays are humans. S[ is a skier. \h - Buildings. \h is a Ham store, "Hh" is Home Depot, etc. -Previous edition was 4 Oct 2007. + +4 Oct 2007. ORIGINAL EXPANSION to OVERLAYS ON ALL SYMBOLS In April 2007, a proposal to expand the use of overlay bytes for the extension of the APRS symbol set was added to the draft APRS1.2 addendum web page. The following document addresses that proposal: -www.aprs.org/symbols/symbols-overlays.txt +http://aprs.org/symbols/symbols-overlays.txt For details on Upgrading your symbol set, please see the background information on Symbols prepared by Stephen Smith, WA8LMF: @@ -164,6 +168,7 @@ Ga = RSGB Radio Society of Great Brittan Ra = RACES Sa = SATERN Salvation Army Wa = WinLink +Ya = C4FM Yaesu repeaters BALLOONS and lighter than air #O (All new Oct 2015) /O = Original Balloon (think Ham balloon) @@ -191,17 +196,22 @@ etc BUILDINGS: #h /h = Hospital \h = Ham Store ** <= now used for HAMFESTS +Ch = Club (ham radio) Fh = HamFest (new Aug 2014) -Hh = Home Dept etc.. +Hh = Home Depot etc.. CARS: #> (Vehicles) /> = normal car (side view) \> = Top view and symbol POINTS in direction of travel #> = Reserve overlays 1-9 for numbered cars (new Aug 2014) -E> = Electric -H> = Hybrid +B> = Battery (was E for electric) +E> = Ethanol (was electric) +F> = Fuelcell or hydrogen +H> = Homemade +P> = Plugin-hybrid S> = Solar powered -V> = GM Volt +T> = Tesla (temporary) +V> = GM Volt (temporary) CIVIL DEFENSE or TRIANGLE: #c /c = Incident Command Post @@ -221,6 +231,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! @@ -240,7 +263,9 @@ GATEWAYS: #& /& = HF Gateway <= the original primary table definition I& = Igate Generic (please use more specific overlay) R& = Receive only IGate (do not send msgs back to RF) +P& = PSKmail node T& = TX igate with path set to 1 hop only) +W& = WIRES-X as opposed to W0 for WiresII 2& = TX igate with path set to 2 hops (not generally good idea) GPS devices: #\ @@ -251,6 +276,7 @@ A\ = Avmap G5 * <= Recommend special symbol HAZARDS: #H /H = hotel \H = Haze +MH = Methane Hazard (new Apr 2017) RH = Radiation detector (new mar 2011) WH = Hazardous Waste XH = Skull&Crossbones @@ -266,16 +292,16 @@ H[ = Hiker HOUSE: #- /- = House \- = (was HF) -5- = 50 Hz mains power -6- = 60 Hz mains power -B- = Backup Battery Power -C- = Club, as in Ham club -E- = Emergency power +5- = 50 Hz if non standard +6- = 60 Hz if non standard +B- = Battery or off grid +C- = Combined alternatives +E- = Emergency power (grid down) G- = Geothermal H- = Hydro powered O- = Operator Present -S- = Solar Powered -W- = Wind powered +S- = Solar Power +W- = Wind power INCIDENT SITES: #' /' = Small Aircraft (original primary symbol) @@ -287,9 +313,11 @@ P' = Pileup T' = Truck wreck NUMBERED CIRCLES: #0 +A0 = Allstar Node (A0) E0 = Echolink Node (E0) I0 = IRLP repeater (I0) S0 = Staging Area (S0) +V0 = Echolink and IRLP (VOIP) W0 = WIRES (Yaesu VOIP) NETWORK NODES: #8 @@ -312,6 +340,7 @@ G% = Geothermal H% = Hydroelectric N% = Nuclear P% = Portable (new Aug 2014) +R% = Renewable (hydrogen etc fuels) S% = Solar T% = Turbine W% = Wind @@ -343,6 +372,7 @@ SHELTERS: #z /z = was available \z = overlayed shelter Cz = Clinic (new Aug 2014) +Ez = Emergency Power Gz = Government building (new Aug 2014) Mz = Morgue (new Aug 2014) Tz = Triage (new Aug 2014) @@ -380,6 +410,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 diff --git a/symbols.c b/symbols.c index e4d51af9..ad3263c3 100644 --- a/symbols.c +++ b/symbols.c @@ -26,22 +26,19 @@ * *------------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include #include #include -#include "direwolf.h" #include "textcolor.h" #include "symbols.h" #include "tt_text.h" -//#if __WIN32__ - char *strcasestr(const char *S, const char *FIND); -//#endif - /* * APRS symbol tables. * @@ -529,7 +526,7 @@ void symbols_list (void) * *------------------------------------------------------------------*/ -const static char ssid_to_sym[16] = { +static const char ssid_to_sym[16] = { ' ', /* 0 - No icon. */ 'a', /* 1 - Ambulance */ 'U', /* 2 - Bus */ diff --git a/telemetry.c b/telemetry.c index 68cd5851..0d389d03 100644 --- a/telemetry.c +++ b/telemetry.c @@ -25,10 +25,10 @@ #if TEST -#define DEBUG1 1 -#define DEBUG2 1 -#define DEBUG3 1 -#define DEBUG4 1 +#define DEBUG1 1 // Activate debug out when testing. +#define DEBUG2 1 // +#define DEBUG3 1 // +#define DEBUG4 1 // #endif @@ -49,6 +49,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -57,7 +59,6 @@ #include #include -#include "direwolf.h" #include "ax25_pad.h" // for packet_t, AX25_MAX_ADDR_LEN #include "decode_aprs.h" // for decode_aprs_t, G_UNKNOWN #include "textcolor.h" @@ -73,8 +74,8 @@ #define T_STR_LEN 16 /* Max len for labels and units. */ -#define MAGIC1 0xa51111a5 /* For checking storage allocation problems. */ -#define MAGIC2 0xa52222a5 +#define MAGIC1 0x5a1111a5 /* For checking storage allocation problems. */ +#define MAGIC2 0x5a2222a5 #define C_A 0 /* Scaling coefficient positions. */ #define C_B 1 @@ -353,7 +354,7 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output strlcpy (comment, next+8, commentsize); next[8] = '\0'; } - for (k = 0; k < strlen(next); k++) { + for (k = 0; k < (int)(strlen(next)); k++) { if (next[k] == '0') { draw[k] = 0; } @@ -818,7 +819,7 @@ void telemetry_bit_sense_message (char *station, char *msg, int quiet) } } - for (n = 0; n < T_NUM_DIGITAL && n < strlen(msg); n++) { + for (n = 0; n < T_NUM_DIGITAL && n < (int)(strlen(msg)); n++) { if (msg[n] == '1') { pm->sense[n] = 1; @@ -834,7 +835,16 @@ void telemetry_bit_sense_message (char *station, char *msg, int quiet) } } -/* Skip comma if first character of comment field. */ +/* + * Skip comma if first character of comment field. + * + * The protocol spec is inconsistent here. + * The definition shows the Project Title immediately after a fixed width field of 8 binary digits. + * The example has a comma in there. + * + * The toolkit telem-bits.pl script does insert the comma because it seems more sensible. + * Here we accept it either way. i.e. Discard first character after data values if it is comma. + */ if (msg[n] == ',') n++; diff --git a/textcolor.c b/textcolor.c index 90b55e30..e518b939 100644 --- a/textcolor.c +++ b/textcolor.c @@ -74,6 +74,8 @@ *--------------------------------------------------------------------*/ +#include "direwolf.h" // Should be first. includes windows.h + #include #include #include @@ -81,8 +83,6 @@ #if __WIN32__ -#include - #define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY) @@ -128,10 +128,10 @@ static const char background_white[] = "\e[5;47m"; static const char black[] = "\e[0;30m" "\e[5;47m"; static const char red[] = "\e[1;31m" "\e[5;47m"; static const char green[] = "\e[1;32m" "\e[5;47m"; -static const char yellow[] = "\e[1;33m" "\e[5;47m"; +//static const char yellow[] = "\e[1;33m" "\e[5;47m"; static const char blue[] = "\e[1;34m" "\e[5;47m"; static const char magenta[] = "\e[1;35m" "\e[5;47m"; -static const char cyan[] = "\e[1;36m" "\e[5;47m"; +//static const char cyan[] = "\e[1;36m" "\e[5;47m"; static const char dark_green[] = "\e[0;32m" "\e[5;47m"; /* Clear from cursor to end of screen. */ @@ -153,10 +153,10 @@ static const char background_white[] = "\e[48;2;255;255;255m"; static const char black[] = "\e[0;30m" "\e[48;2;255;255;255m"; static const char red[] = "\e[0;31m" "\e[48;2;255;255;255m"; static const char green[] = "\e[0;32m" "\e[48;2;255;255;255m"; -static const char yellow[] = "\e[0;33m" "\e[48;2;255;255;255m"; +//static const char yellow[] = "\e[0;33m" "\e[48;2;255;255;255m"; static const char blue[] = "\e[0;34m" "\e[48;2;255;255;255m"; static const char magenta[] = "\e[0;35m" "\e[48;2;255;255;255m"; -static const char cyan[] = "\e[0;36m" "\e[48;2;255;255;255m"; +//static const char cyan[] = "\e[0;36m" "\e[48;2;255;255;255m"; static const char dark_green[] = "\e[0;32m" "\e[48;2;255;255;255m"; @@ -171,10 +171,10 @@ static const char background_white[] = "\e[47;1m"; static const char black[] = "\e[0;30m" "\e[1;47m"; static const char red[] = "\e[1;31m" "\e[1;47m"; static const char green[] = "\e[1;32m" "\e[1;47m"; -static const char yellow[] = "\e[1;33m" "\e[1;47m"; +//static const char yellow[] = "\e[1;33m" "\e[1;47m"; static const char blue[] = "\e[1;34m" "\e[1;47m"; static const char magenta[] = "\e[1;35m" "\e[1;47m"; -static const char cyan[] = "\e[1;36m" "\e[1;47m"; +//static const char cyan[] = "\e[1;36m" "\e[1;47m"; static const char dark_green[] = "\e[0;32m" "\e[1;47m"; diff --git a/tocalls.txt b/tocalls.txt index ac44dfdb..f69b6917 100644 --- a/tocalls.txt +++ b/tocalls.txt @@ -1,6 +1,15 @@ -APRS TO-CALL VERSION NUMBERS 21 Jan 2016 +APRS TO-CALL VERSION NUMBERS 06 Feb 2017 ------------------------------------------------------------------- WB4APR +06 Feb 17 added APIExx for W7KMV's PiAPRS system +25 Jan 17 added APSFxx F5OPV embedded devices - was APZ40 +16 Dec 16 added APYSxx for W2GMD's Python APRS +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 18 Nov 15 Added APSTPO for N0AGI 03 Nov 15 Updated APAND1 and APDRxx for androids @@ -10,12 +19,7 @@ APRS TO-CALL VERSION NUMBERS 21 Jan 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 @@ -49,7 +53,8 @@ a TOCALL number series: APBLO MOdel Rocketry K7RKT APBPQx John G8BPQ Digipeater/IGate APC APCxxx Cellular applications - APCBBx for VE7UDP Blackberry Applications + APCBBx VE7UDP Blackberry Applications + APCDS0 Leon Lessing ZS6LMG's cell tracker APCLEY EYTraker GPRS/GSM tracker by ZS6EY APCLWX EYWeather GPRS/GSM WX station by ZS6EY APCLEZ Telit EZ10 GSM application ZS6CEY @@ -76,13 +81,16 @@ a TOCALL number series: APF APFxxx Firenet APFGxx Flood Gage (KP4DJT) APFIxx for APRS.FI OH7LZB, Hessu + APFPRS for FreeDV by Jeroen PE1RXQ APG APGxxx Gates, etc APGOxx for AA3NJ PDA application APH APHKxx for LA1BR tracker/digipeater APHAXn SM2APRS by PY2UEP APHTxx HMTracker by IU0AAC API APICQx for ICQ - APICxx for HA9MCQ Pic IGate + APICxx HA9MCQ's Pic IGate + APIExx W7KMV's PiAPRS system + APINxx PinPoint by AB0WV APJ APJAxx JavAPRS APJExx JeAPRS APJIxx jAPRSIgate @@ -90,6 +98,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 @@ -104,6 +113,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+ @@ -132,23 +142,25 @@ a TOCALL number series: APRRTx RPC electronics APRS Generic, (obsolete. Digis should use APNxxx instead) APRXxx >40 APRSmax - APRXxx <39 for OH2MQK's RX-igate + APRXxx <39 for OH2MQK's igate APRTLM used in MIM's and Mic-lites, etc APRtfc APRStraffic APRSTx APRStt (Touch tone) APS APSxxx APRS+SA, etc APSARx ZL4FOX's SARTRACK APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK) + APSFxx F5OPV embedded devices - was APZ40 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 @@ -170,13 +182,14 @@ a TOCALL number series: APY008 Yaesu VX-8 series APY350 Yaesu FTM-350 series APYTxx for YagTracker + APYSxx for W2GMD's Python APRS APZ APZxxx Experimental APZ247 for UPRS NR0Q APZ0xx Xastir (old versions. See APX) 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 diff --git a/tq.c b/tq.c index 88ad59e2..fa787c73 100644 --- a/tq.c +++ b/tq.c @@ -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,13 +35,16 @@ * *---------------------------------------------------------------*/ +#define TQ_C 1 + +#include "direwolf.h" + #include #include #include #include #include -#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" @@ -119,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; @@ -186,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 @@ -217,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 @@ -241,12 +250,14 @@ 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; } -/* - * Is transmit queue out of control? +/* + * Is transmit queue out of control? * * There is no technical reason to limit the transmit packet queue length, it just seemed like a good * warning that something wasn't right. @@ -285,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 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 */ + + /*------------------------------------------------------------------- @@ -483,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 */ + /*------------------------------------------------------------------- diff --git a/tq.h b/tq.h index 92ee3ba3..4b5beb81 100644 --- a/tq.h +++ b/tq.h @@ -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 diff --git a/tt_text.c b/tt_text.c index f2803f23..8cd7ba80 100644 --- a/tt_text.c +++ b/tt_text.c @@ -154,6 +154,7 @@ static const char grid[10][10][3] = { "PH", "QH", "OG", "PG", "QG", "OF", "PF", "QF", "RF", "RE" }, // 8 - Aus / NZ { "IL", "IK", "IJ", "JJ", "JI", "JH", "JG", "KG", "JF", "KF" } }; // 9 - Africa +#include "direwolf.h" #include #include @@ -162,7 +163,6 @@ static const char grid[10][10][3] = #include #include -#include "direwolf.h" #include "textcolor.h" #include "tt_text.h" diff --git a/tt_user.c b/tt_user.c index 46aff3ce..f6ce1673 100644 --- a/tt_user.c +++ b/tt_user.c @@ -34,6 +34,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -42,7 +44,6 @@ #include #include -#include "direwolf.h" #include "version.h" #include "ax25_pad.h" #include "textcolor.h" diff --git a/ttcalc.c b/ttcalc.c index 17538e35..ccebf893 100644 --- a/ttcalc.c +++ b/ttcalc.c @@ -45,12 +45,12 @@ *---------------------------------------------------------------*/ +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h + #if __WIN32__ #include -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ -#include +#include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include @@ -146,6 +146,7 @@ int main (int argc, char *argv[]) send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0); #else err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd)); + (void)err; #endif @@ -167,7 +168,7 @@ int main (int argc, char *argv[]) exit (1); } - assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < sizeof(data)); + assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data))); if (mon_cmd.data_len > 0) { #if __WIN32__ @@ -356,7 +357,7 @@ static int connect_to_server (char *hostname, char *port) #if __WIN32__ #else - int e; + //int e; #endif #define MAX_HOSTS 30 @@ -426,8 +427,11 @@ static int connect_to_server (char *hostname, char *port) // Try each address until we find one that is successful. for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); diff --git a/utm2ll.c b/utm2ll.c index e1d793c2..89dc55e5 100644 --- a/utm2ll.c +++ b/utm2ll.c @@ -1,5 +1,6 @@ /* UTM to Latitude / Longitude conversion */ +#include "direwolf.h" #include #include @@ -7,7 +8,6 @@ #include #include -#include "direwolf.h" #include "utm.h" #include "mgrs.h" #include "usng.h" diff --git a/version.h b/version.h index 97b5594c..b61ab1a3 100644 --- a/version.h +++ b/version.h @@ -1,8 +1,8 @@ -/* Dire Wolf version 1.3 */ +/* Dire Wolf version 1.4 */ #define APP_TOCALL "APDW" #define MAJOR_VERSION 1 -#define MINOR_VERSION 3 +#define MINOR_VERSION 4 //#define EXTRA_VERSION "Beta Test" diff --git a/walk96.c b/walk96.c index ef1c34c3..dfad612f 100644 --- a/walk96.c +++ b/walk96.c @@ -30,6 +30,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -37,7 +39,6 @@ #include #include -#include "direwolf.h" #include "config.h" #include "ax25_pad.h" #include "textcolor.h" diff --git a/waypoint.c b/waypoint.c new file mode 100644 index 00000000..1ec77124 --- /dev/null +++ b/waypoint.c @@ -0,0 +1,624 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 +// 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 . +// + + +//#define DEBUG 1 + + +/*------------------------------------------------------------------ + * + * Module: waypoint.c + * + * Purpose: Send NMEA waypoint sentences to GPS display or mapping application. + * + *---------------------------------------------------------------*/ + + +#include "direwolf.h" // should be first + + +#include +#include + +#if __WIN32__ +#include +#else +#include +#include +#include +#endif + +#include +#include +#include + + +#include "config.h" +#include "textcolor.h" +#include "latlong.h" +#include "waypoint.h" +#include "grm_sym.h" /* Garmin symbols */ +#include "mgn_icon.h" /* Magellan icons */ +#include "dwgpsnmea.h" +#include "serial_port.h" + + +static MYFDTYPE s_waypoint_port_fd = MYFDERROR; + +static int s_waypoint_formats = 0; /* which formats should we generate? */ + +static int s_waypoint_debug = 0; /* Print information flowing to attached device. */ + + + +static void append_checksum (char *sentence); +static void send_sentence (char *sent); + + + +void waypoint_set_debug (int n) +{ + s_waypoint_debug = n; +} + + +/*------------------------------------------------------------------- + * + * Name: waypoint_init + * + * Purpose: Initialization for waypoint output port. + * + * Inputs: mc - Pointer to configuration options. + * + * ->waypoint_port - Name of serial port. COM1, /dev/ttyS0, etc. + * + * + * (currently none) - speed, baud. Default 4800 if not set + * + * + * ->waypoint_formats - Set of formats enabled. + * If none set, default to generic & Kenwood. + * + * Global output: s_waypoint_port_fd + * + * Description: First to see if this is shared with GPS input. + * If not, open serial port. + * + * Restriction: MUST be done after GPS init because we might be sharing the + * same serial port device. + * + *---------------------------------------------------------------*/ + + +void waypoint_init (struct misc_config_s *mc) +{ + +#if DEBUG + text_color_set (DW_COLOR_DEBUG); + dw_printf ("waypoint_init() device=%s formats=%d\n", mc->waypoint_port, mc->waypoint_formats); +#endif + +/* + * TODO: + * Are we sharing with GPS input? + * First try to get fd if they have same device name. + * If that fails, do own serial port open. + */ + if (strlen(mc->waypoint_port) > 0) { + + s_waypoint_port_fd = dwgpsnmea_get_fd (mc->waypoint_port, 4800); + + if (s_waypoint_port_fd == MYFDERROR) { + s_waypoint_port_fd = serial_port_open (mc->waypoint_port, 4800); + } + else { + text_color_set (DW_COLOR_INFO); + dw_printf ("Note: Sharing same port for GPS input and waypoint output.\n"); + } + + if (s_waypoint_port_fd == MYFDERROR) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Unable to open %s for waypoint output.\n", mc->waypoint_port); + return; + } + + s_waypoint_formats = mc->waypoint_formats; + if (s_waypoint_formats == 0) { + s_waypoint_formats = WPT_FORMAT_NMEA_GENERIC | WPT_FORMAT_KENWOOD; + } + if (s_waypoint_formats & WPT_FORMAT_GARMIN) { + s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */ + } + } + + +#if DEBUG + text_color_set (DW_COLOR_DEBUG); + dw_printf ("end of waypoint_init: s_waypoint_port_fd = %d\n", s_waypoint_port_fd); +#endif +} + + + +/*------------------------------------------------------------------- + * + * Name: append_checksum + * + * Purpose: Append checksum to the sentence. + * + * In/out: sentence - NMEA sentence beginning with '$'. + * + * Description: Checksum is exclusive of characters except leading '$'. + * We append '*' and an upper case two hexadecimal value. + * + * Don't add CR/LF at this point. + * + *--------------------------------------------------------------------*/ + +static void append_checksum (char *sentence) +{ + char *p; + int cs; + + assert (sentence[0] == '$'); + + cs = 0; + for (p = sentence+1; *p != '\0'; p++) { + cs ^= *p; + } + + sprintf (p, "*%02X", cs & 0xff); + +} /* end append_checksum */ + + + +/*------------------------------------------------------------------- + * + * Name: nema_send_waypoint + * + * Purpose: Convert APRS position or object into NMEA waypoint sentence + * for use by a GPS display or other mapping application. + * + * Inputs: wname_in - Name of waypoint. Max of 9 characters. + * dlat - Latitude. + * dlong - Longitude. + * symtab - Symbol table or overlay character. + * symbol - Symbol code. + * alt - Altitude in meters or G_UNKNOWN. + * course - Course in degrees or G_UNKNOWN for unknown. + * speed - Speed in knots or G_UNKNOWN. + * comment_in - Description or message. + * + * + * Description: Currently we send multiple styles. Maybe someday there might + * be an option to send a selected subset. + * + * $GPWPL - NMEA generic with only location and name. + * $PGRMW - Garmin, adds altitude, symbol, and comment + * to previously named waypoint. + * $PMGNWPL - Magellan, more complete for stationary objects. + * $PKWDWPL - Kenwood with APRS style symbol but missing comment. + * +* + * AvMap G5 notes: + * + * https://sites.google.com/site/kd7kuj/home/files?pli=1 + * https://sites.google.com/site/kd7kuj/home/files/AvMapMessaging040810.pdf?attredirects=0&d=1 + * + * It sends $GPGGA & $GPRMC with location. + * It understands generic $GPWPL and Kenwood $PKWDWPL. + * + * There are some proprietary $PAVP* used only for messaging. + * Messaging would be a separate project. + * + *--------------------------------------------------------------------*/ + + +void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symtab, char symbol, + float alt, float course, float speed, char *comment_in) +{ + char wname[12]; /* Waypoint name. Any , or * removed. */ + char slat[12]; /* DDMM.mmmm */ + char slat_ns[2]; /* N or S */ + char slong[12]; /* DDDMM.mmmm */ + char slong_ew[2]; /* E or W */ + char wcomment[256]; /* Comment. Any , or * removed. */ + char salt[12]; /* altitude as string, empty if unknown */ + char sspeed[12]; /* speed as string, empty if unknown */ + char scourse[12]; /* course as string, empty if unknown */ + char *p; + + char sentence[500]; + +#if DEBUG + text_color_set (DW_COLOR_DEBUG); + dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol); +#endif + + +/* + * We need to remove any , or * from name, symbol, or comment because they are field delimiters. + * Follow precedent of Geosat AvMap $PAVPMSG sentence and make the following substitutions: + * + * , -> | + * * -> ~ + * + * The system on the other end would need to change them back after extracting the + * fields delimited by , or *. + * We will deal with the symbol in the Kenwood section. + * Needs to be left intact for other icon/symbol conversions. + */ + + strlcpy (wname, name_in, sizeof(wname)); + for (p=wname; *p != '\0'; p++) { + if (*p == ',') *p = '|'; + if (*p == '*') *p = '~'; + } + + strlcpy (wcomment, comment_in, sizeof(wcomment)); + for (p=wcomment; *p != '\0'; p++) { + if (*p == ',') *p = '|'; + if (*p == '*') *p = '~'; + } + + +/* + * Convert numeric values to character form. + * G_UNKNOWN value will result in an empty string. + */ + + latitude_to_nmea (dlat, slat, slat_ns); + longitude_to_nmea (dlong, slong, slong_ew); + + + if (alt == G_UNKNOWN) { + strcpy (salt, ""); + } + else { + snprintf (salt, sizeof(salt), "%.1f", alt); + } + + if (speed == G_UNKNOWN) { + strcpy (sspeed, ""); + } + else { + snprintf (sspeed, sizeof(sspeed), "%.1f", speed); + } + + if (course == G_UNKNOWN) { + strcpy (scourse, ""); + } + else { + snprintf (scourse, sizeof(scourse), "%.1f", course); + } + + +/* + * NMEA Generic. + * + * Has only location and name. Rather disappointing. + * + * $GPWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,wname*99 + * + * Where, + * ddmm.mmmm,ns is latitude + * dddmm.mmmm,ew is longitude + * wname is the waypoint name + * *99 is checksum + */ + + if (s_waypoint_formats & WPT_FORMAT_NMEA_GENERIC) { + + snprintf (sentence, sizeof(sentence), "$GPWPL,%s,%s,%s,%s,%s", slat, slat_ns, slong, slong_ew, wname); + append_checksum (sentence); + send_sentence (sentence); + } + + +/* + * Garmin + * + * https://www8.garmin.com/support/pdf/NMEA_0183.pdf + * + * No location! Adds altitude, symbol, and comment to existing waypoint. + * So, we should always send the NMEA generic waypoint before this one. + * The init function should take care of that. + * + * $PGRMW,wname,alt,symbol,comment*99 + * + * Where, + * + * wname is waypoint name. Must match existing waypoint. + * alt is altitude in meters. + * symbol is symbol code. Hexadecimal up to FFFF. + * See Garmin Device Interface Specification + * 001-0063-00 for values of "symbol_type." + * comment is comment for the waypoint. + * *99 is checksum + */ + + if (s_waypoint_formats & WPT_FORMAT_GARMIN) { + + int i = symbol - ' '; + int grm_sym; /* Garmin symbol code. */ + + if (i >= 0 && i < SYMTAB_SIZE) { + if (symtab == '/') { + grm_sym = grm_primary_symtab[i]; + } + else { + grm_sym = grm_alternate_symtab[i]; + } + } + else { + grm_sym = sym_default; + } + + snprintf (sentence, sizeof(sentence), "$PGRMW,%s,%s,%04X,%s", wname, salt, grm_sym, wcomment); + append_checksum (sentence); + send_sentence (sentence); + } + + +/* + * Magellan + * + * http://www.gpsinformation.org/mag-proto-2-11.pdf Rev 2.11, Mar 2003, P/N 21-00091-000 + * http://gpsinformation.net/mag-proto.htm Rev 1.0, Aug 1999, P/N 21-00091-000 + * + * + * $PMGNWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,alt,unit,wname,comment,icon,xx*99 + * + * Where, + * ddmm.mmmm,ns is latitude + * dddmm.mmmm,ew is longitude + * alt is altitude + * unit is M for meters or F for feet + * wname is the waypoint name + * comment is message or comment + * icon is one or two letters for icon code + * xx is waypoint type which is optional, not well + * defined, and not used in their example + * so we won't use it. + * *99 is checksum + * + * Possible enhancement: If the "object report" has the kill option set, use $PMGNDWP + * to delete that specific waypoint. + */ + + if (s_waypoint_formats & WPT_FORMAT_MAGELLAN) { + + int i = symbol - ' '; + char sicon[3]; /* Magellan icon string. Currently 1 or 2 characters. */ + + if (i >= 0 && i < SYMTAB_SIZE) { + if (symtab == '/') { + strlcpy (sicon, mgn_primary_symtab[i], sizeof(sicon)); + } + else { + strlcpy (sicon, mgn_alternate_symtab[i], sizeof(sicon)); + } + } + else { + strlcpy (sicon, MGN_default, sizeof(sicon)); + } + + snprintf (sentence, sizeof(sentence), "$PMGNWPL,%s,%s,%s,%s,%s,M,%s,%s,%s", + slat, slat_ns, slong, slong_ew, salt, wname, wcomment, sicon); + append_checksum (sentence); + send_sentence (sentence); + } + + +/* + * Kenwood + * + * + * $PKWDWPL,hhmmss,v,ddmm.mm,ns,dddmm.mm,ew,speed,course,ddmmyy,alt,wname,ts*99 + * + * Where, + * hhmmss is time in UTC from the clock in the transceiver. + * + * This will be bogus if the clock was not set properly. + * It does not use the timestamp from a position + * report which could be useful. + * + * GPS Status A = active, V = void. + * It looks like this might be modeled after the GPS status values + * we see in $GPRMC. i.e. Does the transceiver know its location? + * I don't see how that information would be relevant in this context. + * I've observed this under various conditions (No GPS, GPS with/without + * fix) and it has always been "V." + * (There is some information out there indicating this field + * can contain "I" for invalid but I don't think that is accurate.) + * + * ddmm.mm,ns is latitude. N or S. + * dddmm.mm,ew is longitude. E or W. + * + * The D710 produces two fractional digits for minutes. + * This is the same resolution most often used + * in APRS packets. Any additional resolution offered by + * the compressed format or the DAO option is not conveyed here. + * We will provide greater resolution. + * + * speed is speed over ground, knots. + * course is course over ground, degrees. + * + * Empty if not available. + * + * ddmmyy is date. See comments for time. + * + * alt is altitude, meters above mean sea level. + * + * Empty if no altitude is available. + * + * wname is the waypoint name. For an Object Report, the id is the object name. + * For a position report, it is the call of the sending station. + * + * An Object name can contain any printable characters. + * What if object name contains , or * characters? + * Those are field delimiter characters and it would be unfortunate + * if they appeared in a NMEA sentence data field. + * + * If there is a comma in the name, such as "test,5" the D710A displays + * it fine but we end up with an extra field. + * + * $PKWDWPL,150803,V,4237.14,N,07120.83,W,,,190316,,test,5,/'*30 + * + * If the name contains an asterisk, it doesn't show up on the + * display and no waypoint sentence is generated. + * We will substitute these two characters following the AvMap precedent. + * + * $PKWDWPL,204714,V,4237.1400,N,07120.8300,W,,,200316,,test|5,/'*61 + * $PKWDWPL,204719,V,4237.1400,N,07120.8300,W,,,200316,,test~6,/'*6D + * + * ts are the table and symbol. + * + * What happens if the symbol is comma or asterisk? + * , Boy Scouts / Girl Scouts + * * SnowMobile / Snow + * + * the D710A just pushes them thru without checking. + * 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 + * + * We perform the usual substitution and the other end would + * need to change them back after extracting from NMEA sentence. + * + * $PKWDWPL,204704,V,4237.1400,N,07120.8300,W,,,200316,,test3,/|*41 + * $PKWDWPL,204709,V,4237.1400,N,07120.8300,W,,,200316,,test4,/~*49 + * + * + * *99 is checksum + * + * Oddly, there is no place for comment. + */ + + + + if (s_waypoint_formats & WPT_FORMAT_KENWOOD) { + + time_t now; + struct tm tm; + char stime[8]; + char sdate[8]; + char ken_sym; /* APRS symbol with , or * substituted. */ + + now = time(NULL); + (void)gmtime_r (&now, &tm); + strftime (stime, sizeof(stime), "%H%M%S", &tm); + strftime (sdate, sizeof(sdate), "%d%m%y", &tm); + + // A symbol code of , or * would not be good because + // they are field delimiters for NMEA sentences. + + // The AvMap G5 to Kenwood protocol description performs a substitution + // for these characters that appear in message text. + // , -> | + // * -> ~ + + // Those two are listed as "TNC Stream Switch" and are not used for symbols. + // It might be reasonable assumption that this same substitution might be + // used for the symbol code. + + if (symbol == ',') ken_sym = '|'; + else if (symbol == '*') ken_sym = '~'; + else ken_sym = symbol; + + snprintf (sentence, sizeof(sentence), "$PKWDWPL,%s,V,%s,%s,%s,%s,%s,%s,%s,%s,%s,%c%c", + stime, slat, slat_ns, slong, slong_ew, + sspeed, scourse, sdate, salt, wname, symtab, ken_sym); + append_checksum (sentence); + send_sentence (sentence); + } + + +/* + * One application recognizes these. Not implemented at this time. + * + * $GPTLL,01,ddmm.mmmm,ns,dddmm.mmmm,ew,tname,000000.00,T,R*99 + * + * Where, + * ddmm.mmmm,ns is latitude + * dddmm.mmmm,ew is longitude + * tname is the target name + * 000000.00 is timestamp ??? + * T is target status (S for need help) + * R is reference target ??? + * *99 is checksum + * + * + * $GPTXT,01,01,tname,message*99 + * + * Where, + * + * 01 is total number of messages in transmission + * 01 is message number in this transmission + * tname is target name. Should match name in WPL or TTL. + * message is the message. + * *99 is checksum + * + */ + + +} /* end waypoint_send_sentence */ + + +/* + * Append CR LF and send it. + */ + +static void send_sentence (char *sent) +{ + + char final[256]; + + + if (s_waypoint_port_fd == MYFDERROR) { + return; + } + + if (s_waypoint_debug) { + text_color_set(DW_COLOR_XMIT); + dw_printf ("%s\n", sent); + } + + strlcpy (final, sent, sizeof(final)); + strlcat (final, "\r\n", sizeof(final)); + + serial_port_write (s_waypoint_port_fd, final, strlen(final)); + +} /* send_sentence */ + + + +void waypoint_term (void) +{ + + if (s_waypoint_port_fd != MYFDERROR) { + //serial_port_close (s_waypoint_port_fd); + s_waypoint_port_fd = MYFDERROR; + } +} + + +/* end waypoint.c */ diff --git a/waypoint.h b/waypoint.h new file mode 100644 index 00000000..0f5ef3af --- /dev/null +++ b/waypoint.h @@ -0,0 +1,22 @@ + +/* + * Name: waypoint.h + */ + + +#include "ax25_pad.h" /* for packet_t */ + +#include "config.h" /* for struct misc_config_s */ + + +void waypoint_init (struct misc_config_s *misc_config); + +void waypoint_set_debug (int n); + +void waypoint_send_sentence (char *wname_in, double dlat, double dlong, char symtab, char symbol, + float alt, float course, float speed, char *comment_in); + +void waypoint_term (); + + +/* end waypoint.h */ diff --git a/xid.c b/xid.c index 7580c942..90ce4378 100644 --- a/xid.c +++ b/xid.c @@ -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 @@ -23,42 +23,36 @@ * * Module: xid.c * - * Purpose: .... + * Purpose: Encode and decode the info field of XID frames. * - * Description: + * Description: If we originate the connection, and the other end is + * capable of AX.25 version 2.2, * - * References: ... + * - We send an XID command frame with our capabilities. + * - the other sends back an XID response, possibly + * reducing some values to be acceptable there. + * - Both ends use the values in that response. * - * + * If the other end originates the connection, + * + * - It sends XID command frame with its capabilities. + * - We might decrease some of them to be acceptable. + * - Send XID response. + * - Both ends use values in my response. + * + * References: AX.25 Protocol Spec, sections 4.3.3.7 & 6.3.2. * *---------------------------------------------------------------*/ +#include "direwolf.h" #include #include #include +#include #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, selective_reject, selective_reject_reject } 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; - - int ack_timer; /* "T1" in mSec. */ - - int retries; /* Inconsistently refered to as "N1" or "N2" */ -}; +#include "xid.h" @@ -90,44 +84,76 @@ 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. * - * Description: + * 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. + * + * Description: 6.3.2 "The receipt of an XID response from the other station + * establishes that both stations are using AX.25 version + * 2.2 or higher and enables the use of the segmenter/reassembler + * and selective reject." * *--------------------------------------------------------------------*/ -//Returns: 1 for mostly successful (with possible error messages), 0 for failure. - - -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]; + + + strlcpy (desc, "", desc_size); + +// What should we do when some fields are missing? + +// The AX.25 v2.2 protocol spec says, for most of these, +// "If this field is not present, the current values are retained." + +// We set the numeric values to our usual G_UNKNOWN to mean undefined and let the caller deal with it. +// rej and modulo are enum so we can't use G_UNKNOWN there. + + result->full_duplex = G_UNKNOWN; + result->rej = unknown_reject; + result->modulo = modulo_unknown; + result->i_field_length_rx = G_UNKNOWN; + result->window_size_rx = G_UNKNOWN; + result->ack_timer = G_UNKNOWN; + result->retries = G_UNKNOWN; - result->full_duplex = 0; - result->rej = selective_reject; - result->modulo = modulo_8; - result->i_field_length_rx = 256; - result->window_size = result->modulo == modulo_128 ? 32 : 4; - result->ack_timer = 3000; - result->retries = 10; +/* 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, %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++; if (*p != GI_Group_Identifier) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("XID error: Second byte of info field should be Group Indicator, %d.\n", GI_Group_Identifier); return 0; } p++; @@ -141,6 +167,11 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result) pind = *p++; plen = *p++; // should have sanity checking + if (plen < 1 || plen > 4) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("XID error: Length ????? TODO ???? %d.\n", plen); + return (1); // got this far. + } pval = 0; for (j=0; jfull_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); @@ -221,38 +260,52 @@ 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 is not a whole number of bytes.\n"); + dw_printf ("XID error: I Field Length Rx, %d, is not a whole number of bytes.\n", pval); } break; case PI_Window_Size_Rx: - result->window_size = pval; + result->window_size_rx = pval; -// TODO must be 1-7 for modulo 8 or 1-127 for modulo 128; + snprintf (stemp, sizeof(stemp), "Window-Size-Rx=%d ", result->window_size_rx); + strlcat (desc, stemp, desc_size); -// if (pval & 0x7) { -// text_color_set (DW_COLOR_ERROR); -// dw_printf ("XID error: Window Size Rx is not in range of 1 thru ???\n"); -// } + 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 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: - break; + break; // Ignore anything we don't recognize. } } @@ -261,7 +314,7 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result) dw_printf ("XID error: Frame / Group Length mismatch.\n"); } - return 1; + return (1); } /* end xid_parse */ @@ -270,46 +323,113 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result) * * Name: xid_encode * - * Purpose: ... + * Purpose: Encode the information part of an XID frame. + * + * Inputs: param-> + * full_duplex - As command, am I capable of full duplex operation? + * When a response, are we both? + * 0 = half duplex. + * 1 = full duplex. + * + * rej - One of: implicit_reject, selective_reject, selective_reject_reject. + * As command, what am I capable of processing? + * As response, take minimum of + * + * + * modulo - 8 or 128. + * + * i_field_length_rx - Maximum number of bytes I can handle in info part. + * Default is 256. + * Up to 8191 will fit into the field. + * Use G_UNKNOWN to omit this. * - * Inputs: param - + * window_size_rx - Maximum window size ("k") that I can handle. + * Defaults are are 4 for modulo 8 and 32 for modulo 128. + * + * ack_timer - Acknowledge timer in milliseconds. + * *** describe meaning. *** + * Default is 3000. + * Use G_UNKNOWN to omit this. + * + * retries - Allows negotiation of retries. + * Default is 10. + * Use G_UNKNOWN to omit this. * * Outputs: info - Information part of XID frame. * Does not include the control byte. + * 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. * - * Returns: Number of bytes in the info part. + * 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 + * AX.25 prior to version 2.2 respond to an XID command frame with a FRMR response + * frame. The TNC receiving the FRMR uses a default set of parameters compatible + * with previous versions of AX.25." * - * Description: + * "This version of AX.25 implements the negotiation or notification of six AX.25 + * parameters. Notification simply tells the distant TNC some limit that cannot be exceeded. + * The distant TNC can choose to use the limit or some other value that is within the + * limits. Notification is used with the Window Size Receive (k) and Information + * Field Length Receive (N1) parameters. Negotiation involves both TNCs choosing a + * value that is mutually acceptable. The XID command frame contains a set of values + * acceptable to the originating TNC. The distant TNC chooses to accept the values + * offered, or other acceptable values, and places these values in the XID response. + * 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; @@ -318,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 == unknown_reject) x |= PV_HDLC_Optional_Functions_REJ_cmd_resp; if (param->rej == selective_reject || param->rej == selective_reject_reject) @@ -326,34 +446,53 @@ 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 modulo_8 and modulo_unknown x |= PV_HDLC_Optional_Functions_Modulo_8; *p++ = (x >> 16) & 0xff; *p++ = (x >> 8) & 0xff; *p++ = x & 0xff; - *p++ = PI_I_Field_Length_Rx; - *p++ = 2; - x = param->i_field_length_rx * 8; - *p++ = (x >> 8) & 0xff; - *p++ = x & 0xff; +// The rest are skipped if undefined values. - *p++ = PI_Window_Size_Rx; - *p++ = 1; - *p++ = param->window_size; +// "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. - *p++ = PI_Ack_Timer; - *p++ = 2; - *p++ = param->ack_timer >> 8; - *p++ = param->ack_timer & 0xff; + if (param->i_field_length_rx != G_UNKNOWN) { + *p++ = PI_I_Field_Length_Rx; + *p++ = 2; + x = param->i_field_length_rx * 8; + *p++ = (x >> 8) & 0xff; + *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; + } - *p++ = PI_Retries; - *p++ = 1; - *p++ = param->retries; +// "Ack Timer" milliseconds. We could handle up to 65535 here. + + if (param->ack_timer != G_UNKNOWN) { + *p++ = PI_Ack_Timer; + *p++ = 2; + *p++ = (param->ack_timer >> 8) & 0xff; + *p++ = param->ack_timer & 0xff; + } + +// "Retries." + + if (param->retries != G_UNKNOWN) { + *p++ = PI_Retries; + *p++ = 1; + *p++ = param->retries; + } len = p - info; - assert (len == 27); + return (len); } /* end xid_encode */ @@ -368,7 +507,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info) * * Description: Run with: * - * gcc -DTEST -g xid.c textcolor.c ; ./a + * gcc -DXIDTEST -g xid.c textcolor.o && ./a * * Result should be: * @@ -379,7 +518,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info) *--------------------------------------------------------------------*/ -#if TEST +#if XIDTEST /* From Figure 4.6. Typical XID frame, from AX.25 protocol spec, v. 2.2 */ /* This is the info part after a control byte of 0xAF. */ @@ -417,19 +556,24 @@ static unsigned char example[27] = { /* PV */ 0x00, /* */ /* PI */ 0x0A, /* Parameter Indicator - Retries (N1) */ /* PL */ 0x01, /* Parameter Length */ - /* PV */ 0x03 /* Parameter Variable - 3 ret */ + /* PV */ 0x03 /* Parameter Variable - 3 retries */ }; 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), ¶m); + n = xid_parse (example, sizeof(example), ¶m, desc, sizeof(desc)); + + text_color_set (DW_COLOR_DEBUG); + dw_printf ("%s\n", desc); text_color_set (DW_COLOR_ERROR); @@ -438,7 +582,7 @@ int main (int argc, char *argv[]) { assert (param.rej == selective_reject_reject); assert (param.modulo == modulo_128); assert (param.i_field_length_rx == 128); - assert (param.window_size == 2); + assert (param.window_size_rx == 2); assert (param.ack_timer == 4096); assert (param.retries == 3); @@ -461,43 +605,99 @@ int main (int argc, char *argv[]) { param.rej = implicit_reject; param.modulo = modulo_8; param.i_field_length_rx = 2048; - param.window_size = 3; - param.ack_timer = 3000; - param.retries = 10; + param.window_size_rx = 3; + param.ack_timer = 1234; + param.retries = 12; n = xid_encode (¶m, info); - n = xid_parse (info, n, ¶m2); + n = xid_parse (info, n, ¶m2, 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 == 3); - assert (param2.ack_timer == 3000); - assert (param2.retries == 10); + assert (param2.window_size_rx == 3); + 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.window_size = 4; - param.ack_timer = 3000; - param.retries = 10; + param.i_field_length_rx = 61; + param.window_size_rx = 4; + param.ack_timer = 5555; + param.retries = 9; n = xid_encode (¶m, info); - n = xid_parse (info, n, ¶m2); + n = xid_parse (info, n, ¶m2, 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 == 61); + assert (param2.window_size_rx == 4); + 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 (¶m, info); + n = xid_parse (info, n, ¶m2, 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.window_size == 4); - assert (param2.ack_timer == 3000); - assert (param2.retries == 10); + 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, ¶m2, 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 == unknown_reject); + assert (param2.modulo == modulo_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_INFO); + text_color_set (DW_COLOR_REC); dw_printf ("XID test: Success.\n"); exit (0); diff --git a/xid.h b/xid.h new file mode 100644 index 00000000..c94003e7 --- /dev/null +++ b/xid.h @@ -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 {unknown_reject=0, 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); \ No newline at end of file diff --git a/xmit.c b/xmit.c index 21d4b67d..ee82ee18 100644 --- a/xmit.c +++ b/xmit.c @@ -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 @@ -51,6 +51,8 @@ * *---------------------------------------------------------------*/ +#include "direwolf.h" + #include #include #include @@ -70,6 +72,8 @@ #include "ptt.h" #include "dtime_now.h" #include "morse.h" +#include "dtmf.h" +#include "xid.h" @@ -105,11 +109,16 @@ 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)]) #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); @@ -128,10 +137,12 @@ 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); +static void xmit_dtmf (int c, packet_t pp, int speed); /*------------------------------------------------------------------- @@ -344,6 +355,67 @@ 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_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_DTMF, 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); + } + + 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. */ + + 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 @@ -365,6 +437,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 @@ -380,6 +455,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, DTMF, and APRS digipeated frames + * are all sent separately. The rest can be bundled. + * *--------------------------------------------------------------------*/ #if __WIN32__ @@ -388,119 +468,135 @@ 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 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; + + 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; + + case FLAVOR_APRS_NEW: + case FLAVOR_OTHER: + default: + xmit_ax25_frames (chan, prio, pp, 256); + break; + } - xmit_morse (c, pp, wpm); - } - else { - xmit_ax25_frames (c, p, pp); - } + // Corresponding lock is in wait_for_clear_channel. - dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(c)])); - } - else { + 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. */ @@ -515,28 +611,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. @@ -556,21 +655,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. */ @@ -578,8 +683,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. @@ -591,60 +695,37 @@ 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 : */ - 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. */ time_ptt = dtime_now (); +// TODO: This was written assuming bits/sec = baud. +// Does it is need to be scaled differently for PSK? + #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); @@ -652,68 +733,85 @@ 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. + */ + + int done = 0; + while (numframe < max_bundle && ! done) { + +/* + * Peek at what is available. + * Don't remove from queue yet because it might not be eligible. */ + 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_DTMF: + case FLAVOR_APRS_DIGI: + default: + done = 1; // not eligible for bundling. + break; - while (numframe < maxframe && tq_count (c,p) > 0) { + case FLAVOR_APRS_NEW: + case FLAVOR_OTHER: - pp = tq_remove (c, p); + pp = tq_remove (chan, prio); #if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, 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"); - } -/* - * Transmit the frame. - */ - flen = ax25_pack (pp, fbuf); - assert (flen >= 1 && flen <= sizeof(fbuf)); - nb = hdlc_send_frame (c, fbuf, flen); - num_bits += nb; - numframe++; + 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); + 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); + 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. @@ -722,7 +820,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. @@ -764,12 +862,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, ¶m, 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 <= (int)(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 @@ -792,8 +1001,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; @@ -802,6 +1009,8 @@ static void xmit_speech (int c, packet_t pp) */ info_len = ax25_get_info (pp, &pinfo); + (void)info_len; + text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.speech] \"%s\"\n", c, pinfo); @@ -872,6 +1081,7 @@ int xmit_speak_it (char *script, int c, char *orig_msg) dw_printf ("Failed to run text-to-speech script, %s\n", script); ignore = getcwd (cwd, sizeof(cwd)); + (void)ignore; strlcpy (path, getenv("PATH"), sizeof(path)); dw_printf ("CWD = %s\n", cwd); @@ -900,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. * *--------------------------------------------------------------------*/ @@ -907,19 +1118,34 @@ 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; + 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.morse] \"%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 = morse_send (c, (char*)pinfo, wpm, MAXX(xmit_txdelay[c] * 10, 300), MAXX(xmit_txtail[c] * 10, 250)); - morse_send (c, (char*)pinfo, wpm, xmit_txdelay[c] * 10, xmit_txtail[c] * 10); + // 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); @@ -927,22 +1153,80 @@ static void xmit_morse (int c, packet_t pp, int wpm) } /* end xmit_morse */ + /*------------------------------------------------------------------- * - * Name: wait_for_clear_channel + * Name: xmit_dtmf * - * Purpose: Wait for the radio channel to be clear and any - * additional time for collision avoidance. + * 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 * - * Inputs: channel - Radio channel number. + * Purpose: Wait for the radio channel to be clear and any + * additional time for collision avoidance. * - * 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. @@ -951,14 +1235,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. @@ -989,17 +1271,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)) { @@ -1007,41 +1285,52 @@ static int wait_for_clear_channel (int channel, int nowait, int slottime, int pe } } -//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) { - - while (1) { - - SLEEP_MS (slottime * 10); +/* + * 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; - if (hdlc_rec_data_detect_any(channel)) { - goto start_over_again; - } + SLEEP_MS (slottime * 10); - 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. + */ + +// TODO: review this. - while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(channel)]))) { + 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)) {