]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 3636] NMEA: combine time/date from multiple sentences
authorJuergen Perlinger <perlinger@ntp.org>
Fri, 17 Jan 2020 05:59:50 +0000 (06:59 +0100)
committerJuergen Perlinger <perlinger@ntp.org>
Fri, 17 Jan 2020 05:59:50 +0000 (06:59 +0100)
bk: 5e214d56Ar7oRt1p7Fd3OhvQzqjHCw

ChangeLog
html/drivers/driver20.html
include/ntp_calgps.h
libntp/ntp_calgps.c
ntpd/refclock_nmea.c

index 0387b9a9baa9e41c0ff7b16db89099bcfcc5caf4..7265ea04bbbe9315fee3e11624e27f3b2fa50246 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,7 @@
 
 * [Sec 3610] process_control() should bail earlier on short packets. stenn@
   - Reported by Philippe Antoine
+* [Bug 3636] NMEA: combine time/date from multiple sentences <perlinger@ntp.org>
 * [Bug 3628] raw DCF decoding - improve robustness with Zeller's congruence
   - implement Zeller's congruence in libparse and libntp <perlinger@ntp.org>
 * [Bug 3620] memory leak in ntpq sysinfo <perlinger@ntp.org>
index 8d7f69e1cd93bc1e936434732c918869960c3f11..1c3ac782f7686824d333dd0ef5a59bb9ed6b213a 100644 (file)
@@ -13,7 +13,7 @@
   <body>
     <h3>Generic NMEA GPS Receiver</h3>
 <p>Last update:
-  <!-- #BeginDate format:En2m -->4-Sep-2019  21:23<!-- #EndDate -->
+  <!-- #BeginDate format:En2m -->13-Jan-2020  07:12<!-- #EndDate -->
   UTC</p>
     <hr>
     <h4>Synopsis</h4>
        <td>Accord</td>
       </tr><tr>
       </tr><tr>
-       <td class="ttf">$PGRMF,GWEEK,WTIME,DATE,UTC,LEAPS,LAT,LAT_REF,LON,LON_REF,TYPE,MODE,SPD,HDOP,TDOP*CS&lt;cr&gt;&lt;lf&gt;</td>
+       <td class="ttf">$PGRMF,gpsWk,gpsTow,DATE,UTC,LEAPS,LAT,LAT_REF,LON,LON_REF,TYPE,MODE,SPD,HDOP,TDOP*CS&lt;cr&gt;&lt;lf&gt;</td>
        <td>Garmin</td>
+      </tr><tr>
+         <td class="ttf">$PUBX,04,UTC,DATE,utcTow,utcWk,LEAPS,clkBias,clkDrift,tpGran,*CS&lt;cr&gt;&lt;lf&gt;</td>
+         <td>UBLOX</td>
       </tr>    
     </tbody></table></p>
 
        <td class="ttf">GPSTIME</td>
        <td>Time of day on GPS timescale. Hours, minutes and seconds [fraction (opt.)] (hhmmss[.f])</td>
       </tr><tr>
-       <td class="ttf">GWEEK</td>
-       <td>Week number in the GPS time scale, modulo 1024 (0..1023)</td>
+       <td class="ttf">gpsTow</td>
+       <td>GPS week time, seconds since start of GPS week (0..604799)</td>
+      </tr><tr>
+       <td class="ttf">gpsWk</td>
+       <td>Week number in the GPS time scale (may exceed 1024)</td>
       </tr><tr>
        <td class="ttf">G_UNIT</td>
        <td>Geoid units (M/F)</td>
       </tr><tr>
        <td class="ttf">UTC</td>
        <td>Time of day on UTC timescale. Hours, minutes and seconds [fraction (opt.)] (hhmmss[.fff])</td>
-      </tr><tr>
-       <td class="ttf">WTIME</td>
-       <td>GPS week time, seconds since start of GPS week (0..604799)</td>
       </tr><tr>
        <td class="ttf">YYYY</td>
        <td>Year</td>
         <td align="center">0x100</td>
        <td>process <tt>$PGRMF</tt></td>
       </tr><tr>
