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)) {