// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2015, 2016, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // /*------------------------------------------------------------------ * * Module: pfilter.c * * Purpose: Packet filtering based on characteristics. * * Description: Sometimes it is desirable to digipeat or drop packets based on rules. * For example, you might want to pass only weather information thru * a cross band digipeater or you might want to drop all packets from * an abusive user that is overloading the channel. * * The filter specifications are loosely modeled after the IGate Server-side Filter * Commands: http://www.aprs-is.net/javaprsfilter.aspx * * We add AND, OR, NOT, and ( ) to allow very flexible control. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "decode_aprs.h" #include "latlong.h" #include "pfilter.h" #include "mheard.h" /* * Global stuff (to this file) * * These are set by init function. */ static struct igate_config_s *save_igate_config_p; static int s_debug = 0; /*------------------------------------------------------------------- * * Name: pfilter_init * * Purpose: One time initialization when main application starts up. * * Inputs: p_igate_config - IGate configuration. * * debug_level - 0 no debug output. * 1 single summary line with final result. Indent by 1. * 2 details from each filter specification. Indent by 3. * 3 Logical operators. Indent by 2. * *--------------------------------------------------------------------*/ void pfilter_init (struct igate_config_s *p_igate_config, int debug_level) { s_debug = debug_level; save_igate_config_p = p_igate_config; } typedef enum token_type_e { TOKEN_AND, TOKEN_OR, TOKEN_NOT, TOKEN_LPAREN, TOKEN_RPAREN, TOKEN_FILTER_SPEC, TOKEN_EOL } token_type_t; #define MAX_FILTER_LEN 1024 #define MAX_TOKEN_LEN 1024 typedef struct pfstate_s { int from_chan; /* From and to channels. MAX_CHANS is used for IGate. */ int to_chan; /* Used only for debug and error messages. */ /* * Original filter string from config file. * All control characters should be replaced by spaces. */ char filter_str[MAX_FILTER_LEN]; int nexti; /* Next available character index. */ /* * Packet object. */ packet_t pp; /* * Are we processing APRS or connected mode? * This determines which types of filters are available. */ int is_aprs; /* * Packet split into separate parts if APRS. * Most interesting fields are: * * g_symbol_table - / \ or overlay * g_symbol_code * g_lat, g_lon - Location * g_name - for object or item * g_comment */ decode_aprs_t decoded; /* * These are set by next_token. */ token_type_t token_type; char token_str[MAX_TOKEN_LEN]; /* Printable string representation for use in error messages. */ int tokeni; /* Index in original string for enhanced error messages. */ } pfstate_t; static int parse_expr (pfstate_t *pf); static int parse_or_expr (pfstate_t *pf); static int parse_and_expr (pfstate_t *pf); static int parse_primary (pfstate_t *pf); static int parse_filter_spec (pfstate_t *pf); static void next_token (pfstate_t *pf); static void print_error (pfstate_t *pf, char *msg); static int filt_bodgu (pfstate_t *pf, char *pattern); static int filt_t (pfstate_t *pf); static int filt_r (pfstate_t *pf, char *sdist); static int filt_s (pfstate_t *pf); static int filt_i (pfstate_t *pf); static char *bool2text (int val) { if (val == 1) return "TRUE"; if (val == 0) return "FALSE"; if (val == -1) return "ERROR"; return "OOPS!"; } /*------------------------------------------------------------------- * * Name: pfilter.c * * Purpose: Decide whether a packet should be allowed thru. * * Inputs: from_chan - Channel packet is coming from. * to_chan - Channel packet is going to. * Both are 0 .. MAX_CHANS-1 or MAX_CHANS for IGate. * For debug/error messages only. * * filter - String of filter specs and logical operators to combine them. * * pp - Packet object handle. * * is_aprs - True for APRS, false for connected mode digipeater. * Connected mode allows a subset of the filter types, only * looking at the addresses, not information part contents. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: This might be running in multiple threads at the same time so * no static data allowed and take other thread-safe precautions. * *--------------------------------------------------------------------*/ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs) { pfstate_t pfstate; char *p; int result; assert (from_chan >= 0 && from_chan <= MAX_CHANS); assert (to_chan >= 0 && to_chan <= MAX_CHANS); memset (&pfstate, 0, sizeof(pfstate)); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n"); return (-1); } if (filter == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR in pfilter: NULL filter string pointer. Please report this!\n"); return (-1); } pfstate.from_chan = from_chan; pfstate.to_chan = to_chan; /* Copy filter string, changing any control characters to spaces. */ strlcpy (pfstate.filter_str, filter, sizeof(pfstate.filter_str)); pfstate.nexti = 0; for (p = pfstate.filter_str; *p != '\0'; p++) { if (iscntrl(*p)) { *p = ' '; } } pfstate.pp = pp; pfstate.is_aprs = is_aprs; if (is_aprs) { decode_aprs (&pfstate.decoded, pp, 1, NULL); } next_token(&pfstate); if (pfstate.token_type == TOKEN_EOL) { /* Empty filter means reject all. */ result = 0; } else { result = parse_expr (&pfstate); if (pfstate.token_type != TOKEN_AND && pfstate.token_type != TOKEN_OR && pfstate.token_type != TOKEN_EOL) { print_error (&pfstate, "Expected logical operator or end of line here."); result = -1; } } if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); if (from_chan == MAX_CHANS) { dw_printf (" Packet filter from IGate to radio channel %d returns %s\n", to_chan, bool2text(result)); } else if (to_chan == MAX_CHANS) { dw_printf (" Packet filter from radio channel %d to IGate returns %s\n", from_chan, bool2text(result)); } else if (is_aprs) { dw_printf (" Packet filter for APRS digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result)); } else { dw_printf (" Packet filter for traditional digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result)); } } return (result); } /* end pfilter */ /*------------------------------------------------------------------- * * Name: next_token * * Purpose: Extract the next token from input string. * * Inputs: pf - Pointer to current state information. * * Outputs: See definition of the structure. * * Description: Look for these special operators: & | ! ( ) end-of-line * Anything else is considered a filter specification. * Note that a filter-spec must be followed by space or * end of line. This is so the magic characters can appear in one. * * Future: Maybe allow words like 'OR' as alternatives to symbols like '|'. * * Unresolved Issue: * * Adding the special operators adds a new complication. * How do we handle the case where we want those characters in * a filter specification? For example how do we know if the * last character of /#& means HF gateway or AND the next part * of the expression. * * Approach 1: Require white space after all filter specifications. * Currently implemented. * Simple. Easy to explain. * More readable than having everything squashed together. * * Approach 2: Use escape character to get literal value. e.g. s/#\& * Linux people would be comfortable with this but * others might have a problem with it. * * Approach 3: use quotation marks if it contains special characters or space. * "s/#&" Simple. Allows embedded space but I'm not sure * that's useful. Doesn't hurt to always put the quotes there * if you can't remember which characters are special. * *--------------------------------------------------------------------*/ static void next_token (pfstate_t *pf) { while (pf->filter_str[pf->nexti] == ' ') { pf->nexti++; } pf->tokeni = pf->nexti; if (pf->filter_str[pf->nexti] == '\0') { pf->token_type = TOKEN_EOL; strlcpy (pf->token_str, "end-of-line", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '&') { pf->nexti++; pf->token_type = TOKEN_AND; strlcpy (pf->token_str, "\"&\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '|') { pf->nexti++; pf->token_type = TOKEN_OR; strlcpy (pf->token_str, "\"|\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '!') { pf->nexti++; pf->token_type = TOKEN_NOT; strlcpy (pf->token_str, "\"!\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '(') { pf->nexti++; pf->token_type = TOKEN_LPAREN; strlcpy (pf->token_str, "\"(\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == ')') { pf->nexti++; pf->token_type = TOKEN_RPAREN; strlcpy (pf->token_str, "\")\"", sizeof(pf->token_str)); } else { char *p = pf->token_str; pf->token_type = TOKEN_FILTER_SPEC; do { *p++ = pf->filter_str[pf->nexti++]; } while (pf->filter_str[pf->nexti] != ' ' && pf->filter_str[pf->nexti] != '\0'); *p = '\0'; } } /* end next_token */ /*------------------------------------------------------------------- * * Name: parse_expr * parse_or_expr * parse_and_expr * parse_primary * * Purpose: Recursive descent parser to evaluate filter specifications * contained within expressions with & | ! ( ). * * Inputs: pf - Pointer to current state information. * * Returns: 1 = yes * 0 = no * -1 = error detected * *--------------------------------------------------------------------*/ static int parse_expr (pfstate_t *pf) { int result; result = parse_or_expr (pf); return (result); } /* or_expr:: and_expr [ | and_expr ] ... */ static int parse_or_expr (pfstate_t *pf) { int result; result = parse_and_expr (pf); if (result < 0) return (-1); while (pf->token_type == TOKEN_OR) { int e; next_token (pf); e = parse_and_expr (pf); if (s_debug >= 3) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s | %s\n", bool2text(result), bool2text(e)); } if (e < 0) return (-1); result |= e; } return (result); } /* and_expr:: primary [ & primary ] ... */ static int parse_and_expr (pfstate_t *pf) { int result; result = parse_primary (pf); if (result < 0) return (-1); while (pf->token_type == TOKEN_AND) { int e; next_token (pf); e = parse_primary (pf); if (s_debug >= 3) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s & %s\n", bool2text(result), bool2text(e)); } if (e < 0) return (-1); result &= e; } return (result); } /* primary:: ( expr ) */ /* ! primary */ /* filter_spec */ static int parse_primary (pfstate_t *pf) { int result; if (pf->token_type == TOKEN_LPAREN) { next_token (pf); result = parse_expr (pf); if (pf->token_type == TOKEN_RPAREN) { next_token (pf); } else { print_error (pf, "Expected \")\" here.\n"); result = -1; } } else if (pf->token_type == TOKEN_NOT) { int e; next_token (pf); e = parse_primary (pf); if (s_debug >= 3) { text_color_set(DW_COLOR_DEBUG); dw_printf (" ! %s\n", bool2text(e)); } if (e < 0) result = -1; else result = ! e; } else if (pf->token_type == TOKEN_FILTER_SPEC) { result = parse_filter_spec (pf); } else { print_error (pf, "Expected filter specification, (, or ! here."); result = -1; } return (result); } /*------------------------------------------------------------------- * * Name: parse_filter_spec * * Purpose: Parse and evaluate filter specification. * * Inputs: pf - Pointer to current state information. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: All filter specifications are allowed for APRS. * Only those dealing with addresses are allowed for connected digipeater. * * b - budlist (source) * d - digipeaters used * v - digipeaters not used * u - unproto (destination) * *--------------------------------------------------------------------*/ static int parse_filter_spec (pfstate_t *pf) { int result = -1; if ( ( ! pf->is_aprs) && strchr ("01bdvu", pf->token_str[0]) == NULL) { print_error (pf, "Only b, d, v, and u specifications are allowed for connected mode digipeater filtering."); result = -1; next_token (pf); return (result); } /* undocumented: can use 0 or 1 for testing. */ if (strcmp(pf->token_str, "0") == 0) { result = 0; } else if (strcmp(pf->token_str, "1") == 0) { result = 1; } /* simple string matching */ /* b - budlist */ else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) { /* Budlist - AX.25 source address */ /* Could be different than source encapsulated by 3rd party header. */ char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr); result = filt_bodgu (pf, addr); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr); } } /* o - object or item name */ else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) { result = filt_bodgu (pf, pf->decoded.g_name); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_name); } } /* d - was digipeated by */ else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) { int n; // Loop on all AX.25 digipeaters. result = 0; for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { // Consider only those with the H (has-been-used) bit set. if (ax25_get_h (pf->pp, n)) { char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, n, addr); result = filt_bodgu (pf, addr); } } if (s_debug >= 2) { char path[100]; ax25_format_via_path (pf->pp, path, sizeof(path)); if (strlen(path) == 0) { strcpy (path, "no digipeater path"); } text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path); } } /* v - via not used */ else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) { int n; // loop on all AX.25 digipeaters (mnemonic Via) result = 0; for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { // This is different than the previous "d" filter. // Consider only those where the the H (has-been-used) bit is NOT set. if ( ! ax25_get_h (pf->pp, n)) { char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, n, addr); result = filt_bodgu (pf, addr); } } if (s_debug >= 2) { char path[100]; ax25_format_via_path (pf->pp, path, sizeof(path)); if (strlen(path) == 0) { strcpy (path, "no digipeater path"); } text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path); } } /* g - Addressee of message. e.g. "BLN*" for bulletins. */ else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) { if (pf->decoded.g_message_subtype == message_subtype_message || pf->decoded.g_message_subtype == message_subtype_ack || pf->decoded.g_message_subtype == message_subtype_rej || pf->decoded.g_message_subtype == message_subtype_bulletin || pf->decoded.g_message_subtype == message_subtype_nws || pf->decoded.g_message_subtype == message_subtype_directed_query) { result = filt_bodgu (pf, pf->decoded.g_addressee); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee); } } else { result = 0; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "not a message"); } } } /* u - unproto (AX.25 destination) */ else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) { /* Probably want to exclude mic-e types */ /* because destination is used for part of location. */ if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') { char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr); result = filt_bodgu (pf, addr); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr); } } else { result = 0; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "MIC-E packet type"); } } } /* t - packet type: position, weather, telemetry, etc. */ else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) { result = filt_t (pf); if (s_debug >= 2) { char *infop = NULL; (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %c data type indicator\n", pf->token_str, bool2text(result), *infop); } } /* r - range */ else if (pf->token_str[0] == 'r' && ispunct(pf->token_str[1])) { /* range */ char sdist[30]; strcpy (sdist, "unknown distance"); result = filt_r (pf, sdist); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), sdist); } } /* s - symbol */ else if (pf->token_str[0] == 's' && ispunct(pf->token_str[1])) { /* symbol */ result = filt_s (pf); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); if (pf->decoded.g_symbol_table == '/') { dw_printf (" %s returns %s for symbol %c in primary table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code); } else if (pf->decoded.g_symbol_table == '\\') { dw_printf (" %s returns %s for symbol %c in alternate table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code); } else { dw_printf (" %s returns %s for symbol %c with overlay %c\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code, pf->decoded.g_symbol_table); } } } /* i - IGate messaging default */ else if (pf->token_str[0] == 'i' && ispunct(pf->token_str[1])) { /* IGatge messaging */ result = filt_i (pf); if (s_debug >= 2) { char *infop = NULL; (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); text_color_set(DW_COLOR_DEBUG); if (pf->decoded.g_packet_type == packet_type_message) { dw_printf (" %s returns %s for message to %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee); } else { dw_printf (" %s returns %s for not an APRS 'message'\n", pf->token_str, bool2text(result)); } } } /* unrecognized filter type */ else { char stemp[80]; snprintf (stemp, sizeof(stemp), "Unrecognized filter type '%c'", pf->token_str[0]); print_error (pf, stemp); result = -1; } next_token (pf); return (result); } /*------------------------------------------------------------------------------ * * Name: filt_bodgu * * Purpose: Filter with text pattern matching * * Inputs: pf - Pointer to current state information. * token_str should have one of these filter specs: * * Budlist b/call1/call2... * Object o/obj1/obj2... * Digipeater d/digi1/digi2... * Group Msg g/call1/call2... * Unproto u/unproto1/unproto2... * Via-not-yet v/digi1/digi2...noteapd * * arg - Value to match from source addr, destination, * used digipeater, object name, etc. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: Same function is used for all of these because they are so similar. * Look for exact match to any of the specified strings. * All of them allow wildcarding with single * at the end. * *------------------------------------------------------------------------------*/ static int filt_bodgu (pfstate_t *pf, char *arg) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; char *v; int result = 0; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; while (result == 0 && (v = strsep (&cp, sep)) != NULL) { int mlen; char *w; if ((w = strchr(v,'*')) != NULL) { /* Wildcarding. Should have single * on end. */ mlen = w - v; if (mlen != (int)(strlen(v) - 1)) { print_error (pf, "Any wildcard * must be at the end of pattern.\n"); return (-1); } if (strncmp(v,arg,mlen) == 0) result = 1; } else { /* Try for exact match. */ if (strcmp(v,arg) == 0) result = 1; } } return (result); } /*------------------------------------------------------------------------------ * * Name: filt_t * * Purpose: Filter by packet type. * * Inputs: pf - Pointer to current state information. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: The filter is loosely based the type filtering described here: * http://www.aprs-is.net/javAPRSFilter.aspx * * Mostly use g_packet_type and g_message_subtype from decode_aprs. * * References: * http://www.aprs-is.net/WX/ * http://wxsvr.aprs.net.au/protocol-new.html (has disappeared) * *------------------------------------------------------------------------------*/ static int filt_t (pfstate_t *pf) { char src[AX25_MAX_ADDR_LEN]; char *infop = NULL; char *f; memset (src, 0, sizeof(src)); ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src); (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); assert (infop != NULL); for (f = pf->token_str + 2; *f != '\0'; f++) { switch (*f) { case 'p': /* Position */ if (pf->decoded.g_packet_type == packet_type_position) return(1); break; case 'o': /* Object */ if (pf->decoded.g_packet_type == packet_type_object) return(1); break; case 'i': /* Item */ if (pf->decoded.g_packet_type == packet_type_item) return(1); break; case 'm': // Any "message." if (pf->decoded.g_packet_type == packet_type_message) return(1); break; case 'q': /* Query */ if (pf->decoded.g_packet_type == packet_type_query) return(1); break; case 'c': /* station Capabilities - my extension */ /* Most often used for IGate statistics. */ if (pf->decoded.g_packet_type == packet_type_capabilities) return(1); break; case 's': /* Status */ if (pf->decoded.g_packet_type == packet_type_status) return(1); break; case 't': /* Telemetry data or metadata */ if (pf->decoded.g_packet_type == packet_type_telemetry) return(1); break; case 'u': /* User-defined */ if (pf->decoded.g_packet_type == packet_type_userdefined) return(1); break; case 'h': /* has third party Header - my extension */ if (pf->decoded.g_has_thirdparty_header) return (1); break; case 'w': /* Weather */ if (pf->decoded.g_packet_type == packet_type_weather) return(1); /* Positions !=/@ with symbol code _ are weather. */ /* Object with _ symbol is also weather. APRS protocol spec page 66. */ // Can't use *infop because it would not work with 3rd party header. if ((pf->decoded.g_packet_type == packet_type_position || pf->decoded.g_packet_type == packet_type_object) && pf->decoded.g_symbol_code == '_') return (1); break; case 'n': /* NWS format */ if (pf->decoded.g_packet_type == packet_type_nws) return(1); break; default: print_error (pf, "Invalid letter in t/ filter.\n"); return (-1); break; } } return (0); /* Didn't match anything. Reject */ } /* end filt_t */ /*------------------------------------------------------------------------------ * * Name: filt_r * * Purpose: Is it in range (kilometers) of given location. * * Inputs: pf - Pointer to current state information. * token_str should contain something of format: * * r/lat/lon/dist * * We also need to know the location (if any) from the packet. * * decoded.g_lat & decoded.g_lon * * Outputs: sdist - Distance as a string for troubleshooting. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: * *------------------------------------------------------------------------------*/ static int filt_r (pfstate_t *pf, char *sdist) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; char *v; double dlat, dlon, ddist, km; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; if (pf->decoded.g_lat == G_UNKNOWN || pf->decoded.g_lon == G_UNKNOWN) { return (0); } v = strsep (&cp, sep); if (v == NULL) { print_error (pf, "Missing latitude for Range filter."); return (-1); } dlat = atof(v); v = strsep (&cp, sep); if (v == NULL) { print_error (pf, "Missing longitude for Range filter."); return (-1); } dlon = atof(v); v = strsep (&cp, sep); if (v == NULL) { print_error (pf, "Missing distance for Range filter."); return (-1); } ddist = atof(v); km = ll_distance_km (dlat, dlon, pf->decoded.g_lat, pf->decoded.g_lon); sprintf (sdist, "%.2f km", km); if (km <= ddist) { return (1); } return (0); } /*------------------------------------------------------------------------------ * * Name: filt_s * * Purpose: Filter by symbol. * * Inputs: pf - Pointer to current state information. * token_str should contain something of format: * * s/pri/alt/over * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: * * s/pri * s/pri/alt * s/pri/alt/ * s/pri/alt/over * * "pri" is zero or more symbols from the primary symbol set. * Symbol codes are any printable ASCII character other than | or ~. * (Zero symbols here would be sensible only if later alt part is specified.) * "alt" is one or more symbols from the alternate symbol set. * "over" is overlay characters for the alternate symbol set. * Only upper case letters, digits, and \ are allowed here. * If the last part is not specified, any overlay or lack of overlay, is ignored. * If the last part is specified, only the listed overlays will match. * An explicit lack of overlay is represented by the \ character. * * Examples: * s/O Balloon. * s/-> House or car from primary symbol table. * * s//# Alternate table digipeater, with or without overlay. * s//#/\ Alternate table digipeater, only if no overlay. * s//#/SL1 Alternate table digipeater, with overlay S, L, or 1. * s//#/SL\ Alternate table digipeater, with S, L, or no overlay. * * s/s/s Any variation of watercraft. Either symbol table. With or without overlay. * s/s/s/ Ship or ship sideview, only if no overlay. * s//s/J Jet Ski. * * What if you want to use the / symbol when / is being used as a delimiter here? Recall that you * can use some other special character after the initial lower case letter and this becomes the * delimiter for the rest of the specification. * * Examples: * * s:/ Red Dot. * s::/ Waypoint Destination, with or without overlay. * s:/:/ Either Red Dot or Waypoint Destination. * s:/:/: Either Red Dot or Waypoint Destination, no overlay. * * Bad example: * * Someone tried using this to include ballons: s/'/O/-/#/_ * probably following the buddy filter pattern of / between each alternative. * There should be an error message because it has more than 3 delimiter characters. * * *------------------------------------------------------------------------------*/ static int filt_s (pfstate_t *pf) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; // Delimiter character. Typically / but it could be different. char *pri = NULL, *alt = NULL, *over = NULL, *extra = NULL; char *x; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; // First, separate the parts and do a strict syntax check. pri = strsep (&cp, sep); if (pri != NULL) { // Zero length is acceptable if alternate symbol(s) specified. Will check that later. for (x = pri; *x != '\0'; x++) { if ( ! isprint(*x) || *x == '|' || *x == '~') { print_error (pf, "Symbol filter, primary must be printable ASCII character(s) other than | or ~."); return (-1); } } alt = strsep (&cp, sep); if (alt != NULL) { // Zero length after second / would be pointless. if (strlen(alt) == 0) { print_error (pf, "Nothing specified for alternate symbol table."); return (-1); } for (x = alt; *x != '\0'; x++) { if ( ! isprint(*x) || *x == '|' || *x == '~') { print_error (pf, "Symbol filter, alternate must be printable ASCII character(s) other than | or ~."); return (-1); } } over = strsep (&cp, sep); if (over != NULL) { // Zero length is acceptable and is not the same as missing. for (x = over; *x != '\0'; x++) { if ( (! isupper(*x)) && (! isdigit(*x)) && *x != '\\') { print_error (pf, "Symbol filter, overlay must be upper case letter, digit, or \\."); return (-1); } } extra = strsep (&cp, sep); if (extra != NULL) { print_error (pf, "More than 3 delimiter characters in Symbol filter."); return (-1); } } } else { // No alt part is OK if at least one primary symbol was specified. if (strlen(pri) == 0) { print_error (pf, "No symbols specified for Symbol filter."); return (-1); } } } else { print_error (pf, "Missing arguments for Symbol filter."); return (-1); } // This applies only for Position, Object, Item. // decode_aprs() should set symbol code to space to mean undefined. if (pf->decoded.g_symbol_code == ' ') { return (0); } // Look for Primary symbols. if (pf->decoded.g_symbol_table == '/') { if (pri != NULL && strlen(pri) > 0) { return (strchr(pri, pf->decoded.g_symbol_code) != NULL); } } if (alt == NULL) { return (0); } //printf ("alt=\"%s\" sym='%c'\n", alt, pf->decoded.g_symbol_code); // Look for Alternate symbols. if (strchr(alt, pf->decoded.g_symbol_code) != NULL) { // We have a match but that might not be enough. // We must see if there was an overlay part specified. if (over != NULL) { if (strlen(over) > 0) { // Non-zero length overlay part was specified. // Need to match one of them. return (strchr(over, pf->decoded.g_symbol_table) != NULL); } else { // Zero length overlay part was specified. // We must have no overlay, i.e. table is \. return (pf->decoded.g_symbol_table == '\\'); } } else { // No check of overlay part. Just make sure it is not primary table. return (pf->decoded.g_symbol_table != '/'); } } return (0); } /* end filt_s */ /*------------------------------------------------------------------------------ * * Name: filt_i * * Purpose: IGate messaging filter. * This would make sense only for IS>RF direction. * * Inputs: pf - Pointer to current state information. * token_str should contain something of format: * * i/time/hops/lat/lon/km * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: Selection is based on time since last heard on RF, and distance * in terms of digipeater hops and/or physical location. * * i/time * i/time/hops * i/time/hops/lat/lon/km * * * "time" is maximum number of minutes since message addressee was last heard. * This is required. APRS-IS uses 3 hours so that would be a good value here. * * "hops" is maximum number of digpeater hops. (i.e. 0 for heard directly). * If hops is not specified, the maximum transmit digipeater hop count, * from the IGTXVIA configuration will be used. * The rest is distanced, in kilometers, from given point. * * Examples: * i/180/0 Heard in past 3 hours directly. * i/45 Past 45 minutes, default max digi hops. * i/180/3 Default time (3 hours), max 3 digi hops. * i/180/8/42.6/-71.3/50. * * * It only makes sense to use this for the IS>RF direction. * The basic idea is that we want to transmit a "message" only if the * addressee has been heard recently and is not too far away. * * That is so we can distinguish messages addressed to a specific * station, and other sundry uses of the addressee field. * * After passing along a "message" we will also allow the next * position report from the sender of the "message." * That is done somewhere else. We are not concerned with it here. * * IMHO, the rules here are too restrictive. * * The APRS-IS would send a "message" to my IGate only if the addressee * has been heard nearby recently. 180 minutes, I believe. * Why would I not want to transmit it? * * Discussion: In retrospect, I think this is far too complicated. * In a future release, I think at options other than time should be removed. * Messages have more value than most packets. Why reduce the chance of successful delivery? * * Consider the following scenario: * * (1) We hear AA1PR-9 by a path of 4 digipeaters. * Looking closer, it's probably only two because there are left over WIDE1-0 and WIDE2-0. * * Digipeater WIDE2 (probably N3LLO-3) audio level = 72(19/15) [NONE] _|||||___ * [0.3] AA1PR-9>APY300,K1EQX-7,WIDE1,N3LLO-3,WIDE2*,ARISS::ANSRVR :cq hotg vt aprsthursday{01<0x0d> * * (2) APRS-IS sends a response to us. * * [ig>tx] ANSRVR>APWW11,KJ4ERJ-15*,TCPIP*,qAS,KJ4ERJ-15::AA1PR-9 :N:HOTG 161 Messages Sent{JL} * * (3) Here is our analysis of whether it should be sent to RF. * * Was message addressee AA1PR-9 heard in the past 180 minutes, with 2 or fewer digipeater hops? * No, AA1PR-9 was last heard over the radio with 4 digipeater hops 0 minutes ago. * * The wrong hop count caused us to drop a packet that should have been transmitted. * We could put in a hack to not count the "WIDE*-0" addresses. * That is not correct because other prefixes could be used and we don't know * what they are for other digipeaters. * I think the best solution is to simply ignore the hop count. * *------------------------------------------------------------------------------*/ static int filt_i (pfstate_t *pf) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; char *v; // http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2020-July/048656.html // Default of 3 hours should be good. // One might question why to have a time limit at all. Messages are very rare // the the APRS-IS wouldn't be sending it to me unless the addressee was in the // vicinity recently. // TODO: Should produce a warning if a user specified filter does not include "i". int heardtime = 180; // 3 hours * 60 min/hr = 180 minutes #if PFTEST int maxhops = 2; #else int maxhops = save_igate_config_p->max_digi_hops; // from IGTXVIA config. #endif double dlat = G_UNKNOWN; double dlon = G_UNKNOWN; double km = G_UNKNOWN; //char src[AX25_MAX_ADDR_LEN]; //char *infop = NULL; //int info_len; //char *f; //char addressee[AX25_MAX_ADDR_LEN]; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; // Get parameters or defaults. v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { heardtime = atoi(v); } else { print_error (pf, "Missing time limit for IGate message filter."); return (-1); } v = strsep (&cp, sep); if (v != NULL) { if (strlen(v) > 0) { maxhops = atoi(v); } else { print_error (pf, "Missing max digipeater hops for IGate message filter."); return (-1); } v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { dlat = atof(v); v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { dlon = atof(v); } else { print_error (pf, "Missing longitude for IGate message filter."); return (-1); } v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { km = atof(v); } else { print_error (pf, "Missing distance, in km, for IGate message filter."); return (-1); } } v = strsep (&cp, sep); if (v != NULL) { print_error (pf, "Something unexpected after distance for IGate message filter."); return (-1); } } #if PFTEST text_color_set(DW_COLOR_DEBUG); dw_printf ("debug: IGate message filter, %d minutes, %d hops, %.2f %.2f %.2f km\n", heardtime, maxhops, dlat, dlon, km); #endif /* * Get source address and info part. * Addressee has already been extracted into pf->decoded.g_addressee. */ if (pf->decoded.g_packet_type != packet_type_message) return(0); #if defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax. (void)dlat; // Suppress set and not used warning. (void)dlon; (void)km; (void)maxhops; (void)heardtime; return (1); #else /* * Condition 1: * "the receiving station has been heard within range within a predefined time * period (range defined as digi hops, distance, or both)." */ int was_heard = mheard_was_recently_nearby ("addressee", pf->decoded.g_addressee, heardtime, maxhops, dlat, dlon, km); if ( ! was_heard) return (0); /* * Condition 2: * "the sending station has not been heard via RF within a predefined time period * (packets gated from the Internet by other stations are excluded from this test)." * * This is the part I'm not so sure about. * I guess the intention is that if the sender can be heard over RF, then the addressee * might hear the sender without the help of Igate stations. * Suppose the sender was 1 digipeater hop to the west and the addressee was 1 digipeater hop to the east. * I can communicate with each of them with 1 digipeater hop but for them to reach each other, they * might need 3 hops and using that many is generally frowned upon and rare. * * Maybe we could compromise here and say the sender must have been heard directly. * It sent the message currently being processed so we must have heard it very recently, i.e. in * the past minute, rather than the usual 180 minutes for the addressee. */ was_heard = mheard_was_recently_nearby ("source", pf->decoded.g_src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN); if (was_heard) return (0); return (1); #endif } /* end filt_i */ /*------------------------------------------------------------------- * * Name: print_error * * Purpose: Print error message with context so someone can figure out what caused it. * * Inputs: pf - Pointer to current state information. * * str - Specific error message. * *--------------------------------------------------------------------*/ static void print_error (pfstate_t *pf, char *msg) { char intro[50]; if (pf->from_chan == MAX_CHANS) { if (pf->to_chan == MAX_CHANS) { snprintf (intro, sizeof(intro), "filter[IG,IG]: "); } else { snprintf (intro, sizeof(intro), "filter[IG,%d]: ", pf->to_chan); } } else { if (pf->to_chan == MAX_CHANS) { snprintf (intro, sizeof(intro), "filter[%d,IG]: ", pf->from_chan); } else { snprintf (intro, sizeof(intro), "filter[%d,%d]: ", pf->from_chan, pf->to_chan); } } text_color_set (DW_COLOR_ERROR); dw_printf ("%s%s\n", intro, pf->filter_str); dw_printf ("%*s\n", (int)(strlen(intro) + pf->tokeni + 1), "^"); dw_printf ("%s\n", msg); } #if PFTEST /*------------------------------------------------------------------- * * Name: main & pftest * * Purpose: Unit test for packet filtering. * * Usage: gcc -Wall -o pftest -DPFTEST pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o latlong.o symbols.o telemetry.o tt_text.c misc.a regex.a && ./pftest * * *--------------------------------------------------------------------*/ static int error_count = 0; static void pftest (int test_num, char *filter, char *packet, int expected); int main () { dw_printf ("Quick test for packet filtering.\n"); dw_printf ("Some error messages are normal. Look at the final success/fail message.\n"); pftest (1, "", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (2, "0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (3, "1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (10, "0 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (11, "0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (12, "1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (13, "1 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (14, "0 | 0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (20, "0 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (21, "0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (22, "1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (23, "1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (24, "1 & 1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (24, "1 & 0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (24, "1 & 1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (30, "0 | ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (31, "! 1 | ! 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (32, "! ! 1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (33, "1 | ! ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (40, "1 &(!0 |0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (41, "0 |(!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (42, "1 |(!!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (42, "(!(1 ) & (1 ))", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (50, "b/W2UB/WB2OSZ-5/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (51, "b/W2UB/WB2OSZ-14/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (52, "b#W2UB#WB2OSZ-5#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (53, "b#W2UB#WB2OSZ-14#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (60, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 0); pftest (61, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1); pftest (62, "o/HOME", "HOME>APDW12,WIDE1-1,WIDE2-1:;AWAY *111111z4237.14N/07120.83W-Chelmsford MA", 0); pftest (63, "o/WB2OSZ-5", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (64, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 0); pftest (65, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 1); pftest (70, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (71, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (72, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (73, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (74, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (75, "d/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (80, "g/W2UB", "WB2OSZ-5>APDW12::W2UB :text", 1); pftest (81, "g/W2UB/W2UB-*", "WB2OSZ-5>APDW12::W2UB-9 :text", 1); pftest (82, "g/W2UB/*", "WB2OSZ-5>APDW12::XXX :text", 1); pftest (83, "g/W2UB/W*UB", "WB2OSZ-5>APDW12::W2UB-9 :text", -1); pftest (84, "g/W2UB*", "WB2OSZ-5>APDW12::W2UB-9 :text", 1); pftest (85, "g/W2UB*", "WB2OSZ-5>APDW12::W2UBZZ :text", 1); pftest (86, "g/W2UB", "WB2OSZ-5>APDW12::W2UB-9 :text", 0); pftest (87, "g/*", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (88, "g/W*", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (90, "u/APWW10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 1); pftest (91, "u/TRSY3T", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 0); pftest (92, "u/APDW11/APDW12", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (93, "u/APDW", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); // rather sparse coverage of the cases pftest (100, "t/mqt", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (101, "t/mqtp", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (102, "t/mqtp", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 0); pftest (103, "t/mqop", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1); pftest (104, "t/p", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 1); pftest (104, "t/s", "KB1CHU-13>APWW10,W1CLA-1*,WIDE2-1:>FN42pb/_DX: W1MHL 36.0mi 306<0xb0> 13:24 4223.32N 07115.23W", 1); pftest (110, "t/p", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 0); pftest (111, "t/w", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 1); pftest (112, "t/t", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 0); pftest (113, "t/w", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 1); /* Telemetry metadata should not be classified as message. */ pftest (114, "t/t", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 1); pftest (115, "t/m", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 0); pftest (116, "t/t", "KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1); /* Bulletins should not be considered to be messages. Was bug in 1.6. */ pftest (117, "t/m", "A>B::W1AW :test", 1); pftest (118, "t/m", "A>B::BLN :test", 0); pftest (119, "t/m", "A>B::NWS :test", 0); // https://www.aprs-is.net/WX/ pftest (121, "t/p", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 0); pftest (122, "t/p", "CWAPID>APRS::SKYCWA :DDHHMMz,ADVISETYPE,zcs{seq#", 0); pftest (123, "t/p", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0); pftest (124, "t/n", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 1); pftest (125, "t/n", "CWAPID>APRS::SKYCWA :DDHHMMz,ADVISETYPE,zcs{seq#", 1); //pftest (126, "t/n", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 1); pftest (127, "t/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0); pftest (128, "t/c", "S0RCE>DEST:DEST:DEST:}WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (131, "t/c", "S0RCE>DEST:}WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (140, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (141, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0); pftest (145, "( t/t & b/WB2OSZ ) | ( t/o & ! r/42.6/-71.3/1 )", "WB2OSZ>APDW12:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1); pftest (150, "s/->", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (151, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W-PHG7140Chelmsford MA", 1); pftest (152, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W>PHG7140Chelmsford MA", 1); pftest (153, "s/->", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W>PHG7140Chelmsford MA", 0); pftest (154, "s//#", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (155, "s//#", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1); pftest (156, "s//#", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0); pftest (157, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (158, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1); pftest (159, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0); pftest (160, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (161, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 0); pftest (162, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0); pftest (163, "s//#/LS\\", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1); pftest (170, "s:/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 1); pftest (171, "s:/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 0); pftest (172, "s::/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 0); pftest (173, "s::/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 1); pftest (174, "s:/:/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 1); pftest (175, "s:/:/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 1); pftest (176, "s:/:/", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1); pftest (177, "s:/:/:X", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1); // FIXME: Different on Windows and 64 bit Linux. //pftest (178, "s:/:/:", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1); pftest (179, "s:/:/:\\", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 0); pftest (180, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (181, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (182, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (183, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (184, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (185, "v/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); /* Test error reporting. */ pftest (200, "x/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (201, "t/w & ( t/w | t/w ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (202, "t/w ) ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (203, "!", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1); pftest (210, "i/30/8/42.6/-71.3/50", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (212, "i/30/8/42.6/-71.3/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (213, "i/30/8/42.6/-71.3", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (214, "i/30/8/42.6/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (215, "i/30/8/42.6", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (216, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (217, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); // FIXME: behaves differently on Windows and Linux. Why? // On Windows we have our own version of strsep because it's not in the MS library. // It must behave differently than the Linux version when nothing follows the last separator. //pftest (228, "i/30/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (229, "i/30", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (230, "i/30", "X>X:}WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (231, "i/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); // Besure bulletins and telemetry metadata don't get included. pftest (234, "i/30", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 0); pftest (235, "i/30", "A>B::BLN :test", 0); pftest (240, "s/", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (241, "s/'/O/-/#/_", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (242, "s/O/O/c", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (243, "s/O/O/1/2", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (244, "s/O/|/1", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (245, "s//", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (246, "s///", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); // Third party header - done properly in 1.7. // Packet filter t/h is no longer a mutually exclusive packet type. // Now it is an independent attribute and the encapsulated part is evaluated. pftest (250, "o/home", "A>B:}WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1); pftest (251, "t/p", "A>B:}W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 1); pftest (252, "i/180", "A>B:}WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (253, "t/m", "A>B:}WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (254, "r/42.6/-71.3/10", "A>B:}WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (254, "r/42.6/-71.3/10", "A>B:}WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0); pftest (255, "t/h", "KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 0); pftest (256, "t/h", "A>B:}KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1); pftest (258, "t/t", "A>B:}KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1); pftest (259, "t/t", "A>B:}KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 1); pftest (270, "g/BLN*", "WB2OSZ>APDW17::BLN1xxxxx:bulletin text", 1); pftest (271, "g/BLN*", "A>B:}WB2OSZ>APDW17::BLN1xxxxx:bulletin text", 1); pftest (272, "g/BLN*", "A>B:}WB2OSZ>APDW17::W1AW :xxxx", 0); pftest (273, "g/NWS*", "WB2OSZ>APDW17::NWS-xxxxx:weather bulletin", 1); pftest (274, "g/NWS*", "A>B:}WB2OSZ>APDW17::NWS-xxxxx:weather bulletin", 1); pftest (275, "g/NWS*", "A>B:}WB2OSZ>APDW17::W1AW :xxxx", 0); // TODO: add b/ with 3rd party header. // TODO: to be continued... directed query ... if (error_count > 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("\nPacket Filtering Test - FAILED! %d errors\n", error_count); exit (EXIT_FAILURE); } text_color_set (DW_COLOR_REC); dw_printf ("\nPacket Filtering Test - SUCCESS!\n"); exit (EXIT_SUCCESS); } static void pftest (int test_num, char *filter, char *monitor, int expected) { int result; packet_t pp; text_color_set (DW_COLOR_DEBUG); dw_printf ("test number %d\n", test_num); pp = ax25_from_text (monitor, 1); assert (pp != NULL); result = pfilter (0, 0, filter, pp, 1); if (result != expected) { text_color_set (DW_COLOR_ERROR); dw_printf ("Unexpected result for test number %d\n", test_num); error_count++; } ax25_delete (pp); } #endif /* if TEST */ /* end pfilter.c */