// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 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: kiss_frame.c * * Purpose: Common code used by Serial port and network versions of KISS protocol. * * Description: The KISS TNS protocol is described in http://www.ka9q.net/papers/kiss.html * * Briefly, a frame is composed of * * * FEND (0xC0) * * Contents - with special escape sequences so a 0xc0 * byte in the data is not taken as end of frame. * as part of the data. * * FEND * * The first byte of the frame contains: * * * port number in upper nybble. * * command in lower nybble. * * * Commands from application recognized: * * _0 Data Frame AX.25 frame in raw format. * * _1 TXDELAY See explanation in xmit.c. * * _2 Persistence " " * * _3 SlotTime " " * * _4 TXtail " " * Spec says it is obsolete but Xastir * sends it and we respect it. * * _5 FullDuplex Ignored. * * _6 SetHardware TNC specific. * * FF Return Exit KISS mode. Ignored. * * * Messages sent to client application: * * _0 Data Frame Received AX.25 frame in raw format. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "kiss_frame.h" #include "tq.h" #include "xmit.h" /* In server.c. Should probably move to some misc. function file. */ void hex_dump (unsigned char *p, int len); #if KISSTEST #define dw_printf printf void text_color_set (dw_color_t c) { return; } #else static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug); #endif /*------------------------------------------------------------------- * * Name: kiss_frame_init * * Purpose: Save information about valid channels for later error checking. * * Inputs: pa - Address of structure of type audio_s. * *-----------------------------------------------------------------*/ static struct audio_s *save_audio_config_p; void kiss_frame_init (struct audio_s *pa) { save_audio_config_p = pa; } /*------------------------------------------------------------------- * * Name: kiss_encapsulate * * Purpose: Ecapsulate a frame into KISS format. * * Inputs: in - Address of input block. * First byte is the "type indicator" with type and * channel but we don't care about that here. * * This seems cumbersome and confusing to have this * one byte offset when encapsulating an AX.25 frame. * Maybe the type/channel byte should be passed in * as a separate argument. * * Note that this is "binary" data and can contain * nul (0x00) values. Don't treat it like a text string! * * ilen - Number of bytes in input block. * * Outputs: out - Address where to place the KISS encoded representation. * The sequence is: * FEND - Magic frame separator. * data - with certain byte values replaced so * FEND will never occur here. * FEND - Magic frame separator. * * Returns: Number of bytes in the output. * Absolute max length will be twice input plus 2. * *-----------------------------------------------------------------*/ int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out) { int olen; int j; olen = 0; out[olen++] = FEND; for (j=0; j * <0x0d> * XFLOW OFF<0x0d> * FULLDUP OFF<0x0d> * KISS ON<0x0d> * RESTART<0x0d> * <0x03><0x03><0x03> * TC 1<0x0d> * TN 2,0<0x0d><0x0d><0x0d> * XFLOW OFF<0x0d> * FULLDUP OFF<0x0d> * KISS ON<0x0d> * RESTART<0x0d> * * This keeps repeating over and over and over and over again if * it doesn't get any sort of response. * * Let's try to keep it happy by sending back a command prompt. */ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,unsigned char*,int,int)) { //dw_printf ("kiss_frame ( %c %02x ) \n", ch, ch); switch (kf->state) { case KS_SEARCHING: /* Searching for starting FEND. */ default: if (ch == FEND) { /* Start of frame. But first print any collected noise for debugging. */ if (kf->noise_len > 0) { if (debug) { kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len); } kf->noise_len = 0; } kf->kiss_len = 0; kf->kiss_msg[kf->kiss_len++] = ch; kf->state = KS_COLLECTING; return; } /* Noise to be rejected. */ if (kf->noise_len < MAX_NOISE_LEN) { kf->noise[kf->noise_len++] = ch; } if (ch == '\r') { if (debug) { kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len); kf->noise[kf->noise_len] = '\0'; } /* Try to appease client app by sending something back. */ if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 || strcasecmp("reset\r", (char*)(kf->noise)) == 0) { (*sendfun) (0, (unsigned char *)"\xc0\xc0", -1, client); } else { (*sendfun) (0, (unsigned char *)"\r\ncmd:", -1, client); } kf->noise_len = 0; } return; break; case KS_COLLECTING: /* Frame collection in progress. */ if (ch == FEND) { unsigned char unwrapped[AX25_MAX_PACKET_LEN]; int ulen; /* End of frame. */ if (kf->kiss_len == 0) { /* Empty frame. Starting a new one. */ kf->kiss_msg[kf->kiss_len++] = ch; return; } if (kf->kiss_len == 1 && kf->kiss_msg[0] == FEND) { /* Empty frame. Just go on collecting. */ return; } kf->kiss_msg[kf->kiss_len++] = ch; if (debug) { /* As received over the wire from client app. */ kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); } ulen = kiss_unwrap (kf->kiss_msg, kf->kiss_len, unwrapped); if (debug >= 2) { /* Append CRC to this and it goes out over the radio. */ text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf ("Packet content after removing KISS framing and any escapes:\n"); /* Don't include the "type" indicator. */ /* It contains the radio channel and type should always be 0 here. */ hex_dump (unwrapped+1, ulen-1); } kiss_process_msg (unwrapped, ulen, debug); kf->state = KS_SEARCHING; return; } if (kf->kiss_len < MAX_KISS_LEN) { kf->kiss_msg[kf->kiss_len++] = ch; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS message exceeded maximum length.\n"); } return; break; } return; /* unreachable but suppress compiler warning. */ } /* end kiss_rec_byte */ /*------------------------------------------------------------------- * * Name: kiss_process_msg * * Purpose: Process a message from the KISS client. * * Inputs: kiss_msg - Kiss frame with FEND and escapes removed. * The first byte contains channel and command. * * kiss_len - Number of bytes including the command. * * debug - Debug option is selected. * *-----------------------------------------------------------------*/ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug) { int port; int cmd; packet_t pp; alevel_t alevel; port = (kiss_msg[0] >> 4) & 0xf; cmd = kiss_msg[0] & 0xf; switch (cmd) { case 0: /* Data Frame */ /* Special hack - Discard apparently bad data from Linux AX25. */ if ((port == 2 || port == 8) && kiss_msg[1] == 'Q' << 1 && kiss_msg[2] == 'S' << 1 && kiss_msg[3] == 'T' << 1 && kiss_msg[4] == ' ' << 1 && kiss_msg[15] == 3 && kiss_msg[16] == 0xcd) { if (debug) { text_color_set(DW_COLOR_ERROR); dw_printf ("Special case - Drop packets which appear to be in error.\n"); } return; } /* Verify that the port (channel) number is valid. */ if (port < 0 || port >= MAX_CHANS || ! save_audio_config_p->achan[port].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid transmit channel %d from KISS client app.\n", port); text_color_set(DW_COLOR_DEBUG); kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len); return; } memset (&alevel, 0xff, sizeof(alevel)); pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Invalid KISS data frame from client app.\n"); } else { /* How can we determine if it is an original or repeated message? */ /* If there is at least one digipeater in the frame, AND */ /* that digipeater has been used, it should go out quickly thru */ /* the high priority queue. */ /* Otherwise, it is an original for the low priority queue. */ if (ax25_get_num_repeaters(pp) >= 1 && ax25_get_h(pp,AX25_REPEATER_1)) { tq_append (port, TQ_PRIO_0_HI, pp); } else { tq_append (port, TQ_PRIO_1_LO, pp); } } break; case 1: /* TXDELAY */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port); xmit_set_txdelay (port, kiss_msg[1]); break; case 2: /* Persistence */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port); xmit_set_persist (port, kiss_msg[1]); break; case 3: /* SlotTime */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set SlotTime = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port); xmit_set_slottime (port, kiss_msg[1]); break; case 4: /* TXtail */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set TXtail = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port); xmit_set_txtail (port, kiss_msg[1]); break; case 5: /* FullDuplex */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set FullDuplex = %d, port %d - Ignored\n", kiss_msg[1], port); break; case 6: /* TNC specific */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set hardware - Ignored.\n"); // TODO: kiss_set_hardware (...) break; case 15: /* End KISS mode, port should be 15. */ /* Ignore it. */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol end KISS mode - Ignored.\n"); break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Invalid command %d\n", cmd); kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len); text_color_set(DW_COLOR_INFO); dw_printf ("Troubleshooting tip:\n"); dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n"); dw_printf ("all communication with the client application.\n"); break; } } /* end kiss_process_msg */ /*------------------------------------------------------------------- * * Name: kiss_set_hardware * * Purpose: Process the "set hardware" command. * * Inputs: * * * Description: This is new in version 1.5. "Set hardware" was previously ignored. * * There are times when the client app might want to send configuration * commands, such as modem speed, to the KISS TNC or inquire about its * current state. * * The immediate motivation for adding this is that one application wants * to know how many frames are currently in the transmit queue. This can * be used for throttling of large transmissions and performing some action * after the last frame has been sent. * * The original KISS protocol spec offers no guidance on what this might look * like. I'm aware of only two, drastically different, implementations: * * fldigi - http://www.w1hkj.com/FldigiHelp-3.22/kiss_command_page.html * * Everything is in human readable text in the form of: * COMMAND : [ parameter [ , parameter ... ] ] * * Used by applications, http://www.w1hkj.com/FldigiHelp/kiss_host_prgs_page.html * - BPQ32 * - UIChar * - YAAC * * mobilinkd - https://raw.githubusercontent.com/mobilinkd/tnc1/tnc2/bertos/net/kiss.c * * Single byte with the command / response code, followed by * zero or more value bytes. * * Used by applications: * - APRSdroid * * It would be beneficial to adopt one of them rather than doing something * completely different. It might even be possible to recognize both. * This might allow leveraging of other existing applications. * *--------------------------------------------------------------------*/ // static void kiss_set_hardware (...) /*------------------------------------------------------------------- * * Name: kiss_debug_print * * Purpose: Print message to/from client for debugging. * * Inputs: fromto - Direction of message. * special - Comment if not a KISS frame. * pmsg - Address of the message block. * msg_len - Length of the message. * *--------------------------------------------------------------------*/ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len) { const char *direction [2] = { "from", "to" }; const char *prefix [2] = { "<<<", ">>>" }; const char *function[16] = { "Data frame", "TXDELAY", "P", "SlotTime", "TXtail", "FullDuplex", "SetHardware", "Invalid 7", "Invalid 8", "Invalid 9", "Invalid 10", "Invalid 11", "Invalid 12", "Invalid 13", "Invalid 14", "Return" }; text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); if (special == NULL) { unsigned char *p; /* to skip over FEND if present. */ p = pmsg; if (*p == FEND) p++; dw_printf ("%s %s %s KISS client application, port %d, total length = %d\n", prefix[(int)fromto], function[p[0] & 0xf], direction[(int)fromto], (p[0] >> 4) & 0xf, msg_len); } else { dw_printf ("%s %s %s KISS client application, total length = %d\n", prefix[(int)fromto], special, direction[(int)fromto], msg_len); } hex_dump (pmsg, msg_len); } /* end kiss_debug_print */ #endif /* Quick unit test for encapsulate & unwrap */ // $ gcc -DKISSTEST kiss_frame.c ; ./a // Quick KISS test passed OK. #if KISSTEST int main () { unsigned char din[512]; unsigned char kissed[520]; unsigned char dout[520]; int klen; int dlen; int k; for (k = 0; k < 512; k++) { if (k < 256) { din[k] = k; } else { din[k] = 511 - k; } } klen = kiss_encapsulate (din, 512, kissed); assert (klen == 512 + 6); dlen = kiss_unwrap (kissed, klen, dout); assert (dlen == 512); assert (memcmp(din, dout, 512) == 0); dlen = kiss_unwrap (kissed+1, klen-1, dout); assert (dlen == 512); assert (memcmp(din, dout, 512) == 0); dw_printf ("Quick KISS test passed OK.\n"); exit (EXIT_SUCCESS); } #endif #endif /* WALK96 */ /* end kiss_frame.c */