New kissutil application.

This commit is contained in:
wb2osz 2017-09-11 21:53:38 -04:00
parent 0dbb376129
commit 678b09df3f
7 changed files with 1563 additions and 40 deletions

View File

@ -4,8 +4,7 @@
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc
all : $(APPS) direwolf.desktop direwolf.conf all : $(APPS) direwolf.desktop direwolf.conf
@echo " " @echo " "
@ -255,18 +254,18 @@ endif
# but the initial test doesn't look good. About 2.3 times as slow. # but the initial test doesn't look good. About 2.3 times as slow.
# If you want to use OSS (for FreeBSD, OpenBSD) instead of # If you want to use OSS (for FreeBSD, OpenBSD) instead of
# ALSA (for Linux), comment out (or remove) the two lines below. # ALSA (for Linux), comment out (or remove) the line below.
# TODO: Can we automate this somehow? # TODO: Can we automate this somehow?
CFLAGS += -DUSE_ALSA alsa = 1
LDFLAGS += -lasound
ifeq ($(wildcard /usr/include/pthread.h),) ifeq ($(wildcard /usr/include/pthread.h),)
$(error /usr/include/pthread.h does not exist. Install it with "sudo apt-get install libc6-dev" or "sudo yum install libc6-dev" ) $(error /usr/include/pthread.h does not exist. Install it with "sudo apt-get install libc6-dev" or "sudo yum install libc6-dev" )
endif endif
ifneq ($(USE_ALSA),) ifneq ($(alsa),)
CFLAGS += -DUSE_ALSA
LDFLAGS += -lasound
ifeq ($(wildcard /usr/include/alsa/asoundlib.h),) ifeq ($(wildcard /usr/include/alsa/asoundlib.h),)
$(error /usr/include/alsa/asoundlib.h does not exist. Install it with "sudo apt-get install libasound2-dev" or "sudo yum install libasound2-dev" ) $(error /usr/include/alsa/asoundlib.h does not exist. Install it with "sudo apt-get install libasound2-dev" or "sudo yum install libasound2-dev" )
endif endif
@ -377,9 +376,9 @@ tocalls-symbols :
# Separate application to decode raw data. # Separate application to decode raw data.
# First two use .c rather than .o because they depend on DECAMAIN definition. # First three use .c rather than .o because they depend on DECAMAIN definition.
decode_aprs : decode_aprs.c kiss_frame.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a
$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS)
@ -429,6 +428,13 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a
$(CC) $(CFLAGS) -g -o $@ $^ $(CC) $(CFLAGS) -g -o $@ $^
# Talk to a KISS TNC.
# Note: kiss_frame.c has conditional compilation on KISSUTIL.
kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o misc.a
$(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^
# Touch Tone to Speech sample application. # Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a
@ -562,6 +568,7 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
$(INSTALL) gen_packets $(INSTALLDIR)/bin $(INSTALL) gen_packets $(INSTALLDIR)/bin
$(INSTALL) atest $(INSTALLDIR)/bin $(INSTALL) atest $(INSTALLDIR)/bin
$(INSTALL) ttcalc $(INSTALLDIR)/bin $(INSTALL) ttcalc $(INSTALLDIR)/bin
$(INSTALL) kissutil $(INSTALLDIR)/bin
$(INSTALL) dwespeak.sh $(INSTALLDIR)/bin $(INSTALL) dwespeak.sh $(INSTALLDIR)/bin
# #
# Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory. # Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory.

View File

@ -16,7 +16,7 @@
# #
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest kissutil
# People say we need -mthreads option for threads to work properly. # People say we need -mthreads option for threads to work properly.
@ -30,6 +30,8 @@ CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_
AR := ar AR := ar
CFLAGS += -g CFLAGS += -g
# TEMP EXPERIMENT - DO NOT RELEASE
#CFLAGS += -fsanitize=undefined
# For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3. # For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3.
@ -159,9 +161,9 @@ tocalls-symbols :
# Separate application to decode raw data. # Separate application to decode raw data.
# First two use .c rather than .o because they depend on DECAMAIN definition. # First three use .c rather than .o because they depend on DECAMAIN definition.
decode_aprs : decode_aprs.c kiss_frame.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^ $(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^
@ -556,6 +558,14 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# Talk to a KISS TNC.
# Note: kiss_frame.c has conditional compilation on KISSUTIL.
kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o sock.o dtime_now.o misc.a regex.a
$(CC) $(CFLAGS) -DKISSUTIL -o $@ $^ -lwinmm -lws2_32
# Touch Tone to Speech sample application. # Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a
@ -641,6 +651,7 @@ dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2l
gen_packets.exe \ gen_packets.exe \
atest.exe \ atest.exe \
ttcalc.exe \ ttcalc.exe \
kissutil.exe \
dwespeak.bat \ dwespeak.bat \
telemetry-toolkit/* telemetry-toolkit/*
rm README-doc.md rm README-doc.md

View File

@ -27,6 +27,9 @@
* *
* Description: The KISS TNS protocol is described in http://www.ka9q.net/papers/kiss.html * Description: The KISS TNS 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 * Briefly, a frame is composed of
* *
* * FEND (0xC0) * * FEND (0xC0)
@ -58,6 +61,9 @@
* _5 FullDuplex Ignored. * _5 FullDuplex Ignored.
* *
* _6 SetHardware TNC specific. * _6 SetHardware TNC specific.
*
* _C XKISS extension - not supported.
* _E XKISS extention - not supported.
* *
* FF Return Exit KISS mode. Ignored. * FF Return Exit KISS mode. Ignored.
* *
@ -86,9 +92,32 @@
/* In server.c. Should probably move to some misc. function file. */ /* In server.c. Should probably move to some misc. function file. */
void hex_dump (unsigned char *p, int len); 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<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;
}
}
#endif
#if KISSTEST #if KISSTEST
@ -102,11 +131,17 @@ void text_color_set (dw_color_t c)
#else #else
#ifndef DECAMAIN #ifndef DECAMAIN
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug); #ifndef KISSUTIL
static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
#endif
#endif #endif
#endif #endif
#if KISSUTIL
#define text_color_set(x) ;
#define dw_printf printf
#endif
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
@ -332,7 +367,7 @@ int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,unsigned char*,int,int)) 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); //dw_printf ("kiss_frame ( %c %02x ) \n", ch, ch);
@ -370,14 +405,17 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
kf->noise[kf->noise_len] = '\0'; kf->noise[kf->noise_len] = '\0';
} }
#ifndef KISSUTIL
/* Try to appease client app by sending something back. */ /* Try to appease client app by sending something back. */
if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 || if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 ||
strcasecmp("reset\r", (char*)(kf->noise)) == 0) { strcasecmp("reset\r", (char*)(kf->noise)) == 0) {
(*sendfun) (0, (unsigned char *)"\xc0\xc0", -1, client); // first 2 parameters don't matter when length is -1 indicating text.
(*sendfun) (0, 0, (unsigned char *)"\xc0\xc0", -1, client);
} }
else { else {
(*sendfun) (0, (unsigned char *)"\r\ncmd:", -1, client); (*sendfun) (0, 0, (unsigned char *)"\r\ncmd:", -1, client);
} }
#endif
kf->noise_len = 0; kf->noise_len = 0;
} }
return; return;
@ -421,7 +459,7 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
hex_dump (unwrapped+1, ulen-1); hex_dump (unwrapped+1, ulen-1);
} }
kiss_process_msg (unwrapped, ulen, debug); kiss_process_msg (unwrapped, ulen, debug, client, sendfun);
kf->state = KS_SEARCHING; kf->state = KS_SEARCHING;
return; return;
@ -456,11 +494,22 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
* *
* kiss_len - Number of bytes including the command. * kiss_len - Number of bytes including the command.
* *
* debug - Debug option is selected. * debug - Debug option is selected.
*
* client - Client app number for TCP KISS.
* Ignored for pseudo termal and serial port.
*
* sendfun - Function to send something to the client application.
* "Set Hardware" can send a response.
* *
*-----------------------------------------------------------------*/ *-----------------------------------------------------------------*/
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug) #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.
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; int port;
int cmd; int cmd;
@ -472,10 +521,19 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
switch (cmd) switch (cmd)
{ {
case 0: /* Data Frame */ case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */
/* Special hack - Discard apparently bad data from Linux AX25. */ /* 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. */
if ((port == 2 || port == 8) && if ((port == 2 || port == 8) &&
kiss_msg[1] == 'Q' << 1 && kiss_msg[1] == 'Q' << 1 &&
kiss_msg[2] == 'S' << 1 && kiss_msg[2] == 'S' << 1 &&
@ -525,50 +583,69 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
} }
break; break;
case 1: /* TXDELAY */ case KISS_CMD_TXDELAY: /* 1 = TXDELAY */
text_color_set(DW_COLOR_INFO); 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); 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]); xmit_set_txdelay (port, kiss_msg[1]);
break; break;
case 2: /* Persistence */ case KISS_CMD_PERSISTENCE: /* 2 = Persistence */
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port); 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]); xmit_set_persist (port, kiss_msg[1]);
break; break;
case 3: /* SlotTime */ case KISS_CMD_SLOTTIME: /* 3 = SlotTime */
text_color_set(DW_COLOR_INFO); 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); 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]); xmit_set_slottime (port, kiss_msg[1]);
break; break;
case 4: /* TXtail */ case KISS_CMD_TXTAIL: /* 4 = TXtail */
text_color_set(DW_COLOR_INFO); 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); 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]); xmit_set_txtail (port, kiss_msg[1]);
break; break;
case 5: /* FullDuplex */ case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set FullDuplex = %d, port %d - Ignored\n", kiss_msg[1], port); dw_printf ("KISS protocol set FullDuplex = %d, port %d - Ignored\n", kiss_msg[1], port);
break; break;
case 6: /* TNC specific */ case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */
kiss_msg[kiss_len] = '\0';
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set hardware - Ignored.\n"); 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);
// TODO: kiss_set_hardware (...)
break; break;
case 15: /* End KISS mode, port should be 15. */ case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */
/* Ignore it. */ /* Ignore it. */
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol end KISS mode - Ignored.\n"); dw_printf ("KISS protocol end KISS mode - Ignored.\n");
@ -584,11 +661,19 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n"); dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n");
dw_printf ("all communication with the client application.\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 ("\n");
}
break; break;
} }
} /* end kiss_process_msg */ } /* end kiss_process_msg */
#endif // ifndef KISSUTIL
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
@ -596,7 +681,25 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
* *
* Purpose: Process the "set hardware" command. * Purpose: Process the "set hardware" command.
* *
* Inputs: * 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.
* 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. * Description: This is new in version 1.5. "Set hardware" was previously ignored.
@ -635,10 +738,61 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
* completely different. It might even be possible to recognize both. * completely different. It might even be possible to recognize both.
* This might allow leveraging of other existing applications. * This might allow leveraging of other existing applications.
* *
* Let's start with the easy to understand human readable format.
*
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
// static void kiss_set_hardware (...) #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, "TXBUF") == 0) { /* Number of frames 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");
}
// See what we have in the transmit queue for specified channel.
// fldigi uses bytes but frames seems to make more sense in this situation.
// Do we add one if PTT is on? That information doesn't seem to be easily available.
// TODO: FIXME: not implemented yet.
// n = tq_count (chan, TQ_PRIO_0_HI) + tq_count (chan, TQ_PRIO_1_LO);
snprintf (response, sizeof(response), "TXBUF:whatever");
(*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client);
}
else if (strcmp(command, "TNC") == 0) { /* Identify software version. */
; // TODO...
}
else if (strcmp(command, "TRXS") == 0) {
; // TODO... BUSY
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware invalid command.\n");
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware expected the form COMMAND:[parameter[,parameter...]]\n");
}
return;
} /* end kiss_set_hardware */
#endif // ifndef KISSUTIL
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
@ -657,6 +811,7 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len) 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 *direction [2] = { "from", "to" };
const char *prefix [2] = { "<<<", ">>>" }; const char *prefix [2] = { "<<<", ">>>" };
const char *function[16] = { const char *function[16] = {
@ -664,11 +819,14 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
"TXtail", "FullDuplex", "SetHardware", "Invalid 7", "TXtail", "FullDuplex", "SetHardware", "Invalid 7",
"Invalid 8", "Invalid 9", "Invalid 10", "Invalid 11", "Invalid 8", "Invalid 9", "Invalid 10", "Invalid 11",
"Invalid 12", "Invalid 13", "Invalid 14", "Return" }; "Invalid 12", "Invalid 13", "Invalid 14", "Return" };
#endif
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
#ifdef KISSUTIL
dw_printf ("From KISS TNC:\n");
#else
dw_printf ("\n");
if (special == NULL) { if (special == NULL) {
unsigned char *p; /* to skip over FEND if present. */ unsigned char *p; /* to skip over FEND if present. */
@ -684,6 +842,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
prefix[(int)fromto], special, direction[(int)fromto], prefix[(int)fromto], special, direction[(int)fromto],
msg_len); msg_len);
} }
#endif
hex_dump (pmsg, msg_len); hex_dump (pmsg, msg_len);
} /* end kiss_debug_print */ } /* end kiss_debug_print */

