// // 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 TNC protocol is described in http://www.ka9q.net/papers/kiss.html * * ( An extended form, to handle multiple TNCs on a single serial port. * Not applicable for our situation. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf ) * * 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 (radio channel) in upper nybble. * * command in lower nybble. * * * Commands from application tp TNC: * * _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 Full Duplex. Transmit immediately without * waiting for channel to be clear. * * _6 SetHardware TNC specific. * * _C XKISS extension - not supported. * _E XKISS extention - not supported. * * FF Return Exit KISS mode. Ignored. * * * Messages sent to client application: * * _0 Data Frame Received AX.25 frame in raw format. * * _6 SetHardware TNC specific. * Usually a response to a query. * *---------------------------------------------------------------*/ #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" #include "version.h" #include "kissnet.h" /* In server.c. Should probably move to some misc. function file. */ void hex_dump (unsigned char *p, int len); #ifdef KISSUTIL void hex_dump (unsigned char *p, int len) { int n, i, offset; offset = 0; while (len > 0) { n = len < 16 ? len : 16; printf (" %03x: ", offset); for (i=0; i * <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,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'; } #ifndef KISSUTIL /* Try to appease client app by sending something back. */ if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 || strcasecmp("reset\r", (char*)(kf->noise)) == 0) { // first 2 parameters don't matter when length is -1 indicating text. (*sendfun) (0, 0, (unsigned char *)"\xc0\xc0", -1, client); } else { (*sendfun) (0, 0, (unsigned char *)"\r\ncmd:", -1, client); } #endif 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, client, sendfun); 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. * * client - Client app number for TCP KISS. * Should be -1 for pseudo termal and serial port. * * sendfun - Function to send something to the client application. * "Set Hardware" can send a response. * *-----------------------------------------------------------------*/ #ifndef KISSUTIL // All these ifdefs in here are a sign that this should be refactored. // Should split this into multiple files. // Some functions are only for the TNC end. // Other functions are suitble for both TNC and client app. // This is used only by the TNC sided. void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)) { int port; // Should rename to chan because that's what we use everywhere else. int cmd; packet_t pp; alevel_t alevel; port = (kiss_msg[0] >> 4) & 0xf; cmd = kiss_msg[0] & 0xf; switch (cmd) { case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */ if (client >= 0) { kissnet_copy (kiss_msg, kiss_len, port, cmd, client); } /* Special hack - Discard apparently bad data from Linux AX25. */ /* Note July 2017: There is a variant of of KISS, called SMACK, that assumes */ /* a TNC can never have more than 8 ports. http://symek.de/g/smack.html */ /* It uses the MSB to indicate that a checksum is added. I wonder if this */ /* is why we sometimes hear about a request to transmit on channel 8. */ /* Should we have a message that asks the user if SMACK is being used, */ /* and if so, turn it off in the application configuration? */ /* Our current default is a maximum of 6 channels but it is easily */ /* increased by changing one number and recompiling. */ // Additional information, from Mike Playle, December 2018, for Issue #42 // // I came across this the other day with Xastir, and took a quick look. // The problem is fixable without the kiss_frame.c hack, which doesn't help with Xastir anyway. // // Workaround // // After the kissattach command, put the interface into CRC mode "none" with a command like this: // // # kissparms -c 1 -p radio // // Analysis // // The source of this behaviour is the kernel's KISS implementation: // // https://elixir.bootlin.com/linux/v4.9/source/drivers/net/hamradio/mkiss.c#L489 // // It defaults to starting in state CRC_MODE_SMACK_TEST and ending up in mode CRC_NONE // after the first two packets, which have their framing byte modified by this code in the process. // It looks to me like deliberate behaviour on the kernel's part. // // Setting the CRC mode explicitly before sending any packets stops this state machine from running. // // Is this a bug? I don't know - that's up to you! Maybe it would make sense for Direwolf to set // the CRC mode itself, or to expect this behaviour and ignore these flags on the first packets // received from the Linux pty. // // This workaround seems sound to me, though, so perhaps this is just a documentation issue. if (kiss_len > 16 && (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 KISS_CMD_TXDELAY: /* 1 = TXDELAY */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for TXDELAY command.\n"); return; } 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); if (kiss_msg[1] < 4 || kiss_msg[1] > 100) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for TXDELAY?\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_txdelay (port, kiss_msg[1]); break; case KISS_CMD_PERSISTENCE: /* 2 = Persistence */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for PERSISTENCE command.\n"); return; } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port); if (kiss_msg[1] < 5 || kiss_msg[1] > 250) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for PERSIST?\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_persist (port, kiss_msg[1]); break; case KISS_CMD_SLOTTIME: /* 3 = SlotTime */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for SLOTTIME command.\n"); return; } 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); if (kiss_msg[1] < 2 || kiss_msg[1] > 50) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for SLOTTIME?\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_slottime (port, kiss_msg[1]); break; case KISS_CMD_TXTAIL: /* 4 = TXtail */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for TXTAIL command.\n"); return; } 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); if (kiss_msg[1] < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Setting TXTAIL so low is asking for trouble. You probably don't want to do this.\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_txtail (port, kiss_msg[1]); break; case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for FULLDUPLEX command.\n"); return; } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kiss_msg[1], port); xmit_set_fulldup (port, kiss_msg[1]); break; case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for SET HARDWARE command.\n"); return; } kiss_msg[kiss_len] = '\0'; text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set hardware \"%s\", port %d\n", (char*)(kiss_msg+1), port); kiss_set_hardware (port, (char*)(kiss_msg+1), debug, client, sendfun); break; case KISS_CMD_END_KISS: /* 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"); if (cmd == XKISS_CMD_DATA || cmd == XKISS_CMD_POLL) { dw_printf ("\n"); dw_printf ("It looks like you are trying to use the \"XKISS\" protocol which is not supported.\n"); dw_printf ("Change your application settings to use standard \"KISS\" rather than some other variant.\n"); dw_printf ("If you are using Winlink Express, configure like this:\n"); dw_printf (" Packet TNC Type: KISS\n"); dw_printf (" Packet TNC Model: NORMAL -- Using ACKMODE will cause this error.\n"); dw_printf ("\n"); } break; } } /* end kiss_process_msg */ #endif // ifndef KISSUTIL /*------------------------------------------------------------------- * * Name: kiss_set_hardware * * Purpose: Process the "set hardware" command. * * Inputs: chan - channel, 0 - 15. * * command - All but the first byte. e.g. "TXBUF:99" * Case sensitive. * Will be modified so be sure caller doesn't care. * * debug - debug level. * * client - Client app number for TCP KISS. * Needed so we can send any response to the right client app. * Ignored for pseudo terminal and serial port. * * sendfun - Function to send something to the client application. * * This is the tricky part. We can have any combination of * serial port, pseudo terminal, and multiple TCP clients. * We need to send the response to same place where query came * from. The function is different for each class of device * and we need a client number for the TCP case because we * can have multiple TCP KISS clients at the same time. * * * 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 "Set Hardware" 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 in both directions: * * COMMAND: [ parameter [ , parameter ... ] ] * * Lack of a parameter, in the client to TNC direction, is a query * which should generate a response in the same format. * * 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. * * Let's start with the easy to understand human readable format. * * Commands: (Client to TNC, with parameter(s) to set something.) * * none yet * * Queries: (Client to TNC, no parameters, generate a response.) * * Query Response Comment * ----- -------- ------- * * TNC: TNC:DIREWOLF 9.9 9.9 represents current version. * * TXBUF: TXBUF:999 Number of bytes (not frames) in transmit queue. * *--------------------------------------------------------------------*/ #ifndef KISSUTIL static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)) { char *param; char response[100]; param = strchr (command, ':'); if (param != NULL) { *param = '\0'; param++; if (strcmp(command, "TNC") == 0) { /* TNC - Identify software version. */ if (strlen(param) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware TNC: Did not expect a parameter.\n"); } snprintf (response, sizeof(response), "DIREWOLF %d.%d", MAJOR_VERSION, MINOR_VERSION); (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client); } else if (strcmp(command, "TXBUF") == 0) { /* TXBUF - Number of bytes in transmit queue. */ if (strlen(param) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware TXBUF: Did not expect a parameter.\n"); } int n = tq_count (chan, -1, "", "", 1); snprintf (response, sizeof(response), "TXBUF:%d", n); (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware unrecognized command: %s.\n", command); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware \"%s\" expected the form COMMAND:[parameter[,parameter...]]\n", command); } return; } /* end kiss_set_hardware */ #endif // ifndef KISSUTIL /*------------------------------------------------------------------- * * 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) { #ifndef KISSUTIL 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" }; #endif text_color_set(DW_COLOR_DEBUG); #ifdef KISSUTIL dw_printf ("From KISS TNC:\n"); #else 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); } #endif hex_dump (pmsg, msg_len); } /* end kiss_debug_print */ #endif #endif /* DECAMAIN */ /* 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 /* KISSTEST */ #endif /* WALK96 */ /* end kiss_frame.c */