1 /*
2  *
3  * XASTIR, Amateur Station Tracking and Information Reporting
4  * Copyright (C) 1999,2000  Frank Giannandrea
5  * Copyright (C) 2000-2019 The Xastir Group
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  *
21  * Look at the README for more information on the program.
22  */
23 
24 
25 
26 // alert_redraw_on_update will cause refresh_image to get called.
27 // alert_add_entry sets it.
28 
29 
30 // In the alert structure, flags[] is size 16.  Only the first two
31 // positions in the array are currently used.
32 //
33 // alert_entry.flags[0] // on_screen
34 //          ?  Initial state or ready-to-recompute state
35 //          -   Expired between 1 sec and 1 hour
36 //          Y   Active alert within viewport
37 //          N   Active alert outside viewport
38 //
39 // alert_entry.flags[1] // source
40 //          DATA_VIA_TNC
41 //          DATA_VIA_LOCAL
42 //
43 // alert_entry.to   alert_entry.alert_level
44 //     CANCL            C   // Colors of alerts?????
45 //     TEST             T   // Colors of alerts?????
46 //     WARN             R   // Colors of alerts?????
47 //     CIVIL            R   // Colors of alerts?????
48 //     WATCH            Y   // Colors of alerts?????
49 //     ADVIS            B   // Colors of alerts?????
50 //     Other            G   // Colors of alerts?????
51 //     Unset            ?
52 //
53 //
54 // Here's how Xastir breaks down an alert into an alert struct:
55 //
56 // SFONPW>APRS::NWS-ADVIS:191700z,WIND,CA_Z007,CA_Z065, ALAMEDA AND CON & NAPA COUNTY {JDIAA
57 // |----|       |-------| |-----| |--| |-----| |-----|                                 |-|
58 //   |              |        |     |      |       |                                     |
59 //  from            to       |     |    title   title                               issue_date
60 //                           |  alert_tag
61 //                        activity (expiration)
62 //
63 //
64 // The code should also handle the case where the packet looks like
65 // this (it's the same as the above packet except for no expiration
66 // date):
67 //
68 // SFONPW>APRS::NWS-ADVIS:WIND,CA_Z007,CA_Z065, ALAMEDA AND CON & NAPA COUNTY {JDIAA
69 //
70 //
71 // Expiration is then computed from the activity field.  Alert_level
72 // is computed from "to" and/or "alert_tag".
73 //
74 //
75 // Stuff from Dale, paraphrased by Curt:
76 //
77 // WATCH - weather of some type is possible or probable for a geographic
78 // area- at present I cannot do watches because they can cover huge areas
79 // with hundreds of counties across many states.  I have a prototye of a
80 // polygon generator - but that is a whole other can of worms
81 //
82 // WARN - warning - Severe or dangerous weather is occuring or is about to
83 // occur in a geographical area.  This we do a pretty good job on output.
84 //
85 // ADVIS - advisory - this can be trivial all the way to a tornado report.
86 // If a tornado warning is issued and another tornado sighting happens in
87 // the same county/zone during the valid time of the first- the info is
88 // transmitted as an advisory.  Most of the time is is updates for other
89 // messages.
90 //
91 // CANCL - cancelation- discussed in earlier e-mail
92 //
93 // I would add CIVIL for terrorist  earthquake  catostrophic type stuff -
94 // the D7 and D&)) have special alarms built in so that a message to
95 // NWS-CIVIL would alert folks no matter what there filters are set for.
96 //
97 //
98 // The clue to which shapefile to use is in the 4th char in the
99 // title (which is the first following an '_')
100 //
101 // ICTSVS>APRS::NWS-ADVIS:120145z,SEVERE_WEATHER,KS_Z091, {C14AA
102 // TSASVR>APRS::NWS-WARN :120230z,SVRTSM,OK_C113,  OSAGE COUNTY {C16AA
103 //
104 // C = county file         (c_mmddyy.dbf)
105 // A = County Warning File (w_mmddyy.dbf)
106 // Z = Zone File           (z_mmddyy.dbf or mz_mmddyy.dbf)
107 // F = Fire zone file      (fz_mmddyy.dbf)
108 // A = Canadian Area       (a_mmddyy.dbf)
109 // R = Canadian Region     (r_mmddyy.dbf)
110 //
111 // Alerts are comma-delimited on the air s.t. after the
112 // :NWS-?????: the first field is the time in zulu (or local
113 // with no 'z'), the 2nd is the warning type
114 // (severe_thunderstorm etc.), the 3rd and up to 7th are s.t.
115 // the first 2 letters are the state or marine zone (1st field
116 // in both the county and zone .dbf files) followed by an
117 // underline char '_', the area type as above (C, Z, or A),
118 // then a 3 digit numerical code derived from:
119 //
120 // Counties:  the fips code (4th field in the .dbf)
121 //
122 // Zones: the zone number (2nd field in the .dbf)
123 //
124 // Marine Zones: have proper code in 1st field with addition of '_' in correct place.
125 //
126 // CWA: 2nd field has cwa-, these are always "CW_A" plus the cwa
127 //
128 // You must ignore anything after a space in the alert.
129 //
130 // We will probably want to add the "issue time" to the alert record
131 // and parse that out if it's present.  Change the view dialog to
132 // show expiration time, issue time, who the alert is apparently
133 // from, and the stuff after the curly brace.  Some of that info
134 // will be useful soon in a finger command to the weather server.
135 //
136 // New compressed-mode weather alert packets:
137 //
138 // LZKNPW>APRS::NWS-ADVIS:221000z,WINTER_STORM,ARZ003>007-012>016-021>025-030>034-037>047-052>057-062>069{LLSAA
139 //
140 // The trick is to step thru the data base contained in the
141 // shapefiles to determine what areas to light up.  In the above
142 // example read ">" as "thru" and "-" as "and".
143 //
144 // More from Dale:
145 // It occurs to me you might need some insight into what shapefile to look
146 // through for a zone/county. The current shape files are c_22mr02,
147 // z_16mr02, and mz21fe02.
148 //
149 // ICTSVS>APRS::NWS-ADVIS:120145z,SEVERE_WEATHER,KS_Z091, {C14AA
150 // would be in z_ file
151 //
152 // TSASVR>APRS::NWS-WARN :120230z,SVRTSM,OK_C113,  OSAGE COUNTY {C16AA
153 // would be in c_ file
154 //
155 // problem comes with marine warnings-
156 //
157 // AM,AN,GM,PZ,PK,PM,LS,LM,LH,LO,LE,SL will look like states, but will all
158 // come from the mz file.
159 //
160 // so AM_Z686 looks like a state zone, but is a marine zone.
161 // Aprs Plus requires the exact file name to be specified - winaprs just
162 // looks for a file in the nwsshape folder starting c_ z_ and mz.  Someone
163 // in the middle of Kansas might not need the marine at all- but here I am
164 // closer to marine zones than land. The fact there is an index file for
165 // the shapes should help the speed in a lookup.
166 //
167 // More from Dale:
168 // The CWA areas themselves were included for just one product- generally
169 // called the "Hazardous Weather Outlook".  The idea was to be able to
170 // click on your region and get a synopsis as a Skywarn Heads-up.  In
171 // winaprs it turned out that you would get (unwanted) the CWA outline
172 // instead of some other data about a specific station.  It makes more
173 // sense to have three or 4 "home CWA'S" that are defined in the config
174 // file - and have a dialog box to view the Hazardous WX Outlook and
175 // watches and warnings just for that CWA.  One step futher- I assume there
176 // is something that trips alarms when a warning is received for a county
177 // or zone right around you - the cwa or cwa's of interest could be derived
178 // from that if it already exists.  A long way to say don't worry about CWA
179 // maps as far as watches/warnings.
180 //
181 // I think the easy coding for determining which shapefile to use would
182 // look like;
183 //
184 // char sevenCharStr[8];  // seven character string in warning or derived
185 // //                        from compressed string i. e. AL_Z001
186 //
187 // if the 4th char == 'C' then use "c_shapefile"
188 // if the 4th char == 'Z';
189 //       if the first two char == 'AN' ||
190 //       if the first two char == 'AM' ||
191 //       if the first two char == 'GM' ||
192 //       If the first two char == 'PZ' ||
193 //       if the first two char == 'LH' ||
194 //       if the first two char == 'LO' ||
195 //       if the first two char == 'LS' ||
196 //       if the first two char == 'SL' ||
197 //       if the first two char == 'LM' ||
198 //            then use the "mzshapefile"
199 //       else use the "z_shapefile"
200 //
201 // I am running out of time but that should be all there is to it- I
202 // will send you a complete list of marine zones later today- I
203 // think there are no more than 13 or 14 to search through - better
204 // that the 54 "states"- could be hard coded.
205 
206 // We now  have fire weather alerts also.  From Dale:
207 // "Ok I think we can use this to solve a problem with the Fire
208 // Watches and Warnings taking over the map of someone not
209 // interested.  Roger,  I had to take Fire warnings off the regular
210 // aprs-is feeds and send it to firenet.us server only because of
211 // complaints of the maps getting cluttered-- with data most people
212 // didn't want. Even though the NWS sends "AZZ148" just as if it
213 // were to be found in the z_mmddyy.dbf (warning zone) type file,
214 // wxsvr knows it is coming out of a "Fire Weather" type product and
215 // can substitute "AZF148" .  Client software (read xastir &
216 // Ui-view) would know to look in the fireweather shapefile.  If
217 // someone doesn't have the fire shapefile loaded, it would just be
218 // ignored (I think)."
219 
220 
221 // Found on the AWIPS web pages so far:
222 // AWIPS Counties          C
223 // County Warning Areas    A
224 // Zone Forecast Areas     Z
225 // Coastal Marine Zones    Z
226 // Offshore Marine Zones   Z
227 // High Seas Marine Zones  Z    (Says "Not for operational use")
228 // Fire Weather Zones      FZ
229 //
230 // Don't forget about the Canadian Areas and Regions, which are
231 // created by Dale Huguley from Environment Canada data.
232 //
233 //
234 // AWIPS Counties:
235 // -----------------------------
236 // STATE        character   2
237 // CWA          character   9
238 // COUNTYNAME   character   24
239 // FIPS         character   5
240 // TIME_ZONE    character   2
241 // FE_AREA      character   2
242 // LON          numeric     10,5
243 // LAT          numeric     9,5
244 //
245 //
246 // County Warning Areas:
247 // -----------------------------
248 // WFO          character   3
249 // CWA          character   3
250 // LON          numeric     10,5
251 // LAT          numeric     9,5
252 //
253 //
254 // Zone Forecast Areas:
255 // -----------------------------
256 // STATE        character   2
257 // ZONE         character   3
258 // CWA          character   3
259 // NAME         character   254
260 // STATE_ZONE   character   5
261 // TIME_ZONE    character   2
262 // FE_AREA      character   2
263 // LON          numeric     10,5
264 // LAT          numeric     9,5
265 //
266 //
267 // Coastal and Offshore Marine Zones:
268 // ----------------------------------
269 // ID           character   6
270 // WFO          character   3
271 // NAME         character   250
272 // LON          numeric     10,5
273 // LAT          numeric     9,5
274 // WFO_AREA     character   200
275 //
276 //
277 // High Seas Marine Zones:
278 // -----------------------------
279 // WFO          character   3
280 // LON          numeric     10,5
281 // LAT          numeric     9,5
282 // HEADING      character   250
283 
284 
285 
286 #ifdef HAVE_CONFIG_H
287   #include "config.h"
288 #endif  // HAVE_CONFIG_H
289 
290 #include <stdlib.h>
291 #include <stdio.h>
292 
293 #include <assert.h>
294 #include <ctype.h>
295 
296 #ifdef  HAVE_LOCALE_H
297   #include <locale.h>
298 #endif  // HAVE_LOCALE_H
299 
300 #include <Xm/XmAll.h>
301 
302 #include "xastir.h"
303 #include "alert.h"
304 #include "util.h"
305 #include "snprintf.h"
306 #include "wx.h"
307 #include "hashtable.h"
308 #include "hashtable_itr.h"
309 
310 // Must be last include file
311 #include "leak_detection.h"
312 
313 
314 
315 #define CHECKMALLOC(m)  if (!m) { fprintf(stderr, "***** Malloc Failed *****\n"); exit(0); }
316 
317 
318 // New method for weather alerts, using a hash
319 //
320 #define ALERT_HASH_SIZE 4096
321 static struct hashtable *wx_alert_hash = NULL;
322 
323 int alert_redraw_on_update = 0;
324 
325 
326 
327 
328 
329 /////////////////////////////////////////////////////////////////////
330 // The following group of functions implement hash storage for
331 // weather alerts.  This makes the code very fast, which is good
332 // because we run through these alerts often.
333 /////////////////////////////////////////////////////////////////////
334 
335 
336 
337 // Starting with an alert_entry, create a concatenated string
338 // containing:
339 //
340 //   *) "From" callsign
341 //   *) Zone
342 //   *) First four chars after the '{' char (the "issue date" field
343 //      plus one more character).
344 //
345 // These items should make the alert unique or nearly unique whether
346 // it was received from the WXSVR software or from hand-entered
347 // alerts.  WXSVR is where all of the machine-readable alerts come
348 // from and is run by Dale Huguley.
349 //
350 // This function takes a mostly filled-in alert_entry and fills in
351 // the unique_string variable portion with values from other
352 // variables in the record.
353 //
alert_fill_unique_string(alert_entry * alert)354 void alert_fill_unique_string(alert_entry *alert)
355 {
356   xastir_snprintf(alert->unique_string,
357                   sizeof(alert->unique_string),
358                   "%s%s%c%c%c%c",
359                   alert->from,
360                   alert->title,
361                   alert->seq[0],
362                   alert->seq[1],
363                   alert->seq[2],
364                   alert->seq[3]);
365 
366   //fprintf(stderr,"'%s'\t'%s'\t'%s'\n", alert->from, alert->title, alert->seq);
367   //fprintf(stderr,"Unique string:'%s'\n", alert->unique_string);
368 }
369 
370 
371 
372 
373 
374 // Add the chars (multiplied by a function of their index number to
375 // weight them) then truncate the final number to ALERT_HASH_SIZE.
376 // This should spread them nicely over the entire hash table.
377 //
wx_alert_hash_from_key(void * key)378 unsigned int wx_alert_hash_from_key(void *key)
379 {
380   alert_entry *temp = key;
381   char *jj = temp->unique_string;
382   unsigned int hash = 1;
383   int ii = 1;
384 
385   while (*jj != '\0')
386   {
387     hash = hash + ((unsigned int)*jj++ * ii);
388     ii += 4;
389   }
390 
391   hash = hash % ALERT_HASH_SIZE;
392 
393   //fprintf(stderr,"%d\n", hash);
394 
395   return ((unsigned int)hash);
396 }
397 
398 
399 
400 
401 
402 // According to Dale Huguley the FROM callsign plus the first four
403 // chars after the curly brace (at the end) should make the record
404 // unique.  Whether or not CANCEL messages get assigned to the
405 // proper live record is another matter.  They may, or may not match
406 // using this scheme, but it's probably better than other schemes.
407 // Multiple types of alerts can come out in the same message for the
408 // same zone/county.  In this case the fourth character will change
409 // from an 'A' to a 'B' or other char to denote the different
410 // messages with the same FROM and timestamp chars.  This again
411 // keeps each alert unique.
412 //
wx_alert_keys_equal(void * key1,void * key2)413 int wx_alert_keys_equal(void *key1, void *key2)
414 {
415   alert_entry *t1 = key1;
416   alert_entry *t2 = key2;
417 
418   // These fprintf's allow us to see how many cache hits we get
419   // and what the results were of the match.  If different
420   // unique_string's hash to the same value, we run through this
421   // compare once for each already-inserted record that also has
422   // the same hash vaue.
423   //    fprintf(stderr,"Comparing %s to %s\t",(char *)key1,(char *)key2);
424 
425   if (strlen((char *)t1->unique_string) == strlen((char *)t2->unique_string)
426       && strncmp((char *)t1->unique_string,
427                  (char *)t2->unique_string,
428                  strlen((char *)t1->unique_string))==0)
429   {
430 
431     //fprintf(stderr,"    match %s = %s\n", t1->unique_string, t2->unique_string);
432     return(1);
433   }
434   else
435   {
436     //fprintf(stderr,"  no match\n");
437     return(0);
438   }
439 }
440 
441 
442 
443 
444 
445 // Creates the wx_alert_hash if it doesn't exist yet.  If clobber is
446 // non-zero, destroys any existing hash then creates a new one.
447 //
init_wx_alert_hash(int clobber)448 void init_wx_alert_hash(int clobber)
449 {
450   //    fprintf(stderr," Initializing wx_alert_hash \n");
451   // make sure we don't leak
452   //fprintf(stderr,"init_wx_alert_hash\n");
453   if (wx_alert_hash)
454   {
455     //fprintf(stderr,"Already have one!\n");
456     if (clobber)
457     {
458       //fprintf(stderr,"Clobbering hash table\n");
459       hashtable_destroy(wx_alert_hash, 1);
460       wx_alert_hash = create_hashtable(ALERT_HASH_SIZE,
461                                        wx_alert_hash_from_key,
462                                        wx_alert_keys_equal);
463     }
464   }
465   else
466   {
467     //fprintf(stderr,"Creating hash table from scratch\n");
468     wx_alert_hash = create_hashtable(ALERT_HASH_SIZE,
469                                      wx_alert_hash_from_key,
470                                      wx_alert_keys_equal);
471   }
472 }
473 
474 
475 
476 
477 
478 // Fetch an alert from the wx_alert_hash based on the from call
479 // concatenated with the first four chars after the '{' character.
480 // This concatenated string should be unique across weather alerts
481 // if the alert came from the WXSVR.
482 //
483 // If it was a hand-entered alert, it won't have the '{' string at
484 // the end.  In that case use the from call and zone concatenated
485 // together instead for matching purposes.
486 //
get_wx_alert_from_hash(char * unique_string)487 alert_entry *get_wx_alert_from_hash(char *unique_string)
488 {
489   alert_entry *result = NULL;
490 
491   if (unique_string == NULL || *unique_string == '\0')
492   {
493     fprintf(stderr,"Empty unique_string passed to get_wx_alert_from_hash()\n");
494     return(NULL);
495   }
496 
497   if (!wx_alert_hash)
498   {
499     // no table to search
500     //fprintf(stderr,"Creating hash table\n");
501     init_wx_alert_hash(1); // so create one
502     return NULL;
503   }
504 
505   //fprintf(stderr,"   searching for %s...",unique_string);
506 
507   result = hashtable_search(wx_alert_hash, unique_string);
508   return (result);
509 }
510 
511 
512 
513 
514 
515 // Add a wx alert to the hash.
516 // This function checks whether there already is something in the
517 // hash table that matches.  If a match found, it skips the record,
518 // else it inserts a new wx alert record into the hash.
519 //
520 // Unfortunately it appears that any unique_string that hashes to
521 // the same value causes us to think it's a duplicate.
522 //
add_wx_alert_to_hash(char * unique_string,alert_entry * alert_record)523 void add_wx_alert_to_hash(char *unique_string, alert_entry *alert_record)
524 {
525   alert_entry *temp;  // alert_record
526   alert_entry *new_record;
527   char *new_unique_str;
528 
529 
530   if (debug_level & 2)
531   {
532     fprintf(stderr,"add_wx_alert_to_hash start\n");
533   }
534 
535   if (unique_string == NULL
536       || unique_string[0] == '\0'
537       || alert_record == NULL)
538   {
539     if (debug_level & 2)
540     {
541       fprintf(stderr,"add_wx_alert_to_hash finish\n");
542     }
543     return;
544   }
545 
546   if (!wx_alert_hash)
547   {
548     // no table to add to
549     //fprintf(stderr,"init_wx_alert_hash\n");
550     init_wx_alert_hash(1); // so create one
551   }
552 
553   // Remove any matching entry to avoid duplicates
554   temp = hashtable_remove(wx_alert_hash, unique_string);
555   if (temp)
556   {
557     // If value found, free the storage space for it as
558     // the hashtable_remove function doesn't.  It does
559     // however remove the key (callsign) ok.
560     free(temp);
561   }
562 
563   //fprintf(stderr, "\t\t\tAdding %s...\n", unique_string);
564 
565   // Allocate new space for the key and the record
566   new_unique_str = (char *)malloc(50);
567   CHECKMALLOC(new_unique_str);
568 
569   new_record = (alert_entry*)malloc(sizeof(alert_entry));
570   CHECKMALLOC(new_record);
571 
572   xastir_snprintf(new_unique_str, 50, "%s", unique_string);
573 
574   memcpy(new_record, alert_record, sizeof(alert_entry));
575 
576   //                    hash           title  alert_record
577   if (!hashtable_insert(wx_alert_hash, new_unique_str, new_record))
578   {
579     fprintf(stderr,"Insert failed on wx alert hash --- fatal\n");
580     free(new_unique_str);
581     free(new_record);
582     //        exit(1);
583   }
584 
585   // Yet another check to see whether hash insert/update worked
586   // properly
587   temp = get_wx_alert_from_hash(unique_string);
588   if (!temp)
589   {
590     fprintf(stderr,"***Failed wx alert hash insert/update***\n");
591   }
592   else
593   {
594     //fprintf(stderr,"Current: %s -> %s\n",
595     //    unique_string,
596     //    temp);
597   }
598 
599   if (debug_level & 2)
600   {
601     fprintf(stderr,"add_wx_alert_to_hash finish\n");
602   }
603 }
604 
605 
606 
607 
608 
609 // Create the wx alert hash table iterator so that we can iterate
610 // through the entire hash table and draw the alerts.
611 //
create_wx_alert_iterator(void)612 struct hashtable_itr *create_wx_alert_iterator(void)
613 {
614 
615   if (wx_alert_hash && hashtable_count(wx_alert_hash) > 0)
616   {
617     return(hashtable_iterator(wx_alert_hash));
618   }
619   else
620   {
621     return(NULL);
622   }
623 }
624 
625 
626 
627 
628 
629 // Get the wx alert entry that the iterator is pointing to.  Advance
630 // the iterator to the following wx alert.
631 //
get_next_wx_alert(struct hashtable_itr * iterator)632 alert_entry *get_next_wx_alert(struct hashtable_itr *iterator)
633 {
634   alert_entry *temp = NULL;
635 
636   if (wx_alert_hash
637       && iterator
638       && hashtable_count(wx_alert_hash) > 0)
639   {
640 
641     // Get record
642     temp = hashtable_iterator_value(iterator);
643 
644     // Advance to the next record
645     hashtable_iterator_advance(iterator);
646   }
647   return(temp);
648 }
649 
650 
651 
652 
653 // alert_print_list()
654 //
655 // Debug routine.  Currently attached to the Test() function in
656 // main.c, but the button in the file menu is normally grey'ed out.
657 // This function prints the current weather alert list out to the
658 // xterm.
659 //
alert_print_list(void)660 void alert_print_list(void)
661 {
662 
663 }
664 
665 
666 
667 
668 
669 // Needed by alert_expire() below
670 static time_t last_alert_expire = 0;
671 
672 
673 
674 // alert_expire()
675 //
676 // Function which iterates through the wx alert hash table, removing
677 // expired alerts as it goes.  Makes sure that the expired alert
678 // doesn't get drawn or shown in the View->WX Alerts dialog.
679 //
680 // Returns the quantity of alerts that were just expired.
681 //
alert_expire(int curr_sec)682 int alert_expire(int curr_sec)
683 {
684   //    int ii;
685   int expire_count = 0;
686   struct hashtable_itr *iterator;
687   alert_entry *temp;
688 
689 
690   // Check only every 60 seconds
691   if ( (last_alert_expire + 60) > curr_sec )
692   {
693     return(0);
694   }
695   last_alert_expire = curr_sec;
696 
697   if (debug_level & 2)
698   {
699     fprintf(stderr,"Checking for expired alerts...\n");
700   }
701 
702   iterator = create_wx_alert_iterator();
703   while (iterator)
704   {
705 
706     // Get current record
707     temp = hashtable_iterator_value(iterator);
708 
709     if (!temp)
710     {
711 #ifndef USING_LIBGC
712       //fprintf(stderr,"free iterator 1\n");
713       if (iterator)
714       {
715         free(iterator);
716       }
717 #endif  // USING_LIBGC
718       return(expire_count);
719     }
720 
721     // If wx alert has expired, remove the record from the hash.
722     if (temp->expiration < time(NULL))
723     {
724 
725       if (debug_level & 2)
726       {
727         fprintf(stderr,
728                 "alert_expire: Clearing %s, current: %lu, alert: %lu\n",
729                 temp->unique_string,
730                 (unsigned long)time(NULL),
731                 (unsigned long)temp->expiration);
732       }
733 
734       // Free the storage space
735       free(temp);
736 
737       // Delete the entry and advance to the next
738       hashtable_iterator_remove(iterator);
739 
740       expire_count++;
741     }
742     else
743     {
744       if (temp && iterator)
745       {
746         // Else advance to the next entry
747         hashtable_iterator_advance(iterator);
748       }
749     }
750   }
751 #ifndef USING_LIBGC
752   //fprintf(stderr,"free iterator 2\n");
753   if (iterator)
754   {
755     free(iterator);
756   }
757 #endif  // USING_LIBGC
758 
759   // Cause a screen redraw if we expired some alerts
760   if (expire_count)
761   {
762     // Schedule a screen update 'cuz we have a new alert
763     alert_redraw_on_update = redraw_on_new_data = 2;
764   }
765 
766   return(expire_count);
767 }
768 
769 
770 
771 
772 
773 // alert_add_entry()
774 //
775 // This function adds a new alert to our alert list.
776 //
777 // Returns address of new entry or NULL.
778 // Called from alert_build_list() function.
779 //
alert_add_entry(alert_entry * entry)780 /*@null@*/ static alert_entry *alert_add_entry(alert_entry *entry)
781 {
782   //    alert_entry *ptr;
783   //    int i;
784 
785   set_dangerous("alert.c:alert_add_entry()");
786 
787   if (debug_level & 2)
788   {
789     fprintf(stderr,"alert_add_entry\n");
790   }
791 
792   if (strlen(entry->title) == 0)
793   {
794     if (debug_level & 2)
795     {
796       fprintf(stderr,"alert_add_entry: Empty title!\n");
797     }
798 
799     clear_dangerous();
800 
801     return(NULL);
802   }
803 
804   // Skip NWS_SOLAR and -NoActivationExpected alerts, they don't
805   // interest us.
806   if ( (strcmp(entry->to, "NWS-SOLAR") == 0)
807        || (strcmp(entry->to, "NWS_SOLAR") == 0) )
808   {
809     if (debug_level & 2)
810     {
811       fprintf(stderr,"NWS-SOLAR, skipping\n");
812     }
813 
814     clear_dangerous();
815 
816     return(NULL);
817   }
818   if (strcasecmp(entry->title, "-NoActivationExpected") == 0)
819   {
820     if (debug_level & 2)
821     {
822       fprintf(stderr,"NoActivationExpected, skipping\n");
823     }
824 
825     clear_dangerous();
826 
827     return(NULL);
828   }
829 
830   // Check whether this new alert has already expired.  If so,
831   // don't add it.
832   if (entry->expiration < time(NULL))
833   {
834     if (debug_level & 2)
835     {
836       fprintf(stderr,
837               "Newest Alert Expired->Clearing, current: %lu, alert: %lu\n",
838               (unsigned long)time(NULL),
839               (unsigned long)entry->expiration );
840     }
841 
842     clear_dangerous();
843 
844     return(NULL);
845   }
846 
847   // Check for non-zero alert title, non-expired alert time in new
848   // alert.
849   if (entry->title[0] != '\0' && entry->expiration >= time(NULL))
850   {
851 
852     // Schedule a screen update 'cuz we have a new alert
853     alert_redraw_on_update = redraw_on_new_data = 2;
854 
855     //WE7U
856 
857     set_dangerous("alert.c:add_wx_alert_to_hash()");
858 
859     add_wx_alert_to_hash(entry->unique_string, entry);
860 
861     clear_dangerous();
862 
863     return(entry);
864 
865   }
866 
867   // If we got to here, the title was empty or the alert has
868   // already expired?  Figure out why we might ever get here.
869   if (debug_level & 2)
870   {
871     fprintf(stderr,"Exiting alert_add_entry() without actually adding the alert:\n");
872     fprintf(stderr,"%s %s %lu\n",
873             entry->to,
874             entry->title,
875             (unsigned long)entry->expiration);
876   }
877 
878   clear_dangerous();
879 
880   return(NULL);
881 }
882 
883 
884 
885 // alert_active()
886 //
887 // Here's where we get rid of expired alerts in the list.  Called
888 // from alert_display_request(), alert_on_screen(),
889 // and alert_build_list() functions.  Also called from
890 // maps.c:load_alert_maps() function.
891 //
892 // Returns the alert level.
893 //
894 // Alert Match Levels:
895 // 0 = ?
896 // 1 = R
897 // 2 = Y
898 // 3 = B
899 // 4 = T
900 // 5 = G
901 // 6 = C
902 //
alert_active(alert_entry * alert,alert_match_level UNUSED (match_level))903 int alert_active(alert_entry *alert, alert_match_level UNUSED (match_level) )
904 {
905   alert_entry *a_ptr;
906   char l_list[] = {"?RYBTGC"};
907   int level = 0;
908   time_t now;
909 
910 
911   if (strlen(alert->title) == 0)
912   {
913     if (debug_level & 2)
914     {
915       fprintf(stderr,"alert_active:NULL\n");
916     }
917     return(0);
918   }
919 
920   if (debug_level & 2)
921   {
922     fprintf(stderr,"alert_active:%s\n",alert->title);
923   }
924 
925   (void)time(&now);
926 
927   //    if ((a_ptr = alert_match(alert, match_level))) {
928   if ((a_ptr = get_wx_alert_from_hash(alert->unique_string)))
929   {
930     if (a_ptr->expiration >= now)
931     {
932       for (level = 0; a_ptr->alert_level != l_list[level] && level < (int)sizeof(l_list); level++)
933       {
934         //do nothing
935       }
936 
937     }
938     else if (a_ptr->expiration < (now - 3600))
939     {
940       // More than an hour past the expiration,
941       a_ptr->title[0] = '\0';                     // so delete it from list by clearing
942       // out the title.
943       //WE7U
944 
945       // Schedule an update 'cuz we need to delete an expired
946       // alert from the list.
947       alert_redraw_on_update = redraw_on_new_data = 2;
948     }
949     else if (a_ptr->flags[on_screen] == '?')
950     {
951       // Expired between 1sec and 1hr and found '?'
952       a_ptr->flags[on_screen] = '-';
953 
954       // Schedule a screen update 'cuz we have an expired alert
955       alert_redraw_on_update = redraw_on_new_data = 2;
956     }
957   }
958   return (level);
959 }
960 
961 
962 
963 
964 
965 // alert_display_request()
966 //
967 // Function which checks whether an alert should be displayed.
968 // Called from maps.c:load_alert_maps() function.
969 //
alert_display_request(void)970 int alert_display_request(void)
971 {
972   //    int i;
973   int alert_count;
974   static int last_alert_count;
975 
976 
977   if (debug_level & 2)
978   {
979     fprintf(stderr,"alert_display_request\n");
980   }
981 
982   //WE7U
983   if (wx_alert_hash)
984   {
985     alert_count = hashtable_count(wx_alert_hash);
986   }
987   else
988   {
989     return((int)FALSE);
990   }
991 
992   // If we found any, return TRUE.
993   if (alert_count != last_alert_count)
994   {
995     last_alert_count = alert_count;
996     return ((int)TRUE);
997   }
998 
999   return ((int)FALSE);
1000 }
1001 
1002 
1003 // alert_list_count
alert_list_count(void)1004 int alert_list_count(void)
1005 {
1006   int count = 0;
1007   if (wx_alert_hash)
1008   {
1009     return(hashtable_count(wx_alert_hash));
1010   }
1011   else
1012   {
1013     return count;
1014   }
1015 }
1016 
1017 
1018 // alert_on_screen()
1019 //
1020 // Returns a count of active weather alerts in the list which are
1021 // within our viewport.
1022 // Called from main.c:UpdateTime() function.  Used for sounding
1023 // alarm if a new weather alert appears on screen.
1024 //
alert_on_screen(void)1025 int alert_on_screen(void)
1026 {
1027   //    int i;
1028   int alert_count = 0;
1029   struct hashtable_itr *iterator;
1030   alert_entry *temp;
1031 
1032 
1033   if (debug_level & 2)
1034   {
1035     fprintf(stderr,"alert_on_screen\n");
1036   }
1037 
1038   //WE7U
1039 
1040   iterator = create_wx_alert_iterator();
1041   temp = get_next_wx_alert(iterator);
1042   while (iterator != NULL && temp)
1043   {
1044     if (alert_active(temp, ALERT_ALL)
1045         && temp->flags[on_screen] == 'Y')
1046     {
1047       alert_count++;
1048     }
1049     temp = get_next_wx_alert(iterator);
1050   }
1051 #ifndef USING_LIBGC
1052   //fprintf(stderr,"free iterator 3\n");
1053   if (iterator)
1054   {
1055     free(iterator);
1056   }
1057 #endif  // USING_LIBGC
1058 
1059   return (alert_count);
1060 }
1061 
1062 
1063 
1064 
1065 
1066 // alert_build_list()
1067 //
1068 // This function builds alert_entry structs from message entries that
1069 // contain NWS alert messages.
1070 //
1071 // Called from alert_data_add() function.
1072 //
1073 //
1074 // Here's how Xastir breaks down an alert into an alert struct:
1075 //
1076 // SFONPW>APRS::NWS-ADVIS:191700z,WIND,CA_Z007,CA_Z065, ALAMEDA AND CON & NAPA COUNTY {JDIAA
1077 // |----|       |-------| |-----| |--| |-----| |-----|                                 |-|
1078 //   |              |        |     |      |       |                                     |
1079 //  from            to       |     |    title   title                               issue_date
1080 //                           |  alert_tag
1081 //                        activity (expiration)
1082 //
1083 //
1084 // The code should also handle the case where the packet looks like
1085 // this (same except no expiration date):
1086 //
1087 // SFONPW>APRS::NWS-ADVIS:WIND,CA_Z007,CA_Z065, ALAMEDA AND CON & NAPA COUNTY {JDIAA
1088 //
1089 // We also have compressed NWS alerts, signified by NWS_ADVIS
1090 // (underline instead of dash).  Note that Pete Loveall, AE5PL, is
1091 // also sending out alerts and sending the "compressed" zone format
1092 // with "NWS-" which is different than how Dale Huguley was sending
1093 // them out.  Pete's change is to support Kenwood radios.
1094 //
1095 //
1096 // Expiration is then computed from the activity field.  Alert_level
1097 // is computed from "to" and/or "alert_tag".  There can be up to
1098 // five titles in this original format.
1099 //
1100 // Here are some real examples captured over the 'net (may be quite old):
1101 //
1102 // TAEFFS>APRS::NWS-ADVIS:181830z,FLOOD,FL_C013,FL_C037,FL_C045,FL_C077, {HHEAA
1103 // ICTFFS>APRS::NWS-ADVIS:180200z,FLOOD,KS_C035, {HEtAA
1104 // JANFFS>APRS::NWS-ADVIS:180200z,FLOOD,MS_C049,MS_C079,MS_C089,MS_C099,MS_C121, {HEvAA
1105 // DSMFFS>APRS::NWS-ADVIS:180500z,FLOOD,IA_Z086, {HHGAA
1106 // EAXFFS>APRS::NWS-ADVIS:180500z,FLOOD,MO_Z023,MO_Z024,MO_Z028,MO_Z030,MO_Z031, {HHIAA
1107 // SECIND>APRS::NWS-SOLAR:Flx134 A004 BK0001232.  PlnK0000232.Ep............Ee........ {HLaAA
1108 // SHVFFS>APRS::NWS-ADVIS:181800z,FLOOD,TX_C005,TX_C073,TX_C347,TX_C365,TX_C401, {HF2AA
1109 // FWDFFS>APRS::NWS-ADVIS:180200z,FLOOD,TX_C379,TX_C467, {HF5AA
1110 // LCHFFS>APRS::NWS-ADVIS:180400z,FLOOD,LA_C003,LA_C079, {HIdAA
1111 // GIDFFS>APRS::NWS-ADVIS:180200z,FLOOD,NE_C125, {H2uAA
1112 // FWDSWO>APRS::NWS-ADVIS:181100z,SKY,CW_AFWD, -NO Activation Expected {HLqAA
1113 // BGMWSW>APRS::NWS-ADVIS:180500z,WINTER_WEATHER,NY_Z015,NY_Z016,NY_Z017,NY_Z022,NY_Z023, {HKYAA
1114 // AMAWSW>APRS::NWS-WARN :180400z,WINTER_STORM,OK_Z001,OK_Z002,TX_Z001,TX_Z002,TX_Z003, {HLGBA
1115 //
1116 // New compressed-mode weather alert packets:
1117 //
1118 // LZKNPW>APRS::NWS-ADVIS:221000z,WINTER_STORM,ARZ003>007-012>016-021>025-030>034-037>047-052>057-062>069{LLSAA
1119 //
1120 // or perhaps (leading zeroes removed):
1121 //
1122 // LZKNPW>APRS::NWS-ADVIS:221000z,WINTER_STORM,ARZ3>7-12>16-21>25-30>34-37>47-52>57-62>69{LLSAA
1123 //
1124 // This one's real:
1125 // DVNFFS>APRS,qAO,WXSVR::NWS_ADVIS:022300z,FLOOD,IAC57-95-103-111-115-163-171-ILC1-67-71-131-MOC45 {2FsAA
1126 //
1127 // The trick is to step thru the data base contained in the
1128 // shapefiles to determine what areas to light up.  In the above
1129 // example read ">" as "thru" and "-" as "and".
1130 //
1131 //
1132 // RIWWSW>APRS::NWS-WARN :191800z,WINTER_STORM,WY_Z014, GREEN MOUNTAINS {JBNBA
1133 // RIWWSW>APRS::SKYRIW   :WINTER STORM WARNING CONTINUING TODAY {JBNBB
1134 // RIWWSW>APRS::SKYRIW   :THROUGH SATURDAY {JBNBC
1135 //
1136 //
1137 // We'll create and fill in "entry", then copy various "titles" into
1138 // is such as "ID_C001", then insert that alert into the system.
1139 //
1140 //
1141 // VK2XJG - November 2011:
1142 // Here are some examples of strings from the new WXSVR-AU for the Aussie Bureau of Meteorology (BOM) alerts:
1143 //
1144 // NECMWW>APRS::BOM_WARN :141300z,WIND,TAS_MW002>003-005>007-009 {D55AG
1145 // YKPMWW>APRS::BOM_ADVIS:131330z,WIND,SA_MW005 {D5aAA
1146 //
1147 // For the BOM alerts note that the STATE portion of the zone can be two or three characters - valid state
1148 // prefixes are "NSW,VIC,QLD,TAS,NT,WA,SA". The two characters following the underscore denote the shapefile to use
1149 // These will be one of "CW,MW,PW,FW or ME".
1150 //
1151 // WXSVR-AU also does NOT strip leading zeros from the zone strings, however the existing NWS code allows for this, so
1152 // it should be supported if WXSVR-AU strips the zeros in the future.
1153 //
1154 //
1155 //
1156 #define TITLE_SIZE 64
alert_build_list(Message * fill)1157 void alert_build_list(Message *fill)
1158 {
1159   alert_entry entry, *list_ptr;
1160   char title[5][TITLE_SIZE+1];  // Storage place for zone/county titles
1161   int ii, jj;
1162   char *ptr;
1163   DataRow *p_station;
1164   int compressed_wx_packet = 0;
1165   char uncompressed_wx[10000];
1166   struct hashtable_itr *iterator;
1167   int tmp_size;
1168 
1169 
1170   //fprintf(stderr,"Message_line:%s\n",fill->message_line);
1171 
1172   if (debug_level & 2)
1173   {
1174     fprintf(stderr,"alert_build_list:%s>%s:%s\n",
1175             fill->from_call_sign,
1176             fill->call_sign,
1177             fill->message_line);
1178   }
1179 
1180   // Empty this string first
1181   uncompressed_wx[0] = uncompressed_wx[1] = '\0';
1182 
1183   // Check for "SKY" text in the "call_sign" field.
1184   if (strncmp(fill->call_sign,"SKY",3) == 0)
1185   {
1186     // Special handling for SKY messages only.
1187 
1188     if (debug_level & 2)
1189     {
1190       fprintf(stderr,"Sky Message: %s\n",fill->message_line);
1191     }
1192 
1193     // Find a matching alert_record, check whether or not it is
1194     // expired.  If not, add this additional text into the
1195     // "desc[0123]" fields, in order.  Check that the
1196     // FROM callsign and the first four chars after the curly
1197     // brace match.  The next character specifies which message
1198     // block to fill in.  In order they should be:
1199     //
1200     // B = "desc0"
1201     // C = "desc1"
1202     // D = "desc2"
1203     // E = "desc3".
1204     //
1205     // A matching alert record would have the same "from" field
1206     // and the first four characters of the "seq" field would
1207     // match.
1208     //
1209     // Need to make this SKY data expire from the message list
1210     // somehow?
1211     //
1212     // Remember to blank out these fields when we expire an
1213     // alert.  Check that all other fields are cleared in this
1214     // case as well.
1215     //
1216 
1217     // Run through the alert list looking for a match to the
1218     // FROM and first four chars of SEQ
1219     //WE7U
1220     iterator = create_wx_alert_iterator();
1221     for (list_ptr = get_next_wx_alert(iterator); iterator != NULL && list_ptr; list_ptr = get_next_wx_alert(iterator))
1222     {
1223       if ( (strcasecmp(list_ptr->from, fill->from_call_sign) == 0)
1224            && ( strncmp(list_ptr->seq,fill->seq,4) == 0 ) )
1225       {
1226 
1227         if (debug_level & 2)
1228         {
1229           fprintf(stderr,"%s:Found a matching alert to a SKY message:\t",list_ptr->seq);
1230         }
1231 
1232         switch (fill->seq[4])
1233         {
1234           case 'B':
1235             tmp_size = sizeof(list_ptr->desc0);
1236             memcpy(list_ptr->desc0, fill->message_line, tmp_size);
1237             list_ptr->desc0[tmp_size-1] = '\0'; // Terminate string
1238             if (debug_level & 2)
1239             {
1240               fprintf(stderr,"Wrote into desc0: %s\n",fill->message_line);
1241             }
1242             break;
1243           case 'C':
1244             tmp_size = sizeof(list_ptr->desc1);
1245             memcpy(list_ptr->desc1, fill->message_line, tmp_size);
1246             list_ptr->desc1[tmp_size-1] = '\0'; // Terminate string
1247             if (debug_level & 2)
1248             {
1249               fprintf(stderr,"Wrote into desc1: %s\n",fill->message_line);
1250             }
1251             break;
1252           case 'D':
1253             tmp_size = sizeof(list_ptr->desc2);
1254             memcpy(list_ptr->desc2, fill->message_line, tmp_size);
1255             list_ptr->desc2[tmp_size-1] = '\0'; // Terminate string
1256             if (debug_level & 2)
1257             {
1258               fprintf(stderr,"Wrote into desc2: %s\n",fill->message_line);
1259             }
1260             break;
1261           case 'E':
1262           default:
1263             tmp_size = sizeof(list_ptr->desc3);
1264             memcpy(list_ptr->desc3, fill->message_line, tmp_size);
1265             list_ptr->desc3[tmp_size-1] = '\0'; // Terminate string
1266             if (debug_level & 2)
1267             {
1268               fprintf(stderr,"Wrote into desc3: %s\n",fill->message_line);
1269             }
1270             break;
1271         }
1272       }
1273     }
1274     if (debug_level & 2)
1275     {
1276       fprintf(stderr,"alert_build_list return 1\n");
1277     }
1278 #ifndef USING_LIBGC
1279     //fprintf(stderr,"free iterator a4\n");
1280     if (iterator)
1281     {
1282       free(iterator);
1283     }
1284 #endif  // USING_LIBGC
1285     return;
1286   }
1287 
1288   if (debug_level & 2)
1289   {
1290     fprintf(stderr,"1\n");
1291   }
1292 
1293   if (fill->active == RECORD_ACTIVE)
1294   {
1295     int ignore_title = 0;
1296 #define MAX_SUB_ALERTS 5000
1297     char *title_ptr[MAX_SUB_ALERTS];
1298     int ret;
1299 
1300 
1301     if (debug_level & 2)
1302     {
1303       fprintf(stderr,"2\n");
1304     }
1305 
1306     memset(&entry, 0, sizeof(entry));
1307     // flags[0] specifies whether it's onscreen or not
1308     memset(entry.flags, (int)'?', sizeof(entry.flags));
1309 
1310     // flags[source] specifies source of the alert DATA_VIA_TNC or
1311     // DATA_VIA_LOCAL
1312     entry.flags[source] = fill->heard_via_tnc;
1313     p_station = NULL;
1314     if (search_station_name(&p_station,fill->from_call_sign,1))
1315     {
1316       entry.flags[source] = p_station->data_via;
1317     }
1318 
1319     // Zero the title strings.  We can have up to five alerts in
1320     // a non-compressed weather alert.
1321     title[0][0] = '\0';
1322     title[1][0] = '\0';
1323     title[2][0] = '\0';
1324     title[3][0] = '\0';
1325     title[4][0] = '\0';
1326 
1327     // This fills in the zone numbers (title) for uncompressed
1328     // alerts with up to five alerts per message.  This doesn't
1329     // handle filling in the title for compressed alerts though.
1330     ret = sscanf(fill->message_line,
1331                  "%20[^,],%20[^,],%32[^,],%32[^,],%32[^,],%32[^,],%32[^,]",
1332                  entry.activity,      // 191700z
1333                  entry.alert_tag,     // WIND
1334                  &title[0][0],        // CA_Z007
1335                  &title[1][0],        // ...
1336                  &title[2][0],        // ...
1337                  &title[3][0],        // ...
1338                  &title[4][0]);       // ...
1339 
1340     if (ret < 3)
1341     {
1342       fprintf(stderr,"sscanf parsed %d values in alert.c (3-7 ok) %s->%s: %s\n", ret,
1343               fill->from_call_sign, fill->call_sign, fill->message_line);
1344     }
1345     // Force a termination for each
1346     entry.activity[20]  = '\0';
1347     entry.alert_tag[20] = '\0';
1348     title[0][TITLE_SIZE]        = '\0';
1349     title[1][TITLE_SIZE]        = '\0';
1350     title[2][TITLE_SIZE]        = '\0';
1351     title[3][TITLE_SIZE]        = '\0';
1352     title[4][TITLE_SIZE]        = '\0';
1353 
1354     // Check for "NWS_" in the call_sign field.  Underline
1355     // signifies compressed alert format.  Dash signifies
1356     // non-compressed format.
1357 
1358     // K2DLS 08/25/17
1359     // Also check for NWS- where title[0] does not contain
1360     // an underscore.  This is to identify AE5PL's compressed
1361     // alerts in uncompressed clothing.
1362 
1363     if ((strncmp(fill->call_sign,"NWS_",4) == 0) |
1364         ((strncmp(fill->call_sign,"NWS-",4) == 0) &
1365          (strstr(title[0], "_") == NULL)))
1366     {
1367 
1368       char compressed_wx[512];
1369       char *ptr;
1370 
1371       /////////////////////////////////////////////////////////////////////
1372       // Compressed weather alert special code
1373       /////////////////////////////////////////////////////////////////////
1374 
1375       compressed_wx_packet++; // Set the flag
1376 
1377       //fprintf(stderr, "Found compressed alert packet via NWS_!\n");
1378 
1379       //fprintf(stderr,"Compressed Weather Alert:%s\n",fill->message_line);
1380       //fprintf(stderr,"Compressed alerts are not fully implemented yet.\n");
1381 
1382       // Create a new weather alert for each of these and then
1383       // call this function on each one?  Seems like it might
1384       // work fine if we watch out for global variables.
1385       // Another method would be to create an incoming message
1386       // for each one and add it to the message queue, or just
1387       // a really long new message and add it to the queue,
1388       // in which case we'd exit from this routine as soon as
1389       // it was submitted.
1390       ret = sscanf(fill->message_line, "%20[^,],%20[^,],%255[^, ]",
1391                    entry.activity,
1392                    entry.alert_tag,
1393                    compressed_wx);     // Stick the long string in here
1394 
1395       if (ret != 3)
1396       {
1397         fprintf(stderr,"sscanf parsed %d/3 values in alert.c\n", ret);
1398         compressed_wx[0] = '\0';  // Remove stale compressed alerts.
1399         compressed_wx_packet = 0; //Clear flag in error condition.
1400       }
1401 
1402       compressed_wx[255] = '\0';
1403 
1404       //fprintf(stderr,"Line:%s\n",compressed_wx);
1405 
1406       // Snag alpha characters (should be three) at the start
1407       // of the string.  Use those until we hit more alpha
1408       // characters.  First two characters of each 3-letter
1409       // alpha group are the state, last character is the
1410       // zone/county/marine-zone indicator.
1411 
1412       // Need to be very careful here to validate the letters/numbers, and
1413       // to not run off the end of the string.  Need more code here to do
1414       // this validation.
1415 
1416 
1417       // Scan through entire string
1418       ptr = compressed_wx;
1419       while (ptr < (compressed_wx + strlen(compressed_wx)))
1420       {
1421         char prefix[5];
1422         char suffix[4];
1423         char temp_suffix[4];
1424         char ending[4];
1425         int iterations = 0;
1426 
1427 
1428         // Snag the ALPHA portion
1429         xastir_snprintf(prefix,
1430                         sizeof(prefix),
1431                         "%s",
1432                         ptr);
1433         ptr += 2;
1434         prefix[2] = '_';
1435         prefix[3] = ptr[0];
1436         prefix[4] = '\0';   // Terminate the string
1437         ptr += 1;
1438 
1439         // prefix should now contain something like "MN_Z"
1440 
1441         // Snag the NUMERIC portion.  Note that the field
1442         // width can vary between 1 and 3.  The leading
1443         // zeroes have been removed.
1444         xastir_snprintf(temp_suffix,
1445                         sizeof(temp_suffix),
1446                         "%s",
1447                         ptr);
1448 
1449         temp_suffix[3] = '\0';   // Terminate the string
1450         if (temp_suffix[1] == '-' || temp_suffix[1] == '>')
1451         {
1452           temp_suffix[1] = '\0';
1453           ptr += 1;
1454         }
1455         else if (temp_suffix[1] != '\0' &&
1456                  (temp_suffix[2] == '-' || temp_suffix[2] == '>'))
1457         {
1458           temp_suffix[2] = '\0';
1459           ptr += 2;
1460         }
1461         else
1462         {
1463           ptr += 3;
1464         }
1465 
1466         // temp_suffix should now contain something like
1467         // "039" or "45" or "2".  Add leading zeroes to give
1468         // "suffix" a length of 3.
1469         xastir_snprintf(suffix,
1470                         sizeof(suffix),
1471                         "000");
1472         switch (strlen(temp_suffix))
1473         {
1474           case 1: // Copy one char across
1475             suffix[2] = temp_suffix[0];
1476             break;
1477           case 2: // Copy two chars across
1478             suffix[1] = temp_suffix[0];
1479             suffix[2] = temp_suffix[1];
1480             break;
1481           case 3: // Copy all three chars across
1482             xastir_snprintf(suffix,
1483                             sizeof(suffix),
1484                             "%s",
1485                             temp_suffix);
1486             break;
1487         }
1488         // Make sure suffix is terminated properly
1489         suffix[3] = '\0';
1490 
1491         // We have our first zone (of this loop) extracted!
1492         if (debug_level & 2)
1493         {
1494           fprintf(stderr,"1Zone:%s%s\n",prefix,suffix);
1495         }
1496 
1497         // Add it to our zone string.  In this case we know
1498         // that the lengths of the strings we're working
1499         // with are quite short.  Little danger of
1500         // overrunning our destination string.
1501         strncat(uncompressed_wx,
1502                 ",",
1503                 sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1504         strncat(uncompressed_wx,
1505                 prefix,
1506                 sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1507         strncat(uncompressed_wx,
1508                 suffix,
1509                 sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1510         // Terminate it every time
1511         uncompressed_wx[9999] = '\0';
1512 
1513         if (debug_level & 2)
1514         {
1515           fprintf(stderr,"uncompressed_wx:%s\n",uncompressed_wx);
1516         }
1517 
1518         // Here we keep looping until we hit another alpha
1519         // portion.  We need to look at the field separator
1520         // to determine whether we have another separate
1521         // field coming up or a range to enumerate.
1522         while ( (ptr < (compressed_wx + strlen(compressed_wx)))
1523                 && ( is_num_chr(ptr[1]) ) )
1524         {
1525 
1526           iterations++;
1527 
1528           // Break out of this loop if we don't find an
1529           // alpha character fairly quickly.  That way the
1530           // Xastir main thread can't hang in this loop
1531           // forever if the input string is malformed.
1532           if (iterations > 30)
1533           {
1534             break;
1535           }
1536 
1537           // Look for '>' or '-' character.  If former, we
1538           // have a numeric sequence to ennumerate.  If the
1539           // latter, we either have another zone number or
1540           // another prefix coming up.
1541           if (ptr[0] == '>' || ptr[0] == '<')
1542           {
1543             // Numeric zone sequence
1544             int start_number;
1545             int end_number;
1546             int kk;
1547 
1548 
1549             ptr++;  // Skip past the '>' or '<' characters
1550 
1551             // Snag the NUMERIC portion.  May be between
1552             // 1 and three digits long.
1553             xastir_snprintf(ending,
1554                             sizeof(ending),
1555                             "%s",
1556                             ptr);
1557 
1558             // Terminate the string and advance the
1559             // pointer past it.
1560             if (!is_num_chr(ending[0]))
1561             {
1562               // We have a problem, 'cuz we didn't
1563               // find at least one number.  Packet is
1564               // badly formatted.
1565               return;
1566             }
1567             else if (!is_num_chr(ending[1]))
1568             {
1569               ending[1] = '\0';
1570               ptr++;
1571             }
1572             else if (!is_num_chr(ending[2]))
1573             {
1574               ending[2] = '\0';
1575               ptr+=2;
1576             }
1577             else
1578             {
1579               ending[3] = '\0';
1580               ptr+=3;
1581             }
1582 
1583             // ending should now contain something like
1584             // "046" or "35" or "2"
1585             if (debug_level & 2)
1586             {
1587               fprintf(stderr,"Ending:%s\n",ending);
1588             }
1589 
1590             start_number = (int)atoi(suffix);
1591             end_number = (int)atoi(ending);
1592             for ( kk=start_number+1; kk<=end_number; kk++)
1593             {
1594               xastir_snprintf(suffix,4,"%03d",kk);
1595 
1596               if (debug_level & 2)
1597               {
1598                 fprintf(stderr,"2Zone:%s%s\n",prefix,suffix);
1599               }
1600 
1601               // And another zone... Ennumerate
1602               // through the sequence, adding each
1603               // new zone to our zone string.  In this
1604               // case we know that the lengths of the
1605               // strings we're working with are quite
1606               // short.  Little danger of overrunning
1607               // our destination string.
1608               strncat(uncompressed_wx,
1609                       ",",
1610                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1611               strncat(uncompressed_wx,
1612                       prefix,
1613                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1614               strncat(uncompressed_wx,
1615                       suffix,
1616                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1617               // Terminate it every time
1618               uncompressed_wx[9999] = '\0';
1619             }
1620           }
1621 
1622           // Wasn't a '>' character, so check for a '-'
1623           else if (ptr[0] == '-')
1624           {
1625             // New zone number, not a numeric sequence.
1626 
1627             ptr++;  // Skip past the '-' character
1628 
1629             if ( is_num_chr(ptr[0]) )
1630             {
1631               // Found another number.  Use the prefix
1632               // stored from last time.
1633 
1634               // Snag the NUMERIC portion.  Note that the field
1635               // width can vary between 1 and 3.  The leading
1636               // zeroes have been removed.
1637               xastir_snprintf(temp_suffix,
1638                               sizeof(temp_suffix),
1639                               "%s",
1640                               ptr);
1641 
1642               // Terminate the string and advance the
1643               // pointer past it.
1644               if (!is_num_chr(temp_suffix[0]))
1645               {
1646                 // We have a problem, 'cuz we didn't
1647                 // find at least one number.  Packet is
1648                 // badly formatted.
1649                 return;
1650               }
1651               else if (!is_num_chr(temp_suffix[1]))
1652               {
1653                 temp_suffix[1] = '\0';
1654                 ptr++;
1655               }
1656               else if (!is_num_chr(temp_suffix[2]))
1657               {
1658                 temp_suffix[2] = '\0';
1659                 ptr+=2;
1660               }
1661               else
1662               {
1663                 temp_suffix[3] = '\0';
1664                 ptr+=3;
1665               }
1666 
1667               // temp_suffix should now contain something like
1668               // "039" or "45" or "2".  Add leading zeroes to give
1669               // "suffix" a length of 3.
1670               xastir_snprintf(suffix,
1671                               sizeof(suffix),
1672                               "000");
1673               switch (strlen(temp_suffix))
1674               {
1675                 case 1: // Copy one char across
1676                   suffix[2] = temp_suffix[0];
1677                   break;
1678                 case 2: // Copy two chars across
1679                   suffix[1] = temp_suffix[0];
1680                   suffix[2] = temp_suffix[1];
1681                   break;
1682                 case 3: // Copy all three chars across
1683                   xastir_snprintf(suffix,
1684                                   sizeof(suffix),
1685                                   "%s",
1686                                   temp_suffix);
1687                   break;
1688               }
1689 
1690               if (debug_level & 2)
1691               {
1692                 fprintf(stderr,"3Zone:%s%s\n",prefix,suffix);
1693               }
1694 
1695               // And another zone...
1696               // Add it to our zone string.  In this
1697               // case we know that the lengths of the
1698               // strings we're working with are quite
1699               // short.  Little danger of overrunning
1700               // our destination string.
1701               strncat(uncompressed_wx,
1702                       ",",
1703                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1704               strncat(uncompressed_wx,
1705                       prefix,
1706                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1707               strncat(uncompressed_wx,
1708                       suffix,
1709                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1710               // Terminate it every time
1711               uncompressed_wx[9999] = '\0';
1712             }
1713             else
1714             {
1715               // New prefix (not a number)
1716               // Start at the top of the outer loop again
1717             }
1718           }
1719         }
1720         // Skip past '-' character, if any, so that we can
1721         // get to the next prefix
1722         // RZG:Added the ptr check, so we don't read a byte off the end
1723         if ( (ptr < (compressed_wx + strlen(compressed_wx))) && (ptr[0] == '-') )
1724         {
1725           ptr++;
1726         }
1727       }
1728 
1729       if (debug_level & 2)
1730       {
1731         fprintf(stderr,"Uncompressed: %s\n", uncompressed_wx);
1732       }
1733     }
1734     /////////////////////////////////////////////////////////////////////
1735     // End of compressed weather alert special code
1736     /////////////////////////////////////////////////////////////////////
1737 
1738     // Australian Buerau of Meeorology alerts (BOM)
1739     // Geoff VK2XJG
1740 
1741     // Check for "BOM_" in the call_sign field.
1742     // WXSVR-AU delivers messages in this format, keeping the protocol as close to
1743     // the NWS WXSVR as possible.
1744     // Underline signifies compressed alert format.  Dash signifies
1745     // non-compressed format, although this has not been implemented on the server.
1746 
1747     if (       (strncmp(fill->call_sign,"BOM_",4) == 0)
1748                || (strncmp(fill->call_sign,"BOM-",4) == 0) )
1749     {
1750 
1751       char compressed_wx[512];
1752       char *ptr;
1753 
1754       /////////////////////////////////////////////////////////////////////
1755       // Compressed weather alert (BOM) special code
1756       /////////////////////////////////////////////////////////////////////
1757 
1758       compressed_wx_packet++; // Set the flag
1759 
1760       //fprintf(stderr, "Found compressed alert packet via BOM_!\n");
1761 
1762       //fprintf(stderr,"Compressed Weather Alert:%s\n",fill->message_line);
1763       //fprintf(stderr,"Compressed alerts are not fully implemented yet.\n");
1764 
1765       // Create a new weather alert for each of these and then
1766       // call this function on each one?  Seems like it might
1767       // work fine if we watch out for global variables.
1768       // Another method would be to create an incoming message
1769       // for each one and add it to the message queue, or just
1770       // a really long new message and add it to the queue,
1771       // in which case we'd exit from this routine as soon as
1772       // it was submitted.
1773       ret = sscanf(fill->message_line, "%20[^,],%20[^,],%255[^, ]",
1774                    entry.activity,
1775                    entry.alert_tag,
1776                    compressed_wx);     // Stick the long string in here
1777 
1778       if (ret != 3)
1779       {
1780         fprintf(stderr,"sscanf parsed %d/3 values in alert.c\n", ret);
1781         compressed_wx[0] = '\0';  // Remove stale compressed alerts.
1782         compressed_wx_packet = 0; //Clear flag in error condition.
1783       }
1784 
1785       compressed_wx[255] = '\0';
1786 
1787       //fprintf(stderr,"Line:%s\n",compressed_wx);
1788 
1789       // Snag alpha characters (should be five) at the start
1790       // of the string.  Use those until we hit more alpha
1791       // characters.  First two/three characters of each 5-letter
1792       // alpha group are the state, last two characters are the
1793       // zone/county/marine-zone indicator.
1794 
1795       // Need to be very careful here to validate the letters/numbers, and
1796       // to not run off the end of the string.  Need more code here to do
1797       // this validation.
1798 
1799 
1800       // Scan through entire string
1801       ptr = compressed_wx;
1802       while (ptr < (compressed_wx + strlen(compressed_wx)))
1803       {
1804         char prefix[7];
1805         char suffix[4];
1806         char temp_suffix[4];
1807         char ending[4];
1808         int iterations = 0;
1809 
1810 
1811         // Snag the ALPHA portion
1812         xastir_snprintf(prefix,
1813                         sizeof(prefix),
1814                         "%s",
1815                         ptr);
1816         ptr += 3;
1817         // Handle a 2 letter state abbreviation (SA/WA/NT)
1818         if (prefix[2] == '_' )
1819         {
1820           prefix[3] = ptr[0];
1821           prefix[5] = '\0';   // Terminate the string
1822           ptr += 3;
1823         }
1824         // All other cases are 3 letter states (NSW/VIC/TAS/QLD)
1825         else
1826         {
1827           prefix[3] = ptr[0];
1828           prefix[6] = '\0';   // Terminate the string
1829           ptr += 3;
1830         }
1831 
1832         // prefix should now contain something like "TAS_CW" or "SA_PW"
1833 
1834         // Snag the NUMERIC portion.  Note that the field
1835         // width can vary between 1 and 3.  The leading
1836         // zeroes have been removed.
1837         xastir_snprintf(temp_suffix,
1838                         sizeof(temp_suffix),
1839                         "%s",
1840                         ptr);
1841 
1842         temp_suffix[3] = '\0';   // Terminate the string
1843         if (temp_suffix[1] == '-' || temp_suffix[1] == '>')
1844         {
1845           temp_suffix[1] = '\0';
1846           ptr += 1;
1847         }
1848         else if (temp_suffix[1] != '\0' &&
1849                  (temp_suffix[2] == '-' || temp_suffix[2] == '>'))
1850         {
1851           temp_suffix[2] = '\0';
1852           ptr += 2;
1853         }
1854         else
1855         {
1856           ptr += 3;
1857         }
1858 
1859         // temp_suffix should now contain something like
1860         // "039" or "45" or "2".  Add leading zeroes to give
1861         // "suffix" a length of 3.
1862         xastir_snprintf(suffix,
1863                         sizeof(suffix),
1864                         "000");
1865         switch (strlen(temp_suffix))
1866         {
1867           case 1: // Copy one char across
1868             suffix[2] = temp_suffix[0];
1869             break;
1870           case 2: // Copy two chars across
1871             suffix[1] = temp_suffix[0];
1872             suffix[2] = temp_suffix[1];
1873             break;
1874           case 3: // Copy all three chars across
1875             xastir_snprintf(suffix,
1876                             sizeof(suffix),
1877                             "%s",
1878                             temp_suffix);
1879             break;
1880         }
1881         // Make sure suffix is terminated properly
1882         suffix[3] = '\0';
1883 
1884         // We have our first zone (of this loop) extracted!
1885         if (debug_level & 2)
1886         {
1887           fprintf(stderr,"1Zone:%s%s\n",prefix,suffix);
1888         }
1889 
1890         // Add it to our zone string.  In this case we know
1891         // that the lengths of the strings we're working
1892         // with are quite short.  Little danger of
1893         // overrunning our destination string.
1894         strncat(uncompressed_wx,
1895                 ",",
1896                 sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1897         strncat(uncompressed_wx,
1898                 prefix,
1899                 sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1900         strncat(uncompressed_wx,
1901                 suffix,
1902                 sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
1903         // Terminate it every time
1904         uncompressed_wx[9999] = '\0';
1905 
1906         if (debug_level & 2)
1907         {
1908           fprintf(stderr,"uncompressed_wx:%s\n",uncompressed_wx);
1909         }
1910 
1911         // Here we keep looping until we hit another alpha
1912         // portion.  We need to look at the field separator
1913         // to determine whether we have another separate
1914         // field coming up or a range to enumerate.
1915         while ( (ptr < (compressed_wx + strlen(compressed_wx)))
1916                 && ( is_num_chr(ptr[1]) ) )
1917         {
1918 
1919           iterations++;
1920 
1921           // Break out of this loop if we don't find an
1922           // alpha character fairly quickly.  That way the
1923           // Xastir main thread can't hang in this loop
1924           // forever if the input string is malformed.
1925           if (iterations > 30)
1926           {
1927             break;
1928           }
1929 
1930           // Look for '>' or '-' character.  If former, we
1931           // have a numeric sequence to ennumerate.  If the
1932           // latter, we either have another zone number or
1933           // another prefix coming up.
1934           if (ptr[0] == '>' || ptr[0] == '<')
1935           {
1936             // Numeric zone sequence
1937             int start_number;
1938             int end_number;
1939             int kk;
1940 
1941 
1942             ptr++;  // Skip past the '>' or '<' characters
1943 
1944             // Snag the NUMERIC portion.  May be between
1945             // 1 and three digits long.
1946             xastir_snprintf(ending,
1947                             sizeof(ending),
1948                             "%s",
1949                             ptr);
1950 
1951             // Terminate the string and advance the
1952             // pointer past it.
1953             if (!is_num_chr(ending[0]))
1954             {
1955               // We have a problem, 'cuz we didn't
1956               // find at least one number.  Packet is
1957               // badly formatted.
1958               return;
1959             }
1960             else if (!is_num_chr(ending[1]))
1961             {
1962               ending[1] = '\0';
1963               ptr++;
1964             }
1965             else if (!is_num_chr(ending[2]))
1966             {
1967               ending[2] = '\0';
1968               ptr+=2;
1969             }
1970             else
1971             {
1972               ending[3] = '\0';
1973               ptr+=3;
1974             }
1975 
1976             // ending should now contain something like
1977             // "046" or "35" or "2"
1978             if (debug_level & 2)
1979             {
1980               fprintf(stderr,"Ending:%s\n",ending);
1981             }
1982 
1983             start_number = (int)atoi(suffix);
1984             end_number = (int)atoi(ending);
1985             for ( kk=start_number+1; kk<=end_number; kk++)
1986             {
1987               xastir_snprintf(suffix,4,"%03d",kk);
1988 
1989               if (debug_level & 2)
1990               {
1991                 fprintf(stderr,"2Zone:%s%s\n",prefix,suffix);
1992               }
1993 
1994               // And another zone... Ennumerate
1995               // through the sequence, adding each
1996               // new zone to our zone string.  In this
1997               // case we know that the lengths of the
1998               // strings we're working with are quite
1999               // short.  Little danger of overrunning
2000               // our destination string.
2001               strncat(uncompressed_wx,
2002                       ",",
2003                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
2004               strncat(uncompressed_wx,
2005                       prefix,
2006                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
2007               strncat(uncompressed_wx,
2008                       suffix,
2009                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
2010               // Terminate it every time
2011               uncompressed_wx[9999] = '\0';
2012             }
2013           }
2014 
2015           // Wasn't a '>' character, so check for a '-'
2016           else if (ptr[0] == '-')
2017           {
2018             // New zone number, not a numeric sequence.
2019 
2020             ptr++;  // Skip past the '-' character
2021 
2022             if ( is_num_chr(ptr[0]) )
2023             {
2024               // Found another number.  Use the prefix
2025               // stored from last time.
2026 
2027               // Snag the NUMERIC portion.  Note that the field
2028               // width can vary between 1 and 3.  The leading
2029               // zeroes have been removed.
2030               xastir_snprintf(temp_suffix,
2031                               sizeof(temp_suffix),
2032                               "%s",
2033                               ptr);
2034 
2035               // Terminate the string and advance the
2036               // pointer past it.
2037               if (!is_num_chr(temp_suffix[0]))
2038               {
2039                 // We have a problem, 'cuz we didn't
2040                 // find at least one number.  Packet is
2041                 // badly formatted.
2042                 return;
2043               }
2044               else if (!is_num_chr(temp_suffix[1]))
2045               {
2046                 temp_suffix[1] = '\0';
2047                 ptr++;
2048               }
2049               else if (!is_num_chr(temp_suffix[2]))
2050               {
2051                 temp_suffix[2] = '\0';
2052                 ptr+=2;
2053               }
2054               else
2055               {
2056                 temp_suffix[3] = '\0';
2057                 ptr+=3;
2058               }
2059 
2060               // temp_suffix should now contain something like
2061               // "039" or "45" or "2".  Add leading zeroes to give
2062               // "suffix" a length of 3.
2063               xastir_snprintf(suffix,
2064                               sizeof(suffix),
2065                               "000");
2066               switch (strlen(temp_suffix))
2067               {
2068                 case 1: // Copy one char across
2069                   suffix[2] = temp_suffix[0];
2070                   break;
2071                 case 2: // Copy two chars across
2072                   suffix[1] = temp_suffix[0];
2073                   suffix[2] = temp_suffix[1];
2074                   break;
2075                 case 3: // Copy all three chars across
2076                   xastir_snprintf(suffix,
2077                                   sizeof(suffix),
2078                                   "%s",
2079                                   temp_suffix);
2080                   break;
2081               }
2082 
2083               if (debug_level & 2)
2084               {
2085                 fprintf(stderr,"3Zone:%s%s\n",prefix,suffix);
2086               }
2087 
2088               // And another zone...
2089               // Add it to our zone string.  In this
2090               // case we know that the lengths of the
2091               // strings we're working with are quite
2092               // short.  Little danger of overrunning
2093               // our destination string.
2094               strncat(uncompressed_wx,
2095                       ",",
2096                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
2097               strncat(uncompressed_wx,
2098                       prefix,
2099                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
2100               strncat(uncompressed_wx,
2101                       suffix,
2102                       sizeof(uncompressed_wx) - 1 - strlen(uncompressed_wx));
2103               // Terminate it every time
2104               uncompressed_wx[9999] = '\0';
2105             }
2106             else
2107             {
2108               // New prefix (not a number)
2109               // Start at the top of the outer loop again
2110             }
2111           }
2112         }
2113         // Skip past '-' character, if any, so that we can
2114         // get to the next prefix
2115         // RZG:Added the ptr check, so we don't read a byte off the end
2116         if ( (ptr < (compressed_wx + strlen(compressed_wx))) && (ptr[0] == '-') )
2117         {
2118           ptr++;
2119         }
2120       }
2121 
2122       if (debug_level & 2)
2123       {
2124         fprintf(stderr,"Uncompressed: %s\n", uncompressed_wx);
2125       }
2126     }
2127     /////////////////////////////////////////////////////////////////////
2128     // End of compressed weather (BOM) alert special code
2129     /////////////////////////////////////////////////////////////////////
2130 
2131 
2132 
2133     if (debug_level & 2)
2134     {
2135       fprintf(stderr,"3\n");
2136     }
2137 
2138     // Terminate the strings
2139     entry.activity[20] = entry.alert_tag[20] = '\0';
2140 
2141     // If the expire time is missing, shift fields to the right
2142     // by one field.  Evidently we can have an alert come across
2143     // that doesn't have an expire time.  The code shuffles the
2144     // titles to the next record before fixing up the title and
2145     // alert_tag for entry.
2146     if (!isdigit((int)entry.activity[0]) && entry.activity[0] != '-')
2147     {
2148 
2149       if (title[0][0] == '\0')
2150       {
2151         // No alerts in this message
2152       }
2153 
2154       // If it's a trashed packet, we may have entries here
2155       // that are too long.  Assure that we don't overwrite
2156       // the strings.
2157       for (jj = 4; jj > 0; jj--)
2158       {
2159         xastir_snprintf(&title[jj][0],
2160                         TITLE_SIZE,
2161                         "%s",
2162                         &title[jj-1][0]);
2163       }
2164 
2165       xastir_snprintf(&title[0][0],
2166                       TITLE_SIZE,
2167                       "%s",
2168                       entry.alert_tag);
2169 
2170       xastir_snprintf(entry.alert_tag,
2171                       sizeof(entry.alert_tag),
2172                       "%s",
2173                       entry.activity);
2174       entry.alert_tag[20] = '\0';
2175 
2176       // Shouldn't we clear out entry.activity in this
2177       // case???  We've determined it's not a date/time value.
2178       xastir_snprintf(entry.activity,sizeof(entry.activity),"------z");
2179       entry.expiration = sec_now() + (24 * 60 * 60);   // Add a day
2180     }
2181     else
2182     {
2183       // Compute expiration time_t from zulu time
2184       entry.expiration = time_from_aprsstring(entry.activity);
2185     }
2186 
2187     if (debug_level & 2)
2188     {
2189       fprintf(stderr,"4\n");
2190     }
2191 
2192     // Copy the sequence (which contains issue_date_time and
2193     // message sequence) into the record.
2194     memcpy(entry.seq, fill->seq, sizeof(entry.seq));
2195     entry.seq[sizeof(entry.seq)-1] = '\0';  // Terminate string
2196 
2197     if (debug_level & 2)
2198     {
2199       fprintf(stderr,"5\n");
2200     }
2201 
2202     // Now compute issue_date_time from the first three characters of
2203     // the sequence number:
2204     // 0-9   = 0-9
2205     // 10-35 = A-Z
2206     // 36-61 = a-z
2207     // The 3 characters are Day/Hour/Minute of the issue date time in
2208     // zulu time.
2209     if (strlen(fill->seq) == 5)
2210     {
2211       // Looks ok so far
2212       // Could add another check to make sure that the first two
2213       // chars are a digit or a capital letter.
2214       char c;
2215       char date_time[10];
2216       char temp[3];
2217 
2218 
2219       date_time[0] = '\0';
2220       for ( ii = 0; ii < 3; ii++ )
2221       {
2222         c = fill->seq[ii];   // Snag one character
2223 
2224         if (is_num_chr(c))
2225         {
2226           // Found numeric char
2227           temp[0] = '0';
2228           temp[1] = c;
2229           temp[2] = '\0'; // Terminate the string
2230         }
2231 
2232         else if (c >= 'A' && c <= 'Z')
2233         {
2234           // Found upper-case letter
2235           // Need to take ord(c) - 55 to get the number
2236           char temp_string[5];
2237           xastir_snprintf(temp_string, sizeof(temp_string), "%02d", (int)c - 55);
2238           memcpy(temp, temp_string, 2);
2239           temp[2] = '\0'; // Terminate the string
2240         }
2241 
2242         else if (c >= 'a' && c <= 'z')
2243         {
2244           // Found lower-case letter
2245           // Need to take ord(c) - 61 to get the number
2246           char temp_string[5];
2247           xastir_snprintf(temp_string, sizeof(temp_string), "%02d", (int)c - 61);
2248           memcpy(temp, temp_string, 2);
2249           temp[2] = '\0'; // Terminate the string
2250         }
2251 
2252         strncat(date_time,temp,sizeof(date_time)-strlen(date_time)-1);  // Concatenate the strings
2253       }
2254       strncat(date_time,"z",sizeof(date_time)-strlen(date_time)-1);   // Add a 'z' on the end.
2255 
2256       if (debug_level & 2)
2257       {
2258         fprintf(stderr,"Seq: %s,\tIssue_time: %s\n",fill->seq,date_time);
2259       }
2260 
2261       xastir_snprintf(entry.issue_date_time,
2262                       sizeof(entry.issue_date_time),
2263                       "%s",
2264                       date_time);
2265       //entry.issue_date_time = time_from_aprsstring(date_time);
2266     }
2267     else
2268     {
2269       xastir_snprintf(entry.issue_date_time,
2270                       sizeof(entry.issue_date_time),
2271                       "%s",
2272                       "312359z");
2273     }
2274 
2275     if (debug_level & 2)
2276     {
2277       fprintf(stderr,"6\n");
2278     }
2279 
2280 
2281     ///////////////////////////////////////////////////////////////////////
2282     ///////////////////////////////////////////////////////////////////////
2283     ///////////////////////////////////////////////////////////////////////
2284     ///////////////////////////////////////////////////////////////////////
2285 
2286 
2287     // Iterate through up to five uncompressed alerts, or
2288     // through the string of now-uncompressed "compressed"
2289     // alerts, creating an alert out of each.
2290     //
2291     if (compressed_wx_packet)
2292     {
2293       // Handle compressed packet.
2294       // Skip the first character of our uncompressed_wx
2295       // string, as it's a leading comma.  Snag out each
2296       // string in turn and use that as the title for a
2297       // weather alert.
2298 
2299       // Feed &uncompressed_wx[1] to split_string to fill in
2300       // an array with the various zone names.
2301       split_string(&uncompressed_wx[1],
2302                    title_ptr,
2303                    MAX_SUB_ALERTS,
2304                    ',');
2305     }
2306     else
2307     {
2308       // Handle non-compressed packet
2309       // We have up to five alerts to process.
2310 
2311       // Set up an array of char pointers so that we can use
2312       // the same code for either compressed or uncompressed
2313       // weather alerts.
2314       title_ptr[0] = &title[0][0];
2315       title_ptr[1] = &title[1][0];
2316       title_ptr[2] = &title[2][0];
2317       title_ptr[3] = &title[3][0];
2318       title_ptr[4] = &title[4][0];
2319       title_ptr[5] = NULL;    // Make sure we terminate
2320     }
2321 
2322     // We now have all of our titles pointed to by the title_ptr[]
2323     // array.  Either type of alert can be processed identically now.
2324 
2325 
2326     // Try to create alerts out of each one.
2327 
2328     for (ii = 0; ii < MAX_SUB_ALERTS && title_ptr[ii]; ii++)
2329     {
2330 
2331       // Copy into our entry.title variable
2332       xastir_snprintf(entry.title,
2333                       sizeof(entry.title),
2334                       "%s",
2335                       title_ptr[ii]);
2336 
2337       // Terminate title string
2338       entry.title[sizeof(entry.title)-1] = '\0';
2339       //fprintf(stderr,"Title: %s\n",entry.title);
2340 
2341       // This one removes spaces from the title.
2342       //while ((ptr = strpbrk(entry.title, " ")))
2343       //    memmove(ptr, ptr+1, strlen(ptr)+1);
2344 
2345       // Instead we should blank out the title and any
2346       // following alert titles if a space is encountered, as
2347       // we're to disregard anything after a space in the
2348       // information field.
2349       if (ignore_title)   // Blank out title if flag is set
2350       {
2351         entry.title[0] = '\0';
2352       }
2353 
2354       // If we found a space in a title, this signifies that
2355       // we hit the end of the current list of zones.
2356       if ( (ptr = strpbrk(entry.title, " ")) )
2357       {
2358         ignore_title++;     // Set flag for following titles
2359         entry.title[0] = '\0';  // Blank out title
2360       }
2361 
2362       if ((ptr = strpbrk(entry.title, "}>=!:/*+;")))
2363       {
2364         if (debug_level & 2)
2365         {
2366           fprintf(stderr,
2367                   "Warning: Weird Weather Message: %ld:%s>%s:%s!\n",
2368                   (long)fill->sec_heard,
2369                   fill->from_call_sign,
2370                   fill->call_sign,
2371                   fill->message_line);
2372         }
2373         *ptr = '\0';
2374       }
2375 
2376       // Skip loop iterations if we don't have a title for an
2377       // entry
2378       if (entry.title[0] == '\0')
2379       {
2380         continue;
2381       }
2382 
2383       xastir_snprintf(entry.from,
2384                       sizeof(entry.from),
2385                       "%s",
2386                       fill->from_call_sign);
2387       xastir_snprintf(entry.to,
2388                       sizeof(entry.to),
2389                       "%s",
2390                       fill->call_sign);
2391       memcpy(entry.seq, fill->seq, sizeof(entry.seq));
2392       entry.seq[sizeof(entry.seq)-1] = '\0';  // Terminate string
2393 
2394       // NWS_ADVIS or NWS_CANCL normally appear in the "to"
2395       // field.  ADVIS can appear in the alert_tag field on a
2396       // CANCL message though, and we want CANCL to have
2397       // priority.
2398       if (strstr(entry.alert_tag, "CANCL") || strstr(entry.to, "CANCL"))
2399       {
2400         entry.alert_level = 'C';
2401       }
2402       else if (!strncmp(entry.alert_tag, "TEST", 4) || strstr(entry.to, "TEST"))
2403       {
2404         entry.alert_level = 'T';
2405       }
2406       else if (strstr(entry.alert_tag, "WARN") || strstr(entry.to, "WARN"))
2407       {
2408         entry.alert_level = 'R';
2409       }
2410       else if (strstr(entry.alert_tag, "CIVIL") || strstr(entry.to, "CIVIL"))
2411       {
2412         entry.alert_level = 'R';
2413       }
2414       else if (strstr(entry.alert_tag, "WATCH") || strstr(entry.to, "WATCH"))
2415       {
2416         entry.alert_level = 'Y';
2417       }
2418       else if (strstr(entry.alert_tag, "ADVIS") || strstr(entry.to, "ADVIS"))
2419       {
2420         entry.alert_level = 'B';
2421       }
2422       else
2423       {
2424         entry.alert_level = 'G';
2425       }
2426 
2427 
2428       // Kludge for fire zones
2429       if (!strncmp(entry.alert_tag,"RED_FLAG",8))
2430 
2431       {
2432         // Replace "Z" in the zone field with "F"
2433         if (entry.title[3] == 'Z')
2434         {
2435           entry.title[3] = 'F';
2436         }
2437       }
2438       // Look for a similar alert
2439 
2440 
2441       // We need some improvements here.  We compare these fields:
2442       //
2443       // from         SFONPW      SFONPW
2444       // to           NWS-ADVIS   NWS-CANCL
2445       // alert_tag    WIND        WIND_ADVIS_CANCEL
2446       // title        CA_Z007     CA_Z007
2447       //
2448       // Of these, "from" and "title" should remain the same between an
2449       // alert and a cancelled alert.  "to" and "alert_tag" change.  Since
2450       // we're comparing all four fields, the cancels don't match any
2451       // existing alerts.
2452 
2453 
2454       //WE7U
2455       // Fill in the unique_string variable.  We need this for
2456       // our hash code.
2457       alert_fill_unique_string(&entry);
2458 
2459       if ((list_ptr = get_wx_alert_from_hash(entry.unique_string)))
2460       {
2461         //fprintf(stderr,"alert_build_list: found match: %s\n",entry.unique_string);
2462 
2463 
2464         // We found a match!  We probably need to copy some more data across
2465         // between the records:  seq, alert_tag, alert_level, from, to,
2466         // issue_date_time, expiration?
2467         // If it's a CANCL or CANCEL, we need to make sure the cancel
2468         // packet's information is kept and the other's info is tossed, so
2469         // that the alert doesn't get drawn anymore.
2470 
2471         // If we're not trying to replace a cancelled alert with
2472         // a new non-cancelled alert, go ahead and copy the
2473         // fields across.
2474         if ( (list_ptr->alert_level != 'C') // Stored alert is _not_ a CANCEL
2475              || (entry.alert_level == 'C') )
2476         {
2477           // Or new one _is_ a CANCEL
2478           list_ptr->expiration = entry.expiration;
2479           xastir_snprintf(list_ptr->activity,
2480                           sizeof(list_ptr->activity),
2481                           "%s",
2482                           entry.activity);
2483           xastir_snprintf(list_ptr->alert_tag,
2484                           sizeof(list_ptr->alert_tag),
2485                           "%s",
2486                           entry.alert_tag);
2487           list_ptr->alert_level = entry.alert_level;
2488           xastir_snprintf(list_ptr->seq,
2489                           sizeof(list_ptr->seq),
2490                           "%s",
2491                           entry.seq);
2492           xastir_snprintf(list_ptr->from,
2493                           sizeof(list_ptr->from),
2494                           "%s",
2495                           entry.from);
2496           xastir_snprintf(list_ptr->to,
2497                           sizeof(list_ptr->to),
2498                           "%s",
2499                           entry.to);
2500           xastir_snprintf(list_ptr->issue_date_time,
2501                           sizeof(list_ptr->issue_date_time),
2502                           "%s",
2503                           entry.issue_date_time);
2504         }
2505         else
2506         {
2507           // Don't copy the info across, as we'd be making a
2508           // cancelled alert active again if we did.
2509         }
2510       }
2511       else
2512       {
2513         // No similar alert, add a new one to the list
2514         entry.index = -1;    // Haven't found it in a file yet
2515 
2516         (void)alert_add_entry(&entry);
2517       }
2518 
2519       if (alert_active(&entry, ALERT_ALL))
2520       {
2521         // Empty "if" body here?????  LCLINT caught this.
2522       }
2523 
2524     }   // End of for loop
2525 
2526 
2527     // Signify that we're done processing the NWS message
2528     fill->active = RECORD_CLOSED;
2529   }
2530 
2531   if (debug_level & 2)
2532   {
2533     fprintf(stderr,"alert_build_list return 2\n");
2534   }
2535 }
2536 
2537 
2538