View File

@ -4,6 +4,25 @@
#include "audio.h" /* for struct audio_s */ #include "audio.h" /* for struct audio_s */
/*
* The first byte of a KISS frame has:
* channel in upper nybble.
* command in lower nybble.
*/
#define KISS_CMD_DATA_FRAME 0
#define KISS_CMD_TXDELAY 1
#define KISS_CMD_PERSISTENCE 2
#define KISS_CMD_SLOTTIME 3
#define KISS_CMD_TXTAIL 4
#define KISS_CMD_FULLDUPLEX 5
#define KISS_CMD_SET_HARDWARE 6
#define XKISS_CMD_DATA 12 // Not supported. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf
#define XKISS_CMD_POLL 14 // Not supported.
#define KISS_CMD_END_KISS 15
/* /*
* Special characters used by SLIP protocol. * Special characters used by SLIP protocol.
*/ */
@ -14,6 +33,7 @@
#define TFESC 0xDD #define TFESC 0xDD
enum kiss_state_e { enum kiss_state_e {
KS_SEARCHING = 0, /* Looking for FEND to start KISS frame. */ KS_SEARCHING = 0, /* Looking for FEND to start KISS frame. */
/* Must be 0 so we can simply zero whole structure to initialize. */ /* Must be 0 so we can simply zero whole structure to initialize. */
@ -41,17 +61,20 @@ typedef struct kiss_frame_s {
} kiss_frame_t; } kiss_frame_t;
#ifndef KISSUTIL
void kiss_frame_init (struct audio_s *pa); void kiss_frame_init (struct audio_s *pa);
#endif
int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out); int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out);
int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out); int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out);
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,unsigned char*,int,int)); void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t; typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len); void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
/* end kiss_frame.h */ /* end kiss_frame.h */

