pud: nmealib: include latest fixes
[olsrd.git] / lib / pud / nmealib / src / parse.c
1 /*
2  * This file is part of nmealib.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include <nmea/parse.h>
19
20 #include <nmea/gmath.h>
21 #include <nmea/tok.h>
22 #include <nmea/context.h>
23
24 #include <string.h>
25 #include <assert.h>
26 #include <math.h>
27 #include <ctype.h>
28 #include <stdio.h>
29
30 /** the size of the buffer to put time string (that is to be parsed) into */
31 #define NMEA_TIMEPARSE_BUF  256
32
33 /**
34  * Parse nmeaTIME (time only, no date) from a string.
35  * The format that is used (hhmmss, hhmmss.s, hhmmss.ss or hhmmss.sss) is
36  * determined by the length of the string.
37  *
38  * @param s the string
39  * @param len the length of the string
40  * @param t a pointer to the nmeaTIME structure in which to store the parsed time
41  * @return true on success, false otherwise
42  */
43 static bool _nmea_parse_time(const char *s, const int len, nmeaTIME *t) {
44         assert(s);
45         assert(t);
46
47         if (len == (sizeof("hhmmss") - 1)) {
48                 t->hsec = 0;
49                 return (3 == nmea_scanf(s, len, "%2d%2d%2d", &t->hour, &t->min, &t->sec));
50         }
51
52         if (len == (sizeof("hhmmss.s") - 1)) {
53                 if (4 == nmea_scanf(s, len, "%2d%2d%2d.%d", &t->hour, &t->min, &t->sec, &t->hsec)) {
54                         t->hsec *= 10;
55                         return true;
56                 }
57                 return false;
58         }
59
60         if (len == (sizeof("hhmmss.ss") - 1)) {
61                 return (4 == nmea_scanf(s, len, "%2d%2d%2d.%d", &t->hour, &t->min, &t->sec, &t->hsec));
62         }
63
64         if (len == (sizeof("hhmmss.sss") - 1)) {
65                 if ((4 == nmea_scanf(s, len, "%2d%2d%2d.%d", &t->hour, &t->min, &t->sec, &t->hsec))) {
66                         t->hsec = (t->hsec + 9) / 10;
67                         return true;
68                 }
69                 return false;
70         }
71
72         nmea_error("Parse error: invalid time format in %s", s);
73         return false;
74 }
75
76 /**
77  * Parse nmeaTIME (date only, no time) from a string.
78  * The month is adjusted -1 to comply with the nmeaTIME month range of [0, 11].
79  * The year is adjusted +100 for years before 90 to comply with the nmeaTIME
80  * year range of [90, 189].
81  *
82  * @param date the date
83  * @param t a pointer to the nmeaTIME structure in which to store the parsed date
84  * @return true on success, false otherwise
85  */
86 static bool _nmea_parse_date(const int date, nmeaTIME *t) {
87         assert(t);
88
89         if ((date < 0) || (date > 999999)) {
90                 nmea_error("Parse error: invalid time format in %d", date);
91                 return false;
92         }
93
94         t->day = date / 10000;
95         t->mon = (date / 100) % 100;
96         t->mon--;
97         t->year = date % 100;
98         if (t->year < 90) {
99                 t->year += 100;
100         }
101
102         return true;
103 }
104
105 /**
106  * Validate the time fields in an nmeaTIME structure.
107  * Expects:
108  * <pre>
109  *   0 <= hour <   24
110  *   0 <= min  <   60
111  *   0 <= sec  <=  60
112  *   0 <= hsec <  100
113  * </pre>
114  *
115  * @param t a pointer to the structure
116  * @return true when valid, false otherwise
117  */
118 static bool validateTime(const nmeaTIME * t) {
119         if (!t) {
120                 return false;
121         }
122
123         if (!((t->hour >= 0) && (t->hour < 24) && (t->min >= 0) && (t->min < 60) && (t->sec >= 0) && (t->sec <= 60)
124                         && (t->hsec >= 0) && (t->hsec < 100))) {
125                 nmea_error("Parse error: invalid time (%d:%d:%d.%d)", t->hour, t->min, t->sec, t->hsec);
126                 return false;
127         }
128
129         return true;
130 }
131
132 /**
133  * Validate the date fields in an nmeaTIME structure.
134  * Expects:
135  * <pre>
136  *   year  [90, 189]
137  *   month [ 0,  11]
138  *   day   [ 1,  31]
139  * </pre>
140  *
141  * @param t a pointer to the structure
142  * @return true when valid, false otherwise
143  */
144 static bool validateDate(const nmeaTIME * t) {
145         if (!t) {
146                 return false;
147         }
148
149         if (!((t->year >= 90) && (t->year <= 189) && (t->mon >= 0) && (t->mon <= 11) && (t->day >= 1) && (t->day <= 31))) {
150                 nmea_error("Parse error: invalid date (%d-%d-%d - D-M-Y)", t->day, t->mon, t->year);
151                 return false;
152         }
153
154         return true;
155 }
156
157 /**
158  * Validate north/south or east/west and uppercase it.
159  * Expects:
160  * <pre>
161  *   c in { n, N, s, S } (for north/south)
162  *   c in { e, E, w, W } (for east/west)
163  * </pre>
164  *
165  * @param c a pointer to the character. The character will be converted to uppercase.
166  * @param ns true: evaluate north/south, false: evaluate east/west
167  * @return true when valid, false otherwise
168  */
169 static bool validateNSEW(char * c, const bool ns) {
170         if (!c) {
171                 return false;
172         }
173
174         *c = toupper(*c);
175
176         if (ns) {
177                 if (!((*c == 'N') || (*c == 'S'))) {
178                         nmea_error("Parse error: invalid north/south (%c)", *c);
179                         return false;
180                 }
181         } else {
182                 if (!((*c == 'E') || (*c == 'W'))) {
183                         nmea_error("Parse error: invalid east/west (%c)", *c);
184                         return false;
185                 }
186         }
187
188         return true;
189 }
190
191 /**
192  * Uppercase mode and validate it.
193  * Expects:
194  * <pre>
195  *   c in { A, D, E, F, M, N, P, R, S }
196  *
197  *   A = Autonomous. Satellite system used in non-differential mode in position fix
198  *   D = Differential. Satellite system used in differential mode in position fix
199  *   E = Estimated (dead reckoning) mode
200  *   F = Float RTK. Satellite system used in real time kinematic mode with floating integers
201  *   M = Manual input mode
202  *   N = No fix. Satellite system not used in position fix, or fix not valid
203  *   P = Precise. Satellite system used in precision mode. Precision mode is defined
204  *       as no deliberate degradation (such as Selective Availability) and higher
205  *       resolution code (P-code) is used to compute position fix.
206  *   R = Real Time Kinematic. Satellite system used in RTK mode with fixed integers
207  *   S = Simulator mode
208  * </pre>
209  *
210  * @param c a pointer to the character. The character will be converted to uppercase.
211  * @return true when valid, false otherwise
212  */
213 static bool validateMode(char * c) {
214         if (!c) {
215                 return false;
216         }
217
218         *c = toupper(*c);
219
220         if (!((*c == 'A') || (*c == 'D') || (*c == 'E') || (*c == 'F') || (*c == 'M') || (*c == 'N') || (*c == 'P')
221                         || (*c == 'R') || (*c == 'S'))) {
222                 nmea_error("Parse error: invalid mode (%c)", *c);
223                 return false;
224         }
225
226         return true;
227 }
228
229 /**
230  * Determine whether the given character is not allowed in an NMEA string.
231  *
232  * @param c
233  * The character to check
234  *
235  * @return
236  * - a pointer to the invalid character name/description when the string has invalid characters
237  * - NULL otherwise
238  */
239 const char * isInvalidNMEACharacter(const char * c) {
240         static const char invalidChars[] = { '$', '*', '!', '\\', '^', '~' };
241         static const char * invalidNonAsciiCharsName = "non-ascii character";
242         static const char * invalidCharsNames[] = { "sentence delimiter ($)", "checksum field delimiter (*)", "comma (,)",
243                         "exclamation mark (!)", "backslash (\\)", "power (^)", "tilde (~)" };
244
245         size_t charIndex;
246
247   if (!((*c >= 32) && (*c <= 126))) {
248     return invalidNonAsciiCharsName;
249   }
250
251   for (charIndex = 0; charIndex < sizeof(invalidChars); charIndex++) {
252     if (*c == invalidChars[charIndex]) {
253       return invalidCharsNames[charIndex];
254     }
255   }
256
257   return NULL;
258 }
259
260 /**
261  * Determine whether the given string contains characters that are not allowed
262  * in an NMEA string.
263  *
264  * @param s
265  * The string to check
266  * @param len
267  * The length of the string to check
268  *
269  * @return
270  * - a pointer to the invalid character name/description when the string has invalid characters
271  * - NULL otherwise
272  */
273 const char * nmea_parse_sentence_has_invalid_chars(const char * s, const size_t len) {
274         size_t i;
275
276         if (!s || !len) {
277                 return NULL;
278         }
279
280         for (i = 0; i < len; i++) {
281                 const char * invalidCharName = isInvalidNMEACharacter(&s[i]);
282                 if (invalidCharName) {
283                   return invalidCharName;
284                 }
285         }
286
287         return NULL;
288 }
289
290 /**
291  * Determine sentence type (see nmeaPACKTYPE) by the header of a string.
292  * The header is the start of an NMEA sentence, right after the $.
293  *
294  * @param s the string. must be the NMEA string, right after the initial $
295  * @param len the length of the string
296  * @return The packet type (or GPNON when it could not be determined)
297  */
298 enum nmeaPACKTYPE nmea_parse_get_sentence_type(const char *s, const int len) {
299         static const char *pheads[] = { "GPGGA", "GPGSA", "GPGSV", "GPRMC", "GPVTG" };
300         static const enum nmeaPACKTYPE types[] = { GPGGA, GPGSA, GPGSV, GPRMC, GPVTG };
301         unsigned int i;
302
303         assert(s);
304
305         if (len < 5) {
306                 return GPNON;
307         }
308
309         for (i = 0; i < (sizeof(types) / sizeof(types[0])); i++) {
310                 if (!memcmp(s, pheads[i], 5)) {
311                         return types[i];
312                 }
313         }
314
315         return GPNON;
316 }
317
318 /**
319  * Parse a GPGGA sentence from a string
320  *
321  * @param s the string
322  * @param len the length of the string
323  * @param has_checksum true when the string contains a checksum
324  * @param pack a pointer to the result structure
325  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
326  */
327 int nmea_parse_GPGGA(const char *s, const int len, bool has_checksum, nmeaGPGGA *pack) {
328         int token_count;
329         char time_buff[NMEA_TIMEPARSE_BUF];
330         size_t time_buff_len = 0;
331
332         if (!has_checksum) {
333           return 0;
334         }
335
336         assert(s);
337         assert(pack);
338
339         nmea_trace_buff(s, len);
340
341         /*
342          * Clear before parsing, to be able to detect absent fields
343          */
344         time_buff[0] = '\0';
345         pack->present = 0;
346         pack->utc.hour = -1;
347         pack->utc.min = -1;
348         pack->utc.sec = -1;
349         pack->utc.hsec = -1;
350         pack->lat = NAN;
351         pack->ns = 0;
352         pack->lon = NAN;
353         pack->ew = 0;
354         pack->sig = -1;
355         pack->satinuse = -1;
356         pack->HDOP = NAN;
357         pack->elv = NAN;
358         pack->elv_units = 0;
359         pack->diff = 0;                 /* ignored */
360         pack->diff_units = 0;   /* ignored */
361         pack->dgps_age = 0;             /* ignored */
362         pack->dgps_sid = 0;             /* ignored */
363
364         /* parse */
365         token_count = nmea_scanf(s, len, "$GPGGA,%s,%f,%c,%f,%c,%d,%d,%f,%f,%c,%f,%c,%f,%d*", &time_buff[0], &pack->lat,
366                         &pack->ns, &pack->lon, &pack->ew, &pack->sig, &pack->satinuse, &pack->HDOP, &pack->elv, &pack->elv_units,
367                         &pack->diff, &pack->diff_units, &pack->dgps_age, &pack->dgps_sid);
368
369         /* see that we have enough tokens */
370         if (token_count != 14) {
371                 nmea_error("GPGGA parse error: need 14 tokens, got %d in %s", token_count, s);
372                 return 0;
373         }
374
375         /* determine which fields are present and validate them */
376
377         time_buff_len = strlen(&time_buff[0]);
378         if (time_buff_len > (NMEA_TIMEPARSE_BUF - 1))
379                 time_buff_len = NMEA_TIMEPARSE_BUF - 1;
380         if (time_buff_len) {
381                 if (!_nmea_parse_time(&time_buff[0], time_buff_len, &pack->utc)) {
382                         return 0;
383                 }
384
385                 if (!validateTime(&pack->utc)) {
386                         return 0;
387                 }
388
389                 nmea_INFO_set_present(&pack->present, UTCTIME);
390         }
391         if (!isnan(pack->lat) && (pack->ns)) {
392                 if (!validateNSEW(&pack->ns, true)) {
393                         return 0;
394                 }
395
396                 nmea_INFO_set_present(&pack->present, LAT);
397         }
398         if (!isnan(pack->lon) && (pack->ew)) {
399                 if (!validateNSEW(&pack->ew, false)) {
400                         return 0;
401                 }
402
403                 nmea_INFO_set_present(&pack->present, LON);
404         }
405         if (pack->sig != -1) {
406                 if (!((pack->sig >= NMEA_SIG_FIRST) && (pack->sig <= NMEA_SIG_LAST))) {
407                         nmea_error("GPGGA parse error: invalid signal %d, expected [%d, %d]", pack->sig, NMEA_SIG_FIRST, NMEA_SIG_LAST);
408                         return 0;
409                 }
410
411                 nmea_INFO_set_present(&pack->present, SIG);
412         }
413         if (pack->satinuse != -1) {
414                 nmea_INFO_set_present(&pack->present, SATINUSECOUNT);
415         }
416         if (!isnan(pack->HDOP)) {
417                 nmea_INFO_set_present(&pack->present, HDOP);
418         }
419         if (!isnan(pack->elv) && (pack->elv_units)) {
420                 if (pack->elv_units != 'M') {
421                         nmea_error("GPGGA parse error: invalid elevation unit (%c)", pack->elv_units);
422                         return 0;
423                 }
424
425                 nmea_INFO_set_present(&pack->present, ELV);
426         }
427         /* ignore diff and diff_units */
428         /* ignore dgps_age and dgps_sid */
429
430         return 1;
431 }
432
433 /**
434  * Parse a GPGSA sentence from a string
435  *
436  * @param s the string
437  * @param len the length of the string
438  * @param has_checksum true when the string contains a checksum
439  * @param pack a pointer to the result structure
440  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
441  */
442 int nmea_parse_GPGSA(const char *s, const int len, bool has_checksum, nmeaGPGSA *pack) {
443         int token_count;
444         int i;
445
446         if (!has_checksum) {
447           return 0;
448         }
449
450         assert(s);
451         assert(pack);
452
453         nmea_trace_buff(s, len);
454
455         /*
456          * Clear before parsing, to be able to detect absent fields
457          */
458         pack->present = 0;
459         pack->fix_mode = 0;
460         pack->fix_type = -1;
461         for (i = 0; i < NMEA_MAXSAT; i++) {
462                 pack->sat_prn[i] = 0;
463         }
464         pack->PDOP = NAN;
465         pack->HDOP = NAN;
466         pack->VDOP = NAN;
467
468         /* parse */
469         token_count = nmea_scanf(s, len, "$GPGSA,%c,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%f,%f,%f*", &pack->fix_mode,
470                         &pack->fix_type, &pack->sat_prn[0], &pack->sat_prn[1], &pack->sat_prn[2], &pack->sat_prn[3],
471                         &pack->sat_prn[4], &pack->sat_prn[5], &pack->sat_prn[6], &pack->sat_prn[7], &pack->sat_prn[8],
472                         &pack->sat_prn[9], &pack->sat_prn[10], &pack->sat_prn[11], &pack->PDOP, &pack->HDOP, &pack->VDOP);
473
474         /* see that we have enough tokens */
475         if (token_count != 17) {
476                 nmea_error("GPGSA parse error: need 17 tokens, got %d in %s", token_count, s);
477                 return 0;
478         }
479
480         /* determine which fields are present and validate them */
481
482         pack->fix_mode = toupper(pack->fix_mode);
483         if (!((pack->fix_mode == 'A') || (pack->fix_mode == 'M'))) {
484                 nmea_error("GPGSA parse error: invalid fix mode (%c)", pack->fix_mode);
485                 return 0;
486         }
487         if (pack->fix_type != -1) {
488                 if (!((pack->fix_type >= NMEA_FIX_FIRST) && (pack->fix_type <= NMEA_FIX_LAST))) {
489                         nmea_error("GPGSA parse error: invalid fix type %d, expected [%d, %d]", pack->fix_type, NMEA_FIX_FIRST, NMEA_FIX_LAST);
490                         return 0;
491                 }
492
493                 nmea_INFO_set_present(&pack->present, FIX);
494         }
495         for (i = 0; i < NMEA_MAXSAT; i++) {
496                 if (pack->sat_prn[i] != 0) {
497                         nmea_INFO_set_present(&pack->present, SATINUSE);
498                         break;
499                 }
500         }
501         if (!isnan(pack->PDOP)) {
502                 nmea_INFO_set_present(&pack->present, PDOP);
503         }
504         if (!isnan(pack->HDOP)) {
505                 nmea_INFO_set_present(&pack->present, HDOP);
506         }
507         if (!isnan(pack->VDOP)) {
508                 nmea_INFO_set_present(&pack->present, VDOP);
509         }
510
511         return 1;
512 }
513
514 /**
515  * Parse a GPGSV sentence from a string
516  *
517  * @param s the string
518  * @param len the length of the string
519  * @param has_checksum true when the string contains a checksum
520  * @param pack a pointer to the result structure
521  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
522  */
523 int nmea_parse_GPGSV(const char *s, const int len, bool has_checksum, nmeaGPGSV *pack) {
524         int token_count;
525         int token_count_expected;
526         int sat_count;
527         int sat_counted = 0;
528
529         if (!has_checksum) {
530           return 0;
531         }
532
533         assert(s);
534         assert(pack);
535
536         nmea_trace_buff(s, len);
537
538         /*
539          * Clear before parsing, to be able to detect absent fields
540          */
541         memset(pack, 0, sizeof(nmeaGPGSV));
542
543         /* parse */
544         token_count = nmea_scanf(s, len, "$GPGSV,%d,%d,%d,"
545                         "%d,%d,%d,%d,"
546                         "%d,%d,%d,%d,"
547                         "%d,%d,%d,%d,"
548                         "%d,%d,%d,%d*", &pack->pack_count, &pack->pack_index, &pack->sat_count, &pack->sat_data[0].id,
549                         &pack->sat_data[0].elv, &pack->sat_data[0].azimuth, &pack->sat_data[0].sig, &pack->sat_data[1].id,
550                         &pack->sat_data[1].elv, &pack->sat_data[1].azimuth, &pack->sat_data[1].sig, &pack->sat_data[2].id,
551                         &pack->sat_data[2].elv, &pack->sat_data[2].azimuth, &pack->sat_data[2].sig, &pack->sat_data[3].id,
552                         &pack->sat_data[3].elv, &pack->sat_data[3].azimuth, &pack->sat_data[3].sig);
553
554         /* return if we have no sentences or sats */
555         if ((pack->pack_count < 1) || (pack->pack_count > NMEA_NSATPACKS) || (pack->pack_index < 1)
556                         || (pack->pack_index > pack->pack_count) || (pack->sat_count < 0) || (pack->sat_count > NMEA_MAXSAT)) {
557                 nmea_error("GPGSV parse error: inconsistent pack (count/index/satcount = %d/%d/%d)", pack->pack_count,
558                                 pack->pack_index, pack->sat_count);
559                 return 0;
560         }
561
562         /* validate all sat settings and count the number of sats in the sentence */
563         for (sat_count = 0; sat_count < NMEA_SATINPACK; sat_count++) {
564                 if (pack->sat_data[sat_count].id != 0) {
565                         if ((pack->sat_data[sat_count].id < 0)) {
566                                 nmea_error("GPGSV parse error: invalid sat %d id (%d)", sat_count + 1, pack->sat_data[sat_count].id);
567                                 return 0;
568                         }
569                         if ((pack->sat_data[sat_count].elv < -90) || (pack->sat_data[sat_count].elv > 90)) {
570                                 nmea_error("GPGSV parse error: invalid sat %d elevation (%d)", sat_count + 1, pack->sat_data[sat_count].elv);
571                                 return 0;
572                         }
573                         if ((pack->sat_data[sat_count].azimuth < 0) || (pack->sat_data[sat_count].azimuth >= 360)) {
574                                 nmea_error("GPGSV parse error: invalid sat %d azimuth (%d)", sat_count + 1, pack->sat_data[sat_count].azimuth);
575                                 return 0;
576                         }
577                         if ((pack->sat_data[sat_count].sig < 0) || (pack->sat_data[sat_count].sig > 99)) {
578                                 nmea_error("GPGSV parse error: invalid sat %d signal (%d)", sat_count + 1, pack->sat_data[sat_count].sig);
579                                 return 0;
580                         }
581                         sat_counted++;
582                 }
583         }
584
585         /* see that we have enough tokens */
586         token_count_expected = (sat_counted * 4) + 3;
587         if ((token_count < token_count_expected) || (token_count > (NMEA_SATINPACK * 4 + 3))) {
588                 nmea_error("GPGSV parse error: need %d tokens, got %d", token_count_expected, token_count);
589                 return 0;
590         }
591
592         /* determine which fields are present and validate them */
593
594         if (pack->sat_count > 0) {
595                 nmea_INFO_set_present(&pack->present, SATINVIEW);
596         }
597
598         return 1;
599 }
600
601 /**
602  * Parse a GPRMC sentence from a string
603  *
604  * @param s the string
605  * @param len the length of the string
606  * @param has_checksum true when the string contains a checksum
607  * @param pack a pointer to the result structure
608  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
609  */
610 int nmea_parse_GPRMC(const char *s, const int len, bool has_checksum, nmeaGPRMC *pack) {
611         int token_count;
612         char time_buff[NMEA_TIMEPARSE_BUF];
613         int date;
614         size_t time_buff_len = 0;
615
616         if (!has_checksum) {
617           return 0;
618         }
619
620         assert(s);
621         assert(pack);
622
623         nmea_trace_buff(s, len);
624
625         /*
626          * Clear before parsing, to be able to detect absent fields
627          */
628         time_buff[0] = '\0';
629         date = -1;
630         pack->present = 0;
631         pack->utc.year = -1;
632         pack->utc.mon = -1;
633         pack->utc.day = -1;
634         pack->utc.hour = -1;
635         pack->utc.min = -1;
636         pack->utc.sec = -1;
637         pack->utc.hsec = -1;
638         pack->status = 0;
639         pack->lat = NAN;
640         pack->ns = 0;
641         pack->lon = NAN;
642         pack->ew = 0;
643         pack->speed = NAN;
644         pack->track = NAN;
645         pack->magvar = NAN;
646         pack->magvar_ew = 0;
647         pack->mode = 0;
648
649         /* parse */
650         token_count = nmea_scanf(s, len, "$GPRMC,%s,%c,%f,%c,%f,%c,%f,%f,%d,%f,%c,%c*", &time_buff[0], &pack->status,
651                         &pack->lat, &pack->ns, &pack->lon, &pack->ew, &pack->speed, &pack->track, &date,
652                         &pack->magvar, &pack->magvar_ew, &pack->mode);
653
654         /* see that we have enough tokens */
655         if ((token_count != 11) && (token_count != 12)) {
656                 nmea_error("GPRMC parse error: need 11 or 12 tokens, got %d in %s", token_count, s);
657                 return 0;
658         }
659
660         /* determine which fields are present and validate them */
661
662         time_buff_len = strlen(&time_buff[0]);
663         if (time_buff_len) {
664                 if (!_nmea_parse_time(&time_buff[0], time_buff_len, &pack->utc)) {
665                         return 0;
666                 }
667
668                 if (!validateTime(&pack->utc)) {
669                         return 0;
670                 }
671
672                 nmea_INFO_set_present(&pack->present, UTCTIME);
673         }
674
675         if (!pack->status) {
676                 pack->status = 'V';
677         } else {
678                 pack->status = toupper(pack->status);
679                 if (!((pack->status == 'A') || (pack->status == 'V'))) {
680                         nmea_error("GPRMC parse error: invalid status (%c)", pack->status);
681                         return 0;
682                 }
683         }
684         if (!isnan(pack->lat) && (pack->ns)) {
685                 if (!validateNSEW(&pack->ns, true)) {
686                         return 0;
687                 }
688
689                 nmea_INFO_set_present(&pack->present, LAT);
690         }
691         if (!isnan(pack->lon) && (pack->ew)) {
692                 if (!validateNSEW(&pack->ew, false)) {
693                         return 0;
694                 }
695
696                 nmea_INFO_set_present(&pack->present, LON);
697         }
698         if (!isnan(pack->speed)) {
699                 nmea_INFO_set_present(&pack->present, SPEED);
700         }
701         if (!isnan(pack->track)) {
702                 nmea_INFO_set_present(&pack->present, TRACK);
703         }
704
705         if (date != -1) {
706                 if (!_nmea_parse_date(date, &pack->utc)) {
707                         return 0;
708                 }
709
710                 if (!validateDate(&pack->utc)) {
711                         return 0;
712                 }
713
714                 nmea_INFO_set_present(&pack->present, UTCDATE);
715         }
716
717         if (!isnan(pack->magvar) && (pack->magvar_ew)) {
718                 if (!validateNSEW(&pack->magvar_ew, false)) {
719                         return 0;
720                 }
721
722                 nmea_INFO_set_present(&pack->present, MAGVAR);
723         }
724         if (token_count == 11) {
725                 pack->mode = 'A';
726         } else {
727                 if (!pack->mode) {
728                         pack->mode = 'N';
729                 } else {
730                         if (!validateMode(&pack->mode)) {
731                                 return 0;
732                         }
733                 }
734         }
735
736         return 1;
737 }
738
739 /**
740  * Parse a GPVTG sentence from a string
741  *
742  * @param s the string
743  * @param len the length of the string
744  * @param has_checksum true when the string contains a checksum
745  * @param pack a pointer to the result structure
746  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
747  */
748 int nmea_parse_GPVTG(const char *s, const int len, bool has_checksum, nmeaGPVTG *pack) {
749         int token_count;
750
751         if (!has_checksum) {
752           return 0;
753         }
754
755         assert(s);
756         assert(pack);
757
758         nmea_trace_buff(s, len);
759
760         /*
761          * Clear before parsing, to be able to detect absent fields
762          */
763         pack->present = 0;
764         pack->track = NAN;
765         pack->track_t = 0;
766         pack->mtrack = NAN;
767         pack->mtrack_m = 0;
768         pack->spn = NAN;
769         pack->spn_n = 0;
770         pack->spk = NAN;
771         pack->spk_k = 0;
772
773         /* parse */
774         token_count = nmea_scanf(s, len, "$GPVTG,%f,%c,%f,%c,%f,%c,%f,%c*", &pack->track, &pack->track_t, &pack->mtrack,
775                         &pack->mtrack_m, &pack->spn, &pack->spn_n, &pack->spk, &pack->spk_k);
776
777         /* see that we have enough tokens */
778         if (token_count != 8) {
779                 nmea_error("GPVTG parse error: need 8 tokens, got %d in %s", token_count, s);
780                 return 0;
781         }
782
783         /* determine which fields are present and validate them */
784
785         if (!isnan(pack->track) && (pack->track_t)) {
786                 pack->track_t = toupper(pack->track_t);
787                 if (pack->track_t != 'T') {
788                         nmea_error("GPVTG parse error: invalid track unit, got %c, expected T", pack->track_t);
789                         return 0;
790                 }
791
792                 nmea_INFO_set_present(&pack->present, TRACK);
793         }
794         if (!isnan(pack->mtrack) && (pack->mtrack_m)) {
795                 pack->mtrack_m = toupper(pack->mtrack_m);
796                 if (pack->mtrack_m != 'M') {
797                         nmea_error("GPVTG parse error: invalid mtrack unit, got %c, expected M", pack->mtrack_m);
798                         return 0;
799                 }
800
801                 nmea_INFO_set_present(&pack->present, MTRACK);
802         }
803         if (!isnan(pack->spn) && (pack->spn_n)) {
804                 pack->spn_n = toupper(pack->spn_n);
805                 if (pack->spn_n != 'N') {
806                         nmea_error("GPVTG parse error: invalid knots speed unit, got %c, expected N", pack->spn_n);
807                         return 0;
808                 }
809
810                 nmea_INFO_set_present(&pack->present, SPEED);
811
812                 if (isnan(pack->spk)) {
813                         pack->spk = pack->spn * NMEA_TUD_KNOTS;
814                         pack->spk_k = 'K';
815                 }
816         }
817         if (!isnan(pack->spk) && (pack->spk_k)) {
818                 pack->spk_k = toupper(pack->spk_k);
819                 if (pack->spk_k != 'K') {
820                         nmea_error("GPVTG parse error: invalid kph speed unit, got %c, expected K", pack->spk_k);
821                         return 0;
822                 }
823
824                 nmea_INFO_set_present(&pack->present, SPEED);
825
826                 if (isnan(pack->spn)) {
827                         pack->spn = pack->spk / NMEA_TUD_KNOTS;
828                         pack->spn_n = 'N';
829                 }
830         }
831
832         return 1;
833 }