1 /*
2 * aprsc
3 *
4 * (c) Heikki Hannikainen, OH7LZB <hessu@hes.iki.fi>
5 *
6 * This program is licensed under the BSD license, which can be found
7 * in the file LICENSE.
8 *
9 */
10
11 /*
12 * A simple APRS parser for aprsc. Translated from Ham::APRS::FAP
13 * perl module (by OH2KKU).
14 *
15 * Only needs to get lat/lng out of the packet, other features would
16 * be unnecessary in this application, and slow down the parser.
17 * ... but lets still classify the packet, output filter needs that.
18 *
19 */
20
21 #include <stdio.h>
22 #include <string.h>
23 #include <math.h>
24 #include <ctype.h>
25
26 #include "parse_aprs.h"
27 #include "hlog.h"
28 #include "filter.h"
29 #include "historydb.h"
30 #include "incoming.h"
31
32 //#define DEBUG_PARSE_APRS 1
33 #ifdef DEBUG_PARSE_APRS
34 #define DEBUG_LOG(...) \
35 do { hlog(LOG_DEBUG, __VA_ARGS__); } while (0)
36 #else
37 #define DEBUG_LOG(...) { }
38 #endif
39
40 /*
41 * Check if the given character is a valid symbol table identifier
42 * or an overlay character. The set is different for compressed
43 * and uncompressed packets - the former has the overlaid number (0-9)
44 * replaced with n-j.
45 */
46
valid_sym_table_compressed(char c)47 static int valid_sym_table_compressed(char c)
48 {
49 return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A)
50 || (c >= 0x61 && c <= 0x6A)); /* [\/\\A-Za-j] */
51 }
52
valid_sym_table_uncompressed(char c)53 static int valid_sym_table_uncompressed(char c)
54 {
55 return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A)
56 || (c >= 0x30 && c <= 0x39)); /* [\/\\A-Z0-9] */
57 }
58
59 /*
60 * Fill the pbuf_t structure with a parsed position and
61 * symbol table & code. Also does range checking for lat/lng
62 * and pre-calculates cosf(lat) for range filters.
63 */
64
pbuf_fill_pos(struct pbuf_t * pb,const float lat,const float lng,const char sym_table,const char sym_code)65 static int pbuf_fill_pos(struct pbuf_t *pb, const float lat, const float lng, const char sym_table, const char sym_code)
66 {
67 int bad = 0;
68 /* symbol table and code */
69 pb->symbol[0] = sym_table;
70 pb->symbol[1] = sym_code;
71 pb->symbol[2] = 0;
72
73 /* Is it perhaps a weather report ? Allow symbol overlays, too. */
74 if (sym_code == '_' && valid_sym_table_uncompressed(sym_table))
75 pb->packettype |= T_WX;
76 if (sym_code == '@' && valid_sym_table_uncompressed(sym_table))
77 pb->packettype |= T_WX; /* Hurricane */
78
79 bad |= (lat < -89.9 && -0.0001 <= lng && lng <= 0.0001);
80 bad |= (lat > 89.9 && -0.0001 <= lng && lng <= 0.0001);
81
82 if (-0.0001 <= lat && lat <= 0.0001) {
83 bad |= ( -0.0001 <= lng && lng <= 0.0001);
84 bad |= ( -90.01 <= lng && lng <= -89.99);
85 bad |= ( 89.99 <= lng && lng <= 90.01);
86 }
87
88
89 if (bad || lat < -90.0 || lat > 90.0 || lng < -180.0 || lng > 180.0) {
90 DEBUG_LOG("\tposition out of range: lat %.5f lng %.5f", lat, lng);
91 return 0; /* out of range */
92 }
93
94 DEBUG_LOG("\tposition ok: lat %.5f lng %.5f", lat, lng);
95
96 /* Pre-calculations for A/R/F/M-filter tests */
97 pb->lat = filter_lat2rad(lat); /* deg-to-radians */
98 pb->cos_lat = cosf(pb->lat); /* used in range filters */
99 pb->lng = filter_lon2rad(lng); /* deg-to-radians */
100
101 pb->flags |= F_HASPOS; /* the packet has positional data */
102
103 return 1;
104 }
105
106 /*
107 * Parse symbol from destination callsign
108 */
109
get_symbol_from_dstcall_twochar(const char c1,const char c2,char * sym_table,char * sym_code)110 static int get_symbol_from_dstcall_twochar(const char c1, const char c2, char *sym_table, char *sym_code)
111 {
112 //DEBUG_LOG("\ttwochar %c %c", c1, c2);
113 if (c1 == 'B') {
114 if (c2 >= 'B' && c2 <= 'P') {
115 *sym_table = '/';
116 *sym_code = c2 - 'B' + '!';
117 return 1;
118 }
119 return 0;
120 }
121
122 if (c1 == 'P') {
123 if (c2 >= '0' && c2 <= '9') {
124 *sym_table = '/';
125 *sym_code = c2;
126 return 1;
127 }
128 if (c2 >= 'A' && c2 <= 'Z') {
129 *sym_table = '/';
130 *sym_code = c2;
131 return 1;
132 }
133 return 0;
134 }
135
136 if (c1 == 'M') {
137 if (c2 >= 'R' && c2 <= 'X') {
138 *sym_table = '/';
139 *sym_code = c2 - 'R' + ':';
140 return 1;
141 }
142 return 0;
143 }
144
145 if (c1 == 'H') {
146 if (c2 >= 'S' && c2 <= 'X') {
147 *sym_table = '/';
148 *sym_code = c2 - 'S' + '[';
149 return 1;
150 }
151 return 0;
152 }
153
154 if (c1 == 'L') {
155 if (c2 >= 'A' && c2 <= 'Z') {
156 *sym_table = '/';
157 *sym_code = c2 - 'A' + 'a';
158 return 1;
159 }
160 return 0;
161 }
162
163 if (c1 == 'J') {
164 if (c2 >= '1' && c2 <= '4') {
165 *sym_table = '/';
166 *sym_code = c2 - '1' + '{';
167 return 1;
168 }
169 return 0;
170 }
171
172 if (c1 == 'O') {
173 if (c2 >= 'B' && c2 <= 'P') {
174 *sym_table = '\\';
175 *sym_code = c2 - 'B' + '!';
176 return 1;
177 }
178 return 0;
179 }
180
181 if (c1 == 'A') {
182 if (c2 >= '0' && c2 <= '9') {
183 *sym_table = '\\';
184 *sym_code = c2;
185 return 1;
186 }
187 if (c2 >= 'A' && c2 <= 'Z') {
188 *sym_table = '\\';
189 *sym_code = c2;
190 return 1;
191 }
192 return 0;
193 }
194
195 if (c1 == 'N') {
196 if (c2 >= 'R' && c2 <= 'X') {
197 *sym_table = '\\';
198 *sym_code = c2 - 'R' + ':';
199 return 1;
200 }
201 return 0;
202 }
203
204 if (c1 == 'D') {
205 if (c2 >= 'S' && c2 <= 'X') {
206 *sym_table = '\\';
207 *sym_code = c2 - 'S' + '[';
208 return 1;
209 }
210 return 0;
211 }
212
213 if (c1 == 'S') {
214 if (c2 >= 'A' && c2 <= 'Z') {
215 *sym_table = '\\';
216 *sym_code = c2 - 'A' + 'a';
217 return 1;
218 }
219 return 0;
220 }
221
222 if (c1 == 'Q') {
223 if (c2 >= '1' && c2 <= '4') {
224 *sym_table = '\\';
225 *sym_code = c2 - '1' + '{';
226 return 1;
227 }
228 return 0;
229 }
230
231 return 0;
232 }
233
get_symbol_from_dstcall(struct pbuf_t * pb,char * sym_table,char * sym_code)234 static int get_symbol_from_dstcall(struct pbuf_t *pb, char *sym_table, char *sym_code)
235 {
236 const char *d_start;
237 char type;
238 char overlay;
239 int sublength;
240 int numberid;
241
242 /* check that the destination call exists and is of the right size for symbol */
243 d_start = pb->srccall_end+1;
244 if (pb->dstcall_end_or_ssid - d_start < 5)
245 return 0; /* too short */
246
247 /* length of the parsed string */
248 sublength = pb->dstcall_end_or_ssid - d_start - 3;
249 if (sublength > 3)
250 sublength = 3;
251
252 DEBUG_LOG("get_symbol_from_dstcall: %.*s (%d)", (int)(pb->dstcall_end_or_ssid - d_start), d_start, sublength);
253
254 if (strncmp(d_start, "GPS", 3) != 0 && strncmp(d_start, "SPC", 3) != 0 && strncmp(d_start, "SYM", 3) != 0)
255 return 0;
256
257 // DEBUG_LOG("\ttesting %c %c %c", d_start[3], d_start[4], d_start[5]);
258 if (!isalnum(d_start[3]) || !isalnum(d_start[4]))
259 return 0;
260
261 if (sublength == 3 && !isalnum(d_start[5]))
262 return 0;
263
264 type = d_start[3];
265
266 if (sublength == 3) {
267 if (type == 'C' || type == 'E') {
268 if (!isdigit(d_start[4]))
269 return 0;
270 if (!isdigit(d_start[5]))
271 return 0;
272 numberid = (d_start[4] - 48) * 10 + (d_start[5] - 48);
273
274 *sym_code = numberid + 32;
275 if (type == 'C')
276 *sym_table = '/';
277 else
278 *sym_table = '\\';
279
280 DEBUG_LOG("\tnumeric symbol id in dstcall: %.*s: table %c code %c",
281 (int)(pb->dstcall_end_or_ssid - d_start - 3), d_start + 3, *sym_table, *sym_code);
282
283 return 1;
284 } else {
285 /* secondary symbol table, with overlay
286 * Check first that we really are in the secondary symbol table
287 */
288 overlay = d_start[5];
289 if ((type == 'O' || type == 'A' || type == 'N' ||
290 type == 'D' || type == 'S' || type == 'Q')
291 && isalnum(overlay)) {
292 return get_symbol_from_dstcall_twochar(d_start[3], d_start[4], sym_table, sym_code);
293 }
294 return 0;
295 }
296 } else {
297 // primary or secondary table, no overlay
298 return get_symbol_from_dstcall_twochar(d_start[3], d_start[4], sym_table, sym_code);
299 }
300
301 return 0;
302 }
303
304 /*
305 * Parse NMEA position packets.
306 */
307
parse_aprs_nmea(struct pbuf_t * pb,const char * body,const char * body_end)308 static int parse_aprs_nmea(struct pbuf_t *pb, const char *body, const char *body_end)
309 {
310 float lat, lng;
311 const char *latp, *lngp;
312 int i, la, lo;
313 char lac, loc;
314 char sym_table = ' ', sym_code = ' ';
315
316 // Parse symbol from destination callsign, first thing before any possible returns
317 get_symbol_from_dstcall(pb, &sym_table, &sym_code);
318
319 DEBUG_LOG("get_symbol_from_dstcall: %.*s => %c%c",
320 (int)(pb->dstcall_end_or_ssid - pb->srccall_end-1), pb->srccall_end+1, sym_table, sym_code);
321
322 if (memcmp(body,"ULT",3) == 0) {
323 /* Ah.. "$ULT..." - that is, Ultimeter 2000 weather instrument */
324 pb->packettype |= T_WX;
325 return 1;
326 }
327
328 lat = lng = 0.0;
329 latp = lngp = NULL;
330
331 /* NMEA sentences to understand:
332 $GPGGA Global Positioning System Fix Data
333 $GPGLL Geographic Position, Latitude/Longitude Data
334 $GPRMC Remommended Minimum Specific GPS/Transit Data
335 $GPWPT Way Point Location ?? (bug in APRS specs ?)
336 $GPWPL Waypoint Load (not in APRS specs, but in NMEA specs)
337 $PNTS Seen on APRS-IS, private sentense based on NMEA..
338 $xxTLL Not seen on radio network, usually $RATLL - Target positions
339 reported by RAdar.
340 */
341
342 if (memcmp(body, "GPGGA,", 6) == 0) {
343 /* GPGGA,175059,3347.4969,N,11805.7319,W,2,12,1.0,6.8,M,-32.1,M,,*7D
344 // v=1, looks fine
345 // GPGGA,000000,5132.038,N,11310.221,W,1,09,0.8,940.0,M,-17.7,,
346 // v=1, timestamp odd, coords look fine
347 // GPGGA,,,,,,0,00,,,,,,,*66
348 // v=0, invalid
349 // GPGGA,121230,4518.7931,N,07322.3202,W,2,08,1.0,40.0,M,-32.4,M,,*46
350 // v=2, looks valid ?
351 // GPGGA,193115.00,3302.50182,N,11651.22581,W,1,08,01.6,00465.90,M,-32.891,M,,*5F
352 // $GPGGA,hhmmss.dd,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,v,
353 // ss,d.d,h.h,M,g.g,M,a.a,xxxx*hh<CR><LF>
354 */
355
356 latp = body+6; // over the keyword
357 while (latp < body_end && *latp != ',')
358 latp++; // scan over the timestamp
359 if (*latp == ',')
360 latp++; // .. and into latitude.
361 lngp = latp;
362 while (lngp < body_end && *lngp != ',')
363 lngp++;
364 if (*lngp == ',')
365 lngp++;
366 if (*lngp != ',')
367 lngp++;
368 if (*lngp == ',')
369 lngp++;
370
371 /* latp, and lngp point to start of latitude and longitude substrings
372 // respectively.
373 */
374
375 } else if (memcmp(body, "GPGLL,", 6) == 0) {
376 /* $GPGLL,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,hhmmss.dd,S,M*hh<CR><LF> */
377 latp = body+6; // over the keyword
378 lngp = latp;
379 while (lngp < body_end && *lngp != ',') // over latitude
380 lngp++;
381 if (*lngp == ',')
382 lngp++; // and lat designator
383 if (*lngp != ',')
384 lngp++; // and lat designator
385 if (*lngp == ',')
386 lngp++;
387 /* latp, and lngp point to start of latitude and longitude substrings
388 // respectively
389 */
390 } else if (memcmp(body, "GPRMC,", 6) == 0) {
391 /* $GPRMC,hhmmss.dd,S,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,s.s,h.h,ddmmyy,d.d, <E|W>,M*hh<CR><LF>
392 // ,S, = Status: 'A' = Valid, 'V' = Invalid
393 //
394 // GPRMC,175050,A,4117.8935,N,10535.0871,W,0.0,324.3,100208,10.0,E,A*3B
395 // GPRMC,000000,V,0000.0000,0,00000.0000,0,000,000,000000,,*01/It wasn't me :)
396 // invalid..
397 // GPRMC,000043,V,4411.7761,N,07927.0448,W,0.000,0.0,290697,10.7,W*57
398 // GPRMC,003803,A,3347.1727,N,11812.7184,W,000.0,000.0,140208,013.7,E*67
399 // GPRMC,050058,A,4609.1143,N,12258.8184,W,0.000,0.0,100208,18.0,E*5B
400 */
401
402 latp = body+6; // over the keyword
403 while (latp < body_end && *latp != ',')
404 latp++; // scan over the timestamp
405 if (*latp == ',')
406 latp++; // .. and into VALIDITY
407 if (*latp != 'A' && *latp != 'V')
408 return 0; // INVALID !
409 if (*latp != ',')
410 latp++;
411 if (*latp == ',')
412 latp++;
413
414 /* now it points to latitude substring */
415 lngp = latp;
416 while (lngp < body_end && *lngp != ',')
417 lngp++;
418
419 if (*lngp == ',')
420 lngp++;
421 if (*lngp != ',')
422 lngp++;
423 if (*lngp == ',')
424 lngp++;
425
426 /* latp, and lngp point to start of latitude and longitude substrings
427 // respectively.
428 */
429
430 } else if (memcmp(body, "GPWPL,", 6) == 0) {
431 /* $GPWPL,4610.586,N,00607.754,E,4*70
432 // $GPWPL,4610.452,N,00607.759,E,5*74
433 */
434 latp = body+6;
435
436 } else if (memcmp(body, "PNTS,1,", 7) == 0) { /* PNTS version 1 */
437 /* $PNTS,1,0,11,01,2002,231932,3539.687,N,13944.480,E,0,000,5,Roppongi UID RELAY,000,1*35
438 // $PNTS,1,0,14,01,2007,131449,3535.182,N,13941.200,E,0,0.0,6,Oota-Ku KissUIDigi,000,1*1D
439 // $PNTS,1,0,17,02,2008,120824,3117.165,N,13036.481,E,49,059,1,Kagoshima,000,1*71
440 // $PNTS,1,0,17,02,2008,120948,3504.283,N,13657.933,E,00,000.0,6,,000,1*36
441 //
442 // From Alinco EJ-41U Terminal Node Controller manual:
443 //
444 // 5-4-7 $PNTS
445 // This is a private-sentence based on NMEA-0183. The data contains date,
446 // time, latitude, longitude, moving speed, direction, altitude plus a short
447 // message, group codes, and icon numbers. The EJ-41U does not analyze this
448 // format but can re-structure it.
449 // The data contains the following information:
450 // l $PNTS Starts the $PNTS sentence
451 // l version
452 // l the registered information. [0]=normal geographical location data.
453 // This is the only data EJ-41U can re-structure. [s]=Initial position
454 // for the course setting [E]=ending position for the course setting
455 // [1]=the course data between initial and ending [P]=the check point
456 // registration [A]=check data when the automatic position transmission
457 // is set OFF [R]=check data when the course data or check point data is
458 // received.
459 // l dd,mm,yyyy,hhmmss: Date and time indication.
460 // l Latitude in DMD followed by N or S
461 // l Longitude in DMD followed by E or W
462 // l Direction: Shown with the number 360 degrees divided by 64.
463 // 00 stands for true north, 16 for east. Speed in Km/h
464 // l One of 15 characters [0] to [9], [A] to [E].
465 // NTSMRK command determines this character when EJ-41U is used.
466 // l A short message up to 20 bites. Use NTSMSG command to determine this message.
467 // l A group code: 3 letters with a combination of [0] to [9], [A] to [Z].
468 // Use NTSGRP command to determine.
469 // l Status: [1] for usable information, [0] for non-usable information.
470 // l *hh<CR><LF> the check-sum and end of PNTS sentence.
471 */
472
473 if (body+55 > body_end) return 0; /* Too short.. */
474 latp = body+7; /* Over the keyword */
475 /* Accept any registered information code */
476 if (*latp++ == ',') return 0;
477 if (*latp++ != ',') return 0;
478 /* Scan over date+time info */
479 while (*latp != ',' && latp <= body_end) ++latp;
480 if (*latp == ',') ++latp;
481 while (*latp != ',' && latp <= body_end) ++latp;
482 if (*latp == ',') ++latp;
483 while (*latp != ',' && latp <= body_end) ++latp;
484 if (*latp == ',') ++latp;
485 while (*latp != ',' && latp <= body_end) ++latp;
486 if (*latp == ',') ++latp;
487 /* now it points to latitude substring */
488 lngp = latp;
489 while (lngp < body_end && *lngp != ',')
490 lngp++;
491
492 if (*lngp == ',')
493 lngp++;
494 if (*lngp != ',')
495 lngp++;
496 if (*lngp == ',')
497 lngp++;
498
499 /* latp, and lngp point to start of latitude and longitude substrings
500 // respectively.
501 */
502 #if 1
503 } else if (memcmp(body, "GPGSA,", 6) == 0 ||
504 memcmp(body, "GPVTG,", 6) == 0 ||
505 memcmp(body, "GPGSV,", 6) == 0) {
506 /* Recognized but ignored */
507 return 1;
508 #endif
509 }
510
511 if (!latp || !lngp) {
512 hlog_packet(LOG_DEBUG, pb->data, pb->packet_len-2, "Unknown NMEA: ");
513 return 0; /* Well.. Not NMEA frame */
514 }
515
516 // DEBUG_LOG("NMEA parsing: %.*s", (int)(body_end - body), body);
517 // DEBUG_LOG(" lat=%.10s lng=%.10s", latp, lngp);
518
519 i = sscanf(latp, "%2d%f,%c,", &la, &lat, &lac);
520 if (i != 3)
521 return 0; // parse failure
522
523 i = sscanf(lngp, "%3d%f,%c,", &lo, &lng, &loc);
524 if (i != 3)
525 return 0; // parse failure
526
527 if (lac != 'N' && lac != 'S' && lac != 'n' && lac != 's')
528 return 0; // bad indicator value
529 if (loc != 'E' && loc != 'W' && loc != 'e' && loc != 'w')
530 return 0; // bad indicator value
531
532 // DEBUG_LOG(" lat: %c %2d %7.4f lng: %c %2d %7.4f",
533 // lac, la, lat, loc, lo, lng);
534
535 lat = (float)la + lat/60.0;
536 lng = (float)lo + lng/60.0;
537
538 if (lac == 'S' || lac == 's')
539 lat = -lat;
540 if (loc == 'W' || loc == 'w')
541 lng = -lng;
542
543 pb->packettype |= T_POSITION;
544
545 return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
546 }
547
parse_aprs_telem(struct pbuf_t * pb,const char * body,const char * body_end)548 static int parse_aprs_telem(struct pbuf_t *pb, const char *body, const char *body_end)
549 {
550 // float lat = 0.0, lng = 0.0;
551
552 DEBUG_LOG("parse_aprs_telem");
553
554 //pbuf_fill_pos(pb, lat, lng, 0, 0);
555 return 0;
556 }
557
558 /*
559 * Parse a MIC-E position packet
560 *
561 * APRS PROTOCOL REFERENCE 1.0.1 Chapter 10, page 42 (52 in PDF)
562 */
563
parse_aprs_mice(struct pbuf_t * pb,const unsigned char * body,const unsigned char * body_end)564 static int parse_aprs_mice(struct pbuf_t *pb, const unsigned char *body, const unsigned char *body_end)
565 {
566 float lat = 0.0, lng = 0.0;
567 unsigned int lat_deg = 0, lat_min = 0, lat_min_frag = 0, lng_deg = 0, lng_min = 0, lng_min_frag = 0;
568 const char *d_start;
569 char dstcall[7];
570 char *p;
571 char sym_table, sym_code;
572 int posambiguity = 0;
573 int i;
574
575 DEBUG_LOG("parse_aprs_mice: %.*s", pb->packet_len-2, pb->data);
576
577 /* check packet length */
578 if (body_end - body < 8)
579 return 0;
580
581 /* check that the destination call exists and is of the right size for mic-e */
582 d_start = pb->srccall_end+1;
583 if (pb->dstcall_end_or_ssid - d_start != 6)
584 return 0; /* eh...? */
585
586 /* validate destination call:
587 * A-K characters are not used in the last 3 characters
588 * and MNO are never used
589 */
590
591 for (i = 0; i < 3; i++)
592 if (!((d_start[i] >= '0' && d_start[i] <= '9')
593 || (d_start[i] >= 'A' && d_start[i] <= 'L')
594 || (d_start[i] >= 'P' && d_start[i] <= 'Z')))
595 return 0;
596
597 for (i = 3; i < 6; i++)
598 if (!((d_start[i] >= '0' && d_start[i] <= '9')
599 || (d_start[i] == 'L')
600 || (d_start[i] >= 'P' && d_start[i] <= 'Z')))
601 return 0;
602
603 DEBUG_LOG("\tpassed dstcall format check");
604
605 /* validate information field (longitude, course, speed and
606 * symbol table and code are checked). Not bullet proof..
607 *
608 * 0 1 23 4 5 6 7
609 * /^[\x26-\x7f][\x26-\x61][\x1c-\x7f]{2}[\x1c-\x7d][\x1c-\x7f][\x21-\x7b\x7d][\/\\A-Z0-9]/
610 */
611 if (body[0] < 0x26 || (unsigned char)body[0] > 0x7f) return 0;
612 if (body[1] < 0x26 || (unsigned char)body[1] > 0x61) return 0;
613 if (body[2] < 0x1c || (unsigned char)body[2] > 0x7f) return 0;
614 if (body[3] < 0x1c || (unsigned char)body[3] > 0x7f) return 0;
615 if (body[4] < 0x1c || (unsigned char)body[4] > 0x7d) return 0;
616 if (body[5] < 0x1c || (unsigned char)body[5] > 0x7f) return 0;
617 if ((body[6] < 0x21 || (unsigned char)body[6] > 0x7b)
618 && (unsigned char)body[6] != 0x7d) return 0;
619 if (!valid_sym_table_uncompressed(body[7])) return 0;
620
621 DEBUG_LOG("\tpassed info format check");
622
623 /* make a local copy, we're going to modify it */
624 strncpy(dstcall, d_start, 6);
625 dstcall[6] = 0;
626
627 /* First do the destination callsign
628 * (latitude, message bits, N/S and W/E indicators and long. offset)
629 *
630 * Translate the characters to get the latitude
631 */
632
633 //fprintf(stderr, "\tuntranslated dstcall: %s\n", dstcall);
634 for (p = dstcall; *p; p++) {
635 if (*p >= 'A' && *p <= 'J')
636 *p -= 'A' - '0';
637 else if (*p >= 'P' && *p <= 'Y')
638 *p -= 'P' - '0';
639 else if (*p == 'K' || *p == 'L' || *p == 'Z')
640 *p = '_';
641 }
642 //fprintf(stderr, "\ttranslated dstcall: %s\n", dstcall);
643
644 /* position ambiquity is going to get ignored now, it's not needed in this application. */
645 if (dstcall[5] == '_') { dstcall[5] = '5'; posambiguity = 1; }
646 if (dstcall[4] == '_') { dstcall[4] = '5'; posambiguity = 2; }
647 if (dstcall[3] == '_') { dstcall[3] = '5'; posambiguity = 3; }
648 if (dstcall[2] == '_') { dstcall[2] = '3'; posambiguity = 4; }
649 if (dstcall[1] == '_' || dstcall[0] == '_') { return 0; } /* cannot use posamb here */
650
651 /* convert to degrees, minutes and decimal degrees, and then to a float lat */
652 if (sscanf(dstcall, "%2u%2u%2u",
653 &lat_deg, &lat_min, &lat_min_frag) != 3) {
654 DEBUG_LOG("\tsscanf failed");
655 return 0;
656 }
657 lat = (float)lat_deg + (float)lat_min / 60.0 + (float)lat_min_frag / 6000.0;
658
659 /* check the north/south direction and correct the latitude if necessary */
660 if (d_start[3] <= 0x4c)
661 lat = 0 - lat;
662
663 /* Decode the longitude, the first three bytes of the body after the data
664 * type indicator. First longitude degrees, remember the longitude offset.
665 */
666 lng_deg = body[0] - 28;
667 if (d_start[4] >= 0x50)
668 lng_deg += 100;
669 if (lng_deg >= 180 && lng_deg <= 189)
670 lng_deg -= 80;
671 else if (lng_deg >= 190 && lng_deg <= 199)
672 lng_deg -= 190;
673
674 /* Decode the longitude minutes */
675 lng_min = body[1] - 28;
676 if (lng_min >= 60)
677 lng_min -= 60;
678
679 /* ... and minute decimals */
680 lng_min_frag = body[2] - 28;
681
682 /* apply position ambiguity to longitude */
683 switch (posambiguity) {
684 case 0:
685 /* use everything */
686 lng = (float)lng_deg + (float)lng_min / 60.0
687 + (float)lng_min_frag / 6000.0;
688 break;
689 case 1:
690 /* ignore last number of lng_min_frag */
691 lng = (float)lng_deg + (float)lng_min / 60.0
692 + (float)(lng_min_frag - lng_min_frag % 10 + 5) / 6000.0;
693 break;
694 case 2:
695 /* ignore lng_min_frag */
696 lng = (float)lng_deg + ((float)lng_min + 0.5) / 60.0;
697 break;
698 case 3:
699 /* ignore lng_min_frag and last number of lng_min */
700 lng = (float)lng_deg + (float)(lng_min - lng_min % 10 + 5) / 60.0;
701 break;
702 case 4:
703 /* minute is unused -> add 0.5 degrees to longitude */
704 lng = (float)lng_deg + 0.5;
705 break;
706 default:
707 return 0;
708 }
709
710 /* check the longitude E/W sign */
711 if (d_start[5] >= 0x50)
712 lng = 0 - lng;
713
714 /* save the symbol table and code */
715 sym_code = body[6];
716 sym_table = body[7];
717
718 /* ok, we're done */
719 /*
720 fprintf(stderr, "\tlat %u %u.%u (%.4f) lng %u %u.%u (%.4f)\n",
721 lat_deg, lat_min, lat_min_frag, lat,
722 lng_deg, lng_min, lng_min_frag, lng);
723 fprintf(stderr, "\tsym '%c' '%c'\n", sym_table, sym_code);
724 */
725
726 return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
727 }
728
729 /*
730 * Parse a compressed APRS position packet
731 *
732 * APRS PROTOCOL REFERENCE 1.0.1 Chapter 9, page 36 (46 in PDF)
733 */
734
parse_aprs_compressed(struct pbuf_t * pb,const char * body,const char * body_end)735 static int parse_aprs_compressed(struct pbuf_t *pb, const char *body, const char *body_end)
736 {
737 char sym_table, sym_code;
738 int i;
739 int lat1, lat2, lat3, lat4, lng1, lng2, lng3, lng4;
740 double lat = 0.0, lng = 0.0;
741
742 DEBUG_LOG("parse_aprs_compressed");
743
744 /* A compressed position is always 13 characters long.
745 * Make sure we get at least 13 characters and that they are ok.
746 * Also check the allowed base-91 characters at the same time.
747 */
748
749 if (body_end - body < 13)
750 return 0; /* too short. */
751
752 sym_table = body[0]; /* has been validated before entering this function */
753 sym_code = body[9];
754
755 /* base-91 check */
756 for (i = 1; i <= 8; i++)
757 if (body[i] < 0x21 || body[i] > 0x7b)
758 return 0;
759
760 // fprintf(stderr, "\tpassed length and format checks, sym %c%c\n", sym_table, sym_code);
761
762 /* decode */
763 lat1 = (body[1] - 33);
764 lat2 = (body[2] - 33);
765 lat3 = (body[3] - 33);
766 lat4 = (body[4] - 33);
767
768 lat1 = ((((lat1 * 91) + lat2) * 91) + lat3) * 91 + lat4;
769
770 lng1 = (body[5] - 33);
771 lng2 = (body[6] - 33);
772 lng3 = (body[7] - 33);
773 lng4 = (body[8] - 33);
774
775 lng1 = ((((lng1 * 91) + lng2) * 91) + lng3) * 91 + lng4;
776
777 /* calculate latitude and longitude */
778
779 lat = 90.0F - ((float)(lat1) / 380926.0F);
780 lng = -180.0F + ((float)(lng1) / 190463.0F);
781
782 return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
783 }
784
785 /*
786 * Parse an uncompressed "normal" APRS packet
787 *
788 * APRS PROTOCOL REFERENCE 1.0.1 Chapter 8, page 32 (42 in PDF)
789 */
790
parse_aprs_uncompressed(struct pbuf_t * pb,const char * body,const char * body_end)791 static int parse_aprs_uncompressed(struct pbuf_t *pb, const char *body, const char *body_end)
792 {
793 char posbuf[20];
794 unsigned int lat_deg = 0, lat_min = 0, lat_min_frag = 0, lng_deg = 0, lng_min = 0, lng_min_frag = 0;
795 float lat, lng;
796 char lat_hemi, lng_hemi;
797 char sym_table, sym_code;
798 int issouth = 0;
799 int iswest = 0;
800
801 DEBUG_LOG("parse_aprs_uncompressed");
802
803 if (body_end - body < 19) {
804 DEBUG_LOG("\ttoo short");
805 return 0;
806 }
807
808 /* make a local copy, so we can overwrite it at will. */
809 memcpy(posbuf, body, 19);
810 posbuf[19] = 0;
811 // fprintf(stderr, "\tposbuf: %s\n", posbuf);
812
813 /* position ambiquity is going to get ignored now, it's not needed in this application. */
814 /* lat */
815 if (posbuf[2] == ' ') posbuf[2] = '3';
816 if (posbuf[3] == ' ') posbuf[3] = '5';
817 if (posbuf[5] == ' ') posbuf[5] = '5';
818 if (posbuf[6] == ' ') posbuf[6] = '5';
819 /* lng */
820 if (posbuf[12] == ' ') posbuf[12] = '3';
821 if (posbuf[13] == ' ') posbuf[13] = '5';
822 if (posbuf[15] == ' ') posbuf[15] = '5';
823 if (posbuf[16] == ' ') posbuf[16] = '5';
824
825 // fprintf(stderr, "\tafter filling amb: %s\n", posbuf);
826 /* 3210.70N/13132.15E# */
827 if (sscanf(posbuf, "%2u%2u.%2u%c%c%3u%2u.%2u%c%c",
828 &lat_deg, &lat_min, &lat_min_frag, &lat_hemi, &sym_table,
829 &lng_deg, &lng_min, &lng_min_frag, &lng_hemi, &sym_code) != 10) {
830 DEBUG_LOG("\tsscanf failed");
831 return 0;
832 }
833
834 if (!valid_sym_table_uncompressed(sym_table))
835 sym_table = 0;
836
837 if (lat_hemi == 'S' || lat_hemi == 's')
838 issouth = 1;
839 else if (lat_hemi != 'N' && lat_hemi != 'n')
840 return 0; /* neither north or south? bail out... */
841
842 if (lng_hemi == 'W' || lng_hemi == 'w')
843 iswest = 1;
844 else if (lng_hemi != 'E' && lng_hemi != 'e')
845 return 0; /* neither west or east? bail out ... */
846
847 if (lat_deg > 89 || lng_deg > 179)
848 return 0; /* too large values for lat/lng degrees */
849
850 lat = (float)lat_deg + (float)lat_min / 60.0 + (float)lat_min_frag / 6000.0;
851 lng = (float)lng_deg + (float)lng_min / 60.0 + (float)lng_min_frag / 6000.0;
852
853 /* Finally apply south/west indicators */
854 if (issouth)
855 lat = 0.0 - lat;
856 if (iswest)
857 lng = 0.0 - lng;
858
859 // fprintf(stderr, "\tlat %u %u.%u %c (%.3f) lng %u %u.%u %c (%.3f)\n",
860 // lat_deg, lat_min, lat_min_frag, (int)lat_hemi, lat,
861 // lng_deg, lng_min, lng_min_frag, (int)lng_hemi, lng);
862 // fprintf(stderr, "\tsym '%c' '%c'\n", sym_table, sym_code);
863
864 return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
865 }
866
867 /*
868 * Parse an APRS object
869 *
870 * APRS PROTOCOL REFERENCE 1.0.1 Chapter 11, page 58 (68 in PDF)
871 */
872
parse_aprs_object(struct pbuf_t * pb,const char * body,const char * body_end)873 static int parse_aprs_object(struct pbuf_t *pb, const char *body, const char *body_end)
874 {
875 int i;
876 int namelen = -1;
877
878 pb->packettype |= T_OBJECT;
879
880 DEBUG_LOG("parse_aprs_object");
881
882 /* check that the object name ends with either * or _ */
883 if (body[9] != '*' && body[9] != '_') {
884 DEBUG_LOG("\tinvalid object kill character");
885 return 0;
886 }
887
888 /* check that the timestamp ends with one of the valid timestamp type IDs */
889 char tz_end = body[16];
890 if (tz_end != 'z' && tz_end != 'h' && tz_end != '/') {
891 DEBUG_LOG("\tinvalid object timestamp type character");
892 return 0;
893 }
894
895 /* check object's name - scan for non-printable characters and the last
896 * non-space character
897 */
898 for (i = 0; i < 9; i++) {
899 if (body[i] < 0x20 || body[i] > 0x7e) {
900 DEBUG_LOG("\tobject name has unprintable characters");
901 return 0; /* non-printable */
902 }
903 if (body[i] != ' ')
904 namelen = i;
905 }
906
907 if (namelen < 0) {
908 DEBUG_LOG("\tobject has empty name");
909 return 0;
910 }
911
912 pb->srcname = body;
913 pb->srcname_len = namelen+1;
914
915 DEBUG_LOG("object name: '%.*s'", pb->srcname_len, pb->srcname);
916
917 /* Forward the location parsing onwards */
918 if (valid_sym_table_compressed(body[17]))
919 return parse_aprs_compressed(pb, body + 17, body_end);
920
921 if (body[17] >= '0' && body[17] <= '9')
922 return parse_aprs_uncompressed(pb, body + 17, body_end);
923
924 DEBUG_LOG("no valid position in object");
925
926 return 0;
927 }
928
929 /*
930 * Parse an APRS item
931 *
932 * APRS PROTOCOL REFERENCE 1.0.1 Chapter 11, page 59 (69 in PDF)
933 */
934
parse_aprs_item(struct pbuf_t * pb,const char * body,const char * body_end)935 static int parse_aprs_item(struct pbuf_t *pb, const char *body, const char *body_end)
936 {
937 int i;
938
939 pb->packettype |= T_ITEM;
940
941 DEBUG_LOG("parse_aprs_item");
942
943 /* check item's name - scan for non-printable characters and the
944 * ending character ! or _
945 */
946 for (i = 0; i < 9 && body[i] != '!' && body[i] != '_'; i++) {
947 if (body[i] < 0x20 || body[i] > 0x7e) {
948 DEBUG_LOG("\titem name has unprintable characters");
949 return 0; /* non-printable */
950 }
951 }
952
953 if (body[i] != '!' && body[i] != '_') {
954 DEBUG_LOG("\titem name ends with neither ! or _");
955 return 0;
956 }
957
958 if (i < 3 || i > 9) {
959 DEBUG_LOG("\titem name has invalid length");
960 return 0;
961 }
962
963 pb->srcname = body;
964 pb->srcname_len = i;
965
966 //fprintf(stderr, "\titem name: '%.*s'\n", pb->srcname_len, pb->srcname);
967
968 /* Forward the location parsing onwards */
969 i++;
970 if (valid_sym_table_compressed(body[i]))
971 return parse_aprs_compressed(pb, body + i, body_end);
972
973 if (body[i] >= '0' && body[i] <= '9')
974 return parse_aprs_uncompressed(pb, body + i, body_end);
975
976 DEBUG_LOG("\tno valid position in item");
977
978 return 0;
979 }
980
981 /* forward declaration to allow recursive calls */
982 static int parse_aprs_body(struct pbuf_t *pb, const char *info_start);
983
984 /*
985 * Parse a 3rd-party packet.
986 * Requires the } > : sequence from src>dst,network,gate: to
987 * detect a packet as a 3rd-party packet. If the sequence is not found,
988 * returns 0 for no match (but do not drop packet).
989 * If the sequence is found, require the packet to match the 3rd-party
990 * packet spec from APRS101.PDF, and validate callsigns too.
991 */
992
parse_aprs_3rdparty(struct pbuf_t * pb,const char * info_start)993 static int parse_aprs_3rdparty(struct pbuf_t *pb, const char *info_start)
994 {
995 const char *body;
996 const char *src_end;
997 const char *s;
998 const char *path_start;
999 const char *dstcall_end;
1000 int pathlen;
1001
1002 /* ignore the CRLF in the end of the body */
1003 s = info_start + 1;
1004
1005 /* find the end of the third-party inner header */
1006 body = memchr(s, ':', pb->packet_len - 2 - (s-pb->data));
1007
1008 /* if not found, bail out */
1009 if (!body)
1010 return 0;
1011
1012 pathlen = body - s;
1013
1014 /* look for the '>' */
1015 src_end = memchr(s, '>', pathlen < CALLSIGNLEN_MAX+1 ? pathlen : CALLSIGNLEN_MAX+1);
1016 if (!src_end)
1017 return 0; // No ">" in packet start..
1018
1019 path_start = src_end+1;
1020 if (path_start >= body) // We're already at the path end
1021 return INERR_INV_3RD_PARTY;
1022
1023 if (check_invalid_src_dst(s, src_end - s) != 0)
1024 return INERR_INV_SRCCALL; /* invalid or too long for source callsign */
1025
1026 dstcall_end = path_start;
1027 while (dstcall_end < body && *dstcall_end != ',' && *dstcall_end != ':')
1028 dstcall_end++;
1029
1030 if (check_invalid_src_dst(path_start, dstcall_end - path_start))
1031 return INERR_INV_DSTCALL; /* invalid or too long for destination callsign */
1032
1033 /* check if there are invalid callsigns in the digipeater path before Q,
1034 * require at least two elements to be present (network ID, gateway callsign)
1035 */
1036 if (check_path_calls(dstcall_end, body) < 2)
1037 return INERR_INV_3RD_PARTY;
1038
1039 /* Ok, fill "name" parameter in packet with the 3rd-party packet
1040 * srccall, so that filtering can match against it. This will be
1041 * overwritten by object/item names.
1042 */
1043 pb->srcname = s;
1044 pb->srcname_len = src_end - s;
1045
1046 /* for now, just parse the inner packet content to learn it's type
1047 * and coordinates, etc
1048 */
1049 return parse_aprs_body(pb, body+1);
1050 }
1051
1052 /*
1053 * Parse APRS message slightly (only as much as is necessary for packet forwarding)
1054 */
1055
1056 static const char *disallow_msg_recipients[] = {
1057 /* old aprsd status messages:
1058 * W5xx>JAVA,qAU,WB5AOH::javaMSG :Foo_bar Linux APRS Server: 192.168.10.55 connected 2 users online.
1059 */
1060 "javaMSG", /* old aprsd */
1061 "JAVATITLE", /* old aprsd */
1062 "JAVATITL2", /* old aprsd */
1063 "USERLIST", /* old aprsd */
1064 /* Status messages from APRS+SA, blocked in javap:
1065 * OK1xx>APRS,qAR,OK1Dxxx-1::KIPSS :KipSS Login:OK1xxx-1{2
1066 * OK1xx>ID,WIDE,qAR,OK1xxx-1::INFO :KipSS Node QRT
1067 */
1068 "KIPSS",
1069 NULL
1070 };
1071
preparse_aprs_message(struct pbuf_t * pb,const char * body,int body_len)1072 static int preparse_aprs_message(struct pbuf_t *pb, const char *body, int body_len)
1073 {
1074 // quick and loose way to identify NWS and SKYWARN messages
1075 // they do apparently originate from "WXSRV", but that is not
1076 // guaranteed thing...
1077 if (memcmp(body,"NWS-",4) == 0) // as seen on specification
1078 pb->packettype |= T_NWS;
1079 if (memcmp(body,"NWS_",4) == 0) // as seen on data
1080 pb->packettype |= T_NWS;
1081 if (memcmp(body,"SKY",3) == 0) // as seen on specification
1082 pb->packettype |= T_NWS;
1083
1084 // Is it perhaps TELEMETRY related "message" ?
1085 if ( body[9] == ':' && body_len >= 10 + 6 &&
1086 ( memcmp( body+10, "PARM.", 5 ) == 0 ||
1087 memcmp( body+10, "UNIT.", 5 ) == 0 ||
1088 memcmp( body+10, "EQNS.", 5 ) == 0 ||
1089 memcmp( body+10, "BITS.", 5 ) == 0 )) {
1090 pb->packettype &= ~T_MESSAGE;
1091 pb->packettype |= T_TELEMETRY;
1092 // Fall through to recipient location lookup
1093 }
1094
1095 // Or perhaps a DIRECTED QUERY ?
1096 /* It might not be a bright idea to mark all messages starting with ?
1097 * queries instead of messages and making them NOT match the
1098 * filter message.
1099 * ALSO: General (non-directed) queries are DROPPED by aprsc.
1100 * Do not mark DIRECTED QUERIES as queries - we don't want to drop them.
1101 if (body[9] == ':' && body[10] == '?') {
1102 pb->packettype &= ~T_MESSAGE;
1103 pb->packettype |= T_QUERY;
1104 // Fall through to recipient location lookup
1105 }
1106 */
1107
1108 /* Collect message recipient */
1109 int i;
1110
1111 for (i = 0; i < CALLSIGNLEN_MAX; ++i) {
1112 // the recipient address is space padded
1113 // to 9 chars, while our historydb is not.
1114 if (body[i] == ' ' || body[i] == ':' || body[i] == 0)
1115 break;
1116 }
1117
1118 pb->dstname = body;
1119 pb->dstname_len = i;
1120
1121 if (check_call_match(disallow_msg_recipients, body, i))
1122 return INERR_DIS_MSG_DST;
1123
1124 return 0;
1125 }
1126
1127 /*
1128 * Parse the body of an APRS packet
1129 */
1130
parse_aprs_body(struct pbuf_t * pb,const char * info_start)1131 static int parse_aprs_body(struct pbuf_t *pb, const char *info_start)
1132 {
1133 char packettype, poschar;
1134 int paclen;
1135 const char *body;
1136 const char *body_end;
1137 const char *pos_start;
1138
1139 /* the following parsing logic has been translated from Ham::APRS::FAP
1140 * Perl module to C
1141 */
1142
1143 /* length of the info field: length of the packet - length of header - CRLF */
1144 paclen = pb->packet_len - (pb->info_start - pb->data) - 2;
1145 if (paclen < 1) return 0; /* Empty frame */
1146
1147 /* Check the first character of the packet and determine the packet type */
1148 packettype = *info_start;
1149
1150 /* failed parsing */
1151 // fprintf(stderr, "parse_aprs (%d):\n", paclen);
1152 // fwrite(info_start, paclen, 1, stderr);
1153 // fprintf(stderr, "\n");
1154
1155 /* body is right after the packet type character */
1156 body = info_start + 1;
1157 /* ignore the CRLF in the end of the body */
1158 body_end = pb->data + pb->packet_len - 2;
1159
1160 switch (packettype) {
1161 /* the following are obsolete mic-e types: 0x1c 0x1d
1162 * case 0x1c:
1163 * case 0x1d:
1164 */
1165 case 0x27: /* ' */
1166 case 0x60: /* ` */
1167 /* could be mic-e, minimum body length 9 chars */
1168 if (paclen >= 9) {
1169 pb->packettype |= T_POSITION;
1170 return parse_aprs_mice(pb, (unsigned char *)body, (unsigned char *)body_end);
1171 }
1172 return 0;
1173
1174 /* normal aprs plaintext packet types: !=/@
1175 * (! might also be a wx packet, so we check for it first and then fall through to
1176 * the position packet code)
1177 */
1178 case '!':
1179 if (info_start[1] == '!') { /* Ultimeter 2000 */
1180 pb->packettype |= T_WX;
1181 return 0;
1182 }
1183 /* intentionally missing break here */
1184 case '=':
1185 case '/':
1186 case '@':
1187 /* check that we won't run over right away */
1188 if (body_end - body < 10)
1189 return 0;
1190 /* Normal or compressed location packet, with or without
1191 * timestamp, with or without messaging capability
1192 *
1193 * ! and / have messaging, / and @ have a prepended timestamp
1194 */
1195 pb->packettype |= T_POSITION;
1196 if (packettype == '/' || packettype == '@') {
1197 /* With a prepended timestamp, jump over it. */
1198 body += 7;
1199 }
1200 poschar = *body;
1201 if (valid_sym_table_compressed(poschar)) { /* [\/\\A-Za-j] */
1202 /* compressed position packet */
1203 if (body_end - body >= 13)
1204 return parse_aprs_compressed(pb, body, body_end);
1205
1206 } else if (poschar >= 0x30 && poschar <= 0x39) { /* [0-9] */
1207 /* normal uncompressed position */
1208 if (body_end - body >= 19)
1209 return parse_aprs_uncompressed(pb, body, body_end);
1210 }
1211 return 0;
1212
1213 case '$':
1214 if (body_end - body > 10) {
1215 // Is it OK to declare it as position packet ?
1216 return parse_aprs_nmea(pb, body, body_end);
1217 }
1218 return 0;
1219
1220 case ':':
1221 if (paclen >= 11) {
1222 pb->packettype |= T_MESSAGE;
1223 return preparse_aprs_message(pb, body, paclen-1);
1224 }
1225 return 0;
1226
1227 case ';':
1228 if (body_end - body > 29)
1229 return parse_aprs_object(pb, body, body_end);
1230 return 0;
1231
1232 case '>':
1233 pb->packettype |= T_STATUS;
1234 return 0;
1235
1236 case '<':
1237 pb->packettype |= T_STATCAPA;
1238 return 0;
1239
1240 case '?':
1241 pb->packettype |= T_QUERY;
1242 return 0;
1243
1244 case ')':
1245 if (body_end - body > 18) {
1246 return parse_aprs_item(pb, body, body_end);
1247 }
1248 return 0;
1249
1250 case 'D':
1251 /* we drop DX cluster packets, they start with "DX de " */
1252 if (strncmp(body, "X de ", 5) == 0)
1253 return INERR_DIS_DX;
1254 break;
1255
1256 case 'T':
1257 if (body_end - body > 18) {
1258 pb->packettype |= T_TELEMETRY;
1259 return parse_aprs_telem(pb, body, body_end);
1260 }
1261 return 0;
1262
1263 case '#': /* Peet Bros U-II Weather Station */
1264 case '*': /* Peet Bros U-I Weather Station */
1265 case '_': /* Weather report without position */
1266 pb->packettype |= T_WX;
1267 return 0;
1268
1269 case '{':
1270 pb->packettype |= T_USERDEF;
1271 return 0;
1272
1273 case '}':
1274 pb->packettype |= T_3RDPARTY;
1275 return parse_aprs_3rdparty(pb, info_start);
1276
1277 default:
1278 break;
1279 }
1280
1281 /* When all else fails, try to look for a !-position that can
1282 * occur anywhere within the 40 first characters according
1283 * to the spec. (X1J TNC digipeater bugs...)
1284 */
1285 pos_start = memchr(body, '!', body_end - body);
1286 if ((pos_start) && pos_start - body <= 39) {
1287 poschar = *pos_start;
1288 if (valid_sym_table_compressed(poschar)) { /* [\/\\A-Za-j] */
1289 /* compressed position packet */
1290 if (body_end - pos_start >= 13)
1291 return parse_aprs_compressed(pb, pos_start, body_end);
1292 return 0;
1293 } else if (poschar >= 0x30 && poschar <= 0x39) { /* [0-9] */
1294 /* normal uncompressed position */
1295 if (body_end - pos_start >= 19)
1296 return parse_aprs_uncompressed(pb, pos_start, body_end);
1297 return 0;
1298 }
1299 }
1300
1301 return 0;
1302 }
1303
1304
1305
1306 /*
1307 * Try to parse an APRS packet.
1308 * Returns 1 if position was parsed successfully,
1309 * 0 if parsing failed, < 0 if packet should be dropped.
1310 *
1311 * Does also front-end part of the output filter's
1312 * packet type classification job.
1313 *
1314 * TODO: Recognize TELEM packets in !/=@ packets too!
1315 *
1316 */
1317
parse_aprs(struct pbuf_t * pb)1318 int parse_aprs(struct pbuf_t *pb)
1319 {
1320 if (!pb->info_start)
1321 return 0;
1322
1323 pb->packettype = T_ALL;
1324
1325 /* T_CW detection - CW\d+, DW\d+, EW\d+ callsigns
1326 * only used for our custom t/c CWOP filter which nobody uses
1327 */
1328 const char *d = pb->data;
1329 if (d[1] == 'W' && (d[0] >= 'C' && d[0] <= 'E')) {
1330 int i;
1331 for (i = 2; i < pb->packet_len; i++) {
1332 if (d[i] < '0' || d[i] > '9')
1333 break;
1334 }
1335 if (d[i] == '>')
1336 pb->packettype |= T_CWOP;
1337 }
1338
1339 return parse_aprs_body(pb, pb->info_start);
1340 }
1341
1342 /*
1343 * Parse an aprs text message (optional, only done to messages addressed to
1344 * SERVER
1345 */
1346
parse_aprs_message(struct pbuf_t * pb,struct aprs_message_t * am)1347 int parse_aprs_message(struct pbuf_t *pb, struct aprs_message_t *am)
1348 {
1349 const char *p;
1350
1351 memset(am, 0, sizeof(*am));
1352
1353 if (!(pb->packettype & T_MESSAGE))
1354 return -1;
1355
1356 if (pb->info_start[10] != ':')
1357 return -2;
1358
1359 am->body = pb->info_start + 11;
1360 /* -2 for the CRLF already in place */
1361 am->body_len = pb->packet_len - 2 - (pb->info_start - pb->data);
1362
1363 /* search for { looking backwards from the end of the packet,
1364 * it separates the msgid
1365 */
1366 p = am->body + am->body_len - 1;
1367 while (p > am->body && *p != '{')
1368 p--;
1369
1370 if (*p == '{') {
1371 am->msgid = p+1;
1372 am->msgid_len = pb->packet_len - 2 - (am->msgid - pb->data);
1373 am->body_len = p - am->body;
1374 }
1375
1376 /* check if this is an ACK */
1377 if ((!am->msgid_len) && am->body_len > 3
1378 && am->body[0] == 'a' && am->body[1] == 'c' && am->body[2] == 'k') {
1379 am->is_ack = 1;
1380 am->msgid = am->body + 3;
1381 am->msgid_len = am->body_len - 3;
1382 am->body_len = 0;
1383 return 0;
1384 }
1385
1386 return 0;
1387 }
1388