1 //
2 // This file is part of Dire Wolf, an amateur radio packet TNC.
3 //
4 // Copyright (C) 2011, 2013, 2014, 2015 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 * Name: digipeater.c
24 *
25 * Purpose: Act as an APRS digital repeater.
26 * Similar cdigipeater.c is for connected mode.
27 *
28 *
29 * Description: Decide whether the specified packet should
30 * be digipeated and make necessary modifications.
31 *
32 *
33 * References: APRS Protocol Reference, document version 1.0.1
34 *
35 * http://www.aprs.org/doc/APRS101.PDF
36 *
37 * APRS SPEC Addendum 1.1
38 *
39 * http://www.aprs.org/aprs11.html
40 *
41 * APRS SPEC Addendum 1.2
42 *
43 * http://www.aprs.org/aprs12.html
44 *
45 * "The New n-N Paradigm"
46 *
47 * http://www.aprs.org/fix14439.html
48 *
49 * Preemptive Digipeating (new in version 0.8)
50 *
51 * http://www.aprs.org/aprs12/preemptive-digipeating.txt
52 *
53 *------------------------------------------------------------------*/
54
55 #define DIGIPEATER_C
56
57 #include "direwolf.h"
58
59 #include <stdlib.h>
60 #include <string.h>
61 #include <assert.h>
62 #include <stdio.h>
63 #include <ctype.h> /* for isdigit, isupper */
64 #include "regex.h"
65 #include <unistd.h>
66
67 #include "ax25_pad.h"
68 #include "digipeater.h"
69 #include "textcolor.h"
70 #include "dedupe.h"
71 #include "tq.h"
72 #include "pfilter.h"
73
74
75 static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
76 regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter);
77
78
79 /*
80 * Keep pointer to configuration options.
81 * Set by digipeater_init and used later.
82 */
83
84
85 static struct audio_s *save_audio_config_p;
86 static struct digi_config_s *save_digi_config_p;
87
88
89 /*
90 * Maintain count of packets digipeated for each combination of from/to channel.
91 */
92
93 static int digi_count[MAX_CHANS][MAX_CHANS];
94
digipeater_get_count(int from_chan,int to_chan)95 int digipeater_get_count (int from_chan, int to_chan) {
96 return (digi_count[from_chan][to_chan]);
97 }
98
99
100
101 /*------------------------------------------------------------------------------
102 *
103 * Name: digipeater_init
104 *
105 * Purpose: Initialize with stuff from configuration file.
106 *
107 * Inputs: p_audio_config - Configuration for audio channels.
108 *
109 * p_digi_config - Digipeater configuration details.
110 *
111 * Outputs: Save pointers to configuration for later use.
112 *
113 * Description: Called once at application startup time.
114 *
115 *------------------------------------------------------------------------------*/
116
digipeater_init(struct audio_s * p_audio_config,struct digi_config_s * p_digi_config)117 void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config)
118 {
119 save_audio_config_p = p_audio_config;
120 save_digi_config_p = p_digi_config;
121
122 dedupe_init (p_digi_config->dedupe_time);
123 }
124
125
126
127
128 /*------------------------------------------------------------------------------
129 *
130 * Name: digipeater
131 *
132 * Purpose: Re-transmit packet if it matches the rules.
133 *
134 * Inputs: chan - Radio channel where it was received.
135 *
136 * pp - Packet object.
137 *
138 * Returns: None.
139 *
140 *
141 *------------------------------------------------------------------------------*/
142
143
144
digipeater(int from_chan,packet_t pp)145 void digipeater (int from_chan, packet_t pp)
146 {
147 int to_chan;
148
149
150 // dw_printf ("digipeater()\n");
151
152
153
154 // Network TNC is OK for UI frames where we don't care about timing.
155
156 if ( from_chan < 0 || from_chan >= MAX_CHANS ||
157 (save_audio_config_p->achan[from_chan].medium != MEDIUM_RADIO &&
158 save_audio_config_p->achan[from_chan].medium != MEDIUM_NETTNC)) {
159 text_color_set(DW_COLOR_ERROR);
160 dw_printf ("APRS digipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
161 }
162
163
164 /*
165 * First pass: Look at packets being digipeated to same channel.
166 *
167 * We want these to get out quickly.
168 */
169
170 for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
171 if (save_digi_config_p->enabled[from_chan][to_chan]) {
172 if (to_chan == from_chan) {
173 packet_t result;
174
175 result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
176 save_audio_config_p->achan[to_chan].mycall,
177 &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan],
178 to_chan, save_digi_config_p->preempt[from_chan][to_chan],
179 save_digi_config_p->filter_str[from_chan][to_chan]);
180 if (result != NULL) {
181 dedupe_remember (pp, to_chan);
182 tq_append (to_chan, TQ_PRIO_0_HI, result);
183 digi_count[from_chan][to_chan]++;
184 }
185 }
186 }
187 }
188
189
190 /*
191 * Second pass: Look at packets being digipeated to different channel.
192 *
193 * These are lower priority
194 */
195
196 for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
197 if (save_digi_config_p->enabled[from_chan][to_chan]) {
198 if (to_chan != from_chan) {
199 packet_t result;
200
201 result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
202 save_audio_config_p->achan[to_chan].mycall,
203 &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan],
204 to_chan, save_digi_config_p->preempt[from_chan][to_chan],
205 save_digi_config_p->filter_str[from_chan][to_chan]);
206 if (result != NULL) {
207 dedupe_remember (pp, to_chan);
208 tq_append (to_chan, TQ_PRIO_1_LO, result);
209 digi_count[from_chan][to_chan]++;
210 }
211 }
212 }
213 }
214
215 } /* end digipeater */
216
217
218
219 /*------------------------------------------------------------------------------
220 *
221 * Name: digipeat_match
222 *
223 * Purpose: A simple digipeater for APRS.
224 *
225 * Input: pp - Pointer to a packet object.
226 *
227 * mycall_rec - Call of my station, with optional SSID,
228 * associated with the radio channel where the
229 * packet was received.
230 *
231 * mycall_xmit - Call of my station, with optional SSID,
232 * associated with the radio channel where the
233 * packet is to be transmitted. Could be the same as
234 * mycall_rec or different.
235 *
236 * alias - Compiled pattern for my station aliases or
237 * "trapping" (repeating only once).
238 *
239 * wide - Compiled pattern for normal WIDEn-n digipeating.
240 *
241 * to_chan - Channel number that we are transmitting to.
242 * This is needed to maintain a history for
243 * removing duplicates during specified time period.
244 *
245 * preempt - Option for "preemptive" digipeating.
246 *
247 * filter_str - Filter expression string or NULL.
248 *
249 * Returns: Packet object for transmission or NULL.
250 * The original packet is not modified. (with one exception, probably obsolete)
251 * We make a copy and return that modified copy!
252 * This is very important because we could digipeat from one channel to many.
253 *
254 * Description: The packet will be digipeated if the next unused digipeater
255 * field matches one of the following:
256 *
257 * - mycall_rec
258 * - udigi list (only once)
259 * - wide list (usual wideN-N rules)
260 *
261 *------------------------------------------------------------------------------*/
262
263 #define OBSOLETE14 1
264
265
266 #ifndef OBSOLETE14
267 static char *dest_ssid_path[16] = {
268 "", /* Use VIA path */
269 "WIDE1-1",
270 "WIDE2-2",
271 "WIDE3-3",
272 "WIDE4-4",
273 "WIDE5-5",
274 "WIDE6-6",
275 "WIDE7-7",
276 "WIDE1-1", /* North */
277 "WIDE1-1", /* South */
278 "WIDE1-1", /* East */
279 "WIDE1-1", /* West */
280 "WIDE2-2", /* North */
281 "WIDE2-2", /* South */
282 "WIDE2-2", /* East */
283 "WIDE2-2" }; /* West */
284 #endif
285
286
digipeat_match(int from_chan,packet_t pp,char * mycall_rec,char * mycall_xmit,regex_t * alias,regex_t * wide,int to_chan,enum preempt_e preempt,char * filter_str)287 static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
288 regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str)
289 {
290 char source[AX25_MAX_ADDR_LEN];
291 int ssid;
292 int r;
293 char repeater[AX25_MAX_ADDR_LEN];
294 int err;
295 char err_msg[100];
296
297 /*
298 * First check if filtering has been configured.
299 */
300 if (filter_str != NULL) {
301
302 if (pfilter(from_chan, to_chan, filter_str, pp, 1) != 1) {
303 return(NULL);
304 }
305 }
306
307 /*
308 * The spec says:
309 *
310 * The SSID in the Destination Address field of all packets is coded to specify
311 * the APRS digipeater path.
312 * If the Destination Address SSID is -0, the packet follows the standard AX.25
313 * digipeater ("VIA") path contained in the Digipeater Addresses field of the
314 * AX.25 frame.
315 * If the Destination Address SSID is non-zero, the packet follows one of 15
316 * generic APRS digipeater paths.
317 *
318 *
319 * What if this is non-zero but there is also a digipeater path?
320 * I will ignore this if there is an explicit path.
321 *
322 * Note that this modifies the input. But only once!
323 * Otherwise we don't want to modify the input because this could be called multiple times.
324 */
325
326 #ifndef OBSOLETE14 // Took it out in 1.4
327
328 if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
329 ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
330 ax25_set_ssid(pp, AX25_DESTINATION, 0);
331 /* Continue with general case, below. */
332 }
333 #endif
334
335
336 /*
337 * Find the first repeater station which doesn't have "has been repeated" set.
338 *
339 * r = index of the address position in the frame.
340 */
341 r = ax25_get_first_not_repeated(pp);
342
343 if (r < AX25_REPEATER_1) {
344 return (NULL);
345 }
346
347 ax25_get_addr_with_ssid(pp, r, repeater);
348 ssid = ax25_get_ssid(pp, r);
349
350 #if DEBUG
351 text_color_set(DW_COLOR_DEBUG);
352 dw_printf ("First unused digipeater is %s, ssid=%d\n", repeater, ssid);
353 #endif
354
355
356 /*
357 * First check for explicit use of my call, including SSID.
358 * Someone might explicitly specify a particular path for testing purposes.
359 * This will bypass the usual checks for duplicates and my call in the source.
360 *
361 * In this case, we don't check the history so it would be possible
362 * to have a loop (of limited size) if someone constructed the digipeater paths
363 * correctly. I would expect it only for testing purposes.
364 */
365
366 if (strcmp(repeater, mycall_rec) == 0) {
367 packet_t result;
368
369 result = ax25_dup (pp);
370 assert (result != NULL);
371
372 /* If using multiple radio channels, they */
373 /* could have different calls. */
374 ax25_set_addr (result, r, mycall_xmit);
375 ax25_set_h (result, r);
376 return (result);
377 }
378
379 /*
380 * Don't digipeat my own. Fixed in 1.4 dev H.
381 * Alternatively we might feed everything transmitted into
382 * dedupe_remember rather than only frames out of digipeater.
383 */
384 ax25_get_addr_with_ssid(pp, AX25_SOURCE, source);
385 if (strcmp(source, mycall_rec) == 0) {
386 return (NULL);
387 }
388
389
390 /*
391 * Next try to avoid retransmitting redundant information.
392 * Duplicates are detected by comparing only:
393 * - source
394 * - destination
395 * - info part
396 * - but not the via path. (digipeater addresses)
397 * A history is kept for some amount of time, typically 30 seconds.
398 * For efficiency, only a checksum, rather than the complete fields
399 * might be kept but the result is the same.
400 * Packets transmitted recently will not be transmitted again during
401 * the specified time period.
402 *
403 */
404
405 if (dedupe_check(pp, to_chan)) {
406 //#if DEBUG
407 /* Might be useful if people are wondering why */
408 /* some are not repeated. Might also cause confusion. */
409
410 text_color_set(DW_COLOR_INFO);
411 dw_printf ("Digipeater: Drop redundant packet to channel %d.\n", to_chan);
412 //#endif
413 return NULL;
414 }
415
416 /*
417 * For the alias pattern, we unconditionally digipeat it once.
418 * i.e. Just replace it with MYCALL.
419 *
420 * My call should be an implied member of this set.
421 * In this implementation, we already caught it further up.
422 */
423 err = regexec(alias,repeater,0,NULL,0);
424 if (err == 0) {
425 packet_t result;
426
427 result = ax25_dup (pp);
428 assert (result != NULL);
429
430 ax25_set_addr (result, r, mycall_xmit);
431 ax25_set_h (result, r);
432 return (result);
433 }
434 else if (err != REG_NOMATCH) {
435 regerror(err, alias, err_msg, sizeof(err_msg));
436 text_color_set (DW_COLOR_ERROR);
437 dw_printf ("%s\n", err_msg);
438 }
439
440 /*
441 * If preemptive digipeating is enabled, try matching my call
442 * and aliases against all remaining unused digipeaters.
443 */
444
445 if (preempt != PREEMPT_OFF) {
446 int r2;
447
448 for (r2 = r+1; r2 < ax25_get_num_addr(pp); r2++) {
449 char repeater2[AX25_MAX_ADDR_LEN];
450
451 ax25_get_addr_with_ssid(pp, r2, repeater2);
452
453 //text_color_set (DW_COLOR_DEBUG);
454 //dw_printf ("test match %d %s\n", r2, repeater2);
455
456 if (strcmp(repeater2, mycall_rec) == 0 ||
457 regexec(alias,repeater2,0,NULL,0) == 0) {
458 packet_t result;
459
460 result = ax25_dup (pp);
461 assert (result != NULL);
462
463 ax25_set_addr (result, r2, mycall_xmit);
464 ax25_set_h (result, r2);
465
466 switch (preempt) {
467 case PREEMPT_DROP: /* remove all prior */
468 while (r2 > AX25_REPEATER_1) {
469 ax25_remove_addr (result, r2-1);
470 r2--;
471 }
472 break;
473
474 case PREEMPT_MARK:
475 r2--;
476 while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) {
477 ax25_set_h (result, r2);
478 r2--;
479 }
480 break;
481
482 case PREEMPT_TRACE: /* remove prior unused */
483 default:
484 while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) {
485 ax25_remove_addr (result, r2-1);
486 r2--;
487 }
488 break;
489 }
490
491 // Idea: Here is an interesting idea for a new option. REORDER?
492 // The preemptive digipeater could move its call after the (formerly) last used digi field
493 // and preserve all the unused fields after that. The list of used addresses would
494 // accurately record the journey taken by the packet.
495
496 // https://groups.yahoo.com/neo/groups/aprsisce/conversations/topics/31935
497
498 // > I was wishing for a non-marking preemptive digipeat so that the original packet would be left intact
499 // > or maybe something like WIDE1-1,WIDE2-1,KJ4OVQ-9 becoming KJ4OVQ-9*,WIDE1-1,WIDE2-1.
500
501 return (result);
502 }
503 }
504 }
505
506 /*
507 * For the wide pattern, we check the ssid and decrement it.
508 */
509
510 err = regexec(wide,repeater,0,NULL,0);
511 if (err == 0) {
512
513 /*
514 * If ssid == 1, we simply replace the repeater with my call and
515 * mark it as being used.
516 *
517 * Otherwise, if ssid in range of 2 to 7,
518 * Decrement y and don't mark repeater as being used.
519 * Insert own call ahead of this one for tracing if we don't already have the
520 * maximum number of repeaters.
521 */
522
523 if (ssid == 1) {
524 packet_t result;
525
526 result = ax25_dup (pp);
527 assert (result != NULL);
528
529 ax25_set_addr (result, r, mycall_xmit);
530 ax25_set_h (result, r);
531 return (result);
532 }
533
534 if (ssid >= 2 && ssid <= 7) {
535 packet_t result;
536
537 result = ax25_dup (pp);
538 assert (result != NULL);
539
540 ax25_set_ssid(result, r, ssid-1); // should be at least 1
541
542 if (ax25_get_num_repeaters(pp) < AX25_MAX_REPEATERS) {
543 ax25_insert_addr (result, r, mycall_xmit);
544 ax25_set_h (result, r);
545 }
546 return (result);
547 }
548 }
549 else if (err != REG_NOMATCH) {
550 regerror(err, wide, err_msg, sizeof(err_msg));
551 text_color_set (DW_COLOR_ERROR);
552 dw_printf ("%s\n", err_msg);
553 }
554
555
556 /*
557 * Don't repeat it if we get here.
558 */
559
560 return (NULL);
561 }
562
563
564
565 /*------------------------------------------------------------------------------
566 *
567 * Name: digi_regen
568 *
569 * Purpose: Send regenerated copy of what we received.
570 *
571 * Inputs: chan - Radio channel where it was received.
572 *
573 * pp - Packet object.
574 *
575 * Returns: None.
576 *
577 * Description: TODO...
578 *
579 * Initial reports were favorable.
580 * Should document what this is all about if there is still interest...
581 *
582 *------------------------------------------------------------------------------*/
583
digi_regen(int from_chan,packet_t pp)584 void digi_regen (int from_chan, packet_t pp)
585 {
586 int to_chan;
587 packet_t result;
588
589 // dw_printf ("digi_regen()\n");
590
591 assert (from_chan >= 0 && from_chan < MAX_CHANS);
592
593 for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
594 if (save_digi_config_p->regen[from_chan][to_chan]) {
595 result = ax25_dup (pp);
596 if (result != NULL) {
597 // TODO: if AX.25 and has been digipeated, put in HI queue?
598 tq_append (to_chan, TQ_PRIO_1_LO, result);
599 }
600 }
601 }
602
603 } /* end dig_regen */
604
605
606
607 /*-------------------------------------------------------------------------
608 *
609 * Name: main
610 *
611 * Purpose: Standalone test case for this funtionality.
612 *
613 * Usage: make -f Makefile.<platform> dtest
614 * ./dtest
615 *
616 *------------------------------------------------------------------------*/
617
618 #if DIGITEST
619
620 static char mycall[] = "WB2OSZ-9";
621
622 static regex_t alias_re;
623
624 static regex_t wide_re;
625
626 static int failed;
627
628 static enum preempt_e preempt = PREEMPT_OFF;
629
630
631
test(char * in,char * out)632 static void test (char *in, char *out)
633 {
634 packet_t pp, result;
635 char rec[256];
636 char xmit[256];
637 unsigned char *pinfo;
638 int info_len;
639 unsigned char frame[AX25_MAX_PACKET_LEN];
640 int frame_len;
641 alevel_t alevel;
642
643 dw_printf ("\n");
644
645 /*
646 * As an extra test, change text to internal format back to
647 * text again to make sure it comes out the same.
648 */
649 pp = ax25_from_text (in, 1);
650 assert (pp != NULL);
651
652 ax25_format_addrs (pp, rec);
653 info_len = ax25_get_info (pp, &pinfo);
654 (void)info_len;
655 strlcat (rec, (char*)pinfo, sizeof(rec));
656
657 if (strcmp(in, rec) != 0) {
658 text_color_set(DW_COLOR_ERROR);
659 dw_printf ("Text/internal/text error-1 %s -> %s\n", in, rec);
660 }
661
662 /*
663 * Just for more fun, write as the frame format, read it back
664 * again, and make sure it is still the same.
665 */
666
667 frame_len = ax25_pack (pp, frame);
668 ax25_delete (pp);
669
670 alevel.rec = 50;
671 alevel.mark = 50;
672 alevel.space = 50;
673
674 pp = ax25_from_frame (frame, frame_len, alevel);
675 assert (pp != NULL);
676 ax25_format_addrs (pp, rec);
677 info_len = ax25_get_info (pp, &pinfo);
678 strlcat (rec, (char*)pinfo, sizeof(rec));
679
680 if (strcmp(in, rec) != 0) {
681 text_color_set(DW_COLOR_ERROR);
682 dw_printf ("internal/frame/internal/text error-2 %s -> %s\n", in, rec);
683 }
684
685 /*
686 * On with the digipeater test.
687 */
688
689 text_color_set(DW_COLOR_REC);
690 dw_printf ("Rec\t%s\n", rec);
691
692 //TODO: Add filtering to test.
693 // V
694 result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, NULL);
695
696 if (result != NULL) {
697
698 dedupe_remember (result, 0);
699 ax25_format_addrs (result, xmit);
700 info_len = ax25_get_info (result, &pinfo);
701 strlcat (xmit, (char*)pinfo, sizeof(xmit));
702 ax25_delete (result);
703 }
704 else {
705 strlcpy (xmit, "", sizeof(xmit));
706 }
707
708 text_color_set(DW_COLOR_XMIT);
709 dw_printf ("Xmit\t%s\n", xmit);
710
711 if (strcmp(xmit, out) == 0) {
712 text_color_set(DW_COLOR_INFO);
713 dw_printf ("OK\n");
714 }
715 else {
716 text_color_set(DW_COLOR_ERROR);
717 dw_printf ("Expect\t%s\n", out);
718 failed++;
719 }
720
721 dw_printf ("\n");
722 }
723
main(int argc,char * argv[])724 int main (int argc, char *argv[])
725 {
726 int e;
727 failed = 0;
728 char message[256];
729
730 dedupe_init (4);
731
732 /*
733 * Compile the patterns.
734 */
735 e = regcomp (&alias_re, "^WIDE[4-7]-[1-7]|CITYD$", REG_EXTENDED|REG_NOSUB);
736 if (e != 0) {
737 regerror (e, &alias_re, message, sizeof(message));
738 text_color_set(DW_COLOR_ERROR);
739 dw_printf ("\n%s\n\n", message);
740 exit (1);
741 }
742
743 e = regcomp (&wide_re, "^WIDE[1-7]-[1-7]$|^TRACE[1-7]-[1-7]$|^MA[1-7]-[1-7]$", REG_EXTENDED|REG_NOSUB);
744 if (e != 0) {
745 regerror (e, &wide_re, message, sizeof(message));
746 text_color_set(DW_COLOR_ERROR);
747 dw_printf ("\n%s\n\n", message);
748 exit (1);
749 }
750
751 /*
752 * Let's start with the most basic cases.
753 */
754
755 test ( "W1ABC>TEST01,TRACE3-3:",
756 "W1ABC>TEST01,WB2OSZ-9*,TRACE3-2:");
757
758 test ( "W1ABC>TEST02,WIDE3-3:",
759 "W1ABC>TEST02,WB2OSZ-9*,WIDE3-2:");
760
761 test ( "W1ABC>TEST03,WIDE3-2:",
762 "W1ABC>TEST03,WB2OSZ-9*,WIDE3-1:");
763
764 test ( "W1ABC>TEST04,WIDE3-1:",
765 "W1ABC>TEST04,WB2OSZ-9*:");
766
767 /*
768 * Look at edge case of maximum number of digipeaters.
769 */
770 test ( "W1ABC>TEST11,R1,R2,R3,R4,R5,R6*,WIDE3-3:",
771 "W1ABC>TEST11,R1,R2,R3,R4,R5,R6,WB2OSZ-9*,WIDE3-2:");
772
773 test ( "W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-3:",
774 "W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-2:");
775
776 test ( "W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7*,WIDE3-1:",
777 "W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7,WB2OSZ-9*:");
778
779 /*
780 * "Trap" large values of "N" by repeating only once.
781 */
782 test ( "W1ABC>TEST21,WIDE4-4:",
783 "W1ABC>TEST21,WB2OSZ-9*:");
784
785 test ( "W1ABC>TEST22,WIDE7-7:",
786 "W1ABC>TEST22,WB2OSZ-9*:");
787
788 /*
789 * Only values in range of 1 thru 7 are valid.
790 */
791 test ( "W1ABC>TEST31,WIDE0-4:",
792 "");
793
794 test ( "W1ABC>TEST32,WIDE8-4:",
795 "");
796
797 test ( "W1ABC>TEST33,WIDE2:",
798 "");
799
800
801 /*
802 * and a few cases actually heard.
803 */
804
805 test ( "WA1ENO>FN42ND,W1MV-1*,WIDE3-2:",
806 "WA1ENO>FN42ND,W1MV-1,WB2OSZ-9*,WIDE3-1:");
807
808 test ( "W1ON-3>BEACON:",
809 "");
810
811 test ( "W1CMD-9>TQ3Y8P,N1RCW-2,W1CLA-1,N8VIM,WIDE2*:",
812 "");
813
814 test ( "W1CLA-1>APX192,W1GLO-1,WIDE2*:",
815 "");
816
817 test ( "AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM*,WIDE2-1:",
818 "AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM,WB2OSZ-9*:");
819
820 /*
821 * Someone is still using the old style and will probably be disappointed.
822 */
823
824 test ( "K1CPD-1>T2SR5R,RELAY*,WIDE,WIDE,SGATE,WIDE:",
825 "");
826
827
828 /*
829 * Change destination SSID to normal digipeater if none specified. (Obsolete, removed.)
830 */
831
832 test ( "W1ABC>TEST-3:",
833 #ifndef OBSOLETE14
834 "W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
835 #else
836 "");
837 #endif
838 test ( "W1DEF>TEST-3,WIDE2-2:",
839 "W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");
840
841 /*
842 * Drop duplicates within specified time interval.
843 * Only the first 1 of 3 should be retransmitted.
844 * The 4th case might be controversial.
845 */
846
847 test ( "W1XYZ>TEST,R1*,WIDE3-2:info1",
848 "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info1");
849
850 test ( "W1XYZ>TEST,R2*,WIDE3-2:info1",
851 "");
852
853 test ( "W1XYZ>TEST,R3*,WIDE3-2:info1",
854 "");
855
856 test ( "W1XYZ>TEST,R1*,WB2OSZ-9:has explicit routing",
857 "W1XYZ>TEST,R1,WB2OSZ-9*:has explicit routing");
858
859
860 /*
861 * Allow same thing after adequate time.
862 */
863 SLEEP_SEC (5);
864
865 test ( "W1XYZ>TEST,R3*,WIDE3-2:info1",
866 "W1XYZ>TEST,R3,WB2OSZ-9*,WIDE3-1:info1");
867
868 /*
869 * Although source and destination match, the info field is different.
870 */
871
872 test ( "W1XYZ>TEST,R1*,WIDE3-2:info4",
873 "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info4");
874
875 test ( "W1XYZ>TEST,R1*,WIDE3-2:info5",
876 "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info5");
877
878 test ( "W1XYZ>TEST,R1*,WIDE3-2:info6",
879 "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info6");
880
881 /*
882 * New in version 0.8.
883 * "Preemptive" digipeating looks ahead beyond the first unused digipeater.
884 */
885
886 test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:off",
887 "");
888
889 preempt = PREEMPT_DROP;
890
891 test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:drop",
892 "W1ABC>TEST11,WB2OSZ-9*,CITYE:drop");
893
894 preempt = PREEMPT_MARK;
895
896 test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:mark1",
897 "W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark1");
898
899 test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,WB2OSZ-9,CITYE:mark2",
900 "W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark2");
901
902 preempt = PREEMPT_TRACE;
903
904 test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:trace1",
905 "W1ABC>TEST11,CITYA,WB2OSZ-9*,CITYE:trace1");
906
907 test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD:trace2",
908 "W1ABC>TEST11,CITYA,WB2OSZ-9*:trace2");
909
910 test ( "W1ABC>TEST11,CITYB,CITYC,CITYD:trace3",
911 "W1ABC>TEST11,WB2OSZ-9*:trace3");
912
913 test ( "W1ABC>TEST11,CITYA*,CITYW,CITYX,CITYY,CITYZ:nomatch",
914 "");
915
916
917 /*
918 * Did I miss any cases?
919 * Yes. Don't retransmit my own. 1.4H
920 */
921
922 test ( "WB2OSZ-7>TEST14,WIDE1-1,WIDE1-1:stuff",
923 "WB2OSZ-7>TEST14,WB2OSZ-9*,WIDE1-1:stuff");
924
925 test ( "WB2OSZ-9>TEST14,WIDE1-1,WIDE1-1:from myself",
926 "");
927
928 test ( "WB2OSZ-9>TEST14,WIDE1-1*,WB2OSZ-9:from myself but explicit routing",
929 "WB2OSZ-9>TEST14,WIDE1-1,WB2OSZ-9*:from myself but explicit routing");
930
931 test ( "WB2OSZ-15>TEST14,WIDE1-1,WIDE1-1:stuff",
932 "WB2OSZ-15>TEST14,WB2OSZ-9*,WIDE1-1:stuff");
933
934
935
936 if (failed == 0) {
937 dw_printf ("SUCCESS -- All digipeater tests passed.\n");
938 }
939 else {
940 text_color_set(DW_COLOR_ERROR);
941 dw_printf ("ERROR - %d digipeater tests failed.\n", failed);
942 }
943
944 return ( failed != 0 );
945
946 } /* end main */
947
948 #endif /* if DIGITEST */
949
950 /* end digipeater.c */
951