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