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