// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2016 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // /*------------------------------------------------------------------ * * Name: ax25_link * * Purpose: Data Link State Machine. * Establish connections and transfer data in the proper * order with retries. * * Description: * * Typical sequence for establishing a connection * initiated by a client application. Try version 2.2, * get refused, and fall back to trying version 2.0. * * * State Client App State Mach. Peer * ----- ---------- ----------- ---- * * 0 disc * Conn. Req ---> * SABME ---> * 5 await 2.2 * <--- FRMR or DM *note * SABM ---> * 1 await 2.0 * <--- UA * <--- CONN Ind. * 3 conn * * * Typical sequence when other end initiates connection. * * * State Client App State Mach. Peer * ----- ---------- ----------- ---- * * 0 disc * <--- SABME or SABM * UA ---> * <--- CONN Ind. * 3 conn * * * *note: * * By reading the v2.2 spec, I expected a 2.0 implementation to send * FRMR in response to SABME. In testing, I found that the KPC-3+ sent DM. * * After consulting the 2.0 spec, it says, that when in disconnected * mode, it will respond to any command other than SABM or UI frame with a DM * response with P/F set to 1. I can see where they might get that idea. * * I think the rule about sending FRMR for any unrecognized command should take precedence. * * * References: * * AX.25 Amateur Packet-Radio Link-Layer Protocol Version 2.0, October 1984 * * https://www.tapr.org/pub_ax25.html * * * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 * * https://www.tapr.org/pdf/AX25.2.2.pdf * * * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 * * http://www.ax25.net/AX25.2.2-Jul%2098-2.pdf * * I accidentally stumbled across this one when searching for some sort of errata * list for the original protocol specification. * * "This is a new version of the 1998 standard. It has had all figures * redone using Microsoft Visio. Errors in the SDL have been corrected." * * The SDL diagrams are dated 2006. I wish I knew about this version earlier * before doing most of the implementation. :-( * * * The functions here are based on the SDL diagrams but turned inside out. * It seems more intuitive to have a function for each type of input and then decide * what to do depending on the state. This also reduces duplicate code because we * often see the same flow chart segments, for the same input, appearing in multiple states. * * Erratum: The protocol spec has many places that appear to be errors or are ambiguous so I wasn't * sure what to do. These should be annotated with Errata comments so we can easily go * back and revisit them. * * X.25: The AX.25 protocol is based on, but does not necessarily adhere to, the X.25 protocol. * Consulting this might provide some insights where the AX.25 spec is not clear. * * http://www.itu.int/rec/T-REC-X.25-199610-I/en/ * * Current Status: This is still under development. * * Features partially tested: * * Connect to/from a KPC-3+ and send I frames in both directions. * v2.2 connect between two instances of direwolf. (Can't find another v2.2 for testing.) * Modulo 8 & 128 sequence numbers. * Recovery from simulated transmission errors using either REJ or SREJ. * XID frame for parameter negotiation. * Segments to allow data larger than max info part size. * * Implemented but not tested properly: * * Connecting thru digipeater(s). * Acting as a digipeater. * T3 timer. * Compatibility with additional types of TNC. * *------------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "ax25_pad2.h" #include "xid.h" #include "textcolor.h" #include "dlq.h" #include "tq.h" #include "ax25_link.h" #include "dtime_now.h" #include "server.h" #include "ptt.h" #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) // Debug switches for different types of information. static int s_debug_protocol_errors = 1; // Less serious Protocol errors. // Useful for debugging but unnecessarily alarming other times. static int s_debug_client_app = 0; // Interaction with client application. // dl_connect_request, dl_data_request, dl_data_indication, etc. static int s_debug_radio = 0; // Received frames and channel busy status. // lm_data_indication, lm_channel_busy static int s_debug_variables = 0; // Variables, state changes. static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending. static int s_debug_timers = 0; // Timer details. static int s_debug_link_handle = 0; // Create data link state machine or pick existing one, // based on my address, peer address, client app index, and radio channel. static int s_debug_stats = 0; // Statistics when connection is closed. static int s_debug_misc = 0; // Anything left over that might be interesting. /* * AX.25 data link state machine. * * One instance for each link identified by * [ client, channel, owncall, peercall ] */ enum dlsm_state_e { state_0_disconnected = 0, state_1_awaiting_connection = 1, state_2_awaiting_release = 2, state_3_connected = 3, state_4_timer_recovery = 4, state_5_awaiting_v22_connection = 5 }; typedef struct ax25_dlsm_s { int magic1; // Look out for bad pointer or corruption. #define MAGIC1 0x11592201 struct ax25_dlsm_s *next; // Next in linked list. int stream_id; // Unique number for each stream. // Internally we use a pointer but this is more user-friendly. int chan; // Radio channel being used. int client; // We have have multiple client applications, // each with their own links. We need to know // which client should receive the data or // notifications about state changes. char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN]; // Up to 10 addresses, same order as in frame. int num_addr; // Number of addresses. Should be in range 2 .. 10. #define OWNCALL AX25_SOURCE // addrs[OWNCALL] is owncall for this end of link. // Note that we are acting on behalf of // a client application so the APRS mycall // might not be relevent. #define PEERCALL AX25_DESTINATION // addrs[PEERCALL] is call for other end. double start_time; // Clock time when this was allocated. Used only for // debug output for timestamps relative to start. enum dlsm_state_e state; // Current state. int modulo; // 8 or 128. // Determines whether we have one or two control // octets. 128 allows a much larger window size. int srej_enabled; // Is other end capable of processing SREJ? (Am I allowed to send it?) // Starts out as false for v2.0 or true for v2.2. // Can be changed when exchanging capabilities. // Can be used only with modulo 128. int n1_paclen; // Maximum length of information field, in bytes. // Starts out as 256 but can be negotiated higher. // (Protocol Spec has this in bits. It is in bytes here.) // "PACLEN" in configuration file. int n2_retry; // Maximum number of retries permitted. // Typically 10. // "RETRY" parameter in configuration file. int k_maxframe; // Window size. Defaults to 4 (mod 8) or 32 (mod 128). // Maximum number of unacknowledged information // frames that can be outstanding. // "MAXFRAME" parameter in configuration file. int rc; // Retry count. Give up after n2. int vs; // 4.2.4.1. Send State Variable V(S) // The send state variable exists within the TNC and is never sent. // It contains the next sequential number to be assigned to the next // transmitted I frame. // This variable is updated with the transmission of each I frame. int va; // 4.2.4.5. Acknowledge State Variable V(A) // The acknowledge state variable exists within the TNC and is never sent. // It contains the sequence number of the last frame acknowledged by // its peer [V(A)-1 equals the N(S) of the last acknowledged I frame]. int vr; // 4.2.4.3. Receive State Variable V(R) // The receive state variable exists within the TNC. // It contains the sequence number of the next expected received I frame // This variable is updated upon the reception of an error-free I frame // whose send sequence number equals the present received state variable value. int layer_3_initiated; // SABM(E) was sent by request of Layer 3; i.e. DL-CONNECT request primitive. // I think this means that it is set only if we initiated the connection. // It would not be set if we are in the middle of accepting a connection from the other station. // Next 5 are called exception conditions. int peer_receiver_busy; // Remote station is busy and can't receive I frames. int reject_exception; // A REJ frame has been sent to the remote station. (boolean) int own_receiver_busy; // Layer 3 is busy and can't receive I frames. // We have no API to convey this information so it should always be 0. int acknowledge_pending; // I frames have been successfully received but not yet // acknowledged to the remote station. // Set when receiving the next expected I frame and P=0. // This gets cleared by sending any I, RR, RNR, REJ. // Cleared when sending SREJ with F=1. // Timing. float srt; // Smoothed roundtrip time in seconds. // This is used to dynamically adjust t1v. // Sometimes the flow chart has SAT instead of SRT. // I think that is a typographical error. float t1v; // How long to wait for an acknowlegement before resending. // Value used when starting timer T1, in seconds. // "FRACK" parameter in some implementations. // Typically it might be 3 seconds after frame has been // sent. Add more for each digipeater in path. // Here it is dynamically adjusted. // Set initial value for T1V. // Multiply FRACK by 2*m+1, where m is number of digipeaters. #define INIT_T1V_SRT \ S->t1v = g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1); \ S->srt = S->t1v / 2.0; int radio_channel_busy; // Either due to DCD or PTT. // Timer T1. // Timer values all use the usual unix time() value but double precision // so we can have fractions of seconds. // T1 is used for retries along with the retry counter, "rc." // When timer T1 is started, the value is obtained from t1v plus the current time. // Appropriate functions should be used rather than accessing the values directly. // This gets a little tricky because we need to pause the timers when the radio // channel is busy. Suppose we sent an I frame and set T1 to 4 seconds so we could // take corrective action if there is no response in a reasonable amount of time. // What if some other station has the channel tied up for 10 seconds? We don't want // T1 to timeout and start a retry sequence. The solution is to pause the timers while // the channel is busy. We don't want to get a timer expiry event when t1_exp is in // the past if it is currently paused. When it is un-paused, the expiration time is adjusted // for the amount of time it was paused. double t1_exp; // This is the time when T1 will expire or 0 if not running. double t1_paused_at; // Time when it was paused or 0 if not paused. float t1_remaining_when_last_stopped; // Number of seconds that were left on T1 when it was stopped. // This is used to fine tune t1v. // Set to negative initially to mean invalid, don't use in calculation. int t1_had_expired; // Set when T1 expires. // Cleared for start & stop. // Timer T3. // T3 is used to terminate connection after extended inactivity. // Similar to T1 except there is not mechanism to capture the remaining time when it is stopped // and it is not paused when the channel is busy. double t3_exp; // When it expires or 0 if not running. #define T3_DEFAULT 300.0 // Copied 5 minutes from Ax.25 for Linux. // http://www.linux-ax25.org/wiki/Run_time_configurable_parameters // D710A also defaults to 30*10 = 300 seconds. // Should it be user-configurable? // KPC-3+ and TM-D710A have "CHECK" command for this purpose. // Statistics for testing purposes. // Count how many frames of each type we received. // This is easy to do because they all come in thru lm_data_indication. // Counting outgoing could probably be done in lm_data_request so // it would not have to be scattered all over the place. TBD int count_recv_frame_type[frame_not_AX25+1]; int peak_rc_value; // Peak value of retry count (rc). // For sending data. cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet. // Linked list. cdata_t *txdata_by_ns[128]; // Data which has already been transmitted. // Indexed by N(S) in case it gets lost and needs to be sent again. // Cleared out when we get ACK for it. int magic3; // Look out for out of bounds for above. #define MAGIC3 0x03331301 cdata_t *rxdata_by_ns[128]; // "Receive buffer" // Data which has been received out of sequence. // Indexed by N(S). int magic2; // Look out for out of bounds for above. #define MAGIC2 0x02221201 // "Management Data Link" (MDL) state machine for XID exchange. enum mdl_state_e { mdl_state_0_ready=0, mdl_state_1_negotiating=1 } mdl_state; int mdl_rc; // Retry count, waiting to get XID response. // The spec has provision for a separate maximum, NM201, but we // just use the regular N2 same as other retries. double tm201_exp; // Timer. Similar to T1. // The spec mentions a separate timeout value but // we will just use the same as T1. double tm201_paused_at; // Time when it was paused or 0 if not paused. // Segment reassembler. cdata_t *ra_buff; // Reassembler buffer. NULL when in ready state. int ra_following; // Most recent number following to predict next expected. } ax25_dlsm_t; /* * List of current state machines for each link. * There is potential many client apps, each with multiple links * connected all at the same time. * * Everything coming thru here should be from a single thread. * The Data Link Queue should serialize all processing. * Therefore, we don't have to worry about critical regions. */ static ax25_dlsm_t *list_head = NULL; /* * Registered callsigns for incoming connections. */ #define RC_MAGIC 0x08291951 typedef struct reg_callsign_s { char callsign[AX25_MAX_ADDR_LEN]; int chan; int client; struct reg_callsign_s *next; int magic; } reg_callsign_t; static reg_callsign_t *reg_callsign_list = NULL; // Use these, rather than setting variables directly, to make debug out easier. #define SET_VS(n) { S->vs = (n); \ if (s_debug_variables) { \ text_color_set(DW_COLOR_DEBUG); \ dw_printf ("V(S) = %d at %s %d\n", S->vs, __func__, __LINE__); \ } \ } // If other guy acks reception of an I frame, we should never get an REJ or SREJ // asking for it again. When we update V(A), we should be able to remove the saved // transmitted data, and everything preceding it, from S->txdata_by_ns[]. #define SET_VA(n) { S->va = (n); \ if (s_debug_variables) { \ text_color_set(DW_COLOR_DEBUG); \ dw_printf ("V(A) = %d at %s %d\n", S->va, __func__, __LINE__); \ } \ int x = AX25MODULO(n-1, S->modulo, __FILE__, __func__, __LINE__); \ while (S->txdata_by_ns[x] != NULL) { \ cdata_delete (S->txdata_by_ns[x]); \ S->txdata_by_ns[x] = NULL; \ x = AX25MODULO(x-1, S->modulo, __FILE__, __func__, __LINE__); \ } \ } #define SET_VR(n) { S->vr = (n); \ if (s_debug_variables) { \ text_color_set(DW_COLOR_DEBUG); \ dw_printf ("V(R) = %d at %s %d\n", S->vr, __func__, __LINE__); \ } \ } //TODO: Make this a macro so we can simplify calls yet keep debug output if something goes wrong. static int AX25MODULO(int n, int m, const char *file, const char *func, int line) { if (m != 8 && m != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: %d modulo %d, %s, %s, %d\n", n, m, file, func, line); m = 8; } // Use masking, rather than % operator, so negative numbers are handled properly. return (n & (m-1)); } // Timer macros to provide debug output with location from where they are called. #define START_T1 start_t1(S, __func__, __LINE__) #define IS_T1_RUNNING is_t1_running(S, __func__, __LINE__) #define STOP_T1 stop_t1(S, __func__, __LINE__) #define PAUSE_T1 pause_t1(S, __func__, __LINE__) #define RESUME_T1 resume_t1(S, __func__, __LINE__) #define START_T3 start_t3(S, __func__, __LINE__) #define STOP_T3 stop_t3(S, __func__, __LINE__) #define START_TM201 start_tm201(S, __func__, __LINE__) #define STOP_TM201 stop_tm201(S, __func__, __LINE__) #define PAUSE_TM201 pause_tm201(S, __func__, __LINE__) #define RESUME_TM201 resume_tm201(S, __func__, __LINE__) static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len); static void lm_seize_confirm (ax25_dlsm_t *S); static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len); static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len); static int is_ns_in_window (ax25_dlsm_t *S, int ns); static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1); static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr); static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr); static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr); static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p); static void disc_frame (ax25_dlsm_t *S, int f); static void dm_frame (ax25_dlsm_t *S, int f); static void ua_frame (ax25_dlsm_t *S, int f); static void frmr_frame (ax25_dlsm_t *S); static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf); static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); static void t1_expiry (ax25_dlsm_t *S); static void t3_expiry (ax25_dlsm_t *S); static void tm201_expiry (ax25_dlsm_t *S); static void nr_error_recovery (ax25_dlsm_t *S); static void clear_exception_conditions (ax25_dlsm_t *S); static void transmit_enquiry (ax25_dlsm_t *S); static void select_t1_value (ax25_dlsm_t *S); static void establish_data_link (ax25_dlsm_t *S); static void set_version_2_0 (ax25_dlsm_t *S); static void set_version_2_2 (ax25_dlsm_t *S); static int is_good_nr (ax25_dlsm_t *S, int nr); static void i_frame_pop_off_queue (ax25_dlsm_t *S); static void discard_i_queue (ax25_dlsm_t *S); static void invoke_retransmission (ax25_dlsm_t *S, int nr_input); static void check_i_frame_ackd (ax25_dlsm_t *S, int nr); static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf); static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f); static void enter_new_state(ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line); static void mdl_negotiate_request (ax25_dlsm_t *S); static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param); static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); // Use macros above rather than calling these directly. static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line); static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); /* * Configuration settings from file or command line. */ static struct misc_config_s *g_misc_config_p; /*------------------------------------------------------------------- * * Name: ax25_link_init * * Purpose: Initialize the ax25_link module. * * Inputs: pconfig - misc. configuration from config file or command line. * Beacon stuff ended up here. * * Outputs: Remember required information for future use. That's all. * *--------------------------------------------------------------------*/ void ax25_link_init (struct misc_config_s *pconfig) { /* * Save parameters for later use. */ g_misc_config_p = pconfig; } /* end ax25_link_init */ /*------------------------------------------------------------------------------ * * Name: get_link_handle * * Purpose: Find existing (or possibly create) state machine for a given link. * It should be possible to have a large number of links active at the * same time. They are uniquely identified by * (owncall, peercall, client id, radio channel) * Note that we could have multiple client applications, all sharing one * TNC, on the same or different radio channels, completely unware of each other. * * Inputs: addrs - Owncall, peercall, and optional digipeaters. * For ease of passing this around, it is an array in the * same order as in the frame. * * num_addr - Number of addresses, 2 thru 10. * * chan - Radio channel number. * * client - Client app number. * We allow multiple concurrent applications with the * AGW network protocol. These are identified as 0, 1, ... * We don't know this for an incoming frame from the radio * so it is -1 at this point. At a later time will will * associate the stream with the right client. * * create - True if OK to create a new one. * Otherwise, return only one already existing. * * This should always be true for outgoing frames. * For incoming frames this would be true only for SABM(e) * with all digipeater fields marked as used. * * Here, we will also check to see if it is in our * registered callsign list. * * Returns: Handle for data link state machine. * NULL if not found and 'create' is false. * * Description: Try to find an existing entry matching owncall, peercall, channel, * and client. If not found create a new one. * *------------------------------------------------------------------------------*/ static int next_stream_id = 0; static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int create) { ax25_dlsm_t *p; if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle (%s>%s, chan=%d, client=%d, create=%d)\n", addrs[AX25_SOURCE], addrs[AX25_DESTINATION], chan, client, create); } // Look for existing. if (client == -1) { // from the radio. // address order is reversed for compare. for (p = list_head; p != NULL; p = p->next) { if (p->chan == chan && strcmp(addrs[AX25_DESTINATION], p->addrs[OWNCALL]) == 0 && strcmp(addrs[AX25_SOURCE], p->addrs[PEERCALL]) == 0) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle returns existing stream id %d for incoming.\n", p->stream_id); } return (p); } } } else { // from client app for (p = list_head; p != NULL; p = p->next) { if (p->chan == chan && p->client == client && strcmp(addrs[AX25_SOURCE], p->addrs[OWNCALL]) == 0 && strcmp(addrs[AX25_DESTINATION], p->addrs[PEERCALL]) == 0) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle returns existing stream id %d for outgoing.\n", p->stream_id); } return (p); } } } // Could not find existing. Should we create a new one? if ( ! create) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle: Search failed. Do not create new.\n"); } return (NULL); } // If it came from the radio, search for destination our registered callsign list. int incoming_for_client = -1; // which client app registered the callsign? if (client == -1) { // from the radio. reg_callsign_t *r, *found; found = NULL; for (r = reg_callsign_list; r != NULL && found == NULL; r = r->next) { if (strcmp(addrs[AX25_DESTINATION], r->callsign) == 0 && chan == r->chan) { found = r; incoming_for_client = r->client; } } if (found == NULL) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle: not for me. Ignore it.\n"); } return (NULL); } } // Create new data link state machine. p = calloc (sizeof(ax25_dlsm_t), 1); p->magic1 = MAGIC1; p->start_time = dtime_now(); p->stream_id = next_stream_id++; p->modulo = 8; p->chan = chan; p->num_addr = num_addr; // If it came in over the radio, we need to swap source/destination and reverse any digi path. if (incoming_for_client >= 0) { strlcpy (p->addrs[AX25_SOURCE], addrs[AX25_DESTINATION], sizeof(p->addrs[AX25_SOURCE])); strlcpy (p->addrs[AX25_DESTINATION], addrs[AX25_SOURCE], sizeof(p->addrs[AX25_DESTINATION])); int j = AX25_REPEATER_1; int k = num_addr - 1; while (k >= AX25_REPEATER_1) { strlcpy (p->addrs[j], addrs[k], sizeof(p->addrs[j])); j++; k--; } p->client = incoming_for_client; } else { memcpy (p->addrs, addrs, sizeof(p->addrs)); p->client = client; } p->state = state_0_disconnected; p->t1_remaining_when_last_stopped = -999; // Invalid, don't use. p->magic2 = MAGIC2; p->magic3 = MAGIC3; // No need for critical region because this should all be in one thread. p->next = list_head; list_head = p; if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle returns NEW stream id %d\n", p->stream_id); } return (p); } //################################################################################### //################################################################################### // // Data Link state machine for sending data in connected mode. // // Incoming: // // Requests from the client application. Set s_debug_client_app for debugging. // // dl_connect_request // dl_disconnect_request // dl_data_request - send connected data // dl_unit_data_request - not implemented. APRS & KISS bypass this // dl_flow_off - not implemented. Not in AGW API. // dl_flow_on - not implemented. Not in AGW API. // dl_register_callsign - Register callsigns(s) for incoming connection requests. // dl_unregister_callsign - Unregister callsigns(s) ... // dl_client_cleanup - Clean up after client which has disappeared. // // Stuff from the radio channel. Set s_debug_radio for debugging. // // lm_data_indication - Received frame. // lm_channel_busy - Change in PTT or DCD. // lm_seize_confirm - We have started to transmit. // // Timer expiration. Set s_debug_timers for debugging. // // dl_timer_expiry // // Outgoing: // // To the client application: // // dl_data_indication - received connected data. // // To the transmitter: // // lm_data_request - Queue up a frame for transmission. // // lm_seize_request - Start transmitter when possible. // lm_seize_confirm will be called when it has. // // // It is important that all requests come thru the data link queue so // everything is serialized. // We don't have to worry about being reentrant or critical regions. // Nothing here should consume a significant amount of time. // i.e. There should be no sleep delay or anything that would block waiting on someone else. // //################################################################################### //################################################################################### /*------------------------------------------------------------------------------ * * Name: dl_connect_request * * Purpose: Client app wants to connect to another station. * * Inputs: E - Event from the queue. * The caller will free it. * * Description: * *------------------------------------------------------------------------------*/ void dl_connect_request (dlq_item_t *E) { ax25_dlsm_t *S; int ok_to_create = 1; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_connect_request ()\n"); } text_color_set(DW_COLOR_INFO); dw_printf ("Attempting connect to %s ...\n", E->addrs[PEERCALL]); S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); switch (S->state) { case state_0_disconnected: INIT_T1V_SRT; if (g_misc_config_p->maxv22 == 0) { // Don't attempt v2.2. set_version_2_0 (S); establish_data_link (S); S->layer_3_initiated = 1; enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } else { // Try v2.2 first, then fall back if appropriate. set_version_2_2 (S); establish_data_link (S); S->layer_3_initiated = 1; enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: discard_i_queue(S); S->layer_3_initiated = 1; // Keep current state. break; case state_2_awaiting_release: // Keep current state. break; case state_3_connected: case state_4_timer_recovery: discard_i_queue(S); establish_data_link(S); S->layer_3_initiated = 1; // My enhancement. Original always sent SABM and went to state 1. // If we were using v2.2, why not reestablish with that? enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); break; } } /* end dl_connect_request */ /*------------------------------------------------------------------------------ * * Name: dl_disconnect_request * * Purpose: Client app wants to terminate connection with another station. * * Inputs: E - Event from the queue. * The caller will free it. * * Outputs: * * Description: * *------------------------------------------------------------------------------*/ void dl_disconnect_request (dlq_item_t *E) { ax25_dlsm_t *S; int ok_to_create = 1; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_disconnect_request ()\n"); } text_color_set(DW_COLOR_INFO); dw_printf ("Disconnect from %s ...\n", E->addrs[PEERCALL]); S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); switch (S->state) { case state_0_disconnected: // DL-DISCONNECT *confirm* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // TODO: FIXME requeue. Not sure what to do here. // If we put it back in the queue we will get it back again probably still in same state. // Need a way to defer it until the next state change. break; case state_2_awaiting_release: { // Erratum. Flow chart simply says "DM (expedited)." // This is the only place we have expedited. Is this correct? cmdres_t cr = cr_res; // DM can only be response. int p = 0; int nopid = 0; // PID applies only to I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // HI means expedited. STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } break; case state_3_connected: case state_4_timer_recovery: discard_i_queue (S); S->rc = 0; // I think this should be 1 but I'm not that worried about it. cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); STOP_T3; START_T1; enter_new_state (S, state_2_awaiting_release, __func__, __LINE__); break; } } /* end dl_disconnect_request */ /*------------------------------------------------------------------------------ * * Name: dl_data_request * * Purpose: Client app wants to send data to another station. * * Inputs: E - Event from the queue. * The caller will free it. * * Description: Append the transmit data block to the I frame queue for later processing. * * We also perform the segmentation handling here. * * C6.1 Segmenter State Machine * Only the following DL primitives will be candidates for modification by the segmented * state machine: * * DL-DATA Request. The user employs this primitive to provide information to be * transmitted using connection-oriented procedures; i.e., using I frames. The * segmenter state machine examines the quantity of data to be transmitted. If the * quantity of data to be transmitted is less than or equal to the data link parameter * N1, the segmenter state machine passes the primitive through transparently. If the * quantity of data to be transmitted exceeds the data link parameter N1, the * segmenter chops up the data into segments of length N1-2 octets. Each segment is * prepended with a two octet header. (See Figures 3.1 and 3.2.) The segments are * then turned over to the Data-link State Machine for transmission, using multiple DL * Data Request primitives. All segments are turned over immediately; therefore the * Data-link State Machine will transmit them consecutively on the data link. * * Erratum: Not sure how to interpret that. See example below for how it was implemented. * *------------------------------------------------------------------------------*/ static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata); void dl_data_request (dlq_item_t *E) { ax25_dlsm_t *S; int ok_to_create = 1; int nseg_to_follow; int orig_offset, remaining_len; S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_data_request (\""); ax25_safe_print (E->txdata->data, E->txdata->len, 1); dw_printf ("\") state=%d\n", S->state); } if (E->txdata->len <= S->n1_paclen) { data_request_good_size (S, E->txdata); E->txdata = NULL; // Now part of transmit I frame queue. return; } // More interesting case. // It is too large to fit in one frame so we segment it. // As an example, suppose we had 6 bytes of data "ABCDEF". // If N1 >= 6, it would be sent normally. // (addresses) // (control bytes) // PID, typically 0xF0 // 'A' - first byte of information field // 'B' // 'C' // 'D' // 'E' // 'F' // Now consider the case where it would not fit. // We would change the PID to 0x08 meaning a segment. // The information part is the segment identifier of this format: // // x xxxxxxx // | ---+--- // | | // | +- Number of additional segments to follow. // | // +- '1' means it is the first segment. // If N1 = 4, it would be split up like this: // (addresses) // (control bytes) // PID = 0x08 means segment // 0x82 - Start of info field. // MSB set indicates FIRST segment. // 2, in lower 7 bits, means 2 more segments to follow. // 0xF0 - original PID, typical value. // 'A' - For the FIRST segment, we have PID and N1-2 data bytes. // 'B' // (addresses) // (control bytes) // PID = 0x08 means segment // 0x01 - Means 1 more segment follows. // 'C' - For subsequent (not first) segments, we have up to N1-1 data bytes. // 'D' // 'E' // (addresses) // (control bytes) // PID = 0x08 // 0x00 - 0 means no more to follow. i.e. This is the last. // 'E' // Number of segments is ceiling( (datalen + 1 ) / (N1 - 1)) // we add one to datalen for the original PID. // We subtract one from N1 for the segment identifier header. #define DIVROUNDUP(a,b) (((a)+(b)-1) / (b)) // Compute number of segments. // We will decrement this before putting it in the frame so the first // will have one less than this number. nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1); if (nseg_to_follow < 2 || nseg_to_follow > 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, number of segments = %d\n", __LINE__, E->txdata->len, S->n1_paclen, nseg_to_follow); cdata_delete (E->txdata); E->txdata = NULL; return; } orig_offset = 0; remaining_len = E->txdata->len; // First segment. int seglen; struct { char header; // 0x80 + number of segments to follow. char original_pid; char segdata[AX25_N1_PACLEN_MAX]; } first_segment; cdata_t *new_txdata; nseg_to_follow--; first_segment.header = 0x80 | nseg_to_follow; first_segment.original_pid = E->txdata->pid; seglen = MIN(S->n1_paclen - 2, remaining_len); if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > sizeof (first_segment.segdata)) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", __LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); cdata_delete (E->txdata); E->txdata = NULL; return; } memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen); new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2); data_request_good_size (S, new_txdata); orig_offset += seglen; remaining_len -= seglen; // Subsequent segments. do { struct { char header; // Number of segments to follow. char segdata[AX25_N1_PACLEN_MAX]; } subsequent_segment; nseg_to_follow--; subsequent_segment.header = nseg_to_follow; seglen = MIN(S->n1_paclen - 1, remaining_len); if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > sizeof (subsequent_segment.segdata)) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", __LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); cdata_delete (E->txdata); E->txdata = NULL; return; } memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen); new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1); data_request_good_size (S, new_txdata); orig_offset += seglen; remaining_len -= seglen; } while (nseg_to_follow > 0); if (remaining_len != 0 || orig_offset != E->txdata->len) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n", __LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len); } cdata_delete (E->txdata); E->txdata = NULL; } /* end dl_data_request */ static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata) { switch (S->state) { case state_0_disconnected: case state_2_awaiting_release: /* * Discard it. */ cdata_delete (txdata); break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: /* * Erratum? * The flow chart shows "push on I frame queue" if layer 3 initiated * is NOT set. This seems backwards but I don't understand enough yet * to make a compelling argument that it is wrong. * Implemented as in flow chart. * TODO: Get better understanding of what'layer_3_initiated' means. */ if (S->layer_3_initiated) { cdata_delete (txdata); break; } // otherwise fall thru. case state_3_connected: case state_4_timer_recovery: /* * "push on I frame queue" * Append to the end would have been a better description because push implies a stack. */ if (S->i_frame_queue == NULL) { txdata->next = NULL; S->i_frame_queue = txdata; } else { cdata_t *plast = S->i_frame_queue; while (plast->next != NULL) { plast = plast->next; } txdata->next = NULL; plast->next = txdata; } break; } i_frame_pop_off_queue (S); } /* end data_request_good_size */ /*------------------------------------------------------------------------------ * * Name: dl_register_callsign * dl_unregister_callsign * * Purpose: Register / Unregister callsigns that we will accept connections for. * * Inputs: E - Event from the queue. * The caller will free it. * * Outputs: New item is pushed on the head of the reg_callsign_list. * We don't bother checking for duplicates so the most recent wins. * * Description: The data link state machine does not use MYCALL from the APRS configuration. * For outgoing frames, the client supplies the source callsign. * For incoming connection requests, we need to know what address(es) to respond to. * * Note that one client application can register multiple callsigns for * multiple channels. * Different clients can register different different addresses on the same channel. * *------------------------------------------------------------------------------*/ void dl_register_callsign (dlq_item_t *E) { reg_callsign_t *r; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_register_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); } r = calloc(sizeof(reg_callsign_t),1); strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign)); r->chan = E->chan; r->client = E->client; r->next = reg_callsign_list; r->magic = RC_MAGIC; reg_callsign_list = r; } /* end dl_register_callsign */ void dl_unregister_callsign (dlq_item_t *E) { reg_callsign_t *r, *prev; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_unregister_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); } prev = NULL; r = reg_callsign_list; while (r != NULL) { assert (r->magic == RC_MAGIC); if (strcmp(r->callsign,E->addrs[0]) == 0 && r->chan == E->chan && r->client == E->client) { if (r == reg_callsign_list) { reg_callsign_list = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = reg_callsign_list; } else { prev->next = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = prev->next; } } else { prev = r; r = r->next; } } } /* end dl_unregister_callsign */ /*------------------------------------------------------------------------------ * * Name: dl_client_cleanup * * Purpose: Client app has gone away. Clean up any data associated with it. * * Inputs: E - Event from the queue. * The caller will free it. * * Description: By client application we mean something that attached with the * AGW network protocol. * * Clean out anything related to the specfied client application. * This would include state machines and registered callsigns. * *------------------------------------------------------------------------------*/ void dl_client_cleanup (dlq_item_t *E) { ax25_dlsm_t *S; ax25_dlsm_t *dlprev; reg_callsign_t *r, *rcprev; if (s_debug_client_app) { text_color_set(DW_COLOR_INFO); dw_printf ("dl_client_cleanup (%d)\n", E->client); } dlprev = NULL; S = list_head; while (S != NULL) { // Look for corruption or double freeing. assert (S->magic1 == MAGIC1); assert (S->magic2 == MAGIC2); assert (S->magic3 == MAGIC3); if (S->client == E->client ) { int n; if (s_debug_stats) { text_color_set(DW_COLOR_INFO); dw_printf ("%d I frames received\n", S->count_recv_frame_type[frame_type_I]); dw_printf ("%d RR frames received\n", S->count_recv_frame_type[frame_type_S_RR]); dw_printf ("%d RNR frames received\n", S->count_recv_frame_type[frame_type_S_RNR]); dw_printf ("%d REJ frames received\n", S->count_recv_frame_type[frame_type_S_REJ]); dw_printf ("%d SREJ frames received\n", S->count_recv_frame_type[frame_type_S_SREJ]); dw_printf ("%d SABME frames received\n", S->count_recv_frame_type[frame_type_U_SABME]); dw_printf ("%d SABM frames received\n", S->count_recv_frame_type[frame_type_U_SABM]); dw_printf ("%d DISC frames received\n", S->count_recv_frame_type[frame_type_U_DISC]); dw_printf ("%d DM frames received\n", S->count_recv_frame_type[frame_type_U_DM]); dw_printf ("%d UA frames received\n", S->count_recv_frame_type[frame_type_U_UA]); dw_printf ("%d FRMR frames received\n", S->count_recv_frame_type[frame_type_U_FRMR]); dw_printf ("%d UI frames received\n", S->count_recv_frame_type[frame_type_U_UI]); dw_printf ("%d XID frames received\n", S->count_recv_frame_type[frame_type_U_XID]); dw_printf ("%d TEST frames received\n", S->count_recv_frame_type[frame_type_U_TEST]); dw_printf ("%d peak retry count\n", S->peak_rc_value); } if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_client_cleanup: remove %s>%s\n", S->addrs[AX25_SOURCE], S->addrs[AX25_DESTINATION]); } discard_i_queue (S); for (n = 0; n < 128; n++) { if (S->txdata_by_ns[n] != NULL) { cdata_delete (S->txdata_by_ns[n]); S->txdata_by_ns[n] = NULL; } } for (n = 0; n < 128; n++) { if (S->rxdata_by_ns[n] != NULL) { cdata_delete (S->rxdata_by_ns[n]); S->rxdata_by_ns[n] = NULL; } } if (S->ra_buff != NULL) { cdata_delete (S->ra_buff); S->ra_buff = NULL; } // Put into disconnected state. // If "connected" indicator (e.g. LED) was on, this will turn it off. enter_new_state (S, state_0_disconnected, __func__, __LINE__); // Take S out of list. S->magic1 = 0; S->magic2 = 0; S->magic3 = 0; if (S == list_head) { // first one on list. list_head = S->next; free (S); S = list_head; } else { // not the first one. dlprev->next = S->next; free (S); S = dlprev->next; } } else { dlprev = S; S = S->next; } } /* * If there are no link state machines (streams) remaining, there should be no txdata items still allocated. */ if (list_head == NULL) { cdata_check_leak(); } /* * Remove registered callsigns for this client. */ rcprev = NULL; r = reg_callsign_list; while (r != NULL) { assert (r->magic == RC_MAGIC); if (r->client == E->client) { if (r == reg_callsign_list) { reg_callsign_list = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = reg_callsign_list; } else { rcprev->next = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = rcprev->next; } } else { rcprev = r; r = r->next; } } } /* end dl_client_cleanup */ /*------------------------------------------------------------------------------ * * Name: dl_data_indication * * Purpose: send connected data to client application. * * Inputs: pid - Protocol ID. * * data - Pointer to array of bytes. * * len - Number of bytes in data. * * * Description: TODO: We perform reassembly of segments here if necessary. * *------------------------------------------------------------------------------*/ static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len) { // Now it gets more interesting. We need to combine segments before passing it along. // See example in dl_data_request. if (S->ra_buff == NULL) { // Ready state. if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); return; } else if (data[0] & 0x80) { // Ready state, First segment. S->ra_following = data[0] & 0x7f; int total = (S->ra_following + 1) * (len - 1) - 1; // len should be other side's N1 S->ra_buff = cdata_new(data[1], NULL, total); S->ra_buff->size = total; // max that we are expecting. S->ra_buff->len = len - 2; // how much accumulated so far. memcpy (S->ra_buff->data, data + 2, len - 2); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not first segment in ready state.\n", S->stream_id); } } else { // Reassembling data state if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not segment in reassembling state.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } else if (data[0] & 0x80) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: First segment in reassembling state.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } else if ((data[0] & 0x7f) != S->ra_following - 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments out of sequence.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } else { // Reassembling data state, Not first segment. S->ra_following = data[0] & 0x7f; if (S->ra_buff->len + len - 1 <= S->ra_buff->size) { memcpy (S->ra_buff->data + S->ra_buff->len, data + 1, len - 1); S->ra_buff->len += len - 1; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments exceed buffer space.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } if (S->ra_following == 0) { // Last one. server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], S->ra_buff->pid, S->ra_buff->data, S->ra_buff->len); cdata_delete(S->ra_buff); S->ra_buff = NULL; } } } } /* end dl_data_indication */ /*------------------------------------------------------------------------------ * * Name: lm_channel_busy * * Purpose: Change in DCD or PTT status for channel so we know when it is busy. * * Inputs: E - Event from the queue. * * E->chan - Radio channel number. * * E->activity - OCTYPE_PTT for my transmission start/end. * - OCTYPE_DCD if we hear someone else. * * E->status - 1 for active or 0 for quiet. * * Outputs: S->radio_channel_busy * * T1 & TM201 paused/resumed if running. * * Description: We need to pause the timers when the channel is busy. * * Signal lm_seize_confirm when we have started to transmit. * *------------------------------------------------------------------------------*/ static int dcd_status[MAX_CHANS]; static int ptt_status[MAX_CHANS]; void lm_channel_busy (dlq_item_t *E) { int busy; assert (E->chan >= 0 && E->chan < MAX_CHANS); assert (E->activity == OCTYPE_PTT || E->activity == OCTYPE_DCD); assert (E->status == 1 || E->status == 0); switch (E->activity) { case OCTYPE_DCD: if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_channel_busy: DCD chan %d = %d\n", E->chan, E->status); } dcd_status[E->chan] = E->status; break; case OCTYPE_PTT: if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_channel_busy: PTT chan %d = %d\n", E->chan, E->status); } ptt_status[E->chan] = E->status; break; default: break; } busy = dcd_status[E->chan] | ptt_status[E->chan]; /* * We know if the given radio channel is busy or not. * This must be applied to all data link state machines associated with that radio channel. */ ax25_dlsm_t *S; for (S = list_head; S != NULL; S = S->next) { if (E->chan == S->chan) { if (busy && ! S->radio_channel_busy) { S->radio_channel_busy = 1; PAUSE_T1; PAUSE_TM201; // Did channel become busy due to PTT turning on? if ( E->activity == OCTYPE_PTT && E->status == 1) { lm_seize_confirm (S); // C4.2. "This primitive indicates, to the Data-link State // machine, that the transmission opportunity has arrived." } } else if ( ! busy && S->radio_channel_busy) { S->radio_channel_busy = 0; RESUME_T1; RESUME_TM201; } } } } /* end lm_channel_busy */ /*------------------------------------------------------------------------------ * * Name: lm_seize_confirm * * Purpose: Notification the the channel is clear. * * Description: C4.2. This primitive indicates to the Data-link State Machine that * the transmission opportunity has arrived. * *------------------------------------------------------------------------------*/ static void lm_seize_confirm (ax25_dlsm_t *S) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_2_awaiting_release: case state_5_awaiting_v22_connection: break; case state_3_connected: case state_4_timer_recovery: if (S->acknowledge_pending) { S->acknowledge_pending = 0; enquiry_response (S, frame_not_AX25, 0); } // Implemenation difference: The flow chart for state 3 has LM-RELEASE Request here. // I don't think I need it because the transmitter will turn off // automatically once the queue is empty. // Erratum: The original spec had LM-SEIZE request here, for state 4, which didn't seem right. // The 2006 revision has LM-RELEASE Request so states 3 & 4 are the same. break; } } /* lm_seize_confirm */ /*------------------------------------------------------------------------------ * * Name: lm_data_indication * * Purpose: We received some sort of frame over the radio. * * Inputs: E - Event from the queue. * Caller is responsible for freeing it. * * Description: First determine if is of interest to me. Two cases: * * (1) We already have a link handle for (from-addr, to-addr, channel). * This could have been set up by an outgoing connect request. * * (2) It is addressed to one of the registered callsigns. This would * catch the case of incoming connect requests. The APRS MYCALL * is not involved at all. The attached client app might have * much different ideas about what the station is called or * aliases it might respond to. * *------------------------------------------------------------------------------*/ void lm_data_indication (dlq_item_t *E) { ax25_frame_type_t ftype; char desc[80]; cmdres_t cr; int pf; int nr; int ns; ax25_dlsm_t *S; int client_not_applicable = -1; int n; int any_unused_digi; if (E->pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, packet pointer is null. %s %s %d\n", __FILE__, __func__, __LINE__); return; } E->num_addr = ax25_get_num_addr(E->pp); // Digipeating is not done here so consider only those with no unused digipeater addresses. any_unused_digi = 0; for (n = AX25_REPEATER_1; n < E->num_addr; n++) { if ( ! ax25_get_h(E->pp, n)) { any_unused_digi = 1; } } if (any_unused_digi) { if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_indication (%d, %s>%s) - ignore due to unused digi address.\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); } return; } // Copy addresses from frame into event structure. for (n = 0; n < E->num_addr; n++) { ax25_get_addr_with_ssid (E->pp, n, E->addrs[n]); } if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_indication (%d, %s>%s)\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); } // Look for existing, or possibly create new, link state matching addresses and channel. // In most cases, we can ignore the frame if we don't have a corresponding // data link state machine. However, we might want to create a new one for SABM or SABME. // get_link_handle will check to see if the destination matches my address. // TODO: This won't work right because we don't know the modulo yet. // Maybe we should have a shorter form that only returns the frame type. // That is all we need at this point. ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); S = get_link_handle (E->addrs, E->num_addr, E->chan, client_not_applicable, (ftype == frame_type_U_SABM) | (ftype == frame_type_U_SABME)); if (S == NULL) { return; } /* * There is not a reliable way to tell if a frame, out of context, has modulo 8 or 128 * sequence numbers. This needs to be supplied from the data link state machine. * * We can't do this until we get the link handle. */ ax25_set_modulo (E->pp, S->modulo); /* * Now we need to use ax25_frame_type again because the previous results, for nr and ns, might be wrong. */ ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); // Gather statistics useful for testing. if (ftype >= 0 && ftype <= frame_not_AX25) { S->count_recv_frame_type[ftype]++; } switch (ftype) { case frame_type_I: if (cr != cr_cmd) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error S: %s must be COMMAND.\n", S->stream_id, desc); } break; case frame_type_S_RR: case frame_type_S_RNR: case frame_type_S_REJ: if (cr != cr_cmd && cr != cr_res) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); } break; case frame_type_U_SABME: case frame_type_U_SABM: case frame_type_U_DISC: if (cr != cr_cmd) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND.\n", S->stream_id, desc); } break; // Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both. // The underlying X.25 spec clearly says it is reponse only. Let's go with that. case frame_type_S_SREJ: case frame_type_U_DM: case frame_type_U_UA: case frame_type_U_FRMR: if (cr != cr_res) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be RESPONSE.\n", S->stream_id, desc); } break; case frame_type_U_XID: case frame_type_U_TEST: if (cr != cr_cmd && cr != cr_res) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); } break; case frame_type_U_UI: // Don't test at this point in case an APRS frame gets thru. // APRS doesn't specify what to put in the Source and Dest C bits. // In practice we see all 4 possble combinations. // I have an opinion about what would be "correct" (discussed elsewhere) // but in practice no one seems to care. break; case frame_type_U: case frame_not_AX25: // not expected. break; } switch (ftype) { case frame_type_I: // Information { int pid; unsigned char *info_ptr; int info_len; pid = ax25_get_pid (E->pp); info_len = ax25_get_info (E->pp, &info_ptr); i_frame (S, cr, pf, nr, ns, pid, (char *)info_ptr, info_len); } break; case frame_type_S_RR: // Receive Ready - System Ready To Receive rr_rnr_frame (S, 1, cr, pf, nr); break; case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full rr_rnr_frame (S, 0, cr, pf, nr); break; case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate rej_frame (S, cr, pf, nr); break; case frame_type_S_SREJ: // Selective Reject - Ask for single frame repeat srej_frame (S, cr, pf, nr); break; case frame_type_U_SABME: // Set Async Balanced Mode, Extended sabm_e_frame (S, 1, pf); break; case frame_type_U_SABM: // Set Async Balanced Mode sabm_e_frame (S, 0, pf); break; case frame_type_U_DISC: // Disconnect disc_frame (S, pf); break; case frame_type_U_DM: // Disconnect Mode dm_frame (S, pf); break; case frame_type_U_UA: // Unnumbered Acknowledge ua_frame (S, pf); break; case frame_type_U_FRMR: // Frame Reject frmr_frame (S); break; case frame_type_U_UI: // Unnumbered Information ui_frame (S, cr, pf); break; case frame_type_U_XID: // Exchange Identification { unsigned char *info_ptr; int info_len; info_len = ax25_get_info (E->pp, &info_ptr); xid_frame (S, cr, pf, info_ptr, info_len); } break; case frame_type_U_TEST: // Test { unsigned char *info_ptr; int info_len; info_len = ax25_get_info (E->pp, &info_ptr); test_frame (S, cr, pf, info_ptr, info_len); } break; case frame_type_U: // other Unnumbered, not used by AX.25. break; case frame_not_AX25: // Could not get control byte from frame. break; } i_frame_pop_off_queue (S); } /* end lm_data_indication */ /*------------------------------------------------------------------------------ * * Name: i_frame * * Purpose: Process I Frame. * * Inputs: S - Data Link State Machine. * cr - Command or Response. We have already issued an error if not command. * p - Poll bit. Assuming we checked earlier that it was a command. * The meaning is described below. * nr - N(R) from the frame. Next expected seq. for other end. * ns - N(S) from the frame. Seq. number of this incoming frame. * pid - protocol id. * info_ptr - pointer to information part of frame. * info_len - Number of bytes in information part of frame. * Should be in range of 0 thru n1_paclen. * * Description: * 6.4.2. Receiving I Frames * * The reception of I frames that contain zero-length information fields is reported to the next layer; no information * field will be transferred. * * 6.4.2.1. Not Busy * * If a TNC receives a valid I frame (one with a correct FCS and whose send sequence number equals the * receiver's receive state variable) and is not in the busy condition, it accepts the received I frame, increments its * receive state variable, and acts in one of the following manners: * * a) If it has an I frame to send, that I frame may be sent with the transmitted N(R) equal to its receive state * variable V(R) (thus acknowledging the received frame). Alternately, the TNC may send an RR frame with N(R) * equal to V(R), and then send the I frame. * * or b) If there are no outstanding I frames, the receiving TNC sends an RR frame with N(R) equal to V(R). The * receiving TNC may wait a small period of time before sending the RR frame to be sure additional I frames are * not being transmitted. * * 6.4.2.2. Busy * * If the TNC is in a busy condition, it ignores any received I frames without reporting this condition, other than * repeating the indication of the busy condition. * If a busy condition exists, the TNC receiving the busy condition indication polls the sending TNC periodically * until the busy condition disappears. * A TNC may poll the busy TNC periodically with RR or RNR frames with the P bit set to "1". * * 6.4.6. Receiving Acknowledgement * * Whenever an I or S frame is correctly received, even in a busy condition, the N(R) of the received frame is * checked to see if it includes an acknowledgement of outstanding sent I frames. The T1 timer is canceled if the * received frame actually acknowledges previously unacknowledged frames. If the T1 timer is canceled and there * are still some frames that have been sent that are not acknowledged, T1 is started again. If the T1 timer expires * before an acknowledgement is received, the TNC proceeds with the retransmission procedure outlined in Section * 6.4.11. * * * 6.2. Poll/Final (P/F) Bit Procedures * * The next response frame returned to an I frame with the P bit set to "1", received during the information * transfer state, is an RR, RNR or REJ response with the F bit set to "1". * * The next response frame returned to a S or I command frame with the P bit set to "1", received in the * disconnected state, is a DM response frame with the F bit set to "1". * *------------------------------------------------------------------------------*/ static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len) { switch (S->state) { case state_0_disconnected: // Logic from flow chart for "all other commands." if (cr == cr_cmd) { cmdres_t r = cr_res; // DM response with F taken from P. int f = p; int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // Ignore it. Keep same state. break; case state_2_awaiting_release: // Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." if (cr == cr_cmd && p == 1) { cmdres_t r = cr_res; // DM response with F = 1. int f = 1; int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_3_connected: case state_4_timer_recovery: // Look carefully. The original had two tiny differences between the two states. // In the 2006 version, these differences no longer exist. if (info_len >= 0 && info_len <= S->n1_paclen) { if (is_good_nr(S,nr)) { // Erratum? // I wonder if this difference is intentional or if only one place was // was modified after a cut-n-paste of the flow chart segment. // Erratum: Discrepancy between original and 2006 version. // Pattern noticed: Anytime we have "is_good_nr" which tests for V(A) <= N(R) <= V(S), // we should always call "check_i_frame_acked" or at least set V(A) from N(R). #if 0 // Erratum: original - states 3 & 4 differ here. if (S->state == state_3_connected) { // This sets "S->va = nr" and also does some timer stuff. check_i_frame_ackd (S,nr); } else { SET_VA(nr); } #else // 2006 version - states 3 & 4 same here. // This sets "S->va = nr" and also does some timer stuff. check_i_frame_ackd (S,nr); #endif if (S->own_receiver_busy) { // This should be unreachable because we currently don't have a way to set own_receiver_busy. // But we might the capability someday so implement this while we are here. if (p == 1) { cmdres_t cr = cr_res; // Erratum: The use of "F" in the flow chart implies that RNR is a response // in this case, but I'm not confident about that. The text says frame. int f = 1; int nr = S->vr; packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f); // I wonder if this difference is intentional or if only one place was // was modified after a cut-n-paste of the flow chart segment. #if 0 // Erratum: Original - state 4 has expedited. if (S->state == state_4_timer_recovery) { lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // "expedited" } else { lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } #else // 2006 version - states 3 & 4 the same. lm_data_request (S->chan, TQ_PRIO_1_LO, pp); #endif S->acknowledge_pending = 0; } } else { // N(R) out of expected range. i_frame_continued (S, p, ns, pid, info_ptr, info_len); } } else { nr_error_recovery (S); // my enhancement. See below. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } else { // Bad information length. // Wouldn't even get to CRC check if not octet aligned. text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error O: Information part length, %d, not in range of 0 thru %d.\n", S->stream_id, info_len, S->n1_paclen); establish_data_link (S); S->layer_3_initiated = 0; // The original spec always sent SABM and went to state 1. // I was thinking, why not use v2.2 instead of we were already connected with v2.2? // My version of establish_data_link combined the two original functions and // already uses SABME or SABM based on S->modulo. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; } } /* end i_frame */ /*------------------------------------------------------------------------------ * * Name: i_frame_continued * * Purpose: The I frame processing logic gets pretty complicated. * Some of it has been split out into a separate function to make * things more readable. * We have already done some error checking and processed N(R). * This is used for both states 3 & 4. * * Inputs: S - Data Link State Machine. We are in state 3. * p - Poll bit. * ns - N(S) from the frame. Seq. number of this incoming frame. * pid - protocol id. * info_ptr - pointer to information part of frame. * info_len - Number of bytes in information part of frame. Already verified. * * Description: * * 4.3.2.3. Reject (REJ) Command and Response * * The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence * number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the * retransmission of the N(R) frame. * Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the * proper reception of I frames up to the I frame that caused the reject condition to be initiated. * The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit * set to one. * * 4.3.2.4. Selective Reject (SREJ) Command and Response * * The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame * numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are * considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ * frame does not indicate acknowledgement of I frames. * * Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) * of the SREJ frame. * * A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set * to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not * transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so * would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ * frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in * * Section 4.5.4. * * I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of * receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the * retransmission of the specific I frame requested by the SREJ frame. * * * 6.4.4. Reception of Out-of-Sequence Frames * * 6.4.4.1. Implicit Reject (REJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number * equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not * been previously established. The received state variable and poll bit of the discarded frame is checked and acted * upon, if necessary. * This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode * requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This * mode is ineffective on systems with long round-trip delays and high data rates. * * 6.4.4.2. Selective Reject (SREJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number * equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously * established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable * and poll bit of the received frame are checked and acted upon, if necessary. * This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can * consume precious buffer space, especially if the user device has limited memory available and several active * links are operational. * * 6.4.4.3. Selective Reject-Reject (SREJ/REJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. * All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is * missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The * received state variable and poll bit of the received frame are checked and acted upon. If another frame error * occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame * and discards frames received after the second errored frame until the first errored frame is recovered. Then, a * REJ is issued to recover the second errored frame and all subsequent discarded frames. * *------------------------------------------------------------------------------*/ static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len) { if (ns == S->vr) { // The receive sequence number, N(S), is the same as what we were expecting, V(R). // Send it to the application and increment the next expected. // It is possible that this was resent and we tucked away others with the following // sequence numbers. If so, process them too. SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); S->reject_exception = 0; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); ax25_safe_print (info_ptr, info_len, 1); dw_printf ("\"\n"); } dl_data_indication (S, pid, info_ptr, info_len); if (S->rxdata_by_ns[ns] != NULL) { // There is a possibility that we might have another received frame stashed // away from 8 or 128 (modulo) frames back. Remove it so it doesn't accidently // show up at some future inopportune time. cdata_delete (S->rxdata_by_ns[ns]); S->rxdata_by_ns[ns] = NULL; } while (S->rxdata_by_ns[S->vr] != NULL) { // dl_data_indication - send connected data to client application. if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, data=\"", __func__, __LINE__, ns, S->vr); ax25_safe_print (S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len, 1); dw_printf ("\"\n"); } dl_data_indication (S, S->rxdata_by_ns[S->vr]->pid, S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len); // Don't keep around anymore after sending it to client app. cdata_delete (S->rxdata_by_ns[S->vr]); S->rxdata_by_ns[S->vr] = NULL; SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); } if (p) { // Mentioned in section 6.2. // The next response frame returned to an I frame with the P bit set to "1", received during the information // transfer state, is an RR, RNR or REJ response with the F bit set to "1". int f = 1; int nr = S->vr; // Next expected sequence number. cmdres_t cr = cr_res; // response with F set to 1. packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } else if ( ! S->acknowledge_pending) { S->acknowledge_pending = 1; // Probably want to set this before the LM-SEIZE Request // in case the LM-SEIZE Confirm gets processed before we // return from it. // Force start of transmission even if the transmit frame queue is empty. // Notify me, with lm_seize_confirm, when transmission has started. // When that event arrives, we check acknowledge_pending and send something // to be determined later. lm_seize_request (S->chan); } } else if (S->reject_exception) { // This is not the sequence we were expecting. // We previously sent REJ, asking for a resend so don't send another. // In this case, send RR only if the Poll bit is set. // Again, reference section 6.2. if (p) { int f = 1; int nr = S->vr; // Next expected sequence number. cmdres_t cr = cr_res; // response with F set to 1. packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } } else if ( ! S->srej_enabled) { // The received sequence number is not the expected one and we can't use SREJ. // The old v2.0 approach is to send and REJ with the number we are expecting. // This can be very inefficient. For example if we received 1,3,4,5,6 in one transmission, // we discard 3,4,5,6, and tell the other end to resend everything starting with 2. // At one time, I had some doubts about when to use command or response for REJ. // I now believe that reponse, as implied by setting F in the flow chart, is correct. int f = p; int nr = S->vr; // Next expected sequence number. cmdres_t cr = cr_res; // response with F copied from P in I frame. packet_t pp; S->reject_exception = 1; if (s_debug_retry) { text_color_set(DW_COLOR_ERROR); // make it more noticable. dw_printf ("sending REJ, at %s %d, SREJ not enabled case, V(R)=%d", __func__, __LINE__, S->vr); } pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_REJ, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } else { // Selective reject is enabled so we can use the more efficient method. // This is normally enabled for v2.2 but XID can be used to change that. // First we save the current frame so we can retrieve it later after getting the fill in. if (S->modulo != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: Should not be sending SREJ in basic (modulo 8) mode.\n"); } #if 1 // Erratum: AX.25 protocol spec did not handle SREJ very well. // Based on X.25 section 2.4.6.4. if (is_ns_in_window(S, ns)) { // X.25 2.4.6.4 (b) // v(R) < N(S) < V(R)+k so it is in the expected range. // Save it in the receive buffer. if (S->rxdata_by_ns[ns] != NULL) { cdata_delete (S->rxdata_by_ns[ns]); S->rxdata_by_ns[ns] = NULL; } S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); if (s_debug_misc) { dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); ax25_safe_print (info_ptr, info_len, 1); dw_printf ("\"\n"); } if (p == 1) { int f = 1; enquiry_response (S, frame_type_I, f); } else if (S->own_receiver_busy) { cmdres_t cr = cr_res; // send RNR response int f = 0; // we know p=0 here. int nr = S->vr; packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } else if (S->rxdata_by_ns[ AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) { // Ask for missing frames when we don't have N(S)-1 in the receive buffer. // I would think that we would want to set F when sending N(R) = V(R) but it says use F=0 here. // Don't understand why yet. // I like my method better. It does not generate duplicate requests for gaps in the same transmission. // This creates a cummulative list each time and would cause repeats to be sent more often than necessary. int resend[128]; int count = 0; int x; int allow_f1 = 0; // F=1 from X.25 2.4.6.4 b) 3) for (x = S->vr; x != ns; x = AX25MODULO(x + 1, S->modulo, __FILE__, __func__, __LINE__)) { if (S->rxdata_by_ns[x] == NULL) { resend[count++] = AX25MODULO(x, S->modulo, __FILE__, __func__, __LINE__); } } send_srej_frames (S, resend, count, allow_f1); } } else { // X.25 2.4.6.4 a) // N(S) is not in expected range. Discard it. Send response if P=1. if (p == 1) { int f = 1; enquiry_response (S, frame_type_I, f); } } #else // my earlier attempt before taking a close look at X.25 spec. // Keeping it around for a little while because I might want to // use earlier technique of sending only needed SREJ for any second // and later gaps in a single multiframe transmission. if (S->rxdata_by_ns[ns] != NULL) { cdata_delete (S->rxdata_by_ns[ns]); S->rxdata_by_ns[ns] = NULL; } S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); S->outstanding_srej[ns] = 0; // Don't care if it was previously set or not. // We have this one so there is no outstanding SREJ for it. if (s_debug_misc) { dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); ax25_safe_print (info_ptr, info_len, 1); dw_printf ("\"\n"); } if (selective_reject_exception(S) == 0) { // Erratum: This is vastly different than the SDL in the AX.25 protocol spec. // That would use SREJ if only one was missing and REJ instead. // Here we do not mix the them. // This agrees with the X.25 protocol spec that says use one or the other. Not both. // Suppose we had incoming I frames 0, 3, 7. // 0 was already processed and V(R)=1 meaning that is the next expected. // At this point we area processing N(S)=3. // In this case, we need to ask for a resend of 1 & 2. // More generally, the range of V(R) thru N(S)-1. int resend[128]; int count = 0; int i; int allow_f1 = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("%s:%d, zero exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, S->vr, ns); for (i = S->vr; i != ns; i = AX25MODULO(i+1, S->modulo, __FILE__, __func__, __LINE__)) { resend[count++] = i; } send_srej_frames (S, resend, count, allow_f1); } else { // Erratum: The SDL says ask for N(S) which is clearly wrong because that's what we just received. // Instead we want to ask for any missing frames up to but not including N(S). // Let's continue with the example above. I frames with N(S) of 0, 3, 7. // selective_reject_exception is non zero meaning there are outstanding requests to resend specified I frames. // V(R) is still 1 because 0 is the last one received with contiguous N(S) values. // 3 has been saved into S->rxdata_by_ns. // We now have N(S)=7. We want to ask for a resend of 4, 5, 6. // This can be achieved by searching S->rxdata_by_ns, starting with N(S)-1, and counting // how many empty slots we have before finding a saved frame. int count = 0; int first; text_color_set(DW_COLOR_ERROR); dw_printf ("%s:%d, %d srej exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, selective_reject_exception(S), S->vr, ns); first = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__); while (S->rxdata_by_ns[first] == NULL) { if (first == AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__)) { // Oops! Went too far. This I frame was already processed. text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR calulating what to put in SREJ, %s line %d\n", __func__, __LINE__); dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, count=%d\n", S->vr, ns, selective_reject_exception(S), first, count); int k; for (k=0; k<128; k++) { if (S->rxdata_by_ns[k] != NULL) { dw_printf ("rxdata_by_ns[%d] has data\n", k); } } break; } count++; first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__); } // Go beyond the slot where we already have an I frame. first = AX25MODULO(first + 1, S->modulo, __FILE__, __func__, __LINE__); // The count could be 0. e.g. We got 4 rather than 7 in this example. if (count > 0) { int resend[128]; int n; int allow_f1 = 1; for (n = 0; n < count; n++) { resend[n] = AX25MODULO(first + n, S->modulo, __FILE__, __func__, __LINE__);; } send_srej_frames (S, resend, count, allow_f1); } } /* end SREJ exception */ #endif // my earlier attempt. // Erratum: original has following but 2006 rev does not. // I think the 2006 version is correct. // SREJ does not always satisfy the need for ack. // There is a special case where F=1. We take care of that inside of send_srej_frames. #if 0 S->acknowledge_pending = 0; #endif } /* end srej_enabled */ } /* end i_frame_continued */ /*------------------------------------------------------------------------------ * * Name: is_ns_in_window * * Purpose: Is the N(S) value of the incoming I frame in the expected range? * * Inputs: ns - Sequence from I frame. * * Description: With selective reject, it is possible that we could receive a repeat of * an I frame with N(S) less than V(R). In this case, we just want to * discard it rather than getting upset and reestablishing the connection. * * The X.25 spec,section 2.4.6.4 (b) asks whether V(R) < N(S) < V(R)+k. * * The problem here is that it depends on the value of k for the other end. * X.25 says that both sides need to agree on a common value of k ahead of time. * We might have k=8 for our sending but the other side could have k=32 so * this test could fail. * * As a hack, we could use the value 63 here. If too small we would discard * I frames that are in the acceptable range because they would be >= V(R)+k. * On the other hand, if this value is too big, the range < V(R) would not be * large enough and we would accept frame we shouldn't. * As a practical matter, using a window size that large is pretty unlikely. * Maybe I could put a limit of 63, rather than 127 in the configuration. * *------------------------------------------------------------------------------*/ #define GENEROUS_K 63 static int is_ns_in_window (ax25_dlsm_t *S, int ns) { int adjusted_vr, adjusted_ns, adjusted_vrpk; int result; /* Shift all values relative to V(R) before comparing so we won't have wrap around. */ #define adjust_by_vr(x) (AX25MODULO((x) - S->vr, S->modulo, __FILE__, __func__, __LINE__)) adjusted_vr = adjust_by_vr(S->vr); // A clever compiler would know it is zero. adjusted_ns = adjust_by_vr(ns); adjusted_vrpk = adjust_by_vr(S->vr + GENEROUS_K); result = adjusted_vr < adjusted_ns && adjusted_ns < adjusted_vrpk; if (s_debug_retry) { text_color_set(DW_COLOR_DEBUG); dw_printf ("is_ns_in_window, V(R) %d < N(S) %d < V(R)+k %d, returns %d\n", S->vr, ns, S->vr + GENEROUS_K, result); } return (result); } /*------------------------------------------------------------------------------ * * Name: send_srej_frames * * Purpose: Ask for a resend of I frames with specified sequence numbers. * * Inputs: resend - Array of N(S) values for missing I frames. * count - Number of items in array. * allow_f1 - When true, set F=1 when asking for V(R). * X.25 section 2.4.6.4 b) 3) says F should be set to 0 * when receiving I frame out of sequence. * X.25 sections 2.4.6.11 & 2.3.5.2.2 say set F to 1 when * responding to command with P=1. (our enquiry_response function). * * Future? The X.25 protocol spec allows additional sequence numbers in one frame * by using the INFO part. * It would be easy but we haven't done that here to remain compatible with AX.25. * *------------------------------------------------------------------------------*/ static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1) { int f; // Set if we are ack-ing one before. int nr; cmdres_t cr = cr_res; // SREJ is always response. int i; packet_t pp; if (count <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, count=%d, %s line %d\n", count, __func__, __LINE__); return; } if (s_debug_retry) { text_color_set(DW_COLOR_INFO); dw_printf ("%s line %d\n", __func__, __LINE__); //dw_printf ("state=%d, count=%d, k=%d, V(R)=%d, SREJ exeception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S)); dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); dw_printf ("resend[]="); for (i = 0; i < count; i++) { dw_printf (" %d", resend[i]); } dw_printf ("\n"); dw_printf ("rxdata_by_ns[]="); for (i = 0; i < 128; i++) { if (S->rxdata_by_ns[i] != NULL) { dw_printf (" %d", i); } } dw_printf ("\n"); } // Something is wrong! We ask for more than the window size. if (count > S->k_maxframe) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR - Extreme number of SREJ, %s line %d\n", __func__, __LINE__); dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); dw_printf ("resend[]="); for (i = 0; i < count; i++) { dw_printf (" %d", resend[i]); } dw_printf ("\n"); dw_printf ("rxdata_by_ns[]="); for (i = 0; i < 128; i++) { if (S->rxdata_by_ns[i] != NULL) { dw_printf (" %d", i); } } dw_printf ("\n"); } for (i = 0; i < count; i++) { nr = resend[i]; f = allow_f1 && (nr == S->vr); // Possibly set if we are asking for the next after // the last one received in contiguous order. if (f) { // In this case the other end is being // informed of my V(R) so no additional // RR etc. is needed. S->acknowledge_pending = 0; } if (nr < 0 || nr >= S->modulo) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, nr=%d, modulo=%d, %s line %d\n", nr, S->modulo, __func__, __LINE__); nr = AX25MODULO(nr, S->modulo, __FILE__, __func__, __LINE__); } pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_SREJ, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } } /* end send_srej_frames */ /*------------------------------------------------------------------------------ * * Name: rr_rnr_frame * * Purpose: Process RR or RNR Frame. * Processing is the essentially the same so they are handled by a single function. * * Inputs: S - Data Link State Machine. * ready - True for RR, false for RNR * cr - Is this command or response? * pf - Poll/Final bit. * nr - N(R) from the frame. * * Description: 4.3.2.1. Receive Ready (RR) Command and Response * * Receive Ready accomplishes the following: * a) indicates that the sender of the RR is now able to receive more I frames; * b) acknowledges properly received I frames up to, and including N(R)-1;and * c) clears a previously-set busy condition created by an RNR command having been sent. * The status of the TNC at the other end of the link can be requested by sending an RR command frame with the * P-bit set to one. * * 4.3.2.2. Receive Not Ready (RNR) Command and Response * * Receive Not Ready indicates to the sender of I frames that the receiving TNC is temporarily busy and cannot * accept any more I frames. Frames up to N(R)-1 are acknowledged. Frames N(R) and above that may have been * transmitted are discarded and must be retransmitted when the busy condition clears. * The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. * The status of the TNC at the other end of the link is requested by sending an RNR command frame with the * P bit set to one. * *------------------------------------------------------------------------------*/ static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr) { // dw_printf ("rr_rnr_frame (ready=%d, cr=%d, pf=%d, nr=%d) state=%d\n", ready, cr, pf, nr, S->state); switch (S->state) { case state_0_disconnected: if (cr == cr_cmd) { cmdres_t r = cr_res; // DM response with F taken from P. int f = pf; int nopid = 0; // PID only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // do nothing. break; case state_2_awaiting_release: // Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." if (cr == cr_cmd && pf == 1) { cmdres_t r = cr_res; // DM response with F = 1. int f = 1; int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } // Erratum: We have a disagreement here between original and 2006 version. // RR, RNR, REJ, SREJ responses would fall under "all other primitives." // In the original, we simply ignore it and stay in state 2. // The 2006 version, page 94, says go into "1 awaiting connection" state. // That makes no sense to me. break; case state_3_connected: S->peer_receiver_busy = ! ready; // Erratum: the flow charts have unconditional check_need_for_response here. // I don't recall exactly why I added the extra test for command and P=1. // It might have been because we were reporting error A for response with F=1. // Other than avoiding that error message, this is functionally equivalent. if (cr == cr_cmd && pf) { check_need_for_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, cr, pf); } if (is_good_nr(S,nr)) { // dw_printf ("rr_rnr_frame (), line %d, state=%d, good nr=%d, calling check_i_frame_ackd\n", __LINE__, S->state, nr); check_i_frame_ackd (S, nr); // keep current state. } else { if (s_debug_retry) { text_color_set(DW_COLOR_DEBUG); dw_printf ("rr_rnr_frame (), line %d, state=%d, bad nr, calling nr_error_recovery\n", __LINE__, S->state); } nr_error_recovery (S); // My enhancement. Original always sent SABM and went to state 1. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; case state_4_timer_recovery: S->peer_receiver_busy = ! ready; if (cr == cr_res && pf == 1) { if (s_debug_retry) { text_color_set(DW_COLOR_DEBUG); dw_printf ("rr_rnr_frame (), Response, pf=1, line %d, state=%d, good nr, calling check_i_frame_ackd\n", __LINE__, S->state); } STOP_T1; select_t1_value(S); if (is_good_nr(S,nr)) { SET_VA(nr); if (S->vs == S->va) { // all caught up with ack from other guy. START_T3; S->rc =0; // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } else { invoke_retransmission (S, nr); // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. STOP_T3; START_T1; S->acknowledge_pending = 0; // end of my addition } } else { nr_error_recovery (S); // Erratum: Another case of my enhancement. // The flow charts go into state 1 after nr_error_recovery. // I use state 5 instead if we were oprating in extended (modulo 128) mode. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } else { if (cr == cr_cmd && pf == 1) { int f = 1; enquiry_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, f); } if (is_good_nr(S,nr)) { SET_VA(nr); // REJ state 4 is identical but has conditional invoke_retransmission here. } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } break; } } /* end rr_rnr_frame */ /*------------------------------------------------------------------------------ * * Name: rej_frame * * Purpose: Process REJ Frame. * * Inputs: S - Data Link State Machine. * cr - Is this command or response? * pf - Poll/Final bit. * nr - N(R) from the frame. * * Description: 4.3.2.2. Receive Not Ready (RNR) Command and Response * * ... The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. ... * * * 4.3.2.3. Reject (REJ) Command and Response * * The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence * number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the * retransmission of the N(R) frame. * Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the * proper reception of I frames up to the I frame that caused the reject condition to be initiated. * The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit * set to one. * * 4.4.3. Reject (REJ) Recovery * * The REJ frame requests a retransmission of I frames following the detection of a N(S) sequence error. Only * one outstanding "sent REJ" condition is allowed at a time. This condition is cleared when the requested I frame * has been received. * A TNC receiving the REJ command clears the condition by resending all outstanding I frames (up to the * window size), starting with the frame indicated in N(R) of the REJ frame. * * * 4.4.5.1. T1 Timer Recovery * * If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I * frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently * does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion * of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described * in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent * frame(s), or by the link being reset. * * 6.2. Poll/Final (P/F) Bit Procedures * * The response frame returned by a TNC depends on the previous command received, as described in the * following paragraphs. * ... * * The next response frame returned to an I frame with the P bit set to "1", received during the information5 * transfer state, is an RR, RNR or REJ response with the F bit set to "1". * * The next response frame returned to a supervisory command frame with the P bit set to "1", received during * the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1". * ... * * The P bit is used in conjunction with the timeout recovery condition discussed in Section 4.5.5. * When not used, the P/F bit is set to "0". * * 6.4.4.1. Implicit Reject (REJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number * equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not * been previously established. The received state variable and poll bit of the discarded frame is checked and acted * upon, if necessary. * This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode * requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This * mode is ineffective on systems with long round-trip delays and high data rates. * * 6.4.7. Receiving REJ * * After receiving a REJ frame, the transmitting TNC sets its send state variable to the same value as the REJ * frame's received sequence number in the control field. The TNC then retransmits any I frame(s) outstanding at * the next available opportunity in accordance with the following: * * a) If the TNC is not transmitting at the time and the channel is open, the TNC may begin retransmission of the * I frame(s) immediately. * b) If the TNC is operating on a full-duplex channel transmitting a UI or S frame when it receives a REJ frame, * it may finish sending the UI or S frame and then retransmit the I frame(s). * c) If the TNC is operating in a full-duplex channel transmitting another I frame when it receives a REJ frame, * it may abort the I frame it was sending and start retransmission of the requested I frames immediately. * d) The TNC may send just the one I frame outstanding, or it may send more than the one indicated if more I * frames followed the first unacknowledged frame, provided that the total to be sent does not exceed the flowcontrol * window (k frames). * If the TNC receives a REJ frame with the poll bit set, it responds with either an RR or RNR frame with the * final bit set before retransmitting the outstanding I frame(s). * *------------------------------------------------------------------------------*/ static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr) { switch (S->state) { case state_0_disconnected: // states 0 and 2 are very similar with one tiny little difference. if (cr == cr_cmd) { cmdres_t r = cr_res; // DM response with F taken from P. int f = pf; int nopid = 0; // PID is only for I and UI. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // Do nothing. break; case state_2_awaiting_release: if (cr == cr_cmd && pf == 1) { cmdres_t r = cr_res; // DM response with F = 1. int f = 1; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } // Erratum: We have a disagreement here between original and 2006 version. // RR, RNR, REJ, SREJ responses would fall under "all other primitives." // In the original, we simply ignore it and stay in state 2. // The 2006 version, page 94, says go into "1 awaiting connection" state. // That makes no sense to me. break; case state_3_connected: S->peer_receiver_busy = 0; // Receipt of the REJ "frame" (either command or response) causes us to // start resending I frames at the specified number. // I think there are 3 possibilities here: // Response is used when incoming I frame processing detects one is missing. // In this case, F is copied from the I frame P bit. I don't think we care here. // Command with P=1 is used during timeout recovery. // The rule is that we are supposed to send a response with F=1 for I, RR, RNR, or REJ with P=1. check_need_for_response (S, frame_type_S_REJ, cr, pf); if (is_good_nr(S,nr)) { SET_VA(nr); STOP_T1; STOP_T3; select_t1_value(S); invoke_retransmission (S, nr); // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. // We ran into cases where I frame(s) would be resent but lost. // T1 was stopped so we just waited and waited and waited instead of trying again. // I added the following after each invoke_retransmission. // This seems clearer than hiding the timer stuff inside of it. // T3 is already stopped. START_T1; S->acknowledge_pending = 0; } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; case state_4_timer_recovery: S->peer_receiver_busy = 0; if (cr == cr_res && pf == 1) { STOP_T1; select_t1_value(S); if (is_good_nr(S,nr)) { SET_VA(nr); if (S->vs == S->va) { START_T3; S->rc =0; // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } else { invoke_retransmission (S, nr); // my addition. // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. STOP_T3; START_T1; S->acknowledge_pending = 0; } } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } else { if (cr == cr_cmd && pf == 1) { int f = 1; enquiry_response (S, frame_type_S_REJ, f); } if (is_good_nr(S,nr)) { SET_VA(nr); if (S->vs != S->va) { // Observation: RR/RNR state 4 is identical but it doesn't have invoke_retransmission here. invoke_retransmission (S, nr); // my addition. // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. STOP_T3; START_T1; S->acknowledge_pending = 0; } } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } break; } } /* end rej_frame */ /*------------------------------------------------------------------------------ * * Name: srej_frame * * Purpose: Process SREJ Response. * * Inputs: S - Data Link State Machine. * cr - Is this command or response? * f - Final bit. When set, it is ack-ing up thru N(R)-1 * nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S). * * Description: 4.3.2.4. Selective Reject (SREJ) Command and Response * * The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame * numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are * considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ * frame does not indicate acknowledgement of I frames. * * Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) * of the SREJ frame. * * A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set * to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not * transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so * would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ * frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in * Section 4.5.4. * * I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of * receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the * retransmission of the specific I frame requested by the SREJ frame. * * Erratum: The section above always refers to SREJ "frames." There doesn't seem to be any clue about when * command vs. response would be used. When we look in the flow charts, we see that we generate only * responses but the code is there to process command and response slightly differently. * * Description: 4.4.4. Selective Reject (SREJ) Recovery * * The SREJ command/response initiates more-efficient error recovery by requesting the retransmission of a * single I frame following the detection of a sequence error. This is an advancement over the earlier versions in * which the requested I frame was retransmitted togther with all additional I frames subsequently transmitted and * successfully received. * * When a TNC sends one or more SREJ commands, each with the P bit set to "0" or "1", or one or more SREJ * responses, each with the F bit set to "0", and the "sent SREJ" conditions are not cleared when the TNC is ready * to issue the next response frame with the F bit set to "1", the TNC sends a SREJ response with the F bit set to "1", * with the same N(R) as the oldest unresolved SREJ frame. * * Because an I or S format frame with the F bit set to "1" can cause checkpoint retransmission, a TNC does not * send SREJ frames until it receives at least one in-sequence I frame, or it perceives by timeout that the checkpoint * retransmission will not be initiated at the remote TNC. * * With respect to each direction of transmission on the data link, one or more "sent SREJ" exception conditions * from a TNC to another TNC may be established at a time. A "sent SREJ" exception condition is cleared when * the requested I frame is received. * * The SREJ frame may be repeated when a TNC perceives by timeout that a requested I frame will not be * received, because either the requested I frame or the SREJ frame was in error or lost. * * When appropriate, a TNC receiving one or more SREJ frames initiates retransmission of the individual I * frames indicated by the N(R) contained in each SREJ frame. After having retransmitted the above frames, new * I frames are transmitted later if they become available. * * When a TNC receives and acts on one or more SREJ commands, each with the P bit set to "0", or an SREJ * command with the P bit set to "1", or one or more SREJ responses each with the F bit set to "0", it disables any * action on the next SREJ response frame if that SREJ frame has the F bit set to "1" and has the same N(R) (i.e., * the same value and the same numbering cycle) as a previously actioned SREJ frame, and if the resultant * retransmission was made following the transmission of the P bit set to a "1". * When the SREJ mechanism is used, the receiving station retains correctly-received I frames and delivers * them to the higher layer in sequence number order. * * * 6.4.4.2. Selective Reject (SREJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number * equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously * established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable * and poll bit of the received frame are checked and acted upon, if necessary. * * This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can * consume precious buffer space, especially if the user device has limited memory available and several active * links are operational. * * * 6.4.4.3. Selective Reject-Reject (SREJ/REJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. * All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is * missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The * received state variable and poll bit of the received frame are checked and acted upon. If another frame error * occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame * and discards frames received after the second errored frame until the first errored frame is recovered. Then, a * REJ is issued to recover the second errored frame and all subsequent discarded frames. * * X.25: States that SREJ is only response. I'm following that and it simplifies matters. * * X.25 2.4.6.6.1 & 2.4.6.6.2 make a distinction between F being 0 or 1 besides copying N(R) into V(A). * They talk about sending a poll under some conditions. * We don't do that here. It seems to work reliably so leave well enough alone. * *------------------------------------------------------------------------------*/ static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int f, int nr) { switch (S->state) { case state_0_disconnected: break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // Do nothing. // Erratum: The original spec said stay in same state. // 2006 revision shows state 5 transitioning into 1. I think that is wrong. // probably a cut-n-paste from state 1 to 5 and that part not updated. break; case state_2_awaiting_release: // Erratum: Flow chart says send DM(F=1) for "I, RR, RNR, REJ, SREJ commands" and P=1. // It is wrong for two reasons. // If SREJ was a command, the P flag has a different meaning than the other Supervisory commands. // It means ack reception of frames up thru N(R)-1; it is not a poll to get a response. // Based on X.25, I don't think SREJ can be a command. // It should say, "I, RR, RNR, REJ commands" // Erratum: We have a disagreement here between original and 2006 version. // RR, RNR, REJ, SREJ responses would fall under "all other primitives." // In the original, we simply ignore it and stay in state 2. // The 2006 version, page 94, says go into "1 awaiting connection" state. // That makes no sense to me. break; case state_3_connected: S->peer_receiver_busy = 0; // Erratum: Flow chart has "check need for response here." // check_need_for_response() does the following: // - for command & P=1, send RR or RNR. // - for response & F=1, error A. // SREJ can only be a response. We don't want to produce an error when F=1. if (is_good_nr(S,nr)) { if (f) { SET_VA(nr); } STOP_T1; START_T3; select_t1_value(S); // Resend I frame with N(S) equal to the N(R) in the SREJ. // Note: X.25 says info part can contain additional sequence numbers. // Here we stay with Ax.25 and don't consider the info part. cdata_t *txdata = S->txdata_by_ns[nr]; if (txdata != NULL) { cmdres_t cr = cr_cmd; int i_frame_ns = nr; int i_frame_nr = S->vr; int p = 0; packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); // dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. // We would sometimes end up in a situation where T1 was stopped on // both ends and everyone would wait for the other guy to timeout and do something. // My solution was to Start T1 after every place we send an I frame if not already there. STOP_T3; START_T1; S->acknowledge_pending = 0; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, nr); } // keep same state. } else { nr_error_recovery (S); // Erratum? Flow chart shows state 1 but that would not be appropriate if modulo is 128. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; case state_4_timer_recovery: S->peer_receiver_busy = 0; // Erratum: Original Flow chart has "check need for response here." // The 2006 version correctly removed it. // check_need_for_response() does the following: // - for command & P=1, send RR or RNR. // - for response & F=1, error A. // SREJ can only be a response. We don't want to produce an error when F=1. // The flow chart splits into two paths for command/response with a lot of duplication. // Command path has been omitted because SREJ can only be response. STOP_T1; select_t1_value(S); if (is_good_nr(S,nr)) { if (f) { // f=1 means ack up thru previous sequence. // Erratum: 2006 version tests "P". Original has "F." SET_VA(nr); } if (S->vs == S->va) { // ACKs all caught up. Back to state 3. START_T3; S->rc =0; // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } else { // Erratum: Difference between two AX.25 revisions. #if 1 // This is from the original protocol spec. // Resend I frame with N(S) equal to the N(R) in the SREJ. cdata_t *txdata = S->txdata_by_ns[nr]; if (txdata != NULL) { cmdres_t cr = cr_cmd; int i_frame_ns = nr; int i_frame_nr = S->vr; int p = 0; packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); // dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R), from V(R), so no need for extra RR at the end only for that. // We would sometimes end up in a situation where T1 was stopped on // both ends and everyone would wait for the other guy to timeout and do something. // My solution was to Start T1 after every place we send an I frame if not already there. STOP_T3; START_T1; S->acknowledge_pending = 0; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, nr); } #else // Erratum! This is from the 2006 revision. // We should resend only the single requested I frame. // I think there was a cut-n-paste from the REJ flow chart and this particular place did not get changed. invoke_retransmission(S); #endif } } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; } } /* end srej_frame */ /*------------------------------------------------------------------------------ * * Name: sabm_e_frame * * Purpose: Process SABM or SABME Frame. * * Inputs: S - Data Link State Machine. * * extended - True for SABME. False for SABM. * * p - Poll bit. TODO: What does it mean in this case? * * Description: This is a request, from the other end, to establish a connection. * * 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command * * The SABM command places two Terminal Node Comtrollers (TNC) in the asynchronous balanced mode * (modulo 8). This a balanced mode of operation in which both devices are treated as equals or peers. * * Information fields are not allowed in SABM commands. Any outstanding I frames left when the SABM * command is issued remain unacknowledged. * * The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if * possible. * * 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command * * The SABME command places two TNCs in the asynchronous balanced mode extended (modulo 128). This * is a balanced mode of operation in which both devices are treated as equals or peers. * Information fields are not allowed in SABME commands. Any outstanding I frames left when the SABME * command is issued remains unacknowledged. * * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. * * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. ** (see note below) * * * Note: The KPC-3+, which does not appear to support v2.2, responds with a DM. * The 2.0 spec, section 2.3.4.3.5, states, "While a DXE is in the disconnected mode, it will respond * to any command other than a SABM or UI frame with a DM response with the P/F bit set to 1." * I think it is a bug in the KPC but I can see how someone might implement it that way. * However, another place says FRMR is sent for any unrecognized frame type. That would seem to take priority. * *------------------------------------------------------------------------------*/ static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p) { switch (S->state) { case state_0_disconnected: // Flow chart has decision: "Able to establish?" // I think this means, are we willing to accept connection requests? // We are always willing to accept connections. // Of course, we wouldn't get this far if local callsigns were not "registered." if (extended) { set_version_2_2 (S); } else { set_version_2_0 (S); } cmdres_t res = cr_res; int f = p; // I don't understand the purpose of "P" in SABM/SABME // but we dutifully copy it into "F" for the UA response. int nopid = 0; // PID is only for I and UI. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); clear_exception_conditions (S); SET_VS(0); SET_VA(0); SET_VR(0); text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], extended ? "v2.2" : "v2.0"); // dl connect indication - inform the client app. int incoming = 1; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); INIT_T1V_SRT; START_T3; S->rc =0; // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); break; case state_1_awaiting_connection: // Don't combine with state 5. They are slightly different. if (extended) { // SABME - respond with DM, enter state 5. cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__); } else { // SABM - respond with UA. // Erratum! 2006 version shows SAMBE twice for state 1. // First one should be SABM in last page of Figure C4.2 // Original appears to be correct. cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // stay in state 1. } break; case state_5_awaiting_v22_connection: if (extended) { // SABME - respond with UA cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // stay in state 5 } else { // SABM, respond with UA, enter state 1 cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } break; case state_2_awaiting_release: // Erratum! Flow charts don't list SABME for state 2. // Probably just want to treat it the same as SABM here. { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited // stay in state 2. } break; case state_3_connected: case state_4_timer_recovery: { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // State 3 & 4 handling are the same except for this one difference. if (S->state == state_4_timer_recovery) { if (extended) { set_version_2_2 (S); } else { set_version_2_0 (S); } } clear_exception_conditions (S); if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error F: Data Link reset; i.e. SABM(e) received in state %d.\n", S->stream_id, S->state); } if (S->vs != S->va) { discard_i_queue (S); // dl connect indication int incoming = 1; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); } STOP_T1; START_T3; SET_VS(0); SET_VA(0); SET_VR(0); S->rc =0; // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } break; } } /* end sabm_e_frame */ /*------------------------------------------------------------------------------ * * Name: disc_frame * * Purpose: Process DISC command. * * Inputs: S - Data Link State Machine. * p - Poll bit. * * Description: 4.3.3.3. Disconnect (DISC) Command * * The DISC command terminates a link session between two stations. An information field is not permitted in * a DISC command frame. * * Prior to acting on the DISC frame, the receiving TNC confirms acceptance of the DISC by issuing a UA * response frame at its earliest opportunity. The TNC sending the DISC enters the disconnected state when it * receives the UA response. * * Any unacknowledged I frames left when this command is acted upon remain unacknowledged. * * * 6.3.4. Link Disconnection * * While in the information-transfer state, either TNC may indicate a request to disconnect the link by transmitting * a DISC command frame and starting timer T1. * * After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected * state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the * disconnected state. * * If a UA or DM response is not correctly received before T1 times out, the DISC frame is sent again and T1 is * restarted. If this happens N2 times, the TNC enters the disconnected state. * *------------------------------------------------------------------------------*/ static void disc_frame (ax25_dlsm_t *S, int p) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_5_awaiting_v22_connection: { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } // keep current state, 0, 1, or 5. break; case state_2_awaiting_release: { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited } // keep current state, 2. break; case state_3_connected: case state_4_timer_recovery: { discard_i_queue (S); cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; STOP_T3; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } break; } } /* end disc_frame */ /*------------------------------------------------------------------------------ * * Name: dm_frame * * Purpose: Process DM Response Frame. * * Inputs: S - Data Link State Machine. * f - Final bit. * * Description: 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command * * The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if * possible. * * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. * * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. * ( I think the KPC-3+ has a bug - it replys with DM - WB2OSZ ) * * 4.3.3.5. Disconnected Mode (DM) Response * * The disconnected mode response is sent whenever a TNC receives a frame other than a SABM(E) or UI * frame while in a disconnected mode. The disconnected mode response also indicates that the TNC cannot * accept a connection at the moment. The DM response does not have an information field. * Whenever a SABM(E) frame is received and it is determined that a connection is not possible, a DM frame is * sent. This indicates that the called station cannot accept a connection at that time. * While a TNC is in the disconnected mode, it responds to any command other than a SABM(E) or UI frame * with a DM response with the P/F bit set to "1". * * 4.3.3.6. Unnumbered Information (UI) Frame * * A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when * in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state. * * 6.3.1. AX.25 Link Connection Establishment * * If the distant TNC receives a SABM command and cannot enter the indicated state, it sends a DM frame. * When the originating TNC receives a DM response to its SABM(E) frame, it cancels its T1 timer and does * not enter the information-transfer state. * * 6.3.4. Link Disconnection * * After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected * state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the * disconnected state. * * 6.5. Resetting Procedure * * If a DM response is received, the TNC enters the disconnected state and stops timer T1. If timer T1 expires * before a UA or DM response frame is received, the SABM(E) is retransmitted and timer T1 restarted. If timer T1 * expires N2 times, the TNC enters the disconnected state. Any previously existing link conditions are cleared. * Other commands or responses received by the TNC before completion of the reset procedure are discarded. * * Erratum: The flow chart shows the same behavior for states 1 and 5. * For state 5, I think we should treat DM the same as FRMR. * *------------------------------------------------------------------------------*/ static void dm_frame (ax25_dlsm_t *S, int f) { switch (S->state) { case state_0_disconnected: // Do nothing. break; case state_1_awaiting_connection: if (f == 1) { discard_i_queue (S); // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { // keep current state. } break; case state_2_awaiting_release: if (f == 1) { // Erratum! Original flow chart, page 91, shows DL-CONNECT confirm. // It should clearly be DISconnect rather than Connect. // 2006 has DISCONNECT *Indication*. // Should it be indication or confirm? Not sure. // dl disconnect *confirm* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { // keep current state. } break; case state_3_connected: case state_4_timer_recovery: if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error E: DM received in state %d.\n", S->stream_id, S->state); } // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); discard_i_queue (S); STOP_T1; STOP_T3; enter_new_state (S, state_0_disconnected, __func__, __LINE__); break; case state_5_awaiting_v22_connection: #if 0 // Erratum: The flow chart says we should do this. // I'm not saying it is wrong. I just found it necessary to change this // to work around an apparent bug in a popular hardware TNC. if (f == 1) { discard_i_queue (S); // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { // keep current state. } #else // Erratum: This is not in original spec. It's copied from the FRMR case. // I was expecting FRMR to mean the other end did not understand v2.2. // Experimentation, with KPC-3+, revealed that we get DM instead. // One part of the the 2.0 spec sort of indicates this might be intentional. // But another part more clearly states it should be FRMR. // At first I thought it was an error in the protocol spec. // Later, I tend to believe it was just implemented wrong in the KPC-3+. if (f == 1) { text_color_set(DW_COLOR_INFO); dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); INIT_T1V_SRT; // Erratum: page 105. We are in state 5 so I think that means modulo is 128, // k is probably something > 7, and selective reject is enabled. // At the end of this we go to state 1. // It seems to me, that we really want to set version 2.0 in here so we have // compatible settings. set_version_2_0 (S); establish_data_link (S); S->layer_3_initiated = 1; enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } #endif break; } } /* end dm_frame */ /*------------------------------------------------------------------------------ * * Name: UA_frame * * Purpose: Process UA Response Frame. * * Inputs: S - Data Link State Machine. * f - Final bit. * * Description: 4.3.3.4. Unnumbered Acknowledge (UA) Response * * The UA response frame acknowledges the reception and acceptance of a SABM(E) or DISC command * frame. A received command is not actually processed until the UA response frame is sent. Information fields are * not permitted in a UA frame. * * 4.4.1. TNC Busy Condition * * When a TNC is temporarily unable to receive I frames (e.g., when receive buffers are full), it sends a Receive * Not Ready (RNR) frame. This informs the sending TNC that the receiving TNC cannot handle any more I * frames at the moment. This receiving TNC clears this condition by the sending a UA, RR, REJ or SABM(E) * command frame. * * 6.2. Poll/Final (P/F) Bit Procedures * * The response frame returned by a TNC depends on the previous command received, as described in the * following paragraphs. * The next response frame returned by the TNC to a SABM(E) or DISC command with the P bit set to "1" is a * UA or DM response with the F bit set to "1". * * 6.3.1. AX.25 Link Connection Establishment * * To connect to a distant TNC, the originating TNC sends a SABM command frame to the distant TNC and * starts its T1 timer. If the distant TNC exists and accepts the connect request, it responds with a UA response * frame and resets all of its internal state variables (V(S), V(A) and V(R)). Reception of the UA response frame by * the originating TNC causes it to cancel the T1 timer and set its internal state variables to "0". * * 6.5. Resetting Procedure * * A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of * a FRMR frame from a TNC using an older version of the protocol. * *------------------------------------------------------------------------------*/ static void ua_frame (ax25_dlsm_t *S, int f) { switch (S->state) { case state_0_disconnected: // Erratum: flow chart says errors C and D. Neither one really makes sense. // "Unexpected UA in states 3, 4, or 5." We are in state 0 here. // "UA received without F=1 when SABM or DISC was sent P=1." if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: if (f == 1) { if (S->layer_3_initiated) { text_color_set(DW_COLOR_INFO); // TODO: add via if apppropriate. dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0"); // There is a subtle difference here between connect confirm and indication. // connect *confirm* means "has been made" // The AGW API distinguishes between incoming (initiated by other station) and // outgoing (initiated by me) connections. int incoming = 0; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); } else if (S->vs != S->va) { #if 1 // Erratum: 2006 version has this. INIT_T1V_SRT; START_T3; // Erratum: Rather pointless because we immediately stop it below. // In the original flow chart, that is. // I think there is an error as explained below. // In my version this is still pointless because we start T3 later. #else // Erratum: Original version has this. // I think this could be harmful. // The client app might have been impatient and started sending // information already. I don't see why we would want to discard it. discard_i_queue (S); #endif text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0"); // Erratum: 2006 version says DL-CONNECT *confirm* but original has *indication*. // connect *indication* means "has been requested". // *confirm* seems right because we got a reply from the other side. int incoming = 0; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); } STOP_T1; #if 1 // My version. START_T3; #else // As shown in flow chart. STOP_T3; // Erratum? I think this is wrong. // We are about to enter state 3. When in state 3 either T1 or T3 should be // running. In state 3, we always see start one / stop the other pairs except where // we are about to enter a different state. // Since there is nothing outstanding where we expect a response, T1 would // not be started. #endif SET_VS(0); SET_VA(0); SET_VR(0); select_t1_value (S); // Erratum: mdl_negotiate_request does not appear in the SDL flow chart. // It is mentioned here: // // C5.3 Internal Operation of the Machine // // The Management Data link State Machine handles the negotiation/notification of // operational parameters. It uses a single command/response exchange to negotiate the // final values of negotiable parameters. // // The station initiating the AX.25 connection will send an XID command after it receives // the UA frame. If the other station is using a version of AX.25 earlier than 2.2, it will // respond with an FRMR of the XID command and the default version 2.0 parameters will // be used. If the other station is using version 2.2 or better, it will respond with an XID // response. if (S->state == state_5_awaiting_v22_connection) { mdl_negotiate_request (S); } S->rc =0; // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } else { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id); } // stay in current state, either 1 or 5. } break; case state_2_awaiting_release: // Erratum: 2006 version is missing yes/no labels on this test. // DL-ERROR Indication does not mention error D. if (f == 1) { text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id); } // stay in same state. } break; case state_3_connected: case state_4_timer_recovery: if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state); } establish_data_link (S); S->layer_3_initiated = 0; // Erratum? Flow chart goes to state 1. Wouldn't we want this to be state 5 if modulo is 128? enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); break; } } /* end ua_frame */ /*------------------------------------------------------------------------------ * * Name: frmr_frame * * Purpose: Process FRMR Response Frame. * * Inputs: S - Data Link State Machine. * * Description: 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command * ... * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. * * 4.3.3.9. FRMR Response Frame * * The FRMR response is removed from the standard for the following reasons: * a) UI frame transmission was not allowed during FRMR recovery; * b) During FRMR recovery, the link could not be reestablished by the station that sent the FRMR; * c) The above functions are better handled by simply resetting the link with a SABM(E) + UA exchange; * d) An implementation that receives and process FRMRs but does not transmit them is compatible with older * versions of the standard; and * e) SDL is simplified and removes the need for one state. * This version of AX.25 operates with previous versions of AX.25. It does not generate a FRMR Response * frame, but handles error conditions by resetting the link. * * 6.3.2. Parameter Negotiation Phase * * Parameter negotiation occurs at any time. It is accomplished by sending the XID command frame and * receiving the XID response frame. Implementations of AX.25 prior to version 2.2 respond to an XID command * frame with a FRMR response frame. The TNC receiving the FRMR uses a default set of parameters compatible * with previous versions of AX.25. * * 6.5. Resetting Procedure * * The link resetting procedure initializes both directions of data flow after a unrecoverable error has occurred. * This resetting procedure is used only in the information-transfer state of an AX.25 link. * A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of * a FRMR frame from a TNC using an older version of the protocol. * *------------------------------------------------------------------------------*/ static void frmr_frame (ax25_dlsm_t *S) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_2_awaiting_release: // Ignore it. Keep current state. break; case state_3_connected: case state_4_timer_recovery: if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error K: FRMR not expected in state %d.\n", S->stream_id, S->state); } set_version_2_0 (S); // Erratum: FRMR can only be sent by v2.0. // Need to force v2.0. Should be added to flow chart. establish_data_link (S); S->layer_3_initiated = 0; enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); break; case state_5_awaiting_v22_connection: text_color_set(DW_COLOR_INFO); dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); INIT_T1V_SRT; set_version_2_0 (S); // Erratum: Need to force v2.0. This is not in flow chart. establish_data_link (S); S->layer_3_initiated = 1; // Erratum? I don't understand the difference here. // State 1 clears it. State 5 sets it. Why not the same? enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); break; } // part of state machine for the XID negotiation. // I would not expect this to happen. // To get here: // We sent SABME. (not SABM) // Other side responded with UA so it understands v2.2. // We sent XID command which puts us int the negotiating state. // Presumably this is in response to the XID and not something else. // Anyhow, we will fall back to v2.0 parameters. switch (S->mdl_state) { case mdl_state_0_ready: break; case mdl_state_1_negotiating: set_version_2_0 (S); S->mdl_state = mdl_state_0_ready; break; } } /* end frmr_frame */ /*------------------------------------------------------------------------------ * * Name: ui_frame * * Purpose: Process XID frame for negotiating protocol parameters. * * Inputs: S - Data Link State Machine. * * cr - Is it command or response? * * pf - Poll/Final bit. * * Description: 4.3.3.6. Unnumbered Information (UI) Frame * * The Unnumbered Information frame contains PID and information fields and passes information along the * link outside the normal information controls. This allows information fields to be exchanged on the link, bypassing * flow control. * * Because these frames cannot be acknowledged, if one such frame is obliterated, it cannot be recovered. * A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when * in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state. * * Reality: The data link state machine was an add-on after APRS and client APIs were already done. * UI frames don't go thru here for normal operation. * The only reason we have this function is so that we can send a response to a UI command with P=1. * *------------------------------------------------------------------------------*/ static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf) { if (cr == cr_cmd && pf == 1) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_2_awaiting_release: case state_5_awaiting_v22_connection: { cmdres_t r = cr_res; // DM response with F taken from P. int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, pf, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_3_connected: case state_4_timer_recovery: enquiry_response (S, frame_type_U_UI, pf); break; } } } /* end ui_frame */ /*------------------------------------------------------------------------------ * * Name: xid_frame * * Purpose: Process XID frame for negotiating protocol parameters. * * Inputs: S - Data Link State Machine. * * cr - Is it command or response? * * pf - Poll/Final bit. * * Description: 4.3.3.7 Exchange Identification (XID) Frame * * The Exchange Identification frame causes the addressed station to identify itself, and * to provide its characteristics to the sending station. An information field is optional within * the XID frame. A station receiving an XID command returns an XID response unless a UA * response to a mode setting command is awaiting transmission, or a FRMR condition * exists. * * The XID frame complies with ISO 8885. Only those fields applicable to AX.25 are * described. All other fields are set to an appropriate value. This implementation is * compatible with any implementation which follows ISO 8885. Only the general-purpose * XID information field identifier is required in this version of AX.25. * * The information field consists of zero or more information elements. The information * elements start with a Format Identifier (FI) octet. The second octet is the Group Identifier * (GI). The third and forth octets form the Group Length (GL). The rest of the information * field contains parameter fields. * * The FI takes the value 82 hex for the general-purpose XID information. The GI takes * the value 80 hex for the parameter-negotiation identifier. The GL indicates the length of * the associated parameter field. This length is expressed as a two-octet binary number * representing the length of the associated parameter field in octets. The high-order bits of * length value are in the first of the two octets. A group length of zero indicates the lack of * an associated parameter field and that all parameters assume their default values. The GL * does not include its own length or the length of the GI. * * The parameter field contains a series of Parameter Identifier (PI), Parameter Length * (PL), and Parameter Value (PV) set structures, in that order. Each PI identifies a * parameter and is one octet in length. Each PL indicates the length of the associated PV in * octets, and is one octet in length. Each PV contains the parameter value and is PL octets * in length. The PL does not include its own length or the length of its associated PI. A PL * value of zero indicates that the associated PV is absent; the parameter assumes the * default value. A PI/PL/PV set may be omitted if it is not required to convey information, or * if present values for the parameter are to be used. The PI/PL/PV fields are placed into the * information field of the XID frame in ascending order. There is only one entry for each * PI/PL/PV field used. A parameter field containing an unrecognized PI is ignored. An * omitted parameter field assumes the currently negotiated value. * *------------------------------------------------------------------------------*/ static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len) { struct xid_param_s param; char desc[120]; int ok; unsigned char xinfo[40]; int xlen; cmdres_t res = cr_res; int f = 1; int nopid = 0; packet_t pp; switch (S->mdl_state) { case mdl_state_0_ready: if (cr == cr_cmd) { if (pf == 1) { // Take parameters sent by other station. // Generally we take minimum of what he wants and what I can do. // Adjust my working configuration and send it back. ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc)); if (ok) { negotiation_response (S, ¶m); xlen = xid_encode (¶m, xinfo); pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_XID, f, nopid, xinfo, xlen); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-A: XID command without P=1.\n", S->stream_id); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-B: Unexpected XID response.\n", S->stream_id); } break; case mdl_state_1_negotiating: if (cr == cr_res) { if (pf == 1) { // Got expected response. Copy into my working parameters. ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc)); if (ok) { complete_negotiation (S, ¶m); } S->mdl_state = mdl_state_0_ready; STOP_TM201; //#define TEST_TEST 1 #if TEST_TEST // Send TEST command to see how it responds. // We currently have no Client API for sending this or reporting result. { char info[80] = "The quick brown fox jumps over the lazy dog."; cmdres_t cmd = cr_cmd; int p = 0; int nopid = 0; packet_t pp; pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_TEST, p, nopid, (unsigned char *)info, (int)strlen(info)); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } #endif } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-D: XID response without F=1.\n", S->stream_id); } } else { // Not expecting to receive a command when I sent one. // Flow chart says requeue but I just drop it. // The other end can retry and maybe I will be back to ready state by then. } break; } } /* end xid_frame */ /*------------------------------------------------------------------------------ * * Name: test_frame * * Purpose: Process TEST command for checking link. * * Inputs: S - Data Link State Machine. * * cr - Is it command or response? * * pf - Poll/Final bit. * * Description: 4.3.3.8. Test (TEST) Frame * * The Test command causes the addressed station to respond with the TEST response at the first respond * opportunity; this performs a basic test of the data-link control. An information field is optional with the TEST * command. If present, the received information field is returned, if possible, by the addressed station, with the * TEST response. The TEST command has no effect on the mode or sequence variables maintained by the station. * * A FRMR condition may be established if the received TEST command information field exceeds the maximum * defined storage capability of the station. If a FRMR response is not returned for this condition, a TEST response * without an information field is returned. * * The station considers the data-link layer test terminated on receipt of the TEST response, or when a time-out * period has expired. The results of the TEST command/response exchange are made available for interrogation * by a higher layer. * * Erratum: TEST frame is not mentioned in the SDL flow charts. * Don't know how P/F is supposed to be used. * Here, the response sends back what was received in the command. * *------------------------------------------------------------------------------*/ static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len) { cmdres_t res = cr_res; int f = pf; int nopid = 0; packet_t pp; if (cr == cr_cmd) { pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_TEST, f, nopid, info_ptr, info_len); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } } /* end test_frame */ /*------------------------------------------------------------------------------ * * Name: dl_timer_expiry * * Purpose: Some timer expired. Figure out which one and act accordingly. * * Inputs: none. * *------------------------------------------------------------------------------*/ void dl_timer_expiry (void) { ax25_dlsm_t *p; double now = dtime_now(); // Examine all of the data link state machines. // Process only those where timer: // - is running. // - is not paused. // - expiration time has arrived or passed. for (p = list_head; p != NULL; p = p->next) { if (p->t1_exp != 0 && p->t1_paused_at == 0 && p->t1_exp <= now) { p->t1_exp = 0; p->t1_paused_at = 0; p->t1_had_expired = 1; t1_expiry (p); } } for (p = list_head; p != NULL; p = p->next) { if (p->t3_exp != 0 && p->t3_exp <= now) { p->t3_exp = 0; t3_expiry (p); } } for (p = list_head; p != NULL; p = p->next) { if (p->tm201_exp != 0 && p->tm201_paused_at == 0 && p->tm201_exp <= now) { p->tm201_exp = 0; p->tm201_paused_at = 0; tm201_expiry (p); } } } /* end dl_timer_expiry */ /*------------------------------------------------------------------------------ * * Name: t1_expiry * * Purpose: Handle T1 timer expiration for outstanding I frame or P-bit. * * Inputs: S - Data Link State Machine. * * Description: 4.4.5.1. T1 Timer Recovery * * If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I * frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently * does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion * of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described * in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent * frame(s), or by the link being reset. * * 6.7.1.1. Acknowledgment Timer T1 * * T1, the Acknowledgement Timer, ensures that a TNC does not wait indefinitely for a response to a frame it * sends. This timer cannot be expressed in absolute time; the time required to send frames varies greatly with the * signaling rate used at Layer 1. T1 should take at least twice the amount of time it would take to send maximum * length frame to the distant TNC and get the proper response frame back from the distant TNC. This allows time * for the distant TNC to do some processing before responding. * If Layer 2 repeaters are used, the value of T1 should be adjusted according to the number of repeaters through * which the frame is being transferred. * *------------------------------------------------------------------------------*/ // Make timer start, stop, expiry a different color to stand out. #define DW_COLOR_DEBUG_TIMER DW_COLOR_ERROR static void t1_expiry (ax25_dlsm_t *S) { if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("t1_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc); } switch (S->state) { case state_0_disconnected: // Ignore it. break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // MAXV22 hack. // If we already sent the maximum number of SABME, fall back to v2.0 SABM. if (S->state == state_5_awaiting_v22_connection && S->rc == g_misc_config_p->maxv22) { set_version_2_0 (S); enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } if (S->rc == S->n2_retry) { discard_i_queue(S); text_color_set(DW_COLOR_INFO); dw_printf ("Failed to connect to %s after %d tries.\n", S->addrs[PEERCALL], S->n2_retry); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1); enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; S->rc++; if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // Keep statistics. pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->state == state_5_awaiting_v22_connection) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); select_t1_value(S); START_T1; // Keep same state. } break; case state_2_awaiting_release: if (S->rc == S->n2_retry) { text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; S->rc++; if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); select_t1_value(S); START_T1; // stay in same state } break; case state_3_connected: S->rc = 1; transmit_enquiry (S); enter_new_state (S, state_4_timer_recovery, __func__, __LINE__); break; case state_4_timer_recovery: if (S->rc == S->n2_retry) { // Erratum: 2006 version, page 103, is missing yes/no labels on decision blocks. if (S->va != S->vr) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error I: N2 timeouts: unacknowledged data.\n", S->stream_id); } } else if (S->peer_receiver_busy) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error U: N2 timeouts: extended peer busy condition.\n", S->stream_id); } } else { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error T: N2 timeouts: no response to enquiry.\n", S->stream_id); } } // Erratum: Flow chart says DL-DISCONNECT "request" in both original and 2006 revision. // That is clearly wrong because a "request" would come FROM the higher level protocol/client app. // I think it should be "indication" rather than "confirm" because the peer condition is unknown. // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s due to timeouts.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1); discard_i_queue (S); cmdres_t cr = cr_res; // DM can only be response. int f = 0; // Erratum: Assuming F=0 because it is not response to P=1 int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { S->rc++; if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // gather statistics. transmit_enquiry (S); // Keep same state. } break; } } /* end t1_expiry */ /*------------------------------------------------------------------------------ * * Name: t3_expiry * * Purpose: Handle T3 timer expiration. * * Inputs: S - Data Link State Machine. * * Description: TODO: still don't understand this. * * 4.4.5.2. Timer T3 Recovery * * Timer T3 ensures that the link is still functional during periods of low information transfer. When T1 is not * running (no outstanding I frames), T3 periodically causes the TNC to poll the other TNC of a link. When T3 * times out, an RR or RNR frame is transmitted as a command with the P bit set, and then T1 is started. When a * response to this command is received, T1 is stopped and T3 is started. If T1 expires before a response is * received, then the waiting acknowledgement procedure (Section 6.4.11) is executed. * * 6.7.1.3. Inactive Link Timer T3 * * T3, the Inactive Link Timer, maintains link integrity whenever T1 is not running. It is recommended that * whenever there are no outstanding unacknowledged I frames or P-bit frames (during the information-transfer * state), an RR or RNR frame with the P bit set to "1" be sent every T3 time units to query the status of the other * TNC. The period of T3 is locally defined, and depends greatly on Layer 1 operation. T3 should be greater than * T1; it may be very large on channels of high integrity. * *------------------------------------------------------------------------------*/ static void t3_expiry (ax25_dlsm_t *S) { if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("t3_expiry (), [now=%.3f]\n", now - S->start_time); } switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_5_awaiting_v22_connection: case state_2_awaiting_release: case state_4_timer_recovery: break; case state_3_connected: // Erratum: Original sets RC to 0, 2006 revision sets RC to 1 which makes more sense. S->rc = 1; transmit_enquiry (S); enter_new_state (S, state_4_timer_recovery, __func__, __LINE__); break; } } /* end t3_expiry */ /*------------------------------------------------------------------------------ * * Name: tm201_expiry * * Purpose: Handle TM201 timer expiration. * * Inputs: S - Data Link State Machine. * * Description: This is used when waiting for a response to an XID command. * *------------------------------------------------------------------------------*/ static void tm201_expiry (ax25_dlsm_t *S) { struct xid_param_s param; unsigned char xinfo[40]; int xlen; cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("tm201_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc); } switch (S->mdl_state) { case mdl_state_0_ready: // Timer shouldn't be running when in this state. break; case mdl_state_1_negotiating: S->mdl_rc++; if (S->mdl_rc > S->n2_retry) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-C: Management retry limit exceeded.\n", S->stream_id); S->mdl_state = mdl_state_0_ready; } else { // No response. Ask again. initiate_negotiation (S, ¶m); xlen = xid_encode (¶m, xinfo); pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); START_TM201; } break; } } /* end tm201_expiry */ //################################################################################### //################################################################################### // // Subroutines from protocol spec, pages 106 - 109 // //################################################################################### //################################################################################### // FIXME: continue review here. /*------------------------------------------------------------------------------ * * Name: nr_error_recovery * * Purpose: Try to recover after receiving an expected N(r) value. * *------------------------------------------------------------------------------*/ static void nr_error_recovery (ax25_dlsm_t *S) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error J: N(r) sequence error.\n", S->stream_id); } establish_data_link (S); S->layer_3_initiated = 0; } /* end nr_error_recovery */ /*------------------------------------------------------------------------------ * * Name: establish_data_link * (Combined with "establish extended data link") * * Purpose: Send SABM or SABME to other station. * * Inputs: S-> * addrs destination, source, and optional digi addresses. * num_addr Number of addresses. Should be 2 .. 10. * modulo Determines if we send SABME or SABM. * * Description: Original spec had two different functions that differed * only by sending SABM or SABME. Here they are combined into one. * *------------------------------------------------------------------------------*/ static void establish_data_link (ax25_dlsm_t *S) { cmdres_t cmd = cr_cmd; int p = 1; packet_t pp; int nopid = 0; clear_exception_conditions (S); // Erratum: We have an off-by-one error here. // Flow chart shows setting RC to 0 and we end up sending SAMB(e) 11 times when N2 (RETRY) is 10. // It should be 1 rather than 0. S->rc = 1; pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->modulo == 128) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); STOP_T3; START_T1; } /* end establish_data_link */ /*------------------------------------------------------------------------------ * * Name: clear_exception_conditions * *------------------------------------------------------------------------------*/ static void clear_exception_conditions (ax25_dlsm_t *S) { S->peer_receiver_busy = 0; S->reject_exception = 0; S->own_receiver_busy = 0; S->acknowledge_pending = 0; // My enhancement. If we are establishing a new connection, we should discard any saved out of sequence incoming I frames. int n; for (n = 0; n < 128; n++) { if (S->rxdata_by_ns[n] != NULL) { cdata_delete (S->rxdata_by_ns[n]); S->rxdata_by_ns[n] = NULL; } } // We retain the transmit I frame queue so we can continue after establishing a new connection. } /* end clear_exception_conditions */ /*------------------------------------------------------------------------------ * * Name: transmit_enquiry, page 106 * * Purpose: This is called only when a timer expires. * * T1: We sent I frames and timed out waiting for the ack. * Poke the other end to determine how much it got so far * so we know where to continue. * * T3: Not activity for substantial amount of time. * Poke the other end to see if it is still there. * * * Observation: This is the only place where we send RR command with P=1. * * Sequence of events: * * We send some I frames to the other guy. * There are outstanding sent I frames for which we did not receive ACK. * * Timer 1 expires when we are in state 3: send RR/RNR command P=1 (here). Enter state 4. * Timer 1 expires when we are in state 4: same until max retry count is exceeded. * * Other guy gets RR/RNR command P=1. * Same action for either state 3 or 4. * Whether he has outstanding un-ack'ed sent I frames is irrelevent. * He calls "enquiry response" which sends RR/RNR response F=1. * (Read about detour 1 below and in enquiry_response.) * * I get back RR/RNR response F=1. Still in state 4. * Of course, N(R) gets copied into V(A). * Now here is the interesting part. * If the ACKs are caught up, i.e. V(A) == V(S), stop T1 and enter state 3. * Otherwise, "invoke retransmission" to resend everything after N(R). * * * Detour 1: You were probably thinking, "Suppose SREJ is enabled and the other guy * had a record of the SREJ frames sent which were not answered by filled in * I frames. Why not send the SREJ again instead of backing up and resending * stuff which already got there OK?" * * The code to handle incoming SREJ in state 4 is there but stop T1 is in the * wrong place as mentioned above. * *------------------------------------------------------------------------------*/ static void transmit_enquiry (ax25_dlsm_t *S) { int p = 1; int nr = S->vr; cmdres_t cmd = cr_cmd; packet_t pp; if (s_debug_retry) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n****** TRANSMIT ENQUIRY ******\n\n"); } // This is the ONLY place that we send RR/RNR *command* with P=1. // Everywhere else should be response. // I don't think we ever use RR/RNR command P=0 but need to check on that. pp = ax25_s_frame (S->addrs, S->num_addr, cmd, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, p); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; START_T1; } /* end transmit_enquiry */ /*------------------------------------------------------------------------------ * * Name: enquiry_response * * Inputs: frame_type - Type of frame received or frame_not_AX25 for LM seize confirm. * I think that this function is being called from too many * different contexts where it really needs to react differently. * So pass in more information about where we are coming from. * * F - Always specified as parameter in the references. * * Description: This is called for: * - UI command with P=1 then F=1. * - LM seize confirm with ack pending then F=0. (TODO: not clear on this yet.) * TODO: I think we want to ensure that this function is called ONLY * for RR/RNR/I command with P=1. LM Seize confirm can do its own thing and * not get involved in this complication. * - check_need_for_response(), command & P=1, then F=1 * - RR/RNR/REJ command & P=1, then F=1 * * In all cases, we see that F has been specified, usually 1 because it is * a response to a command with P=1. * Specifying F would imply response when the flow chart says RR/RNR command. * The documentation says: * * 6.2. Poll/Final (P/F) Bit Procedures * * The next response frame returned to an I frame with the P bit set to "1", received during the information * transfer state, is an RR, RNR or REJ response with the F bit set to "1". * * The next response frame returned to a supervisory command frame with the P bit set to "1", received during * the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1". * * Erattum! The flow chart says RR/RNR *command* but I'm confident should be response. * * Erratum: Ax.25 spec has nothing here for SREJ. See X.25 2.4.6.11 for explanation. * *------------------------------------------------------------------------------*/ static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f) { cmdres_t cr = cr_res; // Response, not command as seen in flow chart. int nr = S->vr; packet_t pp; if (s_debug_retry) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n****** ENQUIRY RESPONSE F=%d ******\n\n", f); } #if 1 // Detour 1 // My addition, Based on X.25 2.4.6.11. // Only for RR, RNR, I. // See sequence of events in transmit_enquiry comments. if (f == 1 && (frame_type == frame_type_S_RR || frame_type == frame_type_S_RNR || frame_type == frame_type_I)) { if (S->own_receiver_busy) { // I'm busy. pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } else if (S->srej_enabled) { // SREJ is enabled. This is based on X.25 2.4.6.11. if (S->modulo != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: enquiry response should not be sending SREJ for modulo 8.\n"); } // Suppose we received I frames with N(S) of 0, 3, 7. // V(R) is still 1 because 0 is the last one received with contiguous N(S) values. // 3 and 7 have been saved into S->rxdata_by_ns. // We have outstanding requests to resend 1, 2, 4, 5, 6. // Either those requests or the replies got lost. // The other end timed out and asked us what is happening by sending RR/RNR command P=1. // First see if we have any out of sequence frames in the receive buffer. int last; last = AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__); while (last != S->vr && S->rxdata_by_ns[last] == NULL) { last = AX25MODULO(last - 1, S->modulo, __FILE__, __func__, __LINE__); } if (last != S->vr) { // Ask for missing frames to be sent again. X.25 2.4.6.11 b) & 2.3.5.2.2 int resend[128]; int count = 0; int j; int allow_f1 = 1; j = S->vr; while (j != last) { if (S->rxdata_by_ns[j] == NULL) { resend[count++] = j; } j = AX25MODULO(j + 1, S->modulo, __FILE__, __func__, __LINE__); } send_srej_frames (S, resend, count, allow_f1); } else { // Not waiting for fill in of missing frames. X.25 2.4.6.11 c) pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } } else { // SREJ not enabled. // One might get the idea that it would make sense send REJ here if the reject exception is set. // However, I can't seem to find that buried in X.25 2.4.5.9. // And when we look at what happens when RR response, F=1 is received in state 4, it is // effectively REJ when N(R) is not the same as V(S). pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } } // end of RR,RNR,I cmd with P=1 else { // For cases other than (RR, RNR, I) command, P=1. pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } #else // As found in AX.25 spec. // Erratum: This is woefully inadequate when SREJ is enabled. // Erratum: Flow chart says RR/RNR command but I'm confident it should be response. pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; # endif } /* end enquiry_response */ /*------------------------------------------------------------------------------ * * Name: invoke_retransmission * * Inputs: nr_input - Resend starting with this. * Continue will all up to and including current V(S) value. * * Description: Resend one or more frames that have already been sent. * * This is probably the result of getting REJ asking for a resend. * *------------------------------------------------------------------------------*/ static void invoke_retransmission (ax25_dlsm_t *S, int nr_input) { // Original flow chart showed saving V(S) into temp variable x, // using V(S) as loop control variable, and finally restoring it // to original value before returning. // Here we just a local variable instead of messing with it. // This should be equivalent but safer. int local_vs; int sent_count = 0; if (S->txdata_by_ns[nr_input] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, Can't resend starting with N(S) = %d. It is not available. %s %s %d\n", nr_input, __FILE__, __func__, __LINE__); return; } local_vs = nr_input; do { if (S->txdata_by_ns[local_vs] != NULL) { cmdres_t cr = cr_cmd; int ns = local_vs; int nr = S->vr; int p = 0; if (s_debug_misc) { text_color_set(DW_COLOR_INFO); dw_printf ("invoke_retransmission(): state=%d, Resending N(S) = %d, probably as result of REJ N(R) = %d\n", S->state, ns, nr_input); } packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, S->txdata_by_ns[ns]->pid, (unsigned char *)(S->txdata_by_ns[ns]->data), S->txdata_by_ns[ns]->len); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // Keep it around in case we need to send again. sent_count++; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, state=%d, need to retransmit N(S) = %d for REJ but it is not available. %s %s %d\n", S->state, local_vs, __FILE__, __func__, __LINE__); } local_vs = AX25MODULO(local_vs + 1, S->modulo, __FILE__, __func__, __LINE__); } while (local_vs != S->vs); if (sent_count == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, Nothing to retransmit. N(R)=%d, %s %s %d\n", nr_input, __FILE__, __func__, __LINE__); } } /* end invoke_retransmission */ /*------------------------------------------------------------------------------ * * Name: check_i_frame_ackd * * Purpose: * * Inputs: nr - N(R) from I or S frame, acknowledging receipt thru N(R)-1. * i.e. The next one expected by the peer is N(R). * * Outputs: S->va - updated from nr. * * Description: TBD... Document when this is used. * *------------------------------------------------------------------------------*/ static void check_i_frame_ackd (ax25_dlsm_t *S, int nr) { if (S->peer_receiver_busy) { SET_VA(nr); // Erratum? This looks odd to me. // It doesn't seem right that we would have T3 and T1 running at the same time. // Normally we stop one when starting the other. // Should this be Stop T3 instead? START_T3; if ( ! IS_T1_RUNNING) { START_T1; } } else if (nr == S->vs) { SET_VA(nr); STOP_T1; START_T3; select_t1_value (S); } else if (nr != S->va) { if (s_debug_misc) { text_color_set(DW_COLOR_DEBUG); dw_printf ("check_i_frame_ackd n(r)=%d, v(a)=%d, Set v(a) to new value %d\n", nr, S->va, nr); } SET_VA(nr); START_T1; // Erratum? Flow chart says "restart" rather than "start." // Is this intentional, what is the difference? } } /* check_i_frame_ackd */ /*------------------------------------------------------------------------------ * * Name: check_need_for_response * * Inputs: frame_type - frame_type_S_RR, etc. * * cr - Is it a command or response? * * pf - P/F from the frame. * * Description: This is called for RR, RNR, and REJ frames. * If it is a command with P=1, we reply with RR or RNR with F=1. * *------------------------------------------------------------------------------*/ static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf) { if (cr == cr_cmd && pf == 1) { int f = 1; enquiry_response (S, frame_type, f); } else if (cr == cr_res && pf == 1) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error A: F=1 received but P=1 not outstanding.\n", S->stream_id); } } } /* end check_need_for_response */ /*------------------------------------------------------------------------------ * * Name: ui_check * * Description: I don't think we need this because UI frames are processed * without going thru the data link state machine. * *------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------ * * Name: select_t1_value * * Purpose: Dynamically adjust the T1 timeout value, commonly a fixed time known as FRACK. * * Inputs: S->rc Retry counter. * * S->srt Smoothed roundtrip time in seconds. * * S->t1_remaining_when_last_stopped * Seconds left on T1 when it is stopped. * * Outputs: S->srt New smoothed roundtrip time. * * S->t1v How long to wait for an acknowlegement before resending. * Value used when starting timer T1, in seconds. * Here it is dynamically adjusted. * * Description: How long should we wait for an ACK before sending again or giving up? * some implementations have a fixed length time. This is usually the FRACK parameter, * typically 3 seconds (D710A) or 4 seconds (KPC-3+). * * This should be increased for each digipeater in the path. * Here it is dynamically adjusted by taking the average time it takes to get a response * and then we double it. * * Rambling: It seems like a good idea to adapt to channel conditions, such as digipeater delays, * but it is fraught with peril if you are not careful. * * For example, if we accept an incoming connection and only receive some I frames and * send no I frames, T1 never gets started. In my earlier attempt, 't1_remaining_when_last_stopped' * had the initial value of 0 lacking any reason to set it differently. The calculation here * then kept pushing t1v up up up. After receiving 20 I frames and sending none, * t1v was over 300 seconds!!! * * We need some way to indicate that 't1_remaining_when_last_stopped' is not valid and * not to use it. Rather than adding a new variable, it is set to a negative value * initially to mean it has not been set yet. That solves one problem. * * T1 is paused whenever the channel is busy, either transmitting or receiving, * so the measured time could turn out to be a tiny fraction of a second, much less than * the frame transmission time. * If this gets too low, an unusually long random delay, before the sender's transmission, * could exceed this. I put in a lower limit for t1v, currently 1 second. * * What happens if we get multiple timeouts because we don't get a response? * For example, when we try to connect to a station which is not there, a KPC-3+ will give * up and report failure after 10 tries x 4 sec = 40 seconds. * * The algorithm in the AX.25 protocol spec shows increasing timeout values. * It might seem like a good idea but either it was not thought out very well * or I am not understanding it. If it is doubled each time, it gets awful large * very quickly. If we try to connect to a station which is not there, * we want to know within a minute, not an hour later. * * Keeping with the spirit of increasing the time but keeping it sane, * I increase the time linearly by a fraction of a second. * *------------------------------------------------------------------------------*/ static void select_t1_value (ax25_dlsm_t *S) { float old_srt = S->srt; // Erratum: I don't think this test for RC == 0 is valid. // We would need to set RC to 0 whenever we enter state 3 and we don't do that. // I think a more appropriate test would be to check if we are in state 3. // When things are going smoothly, it makes sense to fine tune timeout based on smoothed round trip time. // When in some other state, we might want to slowly increase the time to minimize collisions. // Maybe the solution is to set RC=0 when we enter state 3. // TODO: come back and revisit this. if (S->rc == 0) { if (S->t1_remaining_when_last_stopped >= 0) { // Negative means invalid, don't use it. // This is an IIR low pass filter. // Algebraically equivalent to version in AX.25 protocol spec but I think the // original intent is clearer by having 1/8 appear only once. S->srt = 7./8. * S->srt + 1./8. * ( S->t1v - S->t1_remaining_when_last_stopped ); } // We pause T1 when the channel is busy. // This includes both receiving someone else and us transmitting. // This can result in the round trip time going down to almost nothing. // My enhancement is to prevent srt from going below one second so // t1v should never be less than 2 seconds. // When t1v was allowed to go down to 1, we got occastional timeouts // even under ideal conditions, probably due to random CSMA delay time. if (S->srt < 1) { S->srt = 1; // Add another 2 seconds for each digipeater in path. if (S->num_addr > 2) { S->srt += 2 * (S->num_addr - 2); } } S->t1v = S->srt * 2; } else { if (S->t1_had_expired) { // This goes up exponentially if implemented as documented! // For example, if we were trying to connect to a station which is not there, we // would retry after 3, the 8, 16, 32, ... and not time out for over an hour. // That's ridiculous. Let's try increasing it by a quarter second each time. // We now give up after about a minute. // NO! S->t1v = powf(2, S->rc+1) * S->srt; S->t1v = S->rc * 0.25 + S->srt * 2; } } if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, new t1v = %.3f\n", S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v); } if (S->t1v < 0.99 || S->t1v > 30) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR? Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, Extreme new t1v = %.3f\n", S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v); } } /* end select_t1_value */ /*------------------------------------------------------------------------------ * * Name: set_version_2_0 * * Erratum: Flow chart refers to T2 which doesn't appear anywhere else. * *------------------------------------------------------------------------------*/ static void set_version_2_0 (ax25_dlsm_t *S) { S->srej_enabled = 0; S->modulo = 8; S->n1_paclen = g_misc_config_p->paclen; S->k_maxframe = g_misc_config_p->maxframe_basic; S->n2_retry = g_misc_config_p->retry; } /* end set_version_2_0 */ /*------------------------------------------------------------------------------ * * Name: set_version_2_2 * *------------------------------------------------------------------------------*/ static void set_version_2_2 (ax25_dlsm_t *S) { S->srej_enabled = 1; //S->srej_enabled = 0; // temporarily disable for testing of REJ only with modulo 128 S->modulo = 128; S->n1_paclen = g_misc_config_p->paclen; S->k_maxframe = g_misc_config_p->maxframe_extended; S->n2_retry = g_misc_config_p->retry; } /* end set_version_2_2 */ /*------------------------------------------------------------------------------ * * Name: is_good_nr * * Purpose: Evaluate condition "V(a) <= N(r) <= V(s)" which appears in flow charts * for incoming I, RR, RNR, REJ, and SREJ frames. * * Inputs: S - state machine. Contains V(a) and V(s). * * nr - N(r) found in the incoming frame. * * Description: This determines whether the Received Sequence Number, N(R), is in * the expected range for normal processing or if we have an error * condition that needs recovery. * * This gets tricky due to the wrap around of sequence numbers. * * 4.2.4.4. Received Sequence Number N(R) * * The received sequence number exists in both I and S frames. * Prior to sending an I or S frame, this variable is updated to equal that * of the received state variable, thus implicitly acknowledging the proper * reception of all frames up to and including N(R)-1. * * Pattern noticed: Anytime we have "is_good_nr" returning true, we should always * - set V(A) from N(R) or * - call "check_i_frame_acked" which does the same and some timer stuff. * *------------------------------------------------------------------------------*/ static int is_good_nr (ax25_dlsm_t *S, int nr) { int adjusted_va, adjusted_nr, adjusted_vs; int result; /* Adjust all values relative to V(a) before comparing so we won't have wrap around. */ #define adjust_by_va(x) (AX25MODULO((x) - S->va, S->modulo, __FILE__, __func__, __LINE__)) adjusted_va = adjust_by_va(S->va); // A clever compiler would know it is zero. adjusted_nr = adjust_by_va(nr); adjusted_vs = adjust_by_va(S->vs); result = adjusted_va <= adjusted_nr && adjusted_nr <= adjusted_vs; if (s_debug_misc) { text_color_set(DW_COLOR_DEBUG); dw_printf ("is_good_nr, V(a) %d <= nr %d <= V(s) %d, returns %d\n", S->va, nr, S->vs, result); } return (result); } /* end is_good_nr */ /*------------------------------------------------------------------------------ * * Name: i_frame_pop_off_queue * * Purpose: Transmit an I frame if we have one in the queue and conditions are right. * This appears two slightly different ways in the flow charts: * "frame pop off queue" * "I frame pops off queue" * * Inputs: i_frame_queue - Remove items from here. * peer_receiver_busy - If other end not busy. * V(s) - and we haven't reached window size. * V(a) * k * * Outputs: v(s) is incremented for each processed. * ack_pending = 0 * *------------------------------------------------------------------------------*/ static void i_frame_pop_off_queue (ax25_dlsm_t *S) { if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () state=%d\n", S->state); } // TODO: Were we expecting something in the queue? // or is empty an expected situation? if (S->i_frame_queue == NULL) { if (s_debug_misc) { // TODO: add different switch for I frame queue. //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () queue is empty get out, line %d\n", __LINE__); } // I Frame queue is empty. // Nothing to see here, folks. Move along. return; } switch (S->state) { case state_1_awaiting_connection: case state_5_awaiting_v22_connection: if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () line %d\n", __LINE__); } // This seems to say remove the I Frame from the queue and discard it if "layer 3 initiated" is set. // For the case of removing it from the queue and putting it back in we just leave it there. // Erratum? The flow chart seems to be backwards. // It would seem like we want to keep it if we are further along in the connection process. // I don't understand the intention here, and can't make a compelling argument on why it // is backwards, so it is implemented as documented. if (S->layer_3_initiated) { cdata_t *txdata; if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () discarding due to L3 init. line %d\n", __LINE__); } txdata = S->i_frame_queue; // Remove from head of list. S->i_frame_queue = txdata->next; cdata_delete (txdata); } break; case state_3_connected: case state_4_timer_recovery: if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () state %d, line %d\n", S->state, __LINE__); } while ( ( ! S->peer_receiver_busy ) && S->i_frame_queue != NULL && S->vs != AX25MODULO(S->va + S->k_maxframe, S->modulo, __FILE__, __func__, __LINE__) ) { cdata_t *txdata; txdata = S->i_frame_queue; // Remove from head of list. S->i_frame_queue = txdata->next; txdata->next = NULL; cmdres_t cr = cr_cmd; int ns = S->vs; int nr = S->vr; int p = 0; if (s_debug_misc || s_debug_radio) { //dw_printf ("i_frame_pop_off_queue () ns=%d, queue for transmit \"", ns); //ax25_safe_print (txdata->data, txdata->len, 1); //dw_printf ("\"\n"); } packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); } lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // Stash in sent array in case it gets lost and needs to be sent again. if (S->txdata_by_ns[ns] != NULL) { cdata_delete (S->txdata_by_ns[ns]); } S->txdata_by_ns[ns] = txdata; SET_VS(AX25MODULO(S->vs + 1, S->modulo, __FILE__, __func__, __LINE__)); // increment sequence of last sent. S->acknowledge_pending = 0; // Erratum: I think we always want to restart T1 when an I frame is sent. // Otherwise we could time out too soon. #if 1 STOP_T3; START_T1; #else if ( ! IS_T1_RUNNING) { STOP_T3; START_T1; } #endif } break; case state_0_disconnected: case state_2_awaiting_release: // Do nothing. break; } } /* end i_frame_pop_off_queue */ /*------------------------------------------------------------------------------ * * Name: discard_i_queue * * Purpose: Discard any data chunks waiting to be sent. * *------------------------------------------------------------------------------*/ static void discard_i_queue (ax25_dlsm_t *S) { cdata_t *t; while (S->i_frame_queue != NULL) { t = S->i_frame_queue; S->i_frame_queue = S->i_frame_queue->next; cdata_delete (t); } } /* end discard_i_queue */ /*------------------------------------------------------------------------------ * * Name: enter_new_state * * Purpose: Switch to new state. * * Description: Use a function, rather than setting variable directly, so we have * one common point for debug output and possibly other things we * might want to do at a state change. * *------------------------------------------------------------------------------*/ // TODO: requeuing... static void enter_new_state (ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line) { if (s_debug_variables) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); dw_printf (">>> NEW STATE = %d, previously %d, called from %s %d <<<\n", new_state, S->state, from_func, from_line); dw_printf ("\n"); } assert (new_state >= 0 && new_state <= 5); if (( new_state == state_3_connected || new_state == state_4_timer_recovery) && S->state != state_3_connected && S->state != state_4_timer_recovery ) { ptt_set (OCTYPE_CON, S->chan, 1); // Turn on connected indicator if configured. } else if (( new_state != state_3_connected && new_state != state_4_timer_recovery) && ( S->state == state_3_connected || S->state == state_4_timer_recovery ) ) { ptt_set (OCTYPE_CON, S->chan, 0); // Turn off connected indicator if configured. // Ideally we should look at any other link state machines // for this channel and leave the indicator on if any // are connected. I'm not that worried about it. } S->state = new_state; } /* end enter_new_state */ /*------------------------------------------------------------------------------ * * Name: mdl_negotiate_request * * Purpose: After receiving UA, in response to SABME, this starts up the XID exchange. * * Description: Send XID command. * Start timer TM201 so we can retry if timeout waiting for response. * Enter MDL negotiating state. * *------------------------------------------------------------------------------*/ static void mdl_negotiate_request (ax25_dlsm_t *S) { struct xid_param_s param; unsigned char xinfo[40]; int xlen; cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; switch (S->mdl_state) { case mdl_state_0_ready: initiate_negotiation (S, ¶m); xlen = xid_encode (¶m, xinfo); pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->mdl_rc = 0; START_TM201; S->mdl_state = mdl_state_1_negotiating; break; case mdl_state_1_negotiating: // SDL says "requeue" but I don't understand how it would be useful or how to do it. break; } } /* end mdl_negotiate_request */ /*------------------------------------------------------------------------------ * * Name: initiate_negotiation * * Purpose: Used when preparing the XID *command*. * * Description: Prepare set of parameters to request from the other station. * *------------------------------------------------------------------------------*/ static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param) { param->full_duplex = 0; param->rej = S->srej_enabled ? selective_reject : implicit_reject; param->modulo = S->modulo; param->i_field_length_rx = S->n1_paclen; // Hmmmm. Should we ask for what the user // specified for PACLEN or offer the maximum // that we can handle, AX25_N1_PACLEN_MAX? param->window_size_rx = S->k_maxframe; param->ack_timer = (int)(g_misc_config_p->frack * 1000); param->retries = S->n2_retry; } /*------------------------------------------------------------------------------ * * Name: negotiation_response * * Purpose: Used when receiving the XID command and preparing the XID response. * * Description: Take what other station has asked for and reduce if we have lesser capabilities. * For example if other end wants 8k information part we reduce it to 2k. * Ack time and retries are the opposite, we take the maximum. * * Question: If the other send leaves anything undefined should we leave it * undefined or fill in what we would like before sending it back? * * The original version of the protocol spec left this open. * The 2006 revision, in red, says we should fill in defaults for anything * not specified. This makes sense. We send back a complete set of parameters * so both ends should agree. * *------------------------------------------------------------------------------*/ static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param) { // Full duplex would not be that difficult. // Just ignore the Carrier Detect when transmitting. // But we haven't done that yet. param->full_duplex = 0; // Other end might want 8. // Seems unlikely. If it implements XID it should have modulo 128. if (param->modulo == G_UNKNOWN) { param->modulo = 8; // Not specified. Set default. } else { param->modulo = MIN(param->modulo, 128); } // We can do REJ or SREJ but won't combine them. // Erratum: 2006 version, section, 4.3.3.7 says default selective reject - reject. // We can't do that. if (param->rej == G_UNKNOWN) { param->rej = (param->modulo == 128) ? selective_reject : implicit_reject; // not specified, set default } else { param->rej = MIN(param->rej, selective_reject); } // We can currently do up to 2k. // Take minimum of that and what other guy asks for. if (param->i_field_length_rx == G_UNKNOWN) { param->i_field_length_rx = 256; // Not specified, take default. } else { param->i_field_length_rx = MIN(param->i_field_length_rx, AX25_N1_PACLEN_MAX); } // In theory extended mode can have window size of 127 but // I'm limiting it to 63 for the reason mentioned in the SREJ logic. if (param->window_size_rx == G_UNKNOWN) { param->window_size_rx = (param->modulo == 128) ? 32 : 4; // not specified, set default. } else { if (param->modulo == 128) param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_EXTENDED_MAX); else param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_BASIC_MAX); } // Erratum: Unclear. Is the Acknowledgement Timer before or after compensating for digipeaters // in the path? e.g. Typically TNCs use the FRACK parameter for this and it often defaults to 3. // However, the actual timeout value might be something like FRACK*(2*m+1) where m is the number of // digipeaters in the path. I'm assuming this is the FRACK value and any additional time, for // digipeaters will be added in locally at each end on top of this exchanged value. if (param->ack_timer == G_UNKNOWN) { param->ack_timer = 3000; // not specified, set default. } else { param->ack_timer = MAX(param->ack_timer, (int)(g_misc_config_p->frack * 1000)); } if (param->retries == G_UNKNOWN) { param->retries = 10; // not specified, set default. } else { param->retries = MAX(param->retries, S->n2_retry); } // IMPORTANT: Take values we have agreed upon and put into my running configuration. complete_negotiation(S, param); } /*------------------------------------------------------------------------------ * * Name: complete_negotiation * * Purpose: Used when preparing or receiving the XID *response*. * * Description: Take set of parameters which we have agreed upon and apply * to the running configuration. * * TODO: Should do some checking here in case other station * sends something crazy. * *------------------------------------------------------------------------------*/ static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param) { if (param->rej != G_UNKNOWN) { S->srej_enabled = param->rej >= selective_reject; } if (param->modulo != G_UNKNOWN) { // Disaster if aren't agreeing on this. S->modulo = param->modulo; } if (param->i_field_length_rx != G_UNKNOWN) { S->n1_paclen = param->i_field_length_rx; } if (param->window_size_rx != G_UNKNOWN) { S->k_maxframe = param->window_size_rx; } if (param->ack_timer != G_UNKNOWN) { S->t1v = param->ack_timer * 0.001; } if (param->retries != G_UNKNOWN) { S->n2_retry = param->retries; } } //################################################################################### //################################################################################### // // Timers. // // Start. // Stop. // Pause (when channel busy) & resume. // Is it running? // Did it expire before being stopped? // When will next one expire? // //################################################################################### //################################################################################### static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Start T1 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line); } S->t1_exp = now + S->t1v; if (S->radio_channel_busy) { S->t1_paused_at = now; } else { S->t1_paused_at = 0; } S->t1_had_expired = 0; } /* end start_t1 */ static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); RESUME_T1; // adjust expire time if paused. if (S->t1_exp == 0.0) { // Was already stopped. } else { S->t1_remaining_when_last_stopped = S->t1_exp - now; if (S->t1_remaining_when_last_stopped < 0) S->t1_remaining_when_last_stopped = 0; } // Normally this would be at the top but we don't know time remaining at that point. if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); if (S->t1_exp == 0.0) { dw_printf ("Stop T1. Wasn't running, [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line); } else { dw_printf ("Stop T1, %.3f remaining, [now=%.3f] from %s %d\n", S->t1_remaining_when_last_stopped, now - S->start_time, from_func, from_line); } } S->t1_exp = 0.0; // now stopped. S->t1_had_expired = 0; // remember that it did not expire. } /* end stop_t1 */ static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line) { int result = S->t1_exp != 0.0; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("is_t1_running? returns %d\n", result); } return (result); } /* end is_t1_running */ static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->t1_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->t1_paused_at == 0.0) { // Running and not paused. double now = dtime_now(); S->t1_paused_at = now; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Paused T1 with %.3f still remaining, [now=%.3f] from %s %d\n", S->t1_exp - now, now - S->start_time, from_func, from_line); } } else { if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("T1 error: Didn't expect pause when already paused.\n"); } } } /* end pause_t1 */ static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->t1_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->t1_paused_at == 0.0) { // Running but not paused. } else { double now = dtime_now(); double paused_for_sec = now - S->t1_paused_at; S->t1_exp += paused_for_sec; S->t1_paused_at = 0.0; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Resumed T1 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->t1_exp - now, now - S->start_time); } } } /* end resume_t1 */ // T3 is a lot simpler. // Here we are talking about minutes of inactivity with the peer // rather than expecting a response within seconds where timing is more critical. // We don't need to capture remaining time when stopped. // I don't think there is a need to pause it due to the large time frame. static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Start T3 for %.3f sec, [now=%.3f] from %s %d\n", T3_DEFAULT, now - S->start_time, from_func, from_line); } S->t3_exp = now + T3_DEFAULT; } static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); if (S->t3_exp == 0.0) { dw_printf ("Stop T3. Wasn't running.\n"); } else { dw_printf ("Stop T3, %.3f remaining, [now=%.3f] from %s %d\n", S->t3_exp - now, now - S->start_time, from_func, from_line); } } S->t3_exp = 0.0; } // TM201 is similar to T1. // It needs to be paused whent the channel is busy. // Simpler because we don't need to keep track of time remaining when stopped. static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Start TM201 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line); } S->tm201_exp = now + S->t1v; if (S->radio_channel_busy) { S->tm201_paused_at = now; } else { S->tm201_paused_at = 0; } } /* end start_tm201 */ static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Stop TM201. [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line); } S->tm201_exp = 0.0; // now stopped. } /* end stop_tm201 */ static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->tm201_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->tm201_paused_at == 0.0) { // Running and not paused. double now = dtime_now(); S->tm201_paused_at = now; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Paused TM201 with %.3f still remaining, [now=%.3f] from %s %d\n", S->tm201_exp - now, now - S->start_time, from_func, from_line); } } else { if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("TM201 error: Didn't expect pause when already paused.\n"); } } } /* end pause_tm201 */ static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->tm201_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->tm201_paused_at == 0.0) { // Running but not paused. } else { double now = dtime_now(); double paused_for_sec = now - S->tm201_paused_at; S->tm201_exp += paused_for_sec; S->tm201_paused_at = 0.0; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Resumed TM201 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->tm201_exp - now, now - S->start_time); } } } /* end resume_tm201 */ double ax25_link_get_next_timer_expiry (void) { double tnext = 0; ax25_dlsm_t *p; for (p = list_head; p != NULL; p = p->next) { // Consider if running and not paused. if (p->t1_exp != 0 && p->t1_paused_at == 0) { if (tnext == 0) { tnext = p->t1_exp; } else if (p->t1_exp < tnext) { tnext = p->t1_exp; } } if (p->t3_exp != 0) { if (tnext == 0) { tnext = p->t3_exp; } else if (p->t3_exp < tnext) { tnext = p->t3_exp; } } if (p->tm201_exp != 0 && p->tm201_paused_at == 0) { if (tnext == 0) { tnext = p->tm201_exp; } else if (p->tm201_exp < tnext) { tnext = p->tm201_exp; } } } if (s_debug_timers > 1) { text_color_set(DW_COLOR_DEBUG); if (tnext == 0.0) { dw_printf ("ax25_link_get_next_timer_expiry returns none.\n"); } else { dw_printf ("ax25_link_get_next_timer_expiry returns %.3f sec from now.\n", tnext - dtime_now()); } } return (tnext); } /* end ax25_link_get_next_timer_expiry */ /* end ax25_link.c */