// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 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: kissserial.c * * Purpose: Act as a virtual KISS TNC for use by other packet radio applications. * This file provides the service by good old fashioned serial port. * Other files implement a pseudo terminal or TCP KISS interface. * * Description: This implements the KISS TNC protocol as 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. * * * Platform differences: * * This file implements KISS over a serial port. * It should behave pretty much the same for both Windows and Linux. * * When running a client application on Windows, two applications * can be connected together using a a "Null-modem emulator" * such as com0com from http://sourceforge.net/projects/com0com/ * * (When running a client application, on the same host, with Linux, * a pseudo terminal can be used for old applications. More modern * applications will generally have AGW and/or KISS over TCP.) * * * version 1.5: Split out from kiss.c, simplified, consistent for Windows and Linux. * Add polling option for use with Bluetooth. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "serial_port.h" #include "kissserial.h" #include "kiss_frame.h" #include "xmit.h" #if __WIN32__ typedef HANDLE MYFDTYPE; #define MYFDERROR INVALID_HANDLE_VALUE #else typedef int MYFDTYPE; #define MYFDERROR (-1) #endif /* * Save Configuration for later use. */ static struct misc_config_s *g_misc_config_p; /* * Accumulated KISS frame and state of decoder. */ static kiss_frame_t kf; /* * The serial port device handle. */ static MYFDTYPE serialport_fd = MYFDERROR; // TODO: define in one place, use everywhere. #if __WIN32__ #define THREAD_F unsigned __stdcall #else #define THREAD_F void * #endif static THREAD_F kissserial_listen_thread (void *arg); static int kissserial_debug = 0; /* Print information flowing from and to client. */ void kissserial_set_debug (int n) { kissserial_debug = n; } /* In server.c. Should probably move to some misc. function file. */ void hex_dump (unsigned char *p, int len); /*------------------------------------------------------------------- * * Name: kissserial_init * * Purpose: Set up a serial port acting as a virtual KISS TNC. * * Inputs: mc-> * kiss_serial_port - Name of device for real or virtual serial port. * kiss_serial_speed - Speed, bps, or 0 meaning leave it alone. * kiss_serial_poll - When non-zero, poll each n seconds to see if * device has appeared. * * Outputs: * * Description: (1) Open file descriptor for the device. * (2) Start a new thread to listen for commands from client app * so the main application doesn't block while we wait. * *--------------------------------------------------------------------*/ void kissserial_init (struct misc_config_s *mc) { #if __WIN32__ HANDLE kissserial_listen_th; #else pthread_t kissserial_listen_tid; int e; #endif g_misc_config_p = mc; memset (&kf, 0, sizeof(kf)); if (strlen(g_misc_config_p->kiss_serial_port) > 0) { if (g_misc_config_p->kiss_serial_poll == 0) { // Normal case, try to open the serial port at start up time. serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed); if (serialport_fd != MYFDERROR) { text_color_set(DW_COLOR_INFO); dw_printf ("Opened %s for serial port KISS.\n", g_misc_config_p->kiss_serial_port); } else { // An error message was already displayed. } } else { // Polling case. Defer until read and device not opened. text_color_set(DW_COLOR_INFO); dw_printf ("Will be checking periodically for %s\n", g_misc_config_p->kiss_serial_port); } if (g_misc_config_p->kiss_serial_poll != 0 || serialport_fd != MYFDERROR) { #if __WIN32__ kissserial_listen_th = (HANDLE)_beginthreadex (NULL, 0, kissserial_listen_thread, NULL, 0, NULL); if (kissserial_listen_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create kiss serial thread\n"); return; } #else e = pthread_create (&kissserial_listen_tid, NULL, kissserial_listen_thread, NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create kiss serial thread."); } #endif } } #if DEBUG text_color_set (DW_COLOR_DEBUG); dw_printf ("end of kiss_init: serialport_fd = %d, polling = %d\n", serialport_fd, g_misc_config_p->kiss_serial_poll); #endif } /*------------------------------------------------------------------- * * Name: kissserial_send_rec_packet * * Purpose: Send a received packet or text string to the client app. * * Inputs: chan - Channel number where packet was received. * 0 = first, 1 = second if any. * * pp - Identifier for packet object. * * fbuf - Address of raw received frame buffer * or a text string. * * flen - Length of raw received frame not including the FCS * or -1 for a text string. * * client - Not used for serial port version. * Here so that 3 related functions all have * the same parameter list. * * Description: Send message to client. * We really don't care if anyone is listening or not. * I don't even know if we can find out. * *--------------------------------------------------------------------*/ void kissserial_send_rec_packet (int chan, unsigned char *fbuf, int flen, int client) { unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2]; int kiss_len; int err; /* * Quietly discard if we don't have open connection. */ if (serialport_fd == MYFDERROR) { return; } if (flen < 0) { flen = strlen((char*)fbuf); if (kissserial_debug) { kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen); } strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff)); kiss_len = strlen((char *)kiss_buff); } else { unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; if (flen > (int)(sizeof(stemp)) - 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nSerial Port KISS buffer too small. Truncated.\n\n"); flen = (int)(sizeof(stemp)) - 1; } stemp[0] = (chan << 4) + 0; memcpy (stemp+1, fbuf, flen); if (kissserial_debug >= 2) { /* AX.25 frame with the CRC removed. */ text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf ("Packet content before adding KISS framing and any escapes:\n"); hex_dump (fbuf, flen); } kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff); /* This has KISS framing and escapes for sending to client app. */ if (kissserial_debug) { kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len); } } /* * This write can block on Windows if using the virtual null modem * and nothing is connected to the other end. * The solution is found in the com0com ReadMe file: * * Q. My application hangs during its startup when it sends anything to one paired * COM port. The only way to unhang it is to start HyperTerminal, which is connected * to the other paired COM port. I didn't have this problem with physical serial * ports. * A. Your application can hang because receive buffer overrun is disabled by * default. You can fix the problem by enabling receive buffer overrun for the * receiving port. Also, to prevent some flow control issues you need to enable * baud rate emulation for the sending port. So, if your application use port CNCA0 * and other paired port is CNCB0, then: * * 1. Launch the Setup Command Prompt shortcut. * 2. Enter the change commands, for example: * * command> change CNCB0 EmuOverrun=yes * command> change CNCA0 EmuBR=yes */ err = serial_port_write (serialport_fd, (char*)kiss_buff, kiss_len); if (err != kiss_len) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending KISS message to client application thru serial port.\n\n"); serial_port_close (serialport_fd); serialport_fd = MYFDERROR; } } /* kissserial_send_rec_packet */ /*------------------------------------------------------------------- * * Name: kissserial_get * * Purpose: Read one byte from the KISS client app. * * Global In: serialport_fd * * Returns: one byte (value 0 - 255) or terminate thread on error. * * Description: There is room for improvment here. Reading one byte * at a time is inefficient. We could read a large block * into a local buffer and return a byte from that most of the time. * Is it worth the effort? I don't know. With GHz processors and * the low data rate here it might not make a noticable difference. * *--------------------------------------------------------------------*/ static int kissserial_get (void) { int ch; // normally 0-255 but -1 for error. if (g_misc_config_p->kiss_serial_poll == 0) { /* * Normal case, was opened at start up time. */ ch = serial_port_get1 (serialport_fd); if (ch < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n"); serial_port_close (serialport_fd); serialport_fd = MYFDERROR; #if __WIN32__ ExitThread (0); #else pthread_exit (NULL); #endif } #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("kissserial_get(%d) returns 0x%02x\n", fd, ch); #endif return (ch); } /* * Polling case. Wait until device is present and open. */ while (1) { if (serialport_fd != MYFDERROR) { // Open, try to read. ch = serial_port_get1 (serialport_fd); if (ch >= 0) { return (ch); } text_color_set(DW_COLOR_ERROR); dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n"); serial_port_close (serialport_fd); serialport_fd = MYFDERROR; } else { // Not open. Wait for it to appear and try opening. struct stat buf; SLEEP_SEC (g_misc_config_p->kiss_serial_poll); if (stat(g_misc_config_p->kiss_serial_port, &buf) == 0) { // It's there now. Try to open. serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed); if (serialport_fd != MYFDERROR) { text_color_set(DW_COLOR_INFO); dw_printf ("\nOpened %s for serial port KISS.\n\n", g_misc_config_p->kiss_serial_port); memset (&kf, 0, sizeof(kf)); // Start with clean state. } else { // An error message was already displayed. } } } } } /* end kissserial_get */ /*------------------------------------------------------------------- * * Name: kissserial_listen_thread * * Purpose: Read messages from serial port KISS client application. * * Global In: serialport_fd * * Description: Reads bytes from the serial port KISS client app and * sends them to kiss_rec_byte for processing. * kiss_rec_byte is a common function used by all 3 KISS * interfaces: serial port, pseudo terminal, and TCP. * *--------------------------------------------------------------------*/ static THREAD_F kissserial_listen_thread (void *arg) { unsigned char ch; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("kissserial_listen_thread ( %d )\n", fd); #endif while (1) { ch = kissserial_get(); kiss_rec_byte (&kf, ch, kissserial_debug, -1, kissserial_send_rec_packet); } #if __WIN32__ return(0); #else return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */ #endif } /* end kissserial.c */