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