-        <td align="center">9-15</td>
+        <td align="center">9</td>
+        <td align="center">512</td>
+        <td align="center">0x200</td>
+       <td>process <tt>$PUBX,04</tt></td>
+      </tr><tr>
+        <td align="center">10-15</td>
         <td align="center"></td>
-        <td align="center">0xFE00</td>
+        <td align="center">0xFC00</td>
        <td>reserved - leave 0</td>
       </tr><tr>
         <td align="center">16</td>
index 970c4d09629b869b4ee3cc6b7c51df597de99379..7b5d83d1305c519c6393c62fa29daa2aa9809dab 100644 (file)
@@ -107,17 +107,32 @@ gpsntp_from_calendar_ex(TcCivilDate*, l_fp fofs, int/*BOOL*/ warp);
 
 static inline TNtpDatum
 gpsntp_from_calendar(TcCivilDate * pCiv, l_fp fofs) {
-    return gpsntp_from_calendar_ex(pCiv, fofs, TRUE);
+       return gpsntp_from_calendar_ex(pCiv, fofs, TRUE);
 }
 
 extern TNtpDatum
-gpsntp_from_daytime1(TcCivilDate *dt, l_fp fofs, l_fp pivot);
+gpsntp_from_daytime1_ex(TcCivilDate *dt, l_fp fofs, l_fp pivot, int/*BOOL*/ warp);
+
+static inline TNtpDatum
+gpsntp_from_daytime1(TcCivilDate *dt, l_fp fofs, l_fp pivot) {
+       return gpsntp_from_daytime1_ex(dt, fofs, pivot, TRUE);
+}
 
 extern TNtpDatum
-gpsntp_from_daytime2(TcCivilDate *dt, l_fp fofs, TcNtpDatum *pivot);
+gpsntp_from_daytime2_ex(TcCivilDate *dt, l_fp fofs, TcNtpDatum *pivot, int/*BOOL*/ warp);
+
+static inline TNtpDatum
+gpsntp_from_daytime2(TcCivilDate *dt, l_fp fofs, TcNtpDatum *pivot) {
+       return gpsntp_from_daytime2_ex(dt, fofs, pivot, TRUE);
+}
 
 extern TNtpDatum
-gpsntp_from_gpscal(TcGpsDatum*);
+gpsntp_from_gpscal_ex(TcGpsDatum*, int/*BOOL*/ warp);
+
+static inline TNtpDatum
+gpsntp_from_gpscal(TcGpsDatum *wd) {
+       return gpsntp_from_gpscal_ex(wd, FALSE);
+}
 
 extern void
 gpsntp_to_calendar(TCivilDate*, TcNtpDatum*);
index c478aa8af94c07efd37dd4c547d4d782fe22b858..3ce969a30bc82505ba269a35c94956358c53004e 100644 (file)
@@ -168,7 +168,7 @@ _gpsntp_fix_gps_era(
        days = sign ^ (days - base);
        days %= clen;
        days = base + (sign & clen) + (sign ^ days);
-       
+
        out.days = days;
        return out;
 }
@@ -188,7 +188,8 @@ static TNtpDatum
 _gpsntp_from_daytime(
        TcCivilDate *   jd,
        l_fp            fofs,
-       TcNtpDatum *    pivot
+       TcNtpDatum *    pivot,
+       int             warp
        )
 {
        static const int32_t shift = SECSPERDAY / 2;
@@ -211,7 +212,7 @@ _gpsntp_from_daytime(
                retv.days += (retv.secs < lim ||
                              (retv.secs == lim && retv.frac < pivot->frac));
        }
-       return _gpsntp_fix_gps_era(&retv);
+       return warp ? _gpsntp_fix_gps_era(&retv) : retv;
 }
 
 /* -----------------------------------------------------------------
@@ -221,15 +222,16 @@ _gpsntp_from_daytime(
  * stamp is less or equal to 12 hours absolute.
  */
 TNtpDatum
