Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…

// | |
// This file is part of Dire Wolf, an amateur radio packet TNC. | |
// | |
// Copyright (C) 2016, 2017, 2018 John Langner, WB2OSZ | |
// | |
// This program is free software: you can redistribute it and/or modify | |
// it under the terms of the GNU General Public License as published by | |
// the Free Software Foundation, either version 2 of the License, or | |
// (at your option) any later version. | |
// | |
// This program is distributed in the hope that it will be useful, | |
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
// GNU General Public License for more details. | |
// | |
// You should have received a copy of the GNU General Public License | |
// along with this program. If not, see <http://www.gnu.org/licenses/>. | |
// | |
/*------------------------------------------------------------------ | |
* | |
* Name: ax25_link | |
* | |
* Purpose: Data Link State Machine. | |
* Establish connections and transfer data in the proper | |
* order with retries. | |
* | |
* Description: | |
* | |
* Typical sequence for establishing a connection | |
* initiated by a client application. Try version 2.2, | |
* get refused, and fall back to trying version 2.0. | |
* | |
* | |
* State Client App State Machine Peer | |
* ----- ---------- ------------- ---- | |
* | |
* 0 disc | |
* Conn. Req ---> | |
* SABME ---> | |
* 5 await 2.2 | |
* <--- FRMR or DM *note | |
* SABM ---> | |
* 1 await 2.0 | |
* <--- UA | |
* <--- CONN Ind. | |
* 3 conn | |
* | |
* | |
* Typical sequence when other end initiates connection. | |
* | |
* | |
* State Client App State Machine Peer | |
* ----- ---------- ------------- ---- | |
* | |
* 0 disc | |
* <--- SABME or SABM | |
* UA ---> | |
* <--- CONN Ind. | |
* 3 conn | |
* | |
* | |
* *note: | |
* | |
* After carefully studying the v2.2 spec, I expected a 2.0 implementation to send | |
* FRMR in response to SABME. This is important. If a v2.2 implementation | |
* gets FRMR, in response to SABME, it switches to v2.0 and sends SABM instead. | |
* | |
* The v2.0 protocol spec, section 2.3.4.3.3.1, states that FRMR should be sent when | |
* an invalid or not implemented command is received. That all fits together. | |
* | |
* In testing, I found that the KPC-3+ sent DM. | |
* | |
* I can see where they might get that idea. | |
* The v2.0 spec says that when in disconnected mode, it should respond to any | |
* command other than SABM or UI frame with a DM response with P/F set to 1. | |
* I think it was implemented wrong. 2.3.4.3.3.1 should take precedence. | |
* | |
* The TM-D710 does absolutely nothing in response to SABME. | |
* Not responding at all is just plain wrong. To work around this, I put | |
* in a special hack to start sending SABM after a certain number of | |
* SABME go unanswered. There is more discussion in the User Guide. | |
* | |
* References: | |
* * AX.25 Amateur Packet-Radio Link-Layer Protocol Version 2.0, October 1984 | |
* | |
* https://www.tapr.org/pub_ax25.html | |
* http://lea.hamradio.si/~s53mv/nbp/nbp/AX25V20.pdf | |
* | |
* At first glance, they look pretty much the same, but the second one | |
* is more complete with 4 appendices, including a state table. | |
* | |
* * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 | |
* | |
* https://www.tapr.org/pdf/AX25.2.2.pdf | |
* | |
* * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 | |
* | |
* http://www.ax25.net/AX25.2.2-Jul%2098-2.pdf | |
* | |
* I accidentally stumbled across this one when searching for some sort of errata | |
* list for the original protocol specification. | |
* | |
* "This is a new version of the 1998 standard. It has had all figures | |
* redone using Microsoft Visio. Errors in the SDL have been corrected." | |
* | |
* The SDL diagrams are dated 2006. I wish I had known about this version, with | |
* several corrections, before doing most of the implementation. :-( | |
* | |
* The title page still says July 1998 so it's not immediately obvious this | |
* is different than the one on the TAPR site. | |
* | |
* * AX.25 ... Latest revision, in progress. | |
* | |
* http://www.nj7p.org/ | |
* | |
* This is currently being revised in cooperation with software authors | |
* who have noticed some issues during implementation. | |
* | |
* The functions here are based on the SDL diagrams but turned inside out. | |
* It seems more intuitive to have a function for each type of input and then decide | |
* what to do depending on the state. This also reduces duplicate code because we | |
* often see the same flow chart segments, for the same input, appearing in multiple states. | |
* | |
* Errata: The protocol spec has many places that appear to be errors or are ambiguous so I wasn't | |
* sure what to do. These should be annotated with "erratum" comments so we can easily go | |
* back and revisit them. | |
* | |
* X.25: The AX.25 protocol is based on, but does not necessarily adhere to, the X.25 protocol. | |
* Consulting this might provide some insights where the AX.25 spec is not clear. | |
* | |
* http://www.itu.int/rec/T-REC-X.25-199610-I/en/ | |
* | |
* Version 1.4, released April 2017: | |
* | |
* Features tested reasonably well: | |
* | |
* Connect to/from a KPC-3+ and send I frames in both directions. | |
* Same with TM-D710A. | |
* v2.2 connect between two instances of direwolf. (Can't find another v2.2 for testing.) | |
* Modulo 8 & 128 sequence numbers. | |
* Recovery from simulated transmission errors using either REJ or SREJ. | |
* XID frame for parameter negotiation. | |
* Segments to allow data larger than max info part size. | |
* | |
* Implemented but not tested properly: | |
* | |
* Connecting thru digipeater(s). | |
* Acting as a digipeater. | |
* T3 timer. | |
* Compatibility with additional types of TNC. | |
* | |
* Version 1.5, December 2017: | |
* | |
* Implemented Multi Selective Reject. | |
* More efficient generation of SREJ frames. | |
* Reduced number of duplicate I frames sent for both REJ and SREJ cases. | |
* Avoided unnecessary RR when I frame could take care of the ack. | |
* (This led to issue 132 where outgoing data sometimes got stuck in the queue.) | |
* | |
*------------------------------------------------------------------*/ | |
#include "direwolf.h" | |
#include <stdlib.h> | |
#include <string.h> | |
#include <assert.h> | |
#include <stdio.h> | |
#include <ctype.h> | |
#include <math.h> | |
#include "ax25_pad.h" | |
#include "ax25_pad2.h" | |
#include "xid.h" | |
#include "textcolor.h" | |
#include "dlq.h" | |
#include "tq.h" | |
#include "ax25_link.h" | |
#include "dtime_now.h" | |
#include "server.h" | |
#include "ptt.h" | |
#define MIN(a,b) ((a)<(b)?(a):(b)) | |
#define MAX(a,b) ((a)>(b)?(a):(b)) | |
// Debug switches for different types of information. | |
// Should have command line options instead of changing source and recompiling. | |
static int s_debug_protocol_errors = 1; // Less serious Protocol errors. | |
// Useful for debugging but unnecessarily alarming other times. | |
static int s_debug_client_app = 0; // Interaction with client application. | |
// dl_connect_request, dl_data_request, dl_data_indication, etc. | |
static int s_debug_radio = 0; // Received frames and channel busy status. | |
// lm_data_indication, lm_channel_busy | |
static int s_debug_variables = 0; // Variables, state changes. | |
static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending. | |
static int s_debug_timers = 0; // Timer details. | |
static int s_debug_link_handle = 0; // Create data link state machine or pick existing one, | |
// based on my address, peer address, client app index, and radio channel. | |
static int s_debug_stats = 0; // Statistics when connection is closed. | |
static int s_debug_misc = 0; // Anything left over that might be interesting. | |
/* | |
* AX.25 data link state machine. | |
* | |
* One instance for each link identified by | |
* [ client, channel, owncall, peercall ] | |
*/ | |
enum dlsm_state_e { | |
state_0_disconnected = 0, | |
state_1_awaiting_connection = 1, | |
state_2_awaiting_release = 2, | |
state_3_connected = 3, | |
state_4_timer_recovery = 4, | |
state_5_awaiting_v22_connection = 5 }; | |
typedef struct ax25_dlsm_s { | |
int magic1; // Look out for bad pointer or corruption. | |
#define MAGIC1 0x11592201 | |
struct ax25_dlsm_s *next; // Next in linked list. | |
int stream_id; // Unique number for each stream. | |
// Internally we use a pointer but this is more user-friendly. | |
int chan; // Radio channel being used. | |
int client; // We have have multiple client applications, | |
// each with their own links. We need to know | |
// which client should receive the data or | |
// notifications about state changes. | |
char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN]; | |
// Up to 10 addresses, same order as in frame. | |
int num_addr; // Number of addresses. Should be in range 2 .. 10. | |
#define OWNCALL AX25_SOURCE | |
// addrs[OWNCALL] is owncall for this end of link. | |
// Note that we are acting on behalf of | |
// a client application so the APRS mycall | |
// might not be relevent. | |
#define PEERCALL AX25_DESTINATION | |
// addrs[PEERCALL] is call for other end. | |
double start_time; // Clock time when this was allocated. Used only for | |
// debug output for timestamps relative to start. | |
enum dlsm_state_e state; // Current state. | |
int modulo; // 8 or 128. | |
// Determines whether we have one or two control | |
// octets. 128 allows a much larger window size. | |
enum srej_e srej_enable; // Is other end capable of processing SREJ? (Am I allowed to send it?) | |
// Starts out as 'srej_none' for v2.0 or 'srej_single' for v2.2. | |
// Can be changed to 'srej_multi' with XID exchange. | |
// Should be used only with modulo 128. (Is this enforced?) | |
int n1_paclen; // Maximum length of information field, in bytes. | |
// Starts out as 256 but can be negotiated higher. | |
// (Protocol Spec has this in bits. It is in bytes here.) | |
// "PACLEN" in configuration file. | |
int n2_retry; // Maximum number of retries permitted. | |
// Typically 10. | |
// "RETRY" parameter in configuration file. | |
int k_maxframe; // Window size. Defaults to 4 (mod 8) or 32 (mod 128). | |
// Maximum number of unacknowledged information | |
// frames that can be outstanding. | |
// "MAXFRAME" or "EMAXFRAME" parameter in configuration file. | |
int rc; // Retry count. Give up after n2. | |
int vs; // 4.2.4.1. Send State Variable V(S) | |
// The send state variable exists within the TNC and is never sent. | |
// It contains the next sequential number to be assigned to the next | |
// transmitted I frame. | |
// This variable is updated with the transmission of each I frame. | |
int va; // 4.2.4.5. Acknowledge State Variable V(A) | |
// The acknowledge state variable exists within the TNC and is never sent. | |
// It contains the sequence number of the last frame acknowledged by | |
// its peer [V(A)-1 equals the N(S) of the last acknowledged I frame]. | |
int vr; // 4.2.4.3. Receive State Variable V(R) | |
// The receive state variable exists within the TNC. | |
// It contains the sequence number of the next expected received I frame | |
// This variable is updated upon the reception of an error-free I frame | |
// whose send sequence number equals the present received state variable value. | |
int layer_3_initiated; // SABM(E) was sent by request of Layer 3; i.e. DL-CONNECT request primitive. | |
// I think this means that it is set only if we initiated the connection. | |
// It would not be set if we are in the middle of accepting a connection from the other station. | |
// Next 5 are called exception conditions. | |
int peer_receiver_busy; // Remote station is busy and can't receive I frames. | |
int reject_exception; // A REJ frame has been sent to the remote station. (boolean) | |
// This is used only when receving an I frame, in states 3 & 4, SREJ not enabled. | |
// When an I frame has an unepected N(S), | |
// - if not already set, set it and send REJ. | |
// When an I frame with expected N(S) is received, clear it. | |
// This would prevent us from sending additional REJ while | |
// waiting for result from first one. | |
// What happens if the REJ gets lost? Is it resent somehow? | |
int own_receiver_busy; // Layer 3 is busy and can't receive I frames. | |
// We have no API to convey this information so it should always be 0. | |
int acknowledge_pending; // I frames have been successfully received but not yet | |
// acknowledged TO the remote station. | |
// Set when receiving the next expected I frame and P=0. | |
// This gets cleared by sending any I, RR, RNR, REJ. | |
// Cleared when sending SREJ with F=1. | |
// Timing. | |
float srt; // Smoothed roundtrip time in seconds. | |
// This is used to dynamically adjust t1v. | |
// Sometimes the flow chart has SAT instead of SRT. | |
// I think that is a typographical error. | |
float t1v; // How long to wait for an acknowlegement before resending. | |
// Value used when starting timer T1, in seconds. | |
// "FRACK" parameter in some implementations. | |
// Typically it might be 3 seconds after frame has been | |
// sent. Add more for each digipeater in path. | |
// Here it is dynamically adjusted. | |
// Set initial value for T1V. | |
// Multiply FRACK by 2*m+1, where m is number of digipeaters. | |
#define INIT_T1V_SRT \ | |
S->t1v = g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1); \ | |
S->srt = S->t1v / 2.0; | |
int radio_channel_busy; // Either due to DCD or PTT. | |
// Timer T1. | |
// Timer values all use the usual unix time() value but double precision | |
// so we can have fractions of seconds. | |
// T1 is used for retries along with the retry counter, "rc." | |
// When timer T1 is started, the value is obtained from t1v plus the current time. | |
// Appropriate functions should be used rather than accessing the values directly. | |
// This gets a little tricky because we need to pause the timers when the radio | |
// channel is busy. Suppose we sent an I frame and set T1 to 4 seconds so we could | |
// take corrective action if there is no response in a reasonable amount of time. | |
// What if some other station has the channel tied up for 10 seconds? We don't want | |
// T1 to timeout and start a retry sequence. The solution is to pause the timers while | |
// the channel is busy. We don't want to get a timer expiry event when t1_exp is in | |
// the past if it is currently paused. When it is un-paused, the expiration time is adjusted | |
// for the amount of time it was paused. | |
double t1_exp; // This is the time when T1 will expire or 0 if not running. | |
double t1_paused_at; // Time when it was paused or 0 if not paused. | |
float t1_remaining_when_last_stopped; // Number of seconds that were left on T1 when it was stopped. | |
// This is used to fine tune t1v. | |
// Set to negative initially to mean invalid, don't use in calculation. | |
int t1_had_expired; // Set when T1 expires. | |
// Cleared for start & stop. | |
// Timer T3. | |
// T3 is used to terminate connection after extended inactivity. | |
// Similar to T1 except there is not mechanism to capture the remaining time when it is stopped | |
// and it is not paused when the channel is busy. | |
double t3_exp; // When it expires or 0 if not running. | |
#define T3_DEFAULT 300.0 // Copied 5 minutes from Ax.25 for Linux. | |
// http://www.linux-ax25.org/wiki/Run_time_configurable_parameters | |
// D710A also defaults to 30*10 = 300 seconds. | |
// Should it be user-configurable? | |
// KPC-3+ and TM-D710A have "CHECK" command for this purpose. | |
// Statistics for testing purposes. | |
// Count how many frames of each type we received. | |
// This is easy to do because they all come in thru lm_data_indication. | |
// Counting outgoing could probably be done in lm_data_request so | |
// it would not have to be scattered all over the place. TBD | |
int count_recv_frame_type[frame_not_AX25+1]; | |
int peak_rc_value; // Peak value of retry count (rc). | |
// For sending data. | |
cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet. | |
// Linked list. | |
// The name is misleading because these are just blocks of | |
// data, not "I frames" at this point. The name comes from | |
// the protocol specification. | |
cdata_t *txdata_by_ns[128]; // Data which has already been transmitted. | |
// Indexed by N(S) in case it gets lost and needs to be sent again. | |
// Cleared out when we get ACK for it. | |
int magic3; // Look out for out of bounds for above. | |
#define MAGIC3 0x03331301 | |
cdata_t *rxdata_by_ns[128]; // "Receive buffer" | |
// Data which has been received out of sequence. | |
// Indexed by N(S). | |
int magic2; // Look out for out of bounds for above. | |
#define MAGIC2 0x02221201 | |
// "Management Data Link" (MDL) state machine for XID exchange. | |
enum mdl_state_e { mdl_state_0_ready=0, mdl_state_1_negotiating=1 } mdl_state; | |
int mdl_rc; // Retry count, waiting to get XID response. | |
// The spec has provision for a separate maximum, NM201, but we | |
// just use the regular N2 same as other retries. | |
double tm201_exp; // Timer. Similar to T1. | |
// The spec mentions a separate timeout value but | |
// we will just use the same as T1. | |
double tm201_paused_at; // Time when it was paused or 0 if not paused. | |
// Segment reassembler. | |
cdata_t *ra_buff; // Reassembler buffer. NULL when in ready state. | |
int ra_following; // Most recent number following to predict next expected. | |
} ax25_dlsm_t; | |
/* | |
* List of current state machines for each link. | |
* There is potential many client apps, each with multiple links | |
* connected all at the same time. | |
* | |
* Everything coming thru here should be from a single thread. | |
* The Data Link Queue should serialize all processing. | |
* Therefore, we don't have to worry about critical regions. | |
*/ | |
static ax25_dlsm_t *list_head = NULL; | |
/* | |
* Registered callsigns for incoming connections. | |
*/ | |
#define RC_MAGIC 0x08291951 | |
typedef struct reg_callsign_s { | |
char callsign[AX25_MAX_ADDR_LEN]; | |
int chan; | |
int client; | |
struct reg_callsign_s *next; | |
int magic; | |
} reg_callsign_t; | |
static reg_callsign_t *reg_callsign_list = NULL; | |
// Use these, rather than setting variables directly, to make debug out easier. | |
#define SET_VS(n) { S->vs = (n); \ | |
if (s_debug_variables) { \ | |
text_color_set(DW_COLOR_DEBUG); \ | |
dw_printf ("V(S) = %d at %s %d\n", S->vs, __func__, __LINE__); \ | |
} \ | |
assert (S->vs >= 0 && S->vs < S->modulo); \ | |
} | |
// If other guy acks reception of an I frame, we should never get an REJ or SREJ | |
// asking for it again. When we update V(A), we should be able to remove the saved | |
// transmitted data, and everything preceding it, from S->txdata_by_ns[]. | |
#define SET_VA(n) { S->va = (n); \ | |
if (s_debug_variables) { \ | |
text_color_set(DW_COLOR_DEBUG); \ | |
dw_printf ("V(A) = %d at %s %d\n", S->va, __func__, __LINE__); \ | |
} \ | |
assert (S->va >= 0 && S->va < S->modulo); \ | |
int x = AX25MODULO(n-1, S->modulo, __FILE__, __func__, __LINE__); \ | |
while (S->txdata_by_ns[x] != NULL) { \ | |
cdata_delete (S->txdata_by_ns[x]); \ | |
S->txdata_by_ns[x] = NULL; \ | |
x = AX25MODULO(x-1, S->modulo, __FILE__, __func__, __LINE__); \ | |
} \ | |
} | |
#define SET_VR(n) { S->vr = (n); \ | |
if (s_debug_variables) { \ | |
text_color_set(DW_COLOR_DEBUG); \ | |
dw_printf ("V(R) = %d at %s %d\n", S->vr, __func__, __LINE__); \ | |
} \ | |
assert (S->vr >= 0 && S->vr < S->modulo); \ | |
} | |
#define SET_RC(n) { S->rc = (n); \ | |
if (s_debug_variables) { \ | |
text_color_set(DW_COLOR_DEBUG); \ | |
dw_printf ("rc = %d at %s %d, state = %d\n", S->rc, __func__, __LINE__, S->state); \ | |
} \ | |
} | |
//TODO: Make this a macro so we can simplify calls yet keep debug output if something goes wrong. | |
#if 0 | |
#define AX25MODULO(n) ax25modulo((n), S->modulo, __FILE__, __func__, __LINE__) | |
static int ax25modulo(int n, int m, const char *file, const char *func, int line) | |
#else | |
static int AX25MODULO(int n, int m, const char *file, const char *func, int line) | |
#endif | |
{ | |
if (m != 8 && m != 128) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR: %d modulo %d, %s, %s, %d\n", n, m, file, func, line); | |
m = 8; | |
} | |
// Use masking, rather than % operator, so negative numbers are handled properly. | |
return (n & (m-1)); | |
} | |
// Test whether we can send more or if we need to wait | |
// because we have reached 'maxframe' outstanding frames. | |
// Argument must be 'S'. | |
#define WITHIN_WINDOW_SIZE(x) (x->vs != AX25MODULO(x->va + x->k_maxframe, x->modulo, __FILE__, __func__, __LINE__)) | |
// Timer macros to provide debug output with location from where they are called. | |
#define START_T1 start_t1(S, __func__, __LINE__) | |
#define IS_T1_RUNNING is_t1_running(S, __func__, __LINE__) | |
#define STOP_T1 stop_t1(S, __func__, __LINE__) | |
#define PAUSE_T1 pause_t1(S, __func__, __LINE__) | |
#define RESUME_T1 resume_t1(S, __func__, __LINE__) | |
#define START_T3 start_t3(S, __func__, __LINE__) | |
#define STOP_T3 stop_t3(S, __func__, __LINE__) | |
#define START_TM201 start_tm201(S, __func__, __LINE__) | |
#define STOP_TM201 stop_tm201(S, __func__, __LINE__) | |
#define PAUSE_TM201 pause_tm201(S, __func__, __LINE__) | |
#define RESUME_TM201 resume_tm201(S, __func__, __LINE__) | |
static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len); | |
static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len); | |
static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len); | |
static int is_ns_in_window (ax25_dlsm_t *S, int ns); | |
static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1); | |
static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr); | |
static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr); | |
static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr, unsigned char *info_ptr, int info_len); | |
static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p); | |
static void disc_frame (ax25_dlsm_t *S, int f); | |
static void dm_frame (ax25_dlsm_t *S, int f); | |
static void ua_frame (ax25_dlsm_t *S, int f); | |
static void frmr_frame (ax25_dlsm_t *S); | |
static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf); | |
static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); | |
static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); | |
static void t1_expiry (ax25_dlsm_t *S); | |
static void t3_expiry (ax25_dlsm_t *S); | |
static void tm201_expiry (ax25_dlsm_t *S); | |
static void nr_error_recovery (ax25_dlsm_t *S); | |
static void clear_exception_conditions (ax25_dlsm_t *S); | |
static void transmit_enquiry (ax25_dlsm_t *S); | |
static void select_t1_value (ax25_dlsm_t *S); | |
static void establish_data_link (ax25_dlsm_t *S); | |
static void set_version_2_0 (ax25_dlsm_t *S); | |
static void set_version_2_2 (ax25_dlsm_t *S); | |
static int is_good_nr (ax25_dlsm_t *S, int nr); | |
static void i_frame_pop_off_queue (ax25_dlsm_t *S); | |
static void discard_i_queue (ax25_dlsm_t *S); | |
static void invoke_retransmission (ax25_dlsm_t *S, int nr_input); | |
static void check_i_frame_ackd (ax25_dlsm_t *S, int nr); | |
static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf); | |
static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f); | |
static void enter_new_state(ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line); | |
static void mdl_negotiate_request (ax25_dlsm_t *S); | |
static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); | |
static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param); | |
static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); | |
// Use macros above rather than calling these directly. | |
static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); | |
/* | |
* Configuration settings from file or command line. | |
*/ | |
static struct misc_config_s *g_misc_config_p; | |
/*------------------------------------------------------------------- | |
* | |
* Name: ax25_link_init | |
* | |
* Purpose: Initialize the ax25_link module. | |
* | |
* Inputs: pconfig - misc. configuration from config file or command line. | |
* Beacon stuff ended up here. | |
* | |
* Outputs: Remember required information for future use. That's all. | |
* | |
*--------------------------------------------------------------------*/ | |
void ax25_link_init (struct misc_config_s *pconfig) | |
{ | |
/* | |
* Save parameters for later use. | |
*/ | |
g_misc_config_p = pconfig; | |
} /* end ax25_link_init */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: get_link_handle | |
* | |
* Purpose: Find existing (or possibly create) state machine for a given link. | |
* It should be possible to have a large number of links active at the | |
* same time. They are uniquely identified by | |
* (owncall, peercall, client id, radio channel) | |
* Note that we could have multiple client applications, all sharing one | |
* TNC, on the same or different radio channels, completely unware of each other. | |
* | |
* Inputs: addrs - Owncall, peercall, and optional digipeaters. | |
* For ease of passing this around, it is an array in the | |
* same order as in the frame. | |
* | |
* num_addr - Number of addresses, 2 thru 10. | |
* | |
* chan - Radio channel number. | |
* | |
* client - Client app number. | |
* We allow multiple concurrent applications with the | |
* AGW network protocol. These are identified as 0, 1, ... | |
* We don't know this for an incoming frame from the radio | |
* so it is -1 at this point. At a later time will will | |
* associate the stream with the right client. | |
* | |
* create - True if OK to create a new one. | |
* Otherwise, return only one already existing. | |
* | |
* This should always be true for outgoing frames. | |
* For incoming frames this would be true only for SABM(e) | |
* with all digipeater fields marked as used. | |
* | |
* Here, we will also check to see if it is in our | |
* registered callsign list. | |
* | |
* Returns: Handle for data link state machine. | |
* NULL if not found and 'create' is false. | |
* | |
* Description: Try to find an existing entry matching owncall, peercall, channel, | |
* and client. If not found create a new one. | |
* | |
*------------------------------------------------------------------------------*/ | |
static int next_stream_id = 0; | |
static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int create) | |
{ | |
ax25_dlsm_t *p; | |
if (s_debug_link_handle) { | |
text_color_set(DW_COLOR_DECODED); | |
dw_printf ("get_link_handle (%s>%s, chan=%d, client=%d, create=%d)\n", | |
addrs[AX25_SOURCE], addrs[AX25_DESTINATION], chan, client, create); | |
} | |
// Look for existing. | |
if (client == -1) { // from the radio. | |
// address order is reversed for compare. | |
for (p = list_head; p != NULL; p = p->next) { | |
if (p->chan == chan && | |
strcmp(addrs[AX25_DESTINATION], p->addrs[OWNCALL]) == 0 && | |
strcmp(addrs[AX25_SOURCE], p->addrs[PEERCALL]) == 0) { | |
if (s_debug_link_handle) { | |
text_color_set(DW_COLOR_DECODED); | |
dw_printf ("get_link_handle returns existing stream id %d for incoming.\n", p->stream_id); | |
} | |
return (p); | |
} | |
} | |
} | |
else { // from client app | |
for (p = list_head; p != NULL; p = p->next) { | |
if (p->chan == chan && | |
p->client == client && | |
strcmp(addrs[AX25_SOURCE], p->addrs[OWNCALL]) == 0 && | |
strcmp(addrs[AX25_DESTINATION], p->addrs[PEERCALL]) == 0) { | |
if (s_debug_link_handle) { | |
text_color_set(DW_COLOR_DECODED); | |
dw_printf ("get_link_handle returns existing stream id %d for outgoing.\n", p->stream_id); | |
} | |
return (p); | |
} | |
} | |
} | |
// Could not find existing. Should we create a new one? | |
if ( ! create) { | |
if (s_debug_link_handle) { | |
text_color_set(DW_COLOR_DECODED); | |
dw_printf ("get_link_handle: Search failed. Do not create new.\n"); | |
} | |
return (NULL); | |
} | |
// If it came from the radio, search for destination our registered callsign list. | |
int incoming_for_client = -1; // which client app registered the callsign? | |
if (client == -1) { // from the radio. | |
reg_callsign_t *r, *found; | |
found = NULL; | |
for (r = reg_callsign_list; r != NULL && found == NULL; r = r->next) { | |
if (strcmp(addrs[AX25_DESTINATION], r->callsign) == 0 && chan == r->chan) { | |
found = r; | |
incoming_for_client = r->client; | |
} | |
} | |
if (found == NULL) { | |
if (s_debug_link_handle) { | |
text_color_set(DW_COLOR_DECODED); | |
dw_printf ("get_link_handle: not for me. Ignore it.\n"); | |
} | |
return (NULL); | |
} | |
} | |
// Create new data link state machine. | |
p = calloc (sizeof(ax25_dlsm_t), 1); | |
p->magic1 = MAGIC1; | |
p->start_time = dtime_now(); | |
p->stream_id = next_stream_id++; | |
p->modulo = 8; | |
p->chan = chan; | |
p->num_addr = num_addr; | |
// If it came in over the radio, we need to swap source/destination and reverse any digi path. | |
if (incoming_for_client >= 0) { | |
strlcpy (p->addrs[AX25_SOURCE], addrs[AX25_DESTINATION], sizeof(p->addrs[AX25_SOURCE])); | |
strlcpy (p->addrs[AX25_DESTINATION], addrs[AX25_SOURCE], sizeof(p->addrs[AX25_DESTINATION])); | |
int j = AX25_REPEATER_1; | |
int k = num_addr - 1; | |
while (k >= AX25_REPEATER_1) { | |
strlcpy (p->addrs[j], addrs[k], sizeof(p->addrs[j])); | |
j++; | |
k--; | |
} | |
p->client = incoming_for_client; | |
} | |
else { | |
memcpy (p->addrs, addrs, sizeof(p->addrs)); | |
p->client = client; | |
} | |
p->state = state_0_disconnected; | |
p->t1_remaining_when_last_stopped = -999; // Invalid, don't use. | |
p->magic2 = MAGIC2; | |
p->magic3 = MAGIC3; | |
// No need for critical region because this should all be in one thread. | |
p->next = list_head; | |
list_head = p; | |
if (s_debug_link_handle) { | |
text_color_set(DW_COLOR_DECODED); | |
dw_printf ("get_link_handle returns NEW stream id %d\n", p->stream_id); | |
} | |
return (p); | |
} | |
//################################################################################### | |
//################################################################################### | |
// | |
// Data Link state machine for sending data in connected mode. | |
// | |
// Incoming: | |
// | |
// Requests from the client application. Set s_debug_client_app for debugging. | |
// | |
// dl_connect_request | |
// dl_disconnect_request | |
// dl_data_request - send connected data | |
// dl_unit_data_request - not implemented. APRS & KISS bypass this | |
// dl_flow_off - not implemented. Not in AGW API. | |
// dl_flow_on - not implemented. Not in AGW API. | |
// dl_register_callsign - Register callsigns(s) for incoming connection requests. | |
// dl_unregister_callsign - Unregister callsigns(s) ... | |
// dl_client_cleanup - Clean up after client which has disappeared. | |
// | |
// Stuff from the radio channel. Set s_debug_radio for debugging. | |
// | |
// lm_data_indication - Received frame. | |
// lm_channel_busy - Change in PTT or DCD. | |
// lm_seize_confirm - We have started to transmit. | |
// | |
// Timer expiration. Set s_debug_timers for debugging. | |
// | |
// dl_timer_expiry | |
// | |
// Outgoing: | |
// | |
// To the client application: | |
// | |
// dl_data_indication - received connected data. | |
// | |
// To the transmitter: | |
// | |
// lm_data_request - Queue up a frame for transmission. | |
// | |
// lm_seize_request - Start transmitter when possible. | |
// lm_seize_confirm will be called when it has. | |
// | |
// | |
// It is important that all requests come thru the data link queue so | |
// everything is serialized. | |
// We don't have to worry about being reentrant or critical regions. | |
// Nothing here should consume a significant amount of time. | |
// i.e. There should be no sleep delay or anything that would block waiting on someone else. | |
// | |
//################################################################################### | |
//################################################################################### | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: dl_connect_request | |
* | |
* Purpose: Client app wants to connect to another station. | |
* | |
* Inputs: E - Event from the queue. | |
* The caller will free it. | |
* | |
* Description: | |
* | |
*------------------------------------------------------------------------------*/ | |
void dl_connect_request (dlq_item_t *E) | |
{ | |
ax25_dlsm_t *S; | |
int ok_to_create = 1; | |
int old_version; | |
int n; | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("dl_connect_request ()\n"); | |
} | |
text_color_set(DW_COLOR_INFO); | |
dw_printf ("Attempting connect to %s ...\n", E->addrs[PEERCALL]); | |
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); | |
switch (S->state) { | |
case state_0_disconnected: | |
INIT_T1V_SRT; | |
// See if destination station is in list for v2.0 only. | |
old_version = 0; | |
for (n = 0; n < g_misc_config_p->v20_count && ! old_version; n++) { | |
if (strcmp(E->addrs[AX25_DESTINATION],g_misc_config_p->v20_addrs[n]) == 0) { | |
old_version = 1; | |
} | |
} | |
if (old_version || g_misc_config_p->maxv22 == 0) { // Don't attempt v2.2. | |
set_version_2_0 (S); | |
establish_data_link (S); | |
S->layer_3_initiated = 1; | |
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); | |
} | |
else { // Try v2.2 first, then fall back if appropriate. | |
set_version_2_2 (S); | |
establish_data_link (S); | |
S->layer_3_initiated = 1; | |
enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__); | |
} | |
break; | |
case state_1_awaiting_connection: | |
case state_5_awaiting_v22_connection: | |
discard_i_queue(S); | |
S->layer_3_initiated = 1; | |
// Keep current state. | |
break; | |
case state_2_awaiting_release: | |
// Keep current state. | |
break; | |
case state_3_connected: | |
case state_4_timer_recovery: | |
discard_i_queue(S); | |
establish_data_link(S); | |
S->layer_3_initiated = 1; | |
// My enhancement. Original always sent SABM and went to state 1. | |
// If we were using v2.2, why not reestablish with that? | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
break; | |
} | |
} /* end dl_connect_request */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: dl_disconnect_request | |
* | |
* Purpose: Client app wants to terminate connection with another station. | |
* | |
* Inputs: E - Event from the queue. | |
* The caller will free it. | |
* | |
* Outputs: | |
* | |
* Description: | |
* | |
*------------------------------------------------------------------------------*/ | |
void dl_disconnect_request (dlq_item_t *E) | |
{ | |
ax25_dlsm_t *S; | |
int ok_to_create = 1; | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("dl_disconnect_request ()\n"); | |
} | |
text_color_set(DW_COLOR_INFO); | |
dw_printf ("Disconnect from %s ...\n", E->addrs[PEERCALL]); | |
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); | |
switch (S->state) { | |
case state_0_disconnected: | |
// DL-DISCONNECT *confirm* | |
text_color_set(DW_COLOR_INFO); | |
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); | |
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); | |
break; | |
case state_1_awaiting_connection: | |
case state_5_awaiting_v22_connection: | |
// TODO: "requeue." Not sure what to do here. | |
// If we put it back in the queue we will get it back again probably still in same state. | |
// Need a way to defer it until the next state change. | |
break; | |
case state_2_awaiting_release: | |
{ | |
// We have previously started the disconnect sequence and are waiting | |
// for a UA from the other guy. Meanwhile, the application got | |
// impatient and sent us another disconnect request. What should | |
// we do? Ignore it and let the disconnect sequence run its | |
// course? Or should we complete the sequence without waiting | |
// for the other guy to ack? | |
// Erratum. Flow chart simply says "DM (expedited)." | |
// This is the only place we have expedited. Is this correct? | |
cmdres_t cr = cr_res; // DM can only be response. | |
int p = 0; | |
int nopid = 0; // PID applies only to I and UI frames. | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, p, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // HI means expedited. | |
// Erratum: Shouldn't we inform the user when going to disconnected state? | |
// Notifying the application, here, is my own enhancement. | |
text_color_set(DW_COLOR_INFO); | |
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); | |
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); | |
STOP_T1; | |
enter_new_state (S, state_0_disconnected, __func__, __LINE__); | |
} | |
break; | |
case state_3_connected: | |
case state_4_timer_recovery: | |
discard_i_queue (S); | |
SET_RC(0); // I think this should be 1 but I'm not that worried about it. | |
cmdres_t cmd = cr_cmd; | |
int p = 1; | |
int nopid = 0; | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
STOP_T3; | |
START_T1; | |
enter_new_state (S, state_2_awaiting_release, __func__, __LINE__); | |
break; | |
} | |
} /* end dl_disconnect_request */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: dl_data_request | |
* | |
* Purpose: Client app wants to send data to another station. | |
* | |
* Inputs: E - Event from the queue. | |
* The caller will free it. | |
* | |
* Description: Append the transmit data block to the I frame queue for later processing. | |
* | |
* We also perform the segmentation handling here. | |
* | |
* C6.1 Segmenter State Machine | |
* Only the following DL primitives will be candidates for modification by the segmented | |
* state machine: | |
* * DL-DATA Request. The user employs this primitive to provide information to be | |
* transmitted using connection-oriented procedures; i.e., using I frames. The | |
* segmenter state machine examines the quantity of data to be transmitted. If the | |
* quantity of data to be transmitted is less than or equal to the data link parameter | |
* N1, the segmenter state machine passes the primitive through transparently. If the | |
* quantity of data to be transmitted exceeds the data link parameter N1, the | |
* segmenter chops up the data into segments of length N1-2 octets. Each segment is | |
* prepended with a two octet header. (See Figures 3.1 and 3.2.) The segments are | |
* then turned over to the Data-link State Machine for transmission, using multiple DL | |
* Data Request primitives. All segments are turned over immediately; therefore the | |
* Data-link State Machine will transmit them consecutively on the data link. | |
* | |
* Erratum: Not sure how to interpret that. See example below for how it was implemented. | |
* | |
*------------------------------------------------------------------------------*/ | |
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata); | |
void dl_data_request (dlq_item_t *E) | |
{ | |
ax25_dlsm_t *S; | |
int ok_to_create = 1; | |
int nseg_to_follow; | |
int orig_offset, remaining_len; | |
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("dl_data_request (\""); | |
ax25_safe_print (E->txdata->data, E->txdata->len, 1); | |
dw_printf ("\") state=%d\n", S->state); | |
} | |
if (E->txdata->len <= S->n1_paclen) { | |
data_request_good_size (S, E->txdata); | |
E->txdata = NULL; // Now part of transmit I frame queue. | |
return; | |
} | |
// More interesting case. | |
// It is too large to fit in one frame so we segment it. | |
// As an example, suppose we had 6 bytes of data "ABCDEF". | |
// If N1 >= 6, it would be sent normally. | |
// (addresses) | |
// (control bytes) | |
// PID, typically 0xF0 | |
// 'A' - first byte of information field | |
// 'B' | |
// 'C' | |
// 'D' | |
// 'E' | |
// 'F' | |
// Now consider the case where it would not fit. | |
// We would change the PID to 0x08 meaning a segment. | |
// The information part is the segment identifier of this format: | |
// | |
// x xxxxxxx | |
// | ---+--- | |
// | | | |
// | +- Number of additional segments to follow. | |
// | | |
// +- '1' means it is the first segment. | |
// If N1 = 4, it would be split up like this: | |
// (addresses) | |
// (control bytes) | |
// PID = 0x08 means segment | |
// 0x82 - Start of info field. | |
// MSB set indicates FIRST segment. | |
// 2, in lower 7 bits, means 2 more segments to follow. | |
// 0xF0 - original PID, typical value. | |
// 'A' - For the FIRST segment, we have PID and N1-2 data bytes. | |
// 'B' | |
// (addresses) | |
// (control bytes) | |
// PID = 0x08 means segment | |
// 0x01 - Means 1 more segment follows. | |
// 'C' - For subsequent (not first) segments, we have up to N1-1 data bytes. | |
// 'D' | |
// 'E' | |
// (addresses) | |
// (control bytes) | |
// PID = 0x08 | |
// 0x00 - 0 means no more to follow. i.e. This is the last. | |
// 'E' | |
// Number of segments is ceiling( (datalen + 1 ) / (N1 - 1)) | |
// we add one to datalen for the original PID. | |
// We subtract one from N1 for the segment identifier header. | |
#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b)) | |
// Compute number of segments. | |
// We will decrement this before putting it in the frame so the first | |
// will have one less than this number. | |
nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1); | |
if (nseg_to_follow < 2 || nseg_to_follow > 128) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, number of segments = %d\n", | |
__LINE__, E->txdata->len, S->n1_paclen, nseg_to_follow); | |
cdata_delete (E->txdata); | |
E->txdata = NULL; | |
return; | |
} | |
orig_offset = 0; | |
remaining_len = E->txdata->len; | |
// First segment. | |
int seglen; | |
struct { | |
char header; // 0x80 + number of segments to follow. | |
char original_pid; | |
char segdata[AX25_N1_PACLEN_MAX]; | |
} first_segment; | |
cdata_t *new_txdata; | |
nseg_to_follow--; | |
first_segment.header = 0x80 | nseg_to_follow; | |
first_segment.original_pid = E->txdata->pid; | |
seglen = MIN(S->n1_paclen - 2, remaining_len); | |
if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > (int)(sizeof(first_segment.segdata))) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", | |
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); | |
cdata_delete (E->txdata); | |
E->txdata = NULL; | |
return; | |
} | |
memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen); | |
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2); | |
data_request_good_size (S, new_txdata); | |
orig_offset += seglen; | |
remaining_len -= seglen; | |
// Subsequent segments. | |
do { | |
struct { | |
char header; // Number of segments to follow. | |
char segdata[AX25_N1_PACLEN_MAX]; | |
} subsequent_segment; | |
nseg_to_follow--; | |
subsequent_segment.header = nseg_to_follow; | |
seglen = MIN(S->n1_paclen - 1, remaining_len); | |
if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > (int)(sizeof(subsequent_segment.segdata))) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", | |
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); | |
cdata_delete (E->txdata); | |
E->txdata = NULL; | |
return; | |
} | |
memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen); | |
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1); | |
data_request_good_size (S, new_txdata); | |
orig_offset += seglen; | |
remaining_len -= seglen; | |
} while (nseg_to_follow > 0); | |
if (remaining_len != 0 || orig_offset != E->txdata->len) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n", | |
__LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len); | |
} | |
cdata_delete (E->txdata); | |
E->txdata = NULL; | |
} /* end dl_data_request */ | |
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata) | |
{ | |
switch (S->state) { | |
case state_0_disconnected: | |
case state_2_awaiting_release: | |
/* | |
* Discard it. | |
*/ | |
cdata_delete (txdata); | |
break; | |
case state_1_awaiting_connection: | |
case state_5_awaiting_v22_connection: | |
/* | |
* Erratum? | |
* The flow chart shows "push on I frame queue" if layer 3 initiated | |
* is NOT set. This seems backwards but I don't understand enough yet | |
* to make a compelling argument that it is wrong. | |
* Implemented as in flow chart. | |
* TODO: Get better understanding of what'layer_3_initiated' means. | |
*/ | |
if (S->layer_3_initiated) { | |
cdata_delete (txdata); | |
break; | |
} | |
// otherwise fall thru. | |
case state_3_connected: | |
case state_4_timer_recovery: | |
/* | |
* "push on I frame queue" | |
* Append to the end would have been a better description because push implies a stack. | |
*/ | |
if (S->i_frame_queue == NULL) { | |
txdata->next = NULL; | |
S->i_frame_queue = txdata; | |
} | |
else { | |
cdata_t *plast = S->i_frame_queue; | |
while (plast->next != NULL) { | |
plast = plast->next; | |
} | |
txdata->next = NULL; | |
plast->next = txdata; | |
} | |
break; | |
} | |
// v1.5 change in strategy. | |
// New I frames, not sent yet, are delayed until after processing anything in the received transmission. | |
// Give the transmit process a kick unless other side is busy or we have reached our window size. | |
// Previously we had i_frame_pop_off_queue here which would start sending new stuff before we | |
// finished dealing with stuff already in progress. | |
switch (S->state) { | |
case state_3_connected: | |
case state_4_timer_recovery: | |
if ( ( ! S->peer_receiver_busy ) && | |
WITHIN_WINDOW_SIZE(S) ) { | |
S->acknowledge_pending = 1; | |
lm_seize_request (S->chan); | |
} | |
break; | |
default: | |
break; | |
} | |
} /* end data_request_good_size */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: dl_register_callsign | |
* dl_unregister_callsign | |
* | |
* Purpose: Register / Unregister callsigns that we will accept connections for. | |
* | |
* Inputs: E - Event from the queue. | |
* The caller will free it. | |
* | |
* Outputs: New item is pushed on the head of the reg_callsign_list. | |
* We don't bother checking for duplicates so the most recent wins. | |
* | |
* Description: The data link state machine does not use MYCALL from the APRS configuration. | |
* For outgoing frames, the client supplies the source callsign. | |
* For incoming connection requests, we need to know what address(es) to respond to. | |
* | |
* Note that one client application can register multiple callsigns for | |
* multiple channels. | |
* Different clients can register different different addresses on the same channel. | |
* | |
*------------------------------------------------------------------------------*/ | |
void dl_register_callsign (dlq_item_t *E) | |
{ | |
reg_callsign_t *r; | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("dl_register_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); | |
} | |
r = calloc(sizeof(reg_callsign_t),1); | |
strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign)); | |
r->chan = E->chan; | |
r->client = E->client; | |
r->next = reg_callsign_list; | |
r->magic = RC_MAGIC; | |
reg_callsign_list = r; | |
} /* end dl_register_callsign */ | |
void dl_unregister_callsign (dlq_item_t *E) | |
{ | |
reg_callsign_t *r, *prev; | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("dl_unregister_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); | |
} | |
prev = NULL; | |
r = reg_callsign_list; | |
while (r != NULL) { | |
assert (r->magic == RC_MAGIC); | |
if (strcmp(r->callsign,E->addrs[0]) == 0 && r->chan == E->chan && r->client == E->client) { | |
if (r == reg_callsign_list) { | |
reg_callsign_list = r->next; | |
memset (r, 0, sizeof(reg_callsign_t)); | |
free (r); | |
r = reg_callsign_list; | |
} | |
else { | |
prev->next = r->next; | |
memset (r, 0, sizeof(reg_callsign_t)); | |
free (r); | |
r = prev->next; | |
} | |
} | |
else { | |
prev = r; | |
r = r->next; | |
} | |
} | |
} /* end dl_unregister_callsign */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: dl_client_cleanup | |
* | |
* Purpose: Client app has gone away. Clean up any data associated with it. | |
* | |
* Inputs: E - Event from the queue. | |
* The caller will free it. | |
* | |
* Description: By client application we mean something that attached with the | |
* AGW network protocol. | |
* | |
* Clean out anything related to the specfied client application. | |
* This would include state machines and registered callsigns. | |
* | |
*------------------------------------------------------------------------------*/ | |
void dl_client_cleanup (dlq_item_t *E) | |
{ | |
ax25_dlsm_t *S; | |
ax25_dlsm_t *dlprev; | |
reg_callsign_t *r, *rcprev; | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_INFO); | |
dw_printf ("dl_client_cleanup (%d)\n", E->client); | |
} | |
dlprev = NULL; | |
S = list_head; | |
while (S != NULL) { | |
// Look for corruption or double freeing. | |
assert (S->magic1 == MAGIC1); | |
assert (S->magic2 == MAGIC2); | |
assert (S->magic3 == MAGIC3); | |
if (S->client == E->client ) { | |
int n; | |
if (s_debug_stats) { | |
text_color_set(DW_COLOR_INFO); | |
dw_printf ("%d I frames received\n", S->count_recv_frame_type[frame_type_I]); | |
dw_printf ("%d RR frames received\n", S->count_recv_frame_type[frame_type_S_RR]); | |
dw_printf ("%d RNR frames received\n", S->count_recv_frame_type[frame_type_S_RNR]); | |
dw_printf ("%d REJ frames received\n", S->count_recv_frame_type[frame_type_S_REJ]); | |
dw_printf ("%d SREJ frames received\n", S->count_recv_frame_type[frame_type_S_SREJ]); | |
dw_printf ("%d SABME frames received\n", S->count_recv_frame_type[frame_type_U_SABME]); | |
dw_printf ("%d SABM frames received\n", S->count_recv_frame_type[frame_type_U_SABM]); | |
dw_printf ("%d DISC frames received\n", S->count_recv_frame_type[frame_type_U_DISC]); | |
dw_printf ("%d DM frames received\n", S->count_recv_frame_type[frame_type_U_DM]); | |
dw_printf ("%d UA frames received\n", S->count_recv_frame_type[frame_type_U_UA]); | |
dw_printf ("%d FRMR frames received\n", S->count_recv_frame_type[frame_type_U_FRMR]); | |
dw_printf ("%d UI frames received\n", S->count_recv_frame_type[frame_type_U_UI]); | |
dw_printf ("%d XID frames received\n", S->count_recv_frame_type[frame_type_U_XID]); | |
dw_printf ("%d TEST frames received\n", S->count_recv_frame_type[frame_type_U_TEST]); | |
dw_printf ("%d peak retry count\n", S->peak_rc_value); | |
} | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("dl_client_cleanup: remove %s>%s\n", S->addrs[AX25_SOURCE], S->addrs[AX25_DESTINATION]); | |
} | |
discard_i_queue (S); | |
for (n = 0; n < 128; n++) { | |
if (S->txdata_by_ns[n] != NULL) { | |
cdata_delete (S->txdata_by_ns[n]); | |
S->txdata_by_ns[n] = NULL; | |
} | |
} | |
for (n = 0; n < 128; n++) { | |
if (S->rxdata_by_ns[n] != NULL) { | |
cdata_delete (S->rxdata_by_ns[n]); | |
S->rxdata_by_ns[n] = NULL; | |
} | |
} | |
if (S->ra_buff != NULL) { | |
cdata_delete (S->ra_buff); | |
S->ra_buff = NULL; | |
} | |
// Put into disconnected state. | |
// If "connected" indicator (e.g. LED) was on, this will turn it off. | |
enter_new_state (S, state_0_disconnected, __func__, __LINE__); | |
// Take S out of list. | |
S->magic1 = 0; | |
S->magic2 = 0; | |
S->magic3 = 0; | |
if (S == list_head) { // first one on list. | |
list_head = S->next; | |
free (S); | |
S = list_head; | |
} | |
else { // not the first one. | |
dlprev->next = S->next; | |
free (S); | |
S = dlprev->next; | |
} | |
} | |
else { | |
dlprev = S; | |
S = S->next; | |
} | |
} | |
/* | |
* If there are no link state machines (streams) remaining, there should be no txdata items still allocated. | |
*/ | |
if (list_head == NULL) { | |
cdata_check_leak(); | |
} | |
/* | |
* Remove registered callsigns for this client. | |
*/ | |
rcprev = NULL; | |
r = reg_callsign_list; | |
while (r != NULL) { | |
assert (r->magic == RC_MAGIC); | |
if (r->client == E->client) { | |
if (r == reg_callsign_list) { | |
reg_callsign_list = r->next; | |
memset (r, 0, sizeof(reg_callsign_t)); | |
free (r); | |
r = reg_callsign_list; | |
} | |
else { | |
rcprev->next = r->next; | |
memset (r, 0, sizeof(reg_callsign_t)); | |
free (r); | |
r = rcprev->next; | |
} | |
} | |
else { | |
rcprev = r; | |
r = r->next; | |
} | |
} | |
} /* end dl_client_cleanup */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: dl_data_indication | |
* | |
* Purpose: send connected data to client application. | |
* | |
* Inputs: pid - Protocol ID. | |
* | |
* data - Pointer to array of bytes. | |
* | |
* len - Number of bytes in data. | |
* | |
* | |
* Description: TODO: We perform reassembly of segments here if necessary. | |
* | |
*------------------------------------------------------------------------------*/ | |
static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len) | |
{ | |
// Now it gets more interesting. We need to combine segments before passing it along. | |
// See example in dl_data_request. | |
if (S->ra_buff == NULL) { | |
// Ready state. | |
if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { | |
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); | |
return; | |
} | |
else if (data[0] & 0x80) { | |
// Ready state, First segment. | |
S->ra_following = data[0] & 0x7f; | |
int total = (S->ra_following + 1) * (len - 1) - 1; // len should be other side's N1 | |
S->ra_buff = cdata_new(data[1], NULL, total); | |
S->ra_buff->size = total; // max that we are expecting. | |
S->ra_buff->len = len - 2; // how much accumulated so far. | |
memcpy (S->ra_buff->data, data + 2, len - 2); | |
} | |
else { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not first segment in ready state.\n", S->stream_id); | |
} | |
} | |
else { | |
// Reassembling data state | |
if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { | |
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not segment in reassembling state.\n", S->stream_id); | |
cdata_delete(S->ra_buff); | |
S->ra_buff = NULL; | |
return; | |
} | |
else if (data[0] & 0x80) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: First segment in reassembling state.\n", S->stream_id); | |
cdata_delete(S->ra_buff); | |
S->ra_buff = NULL; | |
return; | |
} | |
else if ((data[0] & 0x7f) != S->ra_following - 1) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments out of sequence.\n", S->stream_id); | |
cdata_delete(S->ra_buff); | |
S->ra_buff = NULL; | |
return; | |
} | |
else { | |
// Reassembling data state, Not first segment. | |
S->ra_following = data[0] & 0x7f; | |
if (S->ra_buff->len + len - 1 <= S->ra_buff->size) { | |
memcpy (S->ra_buff->data + S->ra_buff->len, data + 1, len - 1); | |
S->ra_buff->len += len - 1; | |
} | |
else { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments exceed buffer space.\n", S->stream_id); | |
cdata_delete(S->ra_buff); | |
S->ra_buff = NULL; | |
return; | |
} | |
if (S->ra_following == 0) { | |
// Last one. | |
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], S->ra_buff->pid, S->ra_buff->data, S->ra_buff->len); | |
cdata_delete(S->ra_buff); | |
S->ra_buff = NULL; | |
} | |
} | |
} | |
} /* end dl_data_indication */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: lm_channel_busy | |
* | |
* Purpose: Change in DCD or PTT status for channel so we know when it is busy. | |
* | |
* Inputs: E - Event from the queue. | |
* | |
* E->chan - Radio channel number. | |
* | |
* E->activity - OCTYPE_PTT for my transmission start/end. | |
* - OCTYPE_DCD if we hear someone else. | |
* | |
* E->status - 1 for active or 0 for quiet. | |
* | |
* Outputs: S->radio_channel_busy | |
* | |
* T1 & TM201 paused/resumed if running. | |
* | |
* Description: We need to pause the timers when the channel is busy. | |
* | |
*------------------------------------------------------------------------------*/ | |
static int dcd_status[MAX_CHANS]; | |
static int ptt_status[MAX_CHANS]; | |
void lm_channel_busy (dlq_item_t *E) | |
{ | |
int busy; | |
assert (E->chan >= 0 && E->chan < MAX_CHANS); | |
assert (E->activity == OCTYPE_PTT || E->activity == OCTYPE_DCD); | |
assert (E->status == 1 || E->status == 0); | |
switch (E->activity) { | |
case OCTYPE_DCD: | |
if (s_debug_radio) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("lm_channel_busy: DCD chan %d = %d\n", E->chan, E->status); | |
} | |
dcd_status[E->chan] = E->status; | |
break; | |
case OCTYPE_PTT: | |
if (s_debug_radio) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("lm_channel_busy: PTT chan %d = %d\n", E->chan, E->status); | |
} | |
ptt_status[E->chan] = E->status; | |
break; | |
default: | |
break; | |
} | |
busy = dcd_status[E->chan] | ptt_status[E->chan]; | |
/* | |
* We know if the given radio channel is busy or not. | |
* This must be applied to all data link state machines associated with that radio channel. | |
*/ | |
ax25_dlsm_t *S; | |
for (S = list_head; S != NULL; S = S->next) { | |
if (E->chan == S->chan) { | |
if (busy && ! S->radio_channel_busy) { | |
S->radio_channel_busy = 1; | |
PAUSE_T1; | |
PAUSE_TM201; | |
} | |
else if ( ! busy && S->radio_channel_busy) { | |
S->radio_channel_busy = 0; | |
RESUME_T1; | |
RESUME_TM201; | |
} | |
} | |
} | |
} /* end lm_channel_busy */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: lm_seize_confirm | |
* | |
* Purpose: Notification the the channel is clear. | |
* | |
* Description: C4.2. This primitive indicates to the Data-link State Machine that | |
* the transmission opportunity has arrived. | |
* | |
* Version 1.5: Originally this only invoked inquiry_response to provide an ack if not already | |
* taken care of by an earlier frame in this transmission. | |
* After noticing the unnecessary I frame duplication and differing N(R) in the same | |
* transmission, I came to the conclusion that we should delay sending of new | |
* (not resends as a result of rej or srej) frames until after after processing | |
* of everything in the incoming transmission. | |
* The protocol spec simply has "I frame pops off queue" without any indication about | |
* what might trigger this event. | |
* | |
*------------------------------------------------------------------------------*/ | |
void lm_seize_confirm (dlq_item_t *E) | |
{ | |
assert (E->chan >= 0 && E->chan < MAX_CHANS); | |
ax25_dlsm_t *S; | |
for (S = list_head; S != NULL; S = S->next) { | |
if (E->chan == S->chan) { | |
switch (S->state) { | |
case state_0_disconnected: | |
case state_1_awaiting_connection: | |
case state_2_awaiting_release: | |
case state_5_awaiting_v22_connection: | |
break; | |
case state_3_connected: | |
case state_4_timer_recovery: | |
// v1.5 change in strategy. | |
// New I frames, not sent yet, are delayed until after processing anything in the received transmission. | |
// Previously we started sending new frames, from the client app, as soon as they arrived. | |
// Now, we first take care of those in progress before throwing more into the mix. | |
i_frame_pop_off_queue(S); | |
// Need an RR if we didn't have I frame send the necessary ack. | |
if (S->acknowledge_pending) { | |
S->acknowledge_pending = 0; | |
enquiry_response (S, frame_not_AX25, 0); | |
} | |
// Implementation difference: The flow chart for state 3 has LM-RELEASE Request here. | |
// I don't think I need it because the transmitter will turn off | |
// automatically once the queue is empty. | |
// Erratum: The original spec had LM-SEIZE request here, for state 4, which didn't seem right. | |
// The 2006 revision has LM-RELEASE Request so states 3 & 4 are the same. | |
break; | |
} | |
} | |
} | |
} /* lm_seize_confirm */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: lm_data_indication | |
* | |
* Purpose: We received some sort of frame over the radio. | |
* | |
* Inputs: E - Event from the queue. | |
* Caller is responsible for freeing it. | |
* | |
* Description: First determine if is of interest to me. Two cases: | |
* | |
* (1) We already have a link handle for (from-addr, to-addr, channel). | |
* This could have been set up by an outgoing connect request. | |
* | |
* (2) It is addressed to one of the registered callsigns. This would | |
* catch the case of incoming connect requests. The APRS MYCALL | |
* is not involved at all. The attached client app might have | |
* much different ideas about what the station is called or | |
* aliases it might respond to. | |
* | |
*------------------------------------------------------------------------------*/ | |
void lm_data_indication (dlq_item_t *E) | |
{ | |
ax25_frame_type_t ftype; | |
char desc[80]; | |
cmdres_t cr; | |
int pf; | |
int nr; | |
int ns; | |
ax25_dlsm_t *S; | |
int client_not_applicable = -1; | |
int n; | |
int any_unused_digi; | |
if (E->pp == NULL) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Internal Error, packet pointer is null. %s %s %d\n", __FILE__, __func__, __LINE__); | |
return; | |
} | |
E->num_addr = ax25_get_num_addr(E->pp); | |
// Digipeating is not done here so consider only those with no unused digipeater addresses. | |
any_unused_digi = 0; | |
for (n = AX25_REPEATER_1; n < E->num_addr; n++) { | |
if ( ! ax25_get_h(E->pp, n)) { | |
any_unused_digi = 1; | |
} | |
} | |
if (any_unused_digi) { | |
if (s_debug_radio) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("lm_data_indication (%d, %s>%s) - ignore due to unused digi address.\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); | |
} | |
return; | |
} | |
// Copy addresses from frame into event structure. | |
for (n = 0; n < E->num_addr; n++) { | |
ax25_get_addr_with_ssid (E->pp, n, E->addrs[n]); | |
} | |
if (s_debug_radio) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("lm_data_indication (%d, %s>%s)\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); | |
} | |
// Look for existing, or possibly create new, link state matching addresses and channel. | |
// In most cases, we can ignore the frame if we don't have a corresponding | |
// data link state machine. However, we might want to create a new one for SABM or SABME. | |
// get_link_handle will check to see if the destination matches my address. | |
// TODO: This won't work right because we don't know the modulo yet. | |
// Maybe we should have a shorter form that only returns the frame type. | |
// That is all we need at this point. | |
ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); | |
S = get_link_handle (E->addrs, E->num_addr, E->chan, client_not_applicable, | |
(ftype == frame_type_U_SABM) | (ftype == frame_type_U_SABME)); | |
if (S == NULL) { | |
return; | |
} | |
/* | |
* There is not a reliable way to tell if a frame, out of context, has modulo 8 or 128 | |
* sequence numbers. This needs to be supplied from the data link state machine. | |
* | |
* We can't do this until we get the link handle. | |
*/ | |
ax25_set_modulo (E->pp, S->modulo); | |
/* | |
* Now we need to use ax25_frame_type again because the previous results, for nr and ns, might be wrong. | |
*/ | |
ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); | |
// Gather statistics useful for testing. | |
if (ftype <= frame_not_AX25) { | |
S->count_recv_frame_type[ftype]++; | |
} | |
switch (ftype) { | |
case frame_type_I: | |
if (cr != cr_cmd) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Protocol Error S: %s must be COMMAND.\n", S->stream_id, desc); | |
} | |
break; | |
case frame_type_S_RR: | |
case frame_type_S_RNR: | |
case frame_type_S_REJ: | |
if (cr != cr_cmd && cr != cr_res) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); | |
} | |
break; | |
case frame_type_U_SABME: | |
case frame_type_U_SABM: | |
case frame_type_U_DISC: | |
if (cr != cr_cmd) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND.\n", S->stream_id, desc); | |
} | |
break; | |
// Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both. | |
// The underlying X.25 spec clearly says it is reponse only. Let's go with that. | |
case frame_type_S_SREJ: | |
case frame_type_U_DM: | |
case frame_type_U_UA: | |
case frame_type_U_FRMR: | |
if (cr != cr_res) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be RESPONSE.\n", S->stream_id, desc); | |
} | |
break; | |
case frame_type_U_XID: | |
case frame_type_U_TEST: | |
if (cr != cr_cmd && cr != cr_res) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); | |
} | |
break; | |
case frame_type_U_UI: | |
// Don't test at this point in case an APRS frame gets thru. | |
// APRS doesn't specify what to put in the Source and Dest C bits. | |
// In practice we see all 4 possble combinations. | |
// I have an opinion about what would be "correct" (discussed elsewhere) | |
// but in practice no one seems to care. | |
break; | |
case frame_type_U: | |
case frame_not_AX25: | |
// not expected. | |
break; | |
} | |
switch (ftype) { | |
case frame_type_I: // Information | |
{ | |
int pid; | |
unsigned char *info_ptr; | |
int info_len; | |
pid = ax25_get_pid (E->pp); | |
info_len = ax25_get_info (E->pp, &info_ptr); | |
i_frame (S, cr, pf, nr, ns, pid, (char *)info_ptr, info_len); | |
} | |
break; | |
case frame_type_S_RR: // Receive Ready - System Ready To Receive | |
rr_rnr_frame (S, 1, cr, pf, nr); | |
break; | |
case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full | |
rr_rnr_frame (S, 0, cr, pf, nr); | |
break; | |
case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate | |
rej_frame (S, cr, pf, nr); | |
break; | |
case frame_type_S_SREJ: // Selective Reject - Ask for selective frame(s) repeat | |
{ | |
unsigned char *info_ptr; | |
int info_len; | |
info_len = ax25_get_info (E->pp, &info_ptr); | |
srej_frame (S, cr, pf, nr, info_ptr, info_len); | |
} | |
break; | |
case frame_type_U_SABME: // Set Async Balanced Mode, Extended | |
sabm_e_frame (S, 1, pf); | |
break; | |
case frame_type_U_SABM: // Set Async Balanced Mode | |
sabm_e_frame (S, 0, pf); | |
break; | |
case frame_type_U_DISC: // Disconnect | |
disc_frame (S, pf); | |
break; | |
case frame_type_U_DM: // Disconnect Mode | |
dm_frame (S, pf); | |
break; | |
case frame_type_U_UA: // Unnumbered Acknowledge | |
ua_frame (S, pf); | |
break; | |
case frame_type_U_FRMR: // Frame Reject | |
frmr_frame (S); | |
break; | |
case frame_type_U_UI: // Unnumbered Information | |
ui_frame (S, cr, pf); | |
break; | |
case frame_type_U_XID: // Exchange Identification | |
{ | |
unsigned char *info_ptr; | |
int info_len; | |
info_len = ax25_get_info (E->pp, &info_ptr); | |
xid_frame (S, cr, pf, info_ptr, info_len); | |
} | |
break; | |
case frame_type_U_TEST: // Test | |
{ | |
unsigned char *info_ptr; | |
int info_len; | |
info_len = ax25_get_info (E->pp, &info_ptr); | |
test_frame (S, cr, pf, info_ptr, info_len); | |
} | |
break; | |
case frame_type_U: // other Unnumbered, not used by AX.25. | |
break; | |
case frame_not_AX25: // Could not get control byte from frame. | |
break; | |
} | |
// An incoming frame might have ack'ed frames we sent or indicated peer is no longer busy. | |
// Rather than putting this test in many places, where those conditions, may have changed, | |
// we will try to catch them all on this single path. | |
// Start transmission if we now have some outgoing data ready to go. | |
// (Added in 1.5 beta 3 for issue 132.) | |
if ( S->i_frame_queue != NULL && | |
(S->state == state_3_connected || S->state == state_4_timer_recovery) && | |
( ! S->peer_receiver_busy ) && | |
WITHIN_WINDOW_SIZE(S) ) { | |
//S->acknowledge_pending = 1; | |
lm_seize_request (S->chan); | |
} | |
} /* end lm_data_indication */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: i_frame | |
* | |
* Purpose: Process I Frame. | |
* | |
* Inputs: S - Data Link State Machine. | |
* cr - Command or Response. We have already issued an error if not command. | |
* p - Poll bit. Assuming we checked earlier that it was a command. | |
* The meaning is described below. | |
* nr - N(R) from the frame. Next expected seq. for other end. | |
* ns - N(S) from the frame. Seq. number of this incoming frame. | |
* pid - protocol id. | |
* info_ptr - pointer to information part of frame. | |
* info_len - Number of bytes in information part of frame. | |
* Should be in range of 0 thru n1_paclen. | |
* | |
* Description: | |
* 6.4.2. Receiving I Frames | |
* | |
* The reception of I frames that contain zero-length information fields is reported to the next layer; no information | |
* field will be transferred. | |
* | |
* 6.4.2.1. Not Busy | |
* | |
* If a TNC receives a valid I frame (one with a correct FCS and whose send sequence number equals the | |
* receiver's receive state variable) and is not in the busy condition, it accepts the received I frame, increments its | |
* receive state variable, and acts in one of the following manners: | |
* | |
* a) If it has an I frame to send, that I frame may be sent with the transmitted N(R) equal to its receive state | |
* variable V(R) (thus acknowledging the received frame). Alternately, the TNC may send an RR frame with N(R) | |
* equal to V(R), and then send the I frame. | |
* | |
* or b) If there are no outstanding I frames, the receiving TNC sends an RR frame with N(R) equal to V(R). The | |
* receiving TNC may wait a small period of time before sending the RR frame to be sure additional I frames are | |
* not being transmitted. | |
* | |
* 6.4.2.2. Busy | |
* | |
* If the TNC is in a busy condition, it ignores any received I frames without reporting this condition, other than | |
* repeating the indication of the busy condition. | |
* If a busy condition exists, the TNC receiving the busy condition indication polls the sending TNC periodically | |
* until the busy condition disappears. | |
* A TNC may poll the busy TNC periodically with RR or RNR frames with the P bit set to "1". | |
* | |
* 6.4.6. Receiving Acknowledgement | |
* | |
* Whenever an I or S frame is correctly received, even in a busy condition, the N(R) of the received frame is | |
* checked to see if it includes an acknowledgement of outstanding sent I frames. The T1 timer is canceled if the | |
* received frame actually acknowledges previously unacknowledged frames. If the T1 timer is canceled and there | |
* are still some frames that have been sent that are not acknowledged, T1 is started again. If the T1 timer expires | |
* before an acknowledgement is received, the TNC proceeds with the retransmission procedure outlined in Section | |
* 6.4.11. | |
* | |
* | |
* 6.2. Poll/Final (P/F) Bit Procedures | |
* | |
* The next response frame returned to an I frame with the P bit set to "1", received during the information | |
* transfer state, is an RR, RNR or REJ response with the F bit set to "1". | |
* | |
* The next response frame returned to a S or I command frame with the P bit set to "1", received in the | |
* disconnected state, is a DM response frame with the F bit set to "1". | |
* | |
*------------------------------------------------------------------------------*/ | |
static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len) | |
{ | |
switch (S->state) { | |
case state_0_disconnected: | |
// Logic from flow chart for "all other commands." | |
if (cr == cr_cmd) { | |
cmdres_t r = cr_res; // DM response with F taken from P. | |
int f = p; | |
int nopid = 0; // PID applies only for I and UI frames. | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
break; | |
case state_1_awaiting_connection: | |
case state_5_awaiting_v22_connection: | |
// Ignore it. Keep same state. | |
break; | |
case state_2_awaiting_release: | |
// Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." | |
if (cr == cr_cmd && p == 1) { | |
cmdres_t r = cr_res; // DM response with F = 1. | |
int f = 1; | |
int nopid = 0; // PID applies only for I and UI frames. | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
break; | |
case state_3_connected: | |
case state_4_timer_recovery: | |
// Look carefully. The original had two tiny differences between the two states. | |
// In the 2006 version, these differences no longer exist. | |
// Erratum: SDL asks: Is information field length <= N1 (paclen). | |
// (github issue 102 - Thanks to KK6WHJ for pointing this out.) | |
// Just because we are limiting the size of our transmitted data, it doesn't mean | |
// that the other end will be doing the same. With v2.2, the XID frame can be | |
// used to negotiate a maximum info length but with v2.0, there is no way for the | |
// other end to know our paclen value. | |
if (info_len >= 0 && info_len <= AX25_MAX_INFO_LEN) { | |
if (is_good_nr(S,nr)) { | |
// Erratum? | |
// I wonder if this difference is intentional or if only one place was | |
// was modified after a cut-n-paste of the flow chart segment. | |
// Erratum: Discrepancy between original and 2006 version. | |
// Pattern noticed: Anytime we have "is_good_nr" which tests for V(A) <= N(R) <= V(S), | |
// we should always call "check_i_frame_ackd" or at least set V(A) from N(R). | |
#if 0 // Erratum: original - states 3 & 4 differ here. | |
if (S->state == state_3_connected) { | |
// This sets "S->va = nr" and also does some timer stuff. | |
check_i_frame_ackd (S,nr); | |
} | |
else { | |
SET_VA(nr); | |
} | |
#else // 2006 version - states 3 & 4 same here. | |
// This sets "S->va = nr" and also does some timer stuff. | |
check_i_frame_ackd (S,nr); | |
#endif | |
// Erratum: v1.5 - My addition. | |
// I noticed that we sometimes got stuck in state 4 and rc crept up slowly even though | |
// we received 'I' frames with N(R) values indicating that the other side received everything | |
// that we sent. Eventually rc could reach the limit and we would get an error. | |
// If we are in state 4, and other guy ack'ed last I frame we sent, transition to state 3. | |
// We had a similar situation for RR/RNR for cases other than response, F=1. | |
if (S->state == state_4_timer_recovery && S->va == S->vs) { | |
STOP_T1; | |
select_t1_value (S); | |
START_T3; | |
SET_RC(0); | |
enter_new_state (S, state_3_connected, __func__, __LINE__); | |
} | |
if (S->own_receiver_busy) { | |
// This should be unreachable because we currently don't have a way to set own_receiver_busy. | |
// But we might the capability someday so implement this while we are here. | |
if (p == 1) { | |
cmdres_t cr = cr_res; // Erratum: The use of "F" in the flow chart implies that RNR is a response | |
// in this case, but I'm not confident about that. The text says frame. | |
int f = 1; | |
int nr = S->vr; | |
packet_t pp; | |
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f, NULL, 0); | |
// I wonder if this difference is intentional or if only one place was | |
// was modified after a cut-n-paste of the flow chart segment. | |
#if 0 // Erratum: Original - state 4 has expedited. | |
if (S->state == state_4_timer_recovery) { | |
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // "expedited" | |
} | |
else { | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
#else // 2006 version - states 3 & 4 the same. | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
#endif | |
S->acknowledge_pending = 0; | |
} | |
} | |
else { // Own receiver not busy. | |
i_frame_continued (S, p, ns, pid, info_ptr, info_len); | |
} | |
} | |
else { // N(R) not in expected range. | |
nr_error_recovery (S); | |
// my enhancement. See below. | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
} | |
else { // Bad information length. | |
// Wouldn't even get to CRC check if not octet aligned. | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("Stream %d: AX.25 Protocol Error O: Information part length, %d, not in range of 0 thru %d.\n", S->stream_id, info_len, AX25_MAX_INFO_LEN); | |
establish_data_link (S); | |
S->layer_3_initiated = 0; | |
// The original spec always sent SABM and went to state 1. | |
// I was thinking, why not use v2.2 instead of we were already connected with v2.2? | |
// My version of establish_data_link combined the two original functions and | |
// already uses SABME or SABM based on S->modulo. | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
break; | |
} | |
} /* end i_frame */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: i_frame_continued | |
* | |
* Purpose: The I frame processing logic gets pretty complicated. | |
* Some of it has been split out into a separate function to make | |
* things more readable. | |
* We have already done some error checking and processed N(R). | |
* This is used for both states 3 & 4. | |
* | |
* Inputs: S - Data Link State Machine. We are in state 3. | |
* p - Poll bit. | |
* ns - N(S) from the frame. Seq. number of this incoming frame. | |
* pid - protocol id. | |
* info_ptr - pointer to information part of frame. | |
* info_len - Number of bytes in information part of frame. Already verified. | |
* | |
* Description: | |
* | |
* 4.3.2.3. Reject (REJ) Command and Response | |
* | |
* The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence | |
* number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the | |
* retransmission of the N(R) frame. | |
* Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the | |
* proper reception of I frames up to the I frame that caused the reject condition to be initiated. | |
* The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit | |
* set to one. | |
* | |
* 4.3.2.4. Selective Reject (SREJ) Command and Response | |
* | |
* (Erratum: SREJ is only response with F bit.) | |
* | |
* The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame | |
* numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are | |
* considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ | |
* frame does not indicate acknowledgement of I frames. | |
* | |
* Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) | |
* of the SREJ frame. | |
* | |
* A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set | |
* to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not | |
* transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so | |
* would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ | |
* frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in | |
* | |
* Section 4.5.4. | |
* | |
* I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of | |
* receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the | |
* retransmission of the specific I frame requested by the SREJ frame. | |
* | |
* | |
* 6.4.4. Reception of Out-of-Sequence Frames | |
* | |
* 6.4.4.1. Implicit Reject (REJ) | |
* | |
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current | |
* receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number | |
* equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not | |
* been previously established. The received state variable and poll bit of the discarded frame is checked and acted | |
* upon, if necessary. | |
* This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode | |
* requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This | |
* mode is ineffective on systems with long round-trip delays and high data rates. | |
* | |
* 6.4.4.2. Selective Reject (SREJ) | |
* | |
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current | |
* receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number | |
* equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously | |
* established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable | |
* and poll bit of the received frame are checked and acted upon, if necessary. | |
* This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can | |
* consume precious buffer space, especially if the user device has limited memory available and several active | |
* links are operational. | |
* | |
* 6.4.4.3. Selective Reject-Reject (SREJ/REJ) | |
* | |
* (Erratum: REJ/SREJ should not be mixed. Basic (mod 8) allows only REJ. | |
* Extended (mod 128) gives you a choice of one or the other for a link.) | |
* | |
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current | |
* receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. | |
* All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is | |
* missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The | |
* received state variable and poll bit of the received frame are checked and acted upon. If another frame error | |
* occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame | |
* and discards frames received after the second errored frame until the first errored frame is recovered. Then, a | |
* REJ is issued to recover the second errored frame and all subsequent discarded frames. | |
* | |
*------------------------------------------------------------------------------*/ | |
static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len) | |
{ | |
if (ns == S->vr) { | |
// The receive sequence number, N(S), is the same as what we were expecting, V(R). | |
// Send it to the application and increment the next expected. | |
// It is possible that this was resent and we tucked away others with the following | |
// sequence numbers. If so, process them too. | |
SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); | |
S->reject_exception = 0; | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); | |
ax25_safe_print (info_ptr, info_len, 1); | |
dw_printf ("\"\n"); | |
} | |
dl_data_indication (S, pid, info_ptr, info_len); | |
if (S->rxdata_by_ns[ns] != NULL) { | |
// There is a possibility that we might have another received frame stashed | |
// away from 8 or 128 (modulo) frames back. Remove it so it doesn't accidently | |
// show up at some future inopportune time. | |
cdata_delete (S->rxdata_by_ns[ns]); | |
S->rxdata_by_ns[ns] = NULL; | |
} | |
while (S->rxdata_by_ns[S->vr] != NULL) { | |
// dl_data_indication - send connected data to client application. | |
if (s_debug_client_app) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, data=\"", __func__, __LINE__, ns, S->vr); | |
ax25_safe_print (S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len, 1); | |
dw_printf ("\"\n"); | |
} | |
dl_data_indication (S, S->rxdata_by_ns[S->vr]->pid, S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len); | |
// Don't keep around anymore after sending it to client app. | |
cdata_delete (S->rxdata_by_ns[S->vr]); | |
S->rxdata_by_ns[S->vr] = NULL; | |
SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); | |
} | |
if (p) { | |
// Mentioned in section 6.2. | |
// The next response frame returned to an I frame with the P bit set to "1", received during the information | |
// transfer state, is an RR, RNR or REJ response with the F bit set to "1". | |
int f = 1; | |
int nr = S->vr; // Next expected sequence number. | |
cmdres_t cr = cr_res; // response with F set to 1. | |
packet_t pp; | |
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
S->acknowledge_pending = 0; | |
} | |
else if ( ! S->acknowledge_pending) { | |
S->acknowledge_pending = 1; // Probably want to set this before the LM-SEIZE Request | |
// in case the LM-SEIZE Confirm gets processed before we | |
// return from it. | |
// Force start of transmission even if the transmit frame queue is empty. | |
// Notify me, with lm_seize_confirm, when transmission has started. | |
// When that event arrives, we check acknowledge_pending and send something | |
// to be determined later. | |
lm_seize_request (S->chan); | |
} | |
} | |
else if (S->reject_exception) { | |
// This is not the sequence we were expecting. | |
// We previously sent REJ, asking for a resend so don't send another. | |
// In this case, send RR only if the Poll bit is set. | |
// Again, reference section 6.2. | |
if (p) { | |
int f = 1; | |
int nr = S->vr; // Next expected sequence number. | |
cmdres_t cr = cr_res; // response with F set to 1. | |
packet_t pp; | |
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
S->acknowledge_pending = 0; | |
} | |
} | |
else if (S->srej_enable == srej_none) { | |
// The received sequence number is not the expected one and we can't use SREJ. | |
// The old v2.0 approach is to send and REJ with the number we are expecting. | |
// This can be very inefficient. For example if we received 1,3,4,5,6 in one transmission, | |
// we discard 3,4,5,6, and tell the other end to resend everything starting with 2. | |
// At one time, I had some doubts about when to use command or response for REJ. | |
// I now believe that reponse, as implied by setting F in the flow chart, is correct. | |
int f = p; | |
int nr = S->vr; // Next expected sequence number. | |
cmdres_t cr = cr_res; // response with F copied from P in I frame. | |
packet_t pp; | |
S->reject_exception = 1; | |
if (s_debug_retry) { | |
text_color_set(DW_COLOR_ERROR); // make it more noticable. | |
dw_printf ("sending REJ, at %s %d, SREJ not enabled case, V(R)=%d", __func__, __LINE__, S->vr); | |
} | |
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_REJ, S->modulo, nr, f, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
S->acknowledge_pending = 0; | |
} | |
else { | |
// Selective reject is enabled so we can use the more efficient method. | |
// This is normally enabled for v2.2 but XID can be used to change that. | |
// First we save the current frame so we can retrieve it later after getting the fill in. | |
if (S->modulo != 128) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR: Should not be sending SREJ in basic (modulo 8) mode.\n"); | |
} | |
#if 1 | |
// Erratum: AX.25 protocol spec did not handle SREJ very well. | |
// Based on X.25 section 2.4.6.4. | |
if (is_ns_in_window(S, ns)) { | |
// X.25 2.4.6.4 (b) | |
// v(R) < N(S) < V(R)+k so it is in the expected range. | |
// Save it in the receive buffer. | |
if (S->rxdata_by_ns[ns] != NULL) { | |
cdata_delete (S->rxdata_by_ns[ns]); | |
S->rxdata_by_ns[ns] = NULL; | |
} | |
S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); | |
if (s_debug_misc) { | |
dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); | |
ax25_safe_print (info_ptr, info_len, 1); | |
dw_printf ("\"\n"); | |
} | |
if (p == 1) { | |
int f = 1; | |
enquiry_response (S, frame_type_I, f); | |
} | |
else if (S->own_receiver_busy) { | |
cmdres_t cr = cr_res; // send RNR response | |
int f = 0; // we know p=0 here. | |
int nr = S->vr; | |
packet_t pp; | |
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
else if (S->rxdata_by_ns[ AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) { | |
// Ask for missing frames when we don't have N(S)-1 in the receive buffer. | |
// In version 1.4: | |
// We end up sending more SREJ than necessary and and get back redundant information. Example: | |
// When we see 113 missing, we ask for a resend. | |
// When we see 115 & 116 missing, a cummulative SREJ asks for everything. | |
// The other end dutifully sends 113 twice. | |
// | |
// [0.4] DW1>DW0:(SREJ res, n(r)=113, f=0) | |
// [0.4] DW1>DW0:(SREJ res, n(r)=113, f=1)<0xe6><0xe8> | |
// | |
// [0L] DW0>DW1:(I cmd, n(s)=113, n(r)=11, p=0, pid=0xf0)0114 send data<0x0d> | |
// [0L] DW0>DW1:(I cmd, n(s)=113, n(r)=11, p=0, pid=0xf0)0114 send data<0x0d> | |
// [0L] DW0>DW1:(I cmd, n(s)=115, n(r)=11, p=0, pid=0xf0)0116 send data<0x0d> | |
// [0L] DW0>DW1:(I cmd, n(s)=116, n(r)=11, p=0, pid=0xf0)0117 send data<0x0d> | |
// Version 1.5: | |
// Don't generate duplicate requests for gaps in the same transmission. | |
// Ideally, we might wait until carrier drops and then use one Multi-SREJ for entire transmission but | |
// we will keep that for another day. | |
// Probably need a flag similar to acknowledge_pending (or ask_resend_count, here) and the ask_for_resend array. | |
// It could then be processed first in lm_seize_confirm. | |
int ask_for_resend[128]; | |
int ask_resend_count = 0; | |
int x; | |
// Version 1.5 | |
// Erratum: AX.25 says use F=0 here. Doesn't make sense. | |
// We would want to set F when sending N(R) = V(R). | |
// int allow_f1 = 0; // F=1 from X.25 2.4.6.4 b) 3) | |
int allow_f1 = 1; // F=1 from X.25 2.4.6.4 b) 3) | |
// send only for this gap, not cummulative from V(R). | |
int last = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__); | |
int first = last; | |
while (first != S->vr && S->rxdata_by_ns[AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) { | |
first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__); | |
} | |
x = first; | |
do { | |
ask_for_resend[ask_resend_count++] = AX25MODULO(x, S->modulo, __FILE__, __func__, __LINE__); | |
x = AX25MODULO(x + 1, S->modulo, __FILE__, __func__, __LINE__); | |
} while (x != AX25MODULO(last + 1, S->modulo, __FILE__, __func__, __LINE__)); | |
send_srej_frames (S, ask_for_resend, ask_resend_count, allow_f1); | |
} | |
} | |
else { | |
// X.25 2.4.6.4 a) | |
// N(S) is not in expected range. Discard it. Send response if P=1. | |
if (p == 1) { | |
int f = 1; | |
enquiry_response (S, frame_type_I, f); | |
} | |
} | |
#else // my earlier attempt before taking a close look at X.25 spec. | |
// Keeping it around for a little while because I might want to | |
// use earlier technique of sending only needed SREJ for any second | |
// and later gaps in a single multiframe transmission. | |
if (S->rxdata_by_ns[ns] != NULL) { | |
cdata_delete (S->rxdata_by_ns[ns]); | |
S->rxdata_by_ns[ns] = NULL; | |
} | |
S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); | |
S->outstanding_srej[ns] = 0; // Don't care if it was previously set or not. | |
// We have this one so there is no outstanding SREJ for it. | |
if (s_debug_misc) { | |
dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); | |
ax25_safe_print (info_ptr, info_len, 1); | |
dw_printf ("\"\n"); | |
} | |
if (selective_reject_exception(S) == 0) { | |
// Erratum: This is vastly different than the SDL in the AX.25 protocol spec. | |
// That would use SREJ if only one was missing and REJ instead. | |
// Here we do not mix the them. | |
// This agrees with the X.25 protocol spec that says use one or the other. Not both. | |
// Suppose we had incoming I frames 0, 3, 7. | |
// 0 was already processed and V(R)=1 meaning that is the next expected. | |
// At this point we area processing N(S)=3. | |
// In this case, we need to ask for a resend of 1 & 2. | |
// More generally, the range of V(R) thru N(S)-1. | |
int ask_for_resend[128]; | |
int ask_resend_count = 0; | |
int i; | |
int allow_f1 = 1; | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("%s:%d, zero exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, S->vr, ns); | |
for (i = S->vr; i != ns; i = AX25MODULO(i+1, S->modulo, __FILE__, __func__, __LINE__)) { | |
ask_for_resend[ask_resend_count++] = i; | |
} | |
send_srej_frames (S, ask_for_resend, ask_resend_count, allow_f1); | |
} | |
else { | |
// Erratum: The SDL says ask for N(S) which is clearly wrong because that's what we just received. | |
// Instead we want to ask for any missing frames up to but not including N(S). | |
// Let's continue with the example above. I frames with N(S) of 0, 3, 7. | |
// selective_reject_exception is non zero meaning there are outstanding requests to resend specified I frames. | |
// V(R) is still 1 because 0 is the last one received with contiguous N(S) values. | |
// 3 has been saved into S->rxdata_by_ns. | |
// We now have N(S)=7. We want to ask for a resend of 4, 5, 6. | |
// This can be achieved by searching S->rxdata_by_ns, starting with N(S)-1, and counting | |
// how many empty slots we have before finding a saved frame. | |
int ask_resend_count = 0; | |
int first; | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("%s:%d, %d srej exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, selective_reject_exception(S), S->vr, ns); | |
first = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__); | |
while (S->rxdata_by_ns[first] == NULL) { | |
if (first == AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__)) { | |
// Oops! Went too far. This I frame was already processed. | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR calulating what to put in SREJ, %s line %d\n", __func__, __LINE__); | |
dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, ask_resend_count=%d\n", S->vr, ns, selective_reject_exception(S), first, ask_resend_count); | |
int k; | |
for (k=0; k<128; k++) { | |
if (S->rxdata_by_ns[k] != NULL) { | |
dw_printf ("rxdata_by_ns[%d] has data\n", k); | |
} | |
} | |
break; | |
} | |
ask_resend_count++; | |
first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__); | |
} | |
// Go beyond the slot where we already have an I frame. | |
first = AX25MODULO(first + 1, S->modulo, __FILE__, __func__, __LINE__); | |
// The ask_resend_count could be 0. e.g. We got 4 rather than 7 in this example. | |
if (ask_resend_count > 0) { | |
int ask_for_resend[128]; | |
int n; | |
int allow_f1 = 1; | |
for (n = 0; n < ask_resend_count; n++) { | |
ask_for_resend[n] = AX25MODULO(first + n, S->modulo, __FILE__, __func__, __LINE__);; | |
} | |
send_srej_frames (S, ask_for_resend, ask_resend_count, allow_f1); | |
} | |
} /* end SREJ exception */ | |
#endif // my earlier attempt. | |
// Erratum: original has following but 2006 rev does not. | |
// I think the 2006 version is correct. | |
// SREJ does not always satisfy the need for ack. | |
// There is a special case where F=1. We take care of that inside of send_srej_frames. | |
#if 0 | |
S->acknowledge_pending = 0; | |
#endif | |
} /* end srej enabled */ | |
} /* end i_frame_continued */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: is_ns_in_window | |
* | |
* Purpose: Is the N(S) value of the incoming I frame in the expected range? | |
* | |
* Inputs: ns - Sequence from I frame. | |
* | |
* Description: With selective reject, it is possible that we could receive a repeat of | |
* an I frame with N(S) less than V(R). In this case, we just want to | |
* discard it rather than getting upset and reestablishing the connection. | |
* | |
* The X.25 spec,section 2.4.6.4 (b) asks whether V(R) < N(S) < V(R)+k. | |
* | |
* The problem here is that it depends on the value of k for the other end. | |
* X.25 says that both sides need to agree on a common value of k ahead of time. | |
* We might have k=8 for our sending but the other side could have k=32 so | |
* this test could fail. | |
* | |
* As a hack, we could use the value 63 here. If too small we would discard | |
* I frames that are in the acceptable range because they would be >= V(R)+k. | |
* On the other hand, if this value is too big, the range < V(R) would not be | |
* large enough and we would accept frame we shouldn't. | |
* As a practical matter, using a window size that large is pretty unlikely. | |
* Maybe I could put a limit of 63, rather than 127 in the configuration. | |
* | |
*------------------------------------------------------------------------------*/ | |
#define GENEROUS_K 63 | |
static int is_ns_in_window (ax25_dlsm_t *S, int ns) | |
{ | |
int adjusted_vr, adjusted_ns, adjusted_vrpk; | |
int result; | |
/* Shift all values relative to V(R) before comparing so we won't have wrap around. */ | |
#define adjust_by_vr(x) (AX25MODULO((x) - S->vr, S->modulo, __FILE__, __func__, __LINE__)) | |
adjusted_vr = adjust_by_vr(S->vr); // A clever compiler would know it is zero. | |
adjusted_ns = adjust_by_vr(ns); | |
adjusted_vrpk = adjust_by_vr(S->vr + GENEROUS_K); | |
result = adjusted_vr < adjusted_ns && adjusted_ns < adjusted_vrpk; | |
if (s_debug_retry) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("is_ns_in_window, V(R) %d < N(S) %d < V(R)+k %d, returns %d\n", S->vr, ns, S->vr + GENEROUS_K, result); | |
} | |
return (result); | |
} | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: send_srej_frames | |
* | |
* Purpose: Ask for a resend of I frames with specified sequence numbers. | |
* | |
* Inputs: resend - Array of N(S) values for missing I frames. | |
* | |
* count - Number of items in array. | |
* | |
* allow_f1 - When true, set F=1 when asking for V(R). | |
* | |
* X.25 section 2.4.6.4 b) 3) says F should be set to 0 | |
* when receiving I frame out of sequence. | |
* | |
* X.25 sections 2.4.6.11 & 2.3.5.2.2 say set F to 1 when | |
* responding to command with P=1. (our enquiry_response function). | |
* | |
* Version 1.5: The X.25 protocol spec allows additional sequence numbers in one frame | |
* by using the INFO part. | |
* By default that feature is off but can be negotiated with XID. | |
* We should be able to use this between two direwolf stations while | |
* maintaining compatibility with the original AX.25 v2.2. | |
* | |
*------------------------------------------------------------------------------*/ | |
static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1) | |
{ | |
int f; // Set if we are ack-ing one before. | |
int nr; | |
cmdres_t cr = cr_res; // SREJ is always response. | |
int i; | |
packet_t pp; | |
if (count <= 0) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, count=%d, %s line %d\n", count, __func__, __LINE__); | |
return; | |
} | |
if (s_debug_retry) { | |
text_color_set(DW_COLOR_INFO); | |
dw_printf ("%s line %d\n", __func__, __LINE__); | |
//dw_printf ("state=%d, count=%d, k=%d, V(R)=%d, SREJ exeception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S)); | |
dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); | |
dw_printf ("resend[]="); | |
for (i = 0; i < count; i++) { | |
dw_printf (" %d", resend[i]); | |
} | |
dw_printf ("\n"); | |
dw_printf ("rxdata_by_ns[]="); | |
for (i = 0; i < 128; i++) { | |
if (S->rxdata_by_ns[i] != NULL) { | |
dw_printf (" %d", i); | |
} | |
} | |
dw_printf ("\n"); | |
} | |
// Something is wrong! We ask for more than the window size. | |
if (count > S->k_maxframe) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR - Extreme number of SREJ, %s line %d\n", __func__, __LINE__); | |
dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); | |
dw_printf ("resend[]="); | |
for (i = 0; i < count; i++) { | |
dw_printf (" %d", resend[i]); | |
} | |
dw_printf ("\n"); | |
dw_printf ("rxdata_by_ns[]="); | |
for (i = 0; i < 128; i++) { | |
if (S->rxdata_by_ns[i] != NULL) { | |
dw_printf (" %d", i); | |
} | |
} | |
dw_printf ("\n"); | |
} | |
// Multi-SREJ - Use info part for additional sequence number(s) instead of sending separate SREJ for each. | |
if (S->srej_enable == srej_multi && count > 1) { | |
unsigned char info[128]; | |
int info_len = 0; | |
for (i = 1; i < count; i++) { // skip first one | |
if (resend[i] < 0 || resend[i] >= S->modulo) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, additional nr=%d, modulo=%d, %s line %d\n", resend[i], S->modulo, __func__, __LINE__); | |
} | |
// There is also a form to specify a range but I don't | |
// think it is worth the effort to generate it. Maybe later. | |
if (S->modulo == 8) { | |
info[info_len++] = resend[i] << 5; | |
} | |
else { | |
info[info_len++] = resend[i] << 1; | |
} | |
} | |
f = 0; | |
nr = resend[0]; | |
f = allow_f1 && (nr == S->vr); | |
// Possibly set if we are asking for the next after | |
// the last one received in contiguous order. | |
// This could only apply to the first in | |
// the list so this would not go in the loop. | |
if (f) { // In this case the other end is being | |
// informed of my V(R) so no additional | |
// RR etc. is needed. | |
// TODO: Need to think about this. | |
S->acknowledge_pending = 0; | |
} | |
if (nr < 0 || nr >= S->modulo) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, nr=%d, modulo=%d, %s line %d\n", nr, S->modulo, __func__, __LINE__); | |
nr = AX25MODULO(nr, S->modulo, __FILE__, __func__, __LINE__); | |
} | |
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_SREJ, S->modulo, nr, f, info, info_len); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
return; | |
} | |
// Multi-SREJ not enabled. Send separate SREJ for each desired sequence number. | |
for (i = 0; i < count; i++) { | |
nr = resend[i]; | |
f = allow_f1 && (nr == S->vr); | |
// Possibly set if we are asking for the next after | |
// the last one received in contiguous order. | |
if (f) { | |
// In this case the other end is being | |
// informed of my V(R) so no additional | |
// RR etc. is needed. | |
S->acknowledge_pending = 0; | |
} | |
if (nr < 0 || nr >= S->modulo) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("INTERNAL ERROR, nr=%d, modulo=%d, %s line %d\n", nr, S->modulo, __func__, __LINE__); | |
nr = AX25MODULO(nr, S->modulo, __FILE__, __func__, __LINE__); | |
} | |
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_SREJ, S->modulo, nr, f, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
} /* end send_srej_frames */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: rr_rnr_frame | |
* | |
* Purpose: Process RR or RNR Frame. | |
* Processing is the essentially the same so they are handled by a single function. | |
* | |
* Inputs: S - Data Link State Machine. | |
* ready - True for RR, false for RNR | |
* cr - Is this command or response? | |
* pf - Poll/Final bit. | |
* nr - N(R) from the frame. | |
* | |
* Description: 4.3.2.1. Receive Ready (RR) Command and Response | |
* | |
* Receive Ready accomplishes the following: | |
* a) indicates that the sender of the RR is now able to receive more I frames; | |
* b) acknowledges properly received I frames up to, and including N(R)-1;and | |
* c) clears a previously-set busy condition created by an RNR command having been sent. | |
* The status of the TNC at the other end of the link can be requested by sending an RR command frame with the | |
* P-bit set to one. | |
* | |
* 4.3.2.2. Receive Not Ready (RNR) Command and Response | |
* | |
* Receive Not Ready indicates to the sender of I frames that the receiving TNC is temporarily busy and cannot | |
* accept any more I frames. Frames up to N(R)-1 are acknowledged. Frames N(R) and above that may have been | |
* transmitted are discarded and must be retransmitted when the busy condition clears. | |
* The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. | |
* The status of the TNC at the other end of the link is requested by sending an RNR command frame with the | |
* P bit set to one. | |
* | |
*------------------------------------------------------------------------------*/ | |
static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr) | |
{ | |
// dw_printf ("rr_rnr_frame (ready=%d, cr=%d, pf=%d, nr=%d) state=%d\n", ready, cr, pf, nr, S->state); | |
switch (S->state) { | |
case state_0_disconnected: | |
if (cr == cr_cmd) { | |
cmdres_t r = cr_res; // DM response with F taken from P. | |
int f = pf; | |
int nopid = 0; // PID only for I and UI frames. | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
break; | |
case state_1_awaiting_connection: | |
case state_5_awaiting_v22_connection: | |
// do nothing. | |
break; | |
case state_2_awaiting_release: | |
// Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." | |
if (cr == cr_cmd && pf == 1) { | |
cmdres_t r = cr_res; // DM response with F = 1. | |
int f = 1; | |
int nopid = 0; // PID applies only for I and UI frames. | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
// Erratum: We have a disagreement here between original and 2006 version. | |
// RR, RNR, REJ, SREJ responses would fall under "all other primitives." | |
// In the original, we simply ignore it and stay in state 2. | |
// The 2006 version, page 94, says go into "1 awaiting connection" state. | |
// That makes no sense to me. | |
break; | |
case state_3_connected: | |
S->peer_receiver_busy = ! ready; | |
// Erratum: the flow charts have unconditional check_need_for_response here. | |
// I don't recall exactly why I added the extra test for command and P=1. | |
// It might have been because we were reporting error A for response with F=1. | |
// Other than avoiding that error message, this is functionally equivalent. | |
if (cr == cr_cmd && pf) { | |
check_need_for_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, cr, pf); | |
} | |
if (is_good_nr(S,nr)) { | |
// dw_printf ("rr_rnr_frame (), line %d, state=%d, good nr=%d, calling check_i_frame_ackd\n", __LINE__, S->state, nr); | |
check_i_frame_ackd (S, nr); | |
} | |
else { | |
if (s_debug_retry) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("rr_rnr_frame (), line %d, state=%d, bad nr, calling nr_error_recovery\n", __LINE__, S->state); | |
} | |
nr_error_recovery (S); | |
// My enhancement. Original always sent SABM and went to state 1. | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
break; | |
case state_4_timer_recovery: | |
S->peer_receiver_busy = ! ready; | |
if (cr == cr_res && pf == 1) { | |
// RR/RNR Response with F==1. | |
if (s_debug_retry) { | |
text_color_set(DW_COLOR_DEBUG); | |
dw_printf ("rr_rnr_frame (), Response, f=%d, line %d, state=%d, good nr, calling check_i_frame_ackd\n", pf, __LINE__, S->state); | |
} | |
STOP_T1; | |
select_t1_value(S); | |
if (is_good_nr(S,nr)) { | |
SET_VA(nr); | |
if (S->vs == S->va) { // all caught up with ack from other guy. | |
START_T3; | |
SET_RC(0); // My enhancement. See Erratum note in select_t1_value. | |
enter_new_state (S, state_3_connected, __func__, __LINE__); | |
} | |
else { | |
invoke_retransmission (S, nr); | |
// my addition | |
// Erratum: We sent I frame(s) and want to timeout if no ack comes back. | |
// We also sent N(R) so no need for extra RR at the end only for that. | |
STOP_T3; | |
START_T1; | |
S->acknowledge_pending = 0; | |
// end of my addition | |
} | |
} | |
else { | |
nr_error_recovery (S); | |
// Erratum: Another case of my enhancement. | |
// The flow charts go into state 1 after nr_error_recovery. | |
// I use state 5 instead if we were oprating in extended (modulo 128) mode. | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
} | |
else { | |
// RR/RNR command, either P value. | |
// RR/RNR response, F==0 | |
if (cr == cr_cmd && pf == 1) { | |
int f = 1; | |
enquiry_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, f); | |
} | |
if (is_good_nr(S,nr)) { | |
SET_VA(nr); | |
// Erratum: v1.5 - my addition. | |
// I noticed that we sometimes got stuck in state 4 and rc crept up slowly even though | |
// we received RR frames with N(R) values indicating that the other side received everything | |
// that we sent. Eventually rc could reach the limit and we would get an error. | |
// If we are in state 4, and other guy ack'ed last I frame we sent, transition to state 3. | |
// The same thing was done for receving I frames after check_i_frame_ackd. | |
// Thought: Could we simply call check_i_frame_ackd, for consistency, rather than only setting V(A)? | |
if (cr == cr_res && pf == 0) { | |
if (S->vs == S->va) { // all caught up with ack from other guy. | |
STOP_T1; | |
select_t1_value (S); | |
START_T3; | |
SET_RC(0); | |
enter_new_state (S, state_3_connected, __func__, __LINE__); | |
} | |
} | |
} | |
else { | |
nr_error_recovery (S); | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
} | |
break; | |
} | |
} /* end rr_rnr_frame */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: rej_frame | |
* | |
* Purpose: Process REJ Frame. | |
* | |
* Inputs: S - Data Link State Machine. | |
* cr - Is this command or response? | |
* pf - Poll/Final bit. | |
* nr - N(R) from the frame. | |
* | |
* Description: 4.3.2.2. Receive Not Ready (RNR) Command and Response | |
* | |
* ... The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. ... | |
* | |
* | |
* 4.3.2.3. Reject (REJ) Command and Response | |
* | |
* The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence | |
* number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the | |
* retransmission of the N(R) frame. | |
* Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the | |
* proper reception of I frames up to the I frame that caused the reject condition to be initiated. | |
* The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit | |
* set to one. | |
* | |
* 4.4.3. Reject (REJ) Recovery | |
* | |
* The REJ frame requests a retransmission of I frames following the detection of a N(S) sequence error. Only | |
* one outstanding "sent REJ" condition is allowed at a time. This condition is cleared when the requested I frame | |
* has been received. | |
* A TNC receiving the REJ command clears the condition by resending all outstanding I frames (up to the | |
* window size), starting with the frame indicated in N(R) of the REJ frame. | |
* | |
* | |
* 4.4.5.1. T1 Timer Recovery | |
* | |
* If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I | |
* frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently | |
* does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion | |
* of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described | |
* in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent | |
* frame(s), or by the link being reset. | |
* | |
* 6.2. Poll/Final (P/F) Bit Procedures | |
* | |
* The response frame returned by a TNC depends on the previous command received, as described in the | |
* following paragraphs. | |
* ... | |
* | |
* The next response frame returned to an I frame with the P bit set to "1", received during the information5 | |
* transfer state, is an RR, RNR or REJ response with the F bit set to "1". | |
* | |
* The next response frame returned to a supervisory command frame with the P bit set to "1", received during | |
* the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1". | |
* ... | |
* | |
* The P bit is used in conjunction with the timeout recovery condition discussed in Section 4.5.5. | |
* When not used, the P/F bit is set to "0". | |
* | |
* 6.4.4.1. Implicit Reject (REJ) | |
* | |
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current | |
* receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number | |
* equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not | |
* been previously established. The received state variable and poll bit of the discarded frame is checked and acted | |
* upon, if necessary. | |
* This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode | |
* requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This | |
* mode is ineffective on systems with long round-trip delays and high data rates. | |
* | |
* 6.4.7. Receiving REJ | |
* | |
* After receiving a REJ frame, the transmitting TNC sets its send state variable to the same value as the REJ | |
* frame's received sequence number in the control field. The TNC then retransmits any I frame(s) outstanding at | |
* the next available opportunity in accordance with the following: | |
* | |
* a) If the TNC is not transmitting at the time and the channel is open, the TNC may begin retransmission of the | |
* I frame(s) immediately. | |
* b) If the TNC is operating on a full-duplex channel transmitting a UI or S frame when it receives a REJ frame, | |
* it may finish sending the UI or S frame and then retransmit the I frame(s). | |
* c) If the TNC is operating in a full-duplex channel transmitting another I frame when it receives a REJ frame, | |
* it may abort the I frame it was sending and start retransmission of the requested I frames immediately. | |
* d) The TNC may send just the one I frame outstanding, or it may send more than the one indicated if more I | |
* frames followed the first unacknowledged frame, provided that the total to be sent does not exceed the flowcontrol | |
* window (k frames). | |
* If the TNC receives a REJ frame with the poll bit set, it responds with either an RR or RNR frame with the | |
* final bit set before retransmitting the outstanding I frame(s). | |
* | |
*------------------------------------------------------------------------------*/ | |
static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr) | |
{ | |
switch (S->state) { | |
case state_0_disconnected: | |
// states 0 and 2 are very similar with one tiny little difference. | |
if (cr == cr_cmd) { | |
cmdres_t r = cr_res; // DM response with F taken from P. | |
int f = pf; | |
int nopid = 0; // PID is only for I and UI. | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
break; | |
case state_1_awaiting_connection: | |
case state_5_awaiting_v22_connection: | |
// Do nothing. | |
break; | |
case state_2_awaiting_release: | |
if (cr == cr_cmd && pf == 1) { | |
cmdres_t r = cr_res; // DM response with F = 1. | |
int f = 1; | |
int nopid = 0; | |
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); | |
lm_data_request (S->chan, TQ_PRIO_1_LO, pp); | |
} | |
// Erratum: We have a disagreement here between original and 2006 version. | |
// RR, RNR, REJ, SREJ responses would fall under "all other primitives." | |
// In the original, we simply ignore it and stay in state 2. | |
// The 2006 version, page 94, says go into "1 awaiting connection" state. | |
// That makes no sense to me. | |
break; | |
case state_3_connected: | |
S->peer_receiver_busy = 0; | |
// Receipt of the REJ "frame" (either command or response) causes us to | |
// start resending I frames at the specified number. | |
// I think there are 3 possibilities here: | |
// Response is used when incoming I frame processing detects one is missing. | |
// In this case, F is copied from the I frame P bit. I don't think we care here. | |
// Command with P=1 is used during timeout recovery. | |
// The rule is that we are supposed to send a response with F=1 for I, RR, RNR, or REJ with P=1. | |
check_need_for_response (S, frame_type_S_REJ, cr, pf); | |
if (is_good_nr(S,nr)) { | |
SET_VA(nr); | |
STOP_T1; | |
STOP_T3; | |
select_t1_value(S); | |
invoke_retransmission (S, nr); | |
// my addition | |
// Erratum: We sent I frame(s) and want to timeout if no ack comes back. | |
// We also sent N(R) so no need for extra RR at the end only for that. | |
// We ran into cases where I frame(s) would be resent but lost. | |
// T1 was stopped so we just waited and waited and waited instead of trying again. | |
// I added the following after each invoke_retransmission. | |
// This seems clearer than hiding the timer stuff inside of it. | |
// T3 is already stopped. | |
START_T1; | |
S->acknowledge_pending = 0; | |
} | |
else { | |
nr_error_recovery (S); | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
break; | |
case state_4_timer_recovery: | |
S->peer_receiver_busy = 0; | |
if (cr == cr_res && pf == 1) { | |
STOP_T1; | |
select_t1_value(S); | |
if (is_good_nr(S,nr)) { | |
SET_VA(nr); | |
if (S->vs == S->va) { | |
START_T3; | |
SET_RC(0); // My enhancement. See Erratum note in select_t1_value. | |
enter_new_state (S, state_3_connected, __func__, __LINE__); | |
} | |
else { | |
invoke_retransmission (S, nr); | |
// my addition. | |
// Erratum: We sent I frame(s) and want to timeout if no ack comes back. | |
// We also sent N(R) so no need for extra RR at the end only for that. | |
STOP_T3; | |
START_T1; | |
S->acknowledge_pending = 0; | |
} | |
} | |
else { | |
nr_error_recovery (S); | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
} | |
else { | |
if (cr == cr_cmd && pf == 1) { | |
int f = 1; | |
enquiry_response (S, frame_type_S_REJ, f); | |
} | |
if (is_good_nr(S,nr)) { | |
SET_VA(nr); | |
if (S->vs != S->va) { | |
// Observation: RR/RNR state 4 is identical but it doesn't have invoke_retransmission here. | |
invoke_retransmission (S, nr); | |
// my addition. | |
// Erratum: We sent I frame(s) and want to timeout if no ack comes back. | |
// We also sent N(R) so no need for extra RR at the end only for that. | |
STOP_T3; | |
START_T1; | |
S->acknowledge_pending = 0; | |
} | |
} | |
else { | |
nr_error_recovery (S); | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
} | |
break; | |
} | |
} /* end rej_frame */ | |
/*------------------------------------------------------------------------------ | |
* | |
* Name: srej_frame | |
* | |
* Purpose: Process SREJ Response. | |
* | |
* Inputs: S - Data Link State Machine. | |
* cr - Is this command or response? | |
* f - Final bit. When set, it is ack-ing up thru N(R)-1 | |
* nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S). | |
* info - Information field, used only for Multi-SREJ | |
* info_len - Information field length, bytes. | |
* | |
* Description: 4.3.2.4. Selective Reject (SREJ) Command and Response | |
* | |
* The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame | |
* numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are | |
* considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ | |
* frame does not indicate acknowledgement of I frames. | |
* | |
* Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) | |
* of the SREJ frame. | |
* | |
* A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set | |
* to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not | |
* transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so | |
* would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ | |
* frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in | |
* Section 4.5.4. | |
* | |
* I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of | |
* receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the | |
* retransmission of the specific I frame requested by the SREJ frame. | |
* | |
* Erratum: The section above always refers to SREJ "frames." There doesn't seem to be any clue about when | |
* command vs. response would be used. When we look in the flow charts, we see that we generate only | |
* responses but the code is there to process command and response slightly differently. | |
* | |
* Description: 4.4.4. Selective Reject (SREJ) Recovery | |
* | |
* The SREJ command/response initiates more-efficient error recovery by requesting the retransmission of a | |
* single I frame following the detection of a sequence error. This is an advancement over the earlier versions in | |
* which the requested I frame was retransmitted togther with all additional I frames subsequently transmitted and | |
* successfully received. | |
* | |
* When a TNC sends one or more SREJ commands, each with the P bit set to "0" or "1", or one or more SREJ | |
* responses, each with the F bit set to "0", and the "sent SREJ" conditions are not cleared when the TNC is ready | |
* to issue the next response frame with the F bit set to "1", the TNC sends a SREJ response with the F bit set to "1", | |
* with the same N(R) as the oldest unresolved SREJ frame. | |
* | |
* Because an I or S format frame with the F bit set to "1" can cause checkpoint retransmission, a TNC does not | |
* send SREJ frames until it receives at least one in-sequence I frame, or it perceives by timeout that the checkpoint | |
* retransmission will not be initiated at the remote TNC. | |
* | |
* With respect to each direction of transmission on the data link, one or more "sent SREJ" exception conditions | |
* from a TNC to another TNC may be established at a time. A "sent SREJ" exception condition is cleared when | |
* the requested I frame is received. | |
* | |
* The SREJ frame may be repeated when a TNC perceives by timeout that a requested I frame will not be | |
* received, because either the requested I frame or the SREJ frame was in error or lost. | |
* | |
* When appropriate, a TNC receiving one or more SREJ frames initiates retransmission of the individual I | |
* frames indicated by the N(R) contained in each SREJ frame. After having retransmitted the above frames, new | |
* I frames are transmitted later if they become available. | |
* | |
* When a TNC receives and acts on one or more SREJ commands, each with the P bit set to "0", or an SREJ | |
* command with the P bit set to "1", or one or more SREJ responses each with the F bit set to "0", it disables any | |
* action on the next SREJ response frame if that SREJ frame has the F bit set to "1" and has the same N(R) (i.e., | |
* the same value and the same numbering cycle) as a previously actioned SREJ frame, and if the resultant | |
* retransmission was made following the transmission of the P bit set to a "1". | |
* When the SREJ mechanism is used, the receiving station retains correctly-received I frames and delivers | |
* them to the higher layer in sequence number order. | |
* | |
* | |
* 6.4.4.2. Selective Reject (SREJ) | |
* | |
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current | |
* receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number | |
* equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously | |
* established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable | |
* and poll bit of the received frame are checked and acted upon, if necessary. | |
* | |
* This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can | |
* consume precious buffer space, especially if the user device has limited memory available and several active | |
* links are operational. | |
* | |
* | |
* 6.4.4.3. Selective Reject-Reject (SREJ/REJ) | |
* | |
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current | |
* receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. | |
* All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is | |
* missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The | |
* received state variable and poll bit of the received frame are checked and acted upon. If another frame error | |
* occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame | |
* and discards frames received after the second errored frame until the first errored frame is recovered. Then, a | |
* REJ is issued to recover the second errored frame and all subsequent discarded frames. | |
* | |
* X.25: States that SREJ is only response. I'm following that and it simplifies matters. | |
* | |
* X.25 2.4.6.6.1 & 2.4.6.6.2 make a distinction between F being 0 or 1 besides copying N(R) into V(A). | |
* They talk about sending a poll under some conditions. | |
* We don't do that here. It seems to work reliably so leave well enough alone. | |
* | |
*------------------------------------------------------------------------------*/ | |
static int resend_for_srej (ax25_dlsm_t *S, int nr, unsigned char *info, int info_len); | |
static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int f, int nr, unsigned char *info, int info_len) | |
{ | |
switch (S->state) { | |
case state_0_disconnected: | |
break; | |
case state_1_awaiting_connection: | |
case state_5_awaiting_v22_connection: | |
// Do nothing. | |
// Erratum: The original spec said stay in same state. (Seems correct.) | |
// 2006 revision shows state 5 transitioning into 1. I think that is wrong. | |
// probably a cut-n-paste from state 1 to 5 and that part not updated. | |
break; | |
case state_2_awaiting_release: | |
// Erratum: Flow chart says send DM(F=1) for "I, RR, RNR, REJ, SREJ commands" and P=1. | |
// It is wrong for two reasons. | |
// If SREJ was a command, the P flag has a different meaning than the other Supervisory commands. | |
// It means ack reception of frames up thru N(R)-1; it is not a poll to get a response. | |
// Based on X.25, I don't think SREJ can be a command. | |
// It should say, "I, RR, RNR, REJ commands" | |
// Erratum: We have a disagreement here between original and 2006 version. | |
// RR, RNR, REJ, SREJ responses would fall under "all other primitives." | |
// In the original, we simply ignore it and stay in state 2. | |
// The 2006 version, page 94, says go into "1 awaiting connection" state. | |
// That makes no sense to me. | |
break; | |
case state_3_connected: | |
S->peer_receiver_busy = 0; | |
// Erratum: Flow chart has "check need for response here." | |
// check_need_for_response() does the following: | |
// - for command & P=1, send RR or RNR. | |
// - for response & F=1, error A. | |
// SREJ can only be a response. We don't want to produce an error when F=1. | |
if (is_good_nr(S,nr)) { | |
if (f) { | |
SET_VA(nr); | |
} | |
STOP_T1; | |
START_T3; | |
select_t1_value(S); | |
int num_resent = resend_for_srej (S, nr, info, info_len); | |
if (num_resent) { | |
// my addition | |
// Erratum: We sent I frame(s) and want to timeout if no ack comes back. | |
// We also sent N(R), from V(R), so no need for extra RR at the end only for that. | |
// We would sometimes end up in a situation where T1 was stopped on | |
// both ends and everyone would wait for the other guy to timeout and do something. | |
// My solution was to Start T1 after every place we send an I frame if not already there. | |
STOP_T3; | |
START_T1; | |
S->acknowledge_pending = 0; | |
} | |
// keep same state. | |
} | |
else { | |
nr_error_recovery (S); | |
// Erratum? Flow chart shows state 1 but that would not be appropriate if modulo is 128. | |
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); | |
} | |
break; | |
case state_4_timer_recovery: | |
if (s_debug_timers) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("state 4 timer recovery, %s %d nr=%d, f=%d\n", __func__, __LINE__, nr, f); | |
} | |
S->peer_receiver_busy = 0; | |
// Erratum: Original Flow chart has "check need for response here." | |
// The 2006 version correctly removed it. | |
// check_need_for_response() does the following: | |
// - for command & P=1, send RR or RNR. | |
// - for response & F=1, error A. | |
// SREJ can only be a response. We don't want to produce an error when F=1. | |
// The flow chart splits into two paths for command/response with a lot of duplication. | |
// Command path has been omitted because SREJ can only be response. | |
STOP_T1; | |
select_t1_value(S); | |
if (is_good_nr(S,nr)) { | |
if (f) { // f=1 means ack up thru previous sequence. | |
// Erratum: 2006 version tests "P". Original has "F." | |
SET_VA(nr); | |
if (s_debug_timers) { | |
text_color_set(DW_COLOR_ERROR); | |
dw_printf ("state 4 timer recovery, %s %d set v(a)= %d\n", __func__, __LINE__, S->va); | |
} | |
} | |
if (S->vs == S->va) { // ACKs all caught up. Back to state 3. | |
// Erratum: I think this is unreachable. | |
// If the other side is asking for I frame with sequence X, it must have | |
// received X+1 or later. That means my V(S) must be X+2 or greater. | |
// So, I don't think we can ever have V(S) == V(A) here. | |
// If we were to remove the 'if' test and true part, case 4 would then | |
// be exactly the same as state 4. We need to rely on RR to get us | |
// back to state 3. | |
START_T3; | |
SET_RC(0); // My enhancement. See Erratum note in select_t1_value. | |
enter_new_state (S, state_3_connected, __func__, __LINE__); | |
// text_color_set(DW_COLOR_ERROR); | |
// dw_printf ("state 4 timer recovery, go to state 3 \n"); | |
} | |
else { | |
// Erratum: Difference between two AX.25 revisions. | |
#if 1 // This is from the original protocol spec. | |
// Resend I frame with N(S) equal to the N(R) in the SREJ. | |
//text_color_set(DW_COLOR_ERROR); | |
//dw_printf ("state 4 timer recovery, send requested frame(s) \n"); | |
int num_resent = resend_for_srej (S, nr, info, info_len); | |
if (num_resent) { | |