mirror of https://github.com/wb2osz/direwolf.git
7040 lines
235 KiB
C
7040 lines
235 KiB
C
//
|
|
// 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_outstanding_frames_request - (mine) Ask about outgoing queue for a link.
|
|
// dl_data_request - send connected data
|
|
// dl_unit_data_request - not implemented. APRS & KISS bypass this
|
|
// dl_flow_off - not implemented. Not in AGW API.
|
|
// dl_flow_on - not implemented. Not in AGW API.
|
|
// dl_register_callsign - Register callsigns(s) for incoming connection requests.
|
|
// dl_unregister_callsign - Unregister callsigns(s) ...
|
|
// dl_client_cleanup - Clean up after client which has disappeared.
|
|
//
|
|
// Stuff from the radio channel. Set s_debug_radio for debugging.
|
|
//
|
|
// lm_data_indication - Received frame.
|
|
// lm_channel_busy - Change in PTT or DCD.
|
|
// lm_seize_confirm - We have started to transmit.
|
|
//
|
|
// Timer expiration. Set s_debug_timers for debugging.
|
|
//
|
|
// dl_timer_expiry
|
|
//
|
|
// Outgoing:
|
|
//
|
|
// To the client application:
|
|
//
|
|
// dl_data_indication - received connected data.
|
|
//
|
|
// To the transmitter:
|
|
//
|
|
// lm_data_request - Queue up a frame for transmission.
|
|
//
|
|
// lm_seize_request - Start transmitter when possible.
|
|
// lm_seize_confirm will be called when it has.
|
|
//
|
|
//
|
|
// It is important that all requests come thru the data link queue so
|
|
// everything is serialized.
|
|
// We don't have to worry about being reentrant or critical regions.
|
|
// Nothing here should consume a significant amount of time.
|
|
// i.e. There should be no sleep delay or anything that would block waiting on someone else.
|
|
//
|
|
//###################################################################################
|
|
//###################################################################################
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: dl_connect_request
|
|
*
|
|
* Purpose: Client app wants to connect to another station.
|
|
*
|
|
* Inputs: E - Event from the queue.
|
|
* The caller will free it.
|
|
*
|
|
* Description:
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
void dl_connect_request (dlq_item_t *E)
|
|
{
|
|
ax25_dlsm_t *S;
|
|
int ok_to_create = 1;
|
|
int old_version;
|
|
int n;
|
|
|
|
if (s_debug_client_app) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("dl_connect_request ()\n");
|
|
}
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Attempting connect to %s ...\n", E->addrs[PEERCALL]);
|
|
|
|
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
|
|
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
|
|
INIT_T1V_SRT;
|
|
|
|
// See if destination station is in list for v2.0 only.
|
|
|
|
old_version = 0;
|
|
for (n = 0; n < g_misc_config_p->v20_count && ! old_version; n++) {
|
|
if (strcmp(E->addrs[AX25_DESTINATION],g_misc_config_p->v20_addrs[n]) == 0) {
|
|
old_version = 1;
|
|
}
|
|
}
|
|
|
|
if (old_version || g_misc_config_p->maxv22 == 0) { // Don't attempt v2.2.
|
|
|
|
set_version_2_0 (S);
|
|
|
|
establish_data_link (S);
|
|
S->layer_3_initiated = 1;
|
|
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
|
|
}
|
|
else { // Try v2.2 first, then fall back if appropriate.
|
|
|
|
set_version_2_2 (S);
|
|
|
|
establish_data_link (S);
|
|
S->layer_3_initiated = 1;
|
|
enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__);
|
|
}
|
|
break;
|
|
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
discard_i_queue(S);
|
|
S->layer_3_initiated = 1;
|
|
// Keep current state.
|
|
break;
|
|
|
|
case state_2_awaiting_release:
|
|
|
|
// Keep current state.
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
discard_i_queue(S);
|
|
establish_data_link(S);
|
|
S->layer_3_initiated = 1;
|
|
// My enhancement. Original always sent SABM and went to state 1.
|
|
// If we were using v2.2, why not reestablish with that?
|
|
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
|
|
break;
|
|
}
|
|
|
|
} /* end dl_connect_request */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: dl_disconnect_request
|
|
*
|
|
* Purpose: Client app wants to terminate connection with another station.
|
|
*
|
|
* Inputs: E - Event from the queue.
|
|
* The caller will free it.
|
|
*
|
|
* Outputs:
|
|
*
|
|
* Description:
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
void dl_disconnect_request (dlq_item_t *E)
|
|
{
|
|
ax25_dlsm_t *S;
|
|
int ok_to_create = 1;
|
|
|
|
|
|
if (s_debug_client_app) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("dl_disconnect_request ()\n");
|
|
}
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Disconnect from %s ...\n", E->addrs[PEERCALL]);
|
|
|
|
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
|
|
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
|
|
// DL-DISCONNECT *confirm*
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
break;
|
|
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
// 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.
|
|
*
|
|
* Version 1.6: Bug 252. Segmentation was occuring for a V2.0 link. From the spec:
|
|
* "The receipt of an XID response from the other station establishes that both
|
|
* stations are using AX.25 version 2.2 or higher and enables the use of the
|
|
* segmenter/reassembler and selective reject."
|
|
* "The segmenter/reassembler procedure is only enabled if both stations on the
|
|
* link are using AX.25 version 2.2 or higher."
|
|
*
|
|
* The Segmenter Ready State SDL has no decision based on protocol version.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata);
|
|
|
|
|
|
void dl_data_request (dlq_item_t *E)
|
|
{
|
|
ax25_dlsm_t *S;
|
|
int ok_to_create = 1;
|
|
|
|
|
|
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
|
|
|
|
if (s_debug_client_app) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("dl_data_request (\"");
|
|
ax25_safe_print (E->txdata->data, E->txdata->len, 1);
|
|
dw_printf ("\") state=%d\n", S->state);
|
|
}
|
|
|
|
if (E->txdata->len <= S->n1_paclen) {
|
|
data_request_good_size (S, E->txdata);
|
|
E->txdata = NULL; // Now part of transmit I frame queue.
|
|
return;
|
|
}
|
|
|
|
#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b))
|
|
|
|
// Erratum: Don't do V2.2 segmentation for a V2.0 link.
|
|
// In this case, we can just split it into multiple frames not exceeding the specified max size.
|
|
// Hopefully the receiving end treats it like a stream and doesn't care about length of each frame.
|
|
|
|
if (S->modulo == 8) {
|
|
|
|
int num_frames = 0;
|
|
int remaining_len = E->txdata->len;
|
|
int offset = 0;
|
|
|
|
while (remaining_len > 0) {
|
|
int this_len = MIN(remaining_len, S->n1_paclen);
|
|
|
|
cdata_t *new_txdata = cdata_new(E->txdata->pid, E->txdata->data + offset, this_len);
|
|
data_request_good_size (S, new_txdata);
|
|
|
|
offset += this_len;
|
|
remaining_len -= this_len;
|
|
num_frames++;
|
|
}
|
|
|
|
if (num_frames != DIVROUNDUP(E->txdata->len, S->n1_paclen) || remaining_len != 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, num frames = %d, remaining len = %d\n",
|
|
__LINE__, E->txdata->len, S->n1_paclen, num_frames, remaining_len);
|
|
}
|
|
cdata_delete (E->txdata);
|
|
E->txdata = NULL;
|
|
return;
|
|
}
|
|
|
|
// More interesting case.
|
|
// It is too large to fit in one frame so we segment it.
|
|
|
|
// As an example, suppose we had 6 bytes of data "ABCDEF".
|
|
|
|
// If N1 >= 6, it would be sent normally.
|
|
|
|
// (addresses)
|
|
// (control bytes)
|
|
// PID, typically 0xF0
|
|
// 'A' - first byte of information field
|
|
// 'B'
|
|
// 'C'
|
|
// 'D'
|
|
// 'E'
|
|
// 'F'
|
|
|
|
// Now consider the case where it would not fit.
|
|
// We would change the PID to 0x08 meaning a segment.
|
|
// The information part is the segment identifier of this format:
|
|
//
|
|
// x xxxxxxx
|
|
// | ---+---
|
|
// | |
|
|
// | +- Number of additional segments to follow.
|
|
// |
|
|
// +- '1' means it is the first segment.
|
|
|
|
// If N1 = 4, it would be split up like this:
|
|
|
|
// (addresses)
|
|
// (control bytes)
|
|
// PID = 0x08 means segment
|
|
// 0x82 - Start of info field.
|
|
// MSB set indicates FIRST segment.
|
|
// 2, in lower 7 bits, means 2 more segments to follow.
|
|
// 0xF0 - original PID, typical value.
|
|
// 'A' - For the FIRST segment, we have PID and N1-2 data bytes.
|
|
// 'B'
|
|
|
|
// (addresses)
|
|
// (control bytes)
|
|
// PID = 0x08 means segment
|
|
// 0x01 - Means 1 more segment follows.
|
|
// 'C' - For subsequent (not first) segments, we have up to N1-1 data bytes.
|
|
// 'D'
|
|
// 'E'
|
|
|
|
// (addresses)
|
|
// (control bytes)
|
|
// PID = 0x08
|
|
// 0x00 - 0 means no more to follow. i.e. This is the last.
|
|
// 'E'
|
|
|
|
|
|
// Number of segments is ceiling( (datalen + 1 ) / (N1 - 1))
|
|
|
|
// we add one to datalen for the original PID.
|
|
// We subtract one from N1 for the segment identifier header.
|
|
|
|
#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b))
|
|
|
|
// Compute number of segments.
|
|
// We will decrement this before putting it in the frame so the first
|
|
// will have one less than this number.
|
|
|
|
int nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1);
|
|
|
|
if (nseg_to_follow < 2 || nseg_to_follow > 128) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, number of segments = %d\n",
|
|
__LINE__, E->txdata->len, S->n1_paclen, nseg_to_follow);
|
|
cdata_delete (E->txdata);
|
|
E->txdata = NULL;
|
|
return;
|
|
}
|
|
|
|
int orig_offset = 0;
|
|
int remaining_len = E->txdata->len;
|
|
|
|
// First segment.
|
|
|
|
int seglen;
|
|
struct {
|
|
char header; // 0x80 + number of segments to follow.
|
|
char original_pid;
|
|
char segdata[AX25_N1_PACLEN_MAX];
|
|
} first_segment;
|
|
cdata_t *new_txdata;
|
|
|
|
nseg_to_follow--;
|
|
|
|
first_segment.header = 0x80 | nseg_to_follow;
|
|
first_segment.original_pid = E->txdata->pid;
|
|
seglen = MIN(S->n1_paclen - 2, remaining_len);
|
|
|
|
if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > (int)(sizeof(first_segment.segdata))) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
|
|
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
|
|
cdata_delete (E->txdata);
|
|
E->txdata = NULL;
|
|
return;
|
|
}
|
|
|
|
memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen);
|
|
|
|
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2);
|
|
|
|
data_request_good_size (S, new_txdata);
|
|
|
|
orig_offset += seglen;
|
|
remaining_len -= seglen;
|
|
|
|
|
|
// Subsequent segments.
|
|
|
|
do {
|
|
struct {
|
|
char header; // Number of segments to follow.
|
|
char segdata[AX25_N1_PACLEN_MAX];
|
|
} subsequent_segment;
|
|
|
|
nseg_to_follow--;
|
|
|
|
subsequent_segment.header = nseg_to_follow;
|
|
seglen = MIN(S->n1_paclen - 1, remaining_len);
|
|
|
|
if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > (int)(sizeof(subsequent_segment.segdata))) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
|
|
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
|
|
cdata_delete (E->txdata);
|
|
E->txdata = NULL;
|
|
return;
|
|
}
|
|
|
|
memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen);
|
|
|
|
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1);
|
|
|
|
data_request_good_size (S, new_txdata);
|
|
|
|
orig_offset += seglen;
|
|
remaining_len -= seglen;
|
|
|
|
} while (nseg_to_follow > 0);
|
|
|
|
if (remaining_len != 0 || orig_offset != E->txdata->len) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n",
|
|
__LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len);
|
|
}
|
|
|
|
cdata_delete (E->txdata);
|
|
E->txdata = NULL;
|
|
|
|
} /* end dl_data_request */
|
|
|
|
|
|
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata)
|
|
{
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
case state_2_awaiting_release:
|
|
/*
|
|
* Discard it.
|
|
*/
|
|
cdata_delete (txdata);
|
|
break;
|
|
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
/*
|
|
* Erratum?
|
|
* The flow chart shows "push on I frame queue" if layer 3 initiated
|
|
* is NOT set. This seems backwards but I don't understand enough yet
|
|
* to make a compelling argument that it is wrong.
|
|
* Implemented as in flow chart.
|
|
* TODO: Get better understanding of what'layer_3_initiated' means.
|
|
*/
|
|
if (S->layer_3_initiated) {
|
|
cdata_delete (txdata);
|
|
break;
|
|
}
|
|
// otherwise fall thru.
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
/*
|
|
* "push on I frame queue"
|
|
* Append to the end would have been a better description because push implies a stack.
|
|
*/
|
|
|
|
if (S->i_frame_queue == NULL) {
|
|
txdata->next = NULL;
|
|
S->i_frame_queue = txdata;
|
|
}
|
|
else {
|
|
cdata_t *plast = S->i_frame_queue;
|
|
while (plast->next != NULL) {
|
|
plast = plast->next;
|
|
}
|
|
txdata->next = NULL;
|
|
plast->next = txdata;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// v1.5 change in strategy.
|
|
// New I frames, not sent yet, are delayed until after processing anything in the received transmission.
|
|
// Give the transmit process a kick unless other side is busy or we have reached our window size.
|
|
// Previously we had i_frame_pop_off_queue here which would start sending new stuff before we
|
|
// finished dealing with stuff already in progress.
|
|
|
|
switch (S->state) {
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
if ( ( ! S->peer_receiver_busy ) &&
|
|
WITHIN_WINDOW_SIZE(S) ) {
|
|
S->acknowledge_pending = 1;
|
|
lm_seize_request (S->chan);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} /* end data_request_good_size */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: dl_register_callsign
|
|
* dl_unregister_callsign
|
|
*
|
|
* Purpose: Register / Unregister callsigns that we will accept connections for.
|
|
*
|
|
* Inputs: E - Event from the queue.
|
|
* The caller will free it.
|
|
*
|
|
* Outputs: New item is pushed on the head of the reg_callsign_list.
|
|
* We don't bother checking for duplicates so the most recent wins.
|
|
*
|
|
* Description: The data link state machine does not use MYCALL from the APRS configuration.
|
|
* For outgoing frames, the client supplies the source callsign.
|
|
* For incoming connection requests, we need to know what address(es) to respond to.
|
|
*
|
|
* Note that one client application can register multiple callsigns for
|
|
* multiple channels.
|
|
* Different clients can register different different addresses on the same channel.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
void dl_register_callsign (dlq_item_t *E)
|
|
{
|
|
reg_callsign_t *r;
|
|
|
|
if (s_debug_client_app) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("dl_register_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client);
|
|
}
|
|
|
|
r = calloc(sizeof(reg_callsign_t),1);
|
|
strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign));
|
|
r->chan = E->chan;
|
|
r->client = E->client;
|
|
r->next = reg_callsign_list;
|
|
r->magic = RC_MAGIC;
|
|
|
|
reg_callsign_list = r;
|
|
|
|
} /* end dl_register_callsign */
|
|
|
|
|
|
void dl_unregister_callsign (dlq_item_t *E)
|
|
{
|
|
reg_callsign_t *r, *prev;
|
|
|
|
if (s_debug_client_app) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("dl_unregister_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client);
|
|
}
|
|
|
|
prev = NULL;
|
|
r = reg_callsign_list;
|
|
while (r != NULL) {
|
|
|
|
assert (r->magic == RC_MAGIC);
|
|
|
|
if (strcmp(r->callsign,E->addrs[0]) == 0 && r->chan == E->chan && r->client == E->client) {
|
|
|
|
if (r == reg_callsign_list) {
|
|
|
|
reg_callsign_list = r->next;
|
|
memset (r, 0, sizeof(reg_callsign_t));
|
|
free (r);
|
|
r = reg_callsign_list;
|
|
}
|
|
else {
|
|
|
|
prev->next = r->next;
|
|
memset (r, 0, sizeof(reg_callsign_t));
|
|
free (r);
|
|
r = prev->next;
|
|
}
|
|
}
|
|
else {
|
|
prev = r;
|
|
r = r->next;
|
|
}
|
|
}
|
|
|
|
} /* end dl_unregister_callsign */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: dl_outstanding_frames_request
|
|
*
|
|
* Purpose: Client app wants to know how many frames are still on their way
|
|
* to other station. This is handy for flow control. We would like
|
|
* to keep the pipeline filled sufficiently to take advantage of a
|
|
* large window size (MAXFRAMES). It is also good to know that the
|
|
* the last packet sent was actually received before we commence
|
|
* the disconnect.
|
|
*
|
|
* Inputs: E - Event from the queue.
|
|
* The caller will free it.
|
|
*
|
|
* Outputs: This gets back to the AGW server which sends the 'Y' reply.
|
|
*
|
|
* Description: This is the sum of:
|
|
* - Incoming connected data, from application still in the queue.
|
|
* - I frames which have been transmitted but not yet acknowleged.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
void dl_outstanding_frames_request (dlq_item_t *E)
|
|
{
|
|
ax25_dlsm_t *S;
|
|
int ok_to_create = 0; // must exist already.
|
|
|
|
|
|
if (s_debug_client_app) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("dl_outstanding_frames_request ( to %s )\n", E->addrs[PEERCALL]);
|
|
}
|
|
|
|
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
|
|
|
|
if (S == NULL) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Can't get outstanding frames for %s -> %s, chan %d\n", E->addrs[OWNCALL], E->addrs[PEERCALL], E->chan);
|
|
server_outstanding_frames_reply (E->chan, E->client, E->addrs[OWNCALL], E->addrs[PEERCALL], 0);
|
|
return;
|
|
}
|
|
|
|
// Add up these
|
|
//
|
|
// cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet.
|
|
// // Linked list.
|
|
// // The name is misleading because these are just blocks of
|
|
// // data, not "I frames" at this point. The name comes from
|
|
// // the protocol specification.
|
|
//
|
|
// cdata_t *txdata_by_ns[128]; // Data which has already been transmitted.
|
|
// // Indexed by N(S) in case it gets lost and needs to be sent again.
|
|
// // Cleared out when we get ACK for it.
|
|
|
|
int count1 = 0;
|
|
cdata_t *incoming;
|
|
for (incoming = S->i_frame_queue; incoming != NULL; incoming = incoming->next) {
|
|
count1++;
|
|
}
|
|
|
|
int count2 = 0;
|
|
int k;
|
|
for (k = 0; k < S->modulo; k++) {
|
|
if (S->txdata_by_ns[k] != NULL) {
|
|
count2++;
|
|
}
|
|
}
|
|
|
|
server_outstanding_frames_reply (S->chan, S->client, S->addrs[OWNCALL], S->addrs[PEERCALL], count1 + count2);
|
|
|
|
} // end dl_outstanding_frames_request
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: dl_client_cleanup
|
|
*
|
|
* Purpose: Client app has gone away. Clean up any data associated with it.
|
|
*
|
|
* Inputs: E - Event from the queue.
|
|
* The caller will free it.
|
|
*
|
|
|
|
* Description: By client application we mean something that attached with the
|
|
* AGW network protocol.
|
|
*
|
|
* Clean out anything related to the 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) {
|
|
// my addition
|
|
// Erratum: We sent I frame(s) and want to timeout if no ack comes back.
|
|
// We also sent N(R), from V(R), so no need for extra RR at the end only for that.
|
|
|
|
// We would sometimes end up in a situation where T1 was stopped on
|
|
// both ends and everyone would wait for the other guy to timeout and do something.
|
|
// My solution was to Start T1 after every place we send an I frame if not already there.
|
|
|
|
STOP_T3;
|
|
START_T1;
|
|
S->acknowledge_pending = 0;
|
|
}
|
|
#else // Erratum! This is from the 2006 revision.
|
|
// We should resend only the single requested I frame.
|
|
// I think there was a cut-n-paste from the REJ flow chart and this particular place did not get changed.
|
|
|
|
invoke_retransmission(S);
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
nr_error_recovery (S);
|
|
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
|
|
}
|
|
break;
|
|
}
|
|
|
|
} /* end srej_frame */
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: resend_for_srej
|
|
*
|
|
* Purpose: Resend the I frame(s) specified in SREJ response.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
* nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S).
|
|
* info - Information field, might contain additional sequence numbers for Multi-SREJ.
|
|
* info_len - Information field length, bytes.
|
|
*
|
|
* Returns: Number of frames sent. Should be at least one.
|
|
*
|
|
* Description: Simply resend requested frame(s).
|
|
* The calling context will worry about the F bit and other state stuff.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static int resend_for_srej (ax25_dlsm_t *S, int nr, unsigned char *info, int info_len)
|
|
{
|
|
cmdres_t cr = cr_cmd;
|
|
int i_frame_nr = S->vr;
|
|
int i_frame_ns = nr;
|
|
int p = 0;
|
|
int num_resent = 0;
|
|
|
|
// Resend I frame with N(S) equal to the N(R) in the SREJ.
|
|
// Additional sequence numbers can be in optional information part.
|
|
|
|
cdata_t *txdata = S->txdata_by_ns[i_frame_ns];
|
|
|
|
if (txdata != NULL) {
|
|
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len);
|
|
// dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
num_resent++;
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, i_frame_ns);
|
|
}
|
|
|
|
// Multi-SREJ if there is an information part.
|
|
|
|
int j;
|
|
for (j = 0; j < info_len; j++) {
|
|
|
|
// We can have a single sequence number like this:
|
|
// xxx00000 (mod 8)
|
|
// xxxxxxx0 (mod 128)
|
|
// or we can have span (mod 128 only) like this, with the first and last:
|
|
// xxxxxxx1
|
|
// xxxxxxx1
|
|
//
|
|
// Note that the sequence number is shifted left by one
|
|
// and if the LSB is set, there should be two adjacent bytes
|
|
// with it set.
|
|
|
|
if (S->modulo == 8) {
|
|
i_frame_ns = (info[j] >> 5) & 0x07; // no provision for span.
|
|
}
|
|
else {
|
|
i_frame_ns = (info[j] >> 1) & 0x7f; // TODO: test LSB and possible loop here.
|
|
}
|
|
|
|
txdata = S->txdata_by_ns[i_frame_ns];
|
|
if (txdata != NULL) {
|
|
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
num_resent++;
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: INTERNAL ERROR for Multi-SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, i_frame_ns);
|
|
}
|
|
}
|
|
return (num_resent);
|
|
|
|
} /* end resend_for_srej */
|
|
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: sabm_e_frame
|
|
*
|
|
* Purpose: Process SABM or SABME Frame.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* extended - True for SABME. False for SABM.
|
|
*
|
|
* p - Poll bit. TODO: What does it mean in this case?
|
|
*
|
|
* Description: This is a request, from the other end, to establish a connection.
|
|
*
|
|
* 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command
|
|
*
|
|
* The SABM command places two Terminal Node Comtrollers (TNC) in the asynchronous balanced mode
|
|
* (modulo 8). This a balanced mode of operation in which both devices are treated as equals or peers.
|
|
*
|
|
* Information fields are not allowed in SABM commands. Any outstanding I frames left when the SABM
|
|
* command is issued remain unacknowledged.
|
|
*
|
|
* The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the
|
|
* earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if
|
|
* possible.
|
|
*
|
|
* 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command
|
|
*
|
|
* The SABME command places two TNCs in the asynchronous balanced mode extended (modulo 128). This
|
|
* is a balanced mode of operation in which both devices are treated as equals or peers.
|
|
* Information fields are not allowed in SABME commands. Any outstanding I frames left when the SABME
|
|
* command is issued remains unacknowledged.
|
|
*
|
|
* The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the
|
|
* earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame.
|
|
*
|
|
* A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. ** (see note below)
|
|
*
|
|
*
|
|
* Note: The KPC-3+, which does not appear to support v2.2, responds with a DM.
|
|
* The 2.0 spec, section 2.3.4.3.5, states, "While a DXE is in the disconnected mode, it will respond
|
|
* to any command other than a SABM or UI frame with a DM response with the P/F bit set to 1."
|
|
* I think it is a bug in the KPC but I can see how someone might implement it that way.
|
|
* However, another place says FRMR is sent for any unrecognized frame type. That would seem to take priority.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p)
|
|
{
|
|
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
|
|
// Flow chart has decision: "Able to establish?"
|
|
// I think this means, are we willing to accept connection requests?
|
|
// We are always willing to accept connections.
|
|
// Of course, we wouldn't get this far if local callsigns were not "registered."
|
|
|
|
if (extended) {
|
|
set_version_2_2 (S);
|
|
}
|
|
else {
|
|
set_version_2_0 (S);
|
|
}
|
|
|
|
cmdres_t res = cr_res;
|
|
int f = p; // I don't understand the purpose of "P" in SABM/SABME
|
|
// but we dutifully copy it into "F" for the UA response.
|
|
int nopid = 0; // PID is only for I and UI.
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
clear_exception_conditions (S);
|
|
|
|
SET_VS(0);
|
|
SET_VA(0);
|
|
SET_VR(0);
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], extended ? "v2.2" : "v2.0");
|
|
|
|
// dl connect indication - inform the client app.
|
|
int incoming = 1;
|
|
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
|
|
|
|
INIT_T1V_SRT;
|
|
|
|
START_T3;
|
|
SET_RC(0); // My enhancement. See Erratum note in select_t1_value.
|
|
enter_new_state (S, state_3_connected, __func__, __LINE__);
|
|
break;
|
|
|
|
case state_1_awaiting_connection:
|
|
|
|
// Don't combine with state 5. They are slightly different.
|
|
|
|
if (extended) { // SABME - respond with DM, enter state 5.
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__);
|
|
}
|
|
else { // SABM - respond with UA.
|
|
|
|
// Erratum! 2006 version shows SAMBE twice for state 1.
|
|
// First one should be SABM in last page of Figure C4.2
|
|
// Original appears to be correct.
|
|
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
// stay in state 1.
|
|
}
|
|
break;
|
|
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
if (extended) { // SABME - respond with UA
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
// stay in state 5
|
|
}
|
|
else { // SABM, respond with UA, enter state 1
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
|
|
}
|
|
break;
|
|
|
|
case state_2_awaiting_release:
|
|
|
|
// Erratum! Flow charts don't list SABME for state 2.
|
|
// Probably just want to treat it the same as SABM here.
|
|
|
|
{
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited
|
|
// stay in state 2.
|
|
}
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
{
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
// State 3 & 4 handling are the same except for this one difference.
|
|
if (S->state == state_4_timer_recovery) {
|
|
if (extended) {
|
|
set_version_2_2 (S);
|
|
}
|
|
else {
|
|
set_version_2_0 (S);
|
|
}
|
|
}
|
|
|
|
clear_exception_conditions (S);
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error F: Data Link reset; i.e. SABM(e) received in state %d.\n", S->stream_id, S->state);
|
|
}
|
|
if (S->vs != S->va) {
|
|
discard_i_queue (S);
|
|
// dl connect indication
|
|
int incoming = 1;
|
|
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
|
|
}
|
|
STOP_T1;
|
|
START_T3;
|
|
SET_VS(0);
|
|
SET_VA(0);
|
|
SET_VR(0);
|
|
SET_RC(0); // My enhancement. See Erratum note in select_t1_value.
|
|
enter_new_state (S, state_3_connected, __func__, __LINE__);
|
|
}
|
|
break;
|
|
}
|
|
|
|
} /* end sabm_e_frame */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: disc_frame
|
|
*
|
|
* Purpose: Process DISC command.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
* p - Poll bit.
|
|
*
|
|
* Description: 4.3.3.3. Disconnect (DISC) Command
|
|
*
|
|
* The DISC command terminates a link session between two stations. An information field is not permitted in
|
|
* a DISC command frame.
|
|
*
|
|
* Prior to acting on the DISC frame, the receiving TNC confirms acceptance of the DISC by issuing a UA
|
|
* response frame at its earliest opportunity. The TNC sending the DISC enters the disconnected state when it
|
|
* receives the UA response.
|
|
*
|
|
* Any unacknowledged I frames left when this command is acted upon remain unacknowledged.
|
|
*
|
|
*
|
|
* 6.3.4. Link Disconnection
|
|
*
|
|
* While in the information-transfer state, either TNC may indicate a request to disconnect the link by transmitting
|
|
* a DISC command frame and starting timer T1.
|
|
*
|
|
* After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected
|
|
* state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the
|
|
* disconnected state.
|
|
*
|
|
* If a UA or DM response is not correctly received before T1 times out, the DISC frame is sent again and T1 is
|
|
* restarted. If this happens N2 times, the TNC enters the disconnected state.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void disc_frame (ax25_dlsm_t *S, int p)
|
|
{
|
|
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
{
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
}
|
|
// keep current state, 0, 1, or 5.
|
|
break;
|
|
|
|
case state_2_awaiting_release:
|
|
|
|
{
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited
|
|
}
|
|
// keep current state, 2.
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
{
|
|
discard_i_queue (S);
|
|
|
|
cmdres_t res = cr_res;
|
|
int f = p;
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
// dl disconnect *indication*
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
|
|
STOP_T1;
|
|
STOP_T3;
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
break;
|
|
}
|
|
|
|
} /* end disc_frame */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: dm_frame
|
|
*
|
|
* Purpose: Process DM Response Frame.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
* f - Final bit.
|
|
*
|
|
* Description: 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command
|
|
*
|
|
* The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the
|
|
* earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if
|
|
* possible.
|
|
*
|
|
* The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the
|
|
* earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame.
|
|
*
|
|
* A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR.
|
|
* ( I think the KPC-3+ has a bug - it replys with DM - WB2OSZ )
|
|
*
|
|
* 4.3.3.5. Disconnected Mode (DM) Response
|
|
*
|
|
* The disconnected mode response is sent whenever a TNC receives a frame other than a SABM(E) or UI
|
|
* frame while in a disconnected mode. The disconnected mode response also indicates that the TNC cannot
|
|
* accept a connection at the moment. The DM response does not have an information field.
|
|
* Whenever a SABM(E) frame is received and it is determined that a connection is not possible, a DM frame is
|
|
* sent. This indicates that the called station cannot accept a connection at that time.
|
|
* While a TNC is in the disconnected mode, it responds to any command other than a SABM(E) or UI frame
|
|
* with a DM response with the P/F bit set to "1".
|
|
*
|
|
* 4.3.3.6. Unnumbered Information (UI) Frame
|
|
*
|
|
* A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when
|
|
* in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state.
|
|
*
|
|
* 6.3.1. AX.25 Link Connection Establishment
|
|
*
|
|
* If the distant TNC receives a SABM command and cannot enter the indicated state, it sends a DM frame.
|
|
* When the originating TNC receives a DM response to its SABM(E) frame, it cancels its T1 timer and does
|
|
* not enter the information-transfer state.
|
|
*
|
|
* 6.3.4. Link Disconnection
|
|
*
|
|
* After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected
|
|
* state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the
|
|
* disconnected state.
|
|
*
|
|
* 6.5. Resetting Procedure
|
|
*
|
|
* If a DM response is received, the TNC enters the disconnected state and stops timer T1. If timer T1 expires
|
|
* before a UA or DM response frame is received, the SABM(E) is retransmitted and timer T1 restarted. If timer T1
|
|
* expires N2 times, the TNC enters the disconnected state. Any previously existing link conditions are cleared.
|
|
* Other commands or responses received by the TNC before completion of the reset procedure are discarded.
|
|
*
|
|
* Erratum: The flow chart shows the same behavior for states 1 and 5.
|
|
* For state 5, I think we should treat DM the same as FRMR.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static void dm_frame (ax25_dlsm_t *S, int f)
|
|
{
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
// Do nothing.
|
|
break;
|
|
|
|
case state_1_awaiting_connection:
|
|
|
|
if (f == 1) {
|
|
discard_i_queue (S);
|
|
// dl disconnect *indication*
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
STOP_T1;
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
// keep current state.
|
|
}
|
|
break;
|
|
|
|
case state_2_awaiting_release:
|
|
|
|
if (f == 1) {
|
|
|
|
// Erratum! Original flow chart, page 91, shows DL-CONNECT confirm.
|
|
// It should clearly be DISconnect rather than Connect.
|
|
|
|
// 2006 has DISCONNECT *Indication*.
|
|
// Should it be indication or confirm? Not sure.
|
|
|
|
// dl disconnect *confirm*
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
STOP_T1;
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
// keep current state.
|
|
}
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error E: DM received in state %d.\n", S->stream_id, S->state);
|
|
}
|
|
// dl disconnect *indication*
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
discard_i_queue (S);
|
|
STOP_T1;
|
|
STOP_T3;
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
break;
|
|
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
#if 0
|
|
// Erratum: The flow chart says we should do this.
|
|
// I'm not saying it is wrong. I just found it necessary to change this
|
|
// to work around an apparent bug in a popular hardware TNC.
|
|
|
|
if (f == 1) {
|
|
discard_i_queue (S);
|
|
// dl disconnect *indication*
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
STOP_T1;
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
// keep current state.
|
|
}
|
|
#else
|
|
// Erratum: This is not in original spec. It's copied from the FRMR case.
|
|
|
|
// I was expecting FRMR to mean the other end did not understand v2.2.
|
|
// Experimentation, with KPC-3+, revealed that we get DM instead.
|
|
// One part of the the 2.0 spec sort of indicates this might be intentional.
|
|
// But another part more clearly states it should be FRMR.
|
|
|
|
// At first I thought it was an error in the protocol spec.
|
|
// Later, I tend to believe it was just implemented wrong in the KPC-3+.
|
|
|
|
if (f == 1) {
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]);
|
|
|
|
INIT_T1V_SRT;
|
|
|
|
// Erratum: page 105. We are in state 5 so I think that means modulo is 128,
|
|
// k is probably something > 7, and selective reject is enabled.
|
|
// At the end of this we go to state 1.
|
|
// It seems to me, that we really want to set version 2.0 in here so we have
|
|
// compatible settings.
|
|
|
|
set_version_2_0 (S);
|
|
|
|
establish_data_link (S);
|
|
S->layer_3_initiated = 1;
|
|
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
} /* end dm_frame */
|
|
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: UA_frame
|
|
*
|
|
* Purpose: Process UA Response Frame.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
* f - Final bit.
|
|
*
|
|
* Description: 4.3.3.4. Unnumbered Acknowledge (UA) Response
|
|
*
|
|
* The UA response frame acknowledges the reception and acceptance of a SABM(E) or DISC command
|
|
* frame. A received command is not actually processed until the UA response frame is sent. Information fields are
|
|
* not permitted in a UA frame.
|
|
*
|
|
* 4.4.1. TNC Busy Condition
|
|
*
|
|
* When a TNC is temporarily unable to receive I frames (e.g., when receive buffers are full), it sends a Receive
|
|
* Not Ready (RNR) frame. This informs the sending TNC that the receiving TNC cannot handle any more I
|
|
* frames at the moment. This receiving TNC clears this condition by the sending a UA, RR, REJ or SABM(E)
|
|
* command frame.
|
|
*
|
|
* 6.2. Poll/Final (P/F) Bit Procedures
|
|
*
|
|
* The response frame returned by a TNC depends on the previous command received, as described in the
|
|
* following paragraphs.
|
|
* The next response frame returned by the TNC to a SABM(E) or DISC command with the P bit set to "1" is a
|
|
* UA or DM response with the F bit set to "1".
|
|
*
|
|
* 6.3.1. AX.25 Link Connection Establishment
|
|
*
|
|
* To connect to a distant TNC, the originating TNC sends a SABM command frame to the distant TNC and
|
|
* starts its T1 timer. If the distant TNC exists and accepts the connect request, it responds with a UA response
|
|
* frame and resets all of its internal state variables (V(S), V(A) and V(R)). Reception of the UA response frame by
|
|
* the originating TNC causes it to cancel the T1 timer and set its internal state variables to "0".
|
|
*
|
|
* 6.5. Resetting Procedure
|
|
*
|
|
* A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of
|
|
* a FRMR frame from a TNC using an older version of the protocol.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void ua_frame (ax25_dlsm_t *S, int f)
|
|
{
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
|
|
// Erratum: flow chart says errors C and D. Neither one really makes sense.
|
|
// "Unexpected UA in states 3, 4, or 5." We are in state 0 here.
|
|
// "UA received without F=1 when SABM or DISC was sent P=1."
|
|
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state);
|
|
}
|
|
break;
|
|
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
if (f == 1) {
|
|
if (S->layer_3_initiated) {
|
|
text_color_set(DW_COLOR_INFO);
|
|
// TODO: add via if apppropriate.
|
|
dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0");
|
|
// There is a subtle difference here between connect confirm and indication.
|
|
// connect *confirm* means "has been made"
|
|
// The AGW API distinguishes between incoming (initiated by other station) and
|
|
// outgoing (initiated by me) connections.
|
|
int incoming = 0;
|
|
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
|
|
}
|
|
else if (S->vs != S->va) {
|
|
#if 1
|
|
// Erratum: 2006 version has this.
|
|
|
|
INIT_T1V_SRT;
|
|
|
|
START_T3; // Erratum: Rather pointless because we immediately stop it below.
|
|
// In the original flow chart, that is.
|
|
// I think there is an error as explained below.
|
|
// In my version this is still pointless because we start T3 later.
|
|
|
|
#else
|
|
// Erratum: Original version has this.
|
|
// I think this could be harmful.
|
|
// The client app might have been impatient and started sending
|
|
// information already. I don't see why we would want to discard it.
|
|
|
|
discard_i_queue (S);
|
|
#endif
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0");
|
|
|
|
// Erratum: 2006 version says DL-CONNECT *confirm* but original has *indication*.
|
|
|
|
// connect *indication* means "has been requested".
|
|
// *confirm* seems right because we got a reply from the other side.
|
|
|
|
int incoming = 0;
|
|
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
|
|
}
|
|
|
|
STOP_T1;
|
|
#if 1 // My version.
|
|
START_T3;
|
|
#else // As shown in flow chart.
|
|
STOP_T3; // Erratum? I think this is wrong.
|
|
// We are about to enter state 3. When in state 3 either T1 or T3 should be
|
|
// running. In state 3, we always see start one / stop the other pairs except where
|
|
// we are about to enter a different state.
|
|
// Since there is nothing outstanding where we expect a response, T1 would
|
|
// not be started.
|
|
#endif
|
|
SET_VS(0);
|
|
SET_VA(0);
|
|
SET_VR(0);
|
|
select_t1_value (S);
|
|
|
|
// Erratum: mdl_negotiate_request does not appear in the SDL flow chart.
|
|
// It is mentioned here:
|
|
//
|
|
// C5.3 Internal Operation of the Machine
|
|
//
|
|
// The Management Data link State Machine handles the negotiation/notification of
|
|
// operational parameters. It uses a single command/response exchange to negotiate the
|
|
// final values of negotiable parameters.
|
|
//
|
|
// The station initiating the AX.25 connection will send an XID command after it receives
|
|
// the UA frame. If the other station is using a version of AX.25 earlier than 2.2, it will
|
|
// respond with an FRMR of the XID command and the default version 2.0 parameters will
|
|
// be used. If the other station is using version 2.2 or better, it will respond with an XID
|
|
// response.
|
|
|
|
if (S->state == state_5_awaiting_v22_connection) {
|
|
mdl_negotiate_request (S);
|
|
}
|
|
|
|
SET_RC(0); // My enhancement. See Erratum note in select_t1_value.
|
|
enter_new_state (S, state_3_connected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id);
|
|
}
|
|
// stay in current state, either 1 or 5.
|
|
}
|
|
break;
|
|
|
|
case state_2_awaiting_release:
|
|
|
|
// Erratum: 2006 version is missing yes/no labels on this test.
|
|
// DL-ERROR Indication does not mention error D.
|
|
|
|
if (f == 1) {
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
STOP_T1;
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id);
|
|
}
|
|
// stay in same state.
|
|
}
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state);
|
|
}
|
|
establish_data_link (S);
|
|
S->layer_3_initiated = 0;
|
|
|
|
// Erratum? Flow chart goes to state 1. Wouldn't we want this to be state 5 if modulo is 128?
|
|
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
|
|
break;
|
|
}
|
|
|
|
} /* end ua_frame */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: frmr_frame
|
|
*
|
|
* Purpose: Process FRMR Response Frame.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* Description: 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command
|
|
* ...
|
|
* The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the
|
|
* earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame.
|
|
* A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR.
|
|
*
|
|
* 4.3.3.9. FRMR Response Frame
|
|
*
|
|
* The FRMR response is removed from the standard for the following reasons:
|
|
* a) UI frame transmission was not allowed during FRMR recovery;
|
|
* b) During FRMR recovery, the link could not be reestablished by the station that sent the FRMR;
|
|
* c) The above functions are better handled by simply resetting the link with a SABM(E) + UA exchange;
|
|
* d) An implementation that receives and process FRMRs but does not transmit them is compatible with older
|
|
* versions of the standard; and
|
|
* e) SDL is simplified and removes the need for one state.
|
|
* This version of AX.25 operates with previous versions of AX.25. It does not generate a FRMR Response
|
|
* frame, but handles error conditions by resetting the link.
|
|
*
|
|
* 6.3.2. Parameter Negotiation Phase
|
|
*
|
|
* Parameter negotiation occurs at any time. It is accomplished by sending the XID command frame and
|
|
* receiving the XID response frame. Implementations of AX.25 prior to version 2.2 respond to an XID command
|
|
* frame with a FRMR response frame. The TNC receiving the FRMR uses a default set of parameters compatible
|
|
* with previous versions of AX.25.
|
|
*
|
|
* 6.5. Resetting Procedure
|
|
*
|
|
* The link resetting procedure initializes both directions of data flow after a unrecoverable error has occurred.
|
|
* This resetting procedure is used only in the information-transfer state of an AX.25 link.
|
|
* A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of
|
|
* a FRMR frame from a TNC using an older version of the protocol.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static void frmr_frame (ax25_dlsm_t *S)
|
|
{
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
case state_1_awaiting_connection:
|
|
case state_2_awaiting_release:
|
|
// Ignore it. Keep current state.
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error K: FRMR not expected in state %d.\n", S->stream_id, S->state);
|
|
}
|
|
|
|
set_version_2_0 (S); // Erratum: FRMR can only be sent by v2.0.
|
|
// Need to force v2.0. Should be added to flow chart.
|
|
establish_data_link (S);
|
|
S->layer_3_initiated = 0;
|
|
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
|
|
break;
|
|
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]);
|
|
|
|
INIT_T1V_SRT;
|
|
|
|
set_version_2_0 (S); // Erratum: Need to force v2.0. This is not in flow chart.
|
|
|
|
establish_data_link (S);
|
|
S->layer_3_initiated = 1; // Erratum? I don't understand the difference here.
|
|
// State 1 clears it. State 5 sets it. Why not the same?
|
|
|
|
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
|
|
break;
|
|
}
|
|
|
|
// part of state machine for the XID negotiation.
|
|
|
|
// I would not expect this to happen.
|
|
// To get here:
|
|
// We sent SABME. (not SABM)
|
|
// Other side responded with UA so it understands v2.2.
|
|
// We sent XID command which puts us int the negotiating state.
|
|
// Presumably this is in response to the XID and not something else.
|
|
|
|
// Anyhow, we will fall back to v2.0 parameters.
|
|
|
|
switch (S->mdl_state) {
|
|
|
|
case mdl_state_0_ready:
|
|
break;
|
|
|
|
case mdl_state_1_negotiating:
|
|
|
|
set_version_2_0 (S);
|
|
S->mdl_state = mdl_state_0_ready;
|
|
break;
|
|
}
|
|
|
|
} /* end frmr_frame */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: ui_frame
|
|
*
|
|
* Purpose: Process XID frame for negotiating protocol parameters.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* cr - Is it command or response?
|
|
*
|
|
* pf - Poll/Final bit.
|
|
*
|
|
* Description: 4.3.3.6. Unnumbered Information (UI) Frame
|
|
*
|
|
* The Unnumbered Information frame contains PID and information fields and passes information along the
|
|
* link outside the normal information controls. This allows information fields to be exchanged on the link, bypassing
|
|
* flow control.
|
|
*
|
|
* Because these frames cannot be acknowledged, if one such frame is obliterated, it cannot be recovered.
|
|
* A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when
|
|
* in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state.
|
|
*
|
|
* Reality: The data link state machine was an add-on after APRS and client APIs were already done.
|
|
* UI frames don't go thru here for normal operation.
|
|
* The only reason we have this function is so that we can send a response to a UI command with P=1.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf)
|
|
{
|
|
if (cr == cr_cmd && pf == 1) {
|
|
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
case state_1_awaiting_connection:
|
|
case state_2_awaiting_release:
|
|
case state_5_awaiting_v22_connection:
|
|
{
|
|
cmdres_t r = cr_res; // DM response with F taken from P.
|
|
int nopid = 0; // PID applies only for I and UI frames.
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, pf, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
}
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
enquiry_response (S, frame_type_U_UI, pf);
|
|
break;
|
|
}
|
|
}
|
|
|
|
} /* end ui_frame */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: xid_frame
|
|
*
|
|
* Purpose: Process XID frame for negotiating protocol parameters.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* cr - Is it command or response?
|
|
*
|
|
* pf - Poll/Final bit.
|
|
*
|
|
* Description: 4.3.3.7 Exchange Identification (XID) Frame
|
|
*
|
|
* The Exchange Identification frame causes the addressed station to identify itself, and
|
|
* to provide its characteristics to the sending station. An information field is optional within
|
|
* the XID frame. A station receiving an XID command returns an XID response unless a UA
|
|
* response to a mode setting command is awaiting transmission, or a FRMR condition
|
|
* exists.
|
|
*
|
|
* The XID frame complies with ISO 8885. Only those fields applicable to AX.25 are
|
|
* described. All other fields are set to an appropriate value. This implementation is
|
|
* compatible with any implementation which follows ISO 8885. Only the general-purpose
|
|
* XID information field identifier is required in this version of AX.25.
|
|
*
|
|
* The information field consists of zero or more information elements. The information
|
|
* elements start with a Format Identifier (FI) octet. The second octet is the Group Identifier
|
|
* (GI). The third and forth octets form the Group Length (GL). The rest of the information
|
|
* field contains parameter fields.
|
|
*
|
|
* The FI takes the value 82 hex for the general-purpose XID information. The GI takes
|
|
* the value 80 hex for the parameter-negotiation identifier. The GL indicates the length of
|
|
* the associated parameter field. This length is expressed as a two-octet binary number
|
|
* representing the length of the associated parameter field in octets. The high-order bits of
|
|
* length value are in the first of the two octets. A group length of zero indicates the lack of
|
|
* an associated parameter field and that all parameters assume their default values. The GL
|
|
* does not include its own length or the length of the GI.
|
|
*
|
|
* The parameter field contains a series of Parameter Identifier (PI), Parameter Length
|
|
* (PL), and Parameter Value (PV) set structures, in that order. Each PI identifies a
|
|
* parameter and is one octet in length. Each PL indicates the length of the associated PV in
|
|
* octets, and is one octet in length. Each PV contains the parameter value and is PL octets
|
|
* in length. The PL does not include its own length or the length of its associated PI. A PL
|
|
* value of zero indicates that the associated PV is absent; the parameter assumes the
|
|
* default value. A PI/PL/PV set may be omitted if it is not required to convey information, or
|
|
* if present values for the parameter are to be used. The PI/PL/PV fields are placed into the
|
|
* information field of the XID frame in ascending order. There is only one entry for each
|
|
* PI/PL/PV field used. A parameter field containing an unrecognized PI is ignored. An
|
|
* omitted parameter field assumes the currently negotiated value.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len)
|
|
{
|
|
struct xid_param_s param;
|
|
char desc[150];
|
|
int ok;
|
|
unsigned char xinfo[40];
|
|
int xlen;
|
|
cmdres_t res = cr_res;
|
|
int f = 1;
|
|
int nopid = 0;
|
|
packet_t pp;
|
|
|
|
|
|
switch (S->mdl_state) {
|
|
|
|
case mdl_state_0_ready:
|
|
|
|
if (cr == cr_cmd) {
|
|
|
|
if (pf == 1) {
|
|
|
|
// Take parameters sent by other station.
|
|
// Generally we take minimum of what he wants and what I can do.
|
|
// Adjust my working configuration and send it back.
|
|
|
|
ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc));
|
|
|
|
if (ok) {
|
|
negotiation_response (S, ¶m);
|
|
|
|
xlen = xid_encode (¶m, xinfo, res);
|
|
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_XID, f, nopid, xinfo, xlen);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
}
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error MDL-A: XID command without P=1.\n", S->stream_id);
|
|
}
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error MDL-B: Unexpected XID response.\n", S->stream_id);
|
|
}
|
|
break;
|
|
|
|
case mdl_state_1_negotiating:
|
|
|
|
if (cr == cr_res) {
|
|
|
|
if (pf == 1) {
|
|
|
|
// Got expected response. Copy into my working parameters.
|
|
|
|
ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc));
|
|
|
|
if (ok) {
|
|
complete_negotiation (S, ¶m);
|
|
}
|
|
|
|
S->mdl_state = mdl_state_0_ready;
|
|
STOP_TM201;
|
|
|
|
//#define TEST_TEST 1
|
|
|
|
#if TEST_TEST // Send TEST command to see how it responds.
|
|
// We currently have no Client API for sending this or reporting result.
|
|
{
|
|
char info[80] = "The quick brown fox jumps over the lazy dog.";
|
|
cmdres_t cmd = cr_cmd;
|
|
int p = 0;
|
|
int nopid = 0;
|
|
packet_t pp;
|
|
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_TEST, p, nopid, (unsigned char *)info, (int)strlen(info));
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error MDL-D: XID response without F=1.\n", S->stream_id);
|
|
}
|
|
}
|
|
else {
|
|
// Not expecting to receive a command when I sent one.
|
|
// Flow chart says requeue but I just drop it.
|
|
// The other end can retry and maybe I will be back to ready state by then.
|
|
}
|
|
break;
|
|
}
|
|
|
|
} /* end xid_frame */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: test_frame
|
|
*
|
|
* Purpose: Process TEST command for checking link.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* cr - Is it command or response?
|
|
*
|
|
* pf - Poll/Final bit.
|
|
*
|
|
* Description: 4.3.3.8. Test (TEST) Frame
|
|
*
|
|
* The Test command causes the addressed station to respond with the TEST response at the first respond
|
|
* opportunity; this performs a basic test of the data-link control. An information field is optional with the TEST
|
|
* command. If present, the received information field is returned, if possible, by the addressed station, with the
|
|
* TEST response. The TEST command has no effect on the mode or sequence variables maintained by the station.
|
|
*
|
|
* A FRMR condition may be established if the received TEST command information field exceeds the maximum
|
|
* defined storage capability of the station. If a FRMR response is not returned for this condition, a TEST response
|
|
* without an information field is returned.
|
|
*
|
|
* The station considers the data-link layer test terminated on receipt of the TEST response, or when a time-out
|
|
* period has expired. The results of the TEST command/response exchange are made available for interrogation
|
|
* by a higher layer.
|
|
*
|
|
* Erratum: TEST frame is not mentioned in the SDL flow charts.
|
|
* Don't know how P/F is supposed to be used.
|
|
* Here, the response sends back what was received in the command.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len)
|
|
{
|
|
cmdres_t res = cr_res;
|
|
int f = pf;
|
|
int nopid = 0;
|
|
packet_t pp;
|
|
|
|
if (cr == cr_cmd) {
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_TEST, f, nopid, info_ptr, info_len);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
}
|
|
|
|
} /* end test_frame */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: dl_timer_expiry
|
|
*
|
|
* Purpose: Some timer expired. Figure out which one and act accordingly.
|
|
*
|
|
* Inputs: none.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
void dl_timer_expiry (void)
|
|
{
|
|
ax25_dlsm_t *p;
|
|
double now = dtime_now();
|
|
|
|
// Examine all of the data link state machines.
|
|
// Process only those where timer:
|
|
// - is running.
|
|
// - is not paused.
|
|
// - expiration time has arrived or passed.
|
|
|
|
for (p = list_head; p != NULL; p = p->next) {
|
|
if (p->t1_exp != 0 && p->t1_paused_at == 0 && p->t1_exp <= now) {
|
|
p->t1_exp = 0;
|
|
p->t1_paused_at = 0;
|
|
p->t1_had_expired = 1;
|
|
t1_expiry (p);
|
|
}
|
|
}
|
|
|
|
for (p = list_head; p != NULL; p = p->next) {
|
|
if (p->t3_exp != 0 && p->t3_exp <= now) {
|
|
p->t3_exp = 0;
|
|
t3_expiry (p);
|
|
}
|
|
}
|
|
|
|
for (p = list_head; p != NULL; p = p->next) {
|
|
if (p->tm201_exp != 0 && p->tm201_paused_at == 0 && p->tm201_exp <= now) {
|
|
p->tm201_exp = 0;
|
|
p->tm201_paused_at = 0;
|
|
tm201_expiry (p);
|
|
}
|
|
}
|
|
|
|
} /* end dl_timer_expiry */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: t1_expiry
|
|
*
|
|
* Purpose: Handle T1 timer expiration for outstanding I frame or P-bit.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* Description: 4.4.5.1. T1 Timer Recovery
|
|
*
|
|
* If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I
|
|
* frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently
|
|
* does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion
|
|
* of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described
|
|
* in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent
|
|
* frame(s), or by the link being reset.
|
|
*
|
|
* 6.7.1.1. Acknowledgment Timer T1
|
|
*
|
|
* T1, the Acknowledgement Timer, ensures that a TNC does not wait indefinitely for a response to a frame it
|
|
* sends. This timer cannot be expressed in absolute time; the time required to send frames varies greatly with the
|
|
* signaling rate used at Layer 1. T1 should take at least twice the amount of time it would take to send maximum
|
|
* length frame to the distant TNC and get the proper response frame back from the distant TNC. This allows time
|
|
* for the distant TNC to do some processing before responding.
|
|
* If Layer 2 repeaters are used, the value of T1 should be adjusted according to the number of repeaters through
|
|
* which the frame is being transferred.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
// Make timer start, stop, expiry a different color to stand out.
|
|
|
|
#define DW_COLOR_DEBUG_TIMER DW_COLOR_ERROR
|
|
|
|
|
|
static void t1_expiry (ax25_dlsm_t *S)
|
|
{
|
|
|
|
if (s_debug_timers) {
|
|
double now = dtime_now();
|
|
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
dw_printf ("t1_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc);
|
|
}
|
|
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
|
|
// Ignore it.
|
|
break;
|
|
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
// MAXV22 hack.
|
|
// If we already sent the maximum number of SABME, fall back to v2.0 SABM.
|
|
|
|
if (S->state == state_5_awaiting_v22_connection && S->rc == g_misc_config_p->maxv22) {
|
|
set_version_2_0 (S);
|
|
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
|
|
}
|
|
|
|
if (S->rc == S->n2_retry) {
|
|
discard_i_queue(S);
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Failed to connect to %s after %d tries.\n", S->addrs[PEERCALL], S->n2_retry);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1);
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
cmdres_t cmd = cr_cmd;
|
|
int p = 1;
|
|
int nopid = 0;
|
|
|
|
packet_t pp;
|
|
|
|
SET_RC(S->rc+1);
|
|
if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // Keep statistics.
|
|
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->state == state_5_awaiting_v22_connection) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
select_t1_value(S);
|
|
START_T1;
|
|
// Keep same state.
|
|
}
|
|
break;
|
|
|
|
case state_2_awaiting_release:
|
|
|
|
if (S->rc == S->n2_retry) {
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
cmdres_t cmd = cr_cmd;
|
|
int p = 1;
|
|
int nopid = 0;
|
|
|
|
packet_t pp;
|
|
|
|
SET_RC(S->rc+1);
|
|
if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc;
|
|
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
select_t1_value(S);
|
|
START_T1;
|
|
// stay in same state
|
|
}
|
|
break;
|
|
|
|
case state_3_connected:
|
|
|
|
SET_RC(1);
|
|
transmit_enquiry (S);
|
|
enter_new_state (S, state_4_timer_recovery, __func__, __LINE__);
|
|
break;
|
|
|
|
case state_4_timer_recovery:
|
|
|
|
if (S->rc == S->n2_retry) {
|
|
|
|
// Erratum: 2006 version, page 103, is missing yes/no labels on decision blocks.
|
|
|
|
if (S->va != S->vs) {
|
|
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error I: %d timeouts: unacknowledged sent data.\n", S->stream_id, S->n2_retry);
|
|
}
|
|
}
|
|
else if (S->peer_receiver_busy) {
|
|
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error U: %d timeouts: extended peer busy condition.\n", S->stream_id, S->n2_retry);
|
|
}
|
|
}
|
|
else {
|
|
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error T: %d timeouts: no response to enquiry.\n", S->stream_id, S->n2_retry);
|
|
}
|
|
}
|
|
|
|
// Erratum: Flow chart says DL-DISCONNECT "request" in both original and 2006 revision.
|
|
// That is clearly wrong because a "request" would come FROM the higher level protocol/client app.
|
|
// I think it should be "indication" rather than "confirm" because the peer condition is unknown.
|
|
|
|
// dl disconnect *indication*
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Stream %d: Disconnected from %s due to timeouts.\n", S->stream_id, S->addrs[PEERCALL]);
|
|
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1);
|
|
|
|
discard_i_queue (S);
|
|
|
|
cmdres_t cr = cr_res; // DM can only be response.
|
|
int f = 0; // Erratum: Assuming F=0 because it is not response to P=1
|
|
int nopid = 0;
|
|
|
|
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, f, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
|
|
}
|
|
else {
|
|
SET_RC(S->rc+1);
|
|
if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // gather statistics.
|
|
|
|
transmit_enquiry (S);
|
|
// Keep same state.
|
|
}
|
|
break;
|
|
}
|
|
|
|
} /* end t1_expiry */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: t3_expiry
|
|
*
|
|
* Purpose: Handle T3 timer expiration.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* Description: TODO: still don't understand this.
|
|
*
|
|
* 4.4.5.2. Timer T3 Recovery
|
|
*
|
|
* Timer T3 ensures that the link is still functional during periods of low information transfer. When T1 is not
|
|
* running (no outstanding I frames), T3 periodically causes the TNC to poll the other TNC of a link. When T3
|
|
* times out, an RR or RNR frame is transmitted as a command with the P bit set, and then T1 is started. When a
|
|
* response to this command is received, T1 is stopped and T3 is started. If T1 expires before a response is
|
|
* received, then the waiting acknowledgement procedure (Section 6.4.11) is executed.
|
|
*
|
|
* 6.7.1.3. Inactive Link Timer T3
|
|
*
|
|
* T3, the Inactive Link Timer, maintains link integrity whenever T1 is not running. It is recommended that
|
|
* whenever there are no outstanding unacknowledged I frames or P-bit frames (during the information-transfer
|
|
* state), an RR or RNR frame with the P bit set to "1" be sent every T3 time units to query the status of the other
|
|
* TNC. The period of T3 is locally defined, and depends greatly on Layer 1 operation. T3 should be greater than
|
|
* T1; it may be very large on channels of high integrity.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void t3_expiry (ax25_dlsm_t *S)
|
|
{
|
|
|
|
if (s_debug_timers) {
|
|
double now = dtime_now();
|
|
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
dw_printf ("t3_expiry (), [now=%.3f]\n", now - S->start_time);
|
|
}
|
|
|
|
switch (S->state) {
|
|
|
|
case state_0_disconnected:
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
case state_2_awaiting_release:
|
|
case state_4_timer_recovery:
|
|
|
|
break;
|
|
|
|
case state_3_connected:
|
|
|
|
// Erratum: Original sets RC to 0, 2006 revision sets RC to 1 which makes more sense.
|
|
|
|
SET_RC(1);
|
|
transmit_enquiry (S);
|
|
enter_new_state (S, state_4_timer_recovery, __func__, __LINE__);
|
|
break;
|
|
}
|
|
|
|
} /* end t3_expiry */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: tm201_expiry
|
|
*
|
|
* Purpose: Handle TM201 timer expiration.
|
|
*
|
|
* Inputs: S - Data Link State Machine.
|
|
*
|
|
* Description: This is used when waiting for a response to an XID command.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static void tm201_expiry (ax25_dlsm_t *S)
|
|
{
|
|
|
|
struct xid_param_s param;
|
|
unsigned char xinfo[40];
|
|
int xlen;
|
|
cmdres_t cmd = cr_cmd;
|
|
int p = 1;
|
|
int nopid = 0;
|
|
packet_t pp;
|
|
|
|
|
|
if (s_debug_timers) {
|
|
double now = dtime_now();
|
|
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
dw_printf ("tm201_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc);
|
|
}
|
|
|
|
switch (S->mdl_state) {
|
|
|
|
case mdl_state_0_ready:
|
|
|
|
// Timer shouldn't be running when in this state.
|
|
|
|
break;
|
|
|
|
case mdl_state_1_negotiating:
|
|
|
|
S->mdl_rc++;
|
|
if (S->mdl_rc > S->n2_retry) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error MDL-C: Management retry limit exceeded.\n", S->stream_id);
|
|
S->mdl_state = mdl_state_0_ready;
|
|
}
|
|
else {
|
|
// No response. Ask again.
|
|
|
|
initiate_negotiation (S, ¶m);
|
|
|
|
xlen = xid_encode (¶m, xinfo, cmd);
|
|
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
START_TM201;
|
|
}
|
|
break;
|
|
}
|
|
|
|
} /* end tm201_expiry */
|
|
|
|
|
|
//###################################################################################
|
|
//###################################################################################
|
|
//
|
|
// Subroutines from protocol spec, pages 106 - 109
|
|
//
|
|
//###################################################################################
|
|
//###################################################################################
|
|
|
|
// FIXME: continue review here.
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: nr_error_recovery
|
|
*
|
|
* Purpose: Try to recover after receiving an expected N(r) value.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void nr_error_recovery (ax25_dlsm_t *S)
|
|
{
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error J: N(r) sequence error.\n", S->stream_id);
|
|
}
|
|
establish_data_link (S);
|
|
S->layer_3_initiated = 0;
|
|
|
|
} /* end nr_error_recovery */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: establish_data_link
|
|
* (Combined with "establish extended data link")
|
|
*
|
|
* Purpose: Send SABM or SABME to other station.
|
|
*
|
|
* Inputs: S->
|
|
* addrs destination, source, and optional digi addresses.
|
|
* num_addr Number of addresses. Should be 2 .. 10.
|
|
* modulo Determines if we send SABME or SABM.
|
|
*
|
|
* Description: Original spec had two different functions that differed
|
|
* only by sending SABM or SABME. Here they are combined into one.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void establish_data_link (ax25_dlsm_t *S)
|
|
{
|
|
cmdres_t cmd = cr_cmd;
|
|
int p = 1;
|
|
packet_t pp;
|
|
int nopid = 0;
|
|
|
|
clear_exception_conditions (S);
|
|
|
|
// Erratum: We have an off-by-one error here.
|
|
// Flow chart shows setting RC to 0 and we end up sending SAMB(e) 11 times when N2 (RETRY) is 10.
|
|
// It should be 1 rather than 0.
|
|
|
|
SET_RC(1);
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->modulo == 128) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
STOP_T3;
|
|
START_T1;
|
|
|
|
} /* end establish_data_link */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: clear_exception_conditions
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void clear_exception_conditions (ax25_dlsm_t *S)
|
|
{
|
|
S->peer_receiver_busy = 0;
|
|
S->reject_exception = 0;
|
|
S->own_receiver_busy = 0;
|
|
S->acknowledge_pending = 0;
|
|
|
|
// My enhancement. If we are establishing a new connection, we should discard any saved out of sequence incoming I frames.
|
|
|
|
int n;
|
|
|
|
for (n = 0; n < 128; n++) {
|
|
if (S->rxdata_by_ns[n] != NULL) {
|
|
cdata_delete (S->rxdata_by_ns[n]);
|
|
S->rxdata_by_ns[n] = NULL;
|
|
}
|
|
}
|
|
|
|
// We retain the transmit I frame queue so we can continue after establishing a new connection.
|
|
|
|
} /* end clear_exception_conditions */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: transmit_enquiry, page 106
|
|
*
|
|
* Purpose: This is called only when a timer expires.
|
|
*
|
|
* T1: We sent I frames and timed out waiting for the ack.
|
|
* Poke the other end to determine how much it got so far
|
|
* so we know where to continue.
|
|
*
|
|
* T3: Not activity for substantial amount of time.
|
|
* Poke the other end to see if it is still there.
|
|
*
|
|
*
|
|
* Observation: This is the only place where we send RR command with P=1.
|
|
*
|
|
* Sequence of events:
|
|
*
|
|
* We send some I frames to the other guy.
|
|
* There are outstanding sent I frames for which we did not receive ACK.
|
|
*
|
|
* Timer 1 expires when we are in state 3: send RR/RNR command P=1 (here). Enter state 4.
|
|
* Timer 1 expires when we are in state 4: same until max retry count is exceeded.
|
|
*
|
|
* Other guy gets RR/RNR command P=1.
|
|
* Same action for either state 3 or 4.
|
|
* Whether he has outstanding un-ack'ed sent I frames is irrelevent.
|
|
* He calls "enquiry response" which sends RR/RNR response F=1.
|
|
* (Read about detour 1 below and in enquiry_response.)
|
|
*
|
|
* I get back RR/RNR response F=1. Still in state 4.
|
|
* Of course, N(R) gets copied into V(A).
|
|
* Now here is the interesting part.
|
|
* If the ACKs are caught up, i.e. V(A) == V(S), stop T1 and enter state 3.
|
|
* Otherwise, "invoke retransmission" to resend everything after N(R).
|
|
*
|
|
*
|
|
* Detour 1: You were probably thinking, "Suppose SREJ is enabled and the other guy
|
|
* had a record of the SREJ frames sent which were not answered by filled in
|
|
* I frames. Why not send the SREJ again instead of backing up and resending
|
|
* stuff which already got there OK?"
|
|
*
|
|
* The code to handle incoming SREJ in state 4 is there but stop T1 is in the
|
|
* wrong place as mentioned above.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void transmit_enquiry (ax25_dlsm_t *S)
|
|
{
|
|
int p = 1;
|
|
int nr = S->vr;
|
|
cmdres_t cmd = cr_cmd;
|
|
packet_t pp;
|
|
|
|
if (s_debug_retry) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\n****** TRANSMIT ENQUIRY RR/RNR cmd P=1 ****** state=%d, rc=%d\n\n", S->state, S->rc);
|
|
}
|
|
|
|
// This is the ONLY place that we send RR/RNR *command* with P=1.
|
|
// Everywhere else should be response.
|
|
// I don't think we ever use RR/RNR command P=0 but need to check on that.
|
|
|
|
pp = ax25_s_frame (S->addrs, S->num_addr, cmd, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, p, NULL, 0);
|
|
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
S->acknowledge_pending = 0;
|
|
START_T1;
|
|
|
|
} /* end transmit_enquiry */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: enquiry_response
|
|
*
|
|
* Inputs: frame_type - Type of frame received or frame_not_AX25 for LM seize confirm.
|
|
* I think that this function is being called from too many
|
|
* different contexts where it really needs to react differently.
|
|
* So pass in more information about where we are coming from.
|
|
*
|
|
* F - Always specified as parameter in the references.
|
|
*
|
|
* Description: This is called for:
|
|
* - UI command with P=1 then F=1.
|
|
* - LM seize confirm with ack pending then F=0. (TODO: not clear on this yet.)
|
|
* TODO: I think we want to ensure that this function is called ONLY
|
|
* for RR/RNR/I command with P=1. LM Seize confirm can do its own thing and
|
|
* not get involved in this complication.
|
|
* - check_need_for_response(), command & P=1, then F=1
|
|
* - RR/RNR/REJ command & P=1, then F=1
|
|
*
|
|
* In all cases, we see that F has been specified, usually 1 because it is
|
|
* a response to a command with P=1.
|
|
* Specifying F would imply response when the flow chart says RR/RNR command.
|
|
* The documentation says:
|
|
*
|
|
* 6.2. Poll/Final (P/F) Bit Procedures
|
|
*
|
|
* The next response frame returned to an I frame with the P bit set to "1", received during the information
|
|
* transfer state, is an RR, RNR or REJ response with the F bit set to "1".
|
|
*
|
|
* The next response frame returned to a supervisory command frame with the P bit set to "1", received during
|
|
* the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1".
|
|
*
|
|
* Erattum! The flow chart says RR/RNR *command* but I'm confident it should be response.
|
|
*
|
|
* Erratum: Ax.25 spec has nothing here for SREJ. See X.25 2.4.6.11 for explanation.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f)
|
|
{
|
|
cmdres_t cr = cr_res; // Response, not command as seen in flow chart.
|
|
int nr = S->vr;
|
|
packet_t pp;
|
|
|
|
|
|
if (s_debug_retry) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\n****** ENQUIRY RESPONSE F=%d ******\n\n", f);
|
|
}
|
|
|
|
#if 1 // Detour 1
|
|
|
|
// My addition, Based on X.25 2.4.6.11.
|
|
// Only for RR, RNR, I.
|
|
// See sequence of events in transmit_enquiry comments.
|
|
|
|
if (f == 1 && (frame_type == frame_type_S_RR || frame_type == frame_type_S_RNR || frame_type == frame_type_I)) {
|
|
|
|
if (S->own_receiver_busy) {
|
|
|
|
// I'm busy.
|
|
|
|
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
S->acknowledge_pending = 0; // because we sent N(R) from V(R).
|
|
}
|
|
|
|
else if (S->srej_enable == srej_single || S->srej_enable == srej_multi) {
|
|
|
|
|
|
// SREJ is enabled. This is based on X.25 2.4.6.11.
|
|
|
|
if (S->modulo != 128) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("INTERNAL ERROR: enquiry response should not be sending SREJ for modulo 8.\n");
|
|
}
|
|
|
|
// Suppose we received I frames with N(S) of 0, 3, 7.
|
|
// V(R) is still 1 because 0 is the last one received with contiguous N(S) values.
|
|
// 3 and 7 have been saved into S->rxdata_by_ns.
|
|
// We have outstanding requests to resend 1, 2, 4, 5, 6.
|
|
// Either those requests or the replies got lost.
|
|
// The other end timed out and asked us what is happening by sending RR/RNR command P=1.
|
|
|
|
// First see if we have any out of sequence frames in the receive buffer.
|
|
|
|
int last;
|
|
last = AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__);
|
|
while (last != S->vr && S->rxdata_by_ns[last] == NULL) {
|
|
last = AX25MODULO(last - 1, S->modulo, __FILE__, __func__, __LINE__);
|
|
}
|
|
|
|
if (last != S->vr) {
|
|
|
|
// Ask for missing frames to be sent again. X.25 2.4.6.11 b) & 2.3.5.2.2
|
|
|
|
int resend[128];
|
|
int count = 0;
|
|
int j;
|
|
int allow_f1 = 1;
|
|
|
|
j = S->vr;
|
|
while (j != last) {
|
|
if (S->rxdata_by_ns[j] == NULL) {
|
|
resend[count++] = j;
|
|
}
|
|
j = AX25MODULO(j + 1, S->modulo, __FILE__, __func__, __LINE__);
|
|
}
|
|
|
|
send_srej_frames (S, resend, count, allow_f1);
|
|
}
|
|
else {
|
|
|
|
// Not waiting for fill in of missing frames. X.25 2.4.6.11 c)
|
|
|
|
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
S->acknowledge_pending = 0;
|
|
}
|
|
|
|
} else {
|
|
|
|
// SREJ not enabled.
|
|
// One might get the idea that it would make sense send REJ here if the reject exception is set.
|
|
// However, I can't seem to find that buried in X.25 2.4.5.9.
|
|
// And when we look at what happens when RR response, F=1 is received in state 4, it is
|
|
// effectively REJ when N(R) is not the same as V(S).
|
|
|
|
if (s_debug_retry) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\n****** ENQUIRY RESPONSE srej not enbled, sending RR resp F=%d ******\n\n", f);
|
|
}
|
|
|
|
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
S->acknowledge_pending = 0;
|
|
}
|
|
|
|
} // end of RR,RNR,I cmd with P=1
|
|
|
|
else {
|
|
|
|
// For cases other than (RR, RNR, I) command, P=1.
|
|
|
|
pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
S->acknowledge_pending = 0;
|
|
}
|
|
|
|
#else
|
|
|
|
// As found in AX.25 spec.
|
|
// Erratum: This is woefully inadequate when SREJ is enabled.
|
|
// Erratum: Flow chart says RR/RNR command but I'm confident it should be response.
|
|
|
|
pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f, NULL, 0);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
S->acknowledge_pending = 0;
|
|
|
|
# endif
|
|
|
|
} /* end enquiry_response */
|
|
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: invoke_retransmission
|
|
*
|
|
* Inputs: nr_input - Resend starting with this.
|
|
* Continue will all up to and including current V(S) value.
|
|
*
|
|
* Description: Resend one or more frames that have already been sent.
|
|
* Should always send at least one.
|
|
*
|
|
* This is probably the result of getting REJ asking for a resend.
|
|
*
|
|
* Context: I would expect the caller to clear 'acknowledge_pending' after calling this
|
|
* because we sent N(R), from V(R), to ack what was received from other guy.
|
|
* I would also expect Stop T3 & Start T1 at the same place.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void invoke_retransmission (ax25_dlsm_t *S, int nr_input)
|
|
{
|
|
|
|
// Original flow chart showed saving V(S) into temp variable x,
|
|
// using V(S) as loop control variable, and finally restoring it
|
|
// to original value before returning.
|
|
// Here we just a local variable instead of messing with it.
|
|
// This should be equivalent but safer.
|
|
|
|
int local_vs;
|
|
int sent_count = 0;
|
|
|
|
if (s_debug_misc) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("invoke_retransmission(): starting with %d, state=%d, rc=%d, \n", nr_input, S->state, S->rc);
|
|
}
|
|
|
|
// I don't think we should be here if SREJ is enabled.
|
|
// TODO: Figure out why this happens occasionally.
|
|
|
|
// if (S->srej_enable != srej_none) {
|
|
// text_color_set(DW_COLOR_ERROR);
|
|
// dw_printf ("Internal Error, Did not expect to be here when SREJ enabled. %s %s %d\n", __FILE__, __func__, __LINE__);
|
|
// }
|
|
|
|
if (S->txdata_by_ns[nr_input] == NULL) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Internal Error, Can't resend starting with N(S) = %d. It is not available. %s %s %d\n", nr_input, __FILE__, __func__, __LINE__);
|
|
return;
|
|
}
|
|
|
|
|
|
local_vs = nr_input;
|
|
do {
|
|
|
|
if (S->txdata_by_ns[local_vs] != NULL) {
|
|
|
|
cmdres_t cr = cr_cmd;
|
|
int ns = local_vs;
|
|
int nr = S->vr;
|
|
int p = 0;
|
|
|
|
if (s_debug_misc) {
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("invoke_retransmission(): Resending N(S) = %d\n", ns);
|
|
}
|
|
|
|
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p,
|
|
S->txdata_by_ns[ns]->pid, (unsigned char *)(S->txdata_by_ns[ns]->data), S->txdata_by_ns[ns]->len);
|
|
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
// Keep it around in case we need to send again.
|
|
|
|
sent_count++;
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Internal Error, state=%d, need to retransmit N(S) = %d for REJ but it is not available. %s %s %d\n", S->state, local_vs, __FILE__, __func__, __LINE__);
|
|
}
|
|
local_vs = AX25MODULO(local_vs + 1, S->modulo, __FILE__, __func__, __LINE__);
|
|
|
|
} while (local_vs != S->vs);
|
|
|
|
if (sent_count == 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Internal Error, Nothing to retransmit. N(R)=%d, %s %s %d\n", nr_input, __FILE__, __func__, __LINE__);
|
|
}
|
|
|
|
} /* end invoke_retransmission */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: check_i_frame_ackd
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Inputs: nr - N(R) from I or S frame, acknowledging receipt thru N(R)-1.
|
|
* i.e. The next one expected by the peer is N(R).
|
|
*
|
|
* Outputs: S->va - updated from nr.
|
|
*
|
|
* Description: This is called for:
|
|
* - 'I' frame received and N(R) is in expected range, states 3 & 4.
|
|
* - RR/RNR command with p=1 received and N(R) is in expected range, state 3 only.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void check_i_frame_ackd (ax25_dlsm_t *S, int nr)
|
|
{
|
|
if (S->peer_receiver_busy) {
|
|
SET_VA(nr);
|
|
|
|
// Erratum? This looks odd to me.
|
|
// It doesn't seem right that we would have T3 and T1 running at the same time.
|
|
// Normally we stop one when starting the other.
|
|
// Should this be Stop T3 instead?
|
|
|
|
START_T3;
|
|
if ( ! IS_T1_RUNNING) {
|
|
START_T1;
|
|
}
|
|
}
|
|
else if (nr == S->vs) {
|
|
SET_VA(nr);
|
|
STOP_T1;
|
|
START_T3;
|
|
select_t1_value (S);
|
|
}
|
|
else if (nr != S->va) {
|
|
|
|
if (s_debug_misc) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("check_i_frame_ackd n(r)=%d, v(a)=%d, Set v(a) to new value %d\n", nr, S->va, nr);
|
|
}
|
|
|
|
SET_VA(nr);
|
|
START_T1; // Erratum? Flow chart says "restart" rather than "start."
|
|
// Is this intentional, what is the difference?
|
|
}
|
|
|
|
} /* check_i_frame_ackd */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: check_need_for_response
|
|
*
|
|
* Inputs: frame_type - frame_type_S_RR, etc.
|
|
*
|
|
* cr - Is it a command or response?
|
|
*
|
|
* pf - P/F from the frame.
|
|
*
|
|
* Description: This is called for RR, RNR, and REJ frames.
|
|
* If it is a command with P=1, we reply with RR or RNR with F=1.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf)
|
|
{
|
|
if (cr == cr_cmd && pf == 1) {
|
|
int f = 1;
|
|
enquiry_response (S, frame_type, f);
|
|
}
|
|
else if (cr == cr_res && pf == 1) {
|
|
if (s_debug_protocol_errors) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Stream %d: AX.25 Protocol Error A: F=1 received but P=1 not outstanding.\n", S->stream_id);
|
|
}
|
|
}
|
|
|
|
} /* end check_need_for_response */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: ui_check
|
|
*
|
|
* Description: I don't think we need this because UI frames are processed
|
|
* without going thru the data link state machine.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: select_t1_value
|
|
*
|
|
* Purpose: Dynamically adjust the T1 timeout value, commonly a fixed time known as FRACK.
|
|
*
|
|
* Inputs: S->rc Retry counter.
|
|
*
|
|
* S->srt Smoothed roundtrip time in seconds.
|
|
*
|
|
* S->t1_remaining_when_last_stopped
|
|
* Seconds left on T1 when it is stopped.
|
|
*
|
|
* Outputs: S->srt New smoothed roundtrip time.
|
|
*
|
|
* S->t1v How long to wait for an acknowlegement before resending.
|
|
* Value used when starting timer T1, in seconds.
|
|
* Here it is dynamically adjusted.
|
|
*
|
|
* Description: How long should we wait for an ACK before sending again or giving up?
|
|
* some implementations have a fixed length time. This is usually the FRACK parameter,
|
|
* typically 3 seconds (D710A) or 4 seconds (KPC-3+).
|
|
*
|
|
* This should be increased for each digipeater in the path.
|
|
* Here it is dynamically adjusted by taking the average time it takes to get a response
|
|
* and then we double it.
|
|
*
|
|
* Rambling: It seems like a good idea to adapt to channel conditions, such as digipeater delays,
|
|
* but it is fraught with peril if you are not careful.
|
|
*
|
|
* For example, if we accept an incoming connection and only receive some I frames and
|
|
* send no I frames, T1 never gets started. In my earlier attempt, 't1_remaining_when_last_stopped'
|
|
* had the initial value of 0 lacking any reason to set it differently. The calculation here
|
|
* then kept pushing t1v up up up. After receiving 20 I frames and sending none,
|
|
* t1v was over 300 seconds!!!
|
|
*
|
|
* We need some way to indicate that 't1_remaining_when_last_stopped' is not valid and
|
|
* not to use it. Rather than adding a new variable, it is set to a negative value
|
|
* initially to mean it has not been set yet. That solves one problem.
|
|
*
|
|
* T1 is paused whenever the channel is busy, either transmitting or receiving,
|
|
* so the measured time could turn out to be a tiny fraction of a second, much less than
|
|
* the frame transmission time.
|
|
* If this gets too low, an unusually long random delay, before the sender's transmission,
|
|
* could exceed this. I put in a lower limit for t1v, currently 1 second.
|
|
*
|
|
* What happens if we get multiple timeouts because we don't get a response?
|
|
* For example, when we try to connect to a station which is not there, a KPC-3+ will give
|
|
* up and report failure after 10 tries x 4 sec = 40 seconds.
|
|
*
|
|
* The algorithm in the AX.25 protocol spec shows increasing timeout values.
|
|
* It might seem like a good idea but either it was not thought out very well
|
|
* or I am not understanding it. If it is doubled each time, it gets awful large
|
|
* very quickly. If we try to connect to a station which is not there,
|
|
* we want to know within a minute, not an hour later.
|
|
*
|
|
* Keeping with the spirit of increasing the time but keeping it sane,
|
|
* I increase the time linearly by a fraction of a second.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static void select_t1_value (ax25_dlsm_t *S)
|
|
{
|
|
float old_srt = S->srt;
|
|
|
|
|
|
// Erratum: I don't think this test for RC == 0 is valid.
|
|
// We would need to set RC to 0 whenever we enter state 3 and we don't do that.
|
|
// I think a more appropriate test would be to check if we are in state 3.
|
|
// When things are going smoothly, it makes sense to fine tune timeout based on smoothed round trip time.
|
|
// When in some other state, we might want to slowly increase the time to minimize collisions.
|
|
// Maybe the solution is to set RC=0 when we enter state 3.
|
|
|
|
// TODO: come back and revisit this.
|
|
|
|
if (S->rc == 0) {
|
|
|
|
if (S->t1_remaining_when_last_stopped >= 0) { // Negative means invalid, don't use it.
|
|
|
|
// This is an IIR low pass filter.
|
|
// Algebraically equivalent to version in AX.25 protocol spec but I think the
|
|
// original intent is clearer by having 1/8 appear only once.
|
|
|
|
S->srt = 7./8. * S->srt + 1./8. * ( S->t1v - S->t1_remaining_when_last_stopped );
|
|
}
|
|
|
|
// We pause T1 when the channel is busy.
|
|
// This includes both receiving someone else and us transmitting.
|
|
// This can result in the round trip time going down to almost nothing.
|
|
// My enhancement is to prevent srt from going below one second so
|
|
// t1v should never be less than 2 seconds.
|
|
// When t1v was allowed to go down to 1, we got occastional timeouts
|
|
// even under ideal conditions, probably due to random CSMA delay time.
|
|
|
|
if (S->srt < 1) {
|
|
|
|
S->srt = 1;
|
|
|
|
// Add another 2 seconds for each digipeater in path.
|
|
|
|
if (S->num_addr > 2) {
|
|
S->srt += 2 * (S->num_addr - 2);
|
|
}
|
|
}
|
|
|
|
S->t1v = S->srt * 2;
|
|
}
|
|
else {
|
|
|
|
if (S->t1_had_expired) {
|
|
|
|
// This goes up exponentially if implemented as documented!
|
|
// For example, if we were trying to connect to a station which is not there, we
|
|
// would retry after 3, the 8, 16, 32, ... and not time out for over an hour.
|
|
// That's ridiculous. Let's try increasing it by a quarter second each time.
|
|
// We now give up after about a minute.
|
|
|
|
// NO! S->t1v = powf(2, S->rc+1) * S->srt;
|
|
|
|
S->t1v = S->rc * 0.25 + S->srt * 2;
|
|
}
|
|
}
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, new t1v = %.3f\n",
|
|
S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v);
|
|
}
|
|
|
|
|
|
if (S->t1v < 0.99 || S->t1v > 30) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("INTERNAL ERROR? Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, Extreme new t1v = %.3f\n",
|
|
S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v);
|
|
}
|
|
|
|
} /* end select_t1_value */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: set_version_2_0
|
|
*
|
|
* Erratum: Flow chart refers to T2 which doesn't appear anywhere else.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void set_version_2_0 (ax25_dlsm_t *S)
|
|
{
|
|
S->srej_enable = srej_none;
|
|
S->modulo = 8;
|
|
S->n1_paclen = g_misc_config_p->paclen;
|
|
S->k_maxframe = g_misc_config_p->maxframe_basic;
|
|
S->n2_retry = g_misc_config_p->retry;
|
|
|
|
} /* end set_version_2_0 */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: set_version_2_2
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void set_version_2_2 (ax25_dlsm_t *S)
|
|
{
|
|
S->srej_enable = srej_single; // Start with single.
|
|
// Can be increased to multi with XID exchange.
|
|
S->modulo = 128;
|
|
S->n1_paclen = g_misc_config_p->paclen;
|
|
S->k_maxframe = g_misc_config_p->maxframe_extended;
|
|
S->n2_retry = g_misc_config_p->retry;
|
|
|
|
} /* end set_version_2_2 */
|
|
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: is_good_nr
|
|
*
|
|
* Purpose: Evaluate condition "V(a) <= N(r) <= V(s)" which appears in flow charts
|
|
* for incoming I, RR, RNR, REJ, and SREJ frames.
|
|
*
|
|
* Inputs: S - state machine. Contains V(a) and V(s).
|
|
*
|
|
* nr - N(r) found in the incoming frame.
|
|
*
|
|
* Description: This determines whether the Received Sequence Number, N(R), is in
|
|
* the expected range for normal processing or if we have an error
|
|
* condition that needs recovery.
|
|
*
|
|
* This gets tricky due to the wrap around of sequence numbers.
|
|
*
|
|
* 4.2.4.4. Received Sequence Number N(R)
|
|
*
|
|
* The received sequence number exists in both I and S frames.
|
|
* Prior to sending an I or S frame, this variable is updated to equal that
|
|
* of the received state variable, thus implicitly acknowledging the proper
|
|
* reception of all frames up to and including N(R)-1.
|
|
*
|
|
* Pattern noticed: Anytime we have "is_good_nr" returning true, we should always
|
|
* - set V(A) from N(R) or
|
|
* - call "check_i_frame_ackd" which does the same and some timer stuff.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static int is_good_nr (ax25_dlsm_t *S, int nr)
|
|
{
|
|
int adjusted_va, adjusted_nr, adjusted_vs;
|
|
int result;
|
|
|
|
/* Adjust all values relative to V(a) before comparing so we won't have wrap around. */
|
|
|
|
#define adjust_by_va(x) (AX25MODULO((x) - S->va, S->modulo, __FILE__, __func__, __LINE__))
|
|
|
|
adjusted_va = adjust_by_va(S->va); // A clever compiler would know it is zero.
|
|
adjusted_nr = adjust_by_va(nr);
|
|
adjusted_vs = adjust_by_va(S->vs);
|
|
|
|
result = adjusted_va <= adjusted_nr && adjusted_nr <= adjusted_vs;
|
|
|
|
if (s_debug_misc) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("is_good_nr, V(a) %d <= nr %d <= V(s) %d, returns %d\n", S->va, nr, S->vs, result);
|
|
}
|
|
|
|
return (result);
|
|
|
|
} /* end is_good_nr */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: i_frame_pop_off_queue
|
|
*
|
|
* Purpose: Transmit an I frame if we have one in the queue and conditions are right.
|
|
* This appears two slightly different ways in the flow charts:
|
|
* "frame pop off queue"
|
|
* "I frame pops off queue"
|
|
*
|
|
* Inputs: i_frame_queue - Remove items from here.
|
|
* peer_receiver_busy - If other end not busy.
|
|
* V(s) - and we haven't reached window size.
|
|
* V(a)
|
|
* k
|
|
*
|
|
* Outputs: v(s) is incremented for each processed.
|
|
* acknowledge_pending = 0
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void i_frame_pop_off_queue (ax25_dlsm_t *S)
|
|
{
|
|
|
|
|
|
if (s_debug_misc) {
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("i_frame_pop_off_queue () state=%d\n", S->state);
|
|
}
|
|
|
|
// TODO: Were we expecting something in the queue?
|
|
// or is empty an expected situation?
|
|
|
|
if (S->i_frame_queue == NULL) {
|
|
|
|
if (s_debug_misc) {
|
|
// TODO: add different switch for I frame queue.
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("i_frame_pop_off_queue () queue is empty get out, line %d\n", __LINE__);
|
|
}
|
|
|
|
// I Frame queue is empty.
|
|
// Nothing to see here, folks. Move along.
|
|
return;
|
|
}
|
|
|
|
switch (S->state) {
|
|
|
|
case state_1_awaiting_connection:
|
|
case state_5_awaiting_v22_connection:
|
|
|
|
if (s_debug_misc) {
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("i_frame_pop_off_queue () line %d\n", __LINE__);
|
|
}
|
|
|
|
// This seems to say remove the I Frame from the queue and discard it if "layer 3 initiated" is set.
|
|
|
|
// For the case of removing it from the queue and putting it back in we just leave it there.
|
|
|
|
// Erratum? The flow chart seems to be backwards.
|
|
// It would seem like we want to keep it if we are further along in the connection process.
|
|
// I don't understand the intention here, and can't make a compelling argument on why it
|
|
// is backwards, so it is implemented as documented.
|
|
|
|
if (S->layer_3_initiated) {
|
|
cdata_t *txdata;
|
|
|
|
if (s_debug_misc) {
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("i_frame_pop_off_queue () discarding due to L3 init. line %d\n", __LINE__);
|
|
}
|
|
txdata = S->i_frame_queue; // Remove from head of list.
|
|
S->i_frame_queue = txdata->next;
|
|
cdata_delete (txdata);
|
|
}
|
|
break;
|
|
|
|
case state_3_connected:
|
|
case state_4_timer_recovery:
|
|
|
|
if (s_debug_misc) {
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("i_frame_pop_off_queue () state %d, line %d\n", S->state, __LINE__);
|
|
}
|
|
|
|
while ( ( ! S->peer_receiver_busy ) &&
|
|
S->i_frame_queue != NULL &&
|
|
WITHIN_WINDOW_SIZE(S) ) {
|
|
|
|
cdata_t *txdata;
|
|
|
|
txdata = S->i_frame_queue; // Remove from head of list.
|
|
S->i_frame_queue = txdata->next;
|
|
txdata->next = NULL;
|
|
|
|
cmdres_t cr = cr_cmd;
|
|
int ns = S->vs;
|
|
int nr = S->vr;
|
|
int p = 0;
|
|
|
|
if (s_debug_misc || s_debug_radio) {
|
|
//dw_printf ("i_frame_pop_off_queue () ns=%d, queue for transmit \"", ns);
|
|
//ax25_safe_print (txdata->data, txdata->len, 1);
|
|
//dw_printf ("\"\n");
|
|
}
|
|
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len);
|
|
|
|
if (s_debug_misc) {
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__);
|
|
}
|
|
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
// Stash in sent array in case it gets lost and needs to be sent again.
|
|
|
|
if (S->txdata_by_ns[ns] != NULL) {
|
|
cdata_delete (S->txdata_by_ns[ns]);
|
|
}
|
|
S->txdata_by_ns[ns] = txdata;
|
|
|
|
SET_VS(AX25MODULO(S->vs + 1, S->modulo, __FILE__, __func__, __LINE__)); // increment sequence of last sent.
|
|
|
|
S->acknowledge_pending = 0;
|
|
|
|
// Erratum: I think we always want to restart T1 when an I frame is sent.
|
|
// Otherwise we could time out too soon.
|
|
#if 1
|
|
STOP_T3;
|
|
START_T1;
|
|
#else
|
|
if ( ! IS_T1_RUNNING) {
|
|
STOP_T3;
|
|
START_T1;
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case state_0_disconnected:
|
|
case state_2_awaiting_release:
|
|
|
|
// Do nothing.
|
|
break;
|
|
}
|
|
|
|
} /* end i_frame_pop_off_queue */
|
|
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: discard_i_queue
|
|
*
|
|
* Purpose: Discard any data chunks waiting to be sent.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
|
|
static void discard_i_queue (ax25_dlsm_t *S)
|
|
{
|
|
cdata_t *t;
|
|
|
|
while (S->i_frame_queue != NULL) {
|
|
|
|
t = S->i_frame_queue;
|
|
S->i_frame_queue = S->i_frame_queue->next;
|
|
cdata_delete (t);
|
|
}
|
|
|
|
} /* end discard_i_queue */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: enter_new_state
|
|
*
|
|
* Purpose: Switch to new state.
|
|
*
|
|
* Description: Use a function, rather than setting variable directly, so we have
|
|
* one common point for debug output and possibly other things we
|
|
* might want to do at a state change.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
// TODO: requeuing???
|
|
|
|
static void enter_new_state (ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line)
|
|
{
|
|
|
|
if (s_debug_variables) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\n");
|
|
dw_printf (">>> NEW STATE = %d, previously %d, called from %s %d <<<\n", new_state, S->state, from_func, from_line);
|
|
dw_printf ("\n");
|
|
}
|
|
|
|
assert (new_state >= 0 && new_state <= 5);
|
|
|
|
|
|
if (( new_state == state_3_connected || new_state == state_4_timer_recovery) &&
|
|
S->state != state_3_connected && S->state != state_4_timer_recovery ) {
|
|
|
|
ptt_set (OCTYPE_CON, S->chan, 1); // Turn on connected indicator if configured.
|
|
}
|
|
else if (( new_state != state_3_connected && new_state != state_4_timer_recovery) &&
|
|
( S->state == state_3_connected || S->state == state_4_timer_recovery ) ) {
|
|
|
|
ptt_set (OCTYPE_CON, S->chan, 0); // Turn off connected indicator if configured.
|
|
// Ideally we should look at any other link state machines
|
|
// for this channel and leave the indicator on if any
|
|
// are connected. I'm not that worried about it.
|
|
}
|
|
|
|
S->state = new_state;
|
|
|
|
} /* end enter_new_state */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: mdl_negotiate_request
|
|
*
|
|
* Purpose: After receiving UA, in response to SABME, this starts up the XID exchange.
|
|
*
|
|
* Description: Send XID command.
|
|
* Start timer TM201 so we can retry if timeout waiting for response.
|
|
* Enter MDL negotiating state.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void mdl_negotiate_request (ax25_dlsm_t *S)
|
|
{
|
|
struct xid_param_s param;
|
|
unsigned char xinfo[40];
|
|
int xlen;
|
|
cmdres_t cmd = cr_cmd;
|
|
int p = 1;
|
|
int nopid = 0;
|
|
packet_t pp;
|
|
int n;
|
|
|
|
// At least one known [partial] v2.2 implementation understands SABME but not XID.
|
|
// Rather than wasting time, sending XID repeatedly until giving up, we have a workaround.
|
|
// The configuration file can contain a list of stations known not to respond to XID.
|
|
// Obviously this applies only to v2.2 because XID was not part of v2.0.
|
|
|
|
for (n = 0; n < g_misc_config_p->noxid_count; n++) {
|
|
if (strcmp(S->addrs[PEERCALL],g_misc_config_p->noxid_addrs[n]) == 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (S->mdl_state) {
|
|
|
|
case mdl_state_0_ready:
|
|
|
|
initiate_negotiation (S, ¶m);
|
|
|
|
xlen = xid_encode (¶m, xinfo, cmd);
|
|
|
|
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen);
|
|
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
|
|
|
|
S->mdl_rc = 0;
|
|
START_TM201;
|
|
S->mdl_state = mdl_state_1_negotiating;
|
|
|
|
break;
|
|
|
|
case mdl_state_1_negotiating:
|
|
|
|
// SDL says "requeue" but I don't understand how it would be useful or how to do it.
|
|
break;
|
|
}
|
|
|
|
} /* end mdl_negotiate_request */
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: initiate_negotiation
|
|
*
|
|
* Purpose: Used when preparing the XID *command*.
|
|
*
|
|
* Description: Prepare set of parameters to request from the other station.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param)
|
|
{
|
|
param->full_duplex = 0;
|
|
switch (S->srej_enable) {
|
|
case srej_single:
|
|
case srej_multi:
|
|
param->srej = srej_multi; // see if other end reconizes it.
|
|
break;
|
|
case srej_none:
|
|
default:
|
|
param->srej = srej_none;
|
|
break;
|
|
}
|
|
|
|
param->modulo = S->modulo;
|
|
param->i_field_length_rx = S->n1_paclen; // Hmmmm. Should we ask for what the user
|
|
// specified for PACLEN or offer the maximum
|
|
// that we can handle, AX25_N1_PACLEN_MAX?
|
|
param->window_size_rx = S->k_maxframe;
|
|
param->ack_timer = (int)(g_misc_config_p->frack * 1000);
|
|
param->retries = S->n2_retry;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: negotiation_response
|
|
*
|
|
* Purpose: Used when receiving the XID command and preparing the XID response.
|
|
*
|
|
* Description: Take what other station has asked for and reduce if we have lesser capabilities.
|
|
* For example if other end wants 8k information part we reduce it to 2k.
|
|
* Ack time and retries are the opposite, we take the maximum.
|
|
*
|
|
* Question: If the other send leaves anything undefined should we leave it
|
|
* undefined or fill in what we would like before sending it back?
|
|
*
|
|
* The original version of the protocol spec left this open.
|
|
* The 2006 revision, in red, says we should fill in defaults for anything
|
|
* not specified. This makes sense. We send back a complete set of parameters
|
|
* so both ends should agree.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param)
|
|
{
|
|
|
|
// TODO: Integrate with new full duplex capability in v1.5.
|
|
|
|
param->full_duplex = 0;
|
|
|
|
// Other end might want 8.
|
|
// Seems unlikely. If it implements XID it should have modulo 128.
|
|
|
|
if (param->modulo == modulo_unknown) {
|
|
param->modulo = 8; // Not specified. Set default.
|
|
}
|
|
else {
|
|
param->modulo = MIN(param->modulo, 128);
|
|
}
|
|
|
|
// We can do REJ or SREJ but won't combine them.
|
|
// Erratum: 2006 version, section, 4.3.3.7 says default selective reject - reject.
|
|
// We can't do that.
|
|
|
|
if (param->srej == srej_not_specified) {
|
|
param->srej = (param->modulo == 128) ? srej_single : srej_none; // not specified, set default
|
|
}
|
|
|
|
// We can currently do up to 2k.
|
|
// Take minimum of that and what other guy asks for.
|
|
|
|
if (param->i_field_length_rx == G_UNKNOWN) {
|
|
param->i_field_length_rx = 256; // Not specified, take default.
|
|
}
|
|
else {
|
|
param->i_field_length_rx = MIN(param->i_field_length_rx, AX25_N1_PACLEN_MAX);
|
|
}
|
|
|
|
// In theory extended mode can have window size of 127 but
|
|
// I'm limiting it to 63 for the reason mentioned in the SREJ logic.
|
|
|
|
if (param->window_size_rx == G_UNKNOWN) {
|
|
param->window_size_rx = (param->modulo == 128) ? 32 : 4; // not specified, set default.
|
|
}
|
|
else {
|
|
if (param->modulo == 128)
|
|
param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_EXTENDED_MAX);
|
|
else
|
|
param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_BASIC_MAX);
|
|
}
|
|
|
|
// Erratum: Unclear. Is the Acknowledgement Timer before or after compensating for digipeaters
|
|
// in the path? e.g. Typically TNCs use the FRACK parameter for this and it often defaults to 3.
|
|
// However, the actual timeout value might be something like FRACK*(2*m+1) where m is the number of
|
|
// digipeaters in the path. I'm assuming this is the FRACK value and any additional time, for
|
|
// digipeaters will be added in locally at each end on top of this exchanged value.
|
|
|
|
if (param->ack_timer == G_UNKNOWN) {
|
|
param->ack_timer = 3000; // not specified, set default.
|
|
}
|
|
else {
|
|
param->ack_timer = MAX(param->ack_timer, (int)(g_misc_config_p->frack * 1000));
|
|
}
|
|
|
|
if (param->retries == G_UNKNOWN) {
|
|
param->retries = 10; // not specified, set default.
|
|
}
|
|
else {
|
|
param->retries = MAX(param->retries, S->n2_retry);
|
|
}
|
|
|
|
// IMPORTANT: Take values we have agreed upon and put into my running configuration.
|
|
|
|
complete_negotiation(S, param);
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
*
|
|
* Name: complete_negotiation
|
|
*
|
|
* Purpose: Used when preparing or receiving the XID *response*.
|
|
*
|
|
* Description: Take set of parameters which we have agreed upon and apply
|
|
* to the running configuration.
|
|
*
|
|
* TODO: Should do some checking here in case other station
|
|
* sends something crazy.
|
|
*
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param)
|
|
{
|
|
if (param->srej != srej_not_specified) {
|
|
S->srej_enable = param->srej;
|
|
}
|
|
|
|
if (param->modulo != modulo_unknown) {
|
|
// Disaster if aren't agreeing on this.
|
|
S->modulo = param->modulo;
|
|
}
|
|
|
|
if (param->i_field_length_rx != G_UNKNOWN) {
|
|
S->n1_paclen = param->i_field_length_rx;
|
|
}
|
|
|
|
if (param->window_size_rx != G_UNKNOWN) {
|
|
S->k_maxframe = param->window_size_rx;
|
|
}
|
|
|
|
if (param->ack_timer != G_UNKNOWN) {
|
|
S->t1v = param->ack_timer * 0.001;
|
|
}
|
|
|
|
if (param->retries != G_UNKNOWN) {
|
|
S->n2_retry = param->retries;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//###################################################################################
|
|
//###################################################################################
|
|
//
|
|
// Timers.
|
|
//
|
|
// Start.
|
|
// Stop.
|
|
// Pause (when channel busy) & resume.
|
|
// Is it running?
|
|
// Did it expire before being stopped?
|
|
// When will next one expire?
|
|
//
|
|
//###################################################################################
|
|
//###################################################################################
|
|
|
|
|
|
static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
double now = dtime_now();
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
dw_printf ("Start T1 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line);
|
|
}
|
|
|
|
S->t1_exp = now + S->t1v;
|
|
if (S->radio_channel_busy) {
|
|
S->t1_paused_at = now;
|
|
}
|
|
else {
|
|
S->t1_paused_at = 0;
|
|
}
|
|
S->t1_had_expired = 0;
|
|
|
|
} /* end start_t1 */
|
|
|
|
|
|
static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
double now = dtime_now();
|
|
|
|
RESUME_T1; // adjust expire time if paused.
|
|
|
|
if (S->t1_exp == 0.0) {
|
|
// Was already stopped.
|
|
}
|
|
else {
|
|
S->t1_remaining_when_last_stopped = S->t1_exp - now;
|
|
if (S->t1_remaining_when_last_stopped < 0) S->t1_remaining_when_last_stopped = 0;
|
|
}
|
|
|
|
// Normally this would be at the top but we don't know time remaining at that point.
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
if (S->t1_exp == 0.0) {
|
|
dw_printf ("Stop T1. Wasn't running, [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line);
|
|
}
|
|
else {
|
|
dw_printf ("Stop T1, %.3f remaining, [now=%.3f] from %s %d\n", S->t1_remaining_when_last_stopped, now - S->start_time, from_func, from_line);
|
|
}
|
|
}
|
|
|
|
S->t1_exp = 0.0; // now stopped.
|
|
S->t1_had_expired = 0; // remember that it did not expire.
|
|
|
|
} /* end stop_t1 */
|
|
|
|
|
|
static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
int result = S->t1_exp != 0.0;
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("is_t1_running? returns %d\n", result);
|
|
}
|
|
|
|
return (result);
|
|
|
|
} /* end is_t1_running */
|
|
|
|
|
|
static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
|
|
if (S->t1_exp == 0.0) {
|
|
// Stopped so there is nothing to do.
|
|
}
|
|
else if (S->t1_paused_at == 0.0) {
|
|
// Running and not paused.
|
|
|
|
double now = dtime_now();
|
|
|
|
S->t1_paused_at = now;
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Paused T1 with %.3f still remaining, [now=%.3f] from %s %d\n", S->t1_exp - now, now - S->start_time, from_func, from_line);
|
|
}
|
|
}
|
|
else {
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("T1 error: Didn't expect pause when already paused.\n");
|
|
}
|
|
}
|
|
|
|
} /* end pause_t1 */
|
|
|
|
|
|
static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
if (S->t1_exp == 0.0) {
|
|
// Stopped so there is nothing to do.
|
|
}
|
|
else if (S->t1_paused_at == 0.0) {
|
|
// Running but not paused.
|
|
}
|
|
else {
|
|
double now = dtime_now();
|
|
double paused_for_sec = now - S->t1_paused_at;
|
|
|
|
S->t1_exp += paused_for_sec;
|
|
S->t1_paused_at = 0.0;
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Resumed T1 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->t1_exp - now, now - S->start_time);
|
|
}
|
|
}
|
|
|
|
} /* end resume_t1 */
|
|
|
|
|
|
|
|
|
|
// T3 is a lot simpler.
|
|
// Here we are talking about minutes of inactivity with the peer
|
|
// rather than expecting a response within seconds where timing is more critical.
|
|
// We don't need to capture remaining time when stopped.
|
|
// I don't think there is a need to pause it due to the large time frame.
|
|
|
|
|
|
static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
double now = dtime_now();
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
dw_printf ("Start T3 for %.3f sec, [now=%.3f] from %s %d\n", T3_DEFAULT, now - S->start_time, from_func, from_line);
|
|
}
|
|
|
|
S->t3_exp = now + T3_DEFAULT;
|
|
}
|
|
|
|
static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
if (s_debug_timers) {
|
|
double now = dtime_now();
|
|
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
if (S->t3_exp == 0.0) {
|
|
dw_printf ("Stop T3. Wasn't running.\n");
|
|
}
|
|
else {
|
|
dw_printf ("Stop T3, %.3f remaining, [now=%.3f] from %s %d\n", S->t3_exp - now, now - S->start_time, from_func, from_line);
|
|
}
|
|
}
|
|
S->t3_exp = 0.0;
|
|
}
|
|
|
|
|
|
|
|
// TM201 is similar to T1.
|
|
// It needs to be paused whent the channel is busy.
|
|
// Simpler because we don't need to keep track of time remaining when stopped.
|
|
|
|
|
|
|
|
static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
double now = dtime_now();
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
dw_printf ("Start TM201 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line);
|
|
}
|
|
|
|
S->tm201_exp = now + S->t1v;
|
|
if (S->radio_channel_busy) {
|
|
S->tm201_paused_at = now;
|
|
}
|
|
else {
|
|
S->tm201_paused_at = 0;
|
|
}
|
|
|
|
} /* end start_tm201 */
|
|
|
|
|
|
static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
double now = dtime_now();
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG_TIMER);
|
|
dw_printf ("Stop TM201. [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line);
|
|
}
|
|
|
|
S->tm201_exp = 0.0; // now stopped.
|
|
|
|
} /* end stop_tm201 */
|
|
|
|
|
|
|
|
static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
|
|
if (S->tm201_exp == 0.0) {
|
|
// Stopped so there is nothing to do.
|
|
}
|
|
else if (S->tm201_paused_at == 0.0) {
|
|
// Running and not paused.
|
|
|
|
double now = dtime_now();
|
|
|
|
S->tm201_paused_at = now;
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Paused TM201 with %.3f still remaining, [now=%.3f] from %s %d\n", S->tm201_exp - now, now - S->start_time, from_func, from_line);
|
|
}
|
|
}
|
|
else {
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("TM201 error: Didn't expect pause when already paused.\n");
|
|
}
|
|
}
|
|
|
|
} /* end pause_tm201 */
|
|
|
|
|
|
static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
|
|
{
|
|
if (S->tm201_exp == 0.0) {
|
|
// Stopped so there is nothing to do.
|
|
}
|
|
else if (S->tm201_paused_at == 0.0) {
|
|
// Running but not paused.
|
|
}
|
|
else {
|
|
double now = dtime_now();
|
|
double paused_for_sec = now - S->tm201_paused_at;
|
|
|
|
S->tm201_exp += paused_for_sec;
|
|
S->tm201_paused_at = 0.0;
|
|
|
|
if (s_debug_timers) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Resumed TM201 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->tm201_exp - now, now - S->start_time);
|
|
}
|
|
}
|
|
|
|
} /* end resume_tm201 */
|
|
|
|
|
|
|
|
|
|
|
|
double ax25_link_get_next_timer_expiry (void)
|
|
{
|
|
double tnext = 0;
|
|
ax25_dlsm_t *p;
|
|
|
|
for (p = list_head; p != NULL; p = p->next) {
|
|
|
|
// Consider if running and not paused.
|
|
|
|
if (p->t1_exp != 0 && p->t1_paused_at == 0) {
|
|
if (tnext == 0) {
|
|
tnext = p->t1_exp;
|
|
}
|
|
else if (p->t1_exp < tnext) {
|
|
tnext = p->t1_exp;
|
|
}
|
|
}
|
|
|
|
if (p->t3_exp != 0) {
|
|
if (tnext == 0) {
|
|
tnext = p->t3_exp;
|
|
}
|
|
else if (p->t3_exp < tnext) {
|
|
tnext = p->t3_exp;
|
|
}
|
|
}
|
|
|
|
if (p->tm201_exp != 0 && p->tm201_paused_at == 0) {
|
|
if (tnext == 0) {
|
|
tnext = p->tm201_exp;
|
|
}
|
|
else if (p->tm201_exp < tnext) {
|
|
tnext = p->tm201_exp;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (s_debug_timers > 1) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
if (tnext == 0.0) {
|
|
dw_printf ("ax25_link_get_next_timer_expiry returns none.\n");
|
|
}
|
|
else {
|
|
dw_printf ("ax25_link_get_next_timer_expiry returns %.3f sec from now.\n",
|
|
tnext - dtime_now());
|
|
}
|
|
}
|
|
|
|
return (tnext);
|
|
|
|
} /* end ax25_link_get_next_timer_expiry */
|
|
|
|
|
|
/* end ax25_link.c */
|