mirror of https://github.com/wb2osz/direwolf.git
924 lines
22 KiB
C
924 lines
22 KiB
C
|
//
|
|||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
|||
|
//
|
|||
|
// Copyright (C) 2011,2013 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 <http://www.gnu.org/licenses/>.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/*------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Module: kiss.c
|
|||
|
*
|
|||
|
* Purpose: Act as a virtual KISS TNC for use by other packet radio applications.
|
|||
|
*
|
|||
|
* Input:
|
|||
|
*
|
|||
|
* Outputs:
|
|||
|
*
|
|||
|
* Description: This provides a pseudo terminal for communication with a client application.
|
|||
|
*
|
|||
|
* It 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. Always full duplex.
|
|||
|
*
|
|||
|
* 6 SetHardware TNC specific. Ignored.
|
|||
|
*
|
|||
|
* FF Return Exit KISS mode. Ignored.
|
|||
|
*
|
|||
|
*
|
|||
|
* Messages sent to client application:
|
|||
|
*
|
|||
|
* 0 Data Frame Received AX.25 frame in raw format.
|
|||
|
*
|
|||
|
*
|
|||
|
*
|
|||
|
* Platform differences:
|
|||
|
*
|
|||
|
* We can use a pseudo terminal for Linux or Cygwin applications.
|
|||
|
* However, Microsoft Windows doesn't seem to have similar functionality.
|
|||
|
* Native Windows applications expect to see a device named COM1,
|
|||
|
* COM2, COM3, or COM4. Some might offer more flexibility but others
|
|||
|
* might be limited to these four choices.
|
|||
|
*
|
|||
|
* The documentation instucts the user to install the com0com
|
|||
|
* <EFBFBD>Null-modem emulator<EFBFBD> from http://sourceforge.net/projects/com0com/
|
|||
|
* and configure it for COM3 & COM4.
|
|||
|
*
|
|||
|
* By default Dire Wolf will use COM3 (/dev/ttyS2 or /dev/com3 - lower case!)
|
|||
|
* and the client application will use COM4 (available as /dev/ttyS or
|
|||
|
* /dev/com4 for Cygwin applications).
|
|||
|
*
|
|||
|
*
|
|||
|
* This can get confusing.
|
|||
|
*
|
|||
|
* If __WIN32__ is defined,
|
|||
|
* We use the Windows interface to the specfied serial port.
|
|||
|
* This could be a real serial port or the nullmodem driver
|
|||
|
* connected to another application.
|
|||
|
*
|
|||
|
* If __CYGWIN__ is defined,
|
|||
|
* We connect to a serial port as in the previous case but
|
|||
|
* use the Linux I/O interface.
|
|||
|
* We also supply a pseudo terminal for any Cygwin applications
|
|||
|
* such as Xastir so the null modem is not needed.
|
|||
|
*
|
|||
|
* For the Linux case,
|
|||
|
* We supply a pseudo terminal for use by other applications.
|
|||
|
*
|
|||
|
*
|
|||
|
* Reference: http://www.robbayer.com/files/serial-win.pdf
|
|||
|
*
|
|||
|
*---------------------------------------------------------------*/
|
|||
|
|
|||
|
#include <stdio.h>
|
|||
|
#include <unistd.h>
|
|||
|
|
|||
|
#if __WIN32__
|
|||
|
#include <stdlib.h>
|
|||
|
#include <windows.h>
|
|||
|
#else
|
|||
|
#define __USE_XOPEN2KXSI 1
|
|||
|
#define __USE_XOPEN 1
|
|||
|
//#define __USE_POSIX 1
|
|||
|
#include <stdlib.h>
|
|||
|
#include <ctype.h>
|
|||
|
#include <fcntl.h>
|
|||
|
#include <termios.h>
|
|||
|
#include <sys/types.h>
|
|||
|
#include <sys/ioctl.h>
|
|||
|
#include <sys/errno.h>
|
|||
|
#endif
|
|||
|
|
|||
|
#include <assert.h>
|
|||
|
#include <string.h>
|
|||
|
|
|||
|
#include "direwolf.h"
|
|||
|
#include "tq.h"
|
|||
|
#include "ax25_pad.h"
|
|||
|
#include "textcolor.h"
|
|||
|
#include "kiss.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
|
|||
|
|
|||
|
|
|||
|
static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
* These are for a Linux/Cygwin pseudo terminal.
|
|||
|
*/
|
|||
|
|
|||
|
#if ! __WIN32__
|
|||
|
|
|||
|
static MYFDTYPE pt_master_fd = MYFDERROR; /* File descriptor for my end. */
|
|||
|
|
|||
|
static MYFDTYPE pt_slave_fd = MYFDERROR; /* File descriptor for pseudo terminal */
|
|||
|
/* for use by application. */
|
|||
|
|
|||
|
/*
|
|||
|
* Symlink to pseudo terminal name which changes.
|
|||
|
*/
|
|||
|
|
|||
|
#define DEV_KISS_TNC "/tmp/kisstnc"
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
/*
|
|||
|
* This is for native Windows applications and a virtual null modem.
|
|||
|
*/
|
|||
|
|
|||
|
#if __CYGWIN__ || __WIN32__
|
|||
|
|
|||
|
static MYFDTYPE nullmodem_fd = MYFDERROR;
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
static void * kiss_listen_thread (void *arg);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#if DEBUG9
|
|||
|
static FILE *log_fp;
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
static int kiss_debug = 0; /* Print information flowing from and to client. */
|
|||
|
|
|||
|
void kiss_serial_set_debug (int n)
|
|||
|
{
|
|||
|
kiss_debug = n;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* In server.c. Should probably move to some misc. function file. */
|
|||
|
|
|||
|
void hex_dump (unsigned char *p, int len);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/*-------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Name: kiss_init
|
|||
|
*
|
|||
|
* Purpose: Set up a pseudo terminal acting as a virtual KISS TNC.
|
|||
|
*
|
|||
|
*
|
|||
|
* Inputs: mc->nullmodem - name of device for our end of nullmodem.
|
|||
|
*
|
|||
|
* Outputs:
|
|||
|
*
|
|||
|
* Description: (1) Create a pseudo terminal for the client to use.
|
|||
|
* (2) Start a new thread to listen for commands from client app
|
|||
|
* so the main application doesn't block while we wait.
|
|||
|
*
|
|||
|
*
|
|||
|
*--------------------------------------------------------------------*/
|
|||
|
|
|||
|
static MYFDTYPE kiss_open_pt (void);
|
|||
|
static MYFDTYPE kiss_open_nullmodem (char *device);
|
|||
|
|
|||
|
void kiss_init (struct misc_config_s *mc)
|
|||
|
{
|
|||
|
int e;
|
|||
|
#if __WIN32__
|
|||
|
HANDLE kiss_nullmodem_listen_th;
|
|||
|
#else
|
|||
|
pthread_t kiss_pterm_listen_tid;
|
|||
|
pthread_t kiss_nullmodem_listen_tid;
|
|||
|
#endif
|
|||
|
|
|||
|
memset (&kf, 0, sizeof(kf));
|
|||
|
|
|||
|
/*
|
|||
|
* This reads messages from client.
|
|||
|
*/
|
|||
|
|
|||
|
#if ! __WIN32__
|
|||
|
|
|||
|
/*
|
|||
|
* Pseudo terminal for Cygwin and Linux versions.
|
|||
|
*/
|
|||
|
pt_master_fd = MYFDERROR;
|
|||
|
|
|||
|
if (mc->enable_kiss_pt) {
|
|||
|
|
|||
|
pt_master_fd = kiss_open_pt ();
|
|||
|
|
|||
|
if (pt_master_fd != MYFDERROR) {
|
|||
|
e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kiss_listen_thread, (void*)(long)pt_master_fd);
|
|||
|
if (e != 0) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
perror("Could not create kiss listening thread for Linux pseudo terminal");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
text_color_set(DW_COLOR_INFO);
|
|||
|
dw_printf ("Use -p command line option to enable KISS pseudo terminal.\n");
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
#if __CYGWIN__ || __WIN32
|
|||
|
|
|||
|
/*
|
|||
|
* Cygwin and native Windows versions have serial port connection.
|
|||
|
*/
|
|||
|
if (strlen(mc->nullmodem) > 0) {
|
|||
|
|
|||
|
#if ! __WIN32__
|
|||
|
|
|||
|
/* Translate Windows device name into Linux name. */
|
|||
|
/* COM1 -> /dev/ttyS0, etc. */
|
|||
|
|
|||
|
if (strncasecmp(mc->nullmodem, "COM", 3) == 0) {
|
|||
|
int n = atoi (mc->nullmodem + 3);
|
|||
|
text_color_set(DW_COLOR_INFO);
|
|||
|
dw_printf ("Converted nullmodem device '%s'", mc->nullmodem);
|
|||
|
if (n < 1) n = 1;
|
|||
|
sprintf (mc->nullmodem, "/dev/ttyS%d", n-1);
|
|||
|
dw_printf (" to Linux equivalent '%s'\n", mc->nullmodem);
|
|||
|
}
|
|||
|
#endif
|
|||
|
nullmodem_fd = kiss_open_nullmodem (mc->nullmodem);
|
|||
|
|
|||
|
if (nullmodem_fd != MYFDERROR) {
|
|||
|
#if __WIN32__
|
|||
|
kiss_nullmodem_listen_th = _beginthreadex (NULL, 0, kiss_listen_thread, (void*)(long)nullmodem_fd, 0, NULL);
|
|||
|
if (kiss_nullmodem_listen_th == NULL) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("Could not create kiss nullmodem thread\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
#else
|
|||
|
e = pthread_create (&kiss_nullmodem_listen_tid, NULL, kiss_listen_thread, (void*)(long)nullmodem_fd);
|
|||
|
if (e != 0) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
perror("Could not create kiss listening thread for Windows virtual COM port.");
|
|||
|
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
#if DEBUG
|
|||
|
text_color_set (DW_COLOR_DEBUG);
|
|||
|
#if ! __WIN32__
|
|||
|
dw_printf ("end of kiss_init: pt_master_fd = %d\n", pt_master_fd);
|
|||
|
#endif
|
|||
|
#if __CYGWIN__ || __WIN32__
|
|||
|
dw_printf ("end of kiss_init: nullmodem_fd = %d\n", nullmodem_fd);
|
|||
|
#endif
|
|||
|
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
* Returns fd for master side of pseudo terminal or MYFDERROR for error.
|
|||
|
*/
|
|||
|
|
|||
|
#if ! __WIN32__
|
|||
|
|
|||
|
static MYFDTYPE kiss_open_pt (void)
|
|||
|
{
|
|||
|
int fd;
|
|||
|
char *slave_device;
|
|||
|
struct termios ts;
|
|||
|
int e;
|
|||
|
//int flags;
|
|||
|
|
|||
|
#if DEBUG
|
|||
|
text_color_set(DW_COLOR_DEBUG);
|
|||
|
dw_printf ("kiss_open_pt ( )\n");
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
fd = posix_openpt(O_RDWR|O_NOCTTY);
|
|||
|
|
|||
|
if (fd == MYFDERROR
|
|||
|
|| grantpt (fd) == MYFDERROR
|
|||
|
|| unlockpt (fd) == MYFDERROR
|
|||
|
|| (slave_device = ptsname (fd)) == NULL) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("ERROR - Could not create pseudo terminal for KISS TNC.\n");
|
|||
|
return (MYFDERROR);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
e = tcgetattr (fd, &ts);
|
|||
|
if (e != 0) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("Can't get pseudo terminal attributes, err=%d\n", e);
|
|||
|
perror ("pt tcgetattr");
|
|||
|
}
|
|||
|
|
|||
|
cfmakeraw (&ts);
|
|||
|
|
|||
|
ts.c_cc[VMIN] = 1; /* wait for at least one character */
|
|||
|
ts.c_cc[VTIME] = 0; /* no fancy timing. */
|
|||
|
|
|||
|
|
|||
|
e = tcsetattr (fd, TCSANOW, &ts);
|
|||
|
if (e != 0) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("Can't set pseudo terminal attributes, err=%d\n", e);
|
|||
|
perror ("pt tcsetattr");
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* After running for a while on Linux, the write eventually
|
|||
|
* blocks if no one is reading from the other side of
|
|||
|
* the pseudo terminal. We get stuck on the kiss data
|
|||
|
* write and reception stops.
|
|||
|
*
|
|||
|
* I tried using ioctl(,TIOCOUTQ,) to see how much was in
|
|||
|
* the queue but that always returned zero. (Ubuntu)
|
|||
|
*
|
|||
|
* Let's try using non-blocking writes and see if we get
|
|||
|
* the EWOULDBLOCK status instead of hanging.
|
|||
|
*/
|
|||
|
|
|||
|
#if 0 // this is worse. all writes fail. errno = 0 bad file descriptor
|
|||
|
flags = fcntl(fd, F_GETFL, 0);
|
|||
|
e = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
|
|||
|
if (e != 0) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno);
|
|||
|
perror ("pt fcntl");
|
|||
|
}
|
|||
|
#endif
|
|||
|
#if 0 // same
|
|||
|
flags = 1;
|
|||
|
e = ioctl (fd, FIONBIO, &flags);
|
|||
|
if (e != 0) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno);
|
|||
|
perror ("pt ioctl");
|
|||
|
}
|
|||
|
#endif
|
|||
|
text_color_set(DW_COLOR_INFO);
|
|||
|
dw_printf("Virtual KISS TNC is available on %s\n", slave_device);
|
|||
|
dw_printf("WARNING - Dire Wolf will hang eventually if nothing is reading from it.\n");
|
|||
|
|
|||
|
/*
|
|||
|
* The device name is not the same every time.
|
|||
|
* This is inconvenient for the application because it might
|
|||
|
* be necessary to change the device name in the configuration.
|
|||
|
* Create a symlink, /tmp/kisstnc, so the application configuration
|
|||
|
* does not need to change when the pseudo terminal name changes.
|
|||
|
*/
|
|||
|
|
|||
|
//TODO: remove symlink on exit.
|
|||
|
unlink (DEV_KISS_TNC);
|
|||
|
|
|||
|
if (symlink (slave_device, DEV_KISS_TNC) == 0) {
|
|||
|
dw_printf ("Created symlink %s -> %s\n", DEV_KISS_TNC, slave_device);
|
|||
|
}
|
|||
|
else {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("Failed to create symlink %s\n", DEV_KISS_TNC);
|
|||
|
perror ("");
|
|||
|
}
|
|||
|
|
|||
|
#if 1
|
|||
|
// Sample code shows this. Why would we open it here?
|
|||
|
// On Ubuntu, the slave side disappears after a few
|
|||
|
// seconds if no one opens it.
|
|||
|
|
|||
|
pt_slave_fd = open(slave_device, O_RDWR|O_NOCTTY);
|
|||
|
|
|||
|
if (pt_slave_fd < 0)
|
|||
|
return MYFDERROR;
|
|||
|
#endif
|
|||
|
return (fd);
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
/*
|
|||
|
* Returns fd for our side of null modem or MYFDERROR for error.
|
|||
|
*/
|
|||
|
|
|||
|
|
|||
|
#if __CYGWIN__ || __WIN32__
|
|||
|
|
|||
|
static MYFDTYPE kiss_open_nullmodem (char *devicename)
|
|||
|
{
|
|||
|
|
|||
|
#if __WIN32__
|
|||
|
|
|||
|
MYFDTYPE fd;
|
|||
|
DCB dcb;
|
|||
|
int ok;
|
|||
|
|
|||
|
|
|||
|
#if DEBUG
|
|||
|
text_color_set(DW_COLOR_DEBUG);
|
|||
|
dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
|
|||
|
#endif
|
|||
|
|
|||
|
#if DEBUG9
|
|||
|
log_fp = fopen ("kiss-debug.txt", "w");
|
|||
|
#endif
|
|||
|
|
|||
|
// Need to use FILE_FLAG_OVERLAPPED for full duplex operation.
|
|||
|
// Without it, write blocks when waiting on read.
|
|||
|
|
|||
|
// Read http://support.microsoft.com/kb/156932
|
|||
|
|
|||
|
|
|||
|
fd = CreateFile(devicename, GENERIC_READ | GENERIC_WRITE,
|
|||
|
0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
|
|||
|
|
|||
|
if (fd == MYFDERROR) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\n", devicename);
|
|||
|
return (MYFDERROR);
|
|||
|
}
|
|||
|
|
|||
|
/* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */
|
|||
|
|
|||
|
memset (&dcb, 0, sizeof(dcb));
|
|||
|
dcb.DCBlength = sizeof(DCB);
|
|||
|
|
|||
|
ok = GetCommState (fd, &dcb);
|
|||
|
if (! ok) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("kiss_open_nullmodem: GetCommState failed.\n");
|
|||
|
}
|
|||
|
|
|||
|
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
|
|||
|
|
|||
|
// dcb.BaudRate ? shouldn't matter
|
|||
|
dcb.fBinary = 1;
|
|||
|
dcb.fParity = 0;
|
|||
|
dcb.fOutxCtsFlow = 0;
|
|||
|
dcb.fOutxDsrFlow = 0;
|
|||
|
dcb.fDtrControl = 0;
|
|||
|
dcb.fDsrSensitivity = 0;
|
|||
|
dcb.fOutX = 0;
|
|||
|
dcb.fInX = 0;
|
|||
|
dcb.fErrorChar = 0;
|
|||
|
dcb.fNull = 0; /* Don't drop nul characters! */
|
|||
|
dcb.fRtsControl = 0;
|
|||
|
dcb.ByteSize = 8;
|
|||
|
dcb.Parity = NOPARITY;
|
|||
|
dcb.StopBits = ONESTOPBIT;
|
|||
|
|
|||
|
ok = SetCommState (fd, &dcb);
|
|||
|
if (! ok) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("kiss_open_nullmodem: SetCommState failed.\n");
|
|||
|
}
|
|||
|
|
|||
|
text_color_set(DW_COLOR_INFO);
|
|||
|
dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
|
|||
|
|
|||
|
#else
|
|||
|
|
|||
|
/* Cygwin version. */
|
|||
|
|
|||
|
int fd;
|
|||
|
struct termios ts;
|
|||
|
int e;
|
|||
|
|
|||
|
|
|||
|
#if DEBUG
|
|||
|
text_color_set(DW_COLOR_DEBUG);
|
|||
|
dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
|
|||
|
#endif
|
|||
|
|
|||
|
fd = open (devicename, O_RDWR);
|
|||
|
|
|||
|
if (fd == MYFDERROR) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\n", devicename);
|
|||
|
return (MYFDERROR);
|
|||
|
}
|
|||
|
|
|||
|
e = tcgetattr (fd, &ts);
|
|||
|
if (e != 0) { perror ("nm tcgetattr"); }
|
|||
|
|
|||
|
cfmakeraw (&ts);
|
|||
|
|
|||
|
ts.c_cc[VMIN] = 1; /* wait for at least one character */
|
|||
|
ts.c_cc[VTIME] = 0; /* no fancy timing. */
|
|||
|
|
|||
|
e = tcsetattr (fd, TCSANOW, &ts);
|
|||
|
if (e != 0) { perror ("nm tcsetattr"); }
|
|||
|
|
|||
|
text_color_set(DW_COLOR_INFO);
|
|||
|
dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
return (fd);
|
|||
|
}
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/*-------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Name: kiss_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.
|
|||
|
*
|
|||
|
*
|
|||
|
* 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 kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
|
|||
|
{
|
|||
|
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
|
|||
|
int kiss_len;
|
|||
|
int j;
|
|||
|
int err;
|
|||
|
|
|||
|
#if ! __WIN32__
|
|||
|
if (pt_master_fd == MYFDERROR) {
|
|||
|
return;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
#if __CYGWIN__ || __WIN32__
|
|||
|
|
|||
|
if (nullmodem_fd == MYFDERROR) {
|
|||
|
return;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
if (flen < 0) {
|
|||
|
flen = strlen((char*)fbuf);
|
|||
|
if (kiss_debug) {
|
|||
|
kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
|
|||
|
}
|
|||
|
strcpy ((char *)kiss_buff, (char *)fbuf);
|
|||
|
kiss_len = strlen((char *)kiss_buff);
|
|||
|
}
|
|||
|
else {
|
|||
|
|
|||
|
kiss_len = 0;
|
|||
|
kiss_buff[kiss_len++] = FEND;
|
|||
|
kiss_buff[kiss_len++] = chan << 4;
|
|||
|
|
|||
|
for (j=0; j<flen; j++) {
|
|||
|
|
|||
|
if (fbuf[j] == FEND) {
|
|||
|
kiss_buff[kiss_len++] = FESC;
|
|||
|
kiss_buff[kiss_len++] = TFEND;
|
|||
|
}
|
|||
|
else if (fbuf[j] == FESC) {
|
|||
|
kiss_buff[kiss_len++] = FESC;
|
|||
|
kiss_buff[kiss_len++] = TFESC;
|
|||
|
}
|
|||
|
else {
|
|||
|
kiss_buff[kiss_len++] = fbuf[j];
|
|||
|
}
|
|||
|
assert (kiss_len < sizeof (kiss_buff));
|
|||
|
}
|
|||
|
kiss_buff[kiss_len++] = FEND;
|
|||
|
|
|||
|
/* This has the escapes but not the surrounding FENDs. */
|
|||
|
|
|||
|
if (kiss_debug) {
|
|||
|
kiss_debug_print (TO_CLIENT, NULL, kiss_buff+1, kiss_len-2);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
#if ! __WIN32__
|
|||
|
|
|||
|
/* Pseudo terminal for Cygwin and Linux. */
|
|||
|
|
|||
|
|
|||
|
err = write (pt_master_fd, kiss_buff, (size_t)kiss_len);
|
|||
|
|
|||
|
if (err == -1 && errno == EWOULDBLOCK) {
|
|||
|
#if DEBUG
|
|||
|
text_color_set (DW_COLOR_INFO);
|
|||
|
dw_printf ("KISS SEND - discarding message because write would block.\n");
|
|||
|
#endif
|
|||
|
}
|
|||
|
else if (err != kiss_len)
|
|||
|
{
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("\nError sending KISS message to client application on pseudo terminal. fd=%d, len=%d, write returned %d, errno = %d\n\n",
|
|||
|
pt_master_fd, kiss_len, err, errno);
|
|||
|
perror ("pt write");
|
|||
|
}
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
#if __CYGWIN__ || __WIN32__
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
* This write can block if 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
|
|||
|
*/
|
|||
|
|
|||
|
#if __WIN32__
|
|||
|
|
|||
|
DWORD nwritten;
|
|||
|
|
|||
|
/* Without this, write blocks while we are waiting on a read. */
|
|||
|
static OVERLAPPED ov_wr;
|
|||
|
memset (&ov_wr, 0, sizeof(ov_wr));
|
|||
|
|
|||
|
if ( ! WriteFile (nullmodem_fd, kiss_buff, kiss_len, &nwritten, &ov_wr))
|
|||
|
{
|
|||
|
err = GetLastError();
|
|||
|
if (err != ERROR_IO_PENDING)
|
|||
|
{
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("\nError sending KISS message to client application thru null modem. Error %d.\n\n", (int)GetLastError());
|
|||
|
//CloseHandle (nullmodem_fd);
|
|||
|
//nullmodem_fd = MYFDERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
else if (nwritten != flen)
|
|||
|
{
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("\nError sending KISS message to client application thru null modem. Only %d of %d written.\n\n", (int)nwritten, kiss_len);
|
|||
|
//CloseHandle (nullmodem_fd);
|
|||
|
//nullmodem_fd = MYFDERROR;
|
|||
|
}
|
|||
|
|
|||
|
#if DEBUG
|
|||
|
/* Could wait with GetOverlappedResult but we never */
|
|||
|
/* have an issues in this direction. */
|
|||
|
//text_color_set(DW_COLOR_DEBUG);
|
|||
|
//dw_printf ("KISS SEND completed. wrote %d / %d\n", nwritten, kiss_len);
|
|||
|
#endif
|
|||
|
|
|||
|
#else
|
|||
|
err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len);
|
|||
|
if (err != len)
|
|||
|
{
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("\nError sending KISS message to client application thru null modem. err=%d\n\n", err);
|
|||
|
//close (nullmodem_fd);
|
|||
|
//nullmodem_fd = MYFDERROR;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
} /* kiss_send_rec_packet */
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/*-------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Name: kiss_listen_thread
|
|||
|
*
|
|||
|
* Purpose: Wait for messages from an application.
|
|||
|
*
|
|||
|
* Inputs: arg - File descriptor for reading.
|
|||
|
*
|
|||
|
* Outputs: pt_slave_fd - File descriptor for communicating with client app.
|
|||
|
*
|
|||
|
* Description: Process messages from the client application.
|
|||
|
*
|
|||
|
*--------------------------------------------------------------------*/
|
|||
|
|
|||
|
//TODO: should pass fd by reference so it can be zapped.
|
|||
|
//BUG: If we close it here, that fact doesn't get back
|
|||
|
// to the main receiving thread.
|
|||
|
|
|||
|
/* Return one byte (value 0 - 255) or terminate thread on error. */
|
|||
|
|
|||
|
|
|||
|
static int kiss_get (MYFDTYPE fd)
|
|||
|
{
|
|||
|
unsigned char ch;
|
|||
|
|
|||
|
#if __WIN32__ /* Native Windows version. */
|
|||
|
|
|||
|
DWORD n;
|
|||
|
static OVERLAPPED ov_rd;
|
|||
|
|
|||
|
memset (&ov_rd, 0, sizeof(ov_rd));
|
|||
|
ov_rd.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
|
|||
|
|
|||
|
|
|||
|
/* Overlapped I/O makes reading rather complicated. */
|
|||
|
/* See: http://msdn.microsoft.com/en-us/library/ms810467.aspx */
|
|||
|
|
|||
|
/* It seems that the read completes OK with a count */
|
|||
|
/* of 0 every time we send a message to the serial port. */
|
|||
|
|
|||
|
n = 0; /* Number of characters read. */
|
|||
|
|
|||
|
while (n == 0) {
|
|||
|
|
|||
|
if ( ! ReadFile (fd, &ch, 1, &n, &ov_rd))
|
|||
|
{
|
|||
|
int err1 = GetLastError();
|
|||
|
|
|||
|
if (err1 == ERROR_IO_PENDING)
|
|||
|
{
|
|||
|
/* Wait for completion. */
|
|||
|
|
|||
|
if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0)
|
|||
|
{
|
|||
|
if ( ! GetOverlappedResult (fd, &ov_rd, &n, 1))
|
|||
|
{
|
|||
|
int err3 = GetLastError();
|
|||
|
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("\nKISS GetOverlappedResult error %d.\n\n", err3);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Success! n should be 1 */
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("\nKISS ReadFile error %d. Closing connection.\n\n", err1);
|
|||
|
//CloseHandle (fd);
|
|||
|
//fd = MYFDERROR;
|
|||
|
//pthread_exit (NULL);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} /* end while n==0 */
|
|||
|
|
|||
|
CloseHandle(ov_rd.hEvent);
|
|||
|
|
|||
|
if (n != 1) {
|
|||
|
text_color_set(DW_COLOR_ERROR);
|
|||
|
dw_printf ("\nKISS failed to get one byte. n=%d.\n\n", (int)n);
|
|||
|
|
|||
|
#if DEBUG9
|
|||
|
fprintf (log_fp, "n=%d\n", n);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#else /* Linux/Cygwin version */
|
|||
|
|
|||
|
int n;
|
|||
|
|
|||
|
n = read(fd, &ch, (size_t)1);
|
|||
|
|
|||
|
if (n != 1) {
|
|||
|
//text_color_set(DW_COLOR_ERROR);
|
|||
|
//dw_printf ("\nError receiving kiss message from client application. Closing connection %d.\n\n", fd);
|
|||
|
|
|||
|
close (fd);
|
|||
|
|
|||
|
fd = MYFDERROR;
|
|||
|
pthread_exit (NULL);
|
|||
|
}
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
#if DEBUGx
|
|||
|
text_color_set(DW_COLOR_DEBUG);
|
|||
|
dw_printf ("kiss_get(%d) returns 0x%02x\n", fd, ch);
|
|||
|
#endif
|
|||
|
|
|||
|
#if DEBUG9
|
|||
|
fprintf (log_fp, "%02x %c %c", ch,
|
|||
|
isprint(ch) ? ch : '.' ,
|
|||
|
(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
|
|||
|
if (ch == FEND) fprintf (log_fp, " FEND");
|
|||
|
if (ch == FESC) fprintf (log_fp, " FESC");
|
|||
|
if (ch == TFEND) fprintf (log_fp, " TFEND");
|
|||
|
if (ch == TFESC) fprintf (log_fp, " TFESC");
|
|||
|
if (ch == '\r') fprintf (log_fp, " CR");
|
|||
|
if (ch == '\n') fprintf (log_fp, " LF");
|
|||
|
fprintf (log_fp, "\n");
|
|||
|
if (ch == FEND) fflush (log_fp);
|
|||
|
#endif
|
|||
|
return (ch);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
static void * kiss_listen_thread (void *arg)
|
|||
|
{
|
|||
|
MYFDTYPE fd = (MYFDTYPE)(long)arg;
|
|||
|
|
|||
|
unsigned char ch;
|
|||
|
|
|||
|
#if DEBUG
|
|||
|
text_color_set(DW_COLOR_DEBUG);
|
|||
|
dw_printf ("kiss_listen_thread ( %d )\n", fd);
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
while (1) {
|
|||
|
ch = kiss_get(fd);
|
|||
|
|
|||
|
if (kiss_frame (&kf, ch, kiss_debug, kiss_send_rec_packet)) {
|
|||
|
kiss_process_msg (&kf, kiss_debug);
|
|||
|
}
|
|||
|
} /* while (1) */
|
|||
|
|
|||
|
return (NULL); /* Unreachable but avoids compiler warning. */
|
|||
|
}
|
|||
|
|
|||
|
/* end kiss.c */
|