Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
6924 lines (5485 sloc) 231 KB
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016, 2017, 2018 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.
*
* 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.
char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN];
// Up to 10 addresses, same order as in frame.
int num_addr; // Number of addresses. Should be in range 2 .. 10.
#define OWNCALL AX25_SOURCE
// addrs[OWNCALL] is owncall for this end of link.
// Note that we are acting on behalf of
// a client application so the APRS mycall
// might not be relevent.
#define PEERCALL AX25_DESTINATION
// addrs[PEERCALL] is call for other end.
double start_time; // Clock time when this was allocated. Used only for
// debug output for timestamps relative to start.
enum dlsm_state_e state; // Current state.
int modulo; // 8 or 128.
// Determines whether we have one or two control
// octets. 128 allows a much larger window size.
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 receving an I frame, in states 3 & 4, SREJ not enabled.
// When an I frame has an unepected 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 acknowlegement before resending.
// Value used when starting timer T1, in seconds.
// "FRACK" parameter in some implementations.
// Typically it might be 3 seconds after frame has been
// sent. Add more for each digipeater in path.
// Here it is dynamically adjusted.
// Set initial value for T1V.
// Multiply FRACK by 2*m+1, where m is number of digipeaters.
#define INIT_T1V_SRT \
S->t1v = g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1); \
S->srt = S->t1v / 2.0;
int radio_channel_busy; // Either due to DCD or PTT.
// Timer T1.
// Timer values all use the usual unix time() value but double precision
// so we can have fractions of seconds.
// T1 is used for retries along with the retry counter, "rc."
// When timer T1 is started, the value is obtained from t1v plus the current time.
// Appropriate functions should be used rather than accessing the values directly.
// This gets a little tricky because we need to pause the timers when the radio
// channel is busy. Suppose we sent an I frame and set T1 to 4 seconds so we could
// take corrective action if there is no response in a reasonable amount of time.
// What if some other station has the channel tied up for 10 seconds? We don't want
// T1 to timeout and start a retry sequence. The solution is to pause the timers while
// the channel is busy. We don't want to get a timer expiry event when t1_exp is in
// the past if it is currently paused. When it is un-paused, the expiration time is adjusted
// for the amount of time it was paused.
double t1_exp; // This is the time when T1 will expire or 0 if not running.
double t1_paused_at; // Time when it was paused or 0 if not paused.
float t1_remaining_when_last_stopped; // Number of seconds that were left on T1 when it was stopped.
// This is used to fine tune t1v.
// Set to negative initially to mean invalid, don't use in calculation.
int t1_had_expired; // Set when T1 expires.
// Cleared for start & stop.
// Timer T3.
// T3 is used to terminate connection after extended inactivity.
// Similar to T1 except there is not mechanism to capture the remaining time when it is stopped
// and it is not paused when the channel is busy.
double t3_exp; // When it expires or 0 if not running.
#define T3_DEFAULT 300.0 // Copied 5 minutes from Ax.25 for Linux.
// http://www.linux-ax25.org/wiki/Run_time_configurable_parameters
// D710A also defaults to 30*10 = 300 seconds.
// Should it be user-configurable?
// KPC-3+ and TM-D710A have "CHECK" command for this purpose.
// Statistics for testing purposes.
// Count how many frames of each type we received.
// This is easy to do because they all come in thru lm_data_indication.
// Counting outgoing could probably be done in lm_data_request so
// it would not have to be scattered all over the place. TBD
int count_recv_frame_type[frame_not_AX25+1];
int peak_rc_value; // Peak value of retry count (rc).
// For sending data.
cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet.
// Linked list.
// 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);
p->magic1 = MAGIC1;
p->start_time = dtime_now();
p->stream_id = next_stream_id++;
p->modulo = 8;
p->chan = chan;
p->num_addr = num_addr;
// If it came in over the radio, we need to swap source/destination and reverse any digi path.
if (incoming_for_client >= 0) {
strlcpy (p->addrs[AX25_SOURCE], addrs[AX25_DESTINATION], sizeof(p->addrs[AX25_SOURCE]));
strlcpy (p->addrs[AX25_DESTINATION], addrs[AX25_SOURCE], sizeof(p->addrs[AX25_DESTINATION]));
int j = AX25_REPEATER_1;
int k = num_addr - 1;
while (k >= AX25_REPEATER_1) {
strlcpy (p->addrs[j], addrs[k], sizeof(p->addrs[j]));
j++;
k--;
}
p->client = incoming_for_client;
}
else {
memcpy (p->addrs, addrs, sizeof(p->addrs));
p->client = client;
}
p->state = state_0_disconnected;
p->t1_remaining_when_last_stopped = -999; // Invalid, don't use.
p->magic2 = MAGIC2;
p->magic3 = MAGIC3;
// No need for critical region because this should all be in one thread.
p->next = list_head;
list_head = p;
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle returns NEW stream id %d\n", p->stream_id);
}
return (p);
}
//###################################################################################
//###################################################################################
//
// Data Link state machine for sending data in connected mode.
//
// Incoming:
//
// Requests from the client application. Set s_debug_client_app for debugging.
//
// dl_connect_request
// dl_disconnect_request
// dl_data_request - send connected data
// dl_unit_data_request - not implemented. APRS & KISS bypass this
// dl_flow_off - not implemented. Not in AGW API.
// dl_flow_on - not implemented. Not in AGW API.
// dl_register_callsign - Register callsigns(s) for incoming connection requests.
// dl_unregister_callsign - Unregister callsigns(s) ...
// dl_client_cleanup - Clean up after client which has disappeared.
//
// Stuff from the radio channel. Set s_debug_radio for debugging.
//
// lm_data_indication - Received frame.
// lm_channel_busy - Change in PTT or DCD.
// lm_seize_confirm - We have started to transmit.
//
// Timer expiration. Set s_debug_timers for debugging.
//
// dl_timer_expiry
//
// Outgoing:
//
// To the client application:
//
// dl_data_indication - received connected data.
//
// To the transmitter:
//
// lm_data_request - Queue up a frame for transmission.
//
// lm_seize_request - Start transmitter when possible.
// lm_seize_confirm will be called when it has.
//
//
// It is important that all requests come thru the data link queue so
// everything is serialized.
// We don't have to worry about being reentrant or critical regions.
// Nothing here should consume a significant amount of time.
// i.e. There should be no sleep delay or anything that would block waiting on someone else.
//
//###################################################################################
//###################################################################################
/*------------------------------------------------------------------------------
*
* Name: dl_connect_request
*
* Purpose: Client app wants to connect to another station.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Description:
*
*------------------------------------------------------------------------------*/
void dl_connect_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
int ok_to_create = 1;
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:
// TODO: "requeue." Not sure what to do here.
// If we put it back in the queue we will get it back again probably still in same state.
// Need a way to defer it until the next state change.
break;
case state_2_awaiting_release:
{
// 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.
*
*------------------------------------------------------------------------------*/
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata);
void dl_data_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
int ok_to_create = 1;
int nseg_to_follow;
int orig_offset, remaining_len;
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_data_request (\"");
ax25_safe_print (E->txdata->data, E->txdata->len, 1);
dw_printf ("\") state=%d\n", S->state);
}
if (E->txdata->len <= S->n1_paclen) {
data_request_good_size (S, E->txdata);
E->txdata = NULL; // Now part of transmit I frame queue.
return;
}
// More interesting case.
// It is too large to fit in one frame so we segment it.
// As an example, suppose we had 6 bytes of data "ABCDEF".
// If N1 >= 6, it would be sent normally.
// (addresses)
// (control bytes)
// PID, typically 0xF0
// 'A' - first byte of information field
// 'B'
// 'C'
// 'D'
// 'E'
// 'F'
// Now consider the case where it would not fit.
// We would change the PID to 0x08 meaning a segment.
// The information part is the segment identifier of this format:
//
// x xxxxxxx
// | ---+---
// | |
// | +- Number of additional segments to follow.
// |
// +- '1' means it is the first segment.
// If N1 = 4, it would be split up like this:
// (addresses)
// (control bytes)
// PID = 0x08 means segment
// 0x82 - Start of info field.
// MSB set indicates FIRST segment.
// 2, in lower 7 bits, means 2 more segments to follow.
// 0xF0 - original PID, typical value.
// 'A' - For the FIRST segment, we have PID and N1-2 data bytes.
// 'B'
// (addresses)
// (control bytes)
// PID = 0x08 means segment
// 0x01 - Means 1 more segment follows.
// 'C' - For subsequent (not first) segments, we have up to N1-1 data bytes.
// 'D'
// 'E'
// (addresses)
// (control bytes)
// PID = 0x08
// 0x00 - 0 means no more to follow. i.e. This is the last.
// 'E'
// Number of segments is ceiling( (datalen + 1 ) / (N1 - 1))
// we add one to datalen for the original PID.
// We subtract one from N1 for the segment identifier header.
#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b))
// Compute number of segments.
// We will decrement this before putting it in the frame so the first
// will have one less than this number.
nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1);
if (nseg_to_follow < 2 || nseg_to_follow > 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, number of segments = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
orig_offset = 0;
remaining_len = E->txdata->len;
// First segment.
int seglen;
struct {
char header; // 0x80 + number of segments to follow.
char original_pid;
char segdata[AX25_N1_PACLEN_MAX];
} first_segment;
cdata_t *new_txdata;
nseg_to_follow--;
first_segment.header = 0x80 | nseg_to_follow;
first_segment.original_pid = E->txdata->pid;
seglen = MIN(S->n1_paclen - 2, remaining_len);
if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > (int)(sizeof(first_segment.segdata))) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen);
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2);
data_request_good_size (S, new_txdata);
orig_offset += seglen;
remaining_len -= seglen;
// Subsequent segments.
do {
struct {
char header; // Number of segments to follow.
char segdata[AX25_N1_PACLEN_MAX];
} subsequent_segment;
nseg_to_follow--;
subsequent_segment.header = nseg_to_follow;
seglen = MIN(S->n1_paclen - 1, remaining_len);
if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > (int)(sizeof(subsequent_segment.segdata))) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen);
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1);
data_request_good_size (S, new_txdata);
orig_offset += seglen;
remaining_len -= seglen;
} while (nseg_to_follow > 0);
if (remaining_len != 0 || orig_offset != E->txdata->len) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n",
__LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len);
}
cdata_delete (E->txdata);
E->txdata = NULL;
} /* end dl_data_request */
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata)
{
switch (S->state) {
case state_0_disconnected:
case state_2_awaiting_release:
/*
* Discard it.
*/
cdata_delete (txdata);
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
/*
* Erratum?
* The flow chart shows "push on I frame queue" if layer 3 initiated
* is NOT set. This seems backwards but I don't understand enough yet
* to make a compelling argument that it is wrong.
* Implemented as in flow chart.
* TODO: Get better understanding of what'layer_3_initiated' means.
*/
if (S->layer_3_initiated) {
cdata_delete (txdata);
break;
}
// otherwise fall thru.
case state_3_connected:
case state_4_timer_recovery:
/*
* "push on I frame queue"
* Append to the end would have been a better description because push implies a stack.
*/
if (S->i_frame_queue == NULL) {
txdata->next = NULL;
S->i_frame_queue = txdata;
}
else {
cdata_t *plast = S->i_frame_queue;
while (plast->next != NULL) {
plast = plast->next;
}
txdata->next = NULL;
plast->next = txdata;
}
break;
}
// 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);
strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign));
r->chan = E->chan;
r->client = E->client;
r->next = reg_callsign_list;
r->magic = RC_MAGIC;
reg_callsign_list = r;
} /* end dl_register_callsign */
void dl_unregister_callsign (dlq_item_t *E)
{
reg_callsign_t *r, *prev;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_unregister_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client);
}
prev = NULL;
r = reg_callsign_list;
while (r != NULL) {
assert (r->magic == RC_MAGIC);
if (strcmp(r->callsign,E->addrs[0]) == 0 && r->chan == E->chan && r->client == E->client) {
if (r == reg_callsign_list) {
reg_callsign_list = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = reg_callsign_list;
}
else {
prev->next = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = prev->next;
}
}
else {
prev = r;
r = r->next;
}
}
} /* end dl_unregister_callsign */
/*------------------------------------------------------------------------------
*
* Name: dl_client_cleanup
*
* Purpose: Client app has gone away. Clean up any data associated with it.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Description: By client application we mean something that attached with the
* AGW network protocol.
*
* Clean out anything related to the specfied client application.
* This would include state machines and registered callsigns.
*
*------------------------------------------------------------------------------*/
void dl_client_cleanup (dlq_item_t *E)
{
ax25_dlsm_t *S;
ax25_dlsm_t *dlprev;
reg_callsign_t *r, *rcprev;
if (s_debug_client_app) {
text_color_set(DW_COLOR_INFO);
dw_printf ("dl_client_cleanup (%d)\n", E->client);
}
dlprev = NULL;
S = list_head;
while (S != NULL) {
// Look for corruption or double freeing.
assert (S->magic1 == MAGIC1);
assert (S->magic2 == MAGIC2);
assert (S->magic3 == MAGIC3);
if (S->client == E->client ) {
int n;
if (s_debug_stats) {
text_color_set(DW_COLOR_INFO);
dw_printf ("%d I frames received\n", S->count_recv_frame_type[frame_type_I]);
dw_printf ("%d RR frames received\n", S->count_recv_frame_type[frame_type_S_RR]);
dw_printf ("%d RNR frames received\n", S->count_recv_frame_type[frame_type_S_RNR]);
dw_printf ("%d REJ frames received\n", S->count_recv_frame_type[frame_type_S_REJ]);
dw_printf ("%d SREJ frames received\n", S->count_recv_frame_type[frame_type_S_SREJ]);
dw_printf ("%d SABME frames received\n", S->count_recv_frame_type[frame_type_U_SABME]);
dw_printf ("%d SABM frames received\n", S->count_recv_frame_type[frame_type_U_SABM]);
dw_printf ("%d DISC frames received\n", S->count_recv_frame_type[frame_type_U_DISC]);
dw_printf ("%d DM frames received\n", S->count_recv_frame_type[frame_type_U_DM]);
dw_printf ("%d UA frames received\n", S->count_recv_frame_type[frame_type_U_UA]);
dw_printf ("%d FRMR frames received\n", S->count_recv_frame_type[frame_type_U_FRMR]);
dw_printf ("%d UI frames received\n", S->count_recv_frame_type[frame_type_U_UI]);
dw_printf ("%d XID frames received\n", S->count_recv_frame_type[frame_type_U_XID]);
dw_printf ("%d TEST frames received\n", S->count_recv_frame_type[frame_type_U_TEST]);
dw_printf ("%d peak retry count\n", S->peak_rc_value);
}
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_client_cleanup: remove %s>%s\n", S->addrs[AX25_SOURCE], S->addrs[AX25_DESTINATION]);
}
discard_i_queue (S);
for (n = 0; n < 128; n++) {
if (S->txdata_by_ns[n] != NULL) {
cdata_delete (S->txdata_by_ns[n]);
S->txdata_by_ns[n] = NULL;
}
}
for (n = 0; n < 128; n++) {
if (S->rxdata_by_ns[n] != NULL) {
cdata_delete (S->rxdata_by_ns[n]);
S->rxdata_by_ns[n] = NULL;
}
}
if (S->ra_buff != NULL) {
cdata_delete (S->ra_buff);
S->ra_buff = NULL;
}
// Put into disconnected state.
// If "connected" indicator (e.g. LED) was on, this will turn it off.
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
// Take S out of list.
S->magic1 = 0;
S->magic2 = 0;
S->magic3 = 0;
if (S == list_head) { // first one on list.
list_head = S->next;
free (S);
S = list_head;
}
else { // not the first one.
dlprev->next = S->next;
free (S);
S = dlprev->next;
}
}
else {
dlprev = S;
S = S->next;
}
}
/*
* If there are no link state machines (streams) remaining, there should be no txdata items still allocated.
*/
if (list_head == NULL) {
cdata_check_leak();
}
/*
* Remove registered callsigns for this client.
*/
rcprev = NULL;
r = reg_callsign_list;
while (r != NULL) {
assert (r->magic == RC_MAGIC);
if (r->client == E->client) {
if (r == reg_callsign_list) {
reg_callsign_list = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = reg_callsign_list;
}
else {
rcprev->next = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = rcprev->next;
}
}
else {
rcprev = r;
r = r->next;
}
}
} /* end dl_client_cleanup */
/*------------------------------------------------------------------------------
*
* Name: dl_data_indication
*
* Purpose: send connected data to client application.
*
* Inputs: pid - Protocol ID.
*
* data - Pointer to array of bytes.
*
* len - Number of bytes in data.
*
*
* Description: TODO: We perform reassembly of segments here if necessary.
*
*------------------------------------------------------------------------------*/
static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len)
{
// Now it gets more interesting. We need to combine segments before passing it along.
// See example in dl_data_request.
if (S->ra_buff == NULL) {
// Ready state.
if (pid != AX25_PID_SEGMENTATION_FRAGMENT) {
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len);
return;
}
else if (data[0] & 0x80) {
// Ready state, First segment.
S->ra_following = data[0] & 0x7f;
int total = (S->ra_following + 1) * (len - 1) - 1; // len should be other side's N1
S->ra_buff = cdata_new(data[1], NULL, total);
S->ra_buff->size = total; // max that we are expecting.
S->ra_buff->len = len - 2; // how much accumulated so far.
memcpy (S->ra_buff->data, data + 2, len - 2);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not first segment in ready state.\n", S->stream_id);
}
}
else {
// Reassembling data state
if (pid != AX25_PID_SEGMENTATION_FRAGMENT) {
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len);
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not segment in reassembling state.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
else if (data[0] & 0x80) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: First segment in reassembling state.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
else if ((data[0] & 0x7f) != S->ra_following - 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments out of sequence.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
else {
// Reassembling data state, Not first segment.
S->ra_following = data[0] & 0x7f;
if (S->ra_buff->len + len - 1 <= S->ra_buff->size) {
memcpy (S->ra_buff->data + S->ra_buff->len, data + 1, len - 1);
S->ra_buff->len += len - 1;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments exceed buffer space.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
if (S->ra_following == 0) {
// Last one.
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], S->ra_buff->pid, S->ra_buff->data, S->ra_buff->len);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
}
}
}
} /* end dl_data_indication */
/*------------------------------------------------------------------------------
*
* Name: lm_channel_busy
*
* Purpose: Change in DCD or PTT status for channel so we know when it is busy.
*
* Inputs: E - Event from the queue.
*
* E->chan - Radio channel number.
*
* E->activity - OCTYPE_PTT for my transmission start/end.
* - OCTYPE_DCD if we hear someone else.
*
* E->status - 1 for active or 0 for quiet.
*
* Outputs: S->radio_channel_busy
*
* T1 & TM201 paused/resumed if running.
*
* Description: We need to pause the timers when the channel is busy.
*
*------------------------------------------------------------------------------*/
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 reponse only. Let's go with that.
case frame_type_S_SREJ:
case frame_type_U_DM:
case frame_type_U_UA:
case frame_type_U_FRMR:
if (cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be RESPONSE.\n", S->stream_id, desc);
}
break;
case frame_type_U_XID:
case frame_type_U_TEST:
if (cr != cr_cmd && cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc);
}
break;
case frame_type_U_UI:
// Don't test at this point in case an APRS frame gets thru.
// APRS doesn't specify what to put in the Source and Dest C bits.
// In practice we see all 4 possble combinations.
// I have an opinion about what would be "correct" (discussed elsewhere)
// but in practice no one seems to care.
break;
case frame_type_U:
case frame_not_AX25:
// not expected.
break;
}
switch (ftype) {
case frame_type_I: // Information
{
int pid;
unsigned char *info_ptr;
int info_len;
pid = ax25_get_pid (E->pp);
info_len = ax25_get_info (E->pp, &info_ptr);
i_frame (S, cr, pf, nr, ns, pid, (char *)info_ptr, info_len);
}
break;
case frame_type_S_RR: // Receive Ready - System Ready To Receive
rr_rnr_frame (S, 1, cr, pf, nr);
break;
case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full
rr_rnr_frame (S, 0, cr, pf, nr);
break;
case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate
rej_frame (S, cr, pf, nr);
break;
case frame_type_S_SREJ: // Selective Reject - Ask for 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 accidently
// show up at some future inopportune time.
cdata_delete (S->rxdata_by_ns[ns]);
S->rxdata_by_ns[ns] = NULL;
}
while (S->rxdata_by_ns[S->vr] != NULL) {
// dl_data_indication - send connected data to client application.
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, data=\"", __func__, __LINE__, ns, S->vr);
ax25_safe_print (S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len, 1);
dw_printf ("\"\n");
}
dl_data_indication (S, S->rxdata_by_ns[S->vr]->pid, S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len);
// Don't keep around anymore after sending it to client app.
cdata_delete (S->rxdata_by_ns[S->vr]);
S->rxdata_by_ns[S->vr] = NULL;
SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__));
}
if (p) {
// Mentioned in section 6.2.
// The next response frame returned to an I frame with the P bit set to "1", received during the information
// transfer state, is an RR, RNR or REJ response with the F bit set to "1".
int f = 1;
int nr = S->vr; // Next expected sequence number.
cmdres_t cr = cr_res; // response with F set to 1.
packet_t pp;
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, 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 reponse, as implied by setting F in the flow chart, is correct.
int f = p;
int nr = S->vr; // Next expected sequence number.
cmdres_t cr = cr_res; // response with F copied from P in I frame.
packet_t pp;
S->reject_exception = 1;
if (s_debug_retry) {
text_color_set(DW_COLOR_ERROR); // make it more noticable.
dw_printf ("sending REJ, at %s %d, SREJ not enabled case, V(R)=%d", __func__, __LINE__, S->vr);
}
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_REJ, S->modulo, nr, f, 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 cummulative 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 cummulative 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 calulating what to put in SREJ, %s line %d\n", __func__, __LINE__);
dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, 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 exeception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S));
dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr);
dw_printf ("resend[]=");
for (i = 0; i < count; i++) {
dw_printf (" %d", resend[i]);
}
dw_printf ("\n");
dw_printf ("rxdata_by_ns[]=");
for (i = 0; i < 128; i++) {
if (S->rxdata_by_ns[i] != NULL) {
dw_printf (" %d", i);
}
}
dw_printf ("\n");
}
// Something is wrong! We ask for more than the window size.
if (count > S->k_maxframe) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR - Extreme number of SREJ, %s line %d\n", __func__, __LINE__);
dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr);
dw_printf ("resend[]=");
for (i = 0; i < count; i++) {
dw_printf (" %d", resend[i]);
}
dw_printf ("\n");
dw_printf ("rxdata_by_ns[]=");
for (i = 0; i < 128; i++) {
if (S->rxdata_by_ns[i] != NULL) {
dw_printf (" %d", i);
}
}
dw_printf ("\n");
}
// 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 receving 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 togther with all additional I frames subsequently transmitted and
* successfully received.
*
* When a TNC sends one or more SREJ commands, each with the P bit set to "0" or "1", or one or more SREJ
* responses, each with the F bit set to "0", and the "sent SREJ" conditions are not cleared when the TNC is ready
* to issue the next response frame with the F bit set to "1", the TNC sends a SREJ response with the F bit set to "1",
* with the same N(R) as the oldest unresolved SREJ frame.
*
* Because an I or S format frame with the F bit set to "1" can cause checkpoint retransmission, a TNC does not
* send SREJ frames until it receives at least one in-sequence I frame, or it perceives by timeout that the checkpoint
* retransmission will not be initiated at the remote TNC.
*
* With respect to each direction of transmission on the data link, one or more "sent SREJ" exception conditions
* from a TNC to another TNC may be established at a time. A "sent SREJ" exception condition is cleared when
* the requested I frame is received.
*
* The SREJ frame may be repeated when a TNC perceives by timeout that a requested I frame will not be
* received, because either the requested I frame or the SREJ frame was in error or lost.
*
* When appropriate, a TNC receiving one or more SREJ frames initiates retransmission of the individual I
* frames indicated by the N(R) contained in each SREJ frame. After having retransmitted the above frames, new
* I frames are transmitted later if they become available.
*
* When a TNC receives and acts on one or more SREJ commands, each with the P bit set to "0", or an SREJ
* command with the P bit set to "1", or one or more SREJ responses each with the F bit set to "0", it disables any
* action on the next SREJ response frame if that SREJ frame has the F bit set to "1" and has the same N(R) (i.e.,
* the same value and the same numbering cycle) as a previously actioned SREJ frame, and if the resultant
* retransmission was made following the transmission of the P bit set to a "1".
* When the SREJ mechanism is used, the receiving station retains correctly-received I frames and delivers
* them to the higher layer in sequence number order.
*
*
* 6.4.4.2. Selective Reject (SREJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number
* equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously
* established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable
* and poll bit of the received frame are checked and acted upon, if necessary.
*
* This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can
* consume precious buffer space, especially if the user device has limited memory available and several active
* links are operational.
*
*
* 6.4.4.3. Selective Reject-Reject (SREJ/REJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted.
* All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is
* missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The
* received state variable and poll bit of the received frame are checked and acted upon. If another frame error
* occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame
* and discards frames received after the second errored frame until the first errored frame is recovered. Then, a
* REJ is issued to recover the second errored frame and all subsequent discarded frames.
*
* X.25: States that SREJ is only response. I'm following that and it simplifies matters.
*
* X.25 2.4.6.6.1 & 2.4.6.6.2 make a distinction between F being 0 or 1 besides copying N(R) into V(A).
* They talk about sending a poll under some conditions.
* We don't do that here. It seems to work reliably so leave well enough alone.
*
*------------------------------------------------------------------------------*/
static 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) {