-gpsntp_from_daytime2(
+gpsntp_from_daytime2_ex(
        TcCivilDate *   jd,
        l_fp            fofs,
-       TcNtpDatum *    pivot
+       TcNtpDatum *    pivot,
+       int/*BOOL*/     warp
        )
 {
        TNtpDatum       dpiv = *pivot;
        _norm_ntp_datum(&dpiv);
-       return _gpsntp_from_daytime(jd, fofs, &dpiv);
+       return _gpsntp_from_daytime(jd, fofs, &dpiv, warp);
 }
 
 /* -----------------------------------------------------------------
@@ -240,10 +242,11 @@ gpsntp_from_daytime2(
  * NTP time stamp.
  */
 TNtpDatum
-gpsntp_from_daytime1(
+gpsntp_from_daytime1_ex(
        TcCivilDate *   jd,
        l_fp            fofs,
-       l_fp            pivot
+       l_fp            pivot,
+       int/*BOOL*/     warp
        )
 {
        vint64          pvi64;
@@ -255,7 +258,7 @@ gpsntp_from_daytime1(
        dpiv.days = split.hi;
        dpiv.secs = split.lo;
        dpiv.frac = pivot.l_uf;
-       return _gpsntp_from_daytime(jd, fofs, &dpiv);
+       return _gpsntp_from_daytime(jd, fofs, &dpiv, warp);
 }
 
 /* -----------------------------------------------------------------
@@ -271,7 +274,7 @@ gpsntp_from_calendar_ex(
 {
        TGpsDatum       gps;
        gps = gpscal_from_calendar_ex(jd, fofs, warp);
-       return gpsntp_from_gpscal(&gps);
+       return gpsntp_from_gpscal_ex(&gps, FALSE);
 }
 
 /* -----------------------------------------------------------------
@@ -294,15 +297,23 @@ gpsntp_to_calendar(
  * get day/tod representation from week/tow datum
  */
 TNtpDatum
-gpsntp_from_gpscal(
-       TcGpsDatum *    gd
+gpsntp_from_gpscal_ex(
+       TcGpsDatum *    gd,
+       int/*BOOL*/     warp
        )
 {
        TNtpDatum       retv;
        vint64          ts64;
        ntpcal_split    split;
-       
-       ts64  = ntpcal_weekjoin(gd->weeks, gd->wsecs);
+       TGpsDatum       date = *gd;
+
+       if (warp) {
+               uint32_t base = basedate_get_gpsweek() + GPSNTP_WSHIFT;
+               _norm_gps_datum(&date);
+               date.weeks = ((date.weeks - base) & 1023u) + base;
+       }
+
+       ts64  = ntpcal_weekjoin(date.weeks, date.wsecs);
        ts64  = subv64u32(&ts64, (GPSNTP_DSHIFT * SECSPERDAY));
        split = ntpcal_daysplit(&ts64);
 
@@ -349,7 +360,7 @@ _gpscal_fix_gps_era(
         */
        uint32_t        base, week;
        TGpsDatum       out = *in;
-       
+
        week = out.weeks;
        base = basedate_get_gpsweek() + GPSNTP_WSHIFT;
        week = base + ((week - base) & (GPSWEEKS - 1));
@@ -393,7 +404,7 @@ gpscal_from_calendar_ex(
        /*  (-DAY_GPS_STARTS) (mod 7*1024) -- complement of cycle shift */
        static const uint32_t s_compl_shift =
            (7 * 1024) - DAY_GPS_STARTS % (7 * 1024);
-       
+
        TGpsDatum       gps;
        TCivilDate      cal;
        int32_t         days, week;
@@ -473,7 +484,7 @@ gpscal_to_calendar(
        TNtpDatum nd;
 
        memset(cd, 0, sizeof(*cd));
-       nd = gpsntp_from_gpscal(wd);
+       nd = gpsntp_from_gpscal_ex(wd, FALSE);
        gpsntp_to_calendar(cd, &nd);
 }
 
@@ -547,7 +558,7 @@ gpscal_from_weektime2(
        TcGpsDatum *    pivot
        )
 {
-       TGpsDatum wpiv = * pivot;       
+       TGpsDatum wpiv = * pivot;
        _norm_gps_datum(&wpiv);
        return _gpscal_from_weektime(wsecs, fofs, &wpiv);
 }
@@ -555,7 +566,7 @@ gpscal_from_weektime2(
 /* -----------------------------------------------------------------
  * epand a time-of-week around an pivot given as LFP, which in turn
  * is expanded around the current system time and then converted
- * into a week datum. 
+ * into a week datum.
  */
 TGpsDatum
 gpscal_from_weektime1(
@@ -592,7 +603,7 @@ gpscal_from_gpsntp(
        TGpsDatum       retv;
        vint64          ts64;
        ntpcal_split    split;
-       
+
        ts64  = ntpcal_dayjoin(gd->days, gd->secs);
        ts64  = addv64u32(&ts64, (GPSNTP_DSHIFT * SECSPERDAY));
        split = ntpcal_weeksplit(&ts64);
index 93c82eee1d20ee2b221a06b746a1f2f29b126724..4fdadea61dffa425187feb12e6537278f8024f93 100644 (file)
@@ -94,7 +94,7 @@
 #define NMEA_QUIETPPS_MASK     0x00020000U
 #define NMEA_DATETRUST_MASK    0x00040000U
 
-#define NMEA_PROTO_IDLEN       5       /* tag name must be at least 5 chars */
+#define NMEA_PROTO_IDLEN       4       /* tag name must be at least 4 chars */
 #define NMEA_PROTO_MINLEN      6       /* min chars in sentence, excluding CS */
 #define NMEA_PROTO_MAXLEN      80      /* max chars in sentence, excluding CS */
 #define NMEA_PROTO_FIELDS      32      /* not official; limit on fields per record */
 #define        SPEED232        B4800   /* uart speed (4800 bps) */
 #define        PRECISION       (-9)    /* precision assumed (about 2 ms) */
 #define        PPS_PRECISION   (-20)   /* precision assumed (about 1 us) */
+#define        DATE_HOLD       16      /* seconds to hold on provided GPS date */
+#define        DATE_HLIM       4       /* when do we take ANY date format */
 #define        REFID           "GPS\0" /* reference id */
 #define        DESCRIPTION     "NMEA GPS Clock" /* who we are */
 #ifndef O_NOCTTY
  */
 #define NMEA_GPZDG     4
 #define NMEA_PGRMF     5
-#define NMEA_ARRAY_SIZE (NMEA_PGRMF + 1)
+#define NMEA_PUBX04    6
+#define NMEA_ARRAY_SIZE (NMEA_PUBX04 + 1)
 
 /*
  * Sentence selection mode bits
 #define USE_GPGLL              0x00000004u
 #define USE_GPZDA              0x00000008u
 #define USE_PGRMF              0x00000100u
+#define USE_PUBX04             0x00000200u
 
 /* mapping from sentence index to controlling mode bit */
 static const u_int32 sentence_mode[NMEA_ARRAY_SIZE] =
@@ -201,7 +205,8 @@ static const u_int32 sentence_mode[NMEA_ARRAY_SIZE] =
        USE_GPGLL,
        USE_GPZDA,
        USE_GPZDA,
-       USE_PGRMF
+       USE_PGRMF,
+       USE_PUBX04
 };
 
 /* date formats we support */
@@ -210,6 +215,15 @@ enum date_fmt {
        DATE_3_DDMMYYYY /* use 3 fields with 4-digit year */
 };
 
+/* date type */
+enum date_type {
+       DTYP_NONE,
+       DTYP_Y2D,       /* 2-digit year */
+       DTYP_W10B,      /* 10-bit week in GPS epoch */
+       DTYP_Y4D,       /* 4-digit (full) year */
+       DTYP_WEXT       /* extended week in GPS epoch */
+};
+
 /* results for 'field_init()'
  *
  * Note: If a checksum is present, the checksum test must pass OK or the
@@ -237,6 +251,8 @@ typedef struct {
        u_char          gps_time;       /* use GPS time, not UTC */
        l_fp            last_reftime;   /* last processed reference stamp */
        TNtpDatum       last_gpsdate;   /* last processed split date/time */
+       u_short         hold_gpsdate;   /* validity ticker for above */
+       u_short         type_gpsdate;   /* date info type for above */
        /* tally stats, reset each poll cycle */
        struct
        {
@@ -407,7 +423,7 @@ nmea_start(
        up->ppsapi_fd = -1;
 #   endif /* HAVE_PPSAPI */
        ZERO(up->tally);
-       
+
        /* Initialize miscellaneous variables */
        peer->precision = PRECISION;
        pp->clockdesc = DESCRIPTION;
@@ -598,6 +614,9 @@ nmea_timer(
                up->lb_buf[0] = '\0';
                up->lb_len    = 0;
        }
+
+       if (up->hold_gpsdate && (--up->hold_gpsdate < DATE_HLIM))
+               up->type_gpsdate = DTYP_NONE;
 }
 
 /*
@@ -641,8 +660,10 @@ nmea_procrec(
        /* results of sentence/date/time parsing */
        u_char          sentence;       /* sentence tag */
        int             checkres;
+       int             warp;           /* warp to GPS base date */
        char *          cp;
        int             rc_date, rc_time;
+       u_short         rc_dtyp;
 #   ifdef HAVE_PPSAPI
        int             withpps = 0;
 #   endif /* HAVE_PPSAPI */
@@ -701,6 +722,8 @@ nmea_procrec(
                sentence = NMEA_GPZDG;
        else if (strncmp(cp,   "PGRMF,", 6) == 0)
                sentence = NMEA_PGRMF;
+       else if (strncmp(cp,   "PUBX,04,", 8) == 0)
+               sentence = NMEA_PUBX04;
        else
                return; /* not something we know about */
 
@@ -751,9 +774,18 @@ nmea_procrec(
         * Once have processed a $GPZDG, do not process any further UTC
         * sentences (all but $GPZDG currently).
         */
-       if (up->gps_time && NMEA_GPZDG != sentence) {
-               up->tally.filtered++;
-               return;
+       if (sentence == NMEA_GPZDG) {
+               if (!up->gps_time) {
+                       msyslog(LOG_INFO,
+                               "%s using GPS time as if it were UTC",
+                               refnumtoa(&peer->srcadr));
+                       up->gps_time = 1;
+               }
+       } else {
+               if (up->gps_time) {
+                       up->tally.filtered++;
+                       return;
+               }
        }
 
        DPRINTF(1, ("%s processing %d bytes, timecode '%s'\n",
@@ -764,14 +796,18 @@ nmea_procrec(
         * sensitive data from the last timecode.
         */
        rc_date = -1;   /* assume we have to do day-time mapping */
-       switch (sentence) {
+       rc_dtyp = DTYP_NONE;
+               switch (sentence) {
 
        case NMEA_GPRMC:
                /* Check quality byte, fetch data & time */
                rc_time  = parse_time(&date, &tofs, &rdata, 1);
                pp->leap = parse_qual(&rdata, 2, 'A', 0);
-               rc_date  = parse_date(&date, &rdata, 9, DATE_1_DDMMYY);
-               if (CLK_FLAG4 & pp->sloppyclockflag)
+               if (up->type_gpsdate <= DTYP_Y2D) {
+                       rc_date = parse_date(&date, &rdata, 9, DATE_1_DDMMYY);
+                       rc_dtyp = DTYP_Y2D;
+               }
+               if (CLK_FLAG4 & pp->sloppyclockflag)
                        field_wipe(&rdata, 3, 4, 5, 6, -1);
                break;
 
@@ -793,31 +829,47 @@ nmea_procrec(
 
        case NMEA_GPZDA:
                /* No quality.  Assume best, fetch time & full date */
-               pp->leap = LEAP_NOWARNING;
-               rc_time  = parse_time(&date, &tofs, &rdata, 1);
-               rc_date  = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY);
+               rc_time = parse_time(&date, &tofs, &rdata, 1);
+               if (up->type_gpsdate <= DTYP_Y4D) {
+                       rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY);
+                       rc_dtyp = DTYP_Y4D;
+               }
                break;
 
        case NMEA_GPZDG:
                /* Check quality byte, fetch time & full date */
                rc_time  = parse_time(&date, &tofs, &rdata, 1);
-               rc_date  = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY);
                pp->leap = parse_qual(&rdata, 4, '0', 1);
                --tofs.l_ui; /* GPZDG gives *following* second */
+               if (up->type_gpsdate <= DTYP_Y4D) {
+                       rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY);
+                       rc_dtyp = DTYP_Y4D;
+               }
                break;
 
        case NMEA_PGRMF:
-               /* get date, time, qualifier and GPS weektime. We need
-                * date and time-of-day for the century fix, so we read
-                * them first.
-                */
-               rc_time  = parse_gpsw(&wgps, &rdata, 1, 2, 5);
-               rc_date  = -2;
+               /* get time, qualifier and GPS weektime. */
+               rc_time = parse_time(&date, &tofs, &rdata, 4);
+               if (up->type_gpsdate <= DTYP_W10B) {
+                       rc_date = parse_gpsw(&wgps, &rdata, 1, 2, 5);
+                       rc_dtyp = DTYP_W10B;
+               }
                pp->leap = parse_qual(&rdata, 11, '0', 1);
                if (CLK_FLAG4 & pp->sloppyclockflag)
                        field_wipe(&rdata, 6, 8, -1);
                break;
 
+       case NMEA_PUBX04:
+               /* PUBX,04 is peculiar. The UTC time-of-week is the *internal*
+                * time base, which is not exactly on par with the fix time.
+                */
+               rc_time = parse_time(&date, &tofs, &rdata, 2);
+               if (up->type_gpsdate <= DTYP_WEXT) {
+                       rc_date = parse_gpsw(&wgps, &rdata, 5, 4, -1);
+                       rc_dtyp = DTYP_WEXT;
+               }
+               break;
+
        default:
                INVARIANT(0);   /* Coverity 97123 */
                return;
@@ -861,19 +913,35 @@ nmea_procrec(
                rd_timestamp = ntpfp_with_fudge(
                        rd_timestamp, pp->fudgetime2);
 
+       /* set the GPS base date, if possible */
+       warp = !(peer->ttl & NMEA_DATETRUST_MASK);
+       if (rc_dtyp != DTYP_NONE) {
+               DPRINTF(1, ("%s saving date, type=%hu\n",
+                           refnumtoa(&peer->srcadr), rc_dtyp));
+               switch (rc_dtyp) {
+               case DTYP_W10B:
+                       up->last_gpsdate = gpsntp_from_gpscal_ex(
+                               &wgps, (warp = TRUE));
+                       break;
+               case DTYP_WEXT:
+                       up->last_gpsdate = gpsntp_from_gpscal_ex(
+                               &wgps, warp);
+                       break;
+               default:
+                       up->last_gpsdate = gpsntp_from_calendar_ex(
+                               &date, tofs, warp);
+                       break;
+               }
+               up->type_gpsdate = rc_dtyp;
+               up->hold_gpsdate = DATE_HOLD;
+       }
        /* now convert and possibly extend/expand the time stamp. */
-       if (rc_date == 1) {             /* (truncated) date available   */
-               dntp = gpsntp_from_calendar_ex(
-                       &date, tofs, !(peer->ttl & NMEA_DATETRUST_MASK));
-               up->last_gpsdate = dntp;
-       } else if (rc_date == -2) {     /* full GPS week time           */
-               dntp = gpsntp_from_gpscal(&wgps);
-               up->last_gpsdate = dntp;
-       } else if (up->last_gpsdate.days) { /* time of day, based       */
-               dntp = gpsntp_from_daytime2(&date, tofs, &up->last_gpsdate);
-               up->last_gpsdate = dntp;
-       } else {                        /* time of day, floating        */
-               dntp = gpsntp_from_daytime1(&date, tofs, rd_timestamp);
+       if (up->hold_gpsdate) { /* time of day, based */
+               dntp = gpsntp_from_daytime2_ex(
+                       &date, tofs, &up->last_gpsdate, warp);
+       } else {                /* time of day, floating */
+               dntp = gpsntp_from_daytime1_ex(
+                       &date, tofs, rd_timestamp, warp);
        }
 
        if (debug) {
@@ -891,13 +959,6 @@ nmea_procrec(
 #          endif /* !HAVE_PPSAPI */
        }
 
-       /* Check if we must enter GPS time mode; log so if we do */
-       if (!up->gps_time && (sentence == NMEA_GPZDG)) {
-               msyslog(LOG_INFO, "%s using GPS time as if it were UTC",
-                       refnumtoa(&peer->srcadr));
-               up->gps_time = 1;
-       }
-
        /* Get the reference time stamp from the calendar buffer.
         * Process the new sample in the median filter and determine the
         * timecode timestamp, but only if the PPS is not in control.
@@ -974,7 +1035,7 @@ nmea_procrec(
  * timing is badly affected unless a PPS channel is also associated with
  * the clock instance. TCP leaves us nothing to improve on here.
  * -------------------------------------------------------------------
- */ 
+ */
 static void
 nmea_receive(
        struct recvbuf * rbufp
@@ -991,7 +1052,7 @@ nmea_receive(
        /* paranoia check: */
        if (up->lb_len >= sizeof(up->lb_buf))
                up->lb_len = 0;
-       
+
        /* pick up last assembly position; leave room for NUL */
        dp = up->lb_buf + up->lb_len;
        de = up->lb_buf + sizeof(up->lb_buf) - 1;
@@ -1013,7 +1074,7 @@ nmea_receive(
                } else if (ch == '\n' || ch == '\r') {
                        *dp = '\0';
                        up->lb_len = (int)(dp - up->lb_buf);
-                       dp = up->lb_buf;                                
+                       dp = up->lb_buf;
                        nmea_procrec(peer, rbufp->recv_time);
                } else if (ch >= 0x20 && ch < 0x7f) {
                        *dp++ = ch;
@@ -1388,40 +1449,6 @@ static int _parse_sep(UCC *cp, UCC ** ep)
        return rc;
 }
 
-/* /(\.[[:digit:]]*)?/ --> l_fp{0, f}
- * read fractional seconds, convert to l_fp
- *
- * Only the first 9 decimal digits are evaluated; any excess is parsed
- * away but silently ignored. (--> truncation to 1 nanosecond)
- */
-static int _parse_frac(UCC *cp, UCC ** ep, l_fp *into)
-{
-       unsigned int    ndig = 9;
-       struct timespec ts;
-
-       ZERO(ts);
-       if (*cp == '.') {
-               while (ndig && isdigit(*++cp)) {
-                       ts.tv_nsec *= 10;
-                       ts.tv_nsec += (*cp - '0');
-                       --ndig;
-               }
-               if (ndig && ts.tv_nsec) {
-                       uint32_t mmul = 10;
-                       do {
-                               if (ndig & 1)
-                                       ts.tv_nsec *= mmul;
-                               mmul *= mmul;
-                       } while (0 != (ndig >>= 1));
-               }
-               while (isdigit(*cp))
-                       ++cp;
-       }
-       *ep   = cp;
-       *into = tspec_intv_to_lfp(ts);
-       return TRUE;
-}
-
 /* /[[:digit:]]{2}/ --> uint16_t */
 static int _parse_num2d(UCC *cp, UCC ** ep, uint16_t *into)
 {
@@ -1437,15 +1464,15 @@ static int _parse_num2d(UCC *cp, UCC ** ep, uint16_t *into)
 }
 
 /* /[[:digit:]]+/ --> uint16_t */
-static int _parse_u16(UCC *cp, UCC ** ep, uint16_t *into)
+static int _parse_u16(UCC *cp, UCC **ep, uint16_t *into, unsigned int ndig)
 {
        uint16_t        num = 0;
        int             rc  = FALSE;
-       if (isdigit(*cp)) {
+       if (isdigit(*cp) && ndig) {
                rc = TRUE;
                do
-                       num = num * 10 + *cp++ - '0';
-               while (isdigit(*cp));
+                       num = (num * 10) + (*cp - '0');
+               while (isdigit(*++cp) && --ndig);
                *into = num;
        }
        *ep = cp;
@@ -1453,21 +1480,52 @@ static int _parse_u16(UCC *cp, UCC ** ep, uint16_t *into)
 }
 
 /* /[[:digit:]]+/ --> uint32_t */
-static int _parse_u32(UCC *cp, UCC ** ep, uint32_t *into)
+static int _parse_u32(UCC *cp, UCC **ep, uint32_t *into, unsigned int ndig)
 {
        uint32_t        num = 0;
        int             rc  = FALSE;
-       if (isdigit(*cp)) {
+       if (isdigit(*cp) && ndig) {
                rc = TRUE;
                do
-                       num = num * 10 + *cp++ - '0';
-               while (isdigit(*cp));
+                       num = (num * 10) + (*cp - '0');
+               while (isdigit(*++cp) && --ndig);
                *into = num;
        }
        *ep = cp;
        return rc;
 }
 
+/* /(\.[[:digit:]]*)?/ --> l_fp{0, f}
+ * read fractional seconds, convert to l_fp
+ *
+ * Only the first 9 decimal digits are evaluated; any excess is parsed
+ * away but silently ignored. (--> truncation to 1 nanosecond)
+ */
+static int _parse_frac(UCC *cp, UCC **ep, l_fp *into)
+{
+       static const uint32_t powtab[10] = {
+                       0,
+               100000000, 10000000, 1000000,
+                  100000,    10000,    1000,
+                     100,       10,       1
+       };
+
+       struct timespec ts;
+       ZERO(ts);
+       if (*cp == '.') {
+               uint32_t fval = 0;
+               UCC *    sp   = cp + 1;
+               if (_parse_u32(sp, &cp, &fval, 9))
+                       ts.tv_nsec = fval * powtab[(size_t)(cp - sp)];
+               while (isdigit(*cp))
+                       ++cp;
+       }
+
+       *ep   = cp;
+       *into = tspec_intv_to_lfp(ts);
+       return TRUE;
+}
+
 /* /[[:digit:]]{6}/ --> time-of-day
  * parses a number string representing 'HHMMSS'
  */
@@ -1529,11 +1587,11 @@ static int _parse_date3(UCC *cp, UCC **ep, TCivilDate *into)
        int             rc;
        UCC *           xp = cp;
 
-       rc =   _parse_u16(cp, &cp, &d) && (d - 1 < 31)
+       rc =   _parse_u16(cp, &cp, &d, 2) && (d - 1 < 31)
            && _parse_sep(cp, &cp)
-           && _parse_u16(cp, &cp, &m) && (m - 1 < 12)
+           && _parse_u16(cp, &cp, &m, 2) && (m - 1 < 12)
            && _parse_sep(cp, &cp)
-           && _parse_u16(cp, &cp, &y) && (y > 1980)
+           && _parse_u16(cp, &cp, &y, 4) && (y > 1980)
            && _parse_eof(cp, ep);
        if (rc) {
                into->monthday = (uint8_t )d;
@@ -1647,31 +1705,34 @@ parse_gpsw(
        )
 {
        uint32_t        secs;
-       uint16_t        week, leap;
+       uint16_t        week, leap = 0;
        l_fp            fofs;
        int             rc;
 
        UCC *   dpw = (UCC*)field_parse(rd, weekidx);
        UCC *   dps = (UCC*)field_parse(rd, timeidx);
-       UCC *   dpl = (UCC*)field_parse(rd, leapidx);
 
-       rc =   _parse_u16 (dpw, &dpw, &week)
-           && _parse_eof (dpw, &dpw)           && (week < 1024)
-           && _parse_u32 (dps, &dps, &secs)
+       rc =   _parse_u16 (dpw, &dpw, &week, 5)
+           && _parse_eof (dpw, &dpw)
+           && _parse_u32 (dps, &dps, &secs, 9)
            && _parse_frac(dps, &dps, &fofs)
-           && _parse_eof (dps, &dps)           && (secs < 7*SECSPERDAY)
-           && _parse_u16 (dpl, &dpl, &leap)
-           && _parse_eof (dpl, &dpl);
-
+           && _parse_eof (dps, &dps)
+           && (secs < 7*SECSPERDAY);
+       if (rc && leapidx > 0) {
+               UCC *   dpl = (UCC*)field_parse(rd, leapidx);
+               rc =   _parse_u16 (dpl, &dpl, &leap, 5)
+                   && _parse_eof (dpl, &dpl);
+       }
        if (rc) {
                fofs.l_ui -= leap;
                *wd = gpscal_from_gpsweek(week, secs, fofs);
        } else {
-               DPRINTF(1, ("nmea: parse_weekdata: invalid weektime spec\n"));
+               DPRINTF(1, ("nmea: parse_gpsw: invalid weektime spec\n"));
        }
        return rc;
 }
 
+
 #ifdef HAVE_PPSAPI
 static double
 tabsdiffd(