866
kissutil.c Normal file
View File

@ -0,0 +1,866 @@
//
// 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.
* TODO: Prepare the "man" page.
* TODO: Add to User Guide.
*
*---------------------------------------------------------------*/
#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"
// 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_queue[120] = ""; /* -t 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_queue[120] = ""; /* -r 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. */
#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:vt:r:",
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 't': /* -t for transmit queue directory. */
strlcpy (transmit_queue, optarg, sizeof(transmit_queue));
break;
case 'r': /* -t for receive queue directory. */
strlcpy (receive_queue, optarg, sizeof(receive_queue));
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_queue) > 0) {
}
/* 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_queue) > 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_queue);
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_queue, 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_queue);
exit (EXIT_FAILURE);
}
SLEEP_SEC (1);
}
}
else {
/*
* Using stdin.
*/
while (fgets(stuff, sizeof(stuff), stdin) != NULL) {
process_input (stuff);
}
}
} /* 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 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
// TODO: should check for range 0 - 255.
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_TXDELAY, &value, 1);
break;
case 'p': // Persistence
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_PERSISTENCE, &value, 1);
break;
case 's': // Slot time, 10ms units
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_SLOTTIME, &value, 1);
break;
case 't': // txTelay, 10ms units
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_TXTAIL, &value, 1);
break;
case 'f': // Full duplex
value = atoi(p+1);
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, 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) {
SOCK_SEND(server_sock, (char*)kissed, klen);
}
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.
*/
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);
// For the IGate we would loop around and try to reconnect but
// it doesn't seem appropriate here. Just quit.
} /* 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 addrs[500];
unsigned char *pinfo;
int info_len;
ax25_format_addrs (pp, addrs);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_REC);
if (chan != 0) {
dw_printf ("[%d] ", chan);
}
dw_printf ("%s", addrs); /* stations 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 time to 0.01 sec resolution. ??? TBD
*/
if (strlen(receive_queue) > 0) {
char fname [30];
char path [300];
FILE *fp;
snprintf (fname, sizeof(fname), "%.0f", dtime_now() * 100.);
strlcpy (path, receive_queue, 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\n", 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);
// TODO: Just precede by "h " for in/out symmetry?
printf ("KISS protocol set hardware \"%s\", channel %d\n", (char*)(kiss_msg+1), chan);
break;
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 (" -t Transmit queue directory. Processs and delete files here.\n");
dw_printf (" -r Receive queue directory. Store received frames here.\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 */

438
sock.c Normal file
View File

@ -0,0 +1,438 @@
//
// 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: sock.c
*
* Purpose: Functions for TCP sockets.
*
* Description: These are used for connecting between different applications,
* possibly on different hosts.
*
* New in version 1.5:
* Duplicate code already exists in multiple places and I was about
* to add another one. Instead, we will gather the common code here
* instead of having yet another copy.
*
*---------------------------------------------------------------*/
#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 <netdb.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
//#include <termios.h>
#include <sys/errno.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include "textcolor.h"
#include "sock.h"
static void shuffle (struct addrinfo *host[], int nhosts);
/*-------------------------------------------------------------------
*
* Name: sock_init
*
* Purpose: Preparation before using socket interface.
*
* Inputs: none
*
* Returns: 0 for success, -1 for error.
*
* Errors: Message is printed. I've never seen it fail.
*
* Description: Doesn't do anything for Linux.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in kissutil.c
* TODO: Use this instead of own copy in server.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
int sock_init(void)
{
#if __WIN32__
WSADATA wsadata;
int err;
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("WSAStartup failed, error: %d\n", err);
return (-1);
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return (-1);
}
#endif
return (0);
} /* end sock_init */
/*-------------------------------------------------------------------
*
* Name: sock_connect
*
* Purpose: Connect to given host / port.
*
* Inputs: hostname - Host name or IP address.
*
* port - TCP port as text string.
*
* description - Description of the remote server to be used in error message.
* e.g. "APRS-IS (Igate) Server" or "TCP KISS TNC".
*
* allow_ipv6 - True to allow IPv6. Otherwise only IPv4.
*
* debug - Print debugging information.
*
* Outputs: ipaddr_str - The IP address, in text form, is placed here in case
* the caller wants it. Should be SOCK_IPADDR_LEN bytes.
*
* Returns: Socket Handle / file descriptor or -1 for error.
*
* Errors: (1) Can't find address for given host name.
*
* Print error and return -1.
*
* (2) Can't connect to one of the address(es).
*
* Silently try the next one.
*
* (3) Can't connect to any of the address(es).
*
* Nothing is printed for success. The caller might do that
* to provide confirmation on what is happening.
*
*--------------------------------------------------------------------*/
int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[SOCK_IPADDR_LEN])
{
#define MAX_HOSTS 50
struct addrinfo hints;
struct addrinfo *ai_head = NULL;
struct addrinfo *ai;
struct addrinfo *hosts[MAX_HOSTS];
int num_hosts, n;
int err;
int server_sock = -1;
strlcpy (ipaddr_str, "???", SOCK_IPADDR_LEN);
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
if ( ! allow_ipv6) {
hints.ai_family = AF_INET; /* IPv4 only. */
}
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
/*
* First, we need to look up the DNS name to get IP address.
* It is possible to have multiple addresses.
*/
ai_head = NULL;
err = getaddrinfo(hostname, port, &hints, &ai_head);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
#if __WIN32__
dw_printf ("Can't get address for %s, %s, err=%d\n",
description, hostname, WSAGetLastError());
#else
dw_printf ("Can't get address for %s, %s, %s\n",
description, hostname, gai_strerror(err));
#endif
freeaddrinfo(ai_head);
return (-1);
}
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("getaddrinfo returns:\n");
}
num_hosts = 0;
for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
if (debug) {
text_color_set(DW_COLOR_DEBUG);
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
hosts[num_hosts] = ai;
if (num_hosts < MAX_HOSTS) num_hosts++;
}
shuffle (hosts, num_hosts);
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("addresses for hostname:\n");
for (n=0; n<num_hosts; n++) {
sock_ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
}
/*
* Try each address until we find one that is successful.
*/
for (n = 0; n < num_hosts; n++) {
#if __WIN32__
SOCKET is;
#else
int is;
#endif
ai = hosts[n];
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
#if __WIN32__
if (is == INVALID_SOCKET) {
printf ("Socket creation failed, err=%d", WSAGetLastError());
WSACleanup();
is = -1;
continue;
}
#else
if (err != 0) {
printf ("Socket creation failed, err=%s", gai_strerror(err));
(void) close (is);
is = -1;
continue;
}
#endif
#ifndef DEBUG_DNS
err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
#if __WIN32__
if (err == SOCKET_ERROR) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description, hostname, ipaddr_str, port);
#endif
closesocket (is);
is = -1;
continue;
}
#else
if (err != 0) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description, hostname, ipaddr_str, port);
#endif
(void) close (is);
is = -1;
continue;
}
/* IGate documentation says to use no delay. */
/* Does it really make a difference? */
int flag = 1;
err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
if (err < 0) {
printf("setsockopt TCP_NODELAY failed.\n");
}
#endif
/* Success. */
server_sock = is;
#endif
break;
}
freeaddrinfo(ai_head);
// no, caller should handle this.
// function should be generally be silent unless debug option.
if (server_sock == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to connect to %s at %s (%s), port %s\n",
description, hostname, ipaddr_str, port );
}
return (server_sock);
} /* end sock_connect */
/*-------------------------------------------------------------------
*
* Name: sock_bind
*
* Purpose: We also have a bunch of duplicate code for the server side.
*
* Inputs:
*
* TODO: Use this instead of own copy in audio.c
* TODO: Use this instead of own copy in audio_portaudio.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in server.c
*
*--------------------------------------------------------------------*/
// Not implemented yet.
/*
* Addresses don't get mixed up very well.
* IPv6 always shows up last so we'd probably never
* end up using any of them for APRS-IS server.
* Add our own shuffle.
*/
static void shuffle (struct addrinfo *host[], int nhosts)
{
int j, k;
assert (RAND_MAX >= nhosts); /* for % to work right */
if (nhosts < 2) return;
srand (time(NULL));
for (j=0; j<nhosts; j++) {
k = rand() % nhosts;
assert (k >=0 && k<nhosts);
if (j != k) {
struct addrinfo *temp;
temp = host[j]; host[j] = host[k]; host[k] = temp;
}
}
}
/*-------------------------------------------------------------------
*
* Name: sock_ia_to_text
*
* Purpose: Convert binary IP Address to text form.
*
* Inputs: Family - AF_INET or AF_INET6.
*
* pAddr - Pointer to the IP Address storage location.
*
* StringBufSize - Number of bytes in pStringBuf.
*
* Outputs: pStringBuf - Text result is placed here.
*
* Returns: pStringBuf
*
* Description: Can't use InetNtop because it is supported only on Windows Vista and later.
* At one time Dire Wolf worked on Win XP. Haven't tried it for years.
* Maybe some other dependency on a newer OS version has crept in.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
{
struct sockaddr_in *sa4;
struct sockaddr_in6 *sa6;
switch (Family) {
case AF_INET:
sa4 = (struct sockaddr_in *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
sa4->sin_addr.S_un.S_un_b.s_b2,
sa4->sin_addr.S_un.S_un_b.s_b3,
sa4->sin_addr.S_un.S_un_b.s_b4);
#else
inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
#endif
break;
case AF_INET6:
sa6 = (struct sockaddr_in6 *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
#else
inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
#endif
break;
default:
snprintf (pStringBuf, StringBufSize, "Invalid address family!");
}
return pStringBuf;
} /* end sock_ia_to_text */
/* end sock.c */

19
sock.h Normal file
View File

@ -0,0 +1,19 @@
/* sock.h - Socket helper functions. */
#ifndef SOCK_H
#define SOCK_H 1
#define SOCK_IPADDR_LEN 48 // Size of string to hold IPv4 or IPv6 address.
// I think 40 would be adequate but we'll make
// it a little larger just to be safe.
// Use INET6_ADDRSTRLEN (from netinet/in.h) instead?
int sock_init (void);
int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char *ipaddr_str);
/* ipaddr_str needs to be at least SOCK_IPADDR_LEN bytes */
char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
#endif