mirror of https://github.com/wb2osz/direwolf.git
913 lines
25 KiB
C
913 lines
25 KiB
C
//
|
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
|
//
|
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Module: kissutil.c
|
|
*
|
|
* Purpose: Utility for talking to a KISS TNC.
|
|
*
|
|
* Description: Convert between KISS format and usual text representation.
|
|
* This might also serve as the starting point for an application
|
|
* that uses a KISS TNC.
|
|
* The TNC can be attached by TCP or a serial port.
|
|
*
|
|
* Usage: kissutil [ options ]
|
|
*
|
|
* Default is to connect to localhost:8001.
|
|
* See the "usage" functions at the bottom for details.
|
|
*
|
|
* FIXME: This is a rough prototype that needs more work.
|
|
*
|
|
*---------------------------------------------------------------*/
|
|
|
|
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
|
|
|
|
#if __WIN32__
|
|
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
|
|
|
|
#else
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/errno.h>
|
|
|
|
#endif
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
|
|
//#include "ax25_pad.h"
|
|
|
|
#include "textcolor.h"
|
|
#include "serial_port.h"
|
|
#include "kiss_frame.h"
|
|
#include "sock.h"
|
|
#include "dtime_now.h"
|
|
#include "audio.h" // for DEFAULT_TXDELAY, etc.
|
|
|
|
|
|
// TODO: define in one place, use everywhere.
|
|
#if __WIN32__
|
|
#define THREAD_F unsigned __stdcall
|
|
#else
|
|
#define THREAD_F void *
|
|
#endif
|
|
|
|
#if __WIN32__
|
|
#define DIR_CHAR "\\"
|
|
#else
|
|
#define DIR_CHAR "/"
|
|
#endif
|
|
|
|
static THREAD_F tnc_listen_net (void *arg);
|
|
static THREAD_F tnc_listen_serial (void *arg);
|
|
|
|
static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen);
|
|
static void hex_dump (unsigned char *p, int len);
|
|
|
|
|
|
// Why didn't I use send/recv for Linux?
|
|
|
|
#if __WIN32__
|
|
#define SOCK_SEND(s,data,size) send(s,data,size,0)
|
|
#define SOCK_RECV(s,data,size) recv(s,data,size,0)
|
|
#else
|
|
#define SOCK_SEND(s,data,size) write(s,data,size)
|
|
#define SOCK_RECV(s,data,size) read(s,data,size)
|
|
#endif
|
|
|
|
static void usage(void);
|
|
static void usage2(void);
|
|
|
|
|
|
|
|
|
|
/* Obtained from the command line. */
|
|
|
|
static char hostname[50] = "localhost"; /* -h option. */
|
|
/* DNS host name or IPv4 address. */
|
|
/* Some of the code is there for IPv6 but */
|
|
/* it needs more work. */
|
|
/* Defaults to "localhost" if not specified. */
|
|
|
|
static char port[30] = "8001"; /* -p option. */
|
|
/* If it begins with a digit, it is considered */
|
|
/* a TCP port number at the hostname. */
|
|
/* Otherwise, we treat it as a serial port name. */
|
|
|
|
static int using_tcp = 1; /* Are we using TCP or serial port for TNC? */
|
|
/* Use corresponding one of the next two. */
|
|
/* This is derived from the first character of port. */
|
|
|
|
static int server_sock = -1; /* File descriptor for socket interface. */
|
|
/* Set to -1 if not used. */
|
|
/* (Don't use SOCKET type because it is unsigned.) */
|
|
|
|
static MYFDTYPE serial_fd = (MYFDTYPE)(-1); /* Serial port handle. */
|
|
|
|
static int serial_speed = 9600; /* -s option. */
|
|
/* Serial port speed, bps. */
|
|
|
|
static int verbose = 0; /* -v option. */
|
|
/* Display the KISS protocol in hexadecimal for troubleshooting. */
|
|
|
|
static char transmit_from[120] = ""; /* -f option */
|
|
/* When specified, files are read from this directory */
|
|
/* rather than using stdin. Each file is one or more */
|
|
/* lines in the standard monitoring format. */
|
|
|
|
static char receive_output[120] = ""; /* -o option */
|
|
/* When specified, each received frame is stored as a file */
|
|
/* with a unique name here. */
|
|
/* Directory must already exist; we won't create it. */
|
|
|
|
static char timestamp_format[60] = ""; /* -T option */
|
|
/* Precede received frames with timestamp. */
|
|
/* Command line option uses "strftime" format string. */
|
|
|
|
|
|
#if __WIN32__
|
|
#define THREAD_F unsigned __stdcall
|
|
#else
|
|
#define THREAD_F void *
|
|
#endif
|
|
|
|
#if __WIN32__
|
|
static HANDLE tnc_th;
|
|
#else
|
|
static pthread_t tnc_tid;
|
|
#endif
|
|
|
|
static void process_input (char *stuff);
|
|
|
|
/* Trim any CR, LF from the end of line. */
|
|
|
|
static void trim (char *stuff)
|
|
{
|
|
char *p;
|
|
p = stuff + strlen(stuff) - 1;
|
|
while (strlen(stuff) > 0 && (*p == '\r' || *p == '\n')) {
|
|
*p = '\0';
|
|
p--;
|
|
}
|
|
} /* end trim */
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: main
|
|
*
|
|
* Purpose: Attach to KISS TNC and exchange information.
|
|
*
|
|
* Usage: See "usage" functions at end.
|
|
*
|
|
*---------------------------------------------------------------*/
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
text_color_init (0); // Turn off text color.
|
|
// It could interfere with trying to pipe stdout to some other application.
|
|
|
|
#if __WIN32__
|
|
|
|
#else
|
|
int e;
|
|
setlinebuf (stdout); // TODO: What is the Windows equivalent?
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Extract command line args.
|
|
*/
|
|
while (1) {
|
|
int option_index = 0;
|
|
int c;
|
|
static struct option long_options[] = {
|
|
//{"future1", 1, 0, 0},
|
|
//{"future2", 0, 0, 0},
|
|
//{"future3", 1, 0, 'c'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
/* ':' following option character means arg is required. */
|
|
|
|
c = getopt_long(argc, argv, "h:p:s:vf:o:T:",
|
|
long_options, &option_index);
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c) {
|
|
|
|
case 'h': /* -h for hostname. */
|
|
strlcpy (hostname, optarg, sizeof(hostname));
|
|
break;
|
|
|
|
case 'p': /* -p for port, either TCP or serial device. */
|
|
strlcpy (port, optarg, sizeof(port));
|
|
break;
|
|
|
|
case 's': /* -s for serial port speed. */
|
|
serial_speed = atoi(optarg);
|
|
break;
|
|
|
|
case 'v': /* -v for verbose. */
|
|
verbose++;
|
|
break;
|
|
|
|
case 'f': /* -f for transmit files directory. */
|
|
strlcpy (transmit_from, optarg, sizeof(transmit_from));
|
|
break;
|
|
|
|
case 'o': /* -o for receive output directory. */
|
|
strlcpy (receive_output, optarg, sizeof(receive_output));
|
|
break;
|
|
|
|
case 'T': /* -T for receive timestamp. */
|
|
strlcpy (timestamp_format, optarg, sizeof(timestamp_format));
|
|
break;
|
|
|
|
case '?':
|
|
/* Unknown option message was already printed. */
|
|
usage ();
|
|
break;
|
|
|
|
default:
|
|
/* Should not be here. */
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf("?? getopt returned character code 0%o ??\n", c);
|
|
usage ();
|
|
}
|
|
} /* end while(1) for options */
|
|
|
|
if (optind < argc) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Warning: Unused command line arguments are ignored.\n");
|
|
}
|
|
|
|
/*
|
|
* If receive queue directory was specified, make sure that it exists.
|
|
*/
|
|
if (strlen(receive_output) > 0) {
|
|
|
|
// TODO
|
|
}
|
|
|
|
/* If port begins with digit, consider it to be TCP. */
|
|
/* Otherwise, treat as serial port name. */
|
|
|
|
using_tcp = isdigit(port[0]);
|
|
|
|
#if __WIN32__
|
|
if (using_tcp) {
|
|
tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_net, (void *)99, 0, NULL);
|
|
}
|
|
else {
|
|
tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_serial, (void *)99, 0, NULL);
|
|
}
|
|
if (tnc_th == NULL) {
|
|
printf ("Internal error: Could not create TNC listen thread.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
#else
|
|
if (using_tcp) {
|
|
e = pthread_create (&tnc_tid, NULL, tnc_listen_net, (void *)(long)99);
|
|
}
|
|
else {
|
|
e = pthread_create (&tnc_tid, NULL, tnc_listen_serial, (void *)(long)99);
|
|
}
|
|
if (e != 0) {
|
|
perror("Internal error: Could not create TNC listen thread.");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Process keyboard or other input source.
|
|
*/
|
|
char stuff[1000];
|
|
|
|
if (strlen(transmit_from) > 0) {
|
|
/*
|
|
* Process and delete all files in specified directory.
|
|
* When done, sleep for a second and try again.
|
|
* This doesn't take them in any particular order.
|
|
* A future enhancement might sort by name or timestamp.
|
|
*/
|
|
while (1) {
|
|
DIR *dp;
|
|
struct dirent *ep;
|
|
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf("Get directory listing...\n");
|
|
|
|
dp = opendir (transmit_from);
|
|
if (dp != NULL) {
|
|
while ((ep = readdir(dp)) != NULL) {
|
|
char path [300];
|
|
FILE *fp;
|
|
if (ep->d_name[0] == '.')
|
|
continue;
|
|
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Processing %s for transmit...\n", ep->d_name);
|
|
strlcpy (path, transmit_from, sizeof(path));
|
|
strlcat (path, DIR_CHAR, sizeof(path));
|
|
strlcat (path, ep->d_name, sizeof(path));
|
|
fp = fopen (path, "r");
|
|
if (fp != NULL) {
|
|
while (fgets(stuff, sizeof(stuff), fp) != NULL) {
|
|
trim (stuff);
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("%s\n", stuff);
|
|
// TODO: Don't delete file if errors encountered?
|
|
process_input (stuff);
|
|
}
|
|
fclose (fp);
|
|
unlink (path);
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("Can't open for read: %s\n", path);
|
|
}
|
|
}
|
|
closedir (dp);
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("Can't access transmit queue directory %s. Quitting.\n", transmit_from);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
SLEEP_SEC (1);
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* Using stdin.
|
|
*/
|
|
while (fgets(stuff, sizeof(stuff), stdin) != NULL) {
|
|
process_input (stuff);
|
|
}
|
|
}
|
|
|
|
return (EXIT_SUCCESS);
|
|
|
|
} /* end main */
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: process_input
|
|
*
|
|
* Purpose: Process frames/commands from user, either interactively or from files.
|
|
*
|
|
* Inputs: stuff - A frame is in usual format like SOURCE>DEST,DIGI:whatever.
|
|
* Commands begin with lower case letter.
|
|
* Note that it can be modified by this function.
|
|
*
|
|
* Later Enhancement: Return success/fail status. The transmit queue processing might want
|
|
* to preserve files that were not processed as expected.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
static int parse_number (char *str, int de_fault)
|
|
{
|
|
int n;
|
|
|
|
while (isspace(*str)) {
|
|
str++;
|
|
}
|
|
if (strlen(str) == 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Missing number for KISS command. Using default %d.\n", de_fault);
|
|
return (de_fault);
|
|
}
|
|
n = atoi(str);
|
|
if (n < 0 || n > 255) { // must fit in a byte.
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Number for KISS command is out of range 0-255. Using default %d.\n", de_fault);
|
|
return (de_fault);
|
|
}
|
|
return (n);
|
|
}
|
|
|
|
static void process_input (char *stuff)
|
|
{
|
|
char *p;
|
|
int chan = 0;
|
|
|
|
/*
|
|
* Remove any end of line character(s).
|
|
*/
|
|
trim (stuff);
|
|
|
|
/*
|
|
* Optional prefix, like "[9]" to specify channel. TODO FIXME
|
|
*/
|
|
p = stuff;
|
|
|
|
/*
|
|
* If it starts with upper case letter or digit, assume it is an AX.25 frame in monitor format.
|
|
* Lower case is a command (e.g. Persistence or set Hardware).
|
|
* Anything else, print explanation of what is expected.
|
|
*/
|
|
if (isupper(*p) || isdigit(*p)) {
|
|
|
|
// Parse the "TNC2 monitor format" and convert to AX.25 frame.
|
|
|
|
unsigned char frame_data[AX25_MAX_PACKET_LEN];
|
|
packet_t pp = ax25_from_text (p, 1);
|
|
if (pp != NULL) {
|
|
int frame_len = ax25_pack (pp, frame_data);
|
|
send_to_kiss_tnc (chan, KISS_CMD_DATA_FRAME, (char*)frame_data, frame_len);
|
|
ax25_delete (pp);
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("ERROR! Could not convert to AX.25 frame: %s\n", p);
|
|
}
|
|
}
|
|
else if (islower(*p)) {
|
|
char value;
|
|
|
|
switch (*p) {
|
|
case 'd': // txDelay, 10ms units
|
|
value = parse_number(p+1, DEFAULT_TXDELAY);
|
|
send_to_kiss_tnc (chan, KISS_CMD_TXDELAY, &value, 1);
|
|
break;
|
|
case 'p': // Persistence
|
|
value = parse_number(p+1, DEFAULT_PERSIST);
|
|
send_to_kiss_tnc (chan, KISS_CMD_PERSISTENCE, &value, 1);
|
|
break;
|
|
case 's': // Slot time, 10ms units
|
|
value = parse_number(p+1, DEFAULT_SLOTTIME);
|
|
send_to_kiss_tnc (chan, KISS_CMD_SLOTTIME, &value, 1);
|
|
break;
|
|
case 't': // txTail, 10ms units
|
|
value = parse_number(p+1, DEFAULT_TXTAIL);
|
|
send_to_kiss_tnc (chan, KISS_CMD_TXTAIL, &value, 1);
|
|
break;
|
|
case 'f': // Full duplex
|
|
value = parse_number(p+1, 0);
|
|
send_to_kiss_tnc (chan, KISS_CMD_FULLDUPLEX, &value, 1);
|
|
break;
|
|
case 'h': // set Hardware
|
|
p++;
|
|
while (*p != '\0' && isspace(*p)) { p++; }
|
|
send_to_kiss_tnc (chan, KISS_CMD_SET_HARDWARE, p, strlen(p));
|
|
break;
|
|
default:
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Invalid command. Must be one of d p s t f h.\n");
|
|
usage2 ();
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
usage2 ();
|
|
}
|
|
|
|
} /* end process_input */
|
|
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: send_to_kiss_tnc
|
|
*
|
|
* Purpose: Encapsulate the data/command, into a KISS frame, and send to the TNC.
|
|
*
|
|
* Inputs: chan - channel number.
|
|
*
|
|
* cmd - KISS_CMD_DATA_FRAME, KISS_CMD_SET_HARDWARE, etc.
|
|
*
|
|
* data - Information for KISS frame.
|
|
*
|
|
* dlen - Number of bytes in data.
|
|
*
|
|
* Description: Encapsulate as KISS frame and send to TNC.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen)
|
|
{
|
|
unsigned char temp[1000];
|
|
unsigned char kissed[2000];
|
|
int klen;
|
|
|
|
if (chan < 0 || chan > 15) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("ERROR - Invalid channel %d - must be in range 0 to 15.\n", chan);
|
|
chan = 0;
|
|
}
|
|
if (cmd < 0 || cmd > 15) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("ERROR - Invalid command %d - must be in range 0 to 15.\n", cmd);
|
|
cmd = 0;
|
|
}
|
|
if (dlen < 0 || dlen > (int)(sizeof(temp)-1)) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("ERROR - Invalid data length %d - must be in range 0 to %d.\n", dlen, (int)(sizeof(temp)-1));
|
|
dlen = sizeof(temp)-1;
|
|
}
|
|
|
|
temp[0] = (chan << 4) | cmd;
|
|
memcpy (temp+1, data, dlen);
|
|
|
|
klen = kiss_encapsulate(temp, dlen+1, kissed);
|
|
|
|
if (verbose) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Sending to KISS TNC:\n");
|
|
hex_dump (kissed, klen);
|
|
}
|
|
|
|
// FIXME: Should check for non -1 server_sock or serial_fd.
|
|
// Might need to delay when not using interactive input.
|
|
|
|
if (using_tcp) {
|
|
int rc = SOCK_SEND(server_sock, (char*)kissed, klen);
|
|
(void)rc;
|
|
}
|
|
else {
|
|
serial_port_write (serial_fd, (char*)kissed, klen);
|
|
}
|
|
|
|
} /* end send_to_kiss_tnc */
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: tnc_listen_net
|
|
*
|
|
* Purpose: Connect to KISS TNC via TCP port.
|
|
* Print everything it sends to us.
|
|
*
|
|
* Inputs: arg - Currently not used.
|
|
*
|
|
* Global In: host
|
|
* port
|
|
*
|
|
* Global Out: server_sock - Needed to send to the TNC.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
//#define MAX_HOSTS 30
|
|
|
|
static THREAD_F tnc_listen_net (void *arg)
|
|
{
|
|
int err;
|
|
char ipaddr_str[SOCK_IPADDR_LEN]; // Text form of IP address.
|
|
char data[4096];
|
|
int allow_ipv6 = 0; // Maybe someday.
|
|
int debug = 0;
|
|
int client = 0; // Not used in this situation.
|
|
kiss_frame_t kstate;
|
|
|
|
memset (&kstate, 0, sizeof(kstate));
|
|
|
|
err = sock_init ();
|
|
if (err < 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Network interface failure. Can't go on.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Connect to network KISS TNC.
|
|
*/
|
|
// For the IGate we would loop around and try to reconnect if the TNC
|
|
// goes away. We should probably do the same here.
|
|
|
|
server_sock = sock_connect (hostname, port, "TCP KISS TNC", allow_ipv6, debug, ipaddr_str);
|
|
|
|
if (server_sock == -1) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
// Should have been a message already. What else is there to say?
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Print what we get from TNC.
|
|
*/
|
|
int len;
|
|
|
|
while ((len = SOCK_RECV (server_sock, (char*)(data), sizeof(data))) > 0) {
|
|
int j;
|
|
for (j = 0; j < len; j++) {
|
|
|
|
// Feed in one byte at a time.
|
|
// kiss_process_msg is called when a complete frame has been accumulated.
|
|
|
|
// When verbose is specified, we get debug output like this:
|
|
//
|
|
// <<< Data frame from KISS client application, port 0, total length = 46
|
|
// 000: c0 00 82 a0 88 ae 62 6a e0 ae 84 64 9e a6 b4 ff ......bj...d....
|
|
// ...
|
|
// It says "from KISS client application" because it was written
|
|
// on the assumption it was being used in only one direction.
|
|
// Not worried enough about it to do anything at this time.
|
|
|
|
kiss_rec_byte (&kstate, data[j], verbose, client, NULL);
|
|
}
|
|
}
|
|
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Read error from TCP KISS TNC. Terminating.\n");
|
|
exit (EXIT_FAILURE);
|
|
|
|
} /* end tnc_listen_net */
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: tnc_listen_serial
|
|
*
|
|
* Purpose: Connect to KISS TNC via serial port.
|
|
* Print everything it sends to us.
|
|
*
|
|
* Inputs: arg - Currently not used.
|
|
*
|
|
* Global In: port
|
|
* serial_speed
|
|
*
|
|
* Global Out: serial_fd - Need for sending to the TNC.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
static THREAD_F tnc_listen_serial (void *arg)
|
|
{
|
|
int client = 0;
|
|
kiss_frame_t kstate;
|
|
|
|
memset (&kstate, 0, sizeof(kstate));
|
|
|
|
serial_fd = serial_port_open (port, serial_speed);
|
|
|
|
if (serial_fd == MYFDERROR) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("Unable to connect to KISS TNC serial port %s.\n", port);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Read and print.
|
|
*/
|
|
while (1) {
|
|
int ch;
|
|
|
|
ch = serial_port_get1(serial_fd);
|
|
|
|
if (ch < 0) {
|
|
dw_printf("Read error from serial port KISS TNC.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
// Feed in one byte at a time.
|
|
// kiss_process_msg is called when a complete frame has been accumulated.
|
|
|
|
kiss_rec_byte (&kstate, ch, verbose, client, NULL);
|
|
}
|
|
|
|
} /* end tnc_listen_serial */
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: kiss_process_msg
|
|
*
|
|
* Purpose: Process a frame from the KISS TNC.
|
|
* This is called when a complete frame has been accumulated.
|
|
* In this case, we simply print it.
|
|
*
|
|
* 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 - Not used in this case.
|
|
*
|
|
* sendfun - Not used in this case.
|
|
*
|
|
*-----------------------------------------------------------------*/
|
|
|
|
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
|
|
{
|
|
int chan;
|
|
int cmd;
|
|
packet_t pp;
|
|
alevel_t alevel;
|
|
|
|
chan = (kiss_msg[0] >> 4) & 0xf;
|
|
cmd = kiss_msg[0] & 0xf;
|
|
|
|
switch (cmd)
|
|
{
|
|
case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */
|
|
|
|
memset (&alevel, 0, sizeof(alevel));
|
|
pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
|
|
if (pp == NULL) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
printf ("ERROR - Invalid KISS data frame from TNC.\n");
|
|
}
|
|
else {
|
|
char prefix[100]; // Channel and optional timestamp.
|
|
// Like [0] or [2 12:34:56]
|
|
|
|
char addrs[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN]; // Like source>dest,digi,...,digi:
|
|
unsigned char *pinfo;
|
|
int info_len;
|
|
|
|
if (strlen(timestamp_format) > 0) {
|
|
char ts[100];
|
|
timestamp_user_format (ts, sizeof(ts), timestamp_format);
|
|
snprintf (prefix, sizeof(prefix), "[%d %s]", chan, ts);
|
|
}
|
|
else {
|
|
snprintf (prefix, sizeof(prefix), "[%d]", chan);
|
|
}
|
|
|
|
ax25_format_addrs (pp, addrs);
|
|
|
|
info_len = ax25_get_info (pp, &pinfo);
|
|
|
|
text_color_set(DW_COLOR_REC);
|
|
|
|
dw_printf ("%s %s", prefix, addrs); // [channel] Addresses followed by :
|
|
|
|
// Safe print will replace any unprintable characters with
|
|
// hexadecimal representation.
|
|
|
|
ax25_safe_print ((char *)pinfo, info_len, 0);
|
|
dw_printf ("\n");
|
|
|
|
/*
|
|
* Add to receive queue directory if specified.
|
|
* File name will be based on current local time.
|
|
* If you want UTC, just set an environment variable like this:
|
|
*
|
|
* TZ=UTC kissutil ...
|
|
*/
|
|
if (strlen(receive_output) > 0) {
|
|
char fname [30];
|
|
char path [300];
|
|
FILE *fp;
|
|
|
|
timestamp_filename (fname, (int)sizeof(fname));
|
|
|
|
strlcpy (path, receive_output, sizeof(path));
|
|
strlcat (path, DIR_CHAR, sizeof(path));
|
|
strlcat (path, fname, sizeof(path));
|
|
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("Save received frame to %s\n", path);
|
|
fp = fopen (path, "w");
|
|
if (fp != NULL) {
|
|
fprintf (fp, "%s %s%s\n", prefix, addrs, pinfo);
|
|
fclose (fp);
|
|
}
|
|
else {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Unable to open for write: %s\n", path);
|
|
}
|
|
}
|
|
|
|
ax25_delete (pp);
|
|
}
|
|
break;
|
|
|
|
case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */
|
|
|
|
kiss_msg[kiss_len] = '\0';
|
|
text_color_set(DW_COLOR_REC);
|
|
// Display as "h ..." for in/out symmetry.
|
|
// Use safe print here?
|
|
dw_printf ("[%d] h %s\n", chan, (char*)(kiss_msg+1));
|
|
break;
|
|
|
|
/*
|
|
* The rest should only go TO the TNC and not come FROM it.
|
|
*/
|
|
case KISS_CMD_TXDELAY: /* 1 = TXDELAY */
|
|
case KISS_CMD_PERSISTENCE: /* 2 = Persistence */
|
|
case KISS_CMD_SLOTTIME: /* 3 = SlotTime */
|
|
case KISS_CMD_TXTAIL: /* 4 = TXtail */
|
|
case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */
|
|
case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */
|
|
default:
|
|
|
|
text_color_set(DW_COLOR_ERROR);
|
|
printf ("Unexpected KISS command %d, channel %d\n", cmd, chan);
|
|
break;
|
|
}
|
|
|
|
} /* end kiss_process_msg */
|
|
|
|
|
|
// TODO: We have multiple copies of this. Move to some misc file.
|
|
|
|
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<n; i++) {
|
|
printf (" %02x", p[i]);
|
|
}
|
|
for (i=n; i<16; i++) {
|
|
printf (" ");
|
|
}
|
|
printf (" ");
|
|
for (i=0; i<n; i++) {
|
|
printf ("%c", isprint(p[i]) ? p[i] : '.');
|
|
}
|
|
printf ("\n");
|
|
p += 16;
|
|
offset += 16;
|
|
len -= 16;
|
|
}
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("\n");
|
|
dw_printf ("kissutil - Utility for testing a KISS TNC.\n");
|
|
dw_printf ("\n");
|
|
dw_printf ("Convert between KISS format and usual text representation.\n");
|
|
dw_printf ("The TNC can be attached by TCP or a serial port.\n");
|
|
dw_printf ("\n");
|
|
dw_printf ("Usage: kissutil [ options ]\n");
|
|
dw_printf ("\n");
|
|
dw_printf (" -h hostname of TCP KISS TNC, default localhost.\n");
|
|
dw_printf (" -p port, default 8001.\n");
|
|
dw_printf (" If it does not start with a digit, it is\n");
|
|
dw_printf (" a serial port. e.g. /dev/ttyAMA0 or COM3.\n");
|
|
dw_printf (" -s Serial port speed, default 9600.\n");
|
|
dw_printf (" -v Verbose. Show the KISS frame contents.\n");
|
|
dw_printf (" -f Transmit files directory. Processs and delete files here.\n");
|
|
dw_printf (" -o Receive output queue directory. Store received frames here.\n");
|
|
dw_printf (" -T Precede received frames with 'strftime' format time stamp.\n");
|
|
usage2();
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
static void usage2 (void)
|
|
{
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("\n");
|
|
dw_printf ("Input, starting with upper case letter or digit, is assumed\n");
|
|
dw_printf ("to be an AX.25 frame in the usual TNC2 monitoring format.\n");
|
|
dw_printf ("\n");
|
|
dw_printf ("Input, starting with a lower case letter is a commmand.\n");
|
|
dw_printf ("Whitespace, as shown in examples, is optional.\n");
|
|
dw_printf ("\n");
|
|
dw_printf (" letter meaning example\n");
|
|
dw_printf (" ------ ------- -------\n");
|
|
dw_printf (" d txDelay, 10ms units d 30\n");
|
|
dw_printf (" p Persistence p 63\n");
|
|
dw_printf (" s Slot time, 10ms units s 10\n");
|
|
dw_printf (" t txTail, 10ms units t 5\n");
|
|
dw_printf (" f Full duplex f 0\n");
|
|
dw_printf (" h set Hardware h T.B.D.\n");
|
|
dw_printf ("\n");
|
|
dw_printf (" Lines may be preceded by the form \"[9]\" to indicate a\n");
|
|
dw_printf (" channel other than the default 0.\n");
|
|
dw_printf ("\n");
|
|
}
|
|
|
|
/* end kissutil.c */
|