diff --git a/CHANGES.md b/CHANGES.md index e860f99..d842b19 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,6 +30,14 @@ > Add: "FX25TX 1" (or 16 or 32 or 64) + +### Bugs Fixed: ### + +- The t/m packet filter incorrectly included bulletins. It now allows only "messages" to specific stations. Use of t/m is discouraged. i/180 is the preferred filter for messages to users recently heard locally. + +- Packet filtering now skips over any third party header before classifying packet types. + + ## Version 1.6 -- October 2020 ## ### New Build Procedure: ### diff --git a/src/decode_aprs.c b/src/decode_aprs.c index cf628bb..fc6443e 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -169,7 +169,9 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr //dw_printf ("DEBUG decode_aprs info=\"%s\"\n", pinfo); - memset (A, 0, sizeof (*A)); + if (third_party_src == NULL) { + memset (A, 0, sizeof (*A)); + } A->g_quiet = quiet; @@ -234,6 +236,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr memcpy(payload_src, (char*)pinfo+1, sizeof(payload_src)-1); char *q = strchr(payload_src, '>'); if (q != NULL) *q = '\0'; + A->g_has_thirdparty_header = 1; decode_aprs (A, pp_payload, quiet, payload_src); // 1 means used recursively ax25_delete (pp_payload); return; @@ -318,6 +321,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr { aprs_ll_pos (A, pinfo, info_len); } + A->g_packet_type = packet_type_position; break; @@ -330,10 +334,12 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr if (strncmp((char*)pinfo, "$ULTW", 5) == 0) { aprs_ultimeter (A, (char*)pinfo, info_len); // TODO: produce obsolete error. + A->g_packet_type = packet_type_weather; } else { aprs_raw_nmea (A, pinfo, info_len); + A->g_packet_type = packet_type_position; } break; @@ -341,17 +347,20 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr case '`': /* Current Mic-E Data (not used in TM-D700) */ aprs_mic_e (A, pp, pinfo, info_len); + A->g_packet_type = packet_type_position; break; case ')': /* Item. */ aprs_item (A, pinfo, info_len); + A->g_packet_type = packet_type_item; break; case '/': /* Position with timestamp (no APRS messaging) */ case '@': /* Position with timestamp (with APRS messaging) */ aprs_ll_pos_time (A, pinfo, info_len); + A->g_packet_type = packet_type_position; break; @@ -360,42 +369,76 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr /* Telemetry metadata. */ aprs_message (A, pinfo, info_len, quiet); + + switch (A->g_message_subtype) { + case message_subtype_message: + case message_subtype_ack: + case message_subtype_rej: + A->g_packet_type = packet_type_message; + break; + + case message_subtype_nws: + A->g_packet_type = packet_type_nws; + break; + + case message_subtype_bulletin: + default: + break; + + case message_subtype_telem_parm: + case message_subtype_telem_unit: + case message_subtype_telem_eqns: + case message_subtype_telem_bits: + A->g_packet_type = packet_type_telemetry; + break; + + case message_subtype_directed_query: + A->g_packet_type = packet_type_query; + break; + } break; case ';': /* Object */ aprs_object (A, pinfo, info_len); + A->g_packet_type = packet_type_object; break; case '<': /* Station Capabilities */ aprs_station_capabilities (A, (char*)pinfo, info_len); + A->g_packet_type = packet_type_capabilities; break; case '>': /* Status Report */ aprs_status_report (A, (char*)pinfo, info_len); + A->g_packet_type = packet_type_status; break; case '?': /* General Query */ aprs_general_query (A, (char*)pinfo, info_len, quiet); + A->g_packet_type = packet_type_query; break; case 'T': /* Telemetry */ aprs_telemetry (A, (char*)pinfo, info_len, quiet); + A->g_packet_type = packet_type_telemetry; break; case '_': /* Positionless Weather Report */ aprs_positionless_weather_report (A, pinfo, info_len); + A->g_packet_type = packet_type_weather; break; case '{': /* user defined data */ aprs_user_defined (A, (char*)pinfo, info_len); + A->g_packet_type = packet_type_userdefined; break; case 't': /* Raw touch tone data - NOT PART OF STANDARD */ @@ -404,6 +447,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr /* Might move into user defined data, above. */ aprs_raw_touch_tone (A, (char*)pinfo, info_len); + // no packet type for t/ filter break; case 'm': /* Morse Code data - NOT PART OF STANDARD */ @@ -413,6 +457,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr /* Might move into user defined data, above. */ aprs_morse_code (A, (char*)pinfo, info_len); + // no packet type for t/ filter break; //case '}': /* third party header */ @@ -1091,7 +1136,9 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) * * Function: aprs_mic_e * - * Purpose: Decode MIC-E (also Kenwood D7 & D700) packet. + * Purpose: Decode MIC-E (e.g. Kenwood D7 & D700) packet. + * This format is an overzelous quest to make the packet as short as possible. + * It uses non-printable characters and hacks wrapped in kludges. * * Inputs: info - Pointer to Information field. * ilen - Information field length. @@ -1100,31 +1147,120 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) * * Description: * - * Destination Address Field - + * AX.25 Destination Address Field - * - * The 7-byte Destination Address field contains + * The 6-byte Destination Address field contains * the following encoded information: * - * * The 6 latitude digits. - * * A 3-bit Mic-E message identifier, specifying one of 7 Standard Mic-E - * Message Codes or one of 7 Custom Message Codes or an Emergency - * Message Code. - * * The North/South and West/East Indicators. - * * The Longitude Offset Indicator. - * * The generic APRS digipeater path code. - * + * Byte 1: Lat digit 1, message bit A + * Byte 2: Lat digit 2, message bit B + * Byte 3: Lat digit 3, message bit C + * Byte 4: Lat digit 4, N/S lat indicator + * Byte 5: Lat digit 5, Longitude offset + * Byte 6: Lat digit 6, W/E Long indicator + * * * "Although the destination address appears to be quite unconventional, it is * still a valid AX.25 address, consisting only of printable 7-bit ASCII values." * - * References: Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt + * AX.25 Information Field - Starts with ' or ` * - * This is up to date with the 24 Aug 16 version mentioning the TH-D74. + * Bytes 1,2,3: Longitude + * Bytes 4,5,6: Speed and Course + * Byte 6: Symbol code + * Byte 7: Symbol Table ID + * + * The rest of it is a complicated comment field which can hold various information + * and must be intrepreted in a particular order. At this point we look for any + * prefix and/or suffix to identify the equipment type. + * + * References: Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt + * Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt + * + * Next, we have what Addedum 1.2 calls the "type byte." This prefix can be + * space Original MIC-E. + * > Kenwood HT. + * ] Kenwood Mobile. + * none. + * + * We also need to look at the last byte or two + * for a possible suffix to distinguish equipment types. Examples: + * >...... is D7 + * >......= is D72 + * >......^ is D74 + * + * For other brands, it gets worse. There might a 2 character suffix. + * The prefix indicates whether messaging-capable. Examples: + * `....._.% Yaesu FTM-400DR + * `......_) Yaesu FTM-100D + * `......_3 Yaesu FT5D + * + * '......|3 Byonics TinyTrack3 + * '......|4 Byonics TinyTrack4 + * + * Any prefix and suffix must be removed before futher processsing. + * + * Pick one: MIC-E Telemetry Data or "Status Text" (called a comment everywhere else). + * + * If the character after the symbol table id is "," (comma) or 0x1d, we have telemetry. + * (Is this obsoleted by the base-91 telemetry?) + * + * ` Two 2-character hexadecimal numbers. (Channels 1 & 3) + * ' Five 2-character hexadecimal numbers. + * + * Anything left over is a comment which can contain various types of information. + * + * If present, the MIC-E compressed altitude must be first. + * It is three base-91 characters followed by "}". + * Examples: "4T} "4T} ]"4T} + * + * We can also have frequency specification -- http://www.aprs.org/info/freqspec.tx + * + * Warning: Some Kenwood radios add CR at the end, in apparent violation of the spec. + * Watch out so it doesn't get included when looking for equipment type suffix. * * Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt * - * Examples: `b9Z!4y>/>"4N}Paul's_TH-D7 + * Examples: Observed on the air. * - * TODO: Destination SSID can contain generic digipeater path. + * KB1HNZ-9>TSSP5P,W1IMD,WIDE1,KQ1L-8,N3LLO-3,WIDE2*:`b6,l}#>/]"48}449.225MHz<0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff>=<0x0d> + * + * ` b6, l}# >/ ] "48} 449.225MHz ...... = <0x0d> + * mic-e long. cs sym prefix alt. freq comment suffix must-ignore + * Kenwood D710 + *--------------- + * + * N1JDU-9>ECCU8Y,W1MHL*,WIDE2-1:'cZ<0x7f>l#H>/]Go fly a kite!<0x0d> + * + * ' cZ<0x7f> l#H >/ ] ..... <0x0d> + * mic-e long. cs sym prefix comment no-suffix must-ignore + * Kenwood D700 + *--------------- + * + * KC1HHO-7>T2PX5R,WA1PLE-4,WIDE1*,WIDE2-1:`c_snp(k/`"4B}official relay station NTS_(<0x0d> + * + * ` c_s np( k/ ` "4B} ....... _( <0x0d> + * mic-e long. cs sym prefix alt comment suffix must-ignore + * FT2D + *--------------- + * + * N1CMD-12>T3PQ1Y,KA1GJU-3,WIDE1,WA1PLE-4*:`cP#l!Fk/'"7H}|!%&-']|!w`&!|3 + * + * ` cP# l!F k/ ' "7H} |!%&-']| !w`&! |3 + * mic-e long. cs sym prefix alt base91telemetry DAO suffix + * TinyTrack3 + *--------------- + * + * W1STJ-3>T2UR4X,WA1PLE-4,WIDE1*,WIDE2-1:`c@&l#.-/`"5,}146.685MHz T100 -060 146.520 Simplex or Voice Alert_%<0x0d> + * + * ` c@& l#. -/ ` "5,} 146.685MHz T100 -060 .............. _% <0x0d> + * mic-e long. cs sym prefix alt frequency-specification comment suffix must-ignore + * FTM-400DR + *--------------- + * + * + * + * + * TODO: Destination SSID can contain generic digipeater path. (?) * * Bugs: Doesn't handle ambiguous position. "space" treated as zero. * Invalid data results in a message but latitude is not set to unknown. @@ -1507,14 +1643,17 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int // This does not change very often but I'm wondering if we could parse // http://www.aprs.org/aprs12/mic-e-types.txt similar to how we use tocalls.txt. +// TODO: Use https://github.com/aprsorg/aprs-deviceid rather than hardcoding. + if (isT(*pfirst)) { // "legacy" formats. - + if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; } + else if (*pfirst == '>' && *plast == '&') { strlcpy (A->g_mfr, "Kenwood TH-D75", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } @@ -1540,6 +1679,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int // ' should be used for trackers (not message capable). + else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '8') { strlcpy (A->g_mfr, "Anytone D878UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; } @@ -1635,6 +1775,10 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * * Cases: :BLNxxxxxx: ... Bulletin. * :NWSxxxxxx: ... National Weather Service Bulletin. + * http://www.aprs.org/APRS-docs/WX.TXT + * :SKYxxxxxx: ... Need reference. + * :CWAxxxxxx: ... Need reference. + * :BOMxxxxxx: ... Australian version. * * :xxxxxxxxx:PARM. Telemetry metadata, parameter name * :xxxxxxxxx:UNIT. Telemetry metadata, unit/label @@ -1768,19 +1912,36 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q strlcpy (A->g_comment, p->message, sizeof(A->g_comment)); } +#warning = double check. + + // Weather bulletins have addressee starting with NWS, SKY, CWA, or BOM. + // The protocol spec and http://www.aprs.org/APRS-docs/WX.TXT state that + // the 3 letter prefix must be followed by a dash. + // However, https://www.aprs-is.net/WX/ also lists the underscore + // alternative for the compressed format. Xastir implements this. + else if (strlen(addressee) >= 3 && strncmp(addressee,"NWS",3) == 0) { - // NWS-xxxxx if (strlen(addressee) >=4 && addressee[3] == '-') { - snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "NWS bulletin with identifier \"%s\"", addressee+4); + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Weather bulletin with identifier \"%s\"", addressee+4); + } + else if (strlen(addressee) >=4 && addressee[3] == '_') { + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Compressed Weather bulletin with identifier \"%s\"", addressee+4); } else { - snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "NWS bulletin with identifier \"%s\", missing - after NWS", addressee+3); + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Weather bulletin is missing - or _ after %.3s", addressee); } A->g_message_subtype = message_subtype_nws; strlcpy (A->g_comment, p->message, sizeof(A->g_comment)); } + else if (strlen(addressee) >= 3 && (strncmp(addressee,"SKY",3) == 0 || strncmp(addressee,"CWA",3) == 0 || strncmp(addressee,"BOM",3) == 0)) { + // SKY... or CWA... https://www.aprs-is.net/WX/ + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Weather bulletin with identifier \"%s\"", addressee+4); + A->g_message_subtype = message_subtype_nws; + strlcpy (A->g_comment, p->message, sizeof(A->g_comment)); + } + /* * Special message formats contain telemetry metadata. @@ -1794,22 +1955,22 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q */ else if (strncmp(p->message,"PARM.",5) == 0) { - snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Parameter Name Message for \"%s\"", addressee); + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Parameter Name for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_parm; telemetry_name_message (addressee, p->message+5); } else if (strncmp(p->message,"UNIT.",5) == 0) { - snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Unit/Label Message for \"%s\"", addressee); + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Unit/Label for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_unit; telemetry_unit_label_message (addressee, p->message+5); } else if (strncmp(p->message,"EQNS.",5) == 0) { - snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Equation Coefficients Message for \"%s\"", addressee); + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Equation Coefficients for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_eqns; telemetry_coefficents_message (addressee, p->message+5, quiet); } else if (strncmp(p->message,"BITS.",5) == 0) { - snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee); + snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Bit Sense/Project Name for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_bits; telemetry_bit_sense_message (addressee, p->message+5, quiet); } diff --git a/src/decode_aprs.h b/src/decode_aprs.h index e9d5b44..acaae2d 100644 --- a/src/decode_aprs.h +++ b/src/decode_aprs.h @@ -24,7 +24,8 @@ typedef struct decode_aprs_s { int g_quiet; /* Suppress error messages when decoding. */ - char g_src[AX25_MAX_ADDR_LEN]; + char g_src[AX25_MAX_ADDR_LEN]; // In the case of a packet encapsulated by a 3rd party + // header, this is the encapsulated source. char g_dest[AX25_MAX_ADDR_LEN]; @@ -66,6 +67,24 @@ typedef struct decode_aprs_s { /* Also for Directed Station Query which is a */ /* special case of message. */ + // This is so pfilter.c:filt_t does not need to duplicate the same work. + + int g_has_thirdparty_header; + enum packet_type_e { + packet_type_none=0, + packet_type_position, + packet_type_weather, + packet_type_object, + packet_type_item, + packet_type_message, + packet_type_query, + packet_type_capabilities, + packet_type_status, + packet_type_telemetry, + packet_type_userdefined, + packet_type_nws + } g_packet_type; + enum message_subtype_e { message_subtype_invalid = 0, message_subtype_message, message_subtype_ack, diff --git a/src/igate.c b/src/igate.c index 8bdf109..11f2896 100644 --- a/src/igate.c +++ b/src/igate.c @@ -1749,9 +1749,34 @@ static void * satgate_delay_thread (void *arg) * *--------------------------------------------------------------------*/ -// TODO: Use of "message" here is confusing because that term already -// has a special meaning for APRS. This could be an APRS message or -// some other APRS data type. Payload is already used. +#warning - clean up + +// It is unforunate that the : data type indicator (DTI) was overloaded with +// so many different meanings. Simply looking at the DTI is not adequate for +// determining whether a packet is a message. +// We need to exclude the other special cases of telemetry metadata, +// bulletins, and weather bulletins. + +static int is_message_message (char *infop) +{ + if (*infop != ':') return (0); + if (strlen(infop) < 11) return (0); // too short for : addressee : + if (strlen(infop) >= 16) { + if (strncmp(infop+10, ":PARM.", 6) == 0) return (0); + if (strncmp(infop+10, ":UNIT.", 6) == 0) return (0); + if (strncmp(infop+10, ":EQNS.", 6) == 0) return (0); + if (strncmp(infop+10, ":BITS.", 6) == 0) return (0); + } + if (strlen(infop) >= 4) { + if (strncmp(infop+1, "BLN", 3) == 0) return (0); + if (strncmp(infop+1, "NWS", 3) == 0) return (0); + if (strncmp(infop+1, "SKY", 3) == 0) return (0); + if (strncmp(infop+1, "CWA", 3) == 0) return (0); + if (strncmp(infop+1, "BOM", 3) == 0) return (0); + } + return (1); // message, including ack, rej +} + static void maybe_xmit_packet_from_igate (char *message, int to_chan) { @@ -1843,7 +1868,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) * If we recently transmitted a 'message' from some station, * send the position of the message sender when it comes along later. * - * Some refer to this as a courtesy posit report but I don't + * Some refer to this as a "courtesy posit report" but I don't * think that is an official term. * * If we have a position report, look up the sender and see if we should @@ -1988,10 +2013,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) #endif stats_rf_xmit_packets++; // Any type of packet. - // TEMP TEST: metadata temporarily allowed during testing. - - if (*pinfo == ':' && ! is_telem_metadata(pinfo)) { - // temp test // if (*pinfo == ':') { + if (is_message_message(pinfo)) { // We transmitted a "message." Telemetry metadata is excluded. // Remember to pass along address of the sender later. @@ -2449,6 +2471,26 @@ void ig_to_tx_remember (packet_t pp, int chan, int bydigi) } } + +#warning remove + +static int is_message_overload (char *infop) +{ + if (*infop != ':') return (0); + if (strlen(infop) < 16) return (0); + if (strncmp(infop+10, ":PARM.", 6) == 0) return (1); + if (strncmp(infop+10, ":UNIT.", 6) == 0) return (1); + if (strncmp(infop+10, ":EQNS.", 6) == 0) return (1); + if (strncmp(infop+10, ":BITS.", 6) == 0) return (1); + if (strncmp(infop+1, "BLN", 3) == 0) return (1); + if (strncmp(infop+1, "NWS", 3) == 0) return (1); + if (strncmp(infop+1, "SKY", 3) == 0) return (1); + if (strncmp(infop+1, "CWA", 3) == 0) return (1); + if (strncmp(infop+1, "BOM", 3) == 0) return (1); + return (0); +} + + static int ig_to_tx_allow (packet_t pp, int chan) { unsigned short crc = ax25_dedupe_crc(pp); @@ -2481,7 +2523,7 @@ static int ig_to_tx_allow (packet_t pp, int chan) /* We have a duplicate within some time period. */ - if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) { + if (is_message_message((char*)pinfo)) { /* I think I want to avoid the duplicate suppression for "messages." */ /* Suppose we transmit a message from station X and it doesn't get an ack back. */ @@ -2530,7 +2572,7 @@ static int ig_to_tx_allow (packet_t pp, int chan) /* the normal limit for them. */ increase_limit = 1; - if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) { + if (is_message_message((char*)pinfo)) { increase_limit = 3; } diff --git a/src/mheard.c b/src/mheard.c index 5c20f30..f11c68f 100644 --- a/src/mheard.c +++ b/src/mheard.c @@ -336,6 +336,52 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r */ hops = ax25_get_heard(pp) - AX25_SOURCE; +/* + * 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 "WIDEn-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. + * Maybe next release will have a major cleanup. + */ + + // HACK - Reduce hop count by number of used WIDEn-0 addresses. + + if (hops > 1) { + for (int k = 0; k < ax25_get_num_repeaters(pp); k++) { + char digi[AX25_MAX_ADDR_LEN]; + ax25_get_addr_no_ssid (pp, AX25_REPEATER_1 + k, digi); + int ssid = ax25_get_ssid (pp, AX25_REPEATER_1 + k); + int used = ax25_get_h (pp, AX25_REPEATER_1 + k); + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Examining %s-%d used=%d.\n", digi, ssid, used); + + if (used && strlen(digi) == 5 && strncmp(digi, "WIDE", 4) == 0 && isdigit(digi[4]) && ssid == 0) { + hops--; + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Decrease hop count to %d for problematic %s.\n", hops, digi); + } + } + } mptr = mheard_ptr(source); if (mptr == NULL) { @@ -571,7 +617,7 @@ void mheard_save_is (char *ptext) * 8 for RF_CNT. * * time_limit - Include only stations heard within this many minutes. - * Typically 30 or 60. + * Typically 180. * * Returns: Number to be used in the statistics report. * @@ -649,7 +695,7 @@ int mheard_count (int max_hops, int time_limit) * callsign - Callsign for station. * * time_limit - Include only stations heard within this many minutes. - * Typically 30 or 60. + * Typically 180. * * max_hops - Include only stations heard with this number of * digipeater hops or less. For reporting, we might use: diff --git a/src/pfilter.c b/src/pfilter.c index 08922f5..b7ad63e 100644 --- a/src/pfilter.c +++ b/src/pfilter.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2015, 2016 John Langner, WB2OSZ +// 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 @@ -546,7 +546,8 @@ static int parse_filter_spec (pfstate_t *pf) /* b - budlist */ else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) { - /* Budlist - source address */ + /* 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); @@ -572,7 +573,7 @@ static int parse_filter_spec (pfstate_t *pf) else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) { int n; - // loop on all digipeaters + // 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. @@ -599,7 +600,7 @@ static int parse_filter_spec (pfstate_t *pf) else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) { int n; - // loop on all digipeaters (mnemonic Via) + // 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. @@ -623,10 +624,15 @@ static int parse_filter_spec (pfstate_t *pf) } } -/* g - Addressee of message. */ +/* g - Addressee of message. e.g. "BLN*" for bulletins. */ else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) { - if (ax25_get_dti(pf->pp) == ':') { + 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) { @@ -643,7 +649,7 @@ static int parse_filter_spec (pfstate_t *pf) } } -/* u - unproto (destination) */ +/* u - unproto (AX.25 destination) */ else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) { /* Probably want to exclude mic-e types */ @@ -668,7 +674,7 @@ static int parse_filter_spec (pfstate_t *pf) } } -/* t - type: position, weather, etc. */ +/* t - packet type: position, weather, telemetry, etc. */ else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) { @@ -728,7 +734,7 @@ static int parse_filter_spec (pfstate_t *pf) (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); text_color_set(DW_COLOR_DEBUG); - if (*infop == ':' && ! is_telem_metadata(infop)) { + 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 { @@ -832,31 +838,17 @@ static int filt_bodgu (pfstate_t *pf, char *arg) * 0 = no * -1 = error detected * - * Description: The filter is based the type filtering described here: + * Description: The filter is loosely based the type filtering described here: * http://www.aprs-is.net/javAPRSFilter.aspx * - * Most of these simply check the first byte of the information part. - * Trying to detect NWS information is a little trickier. + * 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 + * http://wxsvr.aprs.net.au/protocol-new.html (has disappeared) * *------------------------------------------------------------------------------*/ -/* Telemetry metadata is a special case of message. */ -/* We want to categorize it as telemetry rather than message. */ - -int is_telem_metadata (char *infop) -{ - if (*infop != ':') return (0); - if (strlen(infop) < 16) return (0); - if (strncmp(infop+10, ":PARM.", 6) == 0) return (1); - if (strncmp(infop+10, ":UNIT.", 6) == 0) return (1); - if (strncmp(infop+10, ":EQNS.", 6) == 0) return (1); - if (strncmp(infop+10, ":BITS.", 6) == 0) return (1); - return (0); -} - - static int filt_t (pfstate_t *pf) { char src[AX25_MAX_ADDR_LEN]; @@ -873,108 +865,60 @@ static int filt_t (pfstate_t *pf) switch (*f) { case 'p': /* Position */ - if (*infop == '!') return (1); - if (*infop == '/') return (1); - if (*infop == '=') return (1); - if (*infop == '@') return (1); - if (*infop == '\'') return (1); // MIC-E - if (*infop == '`') return (1); // MIC-E - - // What if we have "_" symbol code for weather? - // Still consider as position. - // The same packet can match more than one type here. + if (pf->decoded.g_packet_type == packet_type_position) return(1); break; case 'o': /* Object */ - if (*infop == ';') return (1); + if (pf->decoded.g_packet_type == packet_type_object) return(1); break; case 'i': /* Item */ - if (*infop == ')') return (1); + if (pf->decoded.g_packet_type == packet_type_item) return(1); break; - case 'm': /* Message */ - if (*infop == ':' && ! is_telem_metadata(infop)) return (1); + case 'm': // Any "message." + if (pf->decoded.g_packet_type == packet_type_message) return(1); break; case 'q': /* Query */ - if (*infop == '?') return (1); + 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 (*infop == '<') return (1); + if (pf->decoded.g_packet_type == packet_type_capabilities) return(1); break; case 's': /* Status */ - if (*infop == '>') return (1); + if (pf->decoded.g_packet_type == packet_type_status) return(1); break; - case 't': /* Telemetry */ - if (*infop == 'T') return (1); - if (is_telem_metadata(infop)) return (1); + case 't': /* Telemetry data or metadata */ + if (pf->decoded.g_packet_type == packet_type_telemetry) return(1); break; case 'u': /* User-defined */ - if (*infop == '{') return (1); + if (pf->decoded.g_packet_type == packet_type_userdefined) return(1); break; - case 'h': /* third party Header - my extension */ - if (*infop == '}') return (1); + case 'h': /* has third party Header - my extension */ + if (pf->decoded.g_has_thirdparty_header) return (1); break; case 'w': /* Weather */ - if (*infop == '*') return (1); // Peet Bros - if (*infop == '_') return (1); // Weather report, no position. - if (strncmp(infop, "!!", 2) == 0) return(1); // Ultimeter 2000. - - /* '$' is normally raw GPS. Check for special case. */ - - if (strncmp(infop, "$ULTW", 5) == 0) return (1); - - /* Positions !=/@ with symbol code _ are weather. */ - - if (strchr("!=/@", *infop) != NULL && - pf->decoded.g_symbol_code == '_') return (1); + 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 (*infop == ';' && - pf->decoded.g_symbol_code == '_') return (1); - -// TODO: need more test cases at end for new weather cases. - + 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 */ -/* - * This is the interesting case. - * The source must be exactly 6 upper case letters, no SSID. - */ - if (strlen(src) != 6) break; - if (! isupper(src[0])) break; - if (! isupper(src[1])) break; - if (! isupper(src[2])) break; - if (! isupper(src[3])) break; - if (! isupper(src[4])) break; - if (! isupper(src[5])) break; -/* - * We can have a "message" with addressee starting with NWS, SKY, or BOM (Australian version.) - */ - if (strncmp(infop, ":NWS", 4) == 0) return (1); - if (strncmp(infop, ":SKY", 4) == 0) return (1); - if (strncmp(infop, ":BOM", 4) == 0) return (1); -/* - * Or we can have an object. - * It's not exactly clear how to distinguish this from other objects. - * It looks like the first 3 characters of the source should be the same - * as the first 3 characters of the addressee. - */ - if (infop[0] == ';' && - infop[1] == src[0] && - infop[2] == src[1] && - infop[3] == src[2]) return (1); + if (pf->decoded.g_packet_type == packet_type_nws) return(1); break; default: @@ -990,6 +934,7 @@ static int filt_t (pfstate_t *pf) + /*------------------------------------------------------------------------------ * * Name: filt_r @@ -1327,8 +1272,36 @@ static int filt_s (pfstate_t *pf) * * IMHO, the rules here are too restrictive. * - * (1) The APRS-IS would send a "message" to my IGate only if the addressee + * 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. * *------------------------------------------------------------------------------*/ @@ -1357,9 +1330,9 @@ static int filt_i (pfstate_t *pf) double km = G_UNKNOWN; - char src[AX25_MAX_ADDR_LEN]; - char *infop = NULL; - int info_len; + //char src[AX25_MAX_ADDR_LEN]; + //char *infop = NULL; + //int info_len; //char *f; //char addressee[AX25_MAX_ADDR_LEN]; @@ -1433,20 +1406,7 @@ static int filt_i (pfstate_t *pf) * Get source address and info part. * Addressee has already been extracted into pf->decoded.g_addressee. */ - - memset (src, 0, sizeof(src)); - ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src); - info_len = ax25_get_info (pf->pp, (unsigned char **)(&infop)); - - if (infop == NULL) return (0); - if (info_len < 1) return (0); - -// Determine packet type. We are interested only in "message." -// Telemetry metadata is not considered a message. - - if (*infop != ':') return (0); - if (is_telem_metadata(infop)) return (0); - + if (pf->decoded.g_packet_type != packet_type_message) return(0); #if defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax. @@ -1486,7 +1446,7 @@ static int filt_i (pfstate_t *pf) * the past minute, rather than the usual 180 minutes for the addressee. */ - was_heard = mheard_was_recently_nearby ("source", src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN); + was_heard = mheard_was_recently_nearby ("source", pf->decoded.g_src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN); if (was_heard) return (0); @@ -1640,24 +1600,29 @@ int main () 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 is a special case of message. */ + /* 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); - pftest (120, "t/p", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 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 (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:}thirdpartyheaderwhatever", 1); - pftest (131, "t/c", "S0RCE>DEST:}thirdpartyheaderwhatever", 0); + pftest (130, "t/h", "S0RCE>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); @@ -1712,13 +1677,13 @@ int main () 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 (220, "i/30/8/42.6/-71.3/50", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); - pftest (222, "i/30/8/42.6/-71.3/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); - pftest (223, "i/30/8/42.6/-71.3", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); - pftest (224, "i/30/8/42.6/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); - pftest (225, "i/30/8/42.6", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); - pftest (226, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); - pftest (227, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 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. @@ -1726,7 +1691,12 @@ int main () //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/", "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); @@ -1736,12 +1706,36 @@ int main () 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. -// TODO: to be continued... + 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!\n"); + dw_printf ("\nPacket Filtering Test - FAILED! %d errors\n", error_count); exit (EXIT_FAILURE); } text_color_set (DW_COLOR_REC);