diff --git a/CHANGES.md b/CHANGES.md
index e1a7bdc..9df5e58 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,20 @@
# Revision History #
+
+
+----------
+
+## Version 1.4 -- Development snapshot D -- November 2016 ##
+
+This is a snapshot at some semi-stable point in the development of the next version. It is not well tested. New features might be incomplete, poorly documented, and subject to change.
+
+
+### New Features: ###
+
+- AX.25 v2.2 connected mode. See chapter 10 of User Guide for details.
+
+
----------
## Version 1.4 -- Development snapshot C -- June 2016 ##
diff --git a/Makefile.linux b/Makefile.linux
index 40498e3..8564f31 100644
--- a/Makefile.linux
+++ b/Makefile.linux
@@ -230,12 +230,12 @@ z := $(notdir ${CURDIR})
direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \
hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \
- fcs_calc.o ax25_pad.o \
+ fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
- gen_tone.o audio.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \
+ gen_tone.o audio.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \
ptt.o beacon.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
- dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o \
+ dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o ax25_link.o \
misc.a geotranz.a
$(CC) -o $@ $^ $(LDFLAGS)
ifneq ($(enable_gpsd),)
@@ -588,7 +588,7 @@ install-rpi : dw-start.sh
# Combine some unit tests into a single regression sanity check.
-check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
+check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates?
@@ -708,6 +708,15 @@ pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o misc.a
rm pad2test
+# Unit Test for XID frame encode/decode.
+
+.PHONY: xidtest
+xidtest : xid.c textcolor.o misc.a
+ $(CC) $(CFLAGS) -DXIDTEST -o $@ $^ $(LDFLAGS)
+ ./xidtest
+ rm xidtest
+
+
# ----------------------------- Manual tests and experiments ---------------------------
diff --git a/Makefile.macosx b/Makefile.macosx
index c77c9cf..2b2c77f 100644
--- a/Makefile.macosx
+++ b/Makefile.macosx
@@ -221,14 +221,14 @@ z := $(notdir ${CURDIR})
# Main application.
-direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beacon.o \
+direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_link.o ax25_pad.o ax25_pad2.o beacon.o \
config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o demod_psk.o \
- demod.o digipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \
+ demod.o digipeater.o cdigipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \
encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \
geotranz.a hdlc_rec.o hdlc_rec2.o hdlc_send.o igate.o kiss_frame.o \
kiss.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \
waypoint.o serial_port.o pfilter.o ptt.o rdq.o recv.o rrbb.o server.o \
- symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xmit.o \
+ symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xid.o xmit.o \
dwgps.o dwgpsnmea.o mheard.o
$(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm
diff --git a/Makefile.win b/Makefile.win
index 24fa6ff..156e291 100644
--- a/Makefile.win
+++ b/Makefile.win
@@ -16,7 +16,7 @@
#
-all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc
+all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest
# People say we need -mthreads option for threads to work properly.
@@ -39,8 +39,6 @@ CFLAGS += -g
#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
-# TODO: Development in progress. Don't try using yet.
-#CFLAGS += -DNEW14
#
# Let's see impact of various optimization levels.
@@ -86,16 +84,14 @@ demod_afsk.o : fsk_demod_state.h
demod_psk.o : fsk_demod_state.h
-# later ax25_link.o
-
direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \
hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \
- fcs_calc.o ax25_pad.o ax25_pad2.o \
+ fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
- gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o \
+ gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o \
ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
- dwgps.o dwgpsnmea.o dtime_now.o mheard.o \
+ dwgps.o dwgpsnmea.o dtime_now.o mheard.o ax25_link.o \
dw-icon.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
@@ -260,7 +256,7 @@ strlcat.o : misc/strlcat.c
# Combine some unit tests into a single regression sanity check.
-check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
+check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates?
# Verify that single bit fixup increases the count.
@@ -317,6 +313,7 @@ atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_psk.c demod_9600.c
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.c \
symbols.c tt_text.c textcolor.c telemetry.c dtime_now.o \
+ decode_aprs.o log.o \
misc.a regex.a
echo " " > tune.h
$(CC) $(CFLAGS) -o $@ $^
@@ -412,10 +409,23 @@ pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o regex.a misc.a
./pad2test
rm pad2test.exe
+# Unit Test for XID frame encode/decode.
+
+.PHONY: xidtest
+xidtest : xid.c textcolor.o misc.a
+ $(CC) $(CFLAGS) -DXIDTEST -o $@ $^
+ ./xidtest
+ rm xidtest.exe
+
+
# ------------------------------ Other manual testing & experimenting -------------------------------
+tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a
+ $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
+
+
# For tweaking the demodulator.
demod.o : tune.h
diff --git a/README.md b/README.md
index dc70652..3d0cb11 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,8 @@ Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf
- Includes separate raw packet decoder, decode_aprs.
+- AX.25 v2.2 connected mode. (New in version 1.4.)
+
- Open source so you can see how it works and make your own modifications.
- Runs in 3 different environments:
@@ -95,4 +97,4 @@ Here are some good places to share information:
- [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/)
-
+The github "issues" section is for reporting software defects and enhancement requests. It is NOT a place to ask questions or have general discussions. Please use one of the locations above.
diff --git a/atest.c b/atest.c
index a97ef22..c78094a 100644
--- a/atest.c
+++ b/atest.c
@@ -743,6 +743,24 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
ax25_safe_print ((char *)pinfo, info_len, 0);
dw_printf ("\n");
+#if 1 // temp experiment TODO: remove this.
+
+#include "decode_aprs.h"
+#include "log.h"
+
+ if (ax25_is_aprs(pp)) {
+
+ decode_aprs_t A;
+
+ decode_aprs (&A, pp, 0);
+
+ // Temp experiment to see how different systems set the RR bits in the source and destination.
+ // log_rr_bits (&A, pp);
+
+ }
+#endif
+
+
ax25_delete (pp);
} /* end fake dlq_append */
diff --git a/audio.h b/audio.h
index 746d96d..b50c05f 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. */
@@ -166,18 +174,20 @@ struct audio_s {
int passall; /* Allow thru even with bad CRC. */
+
/* Additional properties for transmit. */
/* Originally we had control outputs only for PTT. */
/* In version 1.2, we generalize this to allow others such as DCD. */
+ /* In version 1.4 we add CON for connected to another station. */
/* Index following structure by one of these: */
#define OCTYPE_PTT 0
#define OCTYPE_DCD 1
-#define OCTYPE_FUTURE 2
+#define OCTYPE_CON 2
-#define NUM_OCTYPES 4 /* number of values above */
+#define NUM_OCTYPES 4 /* number of values above. WHY IS THIS NOT 3? */
struct {
diff --git a/ax25_link.c b/ax25_link.c
new file mode 100644
index 0000000..b15bdd9
--- /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 > 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 > 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 >= 0 && 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 == G_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 == G_UNKNOWN) {
+ 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 != G_UNKNOWN) {
+ S->srej_enabled = param->rej >= selective_reject;
+ }
+
+ if (param->modulo != G_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 0000000..060b6ab
--- /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 2b15428..8b34051 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)
@@ -165,8 +182,8 @@ char *strtok_r(char *str, const char *delim, char **saveptr);
#endif
-#include "ax25_pad.h"
#include "textcolor.h"
+#include "ax25_pad.h"
#include "fcs_calc.h"
/*
@@ -236,7 +253,13 @@ packet_t ax25_new (void)
/*
* check for memory leak.
*/
- if (new_count > delete_count + 100) {
+
+// version 1.4 push up the threshold. We could have considerably more with connected mode.
+
+ //if (new_count > delete_count + 100) {
+ if (new_count > delete_count + 256) {
+
+
text_color_set(DW_COLOR_ERROR);
dw_printf ("Report to WB2OSZ - Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count);
#if AX25MEMDEBUG
@@ -434,7 +457,7 @@ packet_t ax25_from_text (char *monitor, int strict)
this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK;
this_p->frame_data[14] = AX25_UI_FRAME;
- this_p->frame_data[15] = AX25_NO_LAYER_3;
+ this_p->frame_data[15] = AX25_PID_NO_LAYER_3;
this_p->frame_len = 7 + 7 + 1 + 1;
this_p->num_addr = (-1);
@@ -1517,6 +1540,39 @@ int ax25_get_first_not_repeated(packet_t this_p)
}
+/*------------------------------------------------------------------------------
+ *
+ * Name: ax25_get_rr
+ *
+ * Purpose: Return the two reserved "RR" bits in the specified address field.
+ *
+ * Inputs: pp - Packet object.
+ *
+ * n - Index of address. Use the symbols
+ * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ * Returns: 0, 1, 2, or 3.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_rr (packet_t this_p, int n)
+{
+
+ assert (this_p->magic1 == MAGIC);
+ assert (this_p->magic2 == MAGIC);
+ assert (n >= 0 && n < this_p->num_addr);
+
+ if (n >= 0 && n < this_p->num_addr) {
+ return ((this_p->frame_data[n*7+6] & SSID_RR_MASK) >> SSID_RR_SHIFT);
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Internal error: ax25_get_rr(%d), num_addr=%d\n", n, this_p->num_addr);
+ return (0);
+ }
+}
+
+
/*------------------------------------------------------------------------------
*
* Name: ax25_get_info
@@ -1678,6 +1734,25 @@ double ax25_get_release_time (packet_t this_p)
}
+/*------------------------------------------------------------------------------
+ *
+ * Name: ax25_set_modulo
+ *
+ * Purpose: Set modulo value for I and S frame sequence numbers.
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_set_modulo (packet_t this_p, int modulo)
+{
+ assert (this_p->magic1 == MAGIC);
+ assert (this_p->magic2 == MAGIC);
+
+ this_p->modulo = modulo;
+}
+
+
+
+
/*------------------------------------------------------------------
*
@@ -1793,12 +1868,9 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
*
* Inputs: this_p - pointer to packet object.
*
- * modulo - We often need to know this because context is // TODO: remove this - return cr instead.
- * required to know if control is 1 or 2 bytes.
- *
* Outputs: desc - Text description such as "I frame" or
* "U frame SABME".
- * Supply 16 bytes to be safe.
+ * Supply 40 bytes to be safe.
*
* cr - Command or response?
*
@@ -1813,13 +1885,14 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
*------------------------------------------------------------------*/
// TODO: need someway to ensure caller allocated enough space.
-#define DESC_SIZ 32
+// Should pass in as parameter.
+#define DESC_SIZ 40
ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns)
{
int c; // U frames are always one control byte.
- int c2; // I & S frames can have second Control byte.
+ int c2 = 0; // I & S frames can have second Control byte.
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
@@ -1835,6 +1908,41 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, in
strlcpy (desc, "Not AX.25", DESC_SIZ);
return (frame_not_AX25);
}
+
+/*
+ * TERRIBLE HACK :-( for display purposes.
+ *
+ * I and S frames can have 1 or 2 control bytes but there is
+ * no good way to determine this without dipping into the data
+ * link state machine. Can we guess?
+ *
+ * S frames have no protocol id or information so if there is one
+ * more byte beyond the control field, we could assume there are
+ * two control bytes.
+ *
+ * For I frames, the protocol id will usually be 0xf0. If we find
+ * that as the first byte of the information field, it is probably
+ * the pid and not part of the information. Ditto for segments 0x08.
+ * Not fool proof but good enough for troubleshooting text out.
+ *
+ * If we have a link to the peer station, this will be set properly
+ * before it needs to be used for other reasons.
+ *
+ * Setting one of the RR bits (find reference!) is sounding better and better.
+ * It's in common usage so I should lobby to get that in the official protocol spec.
+ */
+
+ if (this_p->modulo == 0 && (c & 3) == 1 && ax25_get_c2(this_p) != -1) {
+ this_p->modulo = modulo_128;
+ }
+ else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0xF0) {
+ this_p->modulo = modulo_128;
+ }
+ else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0x08) { // same for segments
+ this_p->modulo = modulo_128;
+ }
+
+
if (this_p->modulo == modulo_128) {
c2 = ax25_get_c2 (this_p);
}
@@ -1868,7 +1976,9 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, in
*pf = (c >> 4) & 1;
*nr = (c >> 5) & 7;
}
- snprintf (desc, DESC_SIZ, "I %s, n(s)= %d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf);
+
+ //snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf);
+ snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d, pid=0x%02x", cr_text, *ns, *nr, pf_text, *pf, ax25_get_pid(this_p));
return (frame_type_I);
}
else if ((c & 2) == 0) {
@@ -1960,6 +2070,7 @@ static void hex_dump (unsigned char *p, int len)
}
/* Text description of control octet. */
+// FIXME: this is wrong. It doesn't handle modulo 128.
// TODO: use ax25_frame_type() instead.
@@ -2108,10 +2219,12 @@ int ax25_is_aprs (packet_t this_p)
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
+ if (this_p->frame_len == 0) return(0);
+
ctrl = ax25_get_control(this_p);
pid = ax25_get_pid(this_p);
- is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_NO_LAYER_3;
+ is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_PID_NO_LAYER_3;
#if 0
text_color_set(DW_COLOR_ERROR);
@@ -2120,6 +2233,41 @@ int ax25_is_aprs (packet_t this_p)
return (is_aprs);
}
+
+/*------------------------------------------------------------------
+ *
+ * Function: ax25_is_null_frame
+ *
+ * Purpose: Is this packet structure empty?
+ *
+ * Inputs: this_p - pointer to packet object.
+ *
+ * Returns: True if frame data length is 0.
+ *
+ * Description: This is used when we want to wake up the
+ * transmit queue processing thread but don't
+ * want to transmit a frame.
+ *
+ *------------------------------------------------------------------*/
+
+
+int ax25_is_null_frame (packet_t this_p)
+{
+ int is_null;
+
+ assert (this_p->magic1 == MAGIC);
+ assert (this_p->magic2 == MAGIC);
+
+ is_null = this_p->frame_len == 0;
+
+#if 0
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("ax25_is_null_frame(): is_null=%d\n", is_null);
+#endif
+ return (is_null);
+}
+
+
/*------------------------------------------------------------------
*
* Function: ax25_get_control
@@ -2140,6 +2288,8 @@ int ax25_get_control (packet_t this_p)
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
+ if (this_p->frame_len == 0) return(-1);
+
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_control_offset(this_p)]);
}
@@ -2151,10 +2301,19 @@ int ax25_get_c2 (packet_t this_p)
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
+ if (this_p->frame_len == 0) return(-1);
+
if (this_p->num_addr >= 2) {
- return (this_p->frame_data[ax25_get_control_offset(this_p)+1]);
+ int offset2 = ax25_get_control_offset(this_p)+1;
+
+ if (offset2 < this_p->frame_len) {
+ return (this_p->frame_data[offset2]);
+ }
+ else {
+ return (-1); /* attempt to go beyond the end of frame. */
+ }
}
- return (-1);
+ return (-1); /* not AX.25 */
}
@@ -2184,6 +2343,8 @@ int ax25_get_pid (packet_t this_p)
// TODO: handle 2 control byte case.
// TODO: sanity check: is it I or UI frame?
+ if (this_p->frame_len == 0) return(-1);
+
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
}
@@ -2372,7 +2533,8 @@ void ax25_safe_print (char *pstr, int len, int ascii_only)
if (len > MAXSAFE)
len = MAXSAFE;
- while (len > 0 && *pstr != '\0')
+ //while (len > 0 && *pstr != '\0')
+ while (len > 0)
{
ch = *((unsigned char *)pstr);
diff --git a/ax25_pad.h b/ax25_pad.h
index 2426e56..075ac2a 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. */
@@ -159,9 +161,11 @@ typedef struct packet_s *packet_t;
typedef enum cmdres_e { cr_00 = 2, cr_cmd = 1, cr_res = 0, cr_11 = 3 } cmdres_t;
+extern packet_t ax25_new (void);
+
+
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
-extern packet_t ax25_new (void);
/*
* APRS always has one control octet of 0x03 but the more
@@ -169,6 +173,8 @@ extern packet_t ax25_new (void);
* whether "modulo 128 operation" is in effect.
*/
+//#define DEBUGX 1
+
static inline int ax25_get_control_offset (packet_t this_p)
{
return (this_p->num_addr*7);
@@ -176,7 +182,29 @@ static inline int ax25_get_control_offset (packet_t this_p)
static inline int ax25_get_num_control (packet_t this_p)
{
- return (this_p->modulo == 128 ? 2 : 1);
+ int c;
+
+ c = this_p->frame_data[ax25_get_control_offset(this_p)];
+
+ if ( (c & 0x01) == 0 ) { /* I xxxx xxx0 */
+#if DEBUGX
+ dw_printf ("ax25_get_num_control, %02x is I frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
+#endif
+ return ((this_p->modulo == 128) ? 2 : 1);
+ }
+
+ if ( (c & 0x03) == 1 ) { /* S xxxx xx01 */
+#if DEBUGX
+ dw_printf ("ax25_get_num_control, %02x is S frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
+#endif
+ return ((this_p->modulo == 128) ? 2 : 1);
+ }
+
+#if DEBUGX
+ dw_printf ("ax25_get_num_control, %02x is U frame, always returns 1.\n", c);
+#endif
+
+ return (1); /* U xxxx xx11 */
}
@@ -202,11 +230,17 @@ static int ax25_get_num_pid (packet_t this_p)
c == 0x03 || c == 0x13) { /* UI 000x 0011 */
pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
- if (pid == 0xff) {
+#if DEBUGX
+ dw_printf ("ax25_get_num_pid, %02x is I or UI frame, pid = %02x, returns %d\n", c, pid, (pid==AX25_PID_ESCAPE_CHARACTER) ? 2 : 1);
+#endif
+ if (pid == AX25_PID_ESCAPE_CHARACTER) {
return (2); /* pid 1111 1111 means another follows. */
}
return (1);
}
+#if DEBUGX
+ dw_printf ("ax25_get_num_pid, %02x is neither I nor UI frame, returns 0\n", c);
+#endif
return (0);
}
@@ -225,7 +259,11 @@ static int ax25_get_num_pid (packet_t this_p)
static inline int ax25_get_info_offset (packet_t this_p)
{
- return (ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p));
+ int offset = ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p);
+#if DEBUGX
+ dw_printf ("ax25_get_info_offset, returns %d\n", offset);
+#endif
+ return (offset);
}
static inline int ax25_get_num_info (packet_t this_p)
@@ -249,7 +287,7 @@ typedef enum ax25_modulo_e { modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
typedef enum ax25_frame_type_e {
- frame_type_I, // Information
+ frame_type_I = 0, // Information
frame_type_S_RR, // Receive Ready - System Ready To Receive
frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full
@@ -268,6 +306,8 @@ typedef enum ax25_frame_type_e {
frame_type_U, // other Unnumbered, not used by AX.25.
frame_not_AX25 // Could not get control byte from frame.
+ // This must be last because value plus 1 is
+ // for the size of an array.
} ax25_frame_type_t;
@@ -354,6 +394,8 @@ extern int ax25_get_heard(packet_t this_p);
extern int ax25_get_first_not_repeated(packet_t pp);
+extern int ax25_get_rr (packet_t this_p, int n);
+
extern int ax25_get_info (packet_t pp, unsigned char **paddr);
extern void ax25_set_nextp (packet_t this_p, packet_t next_p);
@@ -365,6 +407,8 @@ extern packet_t ax25_get_nextp (packet_t this_p);
extern void ax25_set_release_time (packet_t this_p, double release_time);
extern double ax25_get_release_time (packet_t this_p);
+extern void ax25_set_modulo (packet_t this_p, int modulo);
+
extern void ax25_format_addrs (packet_t pp, char *);
extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
@@ -374,6 +418,7 @@ extern ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *d
extern void ax25_hex_dump (packet_t this_p);
extern int ax25_is_aprs (packet_t pp);
+extern int ax25_is_null_frame (packet_t this_p);
extern int ax25_get_control (packet_t this_p);
extern int ax25_get_c2 (packet_t this_p);
diff --git a/ax25_pad2.c b/ax25_pad2.c
index 43e2d83..48a7df8 100644
--- a/ax25_pad2.c
+++ b/ax25_pad2.c
@@ -254,7 +254,7 @@ packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
if (t != 2) {
if (cr != t) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Internal error in %s: U frame, cr is %d but must be %d.\n", __func__, cr, t);
+ dw_printf ("Internal error in %s: U frame, cr is %d but must be %d. ftype=%d\n", __func__, cr, t, ftype);
}
}
@@ -270,7 +270,7 @@ packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
if (pid < 0 || pid == 0 || pid == 0xff) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, Invalid pid value 0x%02x.\n", __func__, pid);
- pid = AX25_NO_LAYER_3;
+ pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
@@ -303,7 +303,7 @@ packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
- char check_desc[32];
+ char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
@@ -444,7 +444,7 @@ packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
- char check_desc[32];
+ char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
@@ -573,9 +573,9 @@ packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
// or 0xff (which means more bytes follow).
if (pid < 0 || pid == 0 || pid == 0xff) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Internal error in %s: I frame, Invalid pid value 0x%02x.\n", __func__, pid);
- pid = AX25_NO_LAYER_3;
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Warning: Client application provided invalid PID value, 0x%02x, for I frame.\n", pid);
+ pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
@@ -601,7 +601,7 @@ packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
- char check_desc[32];
+ char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
diff --git a/cdigipeater.c b/cdigipeater.c
new file mode 100644
index 0000000..a28ab07
--- /dev/null
+++ b/cdigipeater.c
@@ -0,0 +1,325 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2016 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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) {
+
+// Take out debug message?
+// Actually it turns out to be useful.
+// Maybe add a quiet option to suppress it.
+//#if DEBUG
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Packet was rejected for digipeating from channel %d to %d by cfilter: %s\n", from_chan, to_chan, filter_str);
+//#endif
+ return(NULL);
+ }
+ }
+
+
+/*
+ * Find the first repeater station which doesn't have "has been repeated" set.
+ *
+ * r = index of the address position in the frame.
+ */
+ r = ax25_get_first_not_repeated(pp);
+
+ if (r < AX25_REPEATER_1) {
+ return (NULL); // Nothing to do.
+ }
+
+ ax25_get_addr_with_ssid(pp, r, repeater);
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("First unused digipeater is %s\n", repeater);
+#endif
+
+
+/*
+ * First check for explicit use of my call.
+ * Note that receive and transmit channels could have different callsigns.
+ */
+
+ if (strcmp(repeater, mycall_rec) == 0) {
+ packet_t result;
+
+ result = ax25_dup (pp);
+ assert (result != NULL);
+
+ /* If using multiple radio channels, they could have different calls. */
+
+ ax25_set_addr (result, r, mycall_xmit);
+ ax25_set_h (result, r);
+ return (result);
+ }
+
+/*
+ * If we have an alias match, substitute MYCALL.
+ */
+ if (alias != NULL) {
+ err = regexec(alias,repeater,0,NULL,0);
+ if (err == 0) {
+ packet_t result;
+
+ result = ax25_dup (pp);
+ assert (result != NULL);
+
+ ax25_set_addr (result, r, mycall_xmit);
+ ax25_set_h (result, r);
+ return (result);
+ }
+ else if (err != REG_NOMATCH) {
+ regerror(err, alias, err_msg, sizeof(err_msg));
+ text_color_set (DW_COLOR_ERROR);
+ dw_printf ("%s\n", err_msg);
+ }
+ }
+
+/*
+ * Don't repeat it if we get here.
+ */
+ return (NULL);
+
+} /* end cdigipeat_match */
+
+
+
+/* end cdigipeater.c */
diff --git a/cdigipeater.h b/cdigipeater.h
new file mode 100644
index 0000000..5a50b87
--- /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 e358b14..1293535 100644
--- a/config.c
+++ b/config.c
@@ -53,6 +53,7 @@
#include "textcolor.h"
#include "audio.h"
#include "digipeater.h"
+#include "cdigipeater.h"
#include "config.h"
#include "aprs_tt.h"
#include "igate.h"
@@ -60,6 +61,7 @@
#include "symbols.h"
#include "xmit.h"
#include "tt_text.h"
+#include "ax25_link.h"
// geotranz
@@ -680,7 +682,9 @@ static char *split (char *string, int rest_of_line)
*
* Outputs: p_audio_config - Radio channel parameters stored here.
*
- * p_digi_config - Digipeater configuration stored here.
+ * p_digi_config - APRS Digipeater configuration stored here.
+ *
+ * p_cdigi_config - Connected Digipeater configuration stored here.
*
* p_tt_config - APRStt stuff.
*
@@ -703,6 +707,7 @@ static char *split (char *string, int rest_of_line)
void config_init (char *fname, struct audio_s *p_audio_config,
struct digi_config_s *p_digi_config,
+ struct cdigi_config_s *p_cdigi_config,
struct tt_config_s *p_tt_config,
struct igate_config_s *p_igate_config,
struct misc_config_s *p_misc_config)
@@ -789,6 +794,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
memset (p_digi_config, 0, sizeof(struct digi_config_s));
p_digi_config->dedupe_time = DEFAULT_DEDUPE;
+ memset (p_cdigi_config, 0, sizeof(struct cdigi_config_s));
memset (p_tt_config, 0, sizeof(struct tt_config_s));
p_tt_config->gateway_enabled = 0;
@@ -865,6 +871,19 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strlcpy (p_misc_config->logdir, "", sizeof(p_misc_config->logdir));
+ /* connected mode. */
+
+ p_misc_config->frack = AX25_T1V_FRACK_DEFAULT; /* Number of seconds to wait for ack to transmission. */
+
+ p_misc_config->retry = AX25_N2_RETRY_DEFAULT; /* Number of times to retry before giving up. */
+
+ p_misc_config->paclen = AX25_N1_PACLEN_DEFAULT; /* Max number of bytes in information part of frame. */
+
+ p_misc_config->maxframe_basic = AX25_K_MAXFRAME_BASIC_DEFAULT; /* Max frames to send before ACK. mod 8 "Window" size. */
+
+ p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */
+
+ p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 2; /* Max SABME before falling back to SABM. */
/*
* Try to extract options from a file.
@@ -1568,6 +1587,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* PTT - Push To Talk signal line.
* DCD - Data Carrier Detect indicator.
+ * CON - Connected to another station indicator.
*
* xxx serial-port [-]rts-or-dtr [ [-]rts-or-dtr ]
* xxx GPIO [-]gpio-num
@@ -1582,7 +1602,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* Applies to most recent CHANNEL command.
*/
- else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0) {
+ else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0 || strcasecmp(t, "CON") == 0) {
int ot;
char otname[8];
@@ -1595,8 +1615,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strlcpy (otname, "DCD", sizeof(otname));
}
else {
- ot = OCTYPE_FUTURE;
- strlcpy (otname, "FUTURE", sizeof(otname));
+ ot = OCTYPE_CON;
+ strlcpy (otname, "CON", sizeof(otname));
}
t = split(NULL,0);
@@ -1784,7 +1804,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
} /* end of second serial port control line. */
} /* end of serial port case. */
- } /* end of PTT */
+ } /* end of PTT, DCD, CON */
/*
* INPUTS
@@ -1984,14 +2004,14 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
- * ==================== Digipeater parameters ====================
+ * ==================== APRS Digipeater parameters ====================
*/
/*
* DIGIPEAT from-chan to-chan alias-pattern wide-pattern [ OFF|DROP|MARK|TRACE ]
*/
- else if (strcasecmp(t, "digipeat") == 0) {
+ else if (strcasecmp(t, "DIGIPEAT") == 0 || strcasecmp(t, "DIGIPEATER") == 0) {
int from_chan, to_chan;
int e;
char message[100];
@@ -2175,7 +2195,82 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
- * ==================== Packet Filtering for digipeater or IGate ====================
+ * ==================== Connected Digipeater parameters ====================
+ */
+
+/*
+ * CDIGIPEAT from-chan to-chan [ alias-pattern ]
+ */
+
+ else if (strcasecmp(t, "CDIGIPEAT") == 0 || strcasecmp(t, "CDIGIPEATER") == 0) {
+ int from_chan, to_chan;
+ int e;
+ char message[100];
+
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
+ continue;
+ }
+ from_chan = atoi(t);
+ if (from_chan < 0 || from_chan >= MAX_CHANS) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n",
+ MAX_CHANS-1, line);
+ continue;
+ }
+ if ( ! p_audio_config->achan[from_chan].valid) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
+ line, from_chan);
+ continue;
+ }
+
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
+ continue;
+ }
+ to_chan = atoi(t);
+ if (to_chan < 0 || to_chan >= MAX_CHANS) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n",
+ MAX_CHANS-1, line);
+ continue;
+ }
+ if ( ! p_audio_config->achan[to_chan].valid) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
+ line, to_chan);
+ continue;
+ }
+
+ t = split(NULL,0);
+ if (t != NULL) {
+ e = regcomp (&(p_cdigi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
+ if (e != 0) {
+ regerror (e, &(p_cdigi_config->alias[from_chan][to_chan]), message, sizeof(message));
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n",
+ line, message);
+ continue;
+ }
+ t = split(NULL,0);
+ }
+
+ p_cdigi_config->enabled[from_chan][to_chan] = 1;
+
+ if (t != NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t);
+ }
+ }
+
+
+/*
+ * ==================== Packet Filtering for APRS digipeater or IGate ====================
*/
/*
@@ -2252,6 +2347,74 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
+/*
+ * ==================== Packet Filtering for connected digipeater ====================
+ */
+
+/*
+ * CFILTER from-chan to-chan filter_specification_expression
+ */
+
+ else if (strcasecmp(t, "CFILTER") == 0) {
+ int from_chan, to_chan;
+
+
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
+ continue;
+ }
+
+ from_chan = isdigit(*t) ? atoi(t) : -999;
+ if (from_chan < 0 || from_chan >= MAX_CHANS) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d on line %d.\n",
+ MAX_CHANS-1, line);
+ continue;
+ }
+
+ if ( ! p_audio_config->achan[from_chan].valid) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
+ line, from_chan);
+ continue;
+ }
+
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
+ continue;
+ }
+
+ to_chan = isdigit(*t) ? atoi(t) : -999;
+ if (to_chan < 0 || to_chan >= MAX_CHANS) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d on line %d.\n",
+ MAX_CHANS-1, line);
+ continue;
+ }
+ if ( ! p_audio_config->achan[to_chan].valid) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
+ line, to_chan);
+ continue;
+ }
+
+ t = split(NULL,1); /* Take rest of line including spaces. */
+
+ if (t == NULL) {
+ t = " "; /* Empty means permit nothing. */
+ }
+
+ p_cdigi_config->filter_str[from_chan][to_chan] = strdup(t);
+
+//TODO1.2: Do a test run to see errors now instead of waiting.
+
+ }
+
+
/*
* ==================== APRStt gateway ====================
*/
@@ -4163,6 +4326,154 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_misc_config->sb_configured = 1;
}
+
+/*
+ * ==================== AX.25 connected mode ====================
+ */
+
+
+/*
+ * FRACK n - Number of seconds to wait for ack to transmission.
+ */
+
+ else if (strcasecmp(t, "FRACK") == 0) {
+ int n;
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing value for FRACK.\n", line);
+ continue;
+ }
+ n = atoi(t);
+ if (n >= AX25_T1V_FRACK_MIN && n <= AX25_T1V_FRACK_MAX) {
+ p_misc_config->frack = n;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid FRACK time. Using default %d.\n", line, p_misc_config->frack);
+ }
+ }
+
+
+/*
+ * RETRY n - Number of times to retry before giving up.
+ */
+
+ else if (strcasecmp(t, "RETRY") == 0) {
+ int n;
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing value for RETRY.\n", line);
+ continue;
+ }
+ n = atoi(t);
+ if (n >= AX25_N2_RETRY_MIN && n <= AX25_N2_RETRY_MAX) {
+ p_misc_config->retry = n;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid RETRY number. Using default %d.\n", line, p_misc_config->retry);
+ }
+ }
+
+
+/*
+ * PACLEN n - Maximum number of bytes in information part.
+ */
+
+ else if (strcasecmp(t, "PACLEN") == 0) {
+ int n;
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing value for PACLEN.\n", line);
+ continue;
+ }
+ n = atoi(t);
+ if (n >= AX25_N1_PACLEN_MIN && n <= AX25_N1_PACLEN_MAX) {
+ p_misc_config->paclen = n;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid PACLEN value. Using default %d.\n", line, p_misc_config->paclen);
+ }
+ }
+
+
+/*
+ * MAXFRAME n - Max frames to send before ACK. mod 8 "Window" size.
+ *
+ * Window size would make more sense but everyone else calls it MAXFRAME.
+ */
+
+ else if (strcasecmp(t, "MAXFRAME") == 0) {
+ int n;
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing value for MAXFRAME.\n", line);
+ continue;
+ }
+ n = atoi(t);
+ if (n >= AX25_K_MAXFRAME_BASIC_MIN && n <= AX25_K_MAXFRAME_BASIC_MAX) {
+ p_misc_config->maxframe_basic = n;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid MAXFRAME value outside range of %d to %d. Using default %d.\n",
+ line, AX25_K_MAXFRAME_BASIC_MIN, AX25_K_MAXFRAME_BASIC_MAX, p_misc_config->maxframe_basic);
+ }
+ }
+
+
+
+/*
+ * EMAXFRAME n - Max frames to send before ACK. mod 128 "Window" size.
+ */
+
+ else if (strcasecmp(t, "EMAXFRAME") == 0) {
+ int n;
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing value for EMAXFRAME.\n", line);
+ continue;
+ }
+ n = atoi(t);
+ if (n >= AX25_K_MAXFRAME_EXTENDED_MIN && n <= AX25_K_MAXFRAME_EXTENDED_MAX) {
+ p_misc_config->maxframe_extended = n;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid EMAXFRAME value outside of range %d to %d. Using default %d.\n",
+ line, AX25_K_MAXFRAME_EXTENDED_MIN, AX25_K_MAXFRAME_EXTENDED_MAX, p_misc_config->maxframe_extended);
+ }
+ }
+
+/*
+ * MAXV22 n - Max number of SABME sent before trying SABM.
+ */
+
+ else if (strcasecmp(t, "MAXV22") == 0) {
+ int n;
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing value for MAXV22.\n", line);
+ continue;
+ }
+ n = atoi(t);
+ if (n >= 0 && n <= AX25_N2_RETRY_MAX) {
+ p_misc_config->maxv22 = n;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid MAXV22 number. Will use half of RETRY.\n", line);
+ }
+ }
+
+
/*
* Invalid command.
*/
@@ -4189,6 +4500,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
for (i=0; ienabled[i][j]) {
if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 ||
@@ -4221,6 +4534,39 @@ void config_init (char *fname, struct audio_s *p_audio_config,
//p_digi_config->enabled[i][j] = 0;
}
}
+
+/* Connected mode digipeating. */
+
+ if (p_cdigi_config->enabled[i][j]) {
+
+ if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 ||
+ strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 ||
+ strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
+
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i);
+ p_cdigi_config->enabled[i][j] = 0;
+ }
+
+ if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 ||
+ strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 ||
+ strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) {
+
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i);
+ p_cdigi_config->enabled[i][j] = 0;
+ }
+
+ b = 0;
+ for (k=0; 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) {
@@ -4243,6 +4589,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
+ if (p_misc_config->maxv22 < 0) {
+ p_misc_config->maxv22 = p_misc_config->retry / 2;
+ }
+
} /* end config_init */
diff --git a/config.h b/config.h
index 1b3c602..c895e29 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 */
@@ -76,6 +77,25 @@ struct misc_config_s {
int sb_turn_angle; /* degrees */
int sb_turn_slope; /* degrees * MPH */
+// AX.25 connected mode.
+
+ int frack; /* Number of seconds to wait for ack to transmission. */
+
+ int retry; /* Number of times to retry before giving up. */
+
+ int paclen; /* Max number of bytes in information part of frame. */
+
+ int maxframe_basic; /* Max frames to send before ACK. mod 8 "Window" size. */
+
+ int maxframe_extended; /* Max frames to send before ACK. mod 128 "Window" size. */
+
+ int maxv22; /* Maximum number of unanswered SABME frames sent before */
+ /* switching to SABM. This is to handle the case of an old */
+ /* TNC which simply ignores SABME rather than replying with FRMR. */
+
+
+
+// Beacons.
int num_beacons; /* Number of beacons defined. */
@@ -166,6 +186,7 @@ struct misc_config_s {
extern void config_init (char *fname, struct audio_s *p_modem,
struct digi_config_s *digi_config,
+ struct cdigi_config_s *cdigi_config,
struct tt_config_s *p_tt_config,
struct igate_config_s *p_igate_config,
struct misc_config_s *misc_config);
diff --git a/digipeater.c b/digipeater.c
index 817032b..aa2f11e 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
@@ -255,6 +256,10 @@ void digipeater (int from_chan, packet_t pp)
*
*------------------------------------------------------------------------------*/
+#define OBSOLETE14 1
+
+
+#ifndef OBSOLETE14
static char *dest_ssid_path[16] = {
"", /* Use VIA path */
"WIDE1-1",
@@ -272,6 +277,7 @@ static char *dest_ssid_path[16] = {
"WIDE2-2", /* South */
"WIDE2-2", /* East */
"WIDE2-2" }; /* West */
+#endif
static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
@@ -290,7 +296,7 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
if (filter_str != NULL) {
- if (pfilter(from_chan, to_chan, filter_str, pp) != 1) {
+ if (pfilter(from_chan, to_chan, filter_str, pp, 1) != 1) {
// TODO1.2: take out debug message
// Actually it turns out to be useful.
@@ -322,11 +328,15 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
* Otherwise we don't want to modify the input because this could be called multiple times.
*/
+#ifndef OBSOLETE14 // Took it out in 1.4
+
if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
ax25_set_ssid(pp, AX25_DESTINATION, 0);
/* Continue with general case, below. */
}
+#endif
+
/*
* Find the first repeater station which doesn't have "has been repeated" set.
@@ -795,11 +805,15 @@ int main (int argc, char *argv[])
/*
- * Change destination SSID to normal digipeater if none specified.
+ * Change destination SSID to normal digipeater if none specified. (Obsolete, removed.)
*/
- test ( "W1ABC>TEST-3:",
- "W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
+ test ( "W1ABC>TEST-3:",
+#ifndef OBSOLETE14
+ "W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
+#else
+ "");
+#endif
test ( "W1DEF>TEST-3,WIDE2-2:",
"W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");
diff --git a/direwolf.c b/direwolf.c
index 5be1b58..5debb08 100644
--- a/direwolf.c
+++ b/direwolf.c
@@ -88,6 +88,7 @@
#include "hdlc_rec.h"
#include "hdlc_rec2.h"
#include "ax25_pad.h"
+#include "xid.h"
#include "decode_aprs.h"
#include "textcolor.h"
#include "server.h"
@@ -97,6 +98,7 @@
#include "waypoint.h"
#include "gen_tone.h"
#include "digipeater.h"
+#include "cdigipeater.h"
#include "tq.h"
#include "xmit.h"
#include "ptt.h"
@@ -112,6 +114,7 @@
#include "recv.h"
#include "morse.h"
#include "mheard.h"
+#include "ax25_link.h"
//static int idx_decoded = 0;
@@ -158,7 +161,8 @@ static void __cpuid(int cpuinfo[4], int infotype){
static struct audio_s audio_config;
static struct tt_config_s tt_config;
-struct digi_config_s digi_config;
+//struct digi_config_s digi_config;
+//struct cdigi_config_s cdigi_config;
static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */
@@ -181,6 +185,7 @@ int main (int argc, char *argv[])
int xmit_calibrate_option = 0;
int enable_pseudo_terminal = 0;
struct digi_config_s digi_config;
+ struct cdigi_config_s cdigi_config;
struct igate_config_s igate_config;
int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */
char P_opt[16];
@@ -199,6 +204,8 @@ int main (int argc, char *argv[])
#if USE_HAMLIB
int d_h_opt = 0; /* "-d h" option for hamlib debugging. Repeat for more detail */
#endif
+ int E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit frames. */
+ int E_rx_opt = 0; /* "-E Rn" Error rate % for clobbering receive frames. */
strlcpy(l_opt, "", sizeof(l_opt));
strlcpy(P_opt, "", sizeof(P_opt));
@@ -247,7 +254,7 @@ int main (int argc, char *argv[])
text_color_init(t_opt);
text_color_set(DW_COLOR_INFO);
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
- dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "C", __DATE__);
+ dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "D", __DATE__);
//dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
#if defined(ENABLE_GPSD) || defined(USE_HAMLIB)
@@ -332,7 +339,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */
- c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:",
+ c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:E:",
long_options, &option_index);
if (c == -1)
break;
@@ -518,6 +525,27 @@ int main (int argc, char *argv[])
exit (0);
break;
+ case 'E': /* -E Error rate (%) for corrupting frames. */
+ /* Just a number is transmit. Precede by R for receive. */
+
+ if (*optarg == 'r' || *optarg == 'R') {
+ E_rx_opt = atoi(optarg+1);
+ if (E_rx_opt < 1 || E_rx_opt > 99) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("-ER must be in range of 1 to 99.\n");
+ E_rx_opt = 10;
+ }
+ }
+ else {
+ E_tx_opt = atoi(optarg);
+ if (E_tx_opt < 1 || E_tx_opt > 99) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("-E must be in range of 1 to 99.\n");
+ E_tx_opt = 10;
+ }
+ }
+ break;
+
default:
/* Should not be here. */
@@ -552,7 +580,7 @@ int main (int argc, char *argv[])
symbols_init ();
- config_init (config_file, &audio_config, &digi_config, &tt_config, &igate_config, &misc_config);
+ config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config);
if (r_opt != 0) {
audio_config.adev[0].samples_per_sec = r_opt;
@@ -621,6 +649,12 @@ int main (int argc, char *argv[])
audio_config.achan[0].decimate = D_opt;
}
+ // temp - only xmit errors.
+
+ audio_config.xmit_error_rate = E_tx_opt;
+ audio_config.recv_error_rate = E_rx_opt;
+
+
if (strlen(l_opt) > 0) {
strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir));
}
@@ -711,6 +745,8 @@ int main (int argc, char *argv[])
*/
digipeater_init (&audio_config, &digi_config);
igate_init (&audio_config, &igate_config, &digi_config, d_i_opt);
+ cdigipeater_init (&audio_config, &cdigi_config);
+ ax25_link_init (&misc_config);
/*
* Provide the AGW & KISS socket interfaces for use by a client application.
@@ -890,7 +926,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
text_color_set(DW_COLOR_REC);
}
else {
- text_color_set(DW_COLOR_DEBUG);
+ text_color_set(DW_COLOR_DECODED);
}
if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) {
@@ -914,26 +950,41 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
if ( ! ax25_is_aprs(pp)) {
ax25_frame_type_t ftype;
cmdres_t cr;
- char desc[32];
+ char desc[80];
int pf;
int nr;
int ns;
ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
- (void)ftype;
+
+ /* Could change by 1, since earlier call, if we guess at modulo 128. */
+ info_len = ax25_get_info (pp, &pinfo);
dw_printf ("(%s)", desc);
+ if (ftype == frame_type_U_XID) {
+ struct xid_param_s param;
+ char info2text[100];
+
+ xid_parse (pinfo, info_len, ¶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");
}
- // for APRS we generally want to display non-ASCII to see UTF-8.
- // for other, probably want to restrict to ASCII only because we are
- // more likely to have compressed data than UTF-8 text.
-
- // TODO: Might want to use d_u_opt for transmitted frames too.
-
-
- ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
- dw_printf ("\n");
// Also display in pure ASCII if non-ASCII characters and "-d u" option specified.
@@ -992,6 +1043,9 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
log_write (chan, &A, pp, alevel, retries);
+ // temp experiment.
+ //log_rr_bits (&A, pp);
+
// Add to list of stations heard.
mheard_save (chan, &A, pp, alevel, retries);
@@ -1051,24 +1105,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__
diff --git a/direwolf.h b/direwolf.h
index c584664..a647b60 100644
--- a/direwolf.h
+++ b/direwolf.h
@@ -233,6 +233,11 @@ char *strcasestr(const char *S, const char *FIND);
#else // Use our own copy
+// These prevent /usr/include/gps.h from providing its own definition.
+#define HAVE_STRLCAT 1
+#define HAVE_STRLCPY 1
+
+
#define DEBUG_STRL 1
#if DEBUG_STRL
diff --git a/dlq.c b/dlq.c
index 3d0a2c9..6a4236f 100644
--- a/dlq.c
+++ b/dlq.c
@@ -24,7 +24,7 @@
*
* Purpose: Received frame queue.
*
- * Description: In earlierversions, the main thread read from the
+ * Description: In earlier versions, the main thread read from the
* audio device and performed the receive demodulation/decoding.
*
* Since version 1.2 we have a separate receive thread
@@ -32,7 +32,7 @@
* received frames from all channels and process them
* serially.
*
- * In version 1.4, other types of events go into this
+ * In version 1.4, other types of events also go into this
* queue and we use it to drive the data link state machine.
*
*---------------------------------------------------------------*/
@@ -85,10 +85,13 @@ static int was_init = 0; /* was initialization performed? */
static void append_to_queue (struct dlq_item_s *pnew);
-static volatile int s_new_count = 0; /* To detect memory leak. */
-static volatile int s_delete_count = 0; // TODO: need to test.
+static volatile int s_new_count = 0; /* To detect memory leak for queue items. */
+static volatile int s_delete_count = 0; // TODO: need to test.
+static volatile int s_cdata_new_count = 0; /* To detect memory leak for connected mode data. */
+static volatile int s_cdata_delete_count = 0; // TODO: need to test.
+
/*-------------------------------------------------------------------
@@ -109,8 +112,6 @@ static volatile int s_delete_count = 0; // TODO: need to test.
void dlq_init (void)
{
- //int c, p;
-
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_init ( )\n");
@@ -189,6 +190,7 @@ void dlq_init (void)
* Normally this was received over the radio but we can create
* our own from APRStt or beaconing.
*
+ * This would correspond to PH-DATA Indication in the AX.25 protocol spec.
*
* Inputs: chan - Channel, 0 is first.
*
@@ -244,11 +246,17 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
}
#endif
+
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
+ if (s_new_count > s_delete_count + 50) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("INTERNAL ERROR: DLQ memory leak, new=%d, delete=%d\n", s_new_count, s_delete_count);
+ }
+
pnew->nextp = NULL;
pnew->type = DLQ_REC_FRAME;
pnew->chan = chan;
@@ -458,6 +466,7 @@ static void append_to_queue (struct dlq_item_s *pnew)
* pid - Protocol ID for data. Normally 0xf0 but the API
* allows the client app to use something non-standard
* for special situations.
+ * TODO: remove this. PID is only for I and UI frames.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
@@ -486,7 +495,6 @@ void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num
memcpy (pnew->addrs, addrs, sizeof(pnew->addrs));
pnew->num_addr = num_addr;
pnew->client = client;
- pnew->pid = pid;
/* Put it into queue. */
@@ -603,9 +611,10 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n
memcpy (pnew->addrs, addrs, sizeof(pnew->addrs));
pnew->num_addr = num_addr;
pnew->client = client;
- pnew->pid = pid;
-/* TODO: haven't thought about user data yet. */
+/* Attach the transmit data. */
+
+ pnew->txdata = cdata_new(pid,xdata_ptr,xdata_len);
/* Put it into queue. */
@@ -614,6 +623,195 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n
} /* end dlq_xmit_data_request */
+
+/*-------------------------------------------------------------------
+ *
+ * Name: dlq_register_callsign
+ * dlq_unregister_callsign
+ *
+ * Purpose: Register callsigns that we will recognize for incoming connection requests.
+ *
+ * Inputs: addrs - Source (owncall), destination (peercall),
+ * and possibly digipeaters.
+ *
+ * chan - Channel, 0 is first.
+ *
+ * client - Client application instance.
+ *
+ * Outputs: Request is appended to queue for processing by
+ * the data link state machine.
+ *
+ * Description: The data link state machine does not use MYCALL from the APRS configuration.
+ * For outgoing frames, the client supplies the source callsign.
+ * For incoming connection requests, we need to know what address(es) to respond to.
+ *
+ * Note that one client application can register multiple callsigns for
+ * multiple channels.
+ * Different clients can register different different addresses on the same channel.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
+{
+ struct dlq_item_s *pnew;
+
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("dlq_register_callsign (%s, chan=%d, client=%d)\n", addr, chan, client);
+#endif
+
+ assert (chan >= 0 && chan < MAX_CHANS);
+
+/* Allocate a new queue item. */
+
+ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ s_new_count++;
+
+ pnew->type = DLQ_REGISTER_CALLSIGN;
+ pnew->chan = chan;
+ strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN);
+ pnew->num_addr = 1;
+ pnew->client = client;
+
+/* Put it into queue. */
+
+ append_to_queue (pnew);
+
+} /* end dlq_register_callsign */
+
+
+void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
+{
+ struct dlq_item_s *pnew;
+
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("dlq_unregister_callsign (%s, chan=%d, client=%d)\n", addr, chan, client);
+#endif
+
+ assert (chan >= 0 && chan < MAX_CHANS);
+
+/* Allocate a new queue item. */
+
+ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ s_new_count++;
+
+ pnew->type = DLQ_UNREGISTER_CALLSIGN;
+ pnew->chan = chan;
+ strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN);
+ pnew->num_addr = 1;
+ pnew->client = client;
+
+/* Put it into queue. */
+
+ append_to_queue (pnew);
+
+} /* end dlq_unregister_callsign */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: dlq_channel_busy
+ *
+ * Purpose: Inform data link state machine about activity on the radio channel.
+ *
+ * Inputs: chan - Radio channel number.
+ *
+ * activity - OCTYPE_PTT or OCTYPE_DCD, as defined in audio.h.
+ * Other values will be discarded.
+ *
+ * status - 1 for active or 0 for quiet.
+ *
+ * Outputs: Request is appended to queue for processing by
+ * the data link state machine.
+ *
+ * Description: Notify the link state machine about changes in carrier detect
+ * and our transmitter.
+ * This is needed for pausing some of our timers. For example,
+ * if we transmit a frame and expect a response in 3 seconds, that
+ * might be delayed because someone else is using the channel.
+ *
+ *--------------------------------------------------------------------*/
+
+void dlq_channel_busy (int chan, int activity, int status)
+{
+ struct dlq_item_s *pnew;
+
+ if (activity == OCTYPE_PTT || activity == OCTYPE_DCD) {
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("dlq_channel_busy (...)\n");
+#endif
+
+
+/* Allocate a new queue item. */
+
+ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ s_new_count++;
+
+ pnew->type = DLQ_CHANNEL_BUSY;
+ pnew->chan = chan;
+ pnew->activity = activity;
+ pnew->status = status;
+
+/* Put it into queue. */
+
+ append_to_queue (pnew);
+ }
+
+} /* end dlq_channel_busy */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: dlq_client_cleanup
+ *
+ * Purpose: Client application has disappeared.
+ * i.e. The TCP connection has been broken.
+ *
+ * Inputs: client - Client application instance.
+ *
+ * Outputs: Request is appended to queue for processing by
+ * the data link state machine.
+ *
+ * Description: Notify the link state machine that given client has gone away.
+ * Clean up all information related to that client application.
+ *
+ *--------------------------------------------------------------------*/
+
+void dlq_client_cleanup (int client)
+{
+ struct dlq_item_s *pnew;
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("dlq_client_cleanup (...)\n");
+#endif
+
+ // assert (client >= 0 && client < MAX_NET_CLIENTS);
+
+/* Allocate a new queue item. */
+
+ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ s_new_count++;
+
+ // All we care about is the client number.
+
+ pnew->type = DLQ_CLIENT_CLEANUP;
+ pnew->client = client;
+
+/* Put it into queue. */
+
+ append_to_queue (pnew);
+
+} /* end dlq_client_cleanup */
+
+
+
/*-------------------------------------------------------------------
*
* Name: dlq_wait_while_empty
@@ -817,12 +1015,145 @@ struct dlq_item_s *dlq_remove (void)
void dlq_delete (struct dlq_item_s *pitem)
{
+ if (pitem == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("INTERNAL ERROR: dlq_delete() given NULL pointer.\n");
+ return;
+ }
+
s_delete_count++;
- if (pitem->pp != NULL) ax25_delete (pitem->pp)
+
+ if (pitem->pp != NULL) {
+ ax25_delete (pitem->pp);
+ pitem->pp = NULL;
+ }
+
+ if (pitem->txdata != NULL) {
+ cdata_delete (pitem->txdata);
+ pitem->txdata = NULL;
+ }
+
free (pitem);
} /* end dlq_delete */
+
+/*-------------------------------------------------------------------
+ *
+ * Name: cdata_new
+ *
+ * Purpose: Allocate blocks of data for sending and receiving connected data.
+ *
+ * Inputs: pid - protocol id.
+ * data - pointer to data. Can be NULL for segment reassembler.
+ * len - length of data.
+ *
+ * Returns: Structure with a copy of the data.
+ *
+ * Description: The flow goes like this:
+ *
+ * Client application extablishes a connection with another station.
+ * Client application calls "dlq_xmit_data_request."
+ * A copy of the data is made with this function and attached to the queue item.
+ * The txdata block is attached to the appropriate link state machine.
+ * At the proper time, it is transmitted in an I frame.
+ * It needs to be kept around in case it needs to be retransmitted.
+ * When no longer needed, it is freed with cdata_delete.
+ *
+ *--------------------------------------------------------------------*/
+
+
+cdata_t *cdata_new (int pid, char *data, int len)
+{
+ int size;
+ cdata_t *cdata;
+
+ s_cdata_new_count++;
+
+ /* Round up the size to the next 128 bytes. */
+ /* The theory is that a smaller number of unique sizes might be */
+ /* beneficial for memory fragmentation and garbage collection. */
+
+ size = ( len + 127 ) & ~0x7f;
+
+ cdata = malloc ( sizeof(cdata_t) + size );
+
+ cdata->magic = TXDATA_MAGIC;
+ cdata->next = NULL;
+ cdata->pid = pid;
+ cdata->size = size;
+ cdata->len = len;
+
+ assert (len >= 0 && len <= size);
+ if (data == NULL) {
+ memset (cdata->data, '?', size);
+ }
+ else {
+ memcpy (cdata->data, data, len);
+ }
+ return (cdata);
+
+} /* end cdata_new */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: cdata_delete
+ *
+ * Purpose: Release storage used by a connected data block.
+ *
+ * Inputs: cdata - Pointer to a data block.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void cdata_delete (cdata_t *cdata)
+{
+ if (cdata == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("INTERNAL ERROR: cdata_delete() given NULL pointer.\n");
+ return;
+ }
+
+ if (cdata->magic != TXDATA_MAGIC) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("INTERNAL ERROR: cdata_delete() given corrupted data.\n");
+ return;
+ }
+
+ s_cdata_delete_count++;
+
+ cdata->magic = 0;
+
+ free (cdata);
+
+} /* end cdata_delete */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: cdata_check_leak
+ *
+ * Purpose: Check for memory leak of cdata items.
+ *
+ * Description: This is called when we expect no outstanding allocations.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void cdata_check_leak (void)
+{
+ if (s_cdata_delete_count != s_cdata_new_count) {
+
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Internal Error, %s, new=%d, delete=%d\n", __func__, s_cdata_new_count, s_cdata_delete_count);
+ }
+
+} /* end cdata_check_leak */
+
+
+
/* end dlq.c */
diff --git a/dlq.h b/dlq.h
index f4f0a90..6874fb0 100644
--- a/dlq.h
+++ b/dlq.h
@@ -12,10 +12,30 @@
#include "audio.h"
+/* A transmit or receive data block for connected mode. */
+
+typedef struct cdata_s {
+ int magic; /* For integrity checking. */
+
+#define TXDATA_MAGIC 0x09110911
+
+ struct cdata_s *next; /* Pointer to next when part of a list. */
+
+ int pid; /* Protocol id. */
+
+ int size; /* Number of bytes allocated. */
+
+ int len; /* Number of bytes actually used. */
+
+ char data[]; /* Variable length data. */
+
+} cdata_t;
+
+
/* Types of things that can be in queue. */
-typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST} dlq_type_t;
+typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_CHANNEL_BUSY, DLQ_CLIENT_CLEANUP} dlq_type_t;
/* A queue item. */
@@ -28,10 +48,13 @@ typedef struct dlq_item_s {
struct dlq_item_s *nextp; /* Next item in queue. */
dlq_type_t type; /* Type of item. */
- /* DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST */
+ /* See enum definition above. */
int chan; /* Radio channel of origin. */
+// I'm not worried about amount of memory used but this might be a
+// little clearer if a union was used for the different event types.
+
// Used for received frame.
int subchan; /* Winning "subchannel" when using multiple */
@@ -49,7 +72,7 @@ typedef struct dlq_item_s {
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */
-// Used by requests from a client application.
+// Used by requests from a client application, connect, etc.
char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
@@ -57,9 +80,19 @@ typedef struct dlq_item_s {
int client;
- int pid;
- /* TODO: xmit data */
+// Used only by client request to transmit connected data.
+
+ cdata_t *txdata;
+
+// Used for channel activity change.
+// It is useful to know when the channel is busy either for carrier detect
+// or when we are transmitting.
+
+ int activity; /* OCTYPE_PTT for my transmission start/end. */
+ /* OCTYPE_DCD if we hear someone else. */
+
+ int status; /* 1 for active or 0 for quiet. */
} dlq_item_t;
@@ -75,7 +108,16 @@ void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num
void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client);
-void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int clienti, int pid, char *xdata_ptr, int xdata_len);
+void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len);
+
+void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
+
+void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
+
+void dlq_channel_busy (int chan, int activity, int status);
+
+void dlq_client_cleanup (int client);
+
int dlq_wait_while_empty (double timeout_val);
@@ -84,6 +126,15 @@ struct dlq_item_s *dlq_remove (void);
void dlq_delete (struct dlq_item_s *pitem);
+
+
+cdata_t *cdata_new (int pid, char *data, int len);
+
+void cdata_delete (cdata_t *txdata);
+
+void cdata_check_leak (void);
+
+
#endif
/* end dlq.h */
diff --git a/doc/Going-beyond-9600-baud.pdf b/doc/Going-beyond-9600-baud.pdf
index 2996aec..18e0aa6 100644
Binary files a/doc/Going-beyond-9600-baud.pdf and b/doc/Going-beyond-9600-baud.pdf differ
diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf
index 23e3d5f..3ccff1f 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 f8fd2e9..5f0d302 100644
Binary files a/doc/Raspberry-Pi-SDR-IGate.pdf and b/doc/Raspberry-Pi-SDR-IGate.pdf differ
diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf
index 412c310..9a53c0c 100644
Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ
diff --git a/gen_packets.c b/gen_packets.c
index 13ad6e2..f6fbcc4 100644
--- a/gen_packets.c
+++ b/gen_packets.c
@@ -141,7 +141,7 @@ static void send_packet (char *str)
}
#endif
hdlc_send_flags (c, 8, 0);
- hdlc_send_frame (c, fbuf, flen);
+ hdlc_send_frame (c, fbuf, flen, 0);
hdlc_send_flags (c, 2, 1);
}
ax25_delete (pp);
diff --git a/hdlc_rec.c b/hdlc_rec.c
index 975f353..15b72c2 100644
--- a/hdlc_rec.c
+++ b/hdlc_rec.c
@@ -260,8 +260,15 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
* with the special "flag" characters.
*
* Idle time of all zero bits (alternating tones at maximum rate)
- * has also been observed rarely.
- * Recognize zero(s) followed by a flag even though it vilolates the spec.
+ * has also been observed rarely. It is easy to understand the reasoning.
+ * The tones alternate at the maximum rate, making it symmetrical and providing
+ * the most opportunity for the PLL to lock on to the edges.
+ * It also violates the published protocol spec.
+ *
+ * Recognize zero(s) followed by a single flag even though it violates the spec.
+ *
+ * It has been reported that the TinyTrak4 does this.
+ * https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/1207
*/
/*
@@ -304,6 +311,15 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
/*
* Loss of signal should result in lack of transitions.
* (all '1' bits) for at least a little while.
+ *
+ * When this was written, I was only concerned about 1200 baud.
+ * For 9600, added later, there is a (de)scrambling function.
+ * So if there is no change in the signal, we would get pseudo random bits here.
+ * Maybe we need to put in another check earlier so DCD is not held on too long
+ * after loss of signal for 9600.
+ * No, that would not be a good idea. part of a valid frame, when scrambled,
+ * could have seven or more "1" bits in a row.
+ * Needs more study.
*/
diff --git a/hdlc_send.c b/hdlc_send.c
index fd43c43..4a09cc8 100644
--- a/hdlc_send.c
+++ b/hdlc_send.c
@@ -52,6 +52,8 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl
*
* flen - Frame length, not including the FCS.
*
+ * bad_fcs - Append an invalid FCS for testing purposes.
+ *
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
*
* Returns: Number of bits sent including "flags" and the
@@ -73,8 +75,7 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl
*
*--------------------------------------------------------------*/
-
-int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
+int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs)
{
int j, fcs;
@@ -84,7 +85,7 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d )\n", chan, fbuf, flen);
+ dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs);
fflush (stdout);
#endif
@@ -97,8 +98,15 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
fcs = fcs_calc (fbuf, flen);
- send_data (chan, fcs & 0xff);
- send_data (chan, (fcs >> 8) & 0xff);
+ if (bad_fcs) {
+ /* For testing only - Simulate a frame getting corrupted along the way. */
+ send_data (chan, (~fcs) & 0xff);
+ send_data (chan, ((~fcs) >> 8) & 0xff);
+ }
+ else {
+ send_data (chan, fcs & 0xff);
+ send_data (chan, (fcs >> 8) & 0xff);
+ }
send_control (chan, 0x7e); /* End frame */
diff --git a/hdlc_send.h b/hdlc_send.h
index 41d44b1..10d200c 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 14a54b7..2d0cb0e 100644
--- a/igate.c
+++ b/igate.c
@@ -883,7 +883,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp)
if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) {
- if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp) != 1) {
+ if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) {
// Is this useful troubleshooting information or just distracting noise?
// Originally this was always printed but there was a request to add a "quiet" option to suppress this.
@@ -1542,7 +1542,7 @@ static void xmit_packet (char *message, int to_chan)
if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) {
- if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3) != 1) {
+ if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3, 1) != 1) {
// Originally this was always printed but it's probably too much noise.
// Version 1.4, print only if debug option is specified.
diff --git a/log.c b/log.c
index c466677..fa86c3e 100644
--- a/log.c
+++ b/log.c
@@ -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 5cc0264..fc6ca44 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/multi_modem.c b/multi_modem.c
index 851f6d9..fd53b48 100644
--- a/multi_modem.c
+++ b/multi_modem.c
@@ -470,7 +470,27 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
if (save_audio_config_p->achan[chan].num_subchan == 1 &&
save_audio_config_p->achan[chan].num_slicers == 1) {
- dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, "");
+
+ int drop_it = 0;
+ if (save_audio_config_p->recv_error_rate != 0) {
+ float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
+
+ //text_color_set(DW_COLOR_INFO);
+ //dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
+
+ if (save_audio_config_p->recv_error_rate / 100.0 > r) {
+ drop_it = 1;
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
+ }
+ }
+
+ if (drop_it ) {
+ ax25_delete (pp);
+ }
+ else {
+ dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, "");
+ }
return;
}
@@ -649,14 +669,34 @@ static void pick_best_candidate (int chan)
j = subchan_from_n(best_n);
k = slice_from_n(best_n);
- dlq_rec_frame (chan, j, k,
+ int drop_it = 0;
+ if (save_audio_config_p->recv_error_rate != 0) {
+ float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
+
+ //text_color_set(DW_COLOR_INFO);
+ //dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
+
+ if (save_audio_config_p->recv_error_rate / 100.0 > r) {
+ drop_it = 1;
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
+ }
+ }
+
+ if ( drop_it ) {
+ ax25_delete (candidate[chan][j][k].packet_p);
+ candidate[chan][j][k].packet_p = NULL;
+ }
+ else {
+ dlq_rec_frame (chan, j, k,
candidate[chan][j][k].packet_p,
candidate[chan][j][k].alevel,
(int)(candidate[chan][j][k].retries),
spectrum);
- /* Someone else owns it now and will delete it later. */
- candidate[chan][j][k].packet_p = NULL;
+ /* Someone else owns it now and will delete it later. */
+ candidate[chan][j][k].packet_p = NULL;
+ }
/* Clear in preparation for next time. */
diff --git a/pfilter.c b/pfilter.c
index 761ca5d..19f232a 100644
--- a/pfilter.c
+++ b/pfilter.c
@@ -81,9 +81,15 @@ typedef struct pfstate_s {
packet_t pp;
/*
- * Packet split into separate parts.
+ * Are we processing APRS or connected mode?
+ * This determines whch types of filters are available.
+ */
+ int is_aprs;
+
+/*
+ * Packet split into separate parts if APRS.
* Most interesting fields are:
- * g_src - source address
+ *
* g_symbol_table - / \ or overlay
* g_symbol_code
* g_lat, g_lon - Location
@@ -133,6 +139,10 @@ static int filt_s (pfstate_t *pf);
*
* pp - Packet object handle.
*
+ * is_aprs - True for APRS, false for connected mode digipeater.
+ * Connected mode allows a subset of the filter types, only
+ * looking at the addresses, not information part contents.
+ *
* Returns: 1 = yes
* 0 = no
* -1 = error detected
@@ -142,7 +152,7 @@ static int filt_s (pfstate_t *pf);
*
*--------------------------------------------------------------------*/
-int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
+int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs)
{
pfstate_t pfstate;
char *p;
@@ -151,6 +161,8 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
assert (from_chan >= 0 && from_chan <= MAX_CHANS);
assert (to_chan >= 0 && to_chan <= MAX_CHANS);
+ memset (&pfstate, 0, sizeof(pfstate));
+
if (pp == NULL) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n");
@@ -165,10 +177,9 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
pfstate.from_chan = from_chan;
pfstate.to_chan = to_chan;
- /* Copy filter string, removing any control characters. */
+ /* Copy filter string, changing any control characers to spaces. */
- memset (pfstate.filter_str, 0, sizeof(pfstate.filter_str));
- strncpy (pfstate.filter_str, filter, MAX_FILTER_LEN-1);
+ strlcpy (pfstate.filter_str, filter, sizeof(pfstate.filter_str));
pfstate.nexti = 0;
for (p = pfstate.filter_str; *p != '\0'; p++) {
@@ -178,7 +189,11 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
}
pfstate.pp = pp;
- decode_aprs (&pfstate.decoded, pp, 1);
+ pfstate.is_aprs = is_aprs;
+
+ if (is_aprs) {
+ decode_aprs (&pfstate.decoded, pp, 1);
+ }
next_token(&pfstate);
@@ -415,6 +430,14 @@ static int parse_primary (pfstate_t *pf)
* 0 = no
* -1 = error detected
*
+ * Description: All filter specifications are allowed for APRS.
+ * Only those dealing with addresses are allowed for connected digipeater.
+ *
+ * b - budlist (source)
+ * d - digipeaters used
+ * v - digipeaters not used
+ * u - unproto (destination)
+ *
*--------------------------------------------------------------------*/
@@ -424,6 +447,16 @@ static int parse_filter_spec (pfstate_t *pf)
int result = -1;
char addr[AX25_MAX_ADDR_LEN];
+
+ if ( ( ! pf->is_aprs) && strchr ("01bdvu", pf->token_str[0]) == NULL) {
+
+ print_error (pf, "Only b, d, v, and u specifications are allowed for connected mode digipeater filtering.");
+ result = -1;
+ next_token (pf);
+ return (result);
+ }
+
+
/* undocumented: can use 0 or 1 for testing. */
if (strcmp(pf->token_str, "0") == 0) {
@@ -437,7 +470,8 @@ static int parse_filter_spec (pfstate_t *pf)
else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) {
/* Budlist - source address */
- result = filt_bodgu (pf, pf->decoded.g_src);
+ ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr);
+ result = filt_bodgu (pf, addr);
}
else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) {
/* Object or item name */
@@ -479,7 +513,7 @@ static int parse_filter_spec (pfstate_t *pf)
}
else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) {
/* Unproto (destination) - probably want to exclude mic-e types */
- /* because destintation is used for part of location. */
+ /* because destination is used for part of location. */
if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') {
ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
@@ -1120,7 +1154,7 @@ static void pftest (int test_num, char *filter, char *monitor, int expected)
pp = ax25_from_text (monitor, 1);
assert (pp != NULL);
- result = pfilter (0, 0, filter, pp);
+ result = pfilter (0, 0, filter, pp, 1);
if (result != expected) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("Unexpected result for test number %d\n", test_num);
diff --git a/pfilter.h b/pfilter.h
index ba4dcee..18b4f61 100644
--- a/pfilter.h
+++ b/pfilter.h
@@ -1,6 +1,6 @@
/* pfilter.h */
-int pfilter (int from_chan, int to_chan, char *filter, packet_t pp);
+int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs);
int is_telem_metadata (char *infop);
\ No newline at end of file
diff --git a/ptt.c b/ptt.c
index 6369f31..40e4ddf 100644
--- a/ptt.c
+++ b/ptt.c
@@ -46,6 +46,8 @@
*
* Version 1.3: HAMLIB support.
*
+ * Version 1.4: The spare "future" indicator is now used when connected to another station.
+ *
* References: http://www.robbayer.com/files/serial-win.pdf
*
* https://www.kernel.org/doc/Documentation/gpio.txt
@@ -127,6 +129,7 @@ typedef int HANDLE;
#include "textcolor.h"
#include "audio.h"
#include "ptt.h"
+#include "dlq.h"
#if __WIN32__
@@ -372,7 +375,7 @@ void ptt_init (struct audio_s *audio_config_p)
strlcpy (otnames[OCTYPE_PTT], "PTT", sizeof(otnames[OCTYPE_PTT]));
strlcpy (otnames[OCTYPE_DCD], "DCD", sizeof(otnames[OCTYPE_DCD]));
- strlcpy (otnames[OCTYPE_FUTURE], "FUTURE", sizeof(otnames[OCTYPE_FUTURE]));
+ strlcpy (otnames[OCTYPE_CON], "CON", sizeof(otnames[OCTYPE_CON]));
for (ch = 0; ch < MAX_CHANS; ch++) {
@@ -773,7 +776,7 @@ void ptt_init (struct audio_s *audio_config_p)
* probably be renamed something like octrl_set.
*
* Inputs: ot - Output control type:
- * OCTYPE_PTT, OCTYPE_DCD, OCTYPE_HAMLIB, OCTYPE_FUTURE
+ * OCTYPE_PTT, OCTYPE_DCD, OCTYPE_FUTURE
*
* chan - channel, 0 .. (number of channels)-1
*
@@ -810,6 +813,13 @@ void ptt_set (int ot, int chan, int ptt_signal)
return;
}
+/*
+ * The data link state machine has an interest in activity on the radio channel.
+ * This is a very convenient place to get that information.
+ */
+
+ dlq_channel_busy (chan, ot, ptt_signal);
+
/*
* Inverted output?
*/
diff --git a/recv.c b/recv.c
index 85bf147..8d7826a 100644
--- a/recv.c
+++ b/recv.c
@@ -68,7 +68,7 @@
*
* The difference is that app_process_rec_frame
* is no longer called directly. Instead
- * the frame is appended to a queue with dlq_append.
+ * the frame is appended to a queue with dlq_rec_frame.
*
* Received frames can now be processed one at
* a time and we don't need to worry about later
@@ -107,9 +107,8 @@
#include "dtmf.h"
#include "aprs_tt.h"
#include "dtime_now.h"
-#if NEW14
#include "ax25_link.h"
-#endif
+
#if __WIN32__
static unsigned __stdcall recv_adev_thread (void *arg);
@@ -266,7 +265,7 @@ static void * recv_adev_thread (void *arg)
}
/* When a complete frame is accumulated, */
- /* dlq_append, is called. */
+ /* dlq_rec_frame, is called. */
/* recv_process, below, drains the queue. */
@@ -293,15 +292,11 @@ void recv_process (void)
while (1) {
int timed_out;
-#if NEW14
+
double timeout_value = ax25_link_get_next_timer_expiry();
-
timed_out = dlq_wait_while_empty (timeout_value);
-#else
- dlq_wait_while_empty (0.0);
- timed_out = 0;
-#endif
+
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
@@ -315,9 +310,7 @@ void recv_process (void)
dw_printf ("recv_process: time waiting on dlq. call dl_timer_expiry.\n");
#endif
-#if NEW14
dl_timer_expiry ();
-#endif
}
else {
@@ -345,17 +338,16 @@ void recv_process (void)
*/
app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->retries, pitem->spectrum);
- pitem->pp = NULL; // Was consumed by above. Don't try to use or free again.
/*
- * Link processing - Can ignore UI frames.
+ * Link processing.
*/
- // TODO - ax25_link_rec_frame & delete & remove delete from above.
+ lm_data_indication(pitem);
break;
-#if NEW14
+
case DLQ_CONNECT_REQUEST:
dl_connect_request (pitem);
@@ -370,7 +362,27 @@ void recv_process (void)
dl_data_request (pitem);
break;
-#endif
+
+ case DLQ_REGISTER_CALLSIGN:
+
+ dl_register_callsign (pitem);
+ break;
+
+ case DLQ_UNREGISTER_CALLSIGN:
+
+ dl_unregister_callsign (pitem);
+ break;
+
+ case DLQ_CHANNEL_BUSY:
+
+ lm_channel_busy (pitem);
+ break;
+
+ case DLQ_CLIENT_CLEANUP:
+
+ dl_client_cleanup (pitem);
+ break;
+
}
dlq_delete (pitem);
diff --git a/serial_port.c b/serial_port.c
index f125178..b3c56ef 100644
--- a/serial_port.c
+++ b/serial_port.c
@@ -184,6 +184,10 @@ MYFDTYPE serial_port_open (char *devicename, int baud)
//text_color_set(DW_COLOR_INFO);
//dw_printf("Successful serial port open on %s.\n", devicename);
+ // Some devices, e.g. KPC-3+, can't turn off hardware flow control and need RTS.
+
+ EscapeCommFunction(fd,SETRTS);
+ EscapeCommFunction(fd,SETDTR);
#else
/* Linux version. */
diff --git a/server.c b/server.c
index 59df2a9..23dff77 100644
--- a/server.c
+++ b/server.c
@@ -179,23 +179,6 @@ static int enable_send_monitor_to_client[MAX_NET_CLIENTS];
/* the client app must send a command to enable this. */
-/*
- * Registered callsigns from 'X' command.
- * For simplicity just use a fixed size array until there
- * is evidence that a larger number would be needed.
- *
- * Also keep track of which client did the registration.
- * For example client 0 might register the callsign ABC
- * and client 1 register DEF. If something comes addressed
- * to DEF, we would want it going only to client 1.
- */
-
-#define MAX_REG_CALLSIGNS 20
-
-static char registered_callsigns[MAX_REG_CALLSIGNS][AX25_MAX_ADDR_LEN];
-static int registered_by_client[MAX_REG_CALLSIGNS];
-
-
// TODO: define in one place, use everywhere.
// TODO: Macro to terminate thread when no point to go on.
@@ -464,8 +447,6 @@ void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc)
enable_send_monitor_to_client[client] = 0;
}
- memset (registered_callsigns, 0, sizeof(registered_callsigns));
-
if (server_port == 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Disabled AGW network client port.\n");
@@ -653,6 +634,7 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
+// TODO: "attached" or some other term would be better because "connected" has a different meaning for AX.25.
dw_printf("\nConnected to AGW client application %d ...\n\n", client);
/*
@@ -836,6 +818,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
closesocket (client_sock[client]);
client_sock[client] = -1;
WSACleanup();
+ dlq_client_cleanup (client);
}
#else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@@ -845,6 +828,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
dw_printf ("\nError sending message to AGW client application. Closing connection.\n\n");
close (client_sock[client]);
client_sock[client] = -1;
+ dlq_client_cleanup (client);
}
#endif
}
@@ -939,6 +923,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
closesocket (client_sock[client]);
client_sock[client] = -1;
WSACleanup();
+ dlq_client_cleanup (client);
}
#else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@@ -947,7 +932,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
}
@@ -964,6 +950,8 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
* Purpose: Send notification to client app when a link has
* been established with another station.
*
+ * DL-CONNECT Confirm or DL-CONNECT Indication in the protocol spec.
+ *
* Inputs: chan - Which radio channel.
*
* client - Which one of potentially several clients.
@@ -1017,6 +1005,8 @@ void server_link_established (int chan, int client, char *remote_call, char *own
* another station has been terminated or a connection
* attempt failed.
*
+ * DL-DISCONNECT Confirm or DL-DISCONNECT Indication in the protocol spec.
+ *
* Inputs: chan - Which radio channel.
*
* client - Which one of potentially several clients.
@@ -1027,7 +1017,7 @@ void server_link_established (int chan, int client, char *remote_call, char *own
*
* timeout - true when no answer from other station.
* How do we distinguish who asked for the
- * termination of an existing linkn?
+ * termination of an existing link?
*
*--------------------------------------------------------------------*/
@@ -1060,6 +1050,66 @@ void server_link_terminated (int chan, int client, char *remote_call, char *own_
} /* end server_link_terminated */
+/*-------------------------------------------------------------------
+ *
+ * Name: server_rec_conn_data
+ *
+ * Purpose: Send received connected data to the application.
+ *
+ * DL-DATA Indication in the protocol spec.
+ *
+ * Inputs: chan - Which radio channel.
+ *
+ * client - Which one of potentially several clients.
+ *
+ * remote_call - Callsign[-ssid] of remote station.
+ *
+ * own_call - Callsign[-ssid] of my end.
+ *
+ * pid - Protocol ID from I frame.
+ *
+ * data_ptr - Pointer to a block of bytes.
+ *
+ * data_len - Number of bytes. Could be zero.
+ *
+ *--------------------------------------------------------------------*/
+
+void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len)
+{
+
+ struct {
+ struct agwpe_s hdr;
+ char info[AX25_MAX_INFO_LEN]; // I suppose there is potential for something larger.
+ // We'll cross that bridge if we ever come to it.
+ } reply;
+
+
+ memset (&reply.hdr, 0, sizeof(reply.hdr));
+ reply.hdr.portx = chan;
+ reply.hdr.datakind = 'D';
+ reply.hdr.pid = pid;
+
+ strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from));
+ strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to));
+
+ if (data_len < 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client);
+ data_len = 0;
+ }
+ else if (data_len > AX25_MAX_INFO_LEN) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client);
+ data_len = AX25_MAX_INFO_LEN;
+ }
+
+ memcpy (reply.info, data_ptr, data_len);
+ reply.hdr.data_len_NETLE = host2netle(data_len);
+
+ send_to_client (client, &reply);
+
+} /* end server_rec_conn_data */
+
/*-------------------------------------------------------------------
*
@@ -1137,7 +1187,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
@@ -1199,6 +1249,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]);
#endif
client_sock[client] = -1;
+ dlq_client_cleanup (client);
continue;
}
@@ -1240,6 +1291,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]);
#endif
client_sock[client] = -1;
+ dlq_client_cleanup (client);
return (0);
}
@@ -1258,6 +1310,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]);
#endif
client_sock[client] = -1;
+ dlq_client_cleanup (client);
return (0);
}
if (n >= 0) {
@@ -1581,38 +1634,34 @@ static THREAD_F cmd_listen_thread (void *arg)
{
struct {
struct agwpe_s hdr;
- char data;
+ char data; /* 1 = success, 0 = failure */
} reply;
- int j, ok;
+ int ok = 1;
+
+ // The protocol spec says it is an error to register the same one more than once.
+ // Too much trouble. Report success if the channel is valid.
+
+
+ int chan = cmd.hdr.portx;
+
+ if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) {
+ ok = 1;
+ dlq_register_callsign (cmd.hdr.call_from, chan, client);
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("AGW protocol error. Register callsign for invalid channel %d.\n", chan);
+ ok = 0;
+ }
+
memset (&reply, 0, sizeof(reply));
reply.hdr.datakind = 'X';
+ reply.hdr.portx = cmd.hdr.portx;
memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from));
reply.hdr.data_len_NETLE = host2netle(1);
-
- // Version 1.0.
- // Previously used sizeof(reply) but compiler rounded it up to next byte boundary.
- // That's why more cumbersome size expression is used.
-
- // The protocol spec says it is an error to register the same one more than once.
- // First make sure is it not already in there. Add if space available.
-
- if (server_callsign_registered_by_client(cmd.hdr.call_from) >= 0) {
- ok = 0;
- }
- else {
- ok = 0;
- for (j = 0; j < MAX_REG_CALLSIGNS && ok == 0; j++) {
- if (registered_callsigns[j][0] == '\0') {
- strlcpy (registered_callsigns[j], cmd.hdr.call_from, sizeof(registered_callsigns[j]));
- registered_by_client[j] = client;
- ok = 1;
- }
- }
- }
-
- reply.data = ok; /* 1 = success, 0 = failure */
+ reply.data = ok;
send_to_client (client, &reply);
}
break;
@@ -1620,13 +1669,15 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'x': /* Unregister CallSign */
{
- int j;
- for (j = 0; j < MAX_REG_CALLSIGNS; j++) {
- if (strcmp(registered_callsigns[j], cmd.hdr.call_from) == 0) {
- registered_callsigns[j][0] = '\0';
- registered_by_client[j] = -1;
- }
+ int chan = cmd.hdr.portx;
+
+ if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) {
+ dlq_unregister_callsign (cmd.hdr.call_from, chan, client);
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("AGW protocol error. Unregister callsign for invalid channel %d.\n", chan);
}
}
/* No reponse is expected. */
@@ -1648,7 +1699,7 @@ static THREAD_F cmd_listen_thread (void *arg)
int j;
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
- strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
+ strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_DESTINATION]));
if (cmd.hdr.datakind == 'c') {
pid = cmd.hdr.pid; /* non standard for NETROM, TCP/IP, etc. */
@@ -1675,15 +1726,9 @@ static THREAD_F cmd_listen_thread (void *arg)
}
}
-#if NEW14
+
dlq_connect_request (callsigns, num_calls, cmd.hdr.portx, client, pid);
-#else
- (void)pid; // suppress unused variable message.
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("\n");
- dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
- dw_printf ("Connected packet mode is not implemented.\n");
-#endif
+
}
break;
@@ -1696,15 +1741,9 @@ static THREAD_F cmd_listen_thread (void *arg)
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
-#if NEW14
+
dlq_xmit_data_request (callsigns, num_calls, cmd.hdr.portx, client, cmd.hdr.pid, cmd.data, netle2host(cmd.hdr.data_len_NETLE));
-#else
- (void)num_calls; // suppress unused variable warning.
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("\n");
- dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
- dw_printf ("Connected packet mode is not implemented.\n");
-#endif
+
}
break;
@@ -1716,15 +1755,9 @@ static THREAD_F cmd_listen_thread (void *arg)
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
-#if NEW14
+
dlq_disconnect_request (callsigns, num_calls, cmd.hdr.portx, client);
-#else
- (void)num_calls; // suppress unused variable warning.
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("\n");
- dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
- dw_printf ("Connected packet mode is not implemented.\n");
-#endif
+
}
break;
@@ -1839,30 +1872,4 @@ static THREAD_F cmd_listen_thread (void *arg)
} /* end send_to_client */
-/*-------------------------------------------------------------------
- *
- * Name: server_callsign_registered_by_client
- *
- * Purpose: See if given callsign was registered.
- *
- * Inputs: callsign
- *
- * Returns: >= 0 for the client number.
- * -1 for not found.
- *
- *--------------------------------------------------------------------*/
-
-int server_callsign_registered_by_client (char *callsign)
-{
- int j;
-
- for (j = 0; j < MAX_REG_CALLSIGNS; j++) {
- if (strcmp(registered_callsigns[j], callsign) == 0) {
- return (registered_by_client[j]);
- }
- }
- return (-1);
-
-} /* end server_callsign_registered_by_client */
-
/* end server.c */
diff --git a/server.h b/server.h
index dda80f3..08db571 100644
--- a/server.h
+++ b/server.h
@@ -22,5 +22,7 @@ void server_link_established (int chan, int client, char *remote_call, char *own
void server_link_terminated (int chan, int client, char *remote_call, char *own_call, int timeout);
+void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len);
+
/* end server.h */
diff --git a/symbols-new.txt b/symbols-new.txt
index 57646a3..c5502ef 100644
--- a/symbols-new.txt
+++ b/symbols-new.txt
@@ -1,4 +1,4 @@
-APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 22 Mar 2016
+APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 18 Oct 2016
---------------------------------------------------------------------
BACKGROUND: This file addresses new additions proposals (OVERLAYS)
@@ -7,6 +7,9 @@ document remains on the www.aprs.org/symbols/symbolsX.txt page, but
only has one line per symbol character. Since each of the symbols
can have up to 36 overlays, this gives us thousands of symbols codes.
+18 Oct 2016: Added Green, Yellow, Red for Flood gaures using G,Y,R
+ overlays + N for Normal. (on existing H2O symbol)
+ Added DIGIPEATERS
22 Mar 2016: Added A0 overlay circle for ALSTAR nodes
and V0 for VOIP combined echolink and IRLP
and P& for PSKmail node
@@ -226,6 +229,19 @@ BD = Bus Depot (new Aug 2014)
LD = LIght Rail or Subway (new Aug 2014)
SD = Seaport Depot (new Aug 2014)
+DIGIPEATERS
+/# - Generic digipeater
+1# - WIDE1-1 digipeater
+A# - Alternate input (i.e. 144.990MHz) digipeater
+E# - Emergency powered (assumed full normal digi)
+I# - I-gate equipped digipeater
+L# - WIDEn-N with path length trapping
+P# - PacComm
+S# - SSn-N digipeater (includes WIDEn-N)
+X# - eXperimental digipeater
+V# - Viscous https://github.com/PhirePhly/aprx/blob/master/ViscousDigipeater.README
+W# - WIDEn-N, SSn-N and Trapping
+
EMERGENCY: #!
/! = Police/Sheriff, etc
\! = Emergency!
@@ -389,6 +405,18 @@ Tu = Tanker
Cu = Chlorine Tanker
Hu = Hazardous
+WATER #w
+/w = Water Station or other H2O
+\w = flooding (or Avalanche/slides)
+Aw = Avalanche
+Gw = Green Flood Gauge
+Mw = Mud slide
+Nw = Normal flood gauge (blue)
+Rw = Red flood gauge
+Sw = Snow Blockage
+Yw = Yellow flood gauge
+
+
Anyone can use any overlay on any of the overlayable symbols for any
special purpose. We are not trying to document all possible such
diff --git a/tocalls.txt b/tocalls.txt
index 2e3e12d..b27dbb5 100644
--- a/tocalls.txt
+++ b/tocalls.txt
@@ -1,6 +1,10 @@
-APRS TO-CALL VERSION NUMBERS 29 Apr 2016
+APRS TO-CALL VERSION NUMBERS 14 Nov 2016
-------------------------------------------------------------------
WB4APR
+14 Nov 16 Added APINxx for PinPoint by AB0WV
+09 Nov 16 added APNICx for SQ5EKU http://sq5eku.blogspot.com/
+24 Oct 16 added APTKPT TrackPoint N0LP, removed APZTKP
+24 Aug 16 added APK004 for Kenwood THD-74
29 Apr 16 added APFPRS for FreeDV by Jeroen PE1RXQ
25 Feb 16 Added APCDS0 for Leon Lessing ZS6LMG's cell tracker
21 Jan 16 added APDNOx for APRSduino by DO3SWW
@@ -12,12 +16,7 @@ APRS TO-CALL VERSION NUMBERS 29 Apr 2016
27 Apr 15 added APZMAJ for Martyn M1MAJ DeLorme inReach Tracker
21 Apr 15 added APB2MF & APR2MF DL2MF - MF2APRS Radiosonde
06 Apr 15 added APAVT5 SainSonic AP510 - a 1watt tracker
-13 Mar 14 added APECAN Pecan Pico APRS Balloon Tracker
-02 Sep 14 added APSTMx W7QO's Balloon trackers
-21 Aug 14 added APSMSx Paul Defrusne's SMS gateway
-11 Aug 14 added APCWP8 John GM7HHB, WinphoneAPRS
-18 Dec 13 added APZWKR GM1WKR NetSked application
-
+. . . . . ...
11 Jan 12 added APYTxx for YagTracker and updated Yaesu APY008/350
In APRS, the AX.25 Destination address is not used for packet
@@ -86,6 +85,7 @@ a TOCALL number series:
APHTxx HMTracker by IU0AAC
API APICQx for ICQ
APICxx for HA9MCQ Pic IGate
+ APINxx for PinPoint by AB0WV
APJ APJAxx JavAPRS
APJExx JeAPRS
APJIxx jAPRSIgate
@@ -93,6 +93,7 @@ a TOCALL number series:
APJYnn KA2DDO Yet another APRS system
APK APK0xx Kenwood TH-D7's
APK003 Kenwood TH-D72
+ APK004 Kenwood TH-D74
APK1xx Kenwood D700's
APK102 Kenwood D710
APKRAM KRAMstuff.com - Mark. G7LEU
@@ -107,6 +108,7 @@ a TOCALL number series:
APN9xx Kantronics KPC-9612 Roms
APNAxx WB6ZSU's APRServe
APNDxx DIGI_NED
+ APNICx SQ5EKU http://sq5eku.blogspot.com/
APNK01 Kenwood D700 (APK101) type
APNK80 KAM version 8.0
APNKMP KAM+
@@ -144,14 +146,15 @@ a TOCALL number series:
APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK)
APSK63 APRS Messenger -over-PSK63
APSK25 APRS Messenger GMSK-250
- APSMSx Paul Defrusne's SMS gateway
+ APSMSx Paul Dufresne's SMSGTE - SMS Gateway
APSTMx for W7QO's Balloon trackers
APSTPO for N0AGI Satellite Tracking and Operations
- APT APTIGR TigerTrack
- APTTxx Tiny Track
- APT2xx Tiny Track II
+ APT APT2xx Tiny Track II
APT3xx Tiny Track III
APTAxx K4ATM's tiny track
+ APTIGR TigerTrack
+ APTKPT TrackPoint N0LP
+ APTTxx Tiny Track
APTWxx Byons WXTrac
APTVxx for ATV/APRN and SSTV applications
APU APU1xx UIview 16 bit applications
@@ -179,7 +182,7 @@ a TOCALL number series:
APZMAJ Martyn M1MAJ DeLorme inReach Tracker
APZMDR for HaMDR trackers - hessu * hes.iki.fi]
APZPAD Smart Palm
- APZTKP TrackPoint, Nick N0LP (Balloon tracking)
+ APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated)
APZWIT MAP27 radio (Mountain Rescue) EI7IG
APZWKR GM1WKR NetSked application
diff --git a/tq.c b/tq.c
index f98518b..fa787c7 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,6 +35,8 @@
*
*---------------------------------------------------------------*/
+#define TQ_C 1
+
#include "direwolf.h"
#include
@@ -120,7 +122,7 @@ void tq_init (struct audio_s *audio_config_p)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("tq_init ( %d )\n", nchan);
+ dw_printf ("tq_init ( )\n");
#endif
save_audio_config_p = audio_config_p;
@@ -187,11 +189,14 @@ void tq_init (struct audio_s *audio_config_p)
*
* Name: tq_append
*
- * Purpose: Add a packet to the end of the specified transmit queue.
+ * Purpose: Add an APRS packet to the end of the specified transmit queue.
+ *
+ * Connected mode is a little different. Use lm_data_request instead.
*
* Inputs: chan - Channel, 0 is first.
*
- * prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
+ * prio - Priority, use TQ_PRIO_0_HI for digipeated or
+ * TQ_PRIO_1_LO for normal.
*
* pp - Address of packet object.
* Caller should NOT make any references to
@@ -218,8 +223,11 @@ void tq_append (int chan, int prio, packet_t pp)
#if DEBUG
+ unsigned char *pinfo;
+ int info_len = ax25_get_info (pp, &pinfo);
+ if (info_len > 10) info_len = 10;
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("tq_append (chan=%d, prio=%d, pp=%p)\n", chan, prio, pp);
+ dw_printf ("tq_append (chan=%d, prio=%d, pp=%p) \"%*s\"\n", chan, prio, pp, info_len, (char*)pinfo);
#endif
@@ -242,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.
@@ -286,14 +296,6 @@ void tq_append (int chan, int prio, packet_t pp)
dw_mutex_lock (&tq_mutex);
-// was_empty = 1;
-// for (c=0; c 10) info_len = 10;
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_data_request (chan=%d, prio=%d, pp=%p) \"%*s\"\n", chan, prio, pp, info_len, (char*)pinfo);
+#endif
+
+
+ assert (prio >= 0 && prio < TQ_NUM_PRIO);
+
+ if (pp == NULL) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("INTERNAL ERROR: lm_data_request NULL packet pointer. Please report this!\n");
+ return;
+ }
+
+#if AX25MEMDEBUG
+
+ if (ax25memdebug_get()) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_data_request (chan=%d, prio=%d, seq=%d)\n", chan, prio, ax25memdebug_seq(pp));
+ }
+#endif
+
+ if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
+ ax25_delete(pp);
+ return;
+ }
+
+/*
+ * Is transmit queue out of control?
+ */
+
+ if (tq_count(chan,prio) > 250) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Warning: Transmit packet queue for channel %d is extremely long.\n", chan);
+ dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n");
+ }
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_data_request: enter critical section\n");
+#endif
+
+ dw_mutex_lock (&tq_mutex);
+
+
+ if (queue_head[chan][prio] == NULL) {
+ queue_head[chan][prio] = pp;
+ }
+ else {
+ plast = queue_head[chan][prio];
+ while ((pnext = ax25_get_nextp(plast)) != NULL) {
+ plast = pnext;
+ }
+ ax25_set_nextp (plast, pp);
+ }
+
+ dw_mutex_unlock (&tq_mutex);
+
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_data_request: left critical section\n");
+#endif
+
+ // Appendix C2a, from the Ax.25 protocol spec, says that a priority frame
+ // will start transmission. If not already transmitting, normal frames
+ // will pile up until LM-SEIZE Request starts transmission.
+
+
+// Erratum: It doesn't take long for that to fail.
+// We send SABM(e) frames to the transmit queue and the transmitter doesn't get activated.
+
+
+//NO! if (prio == TQ_PRIO_0_HI) {
+
+#if DEBUG
+ dw_printf ("lm_data_request (): about to wake up xmit thread.\n");
+#endif
+#if __WIN32__
+ SetEvent (wake_up_event[chan]);
+#else
+ if (xmit_thread_is_waiting[chan]) {
+ int err;
+
+ dw_mutex_lock (&(wake_up_mutex[chan]));
+
+ err = pthread_cond_signal (&(wake_up_cond[chan]));
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("lm_data_request: pthread_cond_signal err=%d", err);
+ perror ("");
+ exit (1);
+ }
+
+ dw_mutex_unlock (&(wake_up_mutex[chan]));
+ }
+#endif
+//NO! }
+
+} /* end lm_data_request */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: lm_seize_request
+ *
+ * Purpose: Force start of transmit even if transmit queue is empty.
+ *
+ * Inputs: chan - Channel, 0 is first.
+ *
+ * Description: 5.4.
+ *
+ * LM-SEIZE Request. The Data-link State Machine uses this primitive to request the
+ * Link Multiplexer State Machine to arrange for transmission at the next available
+ * opportunity. The Data-link State Machine uses this primitive when an
+ * acknowledgement must be made; the exact frame in which the acknowledgement
+ * is sent will be chosen when the actual time for transmission arrives.
+ *
+ * C2a.1
+ *
+ * PH-SEIZE Request. This primitive requests the simplex state machine to begin
+ * transmitting at the next available opportunity. When that opportunity has been
+ * identified (according to the CSMA/p-persistence algorithm included within), the
+ * transmitter started, a parameterized window provided for the startup of a
+ * conventional repeater (if required), and a parameterized time allowed for the
+ * synchronization of the remote station's receiver (known as TXDELAY in most
+ * implementations), then a PH-SEIZE Confirm primitive is returned to the link
+ * multiplexer.
+ *
+ * C3.1
+ *
+ * LM-SEIZE Request. This primitive requests the Link Multiplexer State Machine to
+ * arrange for transmission at the next available opportunity. The Data-link State
+ * Machine uses this primitive when an acknowledgment must be made, but the exact
+ * frame in which the acknowledgment will be sent will be chosen when the actual
+ * time for transmission arrives. The Link Multiplexer State Machine uses the LMSEIZE
+ * Confirm primitive to indicate that the transmission opportunity has arrived.
+ * After the Data-link State Machine has provided the acknowledgment, the Data-link
+ * State Machine gives permission to stop transmission with the LM Release Request
+ * primitive.
+ *
+ * C4.2
+ *
+ * LM-SEIZE Request. This primitive is used by the Data link State Machine to
+ * request the Link Multiplexer State Machine to arrange for transmission at the next
+ * available opportunity. The Data link State Machine uses this primitive when an
+ * acknowledgment must be made, but the exact frame in which the acknowledgment
+ * is sent will be chosen when the actual time for transmission arrives.
+ *
+ *
+ * Implementation: Add a null frame (i.e. length of 0) to give the process a kick.
+ * xmit.c needs to be smart enough to discard it.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void lm_seize_request (int chan)
+{
+ packet_t pp;
+ int prio = TQ_PRIO_1_LO;
+
+ packet_t plast;
+ packet_t pnext;
+
+
+#if DEBUG
+ unsigned char *pinfo;
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_seize_request (chan=%d)\n", chan);
+#endif
+
+ if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
+ return;
+ }
+
+ pp = ax25_new();
+
+#if AX25MEMDEBUG
+
+ if (ax25memdebug_get()) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_seize_request (chan=%d, seq=%d)\n", chan, ax25memdebug_seq(pp));
+ }
+#endif
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_seize_request: enter critical section\n");
+#endif
+
+ dw_mutex_lock (&tq_mutex);
+
+
+ if (queue_head[chan][prio] == NULL) {
+ queue_head[chan][prio] = pp;
+ }
+ else {
+ plast = queue_head[chan][prio];
+ while ((pnext = ax25_get_nextp(plast)) != NULL) {
+ plast = pnext;
+ }
+ ax25_set_nextp (plast, pp);
+ }
+
+ dw_mutex_unlock (&tq_mutex);
+
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("lm_seize_request: left critical section\n");
+#endif
+
+
+#if DEBUG
+ dw_printf ("lm_seize_request (): about to wake up xmit thread.\n");
+#endif
+#if __WIN32__
+ SetEvent (wake_up_event[chan]);
+#else
+ if (xmit_thread_is_waiting[chan]) {
+ int err;
+
+ dw_mutex_lock (&(wake_up_mutex[chan]));
+
+ err = pthread_cond_signal (&(wake_up_cond[chan]));
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("lm_seize_request: pthread_cond_signal err=%d", err);
+ perror ("");
+ exit (1);
+ }
+
+ dw_mutex_unlock (&(wake_up_mutex[chan]));
+ }
+#endif
+
+} /* end lm_seize_request */
+
+
/*-------------------------------------------------------------------
@@ -484,7 +819,61 @@ packet_t tq_remove (int chan, int prio)
}
#endif
return (result_p);
-}
+
+} /* end tq_remove */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: tq_peek
+ *
+ * Purpose: Take a peek at the next frame in the queue but don't remove it.
+ *
+ * Inputs: chan - Channel, 0 is first.
+ *
+ * prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
+ *
+ * Returns: Pointer to packet object or NULL.
+ *
+ * Caller should NOT destroy it because it is still in the queue.
+ *
+ *--------------------------------------------------------------------*/
+
+packet_t tq_peek (int chan, int prio)
+{
+
+ packet_t result_p;
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_peek(%d,%d) enter critical section\n", chan, prio);
+#endif
+
+ // I don't think we need critical region here.
+ //dw_mutex_lock (&tq_mutex);
+
+ result_p = queue_head[chan][prio];
+ // Just take a peek at the head. Don't remove it.
+
+ //dw_mutex_unlock (&tq_mutex);
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_remove(%d,%d) leave critical section, returns %p\n", chan, prio, result_p);
+#endif
+
+#if AX25MEMDEBUG
+
+ if (ax25memdebug_get() && result_p != NULL) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_remove (chan=%d, prio=%d) seq=%d\n", chan, prio, ax25memdebug_seq(result_p));
+ }
+#endif
+ return (result_p);
+
+} /* end tq_peek */
+
/*-------------------------------------------------------------------
diff --git a/tq.h b/tq.h
index 92ee3ba..4b5beb8 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/waypoint.c b/waypoint.c
index 96123f7..6b2e9fb 100644
--- a/waypoint.c
+++ b/waypoint.c
@@ -502,7 +502,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt
* These would not be parsed properly:
*
* $PKWDWPL,150753,V,4237.14,N,07120.83,W,,,190316,,test3,/,*1B
- * $PKWDWPL,150758,V,4237.14,N,07120.83,W,,,190316,,test4,/**3B
+ * $PKWDWPL,150758,V,4237.14,N,07120.83,W,,,190316,,test4,/ **3B
*
* We perform the usual substitution and the other end would
* need to change them back after extracting from NMEA sentence.
diff --git a/xid.c b/xid.c
index b0b1ca8..f018422 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
@@ -49,28 +49,10 @@
#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=1, selective_reject=2, selective_reject_reject=3 } rej;
-
- enum modulo_e {modulo_8 = 8, modulo_128 = 128} modulo;
-
- int i_field_length_rx; /* In bytes. XID has it in bits. */
-
- int window_size_rx;
-
- int ack_timer; /* "T1" in mSec. */
-
- int retries; /* "N1" */
-};
+#include "xid.h"
@@ -102,13 +84,20 @@ struct ax25_param_s {
/*-------------------------------------------------------------------
*
- * Name: ...
+ * Name: xid_parse
*
- * Purpose: ...
+ * Purpose: Decode information part of XID frame into individual values.
*
- * Inputs: ...
+ * Inputs: info - pointer to information part of frame.
*
- * Outputs: ...
+ * info_len - Number of bytes in information part of frame.
+ * Could be 0.
+ *
+ * desc_size - Size of desc. 100 is good.
+ *
+ * Outputs: result - Structure with extracted values.
+ *
+ * desc - Text description for troubleshooting.
*
* Returns: 1 for mostly successful (with possible error messages), 0 for failure.
*
@@ -120,37 +109,44 @@ struct ax25_param_s {
*--------------------------------------------------------------------*/
-
-
-
-int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
+int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, char *desc, int desc_size)
{
unsigned char *p;
int group_len;
char stemp[64];
- char debug_msg[256];
- result->full_duplex = 0;
+ strlcpy (desc, "", desc_size);
- // Default is implicit reject for pre version 2.2 but we wouldn't be here in that case.
- result->rej = selective_reject;
+// What should we do when some fields are missing?
- result->modulo = modulo_8;
+// The AX.25 v2.2 protocol spec says, for most of these,
+// "If this field is not present, the current values are retained."
- result->i_field_length_rx = 256; // bytes here but converted to bits during encoding.
+// We set the values to our usual G_UNKNOWN to mean undefined and let the caller deal with it.
- // Default is 4 for pre version 2.2 but we wouldn't be here in that case.
- result->window_size_rx = result->modulo == modulo_128 ? 32 : 7;
- result->ack_timer = 3000;
- result->retries = 10;
+ result->full_duplex = G_UNKNOWN;
+ result->rej = G_UNKNOWN;
+ result->modulo = G_UNKNOWN;
+ result->i_field_length_rx = G_UNKNOWN;
+ result->window_size_rx = G_UNKNOWN;
+ result->ack_timer = G_UNKNOWN;
+ result->retries = G_UNKNOWN;
+
+
+/* Information field is optional but that seems pretty lame. */
+
+ if (info_len == 0) {
+ return (1);
+ }
p = info;
if (*p != FI_Format_Indicator) {
text_color_set (DW_COLOR_ERROR);
- dw_printf ("XID error: First byte of info field should be Format Indicator, %d.\n", FI_Format_Indicator);
+ dw_printf ("XID error: First byte of info field should be Format Indicator, %02x.\n", FI_Format_Indicator);
+ dw_printf ("XID info part: %02x %02x %02x %02x %02x ... length=%d\n", info[0], info[1], info[2], info[3], info[4], info_len);
return 0;
}
p++;
@@ -192,38 +188,46 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
if (pval & PV_Classes_Procedures_Half_Duplex && ! (pval & PV_Classes_Procedures_Full_Duplex)) {
result->full_duplex = 0;
+ strlcat (desc, "Half-Duplex ", desc_size);
}
else if (pval & PV_Classes_Procedures_Full_Duplex && ! (pval & PV_Classes_Procedures_Half_Duplex)) {
result->full_duplex = 1;
+ strlcat (desc, "Full-Duplex ", desc_size);
}
else {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Expected one of Half or Full Duplex be set.\n");
+ result->full_duplex = 0;
}
break;
case PI_HDLC_Optional_Functions:
- if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
+ if ((pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
result->rej = selective_reject_reject; /* Both bits set */
+ strlcat (desc, "SREJ-REJ ", desc_size);
}
- else if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && ! (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
+ else if ((pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && ! (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
result->rej = implicit_reject; /* Only REJ is set */
+ strlcat (desc, "REJ ", desc_size);
}
else if ( ! (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
result->rej = selective_reject; /* Only SREJ is set */
+ strlcat (desc, "SREJ ", desc_size);
}
else {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Expected one or both of REJ, SREJ to be set.\n");
}
- if (pval & PV_HDLC_Optional_Functions_Modulo_8 && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) {
+ if ((pval & PV_HDLC_Optional_Functions_Modulo_8) && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) {
result->modulo = modulo_8;
+ strlcat (desc, "modulo-8 ", desc_size);
}
- else if (pval & PV_HDLC_Optional_Functions_Modulo_128 && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) {
+ else if ((pval & PV_HDLC_Optional_Functions_Modulo_128) && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) {
result->modulo = modulo_128;
+ strlcat (desc, "modulo-128 ", desc_size);
}
else {
text_color_set (DW_COLOR_ERROR);
@@ -256,6 +260,9 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
result->i_field_length_rx = pval / 8;
+ snprintf (stemp, sizeof(stemp), "I-Field-Length-Rx=%d ", result->i_field_length_rx);
+ strlcat (desc, stemp, desc_size);
+
if (pval & 0x7) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: I Field Length Rx, %d, is not a whole number of bytes.\n", pval);
@@ -267,22 +274,34 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
result->window_size_rx = pval;
- if (pval < 1 || pval >= result->modulo) {
+ snprintf (stemp, sizeof(stemp), "Window-Size-Rx=%d ", result->window_size_rx);
+ strlcat (desc, stemp, desc_size);
+
+ if (pval < 1 || pval > 127) {
text_color_set (DW_COLOR_ERROR);
- dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru %d.\n", pval, result->modulo-1);
- result->window_size_rx = result->modulo == modulo_128 ? 32 : 7;
+ dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru 127.\n", pval);
+ result->window_size_rx = 127;
+ // Let the caller deal with modulo 8 consideration.
}
-//continue here.
+//continue here with more error checking.
break;
case PI_Ack_Timer:
result->ack_timer = pval;
+
+ snprintf (stemp, sizeof(stemp), "Ack-Timer=%d ", result->ack_timer);
+ strlcat (desc, stemp, desc_size);
+
break;
- case PI_Retries:
+ case PI_Retries: // Is it retrys or retries?
result->retries = pval;
+
+ snprintf (stemp, sizeof(stemp), "Retries=%d ", result->retries);
+ strlcat (desc, stemp, desc_size);
+
break;
default:
@@ -338,9 +357,10 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
*
* Outputs: info - Information part of XID frame.
* Does not include the control byte.
- * Supply 32 bytes to be safe.
+ * Use buffer of 40 bytes just to be safe.
*
* Returns: Number of bytes in the info part. Should be at most 27.
+ * Again, provide a larger space just to be safe in case this ever changes.
*
* Description: 6.3.2 "Parameter negotiation occurs at any time. It is accomplished by sending
* the XID command frame and receiving the XID response frame. Implementations of
@@ -359,35 +379,57 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
* Both TNCs set themselves up based on the values used in the XID response. Negotiation
* is used by Classes of Procedures, HDLC Optional Functions, Acknowledge Timer and Retries."
*
+ * Comment: I have a problem with "... occurs at any time." What if we were in the middle
+ * of transferring a large file with k=32 then along comes XID which says switch to modulo 8?
+ *
*--------------------------------------------------------------------*/
-int xid_encode (struct ax25_param_s *param, unsigned char *info)
+int xid_encode (struct xid_param_s *param, unsigned char *info)
{
unsigned char *p;
int len;
int x;
+ int m = 0;
+
p = info;
*p++ = FI_Format_Indicator;
*p++ = GI_Group_Identifier;
*p++ = 0;
- *p++ = 0x17;
+
+ m = 4; // classes of procedures
+ m += 5; // HDLC optional features
+ if (param->i_field_length_rx != G_UNKNOWN) m += 4;
+ if (param->window_size_rx != G_UNKNOWN) m += 3;
+ if (param->ack_timer != G_UNKNOWN) m += 4;
+ if (param->retries != G_UNKNOWN) m += 3;
+
+ *p++ = m; // 0x17 if all present.
+
+// "Classes of Procedures" has half / full duplex.
+
+// We always send this.
*p++ = PI_Classes_of_Procedures;
*p++ = 2;
x = PV_Classes_Procedures_Balanced_ABM;
- if (param->full_duplex)
+ if (param->full_duplex == 1)
x |= PV_Classes_Procedures_Full_Duplex;
- else
+ else // includes G_UNKNOWN
x |= PV_Classes_Procedures_Half_Duplex;
*p++ = (x >> 8) & 0xff;
*p++ = x & 0xff;
+// "HDLC Optional Functions" contains REJ/SREJ & modulo 8/128.
+
+// We always send this.
+// Watch out for unknown values and do something reasonable.
+
*p++ = PI_HDLC_Optional_Functions;
*p++ = 3;
@@ -396,7 +438,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
PV_HDLC_Optional_Functions_16_bit_FCS |
PV_HDLC_Optional_Functions_Synchronous_Tx;
- if (param->rej == implicit_reject || param->rej == selective_reject_reject)
+ if (param->rej == implicit_reject || param->rej == selective_reject_reject || param->rej == G_UNKNOWN)
x |= PV_HDLC_Optional_Functions_REJ_cmd_resp;
if (param->rej == selective_reject || param->rej == selective_reject_reject)
@@ -404,13 +446,18 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
if (param->modulo == modulo_128)
x |= PV_HDLC_Optional_Functions_Modulo_128;
- else
+ else // includes G_UNKNOWN
x |= PV_HDLC_Optional_Functions_Modulo_8;
*p++ = (x >> 16) & 0xff;
*p++ = (x >> 8) & 0xff;
*p++ = x & 0xff;
+// The rest are skipped if undefined values.
+
+// "I Field Length Rx" - max I field length acceptable to me.
+// This is in bits. 8191 would be max number of bytes to fit in field.
+
if (param->i_field_length_rx != G_UNKNOWN) {
*p++ = PI_I_Field_Length_Rx;
*p++ = 2;
@@ -419,12 +466,16 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
*p++ = x & 0xff;
}
+// "Window Size Rx"
+
if (param->window_size_rx != G_UNKNOWN) {
*p++ = PI_Window_Size_Rx;
*p++ = 1;
*p++ = param->window_size_rx;
}
+// "Ack Timer" milliseconds. We could handle up to 65535 here.
+
if (param->ack_timer != G_UNKNOWN) {
*p++ = PI_Ack_Timer;
*p++ = 2;
@@ -432,6 +483,8 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
*p++ = param->ack_timer & 0xff;
}
+// "Retries."
+
if (param->retries != G_UNKNOWN) {
*p++ = PI_Retries;
*p++ = 1;
@@ -439,7 +492,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
}
len = p - info;
- assert (len <= 27);
+
return (len);
} /* end xid_encode */
@@ -508,14 +561,19 @@ static unsigned char example[27] = {
int main (int argc, char *argv[]) {
- struct ax25_param_s param;
- struct ax25_param_s param2;
+ struct xid_param_s param;
+ struct xid_param_s param2;
int n;
- unsigned char info[40];
+ unsigned char info[40]; // Currently max of 27 but things can change.
+ char desc[100]; // 80 is not adequate.
+
/* parse example. */
- n = xid_parse (example, sizeof(example), ¶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);
@@ -548,40 +606,96 @@ int main (int argc, char *argv[]) {
param.modulo = modulo_8;
param.i_field_length_rx = 2048;
param.window_size_rx = 3;
- param.ack_timer = 3000;
- param.retries = 10;
+ param.ack_timer = 1234;
+ param.retries = 12;
n = xid_encode (¶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_rx == 3);
- assert (param2.ack_timer == 3000);
- assert (param2.retries == 10);
+ assert (param2.ack_timer == 1234);
+ assert (param2.retries == 12);
-/* Finally the third possbility for rej. */
+/* The third possbility for rej. We don't use this. */
param.full_duplex = 0;
param.rej = selective_reject;
param.modulo = modulo_8;
- param.i_field_length_rx = 256;
+ param.i_field_length_rx = 61;
param.window_size_rx = 4;
- param.ack_timer = 3000;
- param.retries = 10;
+ param.ack_timer = 5555;
+ param.retries = 9;
n = xid_encode (¶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 == 256);
+ assert (param2.i_field_length_rx == 61);
assert (param2.window_size_rx == 4);
- assert (param2.ack_timer == 3000);
- assert (param2.retries == 10);
+ assert (param2.ack_timer == 5555);
+ assert (param2.retries == 9);
+
+
+/* Specify some and not others. */
+
+ param.full_duplex = 0;
+ param.rej = selective_reject;
+ param.modulo = modulo_8;
+ param.i_field_length_rx = G_UNKNOWN;
+ param.window_size_rx = G_UNKNOWN;
+ param.ack_timer = 999;
+ param.retries = G_UNKNOWN;
+
+ n = xid_encode (¶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 == 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 == G_UNKNOWN);
+ assert (param2.modulo == G_UNKNOWN);
+ assert (param2.i_field_length_rx == G_UNKNOWN);
+ assert (param2.window_size_rx == G_UNKNOWN);
+ assert (param2.ack_timer == G_UNKNOWN);
+ assert (param2.retries == G_UNKNOWN);
+
text_color_set (DW_COLOR_REC);
dw_printf ("XID test: Success.\n");
diff --git a/xid.h b/xid.h
new file mode 100644
index 0000000..80f34a8
--- /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 {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 6419ade..ba996b7 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
@@ -72,6 +72,7 @@
#include "ptt.h"
#include "dtime_now.h"
#include "morse.h"
+#include "xid.h"
@@ -107,6 +108,9 @@ static int xmit_bits_per_sec[MAX_CHANS]; /* Data transmission rate. */
static int g_debug_xmit_packet; /* print packet in hexadecimal form for debugging. */
+// TODO: When this was first written, bits/sec was same as baud.
+// Need to revisit this for PSK modes where they are not the same.
+
#define BITS_TO_MS(b,ch) (((b)*1000)/xmit_bits_per_sec[(ch)])
@@ -130,8 +134,9 @@ static dw_mutex_t audio_out_dev_mutex[MAX_ADEVS];
-static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist);
-static void xmit_ax25_frames (int c, int p, packet_t pp);
+static int wait_for_clear_channel (int channel, int slotttime, int persist);
+static void xmit_ax25_frames (int c, int p, packet_t pp, int max_bundle);
+static int send_one_frame (int c, int p, packet_t pp);
static void xmit_speech (int c, packet_t pp);
static void xmit_morse (int c, packet_t pp, int wpm);
@@ -346,6 +351,62 @@ void xmit_set_txtail (int channel, int value)
}
}
+
+/*-------------------------------------------------------------------
+ *
+ * Name: frame_flavor
+ *
+ * Purpose: Separate frames into different flavors so we can decide
+ * which can be bundled into a single transmission and which should
+ * be sent separately.
+ *
+ * Inputs: pp - Packet object.
+ *
+ * Returns: Flavor, one of:
+ *
+ * FLAVOR_SPEECH - Destination address is SPEECH.
+ * FLAVOR_MORSE - Destination address is MORSE.
+ * FLAVOR_APRS_NEW - APRS original, i.e. not digipeating.
+ * FLAVOR_APRS_DIGI - APRS digipeating.
+ * FLAVOR_OTHER - Anything left over, i.e. connected mode.
+ *
+ *--------------------------------------------------------------------*/
+
+typedef enum flavor_e { FLAVOR_APRS_NEW, FLAVOR_APRS_DIGI, FLAVOR_SPEECH, FLAVOR_MORSE, FLAVOR_OTHER } flavor_t;
+
+static flavor_t frame_flavor (packet_t pp)
+{
+
+ if (ax25_is_aprs (pp)) { // UI frame, PID 0xF0.
+ // It's unfortunate APRS did not use its own special PID.
+
+ char dest[AX25_MAX_ADDR_LEN];
+
+ ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest);
+
+ if (strcmp(dest, "SPEECH") == 0) {
+ return (FLAVOR_SPEECH);
+ }
+
+ if (strcmp(dest, "MORSE") == 0) {
+ return (FLAVOR_MORSE);
+ }
+
+ /* Is there at least one digipeater AND has first one been used? */
+ /* I could be the first in the list or later. Doesn't matter. */
+
+ if (ax25_get_num_repeaters(pp) >= 1 && ax25_get_h(pp,AX25_REPEATER_1)) {
+ return (FLAVOR_APRS_DIGI);
+ }
+
+ return (FLAVOR_APRS_NEW);
+ }
+
+ return (FLAVOR_OTHER);
+
+} /* end frame_flavor */
+
+
/*-------------------------------------------------------------------
*
* Name: xmit_thread
@@ -367,6 +428,9 @@ void xmit_set_txtail (int channel, int value)
* rather than waiting random times to avoid collisions.
* The KPC-3 configuration option for this is "UIDWAIT OFF". (?)
*
+ * AX.25 connected mode also has a couple cases
+ * where "expedited" frames are sent.
+ *
* Low Priority -
*
* Other packets are sent after a random wait time
@@ -382,6 +446,11 @@ void xmit_set_txtail (int channel, int value)
* each channel has its own thread.
* Add speech capability.
*
+ * Version 1.4: Rearranged logic for bundling multiple frames into a single transmission.
+ *
+ * The rule is that Speech, Morse Code, and APRS digipeated frames
+ * are all sent separately. The rest can be bundled.
+ *
*--------------------------------------------------------------------*/
#if __WIN32__
@@ -390,119 +459,126 @@ static unsigned __stdcall xmit_thread (void *arg)
static void * xmit_thread (void *arg)
#endif
{
- int c = (int)(long)arg; // channel number.
+ int chan = (int)(long)arg; // channel number.
packet_t pp;
- int p;
+ int prio;
int ok;
-/*
- * These are for timing of a transmission.
- * All are in usual unix time (seconds since 1/1/1970) but higher resolution
- */
-
while (1) {
- tq_wait_while_empty (c);
+ tq_wait_while_empty (chan);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread, channel %d: woke up\n", c);
#endif
-
- for (p=0; p 0) ? (ssid * 2) : MORSE_DEFAULT_WPM;
// This is a bit of a hack so we don't respond too quickly for APRStt.
// It will be sent in high priority queue while a beacon wouldn't.
// Add a little delay so user has time release PTT after sending #.
// This and default txdelay would give us a second.
- if (p == TQ_PRIO_0_HI) {
+ if (prio == TQ_PRIO_0_HI) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("APRStt morse xmit delay hack...\n");
SLEEP_MS (700);
}
+ xmit_morse (chan, pp, wpm);
+ break;
- xmit_morse (c, pp, wpm);
- }
- else {
- xmit_ax25_frames (c, p, pp);
- }
+ case FLAVOR_APRS_DIGI:
+ xmit_ax25_frames (chan, prio, pp, 1); /* 1 means don't bundle */
+ break;
- dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(c)]));
+ case FLAVOR_APRS_NEW:
+ case FLAVOR_OTHER:
+ default:
+ xmit_ax25_frames (chan, prio, pp, 256);
+ break;
}
- else {
+
+ // Corresponding lock is in wait_for_clear_channel.
+
+ dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(chan)]));
+ }
+ else {
/*
* Timeout waiting for clear channel.
* Discard the packet.
* Display with ERROR color rather than XMIT color.
*/
- char stemp[1024]; /* max size needed? */
- int info_len;
- unsigned char *pinfo;
+ char stemp[1024]; /* max size needed? */
+ int info_len;
+ unsigned char *pinfo;
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Waited too long for clear channel. Discarding packet below.\n");
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Waited too long for clear channel. Discarding packet below.\n");
- ax25_format_addrs (pp, stemp);
+ ax25_format_addrs (pp, stemp);
- info_len = ax25_get_info (pp, &pinfo);
+ info_len = ax25_get_info (pp, &pinfo);
- text_color_set(DW_COLOR_INFO);
- dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("[%d%c] ", chan, (prio==TQ_PRIO_0_HI) ? 'H' : 'L');
- dw_printf ("%s", stemp); /* stations followed by : */
- ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
- dw_printf ("\n");
- ax25_delete (pp);
+ dw_printf ("%s", stemp); /* stations followed by : */
+ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
+ dw_printf ("\n");
+ ax25_delete (pp);
- } /* wait for clear channel. */
- } /* for high priority then low priority */
- }
- }
+ } /* wait for clear channel error. */
+ } /* Have pp */
+ } /* while queue not empty */
+ } /* while 1 */
return 0; /* unreachable but quiet the warning. */
@@ -517,28 +593,31 @@ static void * xmit_thread (void *arg)
* Purpose: After we have a clear channel, and possibly waited a random time,
* we transmit one or more frames.
*
- * Inputs: c - Channel number.
+ * Inputs: chan - Channel number.
*
- * p - Priority of the queue.
+ * prio - Priority of the first frame.
+ * Subsequent frames could be different.
*
* pp - Packet object pointer.
* It will be deleted so caller should not try
* to reference it after this.
*
+ * max_bundle - Max number of frames to bundle into one transmission.
+ *
* Description: Turn on transmitter.
* Send flags for TXDELAY time.
* Send the first packet, given by pp.
- * Possibly send more packets from the same queue.
+ * Possibly send more packets from either queue.
* Send flags for TXTAIL time.
* Turn off transmitter.
*
*
- * How many frames in one transmission?
+ * How many frames in one transmission? (for APRS)
*
* Should we send multiple frames in one transmission if we
* have more than one sitting in the queue? At first I was thinking
* this would help reduce channel congestion. I don't recall seeing
- * anything in the specifications allowing or disallowing multiple
+ * anything in the APRS specifications allowing or disallowing multiple
* frames in one transmission. I can think of some scenarios
* where it might help. I can think of some where it would
* definitely be counter productive.
@@ -558,21 +637,27 @@ static void * xmit_thread (void *arg)
* Version 0.9: Earlier versions always sent one frame per transmission.
* This was fine for APRS but more and more people are now
* using this as a KISS TNC for connected protocols.
- * Rather than having a MAXFRAME configuration file item,
+ * Rather than having a configuration file item,
* we try setting the maximum number automatically.
* 1 for digipeated frames, 7 for others.
*
+ * Version 1.4: Lift the limit. We could theoretically have a window size up to 127.
+ * If another section pumps out that many quickly we shouldn't
+ * break it up here. Empty out both queues with some exceptions.
+ *
+ * Digipeated APRS, Speech, and Morse code should have
+ * their own separate transmissions.
+ * Everything else can be bundled together.
+ * Different priorities can share a single transmission.
+ * Once we have control of the channel, we might as well keep going.
+ * [High] Priority frames will always go to head of the line,
+ *
*--------------------------------------------------------------------*/
-static void xmit_ax25_frames (int c, int p, packet_t pp)
+static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle)
{
- unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
- int flen;
- char stemp[1024]; /* max size needed? */
- int info_len;
- unsigned char *pinfo;
int pre_flags, post_flags;
int num_bits; /* Total number of bits in transmission */
/* including all flags and bit stuffing. */
@@ -580,8 +665,7 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
int already;
int wait_more;
- int maxframe; /* Maximum number of frames for one transmission. */
- int numframe; /* Number of frames sent during this transmission. */
+ int numframe = 0; /* Number of frames sent during this transmission. */
/*
* These are for timing of a transmission.
@@ -593,49 +677,6 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
int nb;
- maxframe = (p == TQ_PRIO_0_HI) ? 1 : 7;
-
-
-/*
- * Print trasmitted packet. Prefix by channel and priority.
- * Do this before we get into the time critical part.
- */
- ax25_format_addrs (pp, stemp);
- info_len = ax25_get_info (pp, &pinfo);
- text_color_set(DW_COLOR_XMIT);
- dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
- dw_printf ("%s", stemp); /* stations followed by : */
-
-/* Demystify non-APRS. Use same format for received frames in direwolf.c. */
-
- if ( ! ax25_is_aprs(pp)) {
- ax25_frame_type_t ftype;
- cmdres_t cr;
- char desc[32];
- int pf;
- int nr;
- int ns;
-
- ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
- (void)ftype;
-
- dw_printf ("(%s)", desc);
- }
-
- ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
- dw_printf ("\n");
- (void)ax25_check_addresses (pp);
-
-/* Optional hex dump of packet. */
-
- if (g_debug_xmit_packet) {
-
- text_color_set(DW_COLOR_DEBUG);
- dw_printf ("------\n");
- ax25_hex_dump (pp);
- dw_printf ("------\n");
- }
-
/*
* Turn on transmitter.
* Start sending leading flag bytes.
@@ -647,26 +688,26 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]);
+ dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", chan, xmit_bits_per_sec[chan]);
#endif
- ptt_set (OCTYPE_PTT, c, 1);
+ ptt_set (OCTYPE_PTT, chan, 1);
- pre_flags = MS_TO_BITS(xmit_txdelay[c] * 10, c) / 8;
- num_bits = hdlc_send_flags (c, pre_flags, 0);
+ pre_flags = MS_TO_BITS(xmit_txdelay[chan] * 10, chan) / 8;
+ num_bits = hdlc_send_flags (chan, pre_flags, 0);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[c], pre_flags, num_bits);
+ dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[chan], pre_flags, num_bits);
#endif
/*
* Transmit the frame.
- */
- flen = ax25_pack (pp, fbuf);
- assert (flen >= 1 && flen <= sizeof(fbuf));
- nb = hdlc_send_frame (c, fbuf, flen);
+ */
+
+ nb = send_one_frame (chan, prio, pp);
+
num_bits += nb;
- numframe = 1;
+ if (nb > 0) numframe++;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
@@ -674,68 +715,84 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
ax25_delete (pp);
/*
- * Additional packets if available and not exceeding max.
+ * See if we can bundle additional frames into this transmission.
*/
- while (numframe < maxframe && tq_count (c,p) > 0) {
-
- pp = tq_remove (c, p);
-#if DEBUG
- text_color_set(DW_COLOR_DEBUG);
- dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp);
-#endif
- ax25_format_addrs (pp, stemp);
- info_len = ax25_get_info (pp, &pinfo);
- text_color_set(DW_COLOR_XMIT);
- dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
- dw_printf ("%s", stemp); /* stations followed by : */
- ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
- dw_printf ("\n");
- (void)ax25_check_addresses (pp);
-
- if (g_debug_xmit_packet) {
- text_color_set(DW_COLOR_DEBUG);
- dw_printf ("------\n");
- ax25_hex_dump (pp);
- dw_printf ("------\n");
- }
+ int done = 0;
+ while (numframe < max_bundle && ! done) {
/*
- * Transmit the frame.
- */
- flen = ax25_pack (pp, fbuf);
- assert (flen >= 1 && flen <= sizeof(fbuf));
- nb = hdlc_send_frame (c, fbuf, flen);
- num_bits += nb;
- numframe++;
+ * 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_APRS_DIGI:
+ default:
+ done = 1; // not eligible for bundling.
+ break;
+
+ case FLAVOR_APRS_NEW:
+ case FLAVOR_OTHER:
+
+ pp = tq_remove (chan, prio);
#if DEBUG
- text_color_set(DW_COLOR_DEBUG);
- dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, pp);
#endif
- ax25_delete (pp);
+
+ nb = send_one_frame (chan, prio, pp);
+
+ num_bits += nb;
+ if (nb > 0) numframe++;
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
+#endif
+ ax25_delete (pp);
+
+ break;
+ }
+ }
+ else {
+ done = 1;
+ }
}
/*
* Need TXTAIL because we don't know exactly when the sound is done.
*/
- post_flags = MS_TO_BITS(xmit_txtail[c] * 10, c) / 8;
- nb = hdlc_send_flags (c, post_flags, 1);
+ post_flags = MS_TO_BITS(xmit_txtail[chan] * 10, chan) / 8;
+ nb = hdlc_send_flags (chan, post_flags, 1);
num_bits += nb;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[c], post_flags, nb, num_bits);
+ dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[chan], post_flags, nb, num_bits);
#endif
/*
* While demodulating is CPU intensive, generating the tones is not.
- * Example: on the RPi, with 50% of the CPU taken with two receive
+ * Example: on the RPi model 1, with 50% of the CPU taken with two receive
* channels, a transmission of more than a second is generated in
* about 40 mS of elapsed real time.
*/
- audio_wait(ACHAN2ADEV(c));
+ audio_wait(ACHAN2ADEV(chan));
/*
* Ideally we should be here just about the time when the audio is ending.
@@ -744,7 +801,7 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
* Calculate how long the frame(s) should take in milliseconds.
*/
- duration = BITS_TO_MS(num_bits, c);
+ duration = BITS_TO_MS(num_bits, chan);
/*
* See how long it has been since PTT was turned on.
@@ -786,12 +843,123 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
dw_printf ("xmit_thread: Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", (int) ((time_now - time_ptt) * 1000.), duration);
#endif
- ptt_set (OCTYPE_PTT, c, 0);
+ ptt_set (OCTYPE_PTT, chan, 0);
} /* end xmit_ax25_frames */
+/*-------------------------------------------------------------------
+ *
+ * Name: send_one_frame
+ *
+ * Purpose: Send one AX.25 frame.
+ *
+ * Inputs: c - Channel number.
+ *
+ * p - Priority.
+ *
+ * pp - Packet object pointer. Caller will delete it.
+ *
+ * Returns: Number of bits transmitted.
+ *
+ * Description: Caller is responsible for activiating PTT, TXDELAY,
+ * deciding how many frames can be in one transmission,
+ * deactivating PTT.
+ *
+ *--------------------------------------------------------------------*/
+
+
+static int send_one_frame (int c, int p, packet_t pp)
+{
+ unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
+ int flen;
+ char stemp[1024]; /* max size needed? */
+ int info_len;
+ unsigned char *pinfo;
+ int nb;
+
+
+ if (ax25_is_null_frame(pp)) {
+ return(0);
+ }
+
+ ax25_format_addrs (pp, stemp);
+ info_len = ax25_get_info (pp, &pinfo);
+ text_color_set(DW_COLOR_XMIT);
+ dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
+ dw_printf ("%s", stemp); /* stations followed by : */
+
+/* Demystify non-APRS. Use same format for received frames in direwolf.c. */
+
+ if ( ! ax25_is_aprs(pp)) {
+ ax25_frame_type_t ftype;
+ cmdres_t cr;
+ char desc[80];
+ int pf;
+ int nr;
+ int ns;
+
+ ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
+
+ dw_printf ("(%s)", desc);
+
+ if (ftype == frame_type_U_XID) {
+ struct xid_param_s param;
+ char info2text[100];
+
+ xid_parse (pinfo, info_len, ¶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 <= sizeof(fbuf));
+
+ int send_invalid_fcs2 = 0;
+
+ if (save_audio_config_p->xmit_error_rate != 0) {
+ float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
+
+ if (save_audio_config_p->xmit_error_rate / 100.0 > r) {
+ send_invalid_fcs2 = 1;
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Intentionally sending invalid CRC for frame above. Xmit Error rate = %d per cent.\n", save_audio_config_p->xmit_error_rate);
+ }
+ }
+
+ nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2);
+ return (nb);
+
+} /* end send_one_frame */
+
+
+
+
/*-------------------------------------------------------------------
*
* Name: xmit_speech
@@ -814,8 +982,6 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
static void xmit_speech (int c, packet_t pp)
{
-
-
int info_len;
unsigned char *pinfo;
@@ -932,8 +1098,6 @@ int xmit_speak_it (char *script, int c, char *orig_msg)
static void xmit_morse (int c, packet_t pp, int wpm)
{
-
-
int info_len;
unsigned char *pinfo;
@@ -960,15 +1124,7 @@ static void xmit_morse (int c, packet_t pp, int wpm)
* Purpose: Wait for the radio channel to be clear and any
* additional time for collision avoidance.
*
- *
- *
- * Inputs: channel - Radio channel number.
- *
- * nowait - Should be true for the high priority queue
- * (packets being digipeated). This will
- * allow transmission immediately when the
- * channel is clear rather than waiting a
- * random amount of time.
+ * Inputs: chan - Radio channel number.
*
* slottime - Amount of time to wait for each iteration
* of the waiting algorithm. 10 mSec units.
@@ -977,14 +1133,12 @@ static void xmit_morse (int c, packet_t pp, int wpm)
*
* Returns: 1 for OK. 0 for timeout.
*
- * Description:
- *
- * New in version 1.2: also obtain a lock on audio out device.
+ * Description: New in version 1.2: also obtain a lock on audio out device.
*
* Transmit delay algorithm:
*
* Wait for channel to be clear.
- * Return if nowait is true.
+ * If anything in high priority queue, bail out of the following.
*
* Wait slottime * 10 milliseconds.
* Generate an 8 bit random number in range of 0 - 255.
@@ -1015,17 +1169,13 @@ static void xmit_morse (int c, packet_t pp, int wpm)
#define WAIT_TIMEOUT_MS (60 * 1000)
#define WAIT_CHECK_EVERY_MS 10
-static int wait_for_clear_channel (int channel, int nowait, int slottime, int persist)
+static int wait_for_clear_channel (int chan, int slottime, int persist)
{
- int r;
- int n;
-
-
- n = 0;
+ int n = 0;
start_over_again:
- while (hdlc_rec_data_detect_any(channel)) {
+ while (hdlc_rec_data_detect_any(chan)) {
SLEEP_MS(WAIT_CHECK_EVERY_MS);
n++;
if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {
@@ -1033,41 +1183,52 @@ start_over_again:
}
}
-//TODO1.2: rethink dwait.
+//TODO: rethink dwait.
/*
* Added in version 1.2 - for transceivers that can't
* turn around fast enough when using squelch and VOX.
*/
- if (save_audio_config_p->achan[channel].dwait > 0) {
- SLEEP_MS (save_audio_config_p->achan[channel].dwait * 10);
+ if (save_audio_config_p->achan[chan].dwait > 0) {
+ SLEEP_MS (save_audio_config_p->achan[chan].dwait * 10);
}
- if (hdlc_rec_data_detect_any(channel)) {
+ if (hdlc_rec_data_detect_any(chan)) {
goto start_over_again;
}
- if ( ! nowait) {
+/*
+ * Wait random time.
+ * Proceed to transmit sooner if anything shows up in high priority queue.
+ */
+ while (tq_peek(chan, TQ_PRIO_0_HI) == NULL) {
+ int r;
- while (1) {
+ SLEEP_MS (slottime * 10);
- SLEEP_MS (slottime * 10);
-
- if (hdlc_rec_data_detect_any(channel)) {
- goto start_over_again;
- }
-
- r = rand() & 0xff;
- if (r <= persist) {
- break;
- }
+ if (hdlc_rec_data_detect_any(chan)) {
+ goto start_over_again;
}
+
+ r = rand() & 0xff;
+ if (r <= persist) {
+ break;
+ }
}
-// TODO1.2
+/*
+ * This is to prevent two channels from transmitting at the same time
+ * thru a stereo audio device.
+ * We are not clever enough to combine two audio streams.
+ * They must go out one at a time.
+ * Documentation recommends using separate audio device for each channel rather than stereo.
+ * That also allows better use of multiple cores for receiving.
+ */
- while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(channel)]))) {
+// TODO: review this.
+
+ while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(chan)]))) {
SLEEP_MS(WAIT_CHECK_EVERY_MS);
n++;
if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {