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