1 //
2 //    This file is part of Dire Wolf, an amateur radio packet TNC.
3 //
4 //    Copyright (C) 2017  John Langner, WB2OSZ
5 //
6 //    This program is free software: you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation, either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    This program is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 //
19 
20 
21 /*------------------------------------------------------------------
22  *
23  * Module:      kissutil.c
24  *
25  * Purpose:   	Utility for talking to a KISS TNC.
26  *
27  * Description:	Convert between KISS format and usual text representation.
28  *		This might also serve as the starting point for an application
29  *		that uses a KISS TNC.
30  *		The TNC can be attached by TCP or a serial port.
31  *
32  * Usage:	kissutil  [ options ]
33  *
34  *		Default is to connect to localhost:8001.
35  *		See the "usage" functions at the bottom for details.
36  *
37  *---------------------------------------------------------------*/
38 
39 #include "direwolf.h"		// Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
40 
41 #if __WIN32__
42 
43 #include <winsock2.h>
44 #include <ws2tcpip.h>  		// _WIN32_WINNT must be set to 0x0501 before including this
45 
46 #else
47 
48 #include <stdlib.h>
49 #include <errno.h>
50 #include <sys/types.h>
51 #include <sys/socket.h>
52 
53 #endif
54 
55 #include <unistd.h>
56 #include <stdio.h>
57 #include <assert.h>
58 #include <ctype.h>
59 #include <stddef.h>
60 #include <string.h>
61 #include <getopt.h>
62 #include <dirent.h>
63 #include <sys/stat.h>
64 
65 #include "ax25_pad.h"
66 #include "textcolor.h"
67 #include "serial_port.h"
68 #include "kiss_frame.h"
69 #include "dwsock.h"
70 #include "dtime_now.h"
71 #include "audio.h"		// for DEFAULT_TXDELAY, etc.
72 #include "dtime_now.h"
73 
74 
75 // TODO:  define in one place, use everywhere.
76 #if __WIN32__
77 #define THREAD_F unsigned __stdcall
78 #else
79 #define THREAD_F void *
80 #endif
81 
82 #if __WIN32__
83 #define DIR_CHAR "\\"
84 #else
85 #define DIR_CHAR "/"
86 #endif
87 
88 static THREAD_F tnc_listen_net (void *arg);
89 static THREAD_F tnc_listen_serial (void *arg);
90 
91 static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen);
92 static void hex_dump (unsigned char *p, int len);
93 
94 static void usage(void);
95 static void usage2(void);
96 
97 
98 
99 
100 /* Obtained from the command line. */
101 
102 static char hostname[50] = "localhost";		/* -h option. */
103 						/* DNS host name or IPv4 address. */
104 						/* Some of the code is there for IPv6 but */
105 						/* it needs more work. */
106 						/* Defaults to "localhost" if not specified. */
107 
108 static char port[30] = "8001";			/* -p option. */
109 						/* If it begins with a digit, it is considered */
110 						/* a TCP port number at the hostname.  */
111 						/* Otherwise, we treat it as a serial port name. */
112 
113 static int using_tcp = 1;			/* Are we using TCP or serial port for TNC? */
114 						/* Use corresponding one of the next two. */
115 						/* This is derived from the first character of port. */
116 
117 static int server_sock = -1;			/* File descriptor for socket interface. */
118 						/* Set to -1 if not used. */
119 						/* (Don't use SOCKET type because it is unsigned.) */
120 
121 static MYFDTYPE serial_fd = (MYFDTYPE)(-1);	/* Serial port handle. */
122 
123 static int serial_speed = 9600;			/* -s option. */
124 						/* Serial port speed, bps. */
125 
126 static int verbose = 0;				/* -v option. */
127 						/* Display the KISS protocol in hexadecimal for troubleshooting. */
128 
129 static char transmit_from[120] = "";		/* -f option */
130 						/* When specified, files are read from this directory */
131 						/* rather than using stdin.  Each file is one or more */
132 						/* lines in the standard monitoring format. */
133 
134 static char receive_output[120] = "";		/* -o option */
135 						/* When specified, each received frame is stored as a file */
136 						/* with a unique name here.  */
137 						/* Directory must already exist; we won't create it. */
138 
139 static char timestamp_format[60] = "";		/* -T option */
140 						/* Precede received frames with timestamp. */
141 						/* Command line option uses "strftime" format string. */
142 
143 
144 #if __WIN32__
145 #define THREAD_F unsigned __stdcall
146 #else
147 #define THREAD_F void *
148 #endif
149 
150 #if __WIN32__
151 	static HANDLE tnc_th;
152 #else
153 	static pthread_t tnc_tid;
154 #endif
155 
156 static void process_input (char *stuff);
157 
158 /* Trim any CR, LF from the end of line. */
159 
trim(char * stuff)160 static void trim (char *stuff)
161 {
162 	char *p;
163 	p = stuff + strlen(stuff) - 1;
164 	while (strlen(stuff) > 0 && (*p == '\r' || *p == '\n')) {
165 	  *p = '\0';
166 	  p--;
167 	}
168 } /* end trim */
169 
170 
171 /*------------------------------------------------------------------
172  *
173  * Name: 	main
174  *
175  * Purpose:   	Attach to KISS TNC and exchange information.
176  *
177  * Usage:	See "usage" functions at end.
178  *
179  *---------------------------------------------------------------*/
180 
main(int argc,char * argv[])181 int main (int argc, char *argv[])
182 {
183 	text_color_init (0);	// Turn off text color.
184 				// It could interfere with trying to pipe stdout to some other application.
185 
186 #if __WIN32__
187 
188 #else
189 	int e;
190 	setlinebuf (stdout);		// TODO:  What is the Windows equivalent?
191 #endif
192 
193 
194 /*
195  * Extract command line args.
196  */
197 	while (1) {
198           int option_index = 0;
199 	  int c;
200           static struct option long_options[] = {
201             //{"future1", 1, 0, 0},
202             //{"future2", 0, 0, 0},
203             //{"future3", 1, 0, 'c'},
204             {0, 0, 0, 0}
205           };
206 
207 	  /* ':' following option character means arg is required. */
208 
209           c = getopt_long(argc, argv, "h:p:s:vf:o:T:",
210 			long_options, &option_index);
211           if (c == -1)
212             break;
213 
214           switch (c) {
215 
216             case 'h':				/* -h for hostname. */
217 	      strlcpy (hostname, optarg, sizeof(hostname));
218               break;
219 
220             case 'p':				/* -p for port, either TCP or serial device. */
221 	      strlcpy (port, optarg, sizeof(port));
222               break;
223 
224             case 's':				/* -s for serial port speed. */
225 	      serial_speed = atoi(optarg);
226               break;
227 
228 	    case 'v':				/* -v for verbose. */
229 	      verbose++;
230 	      break;
231 
232             case 'f':				/* -f for transmit files directory. */
233 	      strlcpy (transmit_from, optarg, sizeof(transmit_from));
234               break;
235 
236             case 'o':				/* -o for receive output directory. */
237 	      strlcpy (receive_output, optarg, sizeof(receive_output));
238               break;
239 
240             case 'T':				/* -T for receive timestamp. */
241 	      strlcpy (timestamp_format, optarg, sizeof(timestamp_format));
242               break;
243 
244             case '?':
245               /* Unknown option message was already printed. */
246               usage ();
247               break;
248 
249             default:
250               /* Should not be here. */
251 	      text_color_set(DW_COLOR_DEBUG);
252               dw_printf("?? getopt returned character code 0%o ??\n", c);
253               usage ();
254            }
255 	}  /* end while(1) for options */
256 
257 	if (optind < argc) {
258 	  text_color_set(DW_COLOR_ERROR);
259           dw_printf ("Warning: Unused command line arguments are ignored.\n");
260  	}
261 
262 /*
263  * If receive queue directory was specified, make sure that it exists.
264  */
265 	if (strlen(receive_output) > 0) {
266 	  struct stat s;
267 
268 	  if (stat(receive_output, &s) == 0) {
269 	    if ( ! S_ISDIR(s.st_mode)) {
270 	      text_color_set(DW_COLOR_ERROR);
271               dw_printf ("Receive queue location, %s, is not a directory.\n", receive_output);
272 	      exit (EXIT_FAILURE);
273 	    }
274 	  }
275 	  else {
276 	    text_color_set(DW_COLOR_ERROR);
277             dw_printf ("Receive queue location, %s, does not exist.\n", receive_output);
278 	    exit (EXIT_FAILURE);
279 	  }
280 	}
281 
282 /* If port begins with digit, consider it to be TCP. */
283 /* Otherwise, treat as serial port name. */
284 
285 	using_tcp = isdigit(port[0]);
286 
287 #if __WIN32__
288 	if (using_tcp) {
289 	  tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_net, (void *)(ptrdiff_t)99, 0, NULL);
290 	}
291 	else {
292 	  tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_serial, (void *)99, 0, NULL);
293 	}
294 	if (tnc_th == NULL) {
295 	  printf ("Internal error: Could not create TNC listen thread.\n");
296 	  exit (EXIT_FAILURE);
297 	}
298 #else
299 	if (using_tcp) {
300 	  e = pthread_create (&tnc_tid, NULL, tnc_listen_net, (void *)(ptrdiff_t)99);
301 	}
302 	else {
303 	  e = pthread_create (&tnc_tid, NULL, tnc_listen_serial, (void *)(ptrdiff_t)99);
304 	}
305 	if (e != 0) {
306 	  perror("Internal error: Could not create TNC listen thread.");
307 	  exit (EXIT_FAILURE);
308 	}
309 #endif
310 
311 /*
312  * Process keyboard or other input source.
313  */
314 	char stuff[1000];
315 
316 	if (strlen(transmit_from) > 0) {
317 /*
318  * Process and delete all files in specified directory.
319  * When done, sleep for a second and try again.
320  * This doesn't take them in any particular order.
321  * A future enhancement might sort by name or timestamp.
322  */
323 	  while (1) {
324 	    DIR *dp;
325 	    struct dirent *ep;
326 
327 	    //text_color_set(DW_COLOR_DEBUG);
328             //dw_printf("Get directory listing...\n");
329 
330 	    dp = opendir (transmit_from);
331 	    if (dp != NULL) {
332 	      while ((ep = readdir(dp)) != NULL) {
333 	        char path [300];
334 	        FILE *fp;
335 	        if (ep->d_name[0] == '.')
336 	          continue;
337 
338 	        text_color_set(DW_COLOR_DEBUG);
339 	        dw_printf ("Processing %s for transmit...\n", ep->d_name);
340 		strlcpy (path, transmit_from, sizeof(path));
341 	        strlcat (path, DIR_CHAR, sizeof(path));
342 	        strlcat (path, ep->d_name, sizeof(path));
343 	        fp = fopen (path, "r");
344 		if (fp != NULL) {
345 	          while (fgets(stuff, sizeof(stuff), fp) != NULL) {
346 	            trim (stuff);
347 	            text_color_set(DW_COLOR_DEBUG);
348 	            dw_printf ("%s\n", stuff);
349 		    // TODO: Don't delete file if errors encountered?
350 	            process_input (stuff);
351 	          }
352 	          fclose (fp);
353 		  unlink (path);
354 	        }
355 	        else {
356 	          text_color_set(DW_COLOR_ERROR);
357                   dw_printf("Can't open for read: %s\n", path);
358 	        }
359 	      }
360 	      closedir (dp);
361 	    }
362 	    else {
363 	      text_color_set(DW_COLOR_ERROR);
364               dw_printf("Can't access transmit queue directory %s.  Quitting.\n", transmit_from);
365 	      exit (EXIT_FAILURE);
366 	    }
367 	    SLEEP_SEC (1);
368 	  }
369 	}
370 	else {
371 /*
372  * Using stdin.
373  */
374 	  while (fgets(stuff, sizeof(stuff), stdin) != NULL) {
375 	    process_input (stuff);
376 	  }
377 	}
378 
379 	return (EXIT_SUCCESS);
380 
381 } /* end main */
382 
383 
384 
385 /*-------------------------------------------------------------------
386  *
387  * Name:        process_input
388  *
389  * Purpose:     Process frames/commands from user, either interactively or from files.
390  *
391  * Inputs:	stuff		- A frame is in usual format like SOURCE>DEST,DIGI:whatever.
392  *				  Commands begin with lower case letter.
393  *				  Note that it can be modified by this function.
394  *
395  * Later Enhancement:	Return success/fail status.  The transmit queue processing might want
396  *		to preserve files that were not processed as expected.
397  *
398  *--------------------------------------------------------------------*/
399 
parse_number(char * str,int de_fault)400 static int parse_number (char *str, int de_fault)
401 {
402 	int n;
403 
404 	while (isspace(*str)) {
405 	  str++;
406 	}
407 	if (strlen(str) == 0) {
408 	  text_color_set(DW_COLOR_ERROR);
409 	  dw_printf ("Missing number for KISS command.  Using default %d.\n", de_fault);
410 	  return (de_fault);
411 	}
412 	n = atoi(str);
413 	if (n < 0 || n > 255) {		// must fit in a byte.
414 	  text_color_set(DW_COLOR_ERROR);
415 	  dw_printf ("Number for KISS command is out of range 0-255.  Using default %d.\n", de_fault);
416 	  return (de_fault);
417 	}
418 	return (n);
419 }
420 
process_input(char * stuff)421 static void process_input (char *stuff)
422 {
423 	char *p;
424 	int chan = 0;
425 
426 /*
427  * Remove any end of line character(s).
428  */
429 	trim (stuff);
430 
431 /*
432  * Optional prefix, like "[9]" or "[99]" to specify channel.
433  */
434 	p = stuff;
435 	while (isspace(*p)) p++;
436 	if (*p == '[') {
437 	  p++;
438 	  if (p[1] == ']') {
439 	    chan = atoi(p);
440 	    p += 2;
441 	  }
442 	  else if (p[2] == ']') {
443 	    chan = atoi(p);
444 	    p += 3;
445 	  }
446 	  else {
447 	    text_color_set(DW_COLOR_ERROR);
448 	    dw_printf ("ERROR! One or two digit channel number and ] was expected after [ at beginning of line.\n");
449 	    usage2();
450 	    return;
451 	  }
452 	  if (chan < 0 || chan > 15) {
453 	    text_color_set(DW_COLOR_ERROR);
454 	    dw_printf ("ERROR! KISS channel number must be in range of 0 thru 15.\n");
455 	    usage2();
456 	    return;
457 	  }
458 	  while (isspace(*p)) p++;
459 	}
460 
461 /*
462  * If it starts with upper case letter or digit, assume it is an AX.25 frame in monitor format.
463  * Lower case is a command (e.g.  Persistence or set Hardware).
464  * Anything else, print explanation of what is expected.
465  */
466 	if (isupper(*p) || isdigit(*p)) {
467 
468 	  // Parse the "TNC2 monitor format" and convert to AX.25 frame.
469 
470 	  unsigned char frame_data[AX25_MAX_PACKET_LEN];
471 	  packet_t pp = ax25_from_text (p, 1);
472 	  if (pp != NULL) {
473 	    int frame_len = ax25_pack (pp, frame_data);
474 	    send_to_kiss_tnc (chan, KISS_CMD_DATA_FRAME, (char*)frame_data, frame_len);
475 	    ax25_delete (pp);
476 	  }
477 	  else {
478 	    text_color_set(DW_COLOR_ERROR);
479 	    dw_printf ("ERROR! Could not convert to AX.25 frame: %s\n", p);
480 	  }
481 	}
482 	else if (islower(*p)) {
483 	  char value;
484 
485 	  switch (*p) {
486 	    case 'd':			// txDelay, 10ms units
487 	      value = parse_number(p+1, DEFAULT_TXDELAY);
488 	      send_to_kiss_tnc (chan, KISS_CMD_TXDELAY, &value, 1);
489 	      break;
490 	    case 'p':			// Persistence
491 	      value = parse_number(p+1, DEFAULT_PERSIST);
492 	      send_to_kiss_tnc (chan, KISS_CMD_PERSISTENCE, &value, 1);
493 	      break;
494 	    case 's':			// Slot time, 10ms units
495 	      value = parse_number(p+1,  DEFAULT_SLOTTIME);
496 	      send_to_kiss_tnc (chan, KISS_CMD_SLOTTIME, &value, 1);
497 	      break;
498 	    case 't':			// txTail, 10ms units
499 	      value = parse_number(p+1, DEFAULT_TXTAIL);
500 	      send_to_kiss_tnc (chan, KISS_CMD_TXTAIL, &value, 1);
501 	      break;
502 	    case 'f':			// Full duplex
503 	      value = parse_number(p+1, 0);
504 	      send_to_kiss_tnc (chan, KISS_CMD_FULLDUPLEX, &value, 1);
505 	      break;
506 	    case 'h':			// set Hardware
507 	      p++;
508 	      while (*p != '\0' && isspace(*p)) { p++; }
509 	      send_to_kiss_tnc (chan, KISS_CMD_SET_HARDWARE, p, strlen(p));
510 	      break;
511 	    default:
512 	      text_color_set(DW_COLOR_ERROR);
513 	      dw_printf ("Invalid command. Must be one of d p s t f h.\n");
514 	      usage2 ();
515 	      break;
516 	  }
517 	}
518 	else {
519 	  usage2 ();
520 	}
521 
522 } /* end process_input */
523 
524 
525 
526 
527 /*-------------------------------------------------------------------
528  *
529  * Name:        send_to_kiss_tnc
530  *
531  * Purpose:     Encapsulate the data/command, into a KISS frame, and send to the TNC.
532  *
533  * Inputs:	chan	- channel number.
534  *
535  *		cmd	- KISS_CMD_DATA_FRAME, KISS_CMD_SET_HARDWARE, etc.
536  *
537  *		data	- Information for KISS frame.
538  *
539  *		dlen	- Number of bytes in data.
540  *
541  * Description:	Encapsulate as KISS frame and send to TNC.
542  *
543  *--------------------------------------------------------------------*/
544 
send_to_kiss_tnc(int chan,int cmd,char * data,int dlen)545 static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen)
546 {
547 	unsigned char temp[1000];
548 	unsigned char kissed[2000];
549 	int klen;
550 
551 	if (chan < 0 || chan > 15) {
552 	  text_color_set(DW_COLOR_ERROR);
553 	  dw_printf ("ERROR - Invalid channel %d - must be in range 0 to 15.\n", chan);
554 	  chan = 0;
555 	}
556 	if (cmd < 0 || cmd > 15) {
557 	  text_color_set(DW_COLOR_ERROR);
558 	  dw_printf ("ERROR - Invalid command %d - must be in range 0 to 15.\n", cmd);
559 	  cmd = 0;
560 	}
561 	if (dlen < 0 || dlen > (int)(sizeof(temp)-1)) {
562 	  text_color_set(DW_COLOR_ERROR);
563 	  dw_printf ("ERROR - Invalid data length %d - must be in range 0 to %d.\n", dlen, (int)(sizeof(temp)-1));
564 	  dlen = sizeof(temp)-1;
565 	}
566 
567 	temp[0] = (chan << 4) | cmd;
568 	memcpy (temp+1, data, dlen);
569 
570 	klen = kiss_encapsulate(temp, dlen+1, kissed);
571 
572 	if (verbose) {
573 	  text_color_set(DW_COLOR_DEBUG);
574 	  dw_printf ("Sending to KISS TNC:\n");
575 	  hex_dump (kissed, klen);
576 	}
577 
578 	if (using_tcp) {
579 	  int rc = SOCK_SEND(server_sock, (char*)kissed, klen);
580 	  if (rc != klen) {
581 	    text_color_set(DW_COLOR_ERROR);
582 	    dw_printf ("ERROR writing KISS frame to socket.\n");
583 	  }
584 	}
585 	else {
586 	  int rc = serial_port_write (serial_fd, (char*)kissed, klen);
587 	  if (rc != klen) {
588 	    text_color_set(DW_COLOR_ERROR);
589 	    dw_printf ("ERROR writing KISS frame to serial port.\n");
590 	  }
591 	}
592 
593 } /* end send_to_kiss_tnc */
594 
595 
596 /*-------------------------------------------------------------------
597  *
598  * Name:        tnc_listen_net
599  *
600  * Purpose:     Connect to KISS TNC via TCP port.
601  *		Print everything it sends to us.
602  *
603  * Inputs:	arg		- Currently not used.
604  *
605  * Global In:	host
606  *		port
607  *
608  * Global Out:	server_sock	- Needed to send to the TNC.
609  *
610  *--------------------------------------------------------------------*/
611 
tnc_listen_net(void * arg)612 static THREAD_F tnc_listen_net (void *arg)
613 {
614 	int err;
615 	char ipaddr_str[DWSOCK_IPADDR_LEN];  	// Text form of IP address.
616 	char data[4096];
617 	int allow_ipv6 = 0;		// Maybe someday.
618 	int debug = 0;
619 	int client = 0;			// Not used in this situation.
620 	kiss_frame_t kstate;
621 
622 	memset (&kstate, 0, sizeof(kstate));
623 
624 	err = dwsock_init ();
625 	if (err < 0) {
626 	  text_color_set(DW_COLOR_ERROR);
627 	  dw_printf ("Network interface failure.  Can't go on.\n");
628 	  exit (EXIT_FAILURE);
629 	}
630 
631 /*
632  * Connect to network KISS TNC.
633  */
634 	// For the IGate we would loop around and try to reconnect if the TNC
635 	// goes away.  We should probably do the same here.
636 
637 	server_sock = dwsock_connect (hostname, port, "TCP KISS TNC", allow_ipv6, debug, ipaddr_str);
638 
639 	if (server_sock == -1) {
640 	  text_color_set(DW_COLOR_ERROR);
641 	  // Should have been a message already.  What else is there to say?
642  	  exit (EXIT_FAILURE);
643 	}
644 
645 /*
646  * Print what we get from TNC.
647  */
648 	int len;
649 
650 	while ((len = SOCK_RECV (server_sock, (char*)(data), sizeof(data))) > 0) {
651 	  int j;
652 	  for (j = 0; j < len; j++) {
653 
654 	    // Feed in one byte at a time.
655 	    // kiss_process_msg is called when a complete frame has been accumulated.
656 
657 	    // When verbose is specified, we get debug output like this:
658 	    //
659 	    // <<< Data frame from KISS client application, port 0, total length = 46
660   	    // 000:  c0 00 82 a0 88 ae 62 6a e0 ae 84 64 9e a6 b4 ff  ......bj...d....
661 	    // ...
662 	    // It says "from KISS client application" because it was written
663 	    // on the assumption it was being used in only one direction.
664 	    // Not worried enough about it to do anything at this time.
665 
666 	    kiss_rec_byte (&kstate, data[j], verbose, client, NULL);
667 	  }
668 	}
669 
670 	text_color_set(DW_COLOR_ERROR);
671 	dw_printf ("Read error from TCP KISS TNC.  Terminating.\n");
672  	exit (EXIT_FAILURE);
673 
674 } /* end tnc_listen_net */
675 
676 
677 /*-------------------------------------------------------------------
678  *
679  * Name:        tnc_listen_serial
680  *
681  * Purpose:     Connect to KISS TNC via serial port.
682  *		Print everything it sends to us.
683  *
684  * Inputs:	arg		- Currently not used.
685  *
686  * Global In:	port
687  *		serial_speed
688  *
689  * Global Out:	serial_fd	- Need for sending to the TNC.
690  *
691  *--------------------------------------------------------------------*/
692 
tnc_listen_serial(void * arg)693 static THREAD_F tnc_listen_serial (void *arg)
694 {
695 	int client = 0;
696 	kiss_frame_t kstate;
697 
698 	memset (&kstate, 0, sizeof(kstate));
699 
700 	serial_fd = serial_port_open (port, serial_speed);
701 
702 	if (serial_fd == MYFDERROR) {
703 	  text_color_set(DW_COLOR_ERROR);
704  	  dw_printf("Unable to connect to KISS TNC serial port %s.\n", port);
705 #if __WIN32__
706 #else
707 	  // More detail such as "permission denied" or "no such device"
708 	  dw_printf("%s\n", strerror(errno));
709 #endif
710 	  exit (EXIT_FAILURE);
711 	}
712 
713 /*
714  * Read and print.
715  */
716 	while (1) {
717 	  int ch;
718 
719 	  ch = serial_port_get1(serial_fd);
720 
721 	  if (ch < 0) {
722  	    dw_printf("Read error from serial port KISS TNC.\n");
723 	    exit (EXIT_FAILURE);
724 	  }
725 
726 	  // Feed in one byte at a time.
727 	  // kiss_process_msg is called when a complete frame has been accumulated.
728 
729 	  kiss_rec_byte (&kstate, ch, verbose, client, NULL);
730 	}
731 
732 } /* end tnc_listen_serial */
733 
734 
735 
736 /*-------------------------------------------------------------------
737  *
738  * Name:        kiss_process_msg
739  *
740  * Purpose:     Process a frame from the KISS TNC.
741  *		This is called when a complete frame has been accumulated.
742  *		In this case, we simply print it.
743  *
744  * Inputs:	kiss_msg	- Kiss frame with FEND and escapes removed.
745  *				  The first byte contains channel and command.
746  *
747  *		kiss_len	- Number of bytes including the command.
748  *
749  *		debug		- Debug option is selected.
750  *
751  *		client		- Not used in this case.
752  *
753  *		sendfun		- Not used in this case.
754  *
755  *-----------------------------------------------------------------*/
756 
kiss_process_msg(unsigned char * kiss_msg,int kiss_len,int debug,int client,void (* sendfun)(int,int,unsigned char *,int,int))757 void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
758 {
759 	int chan;
760 	int cmd;
761 	packet_t pp;
762 	alevel_t alevel;
763 
764 	chan = (kiss_msg[0] >> 4) & 0xf;
765 	cmd = kiss_msg[0] & 0xf;
766 
767 	switch (cmd)
768 	{
769 	  case KISS_CMD_DATA_FRAME:				/* 0 = Data Frame */
770 
771 	    memset (&alevel, 0, sizeof(alevel));
772 	    pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
773 	    if (pp == NULL) {
774 	       text_color_set(DW_COLOR_ERROR);
775 	       printf ("ERROR - Invalid KISS data frame from TNC.\n");
776 	    }
777 	    else {
778 	      char prefix[120];		// Channel and optional timestamp.
779 					// Like [0] or [2 12:34:56]
780 
781 	      char addrs[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN];	// Like source>dest,digi,...,digi:
782 	      unsigned char *pinfo;
783 	      int info_len;
784 
785 	      if (strlen(timestamp_format) > 0) {
786 	        char ts[100];
787 		timestamp_user_format (ts, sizeof(ts), timestamp_format);
788 	        snprintf (prefix, sizeof(prefix), "[%d %s]", chan, ts);
789 	      }
790 	      else {
791 	        snprintf (prefix, sizeof(prefix), "[%d]", chan);
792 	      }
793 
794 	      ax25_format_addrs (pp, addrs);
795 
796 	      info_len = ax25_get_info (pp, &pinfo);
797 
798 	      text_color_set(DW_COLOR_REC);
799 
800 	      dw_printf ("%s %s", prefix, addrs);		// [channel] Addresses followed by :
801 
802 	      // Safe print will replace any unprintable characters with
803 	      // hexadecimal representation.
804 
805 	      ax25_safe_print ((char *)pinfo, info_len, 0);
806 	      dw_printf ("\n");
807 #if __WIN32__
808 	      fflush (stdout);
809 #endif
810 
811 /*
812  * Add to receive queue directory if specified.
813  * File name will be based on current local time.
814  * If you want UTC, just set an environment variable like this:
815  *
816  *	TZ=UTC kissutil ...
817  */
818 	      if (strlen(receive_output) > 0) {
819 		char fname [30];
820 	        char path [300];
821 	        FILE *fp;
822 
823 	        timestamp_filename (fname, (int)sizeof(fname));
824 
825 		strlcpy (path, receive_output, sizeof(path));
826 	        strlcat (path, DIR_CHAR, sizeof(path));
827 	        strlcat (path, fname, sizeof(path));
828 
829 	        text_color_set(DW_COLOR_DEBUG);
830 	        dw_printf ("Save received frame to %s\n", path);
831 	        fp = fopen (path, "w");
832 	        if (fp != NULL) {
833 	          fprintf (fp, "%s %s%s\n", prefix, addrs, pinfo);
834 	          fclose (fp);
835 	        }
836 	        else {
837 	          text_color_set(DW_COLOR_ERROR);
838 	          dw_printf ("Unable to open for write: %s\n", path);
839 	        }
840 	      }
841 
842 	      ax25_delete (pp);
843 	    }
844 	    break;
845 
846           case KISS_CMD_SET_HARDWARE:			/* 6 = TNC specific */
847 
848 	    kiss_msg[kiss_len] = '\0';
849 	    text_color_set(DW_COLOR_REC);
850 	    // Display as "h ..." for in/out symmetry.
851 	    // Use safe print here?
852 	    dw_printf ("[%d] h %s\n", chan, (char*)(kiss_msg+1));
853 	    break;
854 
855 /*
856  * The rest should only go TO the TNC and not come FROM it.
857  */
858           case KISS_CMD_TXDELAY:			/* 1 = TXDELAY */
859           case KISS_CMD_PERSISTENCE:			/* 2 = Persistence */
860           case KISS_CMD_SLOTTIME:			/* 3 = SlotTime */
861           case KISS_CMD_TXTAIL:				/* 4 = TXtail */
862           case KISS_CMD_FULLDUPLEX:			/* 5 = FullDuplex */
863           case KISS_CMD_END_KISS:			/* 15 = End KISS mode, port should be 15. */
864           default:
865 
866 	    text_color_set(DW_COLOR_ERROR);
867 	    printf ("Unexpected KISS command %d, channel %d\n", cmd, chan);
868 	    break;
869 	}
870 
871 } /* end kiss_process_msg */
872 
873 
874 // TODO:  We have multiple copies of this.  Move to some misc file.
875 
hex_dump(unsigned char * p,int len)876 void hex_dump (unsigned char *p, int len)
877 {
878 	int n, i, offset;
879 
880 	offset = 0;
881 	while (len > 0) {
882 	  n = len < 16 ? len : 16;
883 	  printf ("  %03x: ", offset);
884 	  for (i=0; i<n; i++) {
885 	    printf (" %02x", p[i]);
886 	  }
887 	  for (i=n; i<16; i++) {
888 	    printf ("   ");
889 	  }
890 	  printf ("  ");
891 	  for (i=0; i<n; i++) {
892 	    printf ("%c", isprint(p[i]) ? p[i] : '.');
893 	  }
894 	  printf ("\n");
895 	  p += 16;
896 	  offset += 16;
897 	  len -= 16;
898 	}
899 }
900 
usage(void)901 static void usage(void)
902 {
903 	text_color_set(DW_COLOR_INFO);
904 	dw_printf ("\n");
905 	dw_printf ("kissutil  -  Utility for testing a KISS TNC.\n");
906 	dw_printf ("\n");
907 	dw_printf ("Convert between KISS format and usual text representation.\n");
908 	dw_printf ("The TNC can be attached by TCP or a serial port.\n");
909 	dw_printf ("\n");
910 	dw_printf ("Usage:	kissutil  [ options ]\n");
911 	dw_printf ("\n");
912  	dw_printf ("	-h	hostname of TCP KISS TNC, default localhost.\n");
913  	dw_printf ("	-p	port, default 8001.\n");
914 	dw_printf ("		If it does not start with a digit, it is\n");
915  	dw_printf ("		a serial port.  e.g.  /dev/ttyAMA0 or COM3.\n");
916  	dw_printf ("	-s	Serial port speed, default 9600.\n");
917 	dw_printf ("	-v	Verbose.  Show the KISS frame contents.\n");
918 	dw_printf ("	-f	Transmit files directory.  Processs and delete files here.\n");
919 	dw_printf ("	-o	Receive output queue directory.  Store received frames here.\n");
920 	dw_printf ("	-T	Precede received frames with 'strftime' format time stamp.\n");
921 	usage2();
922 	exit (EXIT_SUCCESS);
923 }
924 
usage2(void)925 static void usage2 (void)
926 {
927 	text_color_set(DW_COLOR_INFO);
928 	dw_printf ("\n");
929 	dw_printf ("Input, starting with upper case letter or digit, is assumed\n");
930 	dw_printf ("to be an AX.25 frame in the usual TNC2 monitoring format.\n");
931 	dw_printf ("\n");
932 	dw_printf ("Input, starting with a lower case letter is a commmand.\n");
933 	dw_printf ("Whitespace, as shown in examples, is optional.\n");
934 	dw_printf ("\n");
935 	dw_printf ("	letter	meaning			example\n");
936 	dw_printf ("	------	-------			-------\n");
937 	dw_printf ("	d	txDelay, 10ms units	d 30\n");
938 	dw_printf ("	p	Persistence		p 63\n");
939 	dw_printf ("	s	Slot time, 10ms units	s 10\n");
940 	dw_printf ("	t	txTail, 10ms units	t 5\n");
941 	dw_printf ("	f	Full duplex		f 0\n");
942 	dw_printf ("	h	set Hardware 		h TNC:\n");
943 	dw_printf ("\n");
944 	dw_printf ("	Lines may be preceded by the form \"[9]\" to indicate a\n");
945 	dw_printf ("	channel other than the default 0.\n");
946 	dw_printf ("\n");
947 }
948 
949 /* end kissutil.c */
950