direwolf/src/ax25_link.c

7131 lines
239 KiB
C
Raw Normal View History

//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016, 2017, 2018, 2023 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Name: ax25_link
*
* Purpose: Data Link State Machine.
* Establish connections and transfer data in the proper
* order with retries.
*
* Using the term "data link" is rather unfortunate because it causes
* confusion to someone familiar with the OSI networking model.
* This corresponds to the layer 4 transport, not layer 2 data link.
*
* 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 Machine 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 Machine Peer
* ----- ---------- ------------- ----
*
* 0 disc
* <--- SABME or SABM
* UA --->
* <--- CONN Ind.
* 3 conn
*
*
* *note:
*
* After carefully studying the v2.2 spec, I expected a 2.0 implementation to send
* FRMR in response to SABME. This is important. If a v2.2 implementation
* gets FRMR, in response to SABME, it switches to v2.0 and sends SABM instead.
*
* The v2.0 protocol spec, section 2.3.4.3.3.1, states that FRMR should be sent when
* an invalid or not implemented command is received. That all fits together.
*
* In testing, I found that the KPC-3+ sent DM.
*
* I can see where they might get that idea.
* The v2.0 spec says that when in disconnected mode, it should respond to any
* command other than SABM or UI frame with a DM response with P/F set to 1.
* I think it was implemented wrong. 2.3.4.3.3.1 should take precedence.
*
* The TM-D710 does absolutely nothing in response to SABME.
* Not responding at all is just plain wrong. To work around this, I put
* in a special hack to start sending SABM after a certain number of
* SABME go unanswered. There is more discussion in the User Guide.
*
* References:
* * AX.25 Amateur Packet-Radio Link-Layer Protocol Version 2.0, October 1984
*
* https://www.tapr.org/pub_ax25.html
* http://lea.hamradio.si/~s53mv/nbp/nbp/AX25V20.pdf
*
* At first glance, they look pretty much the same, but the second one
* is more complete with 4 appendices, including a state table.
*
* * 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 had known about this version, with
* several corrections, before doing most of the implementation. :-(
*
* The title page still says July 1998 so it's not immediately obvious this
* is different than the one on the TAPR site.
*
* * AX.25 ... Latest revision, in progress.
*
* http://www.nj7p.org/
*
* This is currently being revised in cooperation with software authors
* who have noticed some issues during 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.
*
* Errata: 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 "erratum" 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/
*
* Version 1.4, released April 2017:
*
* Features tested reasonably well:
*
* Connect to/from a KPC-3+ and send I frames in both directions.
* Same with TM-D710A.
* 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.
*
* Version 1.5, December 2017:
*
* Implemented Multi Selective Reject.
* More efficient generation of SREJ frames.
* Reduced number of duplicate I frames sent for both REJ and SREJ cases.
* Avoided unnecessary RR when I frame could take care of the ack.
* (This led to issue 132 where outgoing data sometimes got stuck in the queue.)
*
*------------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#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.
// Should have command line options instead of changing source and recompiling.
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.
2023-05-11 20:29:45 +00:00
char addrs[AX25_MAX_ADDRS][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 relevant.
#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.
enum srej_e srej_enable; // Is other end capable of processing SREJ? (Am I allowed to send it?)
// Starts out as 'srej_none' for v2.0 or 'srej_single' for v2.2.
// Can be changed to 'srej_multi' with XID exchange.
// Should be used only with modulo 128. (Is this enforced?)
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" or "EMAXFRAME" 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)
// This is used only when receiving an I frame, in states 3 & 4, SREJ not enabled.
// When an I frame has an unexpected N(S),
// - if not already set, set it and send REJ.
// When an I frame with expected N(S) is received, clear it.
// This would prevent us from sending additional REJ while
// waiting for result from first one.
// What happens if the REJ gets lost? Is it resent somehow?
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 acknowledgement 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.
// The name is misleading because these are just blocks of
// data, not "I frames" at this point. The name comes from
// the protocol specification.
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__); \
} \
assert (S->vs >= 0 && S->vs < S->modulo); \
}
// 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__); \
} \
assert (S->va >= 0 && S->va < S->modulo); \
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__); \
} \
assert (S->vr >= 0 && S->vr < S->modulo); \
}
#define SET_RC(n) { S->rc = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("rc = %d at %s %d, state = %d\n", S->rc, __func__, __LINE__, S->state); \
} \
}
//TODO: Make this a macro so we can simplify calls yet keep debug output if something goes wrong.
#if 0
#define AX25MODULO(n) ax25modulo((n), S->modulo, __FILE__, __func__, __LINE__)
static int ax25modulo(int n, int m, const char *file, const char *func, int line)
#else
static int AX25MODULO(int n, int m, const char *file, const char *func, int line)
#endif
{
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));
}
// Test whether we can send more or if we need to wait
// because we have reached 'maxframe' outstanding frames.
// Argument must be 'S'.
#define WITHIN_WINDOW_SIZE(x) (x->vs != AX25MODULO(x->va + x->k_maxframe, x->modulo, __FILE__, __func__, __LINE__))
// 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 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, unsigned char *info_ptr, int info_len);
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);
if (p == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FATAL ERROR: Out of memory.\n");
exit (EXIT_FAILURE);
}
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_outstanding_frames_request - (mine) Ask about outgoing queue for a link.
// 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;
int old_version;
int n;
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;
// See if destination station is in list for v2.0 only.
old_version = 0;
for (n = 0; n < g_misc_config_p->v20_count && ! old_version; n++) {
if (strcmp(E->addrs[AX25_DESTINATION],g_misc_config_p->v20_addrs[n]) == 0) {
old_version = 1;
}
}
if (old_version || 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:
// Erratum: The protocol spec says "requeue." If we put disconnect req back in the
// queue we will probably get it back again here while still in same state.
// I don't think we would want to delay it until the next state transition.
// Suppose someone tried to connect to another station, which is not responding, and decided to cancel
// before all of the SABMe retries were used up. I think we would want to transmit a DISC, send a disc
// notice to the user, and go directly into disconnected state, rather than into awaiting release.
// New code v1.7 dev, May 6 2023
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: In progress connection attempt to %s terminated by user.\n", S->stream_id, S->addrs[PEERCALL]);
discard_i_queue (S);
SET_RC(0);
int p1 = 1;
int nopid0 = 0;
packet_t pp15 = ax25_u_frame (S->addrs, S->num_addr, cr_cmd, frame_type_U_DISC, p1, nopid0, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp15);
STOP_T1; // started in establish_data_link.
STOP_T3; // probably don't need.
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
break;
case state_2_awaiting_release:
{
// We have previously started the disconnect sequence and are waiting
// for a UA from the other guy. Meanwhile, the application got
// impatient and sent us another disconnect request. What should
// we do? Ignore it and let the disconnect sequence run its
// course? Or should we complete the sequence without waiting
// for the other guy to ack?
// 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.
// Erratum: Shouldn't we inform the user when going to disconnected state?
// Notifying the application, here, is my own enhancement.
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__);
}
break;
case state_3_connected:
case state_4_timer_recovery:
discard_i_queue (S);
SET_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.
*
* Version 1.6: Bug 252. Segmentation was occurring for a V2.0 link. From the spec:
* "The receipt of an XID response from the other station establishes that both
* stations are using AX.25 version 2.2 or higher and enables the use of the
* segmenter/reassembler and selective reject."
* "The segmenter/reassembler procedure is only enabled if both stations on the
* link are using AX.25 version 2.2 or higher."
*
* The Segmenter Ready State SDL has no decision based on protocol version.
*
*------------------------------------------------------------------------------*/
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;
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;
}
#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b))
// Erratum: Don't do V2.2 segmentation for a V2.0 link.
// In this case, we can just split it into multiple frames not exceeding the specified max size.
// Hopefully the receiving end treats it like a stream and doesn't care about length of each frame.
if (S->modulo == 8) {
int num_frames = 0;
int remaining_len = E->txdata->len;
int offset = 0;
while (remaining_len > 0) {
int this_len = MIN(remaining_len, S->n1_paclen);
cdata_t *new_txdata = cdata_new(E->txdata->pid, E->txdata->data + offset, this_len);
data_request_good_size (S, new_txdata);
offset += this_len;
remaining_len -= this_len;
num_frames++;
}
if (num_frames != DIVROUNDUP(E->txdata->len, S->n1_paclen) || remaining_len != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, num frames = %d, remaining len = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, num_frames, remaining_len);
}
cdata_delete (E->txdata);
E->txdata = NULL;
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.
int 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;
}
int orig_offset = 0;
int remaining_len = E->txdata->len;
// First segment.
int seglen;
struct {
char header; // 0x80 + number of segments to follow.
char original_pid;
char segdata[AX25_N1_PACLEN_MAX];
} first_segment;
cdata_t *new_txdata;
nseg_to_follow--;
first_segment.header = 0x80 | nseg_to_follow;
first_segment.original_pid = E->txdata->pid;
seglen = MIN(S->n1_paclen - 2, remaining_len);
if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > (int)(sizeof(first_segment.segdata))) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen);
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2);
data_request_good_size (S, new_txdata);
orig_offset += seglen;
remaining_len -= seglen;
// Subsequent segments.
do {
struct {
char header; // Number of segments to follow.
char segdata[AX25_N1_PACLEN_MAX];
} subsequent_segment;
nseg_to_follow--;
subsequent_segment.header = nseg_to_follow;
seglen = MIN(S->n1_paclen - 1, remaining_len);
if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > (int)(sizeof(subsequent_segment.segdata))) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen);
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1);
data_request_good_size (S, new_txdata);
orig_offset += seglen;
remaining_len -= seglen;
} while (nseg_to_follow > 0);
if (remaining_len != 0 || orig_offset != E->txdata->len) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n",
__LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len);
}
cdata_delete (E->txdata);
E->txdata = NULL;
} /* end dl_data_request */
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata)
{
switch (S->state) {
case state_0_disconnected:
case state_2_awaiting_release:
/*
* Discard it.
*/
cdata_delete (txdata);
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
/*
* Erratum?
* The flow chart shows "push on I frame queue" if layer 3 initiated
* is NOT set. This seems backwards but I don't understand enough yet
* to make a compelling argument that it is wrong.
* Implemented as in flow chart.
* TODO: Get better understanding of what'layer_3_initiated' means.
*/
if (S->layer_3_initiated) {
cdata_delete (txdata);
break;
}
// otherwise fall thru.
case state_3_connected:
case state_4_timer_recovery:
/*
* "push on I frame queue"
* Append to the end would have been a better description because push implies a stack.
*/
if (S->i_frame_queue == NULL) {
txdata->next = NULL;
S->i_frame_queue = txdata;
}
else {
cdata_t *plast = S->i_frame_queue;
while (plast->next != NULL) {
plast = plast->next;
}
txdata->next = NULL;
plast->next = txdata;
}
break;
}
// v1.5 change in strategy.
// New I frames, not sent yet, are delayed until after processing anything in the received transmission.
// Give the transmit process a kick unless other side is busy or we have reached our window size.
// Previously we had i_frame_pop_off_queue here which would start sending new stuff before we
// finished dealing with stuff already in progress.
switch (S->state) {
case state_3_connected:
case state_4_timer_recovery:
if ( ( ! S->peer_receiver_busy ) &&
WITHIN_WINDOW_SIZE(S) ) {
S->acknowledge_pending = 1;
lm_seize_request (S->chan);
}
break;
default:
break;
}
} /* 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);
if (r == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("FATAL ERROR: Out of memory.\n");
exit (EXIT_FAILURE);
}
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_outstanding_frames_request
*
* Purpose: Client app wants to know how many frames are still on their way
* to other station. This is handy for flow control. We would like
* to keep the pipeline filled sufficiently to take advantage of a
* large window size (MAXFRAMES). It is also good to know that the
* the last packet sent was actually received before we commence
* the disconnect.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Outputs: This gets back to the AGW server which sends the 'Y' reply.
*
* Description: This is the sum of:
* - Incoming connected data, from application still in the queue.
* - I frames which have been transmitted but not yet acknowledged.
*
* Confusion: https://github.com/wb2osz/direwolf/issues/427
*
* There are different, inconsistent versions of the protocol spec.
*
* One of them simply has:
*
* CallFrom is our call
* CallTo is the call of the other station
*
* A more detailed version has the same thing in the table of fields:
*
* CallFrom 10 bytes Our CallSign
* CallTo 10 bytes Other CallSign
*
* (My first implementation went with that.)
*
* HOWEVER, shortly after that, is contradictory information:
*
* Careful must be exercised to fill correctly both the CallFrom
* and CallTo fields to match the ones of an existing connection,
* otherwise AGWPE wont return any information at all from this query.
*
* The order of the CallFrom and CallTo is not trivial, it should
* reflect the order used to start the connection, so
*
* * If we started the connection CallFrom=US and CallTo=THEM
* * If the other end started the connection CallFrom=THEM and CallTo=US
*
* This seems to make everything unnecessarily more complicated.
* We should only care about the stream going from the local station to the
* remote station. Why would it matter who reqested the link? The state
* machine doesn't even contain this information so the TNC doesn't know.
* The client app interface needs to behave differently for the two cases.
*
* The new code, below, May 2023, should handle both of those cases.
*
*------------------------------------------------------------------------------*/
void dl_outstanding_frames_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
const int ok_to_create = 0; // must exist already.
int reversed_addrs = 0;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_outstanding_frames_request ( to %s )\n", E->addrs[PEERCALL]);
}
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
if (S != NULL) {
reversed_addrs = 0;
}
else {
// Try swapping the addresses.
// this is communicating with the client app, not over the air,
// so we don't need to worry about digipeaters.
2023-05-11 20:29:45 +00:00
char swapped[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
memset (swapped, 0, sizeof(swapped));
strlcpy (swapped[PEERCALL], E->addrs[OWNCALL], sizeof(swapped[PEERCALL]));
strlcpy (swapped[OWNCALL], E->addrs[PEERCALL], sizeof(swapped[OWNCALL]));
S = get_link_handle (swapped, E->num_addr, E->chan, E->client, ok_to_create);
if (S != NULL) {
reversed_addrs = 1;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't get outstanding frames for %s -> %s, chan %d\n", E->addrs[OWNCALL], E->addrs[PEERCALL], E->chan);
server_outstanding_frames_reply (E->chan, E->client, E->addrs[OWNCALL], E->addrs[PEERCALL], 0);
return;
}
}
// Add up these
//
// cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet.
// // Linked list.
// // The name is misleading because these are just blocks of
// // data, not "I frames" at this point. The name comes from
// // the protocol specification.
//
// 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 count1 = 0;
cdata_t *incoming;
for (incoming = S->i_frame_queue; incoming != NULL; incoming = incoming->next) {
count1++;
}
int count2 = 0;
int k;
for (k = 0; k < S->modulo; k++) {
if (S->txdata_by_ns[k] != NULL) {
count2++;
}
}
if (reversed_addrs) {
// Other end initiated the link.
server_outstanding_frames_reply (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], count1 + count2);
}
else {
server_outstanding_frames_reply (S->chan, S->client, S->addrs[OWNCALL], S->addrs[PEERCALL], count1 + count2);
}
} // end dl_outstanding_frames_request
/*------------------------------------------------------------------------------
*
* 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 specified 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.
*
*------------------------------------------------------------------------------*/
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;
}
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.
*
* Version 1.5: Originally this only invoked inquiry_response to provide an ack if not already
* taken care of by an earlier frame in this transmission.
* After noticing the unnecessary I frame duplication and differing N(R) in the same
* transmission, I came to the conclusion that we should delay sending of new
* (not resends as a result of rej or srej) frames until after after processing
* of everything in the incoming transmission.
* The protocol spec simply has "I frame pops off queue" without any indication about
* what might trigger this event.
*
*------------------------------------------------------------------------------*/
void lm_seize_confirm (dlq_item_t *E)
{
assert (E->chan >= 0 && E->chan < MAX_CHANS);
ax25_dlsm_t *S;
for (S = list_head; S != NULL; S = S->next) {
if (E->chan == S->chan) {
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:
// v1.5 change in strategy.
// New I frames, not sent yet, are delayed until after processing anything in the received transmission.
// Previously we started sending new frames, from the client app, as soon as they arrived.
// Now, we first take care of those in progress before throwing more into the mix.
i_frame_pop_off_queue(S);
// Need an RR if we didn't have I frame send the necessary ack.
if (S->acknowledge_pending) {
S->acknowledge_pending = 0;
enquiry_response (S, frame_not_AX25, 0);
}
// Implementation difference: The flow chart for state 3 has LM-RELEASE Request here.
// I don't think I need it because the transmitter will turn off
// automatically once the queue is empty.
// Erratum: The original spec had LM-SEIZE request here, for state 4, which didn't seem right.
// The 2006 revision has LM-RELEASE Request so states 3 & 4 are the same.
break;
}
}
}
} /* lm_seize_confirm */
/*------------------------------------------------------------------------------
*
* Name: lm_data_indication
*
* Purpose: We received some sort of frame over the radio.
*
* Inputs: E - Event from the queue.
* Caller is responsible for freeing it.
*
* Description: First determine if is of interest to me. Two cases:
*
* (1) We already have a link handle for (from-addr, to-addr, channel).
* This could have been set up by an outgoing connect request.
*
* (2) It is addressed to one of the registered callsigns. This would
* catch the case of incoming connect requests. The APRS MYCALL
* is not involved at all. The attached client app might have
* much different ideas about what the station is called or
* aliases it might respond to.
*
*------------------------------------------------------------------------------*/
void lm_data_indication (dlq_item_t *E)
{
ax25_frame_type_t ftype;
char desc[80];
cmdres_t cr;
int pf;
int nr;
int ns;
ax25_dlsm_t *S;
int client_not_applicable = -1;
int n;
int any_unused_digi;
if (E->pp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal Error, packet pointer is null. %s %s %d\n", __FILE__, __func__, __LINE__);
return;
}
E->num_addr = ax25_get_num_addr(E->pp);
// Digipeating is not done here so consider only those with no unused digipeater addresses.
any_unused_digi = 0;
for (n = AX25_REPEATER_1; n < E->num_addr; n++) {
if ( ! ax25_get_h(E->pp, n)) {
any_unused_digi = 1;
}
}
if (any_unused_digi) {
if (s_debug_radio) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_indication (%d, %s>%s) - ignore due to unused digi address.\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]);
}
return;
}
// Copy addresses from frame into event structure.
for (n = 0; n < E->num_addr; n++) {
ax25_get_addr_with_ssid (E->pp, n, E->addrs[n]);
}
if (s_debug_radio) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_indication (%d, %s>%s)\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]);
}
// Look for existing, or possibly create new, link state matching addresses and channel.
// In most cases, we can ignore the frame if we don't have a corresponding
// data link state machine. However, we might want to create a new one for SABM or SABME.
// get_link_handle will check to see if the destination matches my address.
// TODO: This won't work right because we don't know the modulo yet.
// Maybe we should have a shorter form that only returns the frame type.
// That is all we need at this point.
ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns);
S = get_link_handle (E->addrs, E->num_addr, E->chan, client_not_applicable,
(ftype == frame_type_U_SABM) | (ftype == frame_type_U_SABME));
if (S == NULL) {
return;
}
/*
* There is not a reliable way to tell if a frame, out of context, has modulo 8 or 128
* sequence numbers. This needs to be supplied from the data link state machine.
*
* We can't do this until we get the link handle.
*/
ax25_set_modulo (E->pp, S->modulo);
/*
* Now we need to use ax25_frame_type again because the previous results, for nr and ns, might be wrong.
*/
ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns);
// Gather statistics useful for testing.
if (ftype <= frame_not_AX25) {
S->count_recv_frame_type[ftype]++;
}
switch (ftype) {
case frame_type_I:
if (cr != cr_cmd) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error S: %s must be COMMAND.\n", S->stream_id, desc);
}
break;
case frame_type_S_RR:
case frame_type_S_RNR:
case frame_type_S_REJ:
if (cr != cr_cmd && cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc);
}
break;
case frame_type_U_SABME:
case frame_type_U_SABM:
case frame_type_U_DISC:
if (cr != cr_cmd) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND.\n", S->stream_id, desc);
}
break;
// Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both.
// The underlying X.25 spec clearly says it is response 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 possible 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 selective frame(s) repeat
{
unsigned char *info_ptr;
int info_len;
info_len = ax25_get_info (E->pp, &info_ptr);
srej_frame (S, cr, pf, nr, info_ptr, info_len);
}
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;
}
// An incoming frame might have ack'ed frames we sent or indicated peer is no longer busy.
// Rather than putting this test in many places, where those conditions, may have changed,
// we will try to catch them all on this single path.
// Start transmission if we now have some outgoing data ready to go.
// (Added in 1.5 beta 3 for issue 132.)
if ( S->i_frame_queue != NULL &&
(S->state == state_3_connected || S->state == state_4_timer_recovery) &&
( ! S->peer_receiver_busy ) &&
WITHIN_WINDOW_SIZE(S) ) {
//S->acknowledge_pending = 1;
lm_seize_request (S->chan);
}
} /* 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.
// Erratum: SDL asks: Is information field length <= N1 (paclen).
// (github issue 102 - Thanks to KK6WHJ for pointing this out.)
// Just because we are limiting the size of our transmitted data, it doesn't mean
// that the other end will be doing the same. With v2.2, the XID frame can be
// used to negotiate a maximum info length but with v2.0, there is no way for the
// other end to know our paclen value.
if (info_len >= 0 && info_len <= AX25_MAX_INFO_LEN) {
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_ackd" 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
// Erratum: v1.5 - My addition.
// I noticed that we sometimes got stuck in state 4 and rc crept up slowly even though
// we received 'I' frames with N(R) values indicating that the other side received everything
// that we sent. Eventually rc could reach the limit and we would get an error.
// If we are in state 4, and other guy ack'ed last I frame we sent, transition to state 3.
// We had a similar situation for RR/RNR for cases other than response, F=1.
if (S->state == state_4_timer_recovery && S->va == S->vs) {
STOP_T1;
select_t1_value (S);
START_T3;
SET_RC(0);
enter_new_state (S, state_3_connected, __func__, __LINE__);
}
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, NULL, 0);
// 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 { // Own receiver not busy.
i_frame_continued (S, p, ns, pid, info_ptr, info_len);
}
}
else { // N(R) not in expected range.
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, AX25_MAX_INFO_LEN);
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
*
* (Erratum: SREJ is only response with F bit.)
*
* 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)
*
* (Erratum: REJ/SREJ should not be mixed. Basic (mod 8) allows only REJ.
* Extended (mod 128) gives you a choice of one or the other for a link.)
*
* 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 accidentally
// 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, NULL, 0);
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, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
}
else if (S->srej_enable == srej_none) {
// 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 response, 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 noticeable.
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, NULL, 0);
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, NULL, 0);
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.
// In version 1.4:
// We end up sending more SREJ than necessary and and get back redundant information. Example:
// When we see 113 missing, we ask for a resend.
// When we see 115 & 116 missing, a cumulative SREJ asks for everything.
// The other end dutifully sends 113 twice.
//
// [0.4] DW1>DW0:(SREJ res, n(r)=113, f=0)
// [0.4] DW1>DW0:(SREJ res, n(r)=113, f=1)<0xe6><0xe8>
//
// [0L] DW0>DW1:(I cmd, n(s)=113, n(r)=11, p=0, pid=0xf0)0114 send data<0x0d>
// [0L] DW0>DW1:(I cmd, n(s)=113, n(r)=11, p=0, pid=0xf0)0114 send data<0x0d>
// [0L] DW0>DW1:(I cmd, n(s)=115, n(r)=11, p=0, pid=0xf0)0116 send data<0x0d>
// [0L] DW0>DW1:(I cmd, n(s)=116, n(r)=11, p=0, pid=0xf0)0117 send data<0x0d>
// Version 1.5:
// Don't generate duplicate requests for gaps in the same transmission.
// Ideally, we might wait until carrier drops and then use one Multi-SREJ for entire transmission but
// we will keep that for another day.
// Probably need a flag similar to acknowledge_pending (or ask_resend_count, here) and the ask_for_resend array.
// It could then be processed first in lm_seize_confirm.
int ask_for_resend[128];
int ask_resend_count = 0;
int x;
// Version 1.5
// Erratum: AX.25 says use F=0 here. Doesn't make sense.
// We would want to set F when sending N(R) = V(R).
// int allow_f1 = 0; // F=1 from X.25 2.4.6.4 b) 3)
int allow_f1 = 1; // F=1 from X.25 2.4.6.4 b) 3)
// send only for this gap, not cumulative from V(R).
int last = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__);
int first = last;
while (first != S->vr && S->rxdata_by_ns[AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) {
first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__);
}
x = first;
do {
ask_for_resend[ask_resend_count++] = AX25MODULO(x, S->modulo, __FILE__, __func__, __LINE__);
x = AX25MODULO(x + 1, S->modulo, __FILE__, __func__, __LINE__);
} while (x != AX25MODULO(last + 1, S->modulo, __FILE__, __func__, __LINE__));
send_srej_frames (S, ask_for_resend, ask_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 ask_for_resend[128];
int ask_resend_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__)) {
ask_for_resend[ask_resend_count++] = i;
}
send_srej_frames (S, ask_for_resend, ask_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 ask_resend_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 calculating what to put in SREJ, %s line %d\n", __func__, __LINE__);
dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, ask_resend_count=%d\n", S->vr, ns, selective_reject_exception(S), first, ask_resend_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;
}
ask_resend_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 ask_resend_count could be 0. e.g. We got 4 rather than 7 in this example.
if (ask_resend_count > 0) {
int ask_for_resend[128];
int n;
int allow_f1 = 1;
for (n = 0; n < ask_resend_count; n++) {
ask_for_resend[n] = AX25MODULO(first + n, S->modulo, __FILE__, __func__, __LINE__);;
}
send_srej_frames (S, ask_for_resend, ask_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).
*
* Version 1.5: The X.25 protocol spec allows additional sequence numbers in one frame
* by using the INFO part.
* By default that feature is off but can be negotiated with XID.
* We should be able to use this between two direwolf stations while
* maintaining compatibility with the original AX.25 v2.2.
*
*------------------------------------------------------------------------------*/
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 exception=%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");
}
// Multi-SREJ - Use info part for additional sequence number(s) instead of sending separate SREJ for each.
if (S->srej_enable == srej_multi && count > 1) {
unsigned char info[128];
int info_len = 0;
for (i = 1; i < count; i++) { // skip first one
if (resend[i] < 0 || resend[i] >= S->modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, additional nr=%d, modulo=%d, %s line %d\n", resend[i], S->modulo, __func__, __LINE__);
}
// There is also a form to specify a range but I don't
// think it is worth the effort to generate it. Maybe later.
if (S->modulo == 8) {
info[info_len++] = resend[i] << 5;
}
else {
info[info_len++] = resend[i] << 1;
}
}
f = 0;
nr = resend[0];
f = allow_f1 && (nr == S->vr);
// Possibly set if we are asking for the next after
// the last one received in contiguous order.
// This could only apply to the first in
// the list so this would not go in the loop.
if (f) { // In this case the other end is being
// informed of my V(R) so no additional
// RR etc. is needed.
// TODO: Need to think about this.
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, info, info_len);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
return;
}
// Multi-SREJ not enabled. Send separate SREJ for each desired sequence number.
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, NULL, 0);
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);
}
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) {
// RR/RNR Response with F==1.
if (s_debug_retry) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("rr_rnr_frame (), Response, f=%d, line %d, state=%d, good nr, calling check_i_frame_ackd\n", pf, __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;
SET_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 {
// RR/RNR command, either P value.
// RR/RNR response, F==0
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);
// Erratum: v1.5 - my addition.
// I noticed that we sometimes got stuck in state 4 and rc crept up slowly even though
// we received RR frames with N(R) values indicating that the other side received everything
// that we sent. Eventually rc could reach the limit and we would get an error.
// If we are in state 4, and other guy ack'ed last I frame we sent, transition to state 3.
// The same thing was done for receiving I frames after check_i_frame_ackd.
// Thought: Could we simply call check_i_frame_ackd, for consistency, rather than only setting V(A)?
if (cr == cr_res && pf == 0) {
if (S->vs == S->va) { // all caught up with ack from other guy.
STOP_T1;
select_t1_value (S);
START_T3;
SET_RC(0);
enter_new_state (S, state_3_connected, __func__, __LINE__);
}
}
}
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;
SET_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).
* info - Information field, used only for Multi-SREJ
* info_len - Information field length, bytes.
*
* 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 together 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 int resend_for_srej (ax25_dlsm_t *S, int nr, unsigned char *info, int info_len);
static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int f, int nr, unsigned char *info, int info_len)
{
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. (Seems correct.)
// 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);
int num_resent = resend_for_srej (S, nr, info, info_len);
if (num_resent) {
// 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;
}
// 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:
if (s_debug_timers) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("state 4 timer recovery, %s %d nr=%d, f=%d\n", __func__, __LINE__, nr, f);
}
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_debug_timers) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("state 4 timer recovery, %s %d set v(a)= %d\n", __func__, __LINE__, S->va);
}
}
if (S->vs == S->va) { // ACKs all caught up. Back to state 3.
// Erratum: I think this is unreachable.
// If the other side is asking for I frame with sequence X, it must have
// received X+1 or later. That means my V(S) must be X+2 or greater.
// So, I don't think we can ever have V(S) == V(A) here.
// If we were to remove the 'if' test and true part, case 4 would then
// be exactly the same as state 4. We need to rely on RR to get us
// back to state 3.
START_T3;
SET_RC(0); // My enhancement. See Erratum note in select_t1_value.
enter_new_state (S, state_3_connected, __func__, __LINE__);
// text_color_set(DW_COLOR_ERROR);
// dw_printf ("state 4 timer recovery, go to state 3 \n");
}
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.
//text_color_set(DW_COLOR_ERROR);
//dw_printf ("state 4 timer recovery, send requested frame(s) \n");
int num_resent = resend_for_srej (S, nr, info, info_len);
if (num_resent) {
// 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 // 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: resend_for_srej
*
* Purpose: Resend the I frame(s) specified in SREJ response.
*
* Inputs: S - Data Link State Machine.
* nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S).
* info - Information field, might contain additional sequence numbers for Multi-SREJ.
* info_len - Information field length, bytes.
*
* Returns: Number of frames sent. Should be at least one.
*
* Description: Simply resend requested frame(s).
* The calling context will worry about the F bit and other state stuff.
*
*------------------------------------------------------------------------------*/
static int resend_for_srej (ax25_dlsm_t *S, int nr, unsigned char *info, int info_len)
{
cmdres_t cr = cr_cmd;
int i_frame_nr = S->vr;
int i_frame_ns = nr;
int p = 0;
int num_resent = 0;
// Resend I frame with N(S) equal to the N(R) in the SREJ.
// Additional sequence numbers can be in optional information part.
cdata_t *txdata = S->txdata_by_ns[i_frame_ns];
if (txdata != NULL) {
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);
num_resent++;
}
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, i_frame_ns);
}
// Multi-SREJ if there is an information part.
int j;
for (j = 0; j < info_len; j++) {
// We can have a single sequence number like this:
// xxx00000 (mod 8)
// xxxxxxx0 (mod 128)
// or we can have span (mod 128 only) like this, with the first and last:
// xxxxxxx1
// xxxxxxx1
//
// Note that the sequence number is shifted left by one
// and if the LSB is set, there should be two adjacent bytes
// with it set.
if (S->modulo == 8) {
i_frame_ns = (info[j] >> 5) & 0x07; // no provision for span.
}
else {
i_frame_ns = (info[j] >> 1) & 0x7f; // TODO: test LSB and possible loop here.
}
txdata = S->txdata_by_ns[i_frame_ns];
if (txdata != NULL) {
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);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
num_resent++;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: INTERNAL ERROR for Multi-SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, i_frame_ns);
}
}
return (num_resent);
} /* end resend_for_srej */
/*------------------------------------------------------------------------------
*
* 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;
SET_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);
SET_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 replies 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 appropriate.
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);
}
SET_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[150];
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, &param, desc, sizeof(desc));
if (ok) {
negotiation_response (S, &param);
xlen = xid_encode (&param, xinfo, res);
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, &param, desc, sizeof(desc));
if (ok) {
complete_negotiation (S, &param);
}
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;
SET_RC(S->rc+1);
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;
SET_RC(S->rc+1);
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:
SET_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->vs) {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error I: %d timeouts: unacknowledged sent data.\n", S->stream_id, S->n2_retry);
}
}
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: %d timeouts: extended peer busy condition.\n", S->stream_id, S->n2_retry);
}
}
else {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error T: %d timeouts: no response to enquiry.\n", S->stream_id, S->n2_retry);
}
}
// 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 {
SET_RC(S->rc+1);
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.
SET_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, &param);
xlen = xid_encode (&param, xinfo, cmd);
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.
SET_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 irrelevant.
* 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 RR/RNR cmd P=1 ****** state=%d, rc=%d\n\n", S->state, S->rc);
}
// 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, NULL, 0);
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 it 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, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0; // because we sent N(R) from V(R).
}
else if (S->srej_enable == srej_single || S->srej_enable == srej_multi) {
// 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, NULL, 0);
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).
if (s_debug_retry) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n****** ENQUIRY RESPONSE srej not enbled, sending RR resp F=%d ******\n\n", f);
}
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0);
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, NULL, 0);
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, NULL, 0);
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.
* Should always send at least one.
*
* This is probably the result of getting REJ asking for a resend.
*
* Context: I would expect the caller to clear 'acknowledge_pending' after calling this
* because we sent N(R), from V(R), to ack what was received from other guy.
* I would also expect Stop T3 & Start T1 at the same place.
*
*------------------------------------------------------------------------------*/
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_debug_misc) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("invoke_retransmission(): starting with %d, state=%d, rc=%d, \n", nr_input, S->state, S->rc);
}
// I don't think we should be here if SREJ is enabled.
// TODO: Figure out why this happens occasionally.
// if (S->srej_enable != srej_none) {
// text_color_set(DW_COLOR_ERROR);
// dw_printf ("Internal Error, Did not expect to be here when SREJ enabled. %s %s %d\n", __FILE__, __func__, __LINE__);
// }
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(): Resending N(S) = %d\n", ns);
}
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: This is called for:
* - 'I' frame received and N(R) is in expected range, states 3 & 4.
* - RR/RNR command with p=1 received and N(R) is in expected range, state 3 only.
*
*------------------------------------------------------------------------------*/
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 acknowledgement 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_enable = srej_none;
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_enable = srej_single; // Start with single.
// Can be increased to multi with XID exchange.
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_ackd" 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.
* acknowledge_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 &&
WITHIN_WINDOW_SIZE(S) ) {
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;
int n;
// At least one known [partial] v2.2 implementation understands SABME but not XID.
// Rather than wasting time, sending XID repeatedly until giving up, we have a workaround.
// The configuration file can contain a list of stations known not to respond to XID.
// Obviously this applies only to v2.2 because XID was not part of v2.0.
for (n = 0; n < g_misc_config_p->noxid_count; n++) {
if (strcmp(S->addrs[PEERCALL],g_misc_config_p->noxid_addrs[n]) == 0) {
return;
}
}
switch (S->mdl_state) {
case mdl_state_0_ready:
initiate_negotiation (S, &param);
xlen = xid_encode (&param, xinfo, cmd);
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;
switch (S->srej_enable) {
case srej_single:
case srej_multi:
param->srej = srej_multi; // see if other end reconizes it.
break;
case srej_none:
default:
param->srej = srej_none;
break;
}
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)
{
// TODO: Integrate with new full duplex capability in v1.5.
param->full_duplex = 0;
// Other end might want 8.
// Seems unlikely. If it implements XID it should have modulo 128.
if (param->modulo == modulo_unknown) {
param->modulo = 8; // Not specified. Set default.
}
else {
param->modulo = MIN(param->modulo, 128);
}
// We can do REJ or SREJ but won't combine them.
// Erratum: 2006 version, section, 4.3.3.7 says default selective reject - reject.
// We can't do that.
if (param->srej == srej_not_specified) {
param->srej = (param->modulo == 128) ? srej_single : srej_none; // not specified, set default
}
// 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->srej != srej_not_specified) {
S->srej_enable = param->srej;
}
if (param->modulo != modulo_unknown) {
// Disaster if aren't agreeing on this.
S->modulo = param->modulo;
}
if (param->i_field_length_rx != G_UNKNOWN) {
S->n1_paclen = param->i_field_length_rx;
}
if (param->window_size_rx != G_UNKNOWN) {
S->k_maxframe = param->window_size_rx;
}
if (param->ack_timer != G_UNKNOWN) {
S->t1v = param->ack_timer * 0.001;
}
if (param->retries != G_UNKNOWN) {
S->n2_retry = param->retries;
}
}
//###################################################################################
//###################################################################################
//
// Timers.
//
// Start.
// Stop.
// Pause (when channel busy) & resume.
// Is it running?
// Did it expire before being stopped?
// When will next one expire?
//
//###################################################################################
//###################################################################################
static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Start T1 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line);
}
S->t1_exp = now + S->t1v;
if (S->radio_channel_busy) {
S->t1_paused_at = now;
}
else {
S->t1_paused_at = 0;
}
S->t1_had_expired = 0;
} /* end start_t1 */
static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
RESUME_T1; // adjust expire time if paused.
if (S->t1_exp == 0.0) {
// Was already stopped.
}
else {
S->t1_remaining_when_last_stopped = S->t1_exp - now;
if (S->t1_remaining_when_last_stopped < 0) S->t1_remaining_when_last_stopped = 0;
}
// Normally this would be at the top but we don't know time remaining at that point.
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
if (S->t1_exp == 0.0) {
dw_printf ("Stop T1. Wasn't running, [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line);
}
else {
dw_printf ("Stop T1, %.3f remaining, [now=%.3f] from %s %d\n", S->t1_remaining_when_last_stopped, now - S->start_time, from_func, from_line);
}
}
S->t1_exp = 0.0; // now stopped.
S->t1_had_expired = 0; // remember that it did not expire.
} /* end stop_t1 */
static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line)
{
int result = S->t1_exp != 0.0;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("is_t1_running? returns %d\n", result);
}
return (result);
} /* end is_t1_running */
static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->t1_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->t1_paused_at == 0.0) {
// Running and not paused.
double now = dtime_now();
S->t1_paused_at = now;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Paused T1 with %.3f still remaining, [now=%.3f] from %s %d\n", S->t1_exp - now, now - S->start_time, from_func, from_line);
}
}
else {
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("T1 error: Didn't expect pause when already paused.\n");
}
}
} /* end pause_t1 */
static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->t1_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->t1_paused_at == 0.0) {
// Running but not paused.
}
else {
double now = dtime_now();
double paused_for_sec = now - S->t1_paused_at;
S->t1_exp += paused_for_sec;
S->t1_paused_at = 0.0;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Resumed T1 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->t1_exp - now, now - S->start_time);
}
}
} /* end resume_t1 */
// T3 is a lot simpler.
// Here we are talking about minutes of inactivity with the peer
// rather than expecting a response within seconds where timing is more critical.
// We don't need to capture remaining time when stopped.
// I don't think there is a need to pause it due to the large time frame.
static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Start T3 for %.3f sec, [now=%.3f] from %s %d\n", T3_DEFAULT, now - S->start_time, from_func, from_line);
}
S->t3_exp = now + T3_DEFAULT;
}
static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (s_debug_timers) {
double now = dtime_now();
text_color_set(DW_COLOR_DEBUG_TIMER);
if (S->t3_exp == 0.0) {
dw_printf ("Stop T3. Wasn't running.\n");
}
else {
dw_printf ("Stop T3, %.3f remaining, [now=%.3f] from %s %d\n", S->t3_exp - now, now - S->start_time, from_func, from_line);
}
}
S->t3_exp = 0.0;
}
// TM201 is similar to T1.
// It needs to be paused whent the channel is busy.
// Simpler because we don't need to keep track of time remaining when stopped.
static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Start TM201 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line);
}
S->tm201_exp = now + S->t1v;
if (S->radio_channel_busy) {
S->tm201_paused_at = now;
}
else {
S->tm201_paused_at = 0;
}
} /* end start_tm201 */
static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Stop TM201. [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line);
}
S->tm201_exp = 0.0; // now stopped.
} /* end stop_tm201 */
static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->tm201_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->tm201_paused_at == 0.0) {
// Running and not paused.
double now = dtime_now();
S->tm201_paused_at = now;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Paused TM201 with %.3f still remaining, [now=%.3f] from %s %d\n", S->tm201_exp - now, now - S->start_time, from_func, from_line);
}
}
else {
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("TM201 error: Didn't expect pause when already paused.\n");
}
}
} /* end pause_tm201 */
static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->tm201_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->tm201_paused_at == 0.0) {
// Running but not paused.
}
else {
double now = dtime_now();
double paused_for_sec = now - S->tm201_paused_at;
S->tm201_exp += paused_for_sec;
S->tm201_paused_at = 0.0;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Resumed TM201 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->tm201_exp - now, now - S->start_time);
}
}
} /* end resume_tm201 */
double ax25_link_get_next_timer_expiry (void)
{
double tnext = 0;
ax25_dlsm_t *p;
for (p = list_head; p != NULL; p = p->next) {
// Consider if running and not paused.
if (p->t1_exp != 0 && p->t1_paused_at == 0) {
if (tnext == 0) {
tnext = p->t1_exp;
}
else if (p->t1_exp < tnext) {
tnext = p->t1_exp;
}
}
if (p->t3_exp != 0) {
if (tnext == 0) {
tnext = p->t3_exp;
}
else if (p->t3_exp < tnext) {
tnext = p->t3_exp;
}
}
if (p->tm201_exp != 0 && p->tm201_paused_at == 0) {
if (tnext == 0) {
tnext = p->tm201_exp;
}
else if (p->tm201_exp < tnext) {
tnext = p->tm201_exp;
}
}
}
if (s_debug_timers > 1) {
text_color_set(DW_COLOR_DEBUG);
if (tnext == 0.0) {
dw_printf ("ax25_link_get_next_timer_expiry returns none.\n");
}
else {
dw_printf ("ax25_link_get_next_timer_expiry returns %.3f sec from now.\n",
tnext - dtime_now());
}
}
return (tnext);
} /* end ax25_link_get_next_timer_expiry */
/* end ax25_link.c */