//
// 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 */