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