// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2016, 2017 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 . /*------------------------------------------------------------------ * * Module: xid.c * * Purpose: Encode and decode the info field of XID frames. * * Description: If we originate the connection, and the other end is * capable of AX.25 version 2.2, * * - We send an XID command frame with our capabilities. * - the other sends back an XID response, possibly * reducing some values to be acceptable there. * - Both ends use the values in that response. * * If the other end originates the connection, * * - It sends XID command frame with its capabilities. * - We might decrease some of them to be acceptable. * - Send XID response. * - Both ends use values in my response. * * References: AX.25 Protocol Spec, sections 4.3.3.7 & 6.3.2. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include "textcolor.h" #include "xid.h" #define FI_Format_Indicator 0x82 #define GI_Group_Identifier 0x80 #define PI_Classes_of_Procedures 2 #define PI_HDLC_Optional_Functions 3 #define PI_I_Field_Length_Rx 6 #define PI_Window_Size_Rx 8 #define PI_Ack_Timer 9 #define PI_Retries 10 // Forget about the bit order at the physical layer (e.g. HDLC). // It doesn't matter at all here. We are dealing with bytes. // A different encoding could send the bits in the opposite order. // The bit numbers are confusing because this one table (Fig. 4.5) starts // with 1 for the LSB when everywhere else refers to the LSB as bit 0. // The first byte must be of the form 0xx0 0001 // The second byte must be of the form 0000 0000 // If we process the two byte "Classes of Procedures" like // the other multibyte numeric fields, with the more significant // byte first, we end up with the bit masks below. // The bit order would be 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 // (This has nothing to do with the HDLC serializing order. // I'm talking about the way we would normally write binary numbers.) #define PV_Classes_Procedures_Balanced_ABM 0x0100 #define PV_Classes_Procedures_Half_Duplex 0x2000 #define PV_Classes_Procedures_Full_Duplex 0x4000 // The first byte must be of the form 1000 0xx0 // The second byte must be of the form 1010 xx00 // The third byte must be of the form 0000 0010 // If we process the three byte "HDLC Optional Parmeters" like // the other multibyte numeric fields, with the most significant // byte first, we end up with bit masks like this. // The bit order would be 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 24 23 22 21 20 19 18 17 #define PV_HDLC_Optional_Functions_REJ_cmd_resp 0x020000 #define PV_HDLC_Optional_Functions_SREJ_cmd_resp 0x040000 #define PV_HDLC_Optional_Functions_Extended_Address 0x800000 #define PV_HDLC_Optional_Functions_Modulo_8 0x000400 #define PV_HDLC_Optional_Functions_Modulo_128 0x000800 #define PV_HDLC_Optional_Functions_TEST_cmd_resp 0x002000 #define PV_HDLC_Optional_Functions_16_bit_FCS 0x008000 #define PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp 0x000020 #define PV_HDLC_Optional_Functions_Segmenter 0x000040 #define PV_HDLC_Optional_Functions_Synchronous_Tx 0x000002 /*------------------------------------------------------------------- * * Name: xid_parse * * Purpose: Decode information part of XID frame into individual values. * * Inputs: info - pointer to information part of frame. * * info_len - Number of bytes in information part of frame. * Could be 0. * * desc_size - Size of desc. 100 is good. * * Outputs: result - Structure with extracted values. * * desc - Text description for troubleshooting. * * Returns: 1 for mostly successful (with possible error messages), 0 for failure. * * Description: 6.3.2 "The receipt of an XID response from the other station * establishes that both stations are using AX.25 version * 2.2 or higher and enables the use of the segmenter/reassembler * and selective reject." * *--------------------------------------------------------------------*/ int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, char *desc, int desc_size) { unsigned char *p; int group_len; char stemp[64]; strlcpy (desc, "", desc_size); // What should we do when some fields are missing? // The AX.25 v2.2 protocol spec says, for most of these, // "If this field is not present, the current values are retained." // We set the numeric values to our usual G_UNKNOWN to mean undefined and let the caller deal with it. // rej and modulo are enum so we can't use G_UNKNOWN there. result->full_duplex = G_UNKNOWN; result->srej = srej_not_specified; result->modulo = modulo_unknown; result->i_field_length_rx = G_UNKNOWN; result->window_size_rx = G_UNKNOWN; result->ack_timer = G_UNKNOWN; result->retries = G_UNKNOWN; /* Information field is optional but that seems pretty lame. */ if (info_len == 0) { return (1); } p = info; if (*p != FI_Format_Indicator) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: First byte of info field should be Format Indicator, %02x.\n", FI_Format_Indicator); dw_printf ("XID info part: %02x %02x %02x %02x %02x ... length=%d\n", info[0], info[1], info[2], info[3], info[4], info_len); return 0; } p++; if (*p != GI_Group_Identifier) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Second byte of info field should be Group Indicator, %d.\n", GI_Group_Identifier); return 0; } p++; group_len = *p++; group_len = (group_len << 8) + *p++; while (p < info + 4 + group_len) { int pind, plen, pval, j; pind = *p++; plen = *p++; // should have sanity checking if (plen < 1 || plen > 4) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Length ????? TODO ???? %d.\n", plen); return (1); // got this far. } pval = 0; for (j=0; jfull_duplex = 0; strlcat (desc, "Half-Duplex ", desc_size); } else if (pval & PV_Classes_Procedures_Full_Duplex && ! (pval & PV_Classes_Procedures_Half_Duplex)) { result->full_duplex = 1; strlcat (desc, "Full-Duplex ", desc_size); } else { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected one of Half or Full Duplex be set.\n"); result->full_duplex = 0; } break; case PI_HDLC_Optional_Functions: // Pick highest of those offered. if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) { strlcat (desc, "REJ ", desc_size); } if (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) { strlcat (desc, "SREJ ", desc_size); } if (pval & PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp) { strlcat (desc, "Multi-SREJ ", desc_size); } if (pval & PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp) { result->srej = srej_multi; } else if (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) { result->srej = srej_single; } else if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) { result->srej = srej_none; } else { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected at least one of REJ, SREJ, Multi-SREJ to be set.\n"); result->srej = srej_none; } if ((pval & PV_HDLC_Optional_Functions_Modulo_8) && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) { result->modulo = modulo_8; strlcat (desc, "modulo-8 ", desc_size); } else if ((pval & PV_HDLC_Optional_Functions_Modulo_128) && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) { result->modulo = modulo_128; strlcat (desc, "modulo-128 ", desc_size); } else { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected one of Modulo 8 or 128 be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_Extended_Address)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected Extended Address to be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_TEST_cmd_resp)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected TEST cmd/resp to be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_16_bit_FCS)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected 16 bit FCS to be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_Synchronous_Tx)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected Synchronous Tx to be set.\n"); } break; case PI_I_Field_Length_Rx: result->i_field_length_rx = pval / 8; snprintf (stemp, sizeof(stemp), "I-Field-Length-Rx=%d ", result->i_field_length_rx); strlcat (desc, stemp, desc_size); if (pval & 0x7) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: I Field Length Rx, %d, is not a whole number of bytes.\n", pval); } break; case PI_Window_Size_Rx: result->window_size_rx = pval; snprintf (stemp, sizeof(stemp), "Window-Size-Rx=%d ", result->window_size_rx); strlcat (desc, stemp, desc_size); if (pval < 1 || pval > 127) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru 127.\n", pval); result->window_size_rx = 127; // Let the caller deal with modulo 8 consideration. } //continue here with more error checking. break; case PI_Ack_Timer: result->ack_timer = pval; snprintf (stemp, sizeof(stemp), "Ack-Timer=%d ", result->ack_timer); strlcat (desc, stemp, desc_size); break; case PI_Retries: // Is it retrys or retries? result->retries = pval; snprintf (stemp, sizeof(stemp), "Retries=%d ", result->retries); strlcat (desc, stemp, desc_size); break; default: break; // Ignore anything we don't recognize. } } if (p != info + info_len) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Frame / Group Length mismatch.\n"); } return (1); } /* end xid_parse */ /*------------------------------------------------------------------- * * Name: xid_encode * * Purpose: Encode the information part of an XID frame. * * Inputs: param-> * full_duplex - As command, am I capable of full duplex operation? * When a response, are we both? * 0 = half duplex. * 1 = full duplex. * * srej - Level of selective reject. * srej_none (use REJ), srej_single, srej_multi * As command, offer a menu of what I can handle. (i.e. perhaps multiple bits set) * As response, take minimum of what is offered and what I can handle. (one bit set) * * modulo - 8 or 128. * * i_field_length_rx - Maximum number of bytes I can handle in info part. * Default is 256. * Up to 8191 will fit into the field. * Use G_UNKNOWN to omit this. * * window_size_rx - Maximum window size ("k") that I can handle. * Defaults are are 4 for modulo 8 and 32 for modulo 128. * * ack_timer - Acknowledge timer in milliseconds. * *** describe meaning. *** * Default is 3000. * Use G_UNKNOWN to omit this. * * retries - Allows negotiation of retries. * Default is 10. * Use G_UNKNOWN to omit this. * * cr - Is it a command or response? * * Outputs: info - Information part of XID frame. * Does not include the control byte. * Use buffer of 40 bytes just to be safe. * * Returns: Number of bytes in the info part. Should be at most 27. * Again, provide a larger space just to be safe in case this ever changes. * * Description: 6.3.2 "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." * * "This version of AX.25 implements the negotiation or notification of six AX.25 * parameters. Notification simply tells the distant TNC some limit that cannot be exceeded. * The distant TNC can choose to use the limit or some other value that is within the * limits. Notification is used with the Window Size Receive (k) and Information * Field Length Receive (N1) parameters. Negotiation involves both TNCs choosing a * value that is mutually acceptable. The XID command frame contains a set of values * acceptable to the originating TNC. The distant TNC chooses to accept the values * offered, or other acceptable values, and places these values in the XID response. * Both TNCs set themselves up based on the values used in the XID response. Negotiation * is used by Classes of Procedures, HDLC Optional Functions, Acknowledge Timer and Retries." * * Comment: I have a problem with "... occurs at any time." What if we were in the middle * of transferring a large file with k=32 then along comes XID which says switch to modulo 8? * * Insight: Or is it Erratum? * After reading the base standards documents, it seems that the XID command should offer * up a menu of all the acceptable choices. e.g. REJ, SREJ, Multi-SREJ. One or more bits * can be set. The XID response, would set a single bit which is the desired choice from * among those offered. * Should go back and review half/full duplex and modulo. * *--------------------------------------------------------------------*/ int xid_encode (struct xid_param_s *param, unsigned char *info, cmdres_t cr) { unsigned char *p; int len; int x; int m = 0; p = info; *p++ = FI_Format_Indicator; *p++ = GI_Group_Identifier; *p++ = 0; m = 4; // classes of procedures m += 5; // HDLC optional features if (param->i_field_length_rx != G_UNKNOWN) m += 4; if (param->window_size_rx != G_UNKNOWN) m += 3; if (param->ack_timer != G_UNKNOWN) m += 4; if (param->retries != G_UNKNOWN) m += 3; *p++ = m; // 0x17 if all present. // "Classes of Procedures" has half / full duplex. // We always send this. *p++ = PI_Classes_of_Procedures; *p++ = 2; x = PV_Classes_Procedures_Balanced_ABM; if (param->full_duplex == 1) x |= PV_Classes_Procedures_Full_Duplex; else // includes G_UNKNOWN x |= PV_Classes_Procedures_Half_Duplex; *p++ = (x >> 8) & 0xff; *p++ = x & 0xff; // "HDLC Optional Functions" contains REJ/SREJ & modulo 8/128. // We always send this. // Watch out for unknown values and do something reasonable. *p++ = PI_HDLC_Optional_Functions; *p++ = 3; x = PV_HDLC_Optional_Functions_Extended_Address | PV_HDLC_Optional_Functions_TEST_cmd_resp | PV_HDLC_Optional_Functions_16_bit_FCS | PV_HDLC_Optional_Functions_Synchronous_Tx; //text_color_set (DW_COLOR_ERROR); //dw_printf ("****** XID temp hack - test no SREJ ******\n"); // param->srej = srej_none; if (cr == cr_cmd) { // offer a "menu" of acceptable choices. i.e. 1, 2 or 3 bits set. switch (param->srej) { case srej_none: default: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp; break; case srej_single: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp | PV_HDLC_Optional_Functions_SREJ_cmd_resp; break; case srej_multi: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp | PV_HDLC_Optional_Functions_SREJ_cmd_resp | PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp; break; } } else { // for response, set only a single bit. switch (param->srej) { case srej_none: default: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp; break; case srej_single: x |= PV_HDLC_Optional_Functions_SREJ_cmd_resp; break; case srej_multi: x |= PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp; break; } } if (param->modulo == modulo_128) x |= PV_HDLC_Optional_Functions_Modulo_128; else // includes modulo_8 and modulo_unknown x |= PV_HDLC_Optional_Functions_Modulo_8; *p++ = (x >> 16) & 0xff; *p++ = (x >> 8) & 0xff; *p++ = x & 0xff; // The rest are skipped if undefined values. // "I Field Length Rx" - max I field length acceptable to me. // This is in bits. 8191 would be max number of bytes to fit in field. if (param->i_field_length_rx != G_UNKNOWN) { *p++ = PI_I_Field_Length_Rx; *p++ = 2; x = param->i_field_length_rx * 8; *p++ = (x >> 8) & 0xff; *p++ = x & 0xff; } // "Window Size Rx" if (param->window_size_rx != G_UNKNOWN) { *p++ = PI_Window_Size_Rx; *p++ = 1; *p++ = param->window_size_rx; } // "Ack Timer" milliseconds. We could handle up to 65535 here. if (param->ack_timer != G_UNKNOWN) { *p++ = PI_Ack_Timer; *p++ = 2; *p++ = (param->ack_timer >> 8) & 0xff; *p++ = param->ack_timer & 0xff; } // "Retries." if (param->retries != G_UNKNOWN) { *p++ = PI_Retries; *p++ = 1; *p++ = param->retries; } len = p - info; return (len); } /* end xid_encode */ /*------------------------------------------------------------------- * * Name: main * * Purpose: Unit test for other functions here. * * Description: Run with: * * gcc -DXIDTEST -g xid.c textcolor.o && ./a * * Result should be: * * XID test: Success. * * with no error messages. * *--------------------------------------------------------------------*/ #if XIDTEST /* From Figure 4.6. Typical XID frame, from AX.25 protocol spec, v. 2.2 */ /* This is the info part after a control byte of 0xAF. */ static unsigned char example[27] = { /* FI */ 0x82, /* Format indicator */ /* GI */ 0x80, /* Group Identifier - parameter negotiation */ /* GL */ 0x00, /* Group length - all of the PI/PL/PV fields */ /* GL */ 0x17, /* (2 bytes) */ /* PI */ 0x02, /* Parameter Indicator - classes of procedures */ /* PL */ 0x02, /* Parameter Length */ #if 0 // Erratum: Example in the protocol spec looks wrong. /* PV */ 0x00, /* Parameter Variable - Half Duplex, Async, Balanced Mode */ /* PV */ 0x20, /* */ #else // I think it should be like this instead. /* PV */ 0x21, /* Parameter Variable - Half Duplex, Async, Balanced Mode */ /* PV */ 0x00, /* Reserved */ #endif /* PI */ 0x03, /* Parameter Indicator - optional functions */ /* PL */ 0x03, /* Parameter Length */ /* PV */ 0x86, /* Parameter Variable - SREJ/REJ, extended addr */ /* PV */ 0xA8, /* 16-bit FCS, TEST cmd/resp, Modulo 128 */ /* PV */ 0x02, /* synchronous transmit */ /* PI */ 0x06, /* Parameter Indicator - Rx I field length (bits) */ /* PL */ 0x02, /* Parameter Length */ // Erratum: The text does not say anything about the byte order for multibyte // numeric values. In the example, we have two cases where 16 bit numbers are // sent with the more significant byte first. /* PV */ 0x04, /* Parameter Variable - 1024 bits (128 octets) */ /* PV */ 0x00, /* */ /* PI */ 0x08, /* Parameter Indicator - Rx window size */ /* PL */ 0x01, /* Parameter length */ /* PV */ 0x02, /* Parameter Variable - 2 frames */ /* PI */ 0x09, /* Parameter Indicator - Timer T1 */ /* PL */ 0x02, /* Parameter Length */ /* PV */ 0x10, /* Parameter Variable - 4096 MSec */ /* PV */ 0x00, /* */ /* PI */ 0x0A, /* Parameter Indicator - Retries (N1) */ /* PL */ 0x01, /* Parameter Length */ /* PV */ 0x03 /* Parameter Variable - 3 retries */ }; int main (int argc, char *argv[]) { struct xid_param_s param; struct xid_param_s param2; int n; unsigned char info[40]; // Currently max of 27 but things can change. char desc[150]; // I've seen 109. /* parse example. */ n = xid_parse (example, sizeof(example), ¶m, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (n==1); assert (param.full_duplex == 0); assert (param.srej == srej_single); assert (param.modulo == modulo_128); assert (param.i_field_length_rx == 128); assert (param.window_size_rx == 2); assert (param.ack_timer == 4096); assert (param.retries == 3); /* encode and verify it comes out the same. */ n = xid_encode (¶m, info, cr_cmd); assert (n == sizeof(example)); n = memcmp(info, example, 27); //for (n=0; n<27; n++) { // dw_printf ("%2d %02x %02x\n", n, example[n], info[n]); //} assert (n == 0); /* try a couple different values, no srej. */ param.full_duplex = 1; param.srej = srej_none; param.modulo = modulo_8; param.i_field_length_rx = 2048; param.window_size_rx = 3; param.ack_timer = 1234; param.retries = 12; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 1); assert (param2.srej == srej_none); assert (param2.modulo == modulo_8); assert (param2.i_field_length_rx == 2048); assert (param2.window_size_rx == 3); assert (param2.ack_timer == 1234); assert (param2.retries == 12); /* Other values, single srej. */ param.full_duplex = 0; param.srej = srej_single; param.modulo = modulo_8; param.i_field_length_rx = 61; param.window_size_rx = 4; param.ack_timer = 5555; param.retries = 9; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 0); assert (param2.srej == srej_single); assert (param2.modulo == modulo_8); assert (param2.i_field_length_rx == 61); assert (param2.window_size_rx == 4); assert (param2.ack_timer == 5555); assert (param2.retries == 9); /* Other values, multi srej. */ param.full_duplex = 0; param.srej = srej_multi; param.modulo = modulo_128; param.i_field_length_rx = 61; param.window_size_rx = 4; param.ack_timer = 5555; param.retries = 9; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 0); assert (param2.srej == srej_multi); assert (param2.modulo == modulo_128); assert (param2.i_field_length_rx == 61); assert (param2.window_size_rx == 4); assert (param2.ack_timer == 5555); assert (param2.retries == 9); /* Specify some and not others. */ param.full_duplex = 0; param.srej = srej_single; param.modulo = modulo_8; param.i_field_length_rx = G_UNKNOWN; param.window_size_rx = G_UNKNOWN; param.ack_timer = 999; param.retries = G_UNKNOWN; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 0); assert (param2.srej == srej_single); assert (param2.modulo == modulo_8); assert (param2.i_field_length_rx == G_UNKNOWN); assert (param2.window_size_rx == G_UNKNOWN); assert (param2.ack_timer == 999); assert (param2.retries == G_UNKNOWN); /* Default values for empty info field. */ n = 0; n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == G_UNKNOWN); assert (param2.srej == srej_not_specified); assert (param2.modulo == modulo_unknown); assert (param2.i_field_length_rx == G_UNKNOWN); assert (param2.window_size_rx == G_UNKNOWN); assert (param2.ack_timer == G_UNKNOWN); assert (param2.retries == G_UNKNOWN); text_color_set (DW_COLOR_REC); dw_printf ("XID test: Success.\n"); exit (0); } #endif /* end xid.c */