]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 2808] GPSD_JSON driver enhancements, step 1
authorJuergen Perlinger <perlinger@ntp.org>
Mon, 13 Apr 2015 18:39:08 +0000 (20:39 +0200)
committerJuergen Perlinger <perlinger@ntp.org>
Mon, 13 Apr 2015 18:39:08 +0000 (20:39 +0200)
bk: 552c0d4cpnRd1PUiIkQWVXqqr06iuA

ChangeLog
html/drivers/driver46.html
libjsmn/Makefile
libjsmn/README.md
libjsmn/example/jsondump.c [new file with mode: 0644]
libjsmn/example/simple.c [new file with mode: 0644]
libjsmn/jsmn.c
libjsmn/jsmn.h
libjsmn/jsmn_test.c
ntpd/refclock_gpsdjson.c

index e75384c88fab05e526d0397671f961ed748deb4c..66bf1ab6c743ca21176a054addaaec5e27b61f83 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,8 @@
 ---
+* [Bug 2808] GPSD_JSON driver enhancements, step 1
+  Various improvements, see bugs.ntp.org for details
+  Changed libjsmn to a more recent version
+---
 (4.2.8p2) 2015/04/07 Released by Harlan Stenn <stenn@ntp.org>
 (4.2.8p2-RC3) 2015/04/03 Released by Harlan Stenn <stenn@ntp.org>
 
index 40aded80cca101bd498ca44d98ea6b7f782db19c..f52412f5d02adff2b2f0da484a1bcc9cf5bbcf9c 100644 (file)
@@ -14,7 +14,7 @@
   <body>
     <h3>GPSD NG client driver</h3>
 <p>Last update:
-  <!-- #BeginDate format:En2m -->1-Mar-2014  03:48<!-- #EndDate -->
+  <!-- #BeginDate format:En2m -->3-Apr-2015  10:32<!-- #EndDate -->
   UTC</p>
     <hr>
     <h4>Synopsis</h4>
@@ -28,8 +28,9 @@
       Features: <tt></tt>
     </p>
 
-    <h4>Description</h4>
+    <!-- --------------------------------------------------------- -->
 
+    <br><h4>Description</h4>
     <p>
       This driver is a client driver to the <i>GPSD</i> daemon, which
       over the time became increasingly popular for UN*Xish
     <p>
       The overall accuracy depends on the receiver used. The driver
       uses the error estimations (95% probability limits) provided by
-      <i>GPSD</i> to set the clock precision dynamically according to these
-      readings.
+      <i>GPSD</i> to set the clock precision dynamically according to
+      these readings.
     </p>
     <p>
-      The driver needs the VERSION, TPV, PPS and WATCH objects of
-      the <i>GPSD</i> protocol. (Others are quietly ignored.)
+      The driver needs the VERSION, TPV, PPS, WATCH and TOFF objects
+      of the <i>GPSD</i> protocol. (Others are quietly ignored.) The
+      driver can operate without the TOFF objects, which are available
+      with the <i>protocol</i> version 3.10 and above. (Not to be
+      confused with the <i>release</i> version of <i>GPSD</i>!)
+      Running without TOFF objects has a negative impact on the jitter
+      and offset of the serial timing information; if possible, a
+      version of <i>GPSD</i> with support for TOFF objects should be
+      used.
+    </p>
+    <p>The acronym <u>STI</u> is used here as a synonym for <i>serial
+      time information</i> from the data channel of the receiver, no
+      matter what objects were used to obtain it.
     </p>
 
+    <!-- --------------------------------------------------------- -->
 
-    <h4>Naming a Device</h4>
+    <br><h4>Naming a Device</h4>
     <p>
-      The <i>GPSD</i> driver uses the same name as the NMEA driver,
-      namely <tt>/dev/gps<i>u</i></tt>. There is a simple reason for
-      that: While the NMEA driver and the <i>GPSD</i> driver can be
-      active at the same time <b>for different devices</b>,
-      they cannot access the same device at a time. Having the same
-      name helps on that. It also eases migration from using NMEA
-      directly to using <i>GPSD</i>, as no new links etc need to be
-      created.
+      The <i>GPSD</i> driver uses the same device name as the NMEA
+      driver, namely <tt>/dev/gps<i>u</i></tt>. There is a simple
+      reason for that: While the NMEA driver and the <i>GPSD</i>
+      driver can be active at the same time <b>for different
+      devices</b>, they cannot access the same device at a
+      time. Having the same name helps on that. It also eases
+      migration from using NMEA directly to using <i>GPSD</i>, as no
+      new links etc need to be created.
     </p>
     <p>
       <i>GPSD</i> is normally started with the device name to access;
       identification. This makes the migration from the built-in NMEA
       driver a bit easier.
     </p>
-    <p><b>Note:</b> <i>GPSD</i> (as of version 3.10) cannot
-      use kernel mode PPS on devices that are hot-plugged. This would
-      require to attach the PPS line discipline to the file, which is
-      not possible when running with root privileges dropped. This is
-      not likely to change in the future.
+    <p><b>Note:</b> <i>GPSD</i> (as of version 3.10) cannot use kernel
+      mode PPS on devices that are hot-plugged. This would require to
+      attach the PPS line discipline to the character special file,
+      which is not possible when running with root privileges already
+      dropped. This is not likely to change in the future.
     </p>
 
-    <h4>The 'mode' byte</h4>
+    <!-- --------------------------------------------------------- -->
+
+    <br><h4>The 'mode' word</h4>
     <p>
       A few operation modes can be selected with the mode word.
     </p>
       <th colspan="3">The Mode Word</th>
        <tr> <td>Bits</td><td>Value</td><td>Description</td>
        </tr>
-       <tr> <td rowspan="4"align="center">0..1</td><td align="center">0</td>
-         <td>Uses TPV to get absolute time stamps for full
-         synchronization. If PPS is available , it is used to improve
-         the precision, but the clock can work without it.</td>
+       <tr> <td rowspan="4"align="center">0..1</td>
+         <td align="center">0</td>
+         <td>STI only operation. This mode is affected by the timing
+           stability of whatever protocol is used between the GPS
+           device and GPSD.
+           <br>
+           Running on STI only is not recommended in general. Possible
+           use cases include:
+           <ul>
+             <li>The receiver does not provide a PPS signal.
+             <li>The receiver <i>does</i> provide a PPS signal and
+             the secondary PPS unit is used.
+             <li>The receiver has a stable serial timing and a proper
+             fudge can be established.
+             <li>You have other time sources available and want to
+               establish a useful fudge value for <tt>time2</tt>.
+           </ul>
+         </td>
        </tr>
-       <tr><td align="center">1</td>
-         <td>Require TPV <b>and</b> PPS to work.</td>
+       <tr>
+         <td align="center">1</td>
+         <td>Strict operation. This mode needs a valid PPS and a
+           valid STI to combine the absolute time from the STI with
+           the time stamp from the PPS record. Does not feed clock
+           samples if no valid PPS+STI pair is available.
+           <br><br>
+           This type of operation results in an ordinary clock with a
+           very low jitter as long as the PPS data is available, but
+           the clock fails once PPS drops out. This mode is a
+           possible choice for receivers that provide a PPS signal
+           most of the time but have an unstable serial timing that
+           cannot be fudge-compensated.
+         </td>
        </tr>
        <tr><td align="center">2</td>
-         <td>Ignore PPS data, run on TPV only. This is not a
-         recommended mode unless the serial timing is very stable
-         and GPSD provides an information element in TPV that
-         indicates the receive time of the fix data.</td>
+         <td>Automatic mode. Tries to operate in strict mode unless
+           it fails to process valid samples for some time, currently
+           120s. Then it reverts to STI-only operation until the PPS
+           is stable again for 40s, when strict mode is engaged
+           again.
+           <br><br><b>Important Notice: This is an expiremental
+           feature!</b><br>  Switching between strict and STI-only
+           mode will cause changes in offset and jitter. Use this
+           mode only if STI-only works fairly well with your setup,
+           or if you expect longer dropouts of the PPS signal and
+           prefer to use STI alone over not getting synchronised at
+           all.</td>
        </tr>
-       <tr><td align="center">3</td>
-         <td>PPS-only mode. Ignores TPV and does only the PPS phase
-         correction. This means that some other source must get NTPD
-         close to synchronisation; only after that happened and the
-         phase shift between the system clock and the PPS pulse is
-         less than 125msec the PPS lock will be engaged.</td>
+       <tr>
+         <td align="center">3</td>
+         <td><i>(reserved for future extension, do not use)</i></td>
+       </tr>
+       <tr>
+         <td align="center">2..31</td>
+         <td colspan="2"><i>(reserved for future extension, do not
+         use)</i></td>
        </tr>
-       <tf colspan="3"><b>IMPORTANT: work in progress, mode
-       word ignored right now. Fixed mode '0' operation.</b></tf>
       </table>
     </p>
 
-    <h4>Syslog flood throttle</h4>
+    <!-- --------------------------------------------------------- -->
+
+    <br><h4>Syslog flood throttle</h4>
     <p>This driver can create a lot of syslog messages when things go
-    wrong, and cluttering the log files is frowned upon. So we attempt
-    to log persistent or recurring errors only once per hour. On the
-    other hand, when tracking a problem the syslog flood throttle can
-    get into the way.</p>
+      wrong, and cluttering the log files is frowned upon. So we
+      attempt to log persistent or recurring errors only once per
+      hour. On the other hand, when tracking a problem the syslog
+      flood throttle can get into the way.</p>
     <p>Therefore, fudge <i>flag3</i> can be used to <i>disable</i> the
-    flood throttle at any time; the throttle is engaged by
-    default. Running with the syslog flood throttle disabled for
-    lengthy time is not recommended unless the log files are closely
-    monitored.</p>
+      flood throttle at any time; the throttle is engaged by
+      default. Running with the syslog flood throttle disabled for
+      lengthy time is not recommended unless the log files are closely
+      monitored.</p>
 
-    <h4>Fudge Factors</h4>
+    <!-- --------------------------------------------------------- -->
+
+    <br><h4>PPS secondary clock unit</h4>
+    <p>Units with numbers &ge;128 act as secondary clock unit for the
+      primary clock unit (u mod 128). A secondary unit processes only
+      the PPS data from <i>GPSD</i> and needs the corresponding master
+      unit to work<a href="#fn1" name="fn1bl"><sup>1</sup></a>. Use
+      the 'noselect' keyword on the primary unit if you are not
+      interested in its data.
+    </p><p>The secondary unit employs the usual precautions before
+      feeding clock samples:</p>
+    <ul>
+      <li>The system must be already in a synchronised state.    
+      <li>The system offset must be less than 400ms absolute.
+      <li>The phase adjustment from the PPS signal must also be less
+       than 400ms absolute.
+    </ul>
+    <p>If fudge flag <tt>flag1</tt> is set for the secondary unit, the
+      unit asserts the PPS flag on the clock as long as PPS data is
+      available. This makes the unit eligible as PPS peer and should
+      only be used if the GPS receiver can be trusted for the quality
+      of its PPS signal<a href="fn2"
+      name="fn2bl"><sup>2</sup></a>. The PPS flag gets cleared if no
+      PPS records can be aquired for some time. The unit also flushes
+      the sample buffer at this point to avoid the use of stale PPS
+      data.</p>
+    <p><b>Attention:</b> This unit uses its own PPS fudge value
+      which must be set as fudge <tt>time1</tt>. Only the fudge
+      values <tt>time1</tt> and <tt>flag1</tt> have an impact on secondary
+      units.</p>
+
+    <!-- --------------------------------------------------------- -->
+
+    <br><h4>Clockstats</h4>
+    <p>If flag4 is set when the driver is polled, a clockstats record
+      is written for the primary clock unit. (The secondary PPS unit
+      does not provide clock stats yet.) The first 3 fields are the
+      normal date, time, and IP address common to all clockstats
+      records.
+    </p><p>
+      The 4th field is the number of good time samples processed
+      since the last poll.
+    </p><p>
+      The 5th field is the number of bad replies since the last
+      poll. A bad reply is considered malformed when it is missing
+      vital fields or the fields contain malformed data. If the data
+      indicates that the GPS receiver has no valid fix the sample is
+      also accounted as bad.
+    </p><p>
+      The 6th field is the number of received and known JSON records
+      since the last poll. (<b>Note:</b> Since the sample data is
+      collected from several JSON records and some JSON data is not
+      related to samples, this is <i>not</i> the sum of the other
+      fields.
+    </p>
+
+    <!-- --------------------------------------------------------- -->
+
+    <br><h4>Fudge Factors</h4>
 
     <dl>
       <dt><tt>time1 <i>time</i></tt></dt>
       <dd>Specifies the PPS time offset calibration factor, in seconds
       and fraction, with default 0.0.</dd>
       <dt><a name="fudgetime2"><tt>time2 <i>time</i></tt></a></dt>
-      <dd>Specifies the TPV time offset calibration factor, in seconds
-      and fraction, with default 0.0.</dd>
+      <dd><em>[Primary Unit]</em> Specifies the TPV/TIME time offset
+      calibration factor, in seconds and fraction, with default
+      0.0.</dd>
       <dt><tt>stratum <i>number</i></tt></dt>
-      <dd>Specifies the driver stratum, in decimal from 0 to 15, with default 0.</dd>
+      <dd>Specifies the driver stratum, in decimal from 0 to 15, with
+       default 0.</dd>
       <dt><tt>refid <i>string</i></tt></dt>
       <dd>Specifies the driver reference identifier, an ASCII string
        from one to four characters, with default <tt>GPSD</tt>.</dd>
-      <dt><tt>flag1 0 | 1</tt></dt><dd><i>(not used)</i></dd>
-      <dt><tt>flag2 0 | 1</tt></dt><dd><i>(not used)</i></dd>
-      <dt><tt>flag3 0 | 1</tt></dt><dd>If set, <i>disable</i> the
-      log throttle. Useful when tracking problems in the interaction
-      between <i>GPSD</i> and <i>NTPD</i>, since now all error
-      events are logged. Persistent/recurrent errors can easily fill
-      up the log, so this should only be enabled during bug
-      hunts.</dd>
-      <dt><tt>flag4 0 | 1</tt></dt><dd>If set, write a clock stats
-      line on every poll cycle.</dd>
+      <dt><tt>flag1 0 | 1</tt></dt><dd><em>[<b>Secondary</b>
+         Unit]</em> When set, flags the secondary clock unit as a
+       potential PPS peer as long as good PPS data is available.
+      </dd>
+      <dt><tt>flag2 0 | 1</tt></dt>
+      <dd><em>[Primary Unit]</em> When set, <u>disables</u> the
+       processing of incoming PPS records. Intended as an aide to
+       test the effects of a PPS dropout when using automatic mode
+       (mode 2).
+      </dd>
+      <dt><tt>flag3 0 | 1</tt></dt><dd><em>[Primary Unit]</em>
+      If set, <u>disables</u> the log throttle. Useful when tracking
+      problems in the interaction between <i>GPSD</i> and <i>NTPD</i>,
+      since now all error events are logged. Persistent/recurrent
+      errors can easily fill up the log, so this should only be
+      enabled during bug hunts.</dd>
+      <dt><tt>flag4 0 | 1</tt></dt><dd><em>[Primary Unit]</em>
+       If set, write a clock stats line on every poll cycle.
+      </dd>
     </dl>
 
+    <!-- -- footnotes -------------------------------------------- -->
+
+    <hr>
+    <p><a name="fn1" href="#fn1bl"><sup>1</sup>) </a>Data transmission
+      an decoding is done only once by the primary unit. The decoded
+      data is then processed independently in both clock units. This
+      avoids double transmission over two sockets and decoding the
+      same data twice, but the primary unit is always needed as a
+      downside of this approach.
+    </p>
+    <p><a name="fn2" href="#fn2bl"><sup>2</sup>) </a>The clock driver
+      suppresses the processing PPS records when the TPV/TIME data
+      indicates the receiver has no fix. It can also deal with
+      situations where the PPS signal is not delivered
+      to <i>GPSD</i>. But once it is available, it is also processed
+      and used to create samples. If a receiver cannot be trusted for
+      the precision of its PPS signal, it should not be used to create
+      a possible PPS peer: These get extra clout and can effectively
+      become the sole source of input for the control loop. You do not
+      want to use sloppy data for that.
+    <hr>
     <p>Additional Information</p>
     <p><a href="../refclock.html">Reference Clock Drivers</a></p>
     <hr>
index ac947a3af8eb8841a6223cf3085b9993428ab9d6..5e3e2a97f463d0f9af265e9af6c167ef1e457d99 100644 (file)
@@ -13,15 +13,23 @@ test: jsmn_test
        ./jsmn_test
 
 jsmn_test: jsmn_test.o
-       $(CC) -L. -ljsmn $< -o $@
+       $(CC) $(LDFLAGS) -L. -ljsmn $< -o $@
 
 jsmn_test.o: jsmn_test.c libjsmn.a
 
+simple_example: example/simple.o libjsmn.a
+       $(CC) $(LDFLAGS) $^ -o $@
+
+jsondump: example/jsondump.o libjsmn.a
+       $(CC) $(LDFLAGS) $^ -o $@
+
 clean:
-       rm -f jsmn.o jsmn_test.o
+       rm -f jsmn.o jsmn_test.o example/simple.o
        rm -f jsmn_test
        rm -f jsmn_test.exe
        rm -f libjsmn.a
+       rm -f simple_example
+       rm -f jsondump
 
 .PHONY: all clean test
 
index abccffa432473f34abe36ba4351bd309b7de84a6..353af94a8ee6a6f59a2e3fd51b000dee2606073e 100644 (file)
@@ -82,9 +82,8 @@ To clone the repository you should have mercurial installed. Just run:
 
        $ hg clone http://bitbucket.org/zserge/jsmn jsmn
 
-Repository layout is simple: jsmn.c and jsmn.h are library files; demo.c is an
-example of how to use jsmn (it is also used in unit tests); test.sh is a test
-script. You will also find README, LICENSE and Makefile files inside.
+Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in
+the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside.
 
 To build the library, run `make`. It is also recommended to run `make test`.
 Let me know, if some tests fail.
@@ -127,20 +126,27 @@ to simplify string extraction from JSON data.
 
 All job is done by `jsmn_parser` object. You can initialize a new parser using:
 
-       struct jsmn_parser parser;
+       jsmn_parser parser;
        jsmntok_t tokens[10];
 
+       jsmn_init(&parser);
+
        // js - pointer to JSON string
        // tokens - an array of tokens available
        // 10 - number of tokens available
-       jsmn_init_parser(&parser, js, tokens, 10);
+       jsmn_parse(&parser, js, tokens, 10);
+
+This will create a parser, and then it tries to parse up to 10 JSON tokens from
+the `js` string.
 
-This will create a parser, that can parse up to 10 JSON tokens from `js` string.
+A non-negative reutrn value of `jsmn_parse` is the number of tokens actually
+used by the parser.
+Passing NULL instead of the tokens array would not store parsing results, but
+instead the function will return the value of tokens needed to parse the given
+string. This can be useful if you don't know yet how many tokens to allocate.
 
-Later, you can use `jsmn_parse(&parser)` function to process JSON string with the parser.
 If something goes wrong, you will get an error. Error will be one of these:
 
-* `JSMN_SUCCESS` - everything went fine. String was parsed
 * `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted
 * `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large
 * `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data
diff --git a/libjsmn/example/jsondump.c b/libjsmn/example/jsondump.c
new file mode 100644 (file)
index 0000000..3490bbf
--- /dev/null
@@ -0,0 +1,112 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "../jsmn.h"
+
+/*
+ * An example of reading JSON from stdin and printing its content to stdout.
+ * The output looks like YAML, but I'm not sure if it's really compatible.
+ */
+
+static int dump(const char *js, jsmntok_t *t, size_t count, int indent) {
+       int i, j, k;
+       if (count == 0) {
+               return 0;
+       }
+       if (t->type == JSMN_PRIMITIVE) {
+               printf("%.*s", t->end - t->start, js+t->start);
+               return 1;
+       } else if (t->type == JSMN_STRING) {
+               printf("'%.*s'", t->end - t->start, js+t->start);
+               return 1;
+       } else if (t->type == JSMN_OBJECT) {
+               printf("\n");
+               j = 0;
+               for (i = 0; i < t->size; i++) {
+                       for (k = 0; k < indent; k++) printf("  ");
+                       j += dump(js, t+1+j, count-j, indent+1);
+                       printf(": ");
+                       j += dump(js, t+1+j, count-j, indent+1);
+                       printf("\n");
+               }
+               return j+1;
+       } else if (t->type == JSMN_ARRAY) {
+               j = 0;
+               printf("\n");
+               for (i = 0; i < t->size; i++) {
+                       for (k = 0; k < indent-1; k++) printf("  ");
+                       printf("   - ");
+                       j += dump(js, t+1+j, count-j, indent+1);
+                       printf("\n");
+               }
+               return j+1;
+       }
+       return 0;
+}
+
+int main() {
+       int r;
+       int eof_expected = 0;
+       char *js = NULL;
+       size_t jslen = 0;
+       char buf[BUFSIZ];
+
+       jsmn_parser p;
+       jsmntok_t *tok;
+       size_t tokcount = 2;
+
+       /* Prepare parser */
+       jsmn_init(&p);
+
+       /* Allocate some tokens as a start */
+       tok = malloc(sizeof(*tok) * tokcount);
+       if (tok == NULL) {
+               fprintf(stderr, "malloc(): errno=%d\n", errno);
+               return 3;
+       }
+
+       for (;;) {
+               /* Read another chunk */
+               r = fread(buf, 1, sizeof(buf), stdin);
+               if (r < 0) {
+                       fprintf(stderr, "fread(): %d, errno=%d\n", r, errno);
+                       return 1;
+               }
+               if (r == 0) {
+                       if (eof_expected != 0) {
+                               return 0;
+                       } else {
+                               fprintf(stderr, "fread(): unexpected EOF\n");
+                               return 2;
+                       }
+               }
+
+               js = realloc(js, jslen + r + 1);
+               if (js == NULL) {
+                       fprintf(stderr, "realloc(): errno=%d\n", errno);
+                       return 3;
+               }
+               strncpy(js + jslen, buf, r);
+               jslen = jslen + r;
+
+again:
+               r = jsmn_parse(&p, js, jslen, tok, tokcount);
+               if (r < 0) {
+                       if (r == JSMN_ERROR_NOMEM) {
+                               tokcount = tokcount * 2;
+                               tok = realloc(tok, sizeof(*tok) * tokcount);
+                               if (tok == NULL) {
+                                       fprintf(stderr, "realloc(): errno=%d\n", errno);
+                                       return 3;
+                               }
+                               goto again;
+                       }
+               } else {
+                       dump(js, tok, p.toknext, 0);
+                       eof_expected = 1;
+               }
+       }
+
+       return 0;
+}
diff --git a/libjsmn/example/simple.c b/libjsmn/example/simple.c
new file mode 100644 (file)
index 0000000..a6f8e6a
--- /dev/null
@@ -0,0 +1,75 @@
+#include <stdio.h>
+#include <string.h>
+#include "../jsmn.h"
+
+/*
+ * A small example of jsmn parsing when JSON structure is known and number of
+ * tokens is predictable.
+ */
+
+const char *JSON_STRING =
+       "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n  "
+       "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";
+
+static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
+       if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
+                       strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+               return 0;
+       }
+       return -1;
+}
+
+int main() {
+       int i;
+       int r;
+       jsmn_parser p;
+       jsmntok_t t[128]; /* We expect no more than 128 tokens */
+
+       jsmn_init(&p);
+       r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t)/sizeof(t[0]));
+       if (r < 0) {
+               printf("Failed to parse JSON: %d\n", r);
+               return 1;
+       }
+
+       /* Assume the top-level element is an object */
+       if (r < 1 || t[0].type != JSMN_OBJECT) {
+               printf("Object expected\n");
+               return 1;
+       }
+
+       /* Loop over all keys of the root object */
+       for (i = 1; i < r; i++) {
+               if (jsoneq(JSON_STRING, &t[i], "user") == 0) {
+                       /* We may use strndup() to fetch string value */
+                       printf("- User: %.*s\n", t[i+1].end-t[i+1].start,
+                                       JSON_STRING + t[i+1].start);
+                       i++;
+               } else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) {
+                       /* We may additionally check if the value is either "true" or "false" */
+                       printf("- Admin: %.*s\n", t[i+1].end-t[i+1].start,
+                                       JSON_STRING + t[i+1].start);
+                       i++;
+               } else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) {
+                       /* We may want to do strtol() here to get numeric value */
+                       printf("- UID: %.*s\n", t[i+1].end-t[i+1].start,
+                                       JSON_STRING + t[i+1].start);
+                       i++;
+               } else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) {
+                       int j;
+                       printf("- Groups:\n");
+                       if (t[i+1].type != JSMN_ARRAY) {
+                               continue; /* We expect groups to be an array of strings */
+                       }
+                       for (j = 0; j < t[i+1].size; j++) {
+                               jsmntok_t *g = &t[i+j+2];
+                               printf("  * %.*s\n", g->end - g->start, JSON_STRING + g->start);
+                       }
+                       i += t[i+1].size + 1;
+               } else {
+                       printf("Unexpected key: %.*s\n", t[i].end-t[i].start,
+                                       JSON_STRING + t[i].start);
+               }
+       }
+       return 0;
+}
index aa8b12b8020dae1fdc0fb1e096d0c0db2cbdad21..a0f4f69c6710939fefb2c4d04cbfa5b0e5a19946 100644 (file)
@@ -1,15 +1,14 @@
 #include <stdlib.h>
-#include <string.h>
 
 #include "jsmn.h"
 
 /**
  * Allocates a fresh unused token from the token pull.
  */
-static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, 
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
                jsmntok_t *tokens, size_t num_tokens) {
        jsmntok_t *tok;
-       if ((size_t)parser->toknext >= num_tokens) {
+       if (parser->toknext >= num_tokens) {
                return NULL;
        }
        tok = &tokens[parser->toknext++];
@@ -24,7 +23,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
 /**
  * Fills token type and boundaries.
  */
-static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, 
+static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
                             int start, int end) {
        token->type = type;
        token->start = start;
@@ -36,13 +35,13 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
  * Fills next available token with JSON primitive.
  */
 static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
-               jsmntok_t *tokens, size_t num_tokens) {
+               size_t len, jsmntok_t *tokens, size_t num_tokens) {
        jsmntok_t *token;
        int start;
 
        start = parser->pos;
 
-       for (; js[parser->pos] != '\0'; parser->pos++) {
+       for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
                switch (js[parser->pos]) {
 #ifndef JSMN_STRICT
                        /* In strict mode primitive must be followed by "," or "}" or "]" */
@@ -64,6 +63,10 @@ static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
 #endif
 
 found:
+       if (tokens == NULL) {
+               parser->pos--;
+               return 0;
+       }
        token = jsmn_alloc_token(parser, tokens, num_tokens);
        if (token == NULL) {
                parser->pos = start;
@@ -74,14 +77,14 @@ found:
        token->parent = parser->toksuper;
 #endif
        parser->pos--;
-       return JSMN_SUCCESS;
+       return 0;
 }
 
 /**
  * Filsl next token with JSON string.
  */
 static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
-               jsmntok_t *tokens, size_t num_tokens) {
+               size_t len, jsmntok_t *tokens, size_t num_tokens) {
        jsmntok_t *token;
 
        int start = parser->pos;
@@ -89,11 +92,14 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
        parser->pos++;
 
        /* Skip starting quote */
-       for (; js[parser->pos] != '\0'; parser->pos++) {
+       for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
                char c = js[parser->pos];
 
                /* Quote: end of string */
                if (c == '\"') {
+                       if (tokens == NULL) {
+                               return 0;
+                       }
                        token = jsmn_alloc_token(parser, tokens, num_tokens);
                        if (token == NULL) {
                                parser->pos = start;
@@ -103,13 +109,12 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
 #ifdef JSMN_PARENT_LINKS
                        token->parent = parser->toksuper;
 #endif
-                       return JSMN_SUCCESS;
+                       return 0;
                }
 
                /* Backslash: Quoted symbol expected */
-               if (c == '\\') {
-                       int i = 0;
-
+               if (c == '\\' && parser->pos + 1 < len) {
+                       int i;
                        parser->pos++;
                        switch (js[parser->pos]) {
                                /* Allowed escaped symbols */
@@ -119,7 +124,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
                                /* Allows escaped symbol \uXXXX */
                                case 'u':
                                        parser->pos++;
-                                       for(; i < 4 && js[parser->pos] != '\0'; i++) {
+                                       for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
                                                /* If it isn't a hex character we have an error */
                                                if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
                                                                        (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
@@ -145,19 +150,24 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
 /**
  * Parse JSON string and fill tokens.
  */
-jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, 
-               unsigned int num_tokens) {
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
+               jsmntok_t *tokens, unsigned int num_tokens) {
        jsmnerr_t r;
        int i;
        jsmntok_t *token;
+       int count = 0;
 
-       for (; js[parser->pos] != '\0'; parser->pos++) {
+       for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
                char c;
                jsmntype_t type;
 
                c = js[parser->pos];
                switch (c) {
                        case '{': case '[':
+                               count++;
+                               if (tokens == NULL) {
+                                       break;
+                               }
                                token = jsmn_alloc_token(parser, tokens, num_tokens);
                                if (token == NULL)
                                        return JSMN_ERROR_NOMEM;
@@ -172,6 +182,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens,
                                parser->toksuper = parser->toknext - 1;
                                break;
                        case '}': case ']':
+                               if (tokens == NULL)
+                                       break;
                                type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
 #ifdef JSMN_PARENT_LINKS
                                if (parser->toknext < 1) {
@@ -216,25 +228,56 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens,
 #endif
                                break;
                        case '\"':
-                               r = jsmn_parse_string(parser, js, tokens, num_tokens);
+                               r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
                                if (r < 0) return r;
-                               if (parser->toksuper != -1)
+                               count++;
+                               if (parser->toksuper != -1 && tokens != NULL)
                                        tokens[parser->toksuper].size++;
                                break;
-                       case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': 
+                       case '\t' : case '\r' : case '\n' : case ' ':
+                               break;
+                       case ':':
+                               parser->toksuper = parser->toknext - 1;
+                               break;
+                       case ',':
+                               if (tokens != NULL &&
+                                               tokens[parser->toksuper].type != JSMN_ARRAY &&
+                                               tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+                                       parser->toksuper = tokens[parser->toksuper].parent;
+#else
+                                       for (i = parser->toknext - 1; i >= 0; i--) {
+                                               if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+                                                       if (tokens[i].start != -1 && tokens[i].end == -1) {
+                                                               parser->toksuper = i;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+#endif
+                               }
                                break;
 #ifdef JSMN_STRICT
                        /* In strict mode primitives are: numbers and booleans */
                        case '-': case '0': case '1' : case '2': case '3' : case '4':
                        case '5': case '6': case '7' : case '8': case '9':
                        case 't': case 'f': case 'n' :
+                               /* And they must not be keys of the object */
+                               if (tokens != NULL) {
+                                       jsmntok_t *t = &tokens[parser->toksuper];
+                                       if (t->type == JSMN_OBJECT ||
+                                                       (t->type == JSMN_STRING && t->size != 0)) {
+                                               return JSMN_ERROR_INVAL;
+                                       }
+                               }
 #else
                        /* In non-strict mode every unquoted value is a primitive */
                        default:
 #endif
-                               r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+                               r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
                                if (r < 0) return r;
-                               if (parser->toksuper != -1)
+                               count++;
+                               if (parser->toksuper != -1 && tokens != NULL)
                                        tokens[parser->toksuper].size++;
                                break;
 
@@ -243,7 +286,6 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens,
                        default:
                                return JSMN_ERROR_INVAL;
 #endif
-
                }
        }
 
@@ -254,11 +296,11 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens,
                }
        }
 
-       return JSMN_SUCCESS;
+       return count;
 }
 
 /**
- * Creates a new parser based over a given  buffer with an array of tokens 
+ * Creates a new parser based over a given  buffer with an array of tokens
  * available.
  */
 void jsmn_init(jsmn_parser *parser) {
index 03b2c1aa4e48e6b3646b66d7c857c8b463ee6166..95fb2cabd4d8b0769adafa28f10ca12f252b9724 100644 (file)
@@ -1,6 +1,12 @@
 #ifndef __JSMN_H_
 #define __JSMN_H_
 
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /**
  * JSON type identifier. Basic types are:
  *     o Object
@@ -21,9 +27,7 @@ typedef enum {
        /* Invalid character inside JSON string */
        JSMN_ERROR_INVAL = -2,
        /* The string is not a full JSON packet, more bytes expected */
-       JSMN_ERROR_PART = -3,
-       /* Everything was fine */
-       JSMN_SUCCESS = 0
+       JSMN_ERROR_PART = -3
 } jsmnerr_t;
 
 /**
@@ -48,7 +52,7 @@ typedef struct {
  */
 typedef struct {
        unsigned int pos; /* offset in the JSON string */
-       int toknext; /* next token to allocate */
+       unsigned int toknext; /* next token to allocate */
        int toksuper; /* superior token node, e.g parent object or array */
 } jsmn_parser;
 
@@ -61,7 +65,11 @@ void jsmn_init(jsmn_parser *parser);
  * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
  * a single JSON object.
  */
-jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, 
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
                jsmntok_t *tokens, unsigned int num_tokens);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* __JSMN_H_ */
index fe5f0004555b8a67fb0880ef4000cb2d89dd8524..39688592293cda07d39bb1456af62ec6ba5f33a3 100644 (file)
@@ -2,8 +2,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "jsmn.c"
-
 static int test_passed = 0;
 static int test_failed = 0;
 
@@ -40,6 +38,9 @@ static void test(int (*func)(void), const char *name) {
        printf("start: %d, end: %d, type: %d, size: %d\n", \
                        (t).start, (t).end, (t).type, (t).size)
 
+#define JSMN_STRICT
+#include "jsmn.c"
+
 int test_empty() {
        const char *js;
        int r;
@@ -48,30 +49,30 @@ int test_empty() {
 
        js = "{}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, t, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), t, 10);
+       check(r >= 0);
        check(t[0].type == JSMN_OBJECT);
        check(t[0].start == 0 && t[0].end == 2);
 
        js = "[]";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, t, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), t, 10);
+       check(r >= 0);
        check(t[0].type == JSMN_ARRAY);
        check(t[0].start == 0 && t[0].end == 2);
 
        js = "{\"a\":[]}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, t, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), t, 10);
+       check(r >= 0);
        check(t[0].type == JSMN_OBJECT && t[0].start == 0 && t[0].end == 8);
        check(t[1].type == JSMN_STRING && t[1].start == 2 && t[1].end == 3);
        check(t[2].type == JSMN_ARRAY && t[2].start == 5 && t[2].end == 7);
 
        js = "[{},{}]";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, t, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), t, 10);
+       check(r >= 0);
        check(t[0].type == JSMN_ARRAY && t[0].start == 0 && t[0].end == 7);
        check(t[1].type == JSMN_OBJECT && t[1].start == 1 && t[1].end == 3);
        check(t[2].type == JSMN_OBJECT && t[2].start == 4 && t[2].end == 6);
@@ -87,8 +88,8 @@ int test_simple() {
        js = "{\"a\": 0}";
 
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
        check(TOKEN_EQ(tokens[0], 0, 8, JSMN_OBJECT));
        check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING));
        check(TOKEN_EQ(tokens[2], 6, 7, JSMN_PRIMITIVE));
@@ -99,59 +100,59 @@ int test_simple() {
 
        jsmn_init(&p);
        js = "[\"a\":{},\"b\":{}]";
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
 
        jsmn_init(&p);
        js = "{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }";
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
 
        return 0;
 }
 
 int test_primitive() {
+#ifndef JSMN_STRICT
        int r;
        jsmn_parser p;
        jsmntok_t tok[10];
        const char *js;
-#ifndef JSMN_STRICT
        js = "\"boolVar\" : true";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_PRIMITIVE);
        check(TOKEN_STRING(js, tok[0], "boolVar"));
        check(TOKEN_STRING(js, tok[1], "true"));
 
        js = "\"boolVar\" : false";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_PRIMITIVE);
        check(TOKEN_STRING(js, tok[0], "boolVar"));
        check(TOKEN_STRING(js, tok[1], "false"));
 
        js = "\"intVar\" : 12345";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_PRIMITIVE);
        check(TOKEN_STRING(js, tok[0], "intVar"));
        check(TOKEN_STRING(js, tok[1], "12345"));
 
        js = "\"floatVar\" : 12.345";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_PRIMITIVE);
        check(TOKEN_STRING(js, tok[0], "floatVar"));
        check(TOKEN_STRING(js, tok[1], "12.345"));
 
        js = "\"nullVar\" : null";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_PRIMITIVE);
        check(TOKEN_STRING(js, tok[0], "nullVar"));
        check(TOKEN_STRING(js, tok[1], "null"));
@@ -167,24 +168,24 @@ int test_string() {
 
        js = "\"strVar\" : \"hello world\"";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_STRING);
        check(TOKEN_STRING(js, tok[0], "strVar"));
        check(TOKEN_STRING(js, tok[1], "hello world"));
 
        js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_STRING);
        check(TOKEN_STRING(js, tok[0], "strVar"));
        check(TOKEN_STRING(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\"));
 
        js = "\"strVar\" : \"\"";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_STRING);
        check(TOKEN_STRING(js, tok[0], "strVar"));
        check(TOKEN_STRING(js, tok[1], ""));
@@ -200,27 +201,37 @@ int test_partial_string() {
 
        jsmn_init(&p);
        js = "\"x\": \"va";
-       r = jsmn_parse(&p, js, tok, 10);
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
        check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING);
        check(TOKEN_STRING(js, tok[0], "x"));
        check(p.toknext == 1);
 
+       jsmn_init(&p);
+       char js_slash[9] = "\"x\": \"va\\";
+       r = jsmn_parse(&p, js_slash, sizeof(js_slash), tok, 10);
+       check(r == JSMN_ERROR_PART);
+
+       jsmn_init(&p);
+       char js_unicode[10] = "\"x\": \"va\\u";
+       r = jsmn_parse(&p, js_unicode, sizeof(js_unicode), tok, 10);
+       check(r == JSMN_ERROR_PART);
+
        js = "\"x\": \"valu";
-       r = jsmn_parse(&p, js, tok, 10);
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
        check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING);
        check(TOKEN_STRING(js, tok[0], "x"));
        check(p.toknext == 1);
 
        js = "\"x\": \"value\"";
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_STRING);
        check(TOKEN_STRING(js, tok[0], "x"));
        check(TOKEN_STRING(js, tok[1], "value"));
 
        js = "\"x\": \"value\", \"y\": \"value y\"";
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_STRING
                        && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING
                        && tok[3].type == JSMN_STRING);
        check(TOKEN_STRING(js, tok[0], "x"));
@@ -241,8 +252,8 @@ int test_unquoted_keys() {
        jsmn_init(&p);
        js = "key1: \"value\"\nkey2 : 123";
 
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_PRIMITIVE
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_PRIMITIVE
                        && tok[1].type == JSMN_STRING && tok[2].type == JSMN_PRIMITIVE
                        && tok[3].type == JSMN_PRIMITIVE);
        check(TOKEN_STRING(js, tok[0], "key1"));
@@ -261,19 +272,19 @@ int test_partial_array() {
 
        jsmn_init(&p);
        js = "  [ 1, true, ";
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY
                        && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE);
 
        js = "  [ 1, true, [123, \"hello";
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY
                        && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE
                        && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE);
 
        js = "  [ 1, true, [123, \"hello\"]";
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY
                        && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE
                        && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE
                        && tok[5].type == JSMN_STRING);
@@ -281,8 +292,8 @@ int test_partial_array() {
        check(tok[3].size == 2);
 
        js = "  [ 1, true, [123, \"hello\"]]";
-       r = jsmn_parse(&p, js, tok, 10);
-       check(r == JSMN_SUCCESS && tok[0].type == JSMN_ARRAY 
+       r = jsmn_parse(&p, js, strlen(js), tok, 10);
+       check(r >= 0 && tok[0].type == JSMN_ARRAY
                        && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE
                        && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE
                        && tok[5].type == JSMN_STRING);
@@ -304,13 +315,13 @@ int test_array_nomem() {
                jsmn_init(&p);
                memset(toksmall, 0, sizeof(toksmall));
                memset(toklarge, 0, sizeof(toklarge));
-               r = jsmn_parse(&p, js, toksmall, i);
+               r = jsmn_parse(&p, js, strlen(js), toksmall, i);
                check(r == JSMN_ERROR_NOMEM);
 
                memcpy(toklarge, toksmall, sizeof(toksmall));
 
-               r = jsmn_parse(&p, js, toklarge, 10);
-               check(r == JSMN_SUCCESS);
+               r = jsmn_parse(&p, js, strlen(js), toklarge, 10);
+               check(r >= 0);
 
                check(toklarge[0].type == JSMN_ARRAY && toklarge[0].size == 3);
                check(toklarge[3].type == JSMN_ARRAY && toklarge[3].size == 2);
@@ -319,7 +330,6 @@ int test_array_nomem() {
 }
 
 int test_objects_arrays() {
-       int i;
        int r;
        jsmn_parser p;
        jsmntok_t tokens[10];
@@ -327,24 +337,57 @@ int test_objects_arrays() {
 
        js = "[10}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
        check(r == JSMN_ERROR_INVAL);
 
        js = "[10]";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
 
        js = "{\"a\": 1]";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
        check(r == JSMN_ERROR_INVAL);
 
        js = "{\"a\": 1}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
+
+       return 0;
+}
 
+int test_issue_22() {
+       int r;
+       jsmn_parser p;
+       jsmntok_t tokens[128];
+       const char *js;
+
+       js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, "
+               "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", "
+               "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], "
+               "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, "
+               "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", "
+               "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", "
+               "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], "
+               "\"tilewidth\":32, \"version\":1, \"width\":10 }";
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 128);
+       check(r >= 0);
+#if 0
+       for (i = 1; tokens[i].end < tokens[0].end; i++) {
+               if (tokens[i].type == JSMN_STRING || tokens[i].type == JSMN_PRIMITIVE) {
+                       printf("%.*s\n", tokens[i].end - tokens[i].start, js + tokens[i].start);
+               } else if (tokens[i].type == JSMN_ARRAY) {
+                       printf("[%d elems]\n", tokens[i].size);
+               } else if (tokens[i].type == JSMN_OBJECT) {
+                       printf("{%d elems}\n", tokens[i].size);
+               } else {
+                       TOKEN_PRINT(tokens[i]);
+               }
+       }
+#endif
        return 0;
 }
 
@@ -356,39 +399,190 @@ int test_unicode_characters() {
        int r;
        js = "{\"a\":\"\\uAbcD\"}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
 
        js = "{\"a\":\"str\\u0000\"}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
 
        js = "{\"a\":\"\\uFFFFstr\"}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
 
        js = "{\"a\":\"str\\uFFGFstr\"}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
        check(r == JSMN_ERROR_INVAL);
 
        js = "{\"a\":\"str\\u@FfF\"}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
        check(r == JSMN_ERROR_INVAL);
 
        js = "{\"a\":[\"\\u028\"]}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
        check(r == JSMN_ERROR_INVAL);
 
        js = "{\"a\":[\"\\u0280\"]}";
        jsmn_init(&p);
-       r = jsmn_parse(&p, js, tokens, 10);
-       check(r == JSMN_SUCCESS);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r >= 0);
+
+       return 0;
+}
+
+int test_input_length() {
+       const char *js;
+       int r;
+       jsmn_parser p;
+       jsmntok_t tokens[10];
+
+       js = "{\"a\": 0}garbage";
+
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, 8, tokens, 10);
+       check(r == 3);
+       check(TOKEN_STRING(js, tokens[0], "{\"a\": 0}"));
+       check(TOKEN_STRING(js, tokens[1], "a"));
+       check(TOKEN_STRING(js, tokens[2], "0"));
+
+       return 0;
+}
+
+int test_count() {
+       jsmn_parser p;
+       const char *js;
+
+       js = "{}";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1);
+
+       js = "[]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1);
+
+       js = "[[]]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2);
+
+       js = "[[], []]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3);
+
+       js = "[[], []]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3);
+
+       js = "[[], [[]], [[], []]]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7);
+
+       js = "[\"a\", [[], []]]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5);
+
+       js = "[[], \"[], [[]]\", [[]]]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5);
+
+       js = "[1, 2, 3]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4);
+
+       js = "[1, 2, [3, \"a\"], null]";
+       jsmn_init(&p);
+       check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7);
+
+       return 0;
+}
+
+int test_keyvalue() {
+       const char *js;
+       int r;
+       jsmn_parser p;
+       jsmntok_t tokens[10];
+
+       js = "{\"a\": 0, \"b\": \"c\"}";
+
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r == 5);
+       check(tokens[0].size == 2); /* two keys */
+       check(tokens[1].size == 1 && tokens[3].size == 1); /* one value per key */
+       check(tokens[2].size == 0 && tokens[4].size == 0); /* values have zero size */
+
+       js = "{\"a\"\n0}";
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r == JSMN_ERROR_INVAL);
 
+       js = "{\"a\", 0}";
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r == JSMN_ERROR_INVAL);
+
+       js = "{\"a\": {2}}";
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r == JSMN_ERROR_INVAL);
+
+       js = "{\"a\": {2: 3}}";
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r == JSMN_ERROR_INVAL);
+
+
+       js = "{\"a\": {\"a\": 2 3}}";
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r == JSMN_ERROR_INVAL);
+       return 0;
+}
+
+/** A huge redefinition of everything to include jsmn in non-script mode */
+#define jsmn_init jsmn_init_nonstrict
+#define jsmn_parse jsmn_parse_nonstrict
+#define jsmn_parser jsmn_parser_nonstrict
+#define jsmn_alloc_token jsmn_alloc_token_nonstrict
+#define jsmn_fill_token jsmn_fill_token_nonstrict
+#define jsmn_parse_primitive jsmn_parse_primitive_nonstrict
+#define jsmn_parse_string jsmn_parse_string_nonstrict
+#define jsmntype_t jsmntype_nonstrict_t
+#define jsmnerr_t jsmnerr_nonstrict_t
+#define jsmntok_t jsmntok_nonstrict_t
+#define JSMN_PRIMITIVE JSMN_PRIMITIVE_NONSTRICT
+#define JSMN_OBJECT JSMN_OBJECT_NONSTRICT
+#define JSMN_ARRAY JSMN_ARRAY_NONSTRICT
+#define JSMN_STRING JSMN_STRING_NONSTRICT
+#define JSMN_ERROR_NOMEM JSMN_ERROR_NOMEM_NONSTRICT
+#define JSMN_ERROR_INVAL JSMN_ERROR_INVAL_NONSTRICT
+#define JSMN_ERROR_PART JSMN_ERROR_PART_NONSTRICT
+#undef __JSMN_H_
+#undef JSMN_STRICT
+#include "jsmn.c"
+
+int test_nonstrict() {
+       const char *js;
+       int r;
+       jsmn_parser p;
+       jsmntok_t tokens[10];
+
+       js = "a: 0garbage";
+
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, 4, tokens, 10);
+       check(r == 2);
+       check(TOKEN_STRING(js, tokens[0], "a"));
+       check(TOKEN_STRING(js, tokens[1], "0"));
+
+       js = "Day : 26\nMonth : Sep\n\nYear: 12";
+       jsmn_init(&p);
+       r = jsmn_parse(&p, js, strlen(js), tokens, 10);
+       check(r == 6);
        return 0;
 }
 
@@ -403,6 +597,11 @@ int main() {
        test(test_unquoted_keys, "test unquoted keys (like in JavaScript)");
        test(test_objects_arrays, "test objects and arrays");
        test(test_unicode_characters, "test unicode characters");
+       test(test_input_length, "test strings that are not null-terminated");
+       test(test_issue_22, "test issue #22");
+       test(test_count, "test tokens count estimation");
+       test(test_nonstrict, "test for non-strict mode");
+       test(test_keyvalue, "test for keys/values");
        printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed);
        return 0;
 }
index 2171a4fff7886bb79e2f033dcac0d29fe1378286..8cf43996e6ff17d7819f7c99a7aecbf37c7d6689 100644 (file)
@@ -6,20 +6,47 @@
  *
  *     Heavily inspired by refclock_nmea.c
  *
+ * Special thanks to Gary Miller and Hal Murray for their comments and
+ * ideas.
+ *
  * Note: This will currently NOT work with Windows due to some
  * limitations:
  *
  *  - There is no GPSD for Windows. (There is an unofficial port to
  *    cygwin, but Windows is not officially supported.)
  *
- *  - To work properly, this driver needs PPS and TPV sentences from
- *    GPSD. I don't see how the cygwin port should deal with that.
+ *  - To work properly, this driver needs PPS and TPV/TOFF sentences
+ *    from GPSD. I don't see how the cygwin port should deal with the
+ *    PPS signal.
  *
  *  - The device name matching must be done in a different way for
  *    Windows. (Can be done with COMxx matching, as done for NMEA.)
  *
  * Apart from those minor hickups, once GPSD has been fully ported to
- * Windows, there's no reason why this should not work there ;-)
+ * Windows, there's no reason why this should not work there ;-) If this
+ * is ever to happen at all is a different question.
+ *
+ * ---------------------------------------------------------------------
+ *
+ * This driver works slightly different from most others, as the PPS
+ * information (if available) is also coming from GPSD via the data
+ * connection. This makes using both the PPS data and the serial data
+ * easier, but OTOH it's not possible to use the ATOM driver to feed a
+ * raw PPS stream to the core of NTPD.
+ *
+ * To go around this, the driver can use a secondary clock unit
+ * (units>=128) that operate in tandem with the primary clock unit
+ * (unit%128). The primary clock unit does all the IO stuff and data
+ * decoding; if a a secondary unit is attached to a primary unit, this
+ * secondary unit is feed with the PPS samples only and can act as a PPS
+ * source to the clock selection.
+ *
+ * The drawback is that the primary unit must be present for the
+ * secondary unit to work.
+ *
+ * This design is a compromise to reduce the IO load for both NTPD and
+ * GPSD; it also ensures that data is transmitted and evaluated only
+ * once on the side of NTPD.
  */
 
 #ifdef HAVE_CONFIG_H
 
 #include "ntp_types.h"
 
-#if defined(REFCLOCK) && defined(CLOCK_GPSDJSON) && !defined(SYS_WINNT) 
+#if defined(REFCLOCK) && defined(CLOCK_GPSDJSON) && !defined(SYS_WINNT)
 
 /* =====================================================================
  * get the little JSMN library directly into our guts
  */
+#define JSMN_PARENT_LINKS
 #include "../libjsmn/jsmn.c"
 
+/* =====================================================================
+ * JSON parsing stuff
+ */
+
+#define JSMN_MAXTOK    30
+#define INVALID_TOKEN (-1)
+
+typedef struct json_ctx {
+       char        * buf;
+       int           ntok;
+       jsmntok_t     tok[JSMN_MAXTOK];
+} json_ctx;
+
+typedef int tok_ref;
+
+/* Not all targets have 'long long', and not all of them have 'strtoll'.
+ * Sigh. We roll our own integer number parser.
+ */
+#ifdef HAVE_LONG_LONG
+typedef signed   long long int json_int;
+typedef unsigned long long int json_uint;
+#define JSON_INT_MAX LLONG_MAX
+#define JSON_INT_MIN LLONG_MIN
+#else
+typedef signed   long int json_int;
+typedef unsigned long int json_uint;
+#define JSON_INT_MAX LONG_MAX
+#define JSON_INT_MIN LONG_MIN
+#endif
+
 /* =====================================================================
  * header stuff we need
  */
 #include <fcntl.h>
 #include <string.h>
 #include <ctype.h>
+#include <math.h>
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include "ntp_calendar.h"
 #include "timespecops.h"
 
+/* get operation modes from mode word.
+
+ * + SERIAL (default) evaluates only serial time information ('STI') as
+ *   provided by TPV and TOFF records. TPV evaluation suffers from a
+ *   bigger jitter than TOFF, sine it does not contain the receive time
+ *   from GPSD and therefore the receive time of NTPD must be
+ *   substituted for it. The network latency makes this a second rate
+ *   guess.
+ *
+ *   If TOFF records are detected in the data stream, the timing
+ *   information is gleaned from this record -- it contains the local
+ *   receive time stamp from GPSD and therefore eliminates the
+ *   transmission latency between GPSD and NTPD. The timing information
+ *   from TPV is ignored once a TOFF is detected or expected.
+ *
+ *   TPV is still used to check the fix status, so the driver can stop
+ *   feeding samples when GPSD says that the time information is
+ *   effectively unreliable.
+ *
+ * + STRICT means only feed clock samples when a valid STI/PPS pair is
+ *   available. Combines the reference time from STI with the pulse time
+ *   from PPS. Masks the serial data jitter as long PPS is available,
+ *   but can rapidly deteriorate once PPS drops out.
+ *
+ * + AUTO tries to use STI/PPS pairs if available for some time, and if
+ *   this fails for too long switches back to STI only until the PPS
+ *   signal becomes available again. See the HTML docs for this driver
+ *   about the gotchas and why this is not the default.
+ */
+#define MODE_OP_MASK   0x03
+#define MODE_OP_STI    0
+#define MODE_OP_STRICT 1
+#define MODE_OP_AUTO   2
+#define MODE_OP_MAXVAL 2
+#define MODE_OP_MODE(x)                ((x) & MODE_OP_MASK)
+
 #define        PRECISION       (-9)    /* precision assumed (about 2 ms) */
 #define        PPS_PRECISION   (-20)   /* precision assumed (about 1 us) */
 #define        REFID           "GPSD"  /* reference id */
 #define TICKOVER_HIGH  120
 #define LOGTHROTTLE    3600
 
-#define PPS_MAXCOUNT   30
-#define PPS_HIWAT       20
-#define PPS_LOWAT       10
+/* Primary channel PPS avilability dance:
+ * Every good PPS sample gets us a credit of PPS_INCCOUNT points, every
+ * bad/missing PPS sample costs us a debit of PPS_DECCOUNT points. When
+ * the account reaches the upper limit we change to a mode where only
+ * PPS-augmented samples are fed to the core; when the account drops to
+ * zero we switch to a mode where TPV-only timestamps are fed to the
+ * core.
+ * This reduces the chance of rapid alternation between raw and
+ * PPS-augmented time stamps.
+ */
+#define PPS_MAXCOUNT   60      /* upper limit of account  */
+#define PPS_INCCOUNT     3     /* credit for good samples */
+#define PPS_DECCOUNT     1     /* debit for bad samples   */
+
+/* The secondary (PPS) channel uses a different strategy to avoid old
+ * PPS samples in the median filter.
+ */
+#define PPS2_MAXCOUNT 10
 
 #ifndef BOOL
 # define BOOL int
 # define FALSE 0
 #endif
 
-/* some local typedefs : The NTPD formatting style cries for short type
+#define PROTO_VERSION(hi,lo) \
+           ((((uint32_t)(hi) << 16) & 0xFFFF0000u) | \
+            ((uint32_t)(lo) & 0x0FFFFu))
+
+/* some local typedefs: The NTPD formatting style cries for short type
  * names, and we provide them locally. Note:the suffix '_t' is reserved
  * for the standard; I use a capital T instead.
  */
@@ -116,10 +230,12 @@ static    void    gpsd_poll       (int, peerT *);
 static void    gpsd_control    (int, const struct refclockstat *,
                                 struct refclockstat *, peerT *);
 static void    gpsd_timer      (int, peerT *);
-static  void    gpsd_clockstats (int, peerT *);
 
 static  int     myasprintf(char**, char const*, ...);
 
+static void     enter_opmode(peerT *peer, int mode);
+static void    leave_opmode(peerT *peer, int mode);
+
 struct refclock refclock_gpsdjson = {
        gpsd_start,             /* start up driver */
        gpsd_shutdown,          /* shut down driver */
@@ -133,55 +249,82 @@ struct refclock refclock_gpsdjson = {
 /* =====================================================================
  * our local clock unit and data
  */
-typedef struct gpsd_unit {
-       int      unit;
+struct gpsd_unit;
+typedef struct gpsd_unit gpsd_unitT;
+
+struct gpsd_unit {
+       /* links for sharing between master/slave units */
+       gpsd_unitT *next_unit;
+       size_t      refcount;
+
+       /* data for the secondary PPS channel */
+       peerT      *pps_peer;
+
+       /* unit and operation modes */
+       int      unit;
+       int      mode;
+       char    *logname;       /* cached name for log/print */
+       char    * device;       /* device name of unit */
+
        /* current line protocol version */
-       uint16_t proto_major;
-       uint16_t proto_minor;
+       uint32_t proto_version;
 
-       /* PPS time stamps */
+       /* PPS time stamps primary + secondary channel */
        l_fp pps_local; /* when we received the PPS message */
        l_fp pps_stamp; /* related reference time */
        l_fp pps_recvt; /* when GPSD detected the pulse */
+       l_fp pps_stamp2;/* related reference time (secondary) */
+       l_fp pps_recvt2;/* when GPSD detected the pulse (secondary)*/
+       int  ppscount;  /* PPS counter (primary unit) */
+       int  ppscount2; /* PPS counter (secondary unit) */
+
+       /* TPV or TOFF serial time information */
+       l_fp sti_local; /* when we received the TPV/TOFF message */
+       l_fp sti_stamp; /* effective GPS time stamp */
+       l_fp sti_recvt; /* when GPSD got the fix */
 
-       /* TPV (GPS data) time stamps */
-       l_fp tpv_local; /* when we received the TPV message */
-       l_fp tpv_stamp; /* effective GPS time stamp */
-       l_fp tpv_recvt; /* when GPSD got the fix */
+       /* precision estimates */
+       int16_t     sti_prec;   /* serial precision based on EPT */
+       int16_t     pps_prec;   /* PPS precision from GPSD or above */
 
        /* fudge values for correction, mirrored as 'l_fp' */
-       l_fp pps_fudge;
-       l_fp tpv_fudge;
+       l_fp pps_fudge;         /* PPS fudge primary channel */
+       l_fp pps_fudge2;        /* PPS fudge secondary channel */
+       l_fp sti_fudge;         /* TPV/TOFF serial data fudge */
 
        /* Flags to indicate available data */
-       int fl_tpv   : 1;       /* valid TPV seen (have time) */
+       int fl_nosync: 1;       /* GPSD signals bad quality */
+       int fl_sti   : 1;       /* valid TPV/TOFF seen (have time) */
        int fl_pps   : 1;       /* valid pulse seen */
+       int fl_pps2  : 1;       /* valid pulse seen for PPS channel */
+       int fl_rawsti: 1;       /* permit raw TPV/TOFF time stamps */
        int fl_vers  : 1;       /* have protocol version */
        int fl_watch : 1;       /* watch reply seen */
-       int fl_nsec  : 1;       /* have nanosec PPS info */
+       /* protocol flags */
+       int pf_nsec  : 1;       /* have nanosec PPS info */
+       int pf_toff  : 1;       /* have TOFF record for timing */
 
        /* admin stuff for sockets and device selection */
        int         fdt;        /* current connecting socket */
        addrinfoT * addr;       /* next address to try */
        u_int       tickover;   /* timeout countdown */
        u_int       tickpres;   /* timeout preset */
-       u_int       ppscount;   /* PPS mode up/down count */
-       char      * device;     /* device name of unit */
 
        /* tallies for the various events */
        u_int       tc_good;    /* good samples received */
-       u_int       tc_btime;   /* bad time stamps */
-       u_int       tc_bdate;   /* bad date strings */
        u_int       tc_breply;  /* bad replies */
        u_int       tc_recv;    /* received known records */
 
        /* log bloat throttle */
        u_int       logthrottle;/* seconds to next log slot */
 
-       /* record assemby buffer and saved length */    
+       /* The parse context for the current record */
+       json_ctx    json_parse;
+
+       /* record assemby buffer and saved length */
        int  buflen;
        char buffer[MAX_PDU_LEN];
-} gpsd_unitT;
+};
 
 /* =====================================================================
  * static local helpers forward decls
@@ -195,6 +338,9 @@ static void gpsd_parse(peerT * const peer,
 static BOOL convert_ascii_time(l_fp * fp, const char * gps_time);
 static void save_ltc(clockprocT * const pp, const char * const tc);
 static int  syslogok(clockprocT * const pp, gpsd_unitT * const up);
+static void log_data(peerT *peer, const char *what,
+                    const char *buf, size_t len);
+static int16_t clamped_precision(int rawprec);
 
 /* =====================================================================
  * local / static stuff
@@ -202,15 +348,31 @@ static int  syslogok(clockprocT * const pp, gpsd_unitT * const up);
 
 /* The logon string is actually the ?WATCH command of GPSD, using JSON
  * data and selecting the GPS device name we created from our unit
- * number. [Note: This is a format string!]
+ * number. We have an old a newer version that request PPS (and TOFF)
+ * transmission.
+ * Note: These are actually format strings!
  */
-#define s_logon \
-    "?WATCH={\"enable\":true,\"json\":true,\"device\":\"%s\"};\r\n"
+static const char * const s_req_watch[2] = {
+       "?WATCH={\"device\":\"%s\",\"enable\":true,\"json\":true};\r\n",
+       "?WATCH={\"device\":\"%s\",\"enable\":true,\"json\":true,\"pps\":true};\r\n"
+};
+
+static const char * const s_req_version =
+    "?VERSION;\r\n";
 
 /* We keep a static list of network addresses for 'localhost:gpsd', and
  * we try to connect to them in round-robin fashion.
  */
-static addrinfoT * s_gpsd_addr;
+static addrinfoT  *s_gpsd_addr;
+static gpsd_unitT *s_clock_units;
+
+/* list of service/socket names we want to resolve against */
+static const char * const s_svctab[][2] = {
+       { "localhost", "gpsd" },
+       { "localhost", "2947" },
+       { "127.0.0.1", "2947" },
+       { NULL, NULL }
+};
 
 /* =====================================================================
  * log throttling
@@ -238,41 +400,87 @@ syslogok(
 static void
 gpsd_init(void)
 {
-       addrinfoT hints;
-       
+       addrinfoT   hints;
+       int         rc, idx;
+
        memset(&hints, 0, sizeof(hints));
        hints.ai_family   = AF_UNSPEC;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_socktype = SOCK_STREAM;
 
-       /* just take the first configured address of localhost... */
-       if (getaddrinfo("localhost", "gpsd", &hints, &s_gpsd_addr))
+       for (idx = 0; s_svctab[idx][0] && !s_gpsd_addr; idx++) {
+               rc = getaddrinfo(s_svctab[idx][0], s_svctab[idx][1],
+                                &hints, &s_gpsd_addr);
+               if (0 == rc)
+                       break;
+               msyslog(LOG_WARNING,
+                       "GPSD_JSON: failed to resolve '%s:%s', rc=%d (%s)",
+                       s_svctab[idx][0], s_svctab[idx][1],
+                       rc, gai_strerror(rc));
                s_gpsd_addr = NULL;
+       }
+
+       if (s_gpsd_addr == NULL)
+               msyslog(LOG_ERR, "%s",
+                       "GPSD_JSON: failed to get socket address, giving up.");
+       else if (idx != 0)
+               msyslog(LOG_WARNING,
+                       "GPSD_JSON: using '%s:%s' instead off '%s:%s'",
+                       s_svctab[idx][0], s_svctab[idx][1],
+                       s_svctab[0][0], s_svctab[0][1]);
 }
 
 /* ---------------------------------------------------------------------
  * Start: allocate a unit pointer and set up the runtime data
  */
-
 static int
 gpsd_start(
        int     unit,
        peerT * peer)
 {
-       clockprocT * const pp = peer->procptr;
-       gpsd_unitT * const up = emalloc_zero(sizeof(*up));
+       clockprocT  * const pp = peer->procptr;
+       gpsd_unitT  * up;
+       gpsd_unitT ** uscan    = &s_clock_units;
 
        struct stat sb;
 
-       /* initialize the unit structure */
-       up->fdt      = -1;
-       up->addr     = s_gpsd_addr;
-       up->tickpres = TICKOVER_LOW;
+       /* search for matching unit */
+       while ((up = *uscan) != NULL && up->unit != (unit & 0x7F))
+               uscan = &up->next_unit;
+       if (up == NULL) {
+               up = emalloc_zero(sizeof(*up));
+               *uscan = up;
+
+               /* initialize the unit structure */
+               up->logname  = estrdup(refnumtoa(&peer->srcadr));
+               up->unit     = unit & 0x7F;
+               up->fdt      = -1;
+               up->addr     = s_gpsd_addr;
+               up->tickpres = TICKOVER_LOW;
+
+               /* Create the device name and check for a Character
+                * Device. It's assumed that GPSD was started with the
+                * same link, so the names match. (If this is not
+                * practicable, we will have to read the symlink, if
+                * any, so we can get the true device file.)
+                */
+               if (-1 == myasprintf(&up->device, "%s%u",
+                                    s_dev_stem, up->unit)) {
+                       msyslog(LOG_ERR, "%s: clock device name too long",
+                               up->logname);
+                       goto dev_fail;
+               }
+               if (-1 == stat(up->device, &sb) || !S_ISCHR(sb.st_mode)) {
+                       msyslog(LOG_ERR, "%s: '%s' is not a character device",
+                               up->logname, up->device);
+                       goto dev_fail;
+               }
+       }
+       up->refcount++;
 
        /* setup refclock processing */
-       up->unit    = unit;
        pp->unitptr = (caddr_t)up;
-       pp->io.fd   = -1;
+       pp->io.fd         = -1;
        pp->io.clock_recv = gpsd_receive;
        pp->io.srcclock   = peer;
        pp->io.datalen    = 0;
@@ -282,35 +490,39 @@ gpsd_start(
        memcpy(&pp->refid, REFID, 4);
 
        /* Initialize miscellaneous variables */
-       peer->precision = PRECISION;
+       if (unit >= 128)
+               peer->precision = PPS_PRECISION;
+       else
+               peer->precision = PRECISION;
 
-       /* Create the device name and check for a Character Device. It's
-        * assumed that GPSD was started with the same link, so the
-        * names match. (If this is not practicable, we will have to
-        * read the symlink, if any, so we can get the true device
-        * file.)
-        */
-       if (-1 == myasprintf(&up->device, "%s%u", s_dev_stem, unit)) {
-           msyslog(LOG_ERR, "%s clock device name too long",
-                   refnumtoa(&peer->srcadr));
-           goto dev_fail;
-       }
-       if (-1 == stat(up->device, &sb) || !S_ISCHR(sb.st_mode)) {
-               msyslog(LOG_ERR, "%s: '%s' is not a character device",
-                       refnumtoa(&peer->srcadr), up->device);
-           goto dev_fail;
+       /* If the daemon name lookup failed, just give up now. */
+       if (NULL == up->addr) {
+               msyslog(LOG_ERR, "%s: no GPSD socket address, giving up",
+                       up->logname);
+               goto dev_fail;
        }
+
        LOGIF(CLOCKINFO,
              (LOG_NOTICE, "%s: startup, device is '%s'",
               refnumtoa(&peer->srcadr), up->device));
+       up->mode = MODE_OP_MODE(peer->ttl);
+       if (up->mode > MODE_OP_MAXVAL)
+               up->mode = 0;
+       if (unit >= 128)
+               up->pps_peer = peer;
+       else
+               enter_opmode(peer, up->mode);
        return TRUE;
 
 dev_fail:
        /* On failure, remove all UNIT ressources and declare defeat. */
 
        INSIST (up);
-       free(up->device);
-       free(up);
+       if (!--up->refcount) {
+               *uscan = up->next_unit;
+               free(up->device);
+               free(up);
+       }
 
        pp->unitptr = (caddr_t)NULL;
        return FALSE;
@@ -325,17 +537,33 @@ gpsd_shutdown(
 {
        clockprocT * const pp = peer->procptr;
        gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
+       gpsd_unitT ** uscan   = &s_clock_units;
 
        UNUSED_ARG(unit);
 
-       if (up) {
-           free(up->device);
-           free(up);
+       INSIST(up);
+       if (peer != up->pps_peer) {
+               if (-1 != pp->io.fd) {
+                       DPRINTF(1, ("%s: closing clock, fd=%d\n",
+                                   up->logname, pp->io.fd));
+                       io_closeclock(&pp->io);
+                       pp->io.fd = -1;
+               }
+               if (up->fdt != -1)
+                       close(up->fdt);
+       }
+       if (!--up->refcount) {
+               /* unlink this unit */
+               while (*uscan != NULL)
+                       if (*uscan == up)
+                               *uscan = up->next_unit;
+                       else
+                               uscan = &(*uscan)->next_unit;
+               free(up->logname);
+               free(up->device);
+               free(up);
        }
        pp->unitptr = (caddr_t)NULL;
-       if (-1 != pp->io.fd)
-               io_closeclock(&pp->io);
-       pp->io.fd = -1;
        LOGIF(CLOCKINFO,
              (LOG_NOTICE, "%s: shutdown", refnumtoa(&peer->srcadr)));
 }
@@ -349,11 +577,16 @@ gpsd_receive(
        /* declare & init control structure ptrs */
        peerT      * const peer = rbufp->recv_peer;
        clockprocT * const pp   = peer->procptr;
-       gpsd_unitT * const up   = (gpsd_unitT *)pp->unitptr;    
+       gpsd_unitT * const up   = (gpsd_unitT *)pp->unitptr;
 
        const char *psrc, *esrc;
        char       *pdst, *edst, ch;
 
+       /* log the data stream, if this is enabled */
+       log_data(peer, "recv", (const char*)rbufp->recv_buffer,
+                (size_t)rbufp->recv_length);
+
+
        /* Since we're getting a raw stream data, we must assemble lines
         * in our receive buffer. We can't use neither 'refclock_gtraw'
         * not 'refclock_gtlin' here...  We process chars until we reach
@@ -375,6 +608,7 @@ gpsd_receive(
                                --pdst;
                        *pdst = '\0';
                        /* process data and reset buffer */
+                       up->buflen = pdst - up->buffer;
                        gpsd_parse(peer, &rbufp->recv_time);
                        pdst = up->buffer;
                } else if (pdst != edst) {
@@ -390,49 +624,73 @@ gpsd_receive(
 /* ------------------------------------------------------------------ */
 
 static void
-gpsd_poll(
-       int     unit,
-       peerT * peer)
+poll_primary(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
 {
-       clockprocT * const pp = peer->procptr;
-       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
-       u_int   tc_max;
-
-       ++pp->polls;
-
-       /* find the dominant error */
-       tc_max = max(up->tc_btime, up->tc_bdate);
-       tc_max = max(tc_max, up->tc_breply);
-
        if (pp->coderecv != pp->codeproc) {
                /* all is well */
                pp->lastref = pp->lastrec;
+               refclock_report(peer, CEVNT_NOMINAL);
                refclock_receive(peer);
        } else {
-               /* not working properly, admit to it */
-               peer->flags    &= ~FLAG_PPS;
+               /* Not working properly, admit to it. If we have no
+                * connection to GPSD, declare the clock as faulty. If
+                * there were bad replies, this is handled as the major
+                * cause, and everything else is just a timeout.
+                */
                peer->precision = PRECISION;
-
-               if (-1 == pp->io.fd) {
-                       /* not connected to GPSD: clearly not working! */
+               if (-1 == pp->io.fd)
                        refclock_report(peer, CEVNT_FAULT);
-               } else if (tc_max == up->tc_breply) {
+               else if (0 != up->tc_breply)
                        refclock_report(peer, CEVNT_BADREPLY);
-               } else if (tc_max == up->tc_btime) {
-                       refclock_report(peer, CEVNT_BADTIME);
-               } else if (tc_max == up->tc_bdate) {
-                       refclock_report(peer, CEVNT_BADDATE);
-               } else {
+               else
                        refclock_report(peer, CEVNT_TIMEOUT);
-               }
        }
 
        if (pp->sloppyclockflag & CLK_FLAG4)
-               gpsd_clockstats(unit, peer);
+               mprintf_clock_stats(
+                       &peer->srcadr,"%u %u %u",
+                       up->tc_good, up->tc_breply, up->tc_recv);
 
        /* clear tallies for next round */
-       up->tc_good = up->tc_btime = up->tc_bdate =
-           up->tc_breply = up->tc_recv = 0;
+       up->tc_good   = 0;
+       up->tc_breply = 0;
+       up->tc_recv   = 0;
+}
+
+static void
+poll_secondary(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
+{
+       if (pp->coderecv != pp->codeproc) {
+               /* all is well */
+               pp->lastref = pp->lastrec;
+               refclock_report(peer, CEVNT_NOMINAL);
+               refclock_receive(peer);
+       } else {
+               peer->precision = PPS_PRECISION;
+               peer->flags &= ~FLAG_PPS;
+               refclock_report(peer, CEVNT_TIMEOUT);
+       }
+}
+
+static void
+gpsd_poll(
+       int     unit,
+       peerT * peer)
+{
+       clockprocT * const pp = peer->procptr;
+       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
+
+       ++pp->polls;
+       if (peer == up->pps_peer)
+               poll_secondary(peer, pp, up);
+       else
+               poll_primary(peer, pp, up);
 }
 
 /* ------------------------------------------------------------------ */
@@ -447,23 +705,32 @@ gpsd_control(
        clockprocT * const pp = peer->procptr;
        gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
 
-       /* save preprocessed fudge times */
-       DTOLFP(pp->fudgetime1, &up->pps_fudge);
-       DTOLFP(pp->fudgetime2, &up->tpv_fudge);
-}
+       if (peer == up->pps_peer) {
+               DTOLFP(pp->fudgetime1, &up->pps_fudge2);
+               if ( ! (pp->sloppyclockflag & CLK_FLAG1))
+                       peer->flags &= ~FLAG_PPS;
+       } else {
+               /* save preprocessed fudge times */
+               DTOLFP(pp->fudgetime1, &up->pps_fudge);
+               DTOLFP(pp->fudgetime2, &up->sti_fudge);
+
+               if (MODE_OP_MODE(up->mode ^ peer->ttl)) {
+                       leave_opmode(peer, up->mode);
+                       up->mode = MODE_OP_MODE(peer->ttl);
+                       enter_opmode(peer, up->mode);
+               }
+       }
+ }
 
 /* ------------------------------------------------------------------ */
 
 static void
-gpsd_timer(
-       int     unit,
-       peerT * peer)
+timer_primary(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
 {
-       static const char query[] = "?VERSION;";
-
-       clockprocT * const pp = peer->procptr;
-       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
-       int                rc;
+       int rc;
 
        /* This is used for timeout handling. Nothing that needs
         * sub-second precison happens here, so receive/connect/retry
@@ -479,20 +746,17 @@ gpsd_timer(
                --up->tickover;
        switch (up->tickover) {
        case 4:
-               /* try to get a live signal
-                * If the device is not yet present, we will most likely
-                * get an error. We put out a new version request,
-                * because the reply will initiate a new watch request
-                * cycle.
+               /* If we are connected to GPSD, try to get a live signal
+                * by querying the version. Otherwise just check the
+                * socket to become ready.
                 */
                if (-1 != pp->io.fd) {
-                       if ( ! up->fl_watch) {
-                               DPRINTF(2, ("GPSD_JSON(%d): timer livecheck: '%s'\n",
-                                           up->unit, query));
-                               rc = write(pp->io.fd,
-                                          query, sizeof(query));
-                               (void)rc;
-                       }
+                       size_t rlen = strlen(s_req_version);
+                       DPRINTF(2, ("%s: timer livecheck: '%s'\n",
+                                   up->logname, s_req_version));
+                       log_data(peer, "send", s_req_version, rlen);
+                       rc = write(pp->io.fd, s_req_version, rlen);
+                       (void)rc;
                } else if (-1 != up->fdt) {
                        gpsd_test_socket(peer);
                }
@@ -511,35 +775,277 @@ gpsd_timer(
                if (-1 == pp->io.fd && -1 != up->fdt)
                        gpsd_test_socket(peer);
        }
+}
 
-       if (up->ppscount > PPS_HIWAT && !(peer->flags & FLAG_PPS))
-               peer->flags |= FLAG_PPS;
-       if (up->ppscount < PPS_LOWAT &&  (peer->flags & FLAG_PPS))
+static void
+timer_secondary(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
+{
+       /* Reduce the count by one. Flush sample buffer and clear PPS
+        * flag when this happens.
+        */
+       up->ppscount2 = max(0, (up->ppscount2 - 1));
+       if (0 == up->ppscount2) {
+               if (pp->coderecv != pp->codeproc) {
+                       refclock_report(peer, CEVNT_TIMEOUT);
+                       pp->coderecv = pp->codeproc;
+               }
                peer->flags &= ~FLAG_PPS;
+       }
+}
+
+static void
+gpsd_timer(
+       int     unit,
+       peerT * peer)
+{
+       clockprocT * const pp = peer->procptr;
+       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
+
+       if (peer == up->pps_peer)
+               timer_secondary(peer, pp, up);
+       else
+               timer_primary(peer, pp, up);
+}
+
+/* =====================================================================
+ * handle opmode switches
+ */
+
+static void
+enter_opmode(
+       peerT *peer,
+       int    mode)
+{
+       clockprocT * const pp = peer->procptr;
+       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
+
+       DPRINTF(1, ("%s: enter operation mode %d\n",
+                   up->logname, MODE_OP_MODE(mode)));
+
+       if (MODE_OP_MODE(mode) == MODE_OP_AUTO) {
+               up->fl_rawsti = 0;
+               up->ppscount  = PPS_MAXCOUNT / 2;
+       }
+       up->fl_pps = 0;
+       up->fl_sti = 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void
+leave_opmode(
+       peerT *peer,
+       int    mode)
+{
+       clockprocT * const pp = peer->procptr;
+       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
+
+       DPRINTF(1, ("%s: leaving operation mode %d\n",
+                   up->logname, MODE_OP_MODE(mode)));
+
+       if (MODE_OP_MODE(mode) == MODE_OP_AUTO) {
+               up->fl_rawsti = 0;
+               up->ppscount  = 0;
+       }
+       up->fl_pps = 0;
+       up->fl_sti = 0;
+}
+
+/* =====================================================================
+ * operation mode specific evaluation
+ */
+
+static void
+add_clock_sample(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       l_fp               stamp,
+       l_fp               recvt)
+{
+       pp->lastref = stamp;
+       if (pp->coderecv == pp->codeproc)
+               refclock_report(peer, CEVNT_NOMINAL);
+       refclock_process_offset(pp, stamp, recvt, 0.0);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void
+eval_strict(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
+{
+       if (up->fl_sti && up->fl_pps) {
+               /* use TPV reference time + PPS receive time */
+               add_clock_sample(peer, pp, up->sti_stamp, up->pps_recvt);
+               peer->precision = up->pps_prec;
+               up->tc_good += 1;
+               /* both packets consumed now... */
+               up->fl_pps = 0;
+               up->fl_sti = 0;
+       }
+}
+
+/* ------------------------------------------------------------------ */
+/* PPS processing for the secondary channel. GPSD provides us with full
+ * timing information, so there's no danger of PLL-locking to the wrong
+ * second. The belts and suspenders needed for the raw ATOM clock are
+ * unnecessary here.
+ */
+static void
+eval_pps_secondary(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
+{
+       if (up->fl_pps2) {
+               /* feed data */
+               add_clock_sample(peer, pp, up->pps_stamp2, up->pps_recvt2);
+               peer->precision = up->pps_prec;
+               /* PPS peer flag logic */
+               up->ppscount2 = min(PPS2_MAXCOUNT, (up->ppscount2 + 2));
+               if ((PPS2_MAXCOUNT == up->ppscount2) &&
+                   (pp->sloppyclockflag & CLK_FLAG1) )
+                       peer->flags |= FLAG_PPS;
+               /* mark time stamp as burned... */
+               up->fl_pps2 = 0;
+       }
+}
+
+/* ------------------------------------------------------------------ */
+
+static void
+eval_serial(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
+{
+       if (up->fl_sti) {
+               add_clock_sample(peer, pp, up->sti_stamp, up->sti_recvt);
+               peer->precision = up->sti_prec;
+               up->tc_good += 1;
+               /* mark time stamp as burned... */
+               up->fl_sti = 0;
+       }
+}
+
+/* ------------------------------------------------------------------ */
+static void
+eval_auto(
+       peerT      * const peer ,
+       clockprocT * const pp   ,
+       gpsd_unitT * const up   )
+{
+       /* If there's no TPV available, stop working here... */
+       if (!up->fl_sti)
+               return;
+
+       /* check how to handle STI+PPS: Can PPS be used to augment STI
+        * (or vice versae), do we drop the sample because there is a
+        * temporary missing PPS signal, or do we feed on STI time
+        * stamps alone?
+        *
+        * Do a counter/threshold dance to decide how to proceed.
+        */
+       if (up->fl_pps) {
+               up->ppscount = min(PPS_MAXCOUNT,
+                                  (up->ppscount + PPS_INCCOUNT));
+               if ((PPS_MAXCOUNT == up->ppscount) && up->fl_rawsti) {
+                       up->fl_rawsti = 0;
+                       msyslog(LOG_INFO,
+                               "%s: expect valid PPS from now",
+                               up->logname);
+               }
+       } else {
+               up->ppscount = max(0, (up->ppscount - PPS_DECCOUNT));
+               if ((0 == up->ppscount) && !up->fl_rawsti) {
+                       up->fl_rawsti = -1;
+                       msyslog(LOG_WARNING,
+                               "%s: use TPV alone from now",
+                               up->logname);
+               }
+       }
+
+       /* now eventually feed the sample */
+       if (up->fl_rawsti)
+               eval_serial(peer, pp, up);
+       else
+               eval_strict(peer, pp, up);
 }
 
 /* =====================================================================
  * JSON parsing stuff
  */
 
-#define JSMN_MAXTOK    100
-#define INVALID_TOKEN (-1)
+/* ------------------------------------------------------------------ */
+/* Parse a decimal integer with a possible sign. Works like 'strtoll()'
+ * or 'strtol()', but with a fixed base of 10 and without eating away
+ * leading whitespace. For the error codes, the handling of the end
+ * pointer and the return values see 'strtol()'.
+ */
+static json_int
+strtojint(
+       const char *cp, char **ep)
+{
+       json_uint     accu, limit_lo, limit_hi;
+       int           flags; /* bit 0: overflow; bit 1: sign */
+       const char  * hold;
 
-typedef struct json_ctx {
-       char        * buf;
-       int           ntok;
-       jsmntok_t     tok[JSMN_MAXTOK];
-} json_ctx;
+       /* pointer union to circumvent a tricky/sticky const issue */
+       union { const char * c; char * v; } vep;
 
-typedef int tok_ref;
+       /* store initial value of 'cp' -- see 'strtol()' */
+       vep.c = cp;
 
-#ifdef HAVE_LONG_LONG
-typedef long long json_int;
- #define JSON_STRING_TO_INT strtoll
-#else
-typedef long json_int;
- #define JSON_STRING_TO_INT strtol
-#endif
+       /* Eat away an optional sign and set the limits accordingly: The
+        * high limit is the maximum absolute value that can be returned,
+        * and the low limit is the biggest value that does not cause an
+        * overflow when multiplied with 10. Avoid negation overflows.
+        */
+       if (*cp == '-') {
+               cp += 1;
+               flags    = 2;
+               limit_hi = (json_uint)-(JSON_INT_MIN + 1) + 1;
+       } else {
+               cp += (*cp == '+');
+               flags    = 0;
+               limit_hi = (json_uint)JSON_INT_MAX;
+       }
+       limit_lo = limit_hi / 10;
+
+       /* Now try to convert a sequence of digits. */
+       hold = cp;
+       accu = 0;
+       while (isdigit(*(const unsigned char*)cp)) {
+               flags |= (accu > limit_lo);
+               accu = accu * 10 + (*(const unsigned char*)cp++ - '0');
+               flags |= (accu > limit_hi);
+       }
+       /* Check for empty conversion (no digits seen). */
+       if (hold != cp)
+               vep.c = cp;
+       else
+               errno = EINVAL; /* accu is still zero */
+       /* Check for range overflow */
+       if (flags & 1) {
+               errno = ERANGE;
+               accu  = limit_hi;
+       }
+       /* If possible, store back the end-of-conversion pointer */
+       if (ep)
+               *ep = vep.v;
+       /* If negative, return the negated result if the accu is not
+        * zero. Avoid negation overflows.
+        */
+       if ((flags & 2) && accu)
+               return -(json_int)(accu - 1) - 1;
+       else
+               return (json_int)accu;
+}
 
 /* ------------------------------------------------------------------ */
 
@@ -548,64 +1054,114 @@ json_token_skip(
        const json_ctx * ctx,
        tok_ref          tid)
 {
-       int len;
-       len = ctx->tok[tid].size;
-       for (++tid; len; --len)
-               if (tid < ctx->ntok)
-                       tid = json_token_skip(ctx, tid);
-               else
+       if (tid >= 0 && tid < ctx->ntok) {
+               int len = ctx->tok[tid].size;
+               /* For arrays and objects, the size is the number of
+                * ITEMS in the compound. Thats the number of objects in
+                * the array, and the number of key/value pairs for
+                * objects. In theory, the key must be a string, and we
+                * could simply skip one token before skipping the
+                * value, which can be anything. We're a bit paranoid
+                * and lazy at the same time: We simply double the
+                * number of tokens to skip and fall through into the
+                * array processing when encountering an object.
+                */
+               switch (ctx->tok[tid].type) {
+               case JSMN_OBJECT:
+                       len *= 2;
+                       /* FALLTHROUGH */
+               case JSMN_ARRAY:
+                       for (++tid; len; --len)
+                               tid = json_token_skip(ctx, tid);
                        break;
-       if (tid > ctx->ntok)
-               tid = ctx->ntok;
+                       
+               default:
+                       ++tid;
+                       break;
+               }
+               if (tid > ctx->ntok) /* Impossible? Paranoia rulez. */
+                       tid = ctx->ntok;
+       }
        return tid;
 }
-       
+
 /* ------------------------------------------------------------------ */
 
 static int
 json_object_lookup(
-       const json_ctx * ctx,
-       tok_ref          tid,
-       const char     * key)
+       const json_ctx * ctx ,
+       tok_ref          tid ,
+       const char     * key ,
+       int              what)
 {
        int len;
 
-       if (tid >= ctx->ntok || ctx->tok[tid].type != JSMN_OBJECT)
+       if (tid < 0 || tid >= ctx->ntok ||
+           ctx->tok[tid].type != JSMN_OBJECT)
                return INVALID_TOKEN;
-       len = ctx->ntok - tid - 1;
-       if (len > ctx->tok[tid].size)
-               len = ctx->tok[tid].size;
-       for (tid += 1; len > 1; len-=2) {
-               if (ctx->tok[tid].type != JSMN_STRING)
-                       continue; /* hmmm... that's an error, strictly speaking */
-               if (!strcmp(key, ctx->buf + ctx->tok[tid].start))
+       
+       len = ctx->tok[tid].size;
+       for (++tid; len && tid+1 < ctx->ntok; --len) {
+               if (ctx->tok[tid].type != JSMN_STRING) { /* Blooper! */
+                       tid = json_token_skip(ctx, tid); /* skip key */
+                       tid = json_token_skip(ctx, tid); /* skip val */
+               } else if (strcmp(key, ctx->buf + ctx->tok[tid].start)) {
+                       tid = json_token_skip(ctx, tid+1); /* skip key+val */
+               } else if (what < 0 || what == ctx->tok[tid+1].type) {
                        return tid + 1;
-               tid = json_token_skip(ctx, tid + 1);
+               } else {
+                       break;
+               }
        }
        return INVALID_TOKEN;
 }
 
 /* ------------------------------------------------------------------ */
 
-#if 0 /* currently unused */
+static const char*
+json_object_lookup_primitive(
+       const json_ctx * ctx,
+       tok_ref          tid,
+       const char     * key)
+{
+       tid = json_object_lookup(ctx, tid, key, JSMN_PRIMITIVE);
+       if (INVALID_TOKEN  != tid)
+               return ctx->buf + ctx->tok[tid].start;
+       else
+               return NULL;
+}
+/* ------------------------------------------------------------------ */
+/* look up a boolean value. This essentially returns a tribool:
+ * 0->false, 1->true, (-1)->error/undefined
+ */
+static int
+json_object_lookup_bool(
+       const json_ctx * ctx,
+       tok_ref          tid,
+       const char     * key)
+{
+       const char *cp;
+       cp  = json_object_lookup_primitive(ctx, tid, key);
+       switch ( cp ? *cp : '\0') {
+       case 't': return  1;
+       case 'f': return  0;
+       default : return -1;
+       }
+}
+
+/* ------------------------------------------------------------------ */
+
 static const char*
 json_object_lookup_string(
        const json_ctx * ctx,
        tok_ref          tid,
        const char     * key)
 {
-       tok_ref val_ref;
-       val_ref = json_object_lookup(ctx, tid, key);
-       if (INVALID_TOKEN == val_ref               ||
-           JSMN_STRING   != ctx->tok[val_ref].type )
-               goto cvt_error;
-       return ctx->buf + ctx->tok[val_ref].start;
-
-  cvt_error:
-       errno = EINVAL;
+       tid = json_object_lookup(ctx, tid, key, JSMN_STRING);
+       if (INVALID_TOKEN != tid)
+               return ctx->buf + ctx->tok[tid].start;
        return NULL;
 }
-#endif
 
 static const char*
 json_object_lookup_string_default(
@@ -614,12 +1170,10 @@ json_object_lookup_string_default(
        const char     * key,
        const char     * def)
 {
-       tok_ref val_ref;
-       val_ref = json_object_lookup(ctx, tid, key);
-       if (INVALID_TOKEN == val_ref               ||
-           JSMN_STRING   != ctx->tok[val_ref].type )
-               return def;
-       return ctx->buf + ctx->tok[val_ref].start;
+       tid = json_object_lookup(ctx, tid, key, JSMN_STRING);
+       if (INVALID_TOKEN != tid)
+               return ctx->buf + ctx->tok[tid].start;
+       return def;
 }
 
 /* ------------------------------------------------------------------ */
@@ -630,22 +1184,18 @@ json_object_lookup_int(
        tok_ref          tid,
        const char     * key)
 {
-       json_int  ret;
-       tok_ref   val_ref;
-       char    * ep;
-
-       val_ref = json_object_lookup(ctx, tid, key);
-       if (INVALID_TOKEN  == val_ref               ||
-           JSMN_PRIMITIVE != ctx->tok[val_ref].type )
-               goto cvt_error;
-       ret = JSON_STRING_TO_INT(
-               ctx->buf + ctx->tok[val_ref].start, &ep, 10);
-       if (*ep)
-               goto cvt_error;
-       return ret;
-
-  cvt_error:
-       errno = EINVAL;
+       json_int     ret;
+       const char * cp;
+       char       * ep;
+
+       cp = json_object_lookup_primitive(ctx, tid, key);
+       if (NULL != cp) {
+               ret = strtojint(cp, &ep);
+               if (cp != ep && '\0' == *ep)
+                       return ret;
+       } else {
+               errno = EINVAL;
+       }
        return 0;
 }
 
@@ -656,43 +1206,42 @@ json_object_lookup_int_default(
        const char     * key,
        json_int         def)
 {
-       json_int  retv;
-       int       esave;
-       
-       esave = errno;
-       errno = 0;
-       retv  = json_object_lookup_int(ctx, tid, key);
-       if (0 != errno)
-               retv = def;
-       errno = esave;
-       return retv;
+       json_int     ret;
+       const char * cp;
+       char       * ep;
+
+       cp = json_object_lookup_primitive(ctx, tid, key);
+       if (NULL != cp) {
+               ret = strtojint(cp, &ep);
+               if (cp != ep && '\0' == *ep)
+                       return ret;
+       }
+       return def;
 }
 
 /* ------------------------------------------------------------------ */
-
+#if 0 /* currently unused */
 static double
 json_object_lookup_float(
        const json_ctx * ctx,
        tok_ref          tid,
        const char     * key)
 {
-       double    ret;
-       tok_ref   val_ref;
-       char    * ep;
-
-       val_ref = json_object_lookup(ctx, tid, key);
-       if (INVALID_TOKEN  == val_ref               ||
-           JSMN_PRIMITIVE != ctx->tok[val_ref].type )
-               goto cvt_error;
-       ret = strtod(ctx->buf + ctx->tok[val_ref].start, &ep);
-       if (*ep)
-               goto cvt_error;
-       return ret;
-
-  cvt_error:
-       errno = EINVAL;
+       double       ret;
+       const char * cp;
+       char       * ep;
+
+       cp = json_object_lookup_primitive(ctx, tid, key);
+       if (NULL != cp) {
+               ret = strtod(cp, &ep);
+               if (cp != ep && '\0' == *ep)
+                       return ret;
+       } else {
+               errno = EINVAL;
+       }
        return 0.0;
 }
+#endif
 
 static double
 json_object_lookup_float_default(
@@ -701,16 +1250,17 @@ json_object_lookup_float_default(
        const char     * key,
        double           def)
 {
-       double    retv;
-       int       esave;
-       
-       esave = errno;
-       errno = 0;
-       retv  = json_object_lookup_float(ctx, tid, key);
-       if (0 != errno)
-               retv = def;
-       errno = esave;
-       return retv;
+       double       ret;
+       const char * cp;
+       char       * ep;
+
+       cp = json_object_lookup_primitive(ctx, tid, key);
+       if (NULL != cp) {
+               ret = strtod(cp, &ep);
+               if (cp != ep && '\0' == *ep)
+                       return ret;
+       }
+       return def;
 }
 
 /* ------------------------------------------------------------------ */
@@ -718,31 +1268,29 @@ json_object_lookup_float_default(
 static BOOL
 json_parse_record(
        json_ctx * ctx,
-       char     * buf)
+       char     * buf,
+       size_t     len)
 {
        jsmn_parser jsm;
        int         idx, rc;
 
        jsmn_init(&jsm);
-       rc = jsmn_parse(&jsm, buf, ctx->tok, JSMN_MAXTOK);
+       rc = jsmn_parse(&jsm, buf, len, ctx->tok, JSMN_MAXTOK);
+       if (rc <= 0)
+               return FALSE;
        ctx->buf  = buf;
-       ctx->ntok = jsm.toknext;
+       ctx->ntok = rc;
+
+       if (JSMN_OBJECT != ctx->tok[0].type)
+               return FALSE; /* not object!?! */
 
        /* Make all tokens NUL terminated by overwriting the
-        * terminator symbol
+        * terminator symbol. Makes string compares and number parsing a
+        * lot easier!
         */
-       for (idx = 0; idx < jsm.toknext; ++idx)
+       for (idx = 0; idx < ctx->ntok; ++idx)
                if (ctx->tok[idx].end > ctx->tok[idx].start)
                        ctx->buf[ctx->tok[idx].end] = '\0';
-
-       if (JSMN_ERROR_PART  != rc &&
-           JSMN_ERROR_NOMEM != rc &&
-           JSMN_SUCCESS     != rc  )
-               return FALSE; /* not parseable - bail out */
-
-       if (0 >= jsm.toknext || JSMN_OBJECT != ctx->tok[0].type)
-               return FALSE; /* not object or no data!?! */
-
        return TRUE;
 }
 
@@ -750,6 +1298,27 @@ json_parse_record(
 /* =====================================================================
  * static local helpers
  */
+static BOOL
+get_binary_time(
+       l_fp       * const dest     ,
+       json_ctx   * const jctx     ,
+       const char * const time_name,
+       const char * const frac_name,
+       long               fscale   )
+{
+       BOOL            retv = FALSE;
+       struct timespec ts;
+
+       errno = 0;
+       ts.tv_sec  = (time_t)json_object_lookup_int(jctx, 0, time_name);
+       ts.tv_nsec = (long  )json_object_lookup_int(jctx, 0, frac_name);
+       if (0 == errno) {
+               ts.tv_nsec *= fscale;
+               *dest = tspec_stamp_to_lfp(ts);
+               retv  = TRUE;
+       }
+       return retv;
+}
 
 /* ------------------------------------------------------------------ */
 /* Process a WATCH record
@@ -766,7 +1335,19 @@ process_watch(
        clockprocT * const pp = peer->procptr;
        gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
 
-       up->fl_watch = -1;
+       const char * path;
+
+       path = json_object_lookup_string(jctx, 0, "device");
+       if (NULL == path || strcmp(path, up->device))
+               return;
+
+       if (json_object_lookup_bool(jctx, 0, "enable") > 0 &&
+           json_object_lookup_bool(jctx, 0, "json"  ) > 0  )
+               up->fl_watch = -1;
+       else
+               up->fl_watch = 0;
+       DPRINTF(2, ("%s: process_watch, enabled=%d\n",
+                   up->logname, (up->fl_watch & 1)));
 }
 
 /* ------------------------------------------------------------------ */
@@ -784,39 +1365,48 @@ process_version(
        char * buf;
        const char *revision;
        const char *release;
+       uint16_t    pvhi, pvlo;
 
        /* get protocol version number */
        revision = json_object_lookup_string_default(
-           jctx, 0, "rev", "(unknown)");
+               jctx, 0, "rev", "(unknown)");
        release  = json_object_lookup_string_default(
-           jctx, 0, "release", "(unknown)");
+               jctx, 0, "release", "(unknown)");
        errno = 0;
-       up->proto_major = (uint16_t)json_object_lookup_int(
-               jctx, 0, "proto_major");
-       up->proto_minor = (uint16_t)json_object_lookup_int(
-               jctx, 0, "proto_minor");
+       pvhi = (uint16_t)json_object_lookup_int(jctx, 0, "proto_major");
+       pvlo = (uint16_t)json_object_lookup_int(jctx, 0, "proto_minor");
+
        if (0 == errno) {
+               if ( ! up->fl_vers)
+                       msyslog(LOG_INFO,
+                               "%s: GPSD revision=%s release=%s protocol=%u.%u",
+                               up->logname, revision, release,
+                               pvhi, pvlo);
+               up->proto_version = PROTO_VERSION(pvhi, pvlo);
                up->fl_vers = -1;
+       } else {
                if (syslogok(pp, up))
                        msyslog(LOG_INFO,
-                               "%s: GPSD revision=%s release=%s protocol=%u.%u",
-                               refnumtoa(&peer->srcadr),
-                               revision, release,
-                               up->proto_major, up->proto_minor);
+                               "%s: could not evaluate version data",
+                               up->logname);
+               return;
        }
+       /* With the 3.9 GPSD protocol, '*_musec' vanished from the PPS
+        * record and was replace by '*_nsec'.
+        */
+       up->pf_nsec = -(up->proto_version >= PROTO_VERSION(3,9));
 
-       /* With the 3.9 GPSD protocol, '*_musec' vanished and was
-        * replace by '*_nsec'. Dispatch properly.
+       /* With the 3.10 protocol we can get TOFF records for better
+        * timing information.
         */
-       if ( up->proto_major >  3 ||
-           (up->proto_major == 3 && up->proto_minor >= 9))
-               up->fl_nsec = -1;
-       else
-               up->fl_nsec = 0;
+       up->pf_toff = -(up->proto_version >= PROTO_VERSION(3,10));
 
-       /*TODO: validate protocol version! */
-       
-       /* request watch for our GPS device
+       /* request watch for our GPS device if not yet watched.
+        *
+        * The version string is also sent as a life signal, if we have
+        * seen useable data. So if we're already watching the device,
+        * skip the request.
+        *
         * Reuse the input buffer, which is no longer needed in the
         * current cycle. Also assume that we can write the watch
         * request in one sweep into the socket; since we do not do
@@ -824,19 +1414,21 @@ process_version(
         * TCP/IP window size gets lower than the length of the
         * request. We handle that when it happens.)
         */
+       if (up->fl_watch)
+               return;
+
        snprintf(up->buffer, sizeof(up->buffer),
-                s_logon, up->device);
+                s_req_watch[up->pf_toff != 0], up->device);
        buf = up->buffer;
        len = strlen(buf);
-       if (len != write(pp->io.fd, buf, len)) {
-               /*Note: if the server fails to read our request, the
+       log_data(peer, "send", buf, len);
+       if (len != write(pp->io.fd, buf, len) && (syslogok(pp, up))) {
+               /* Note: if the server fails to read our request, the
                 * resulting data timeout will take care of the
                 * connection!
                 */
-               if (syslogok(pp, up))
-                       msyslog(LOG_ERR,
-                               "%s: failed to write watch request (%m)",
-                               refnumtoa(&peer->srcadr));
+               msyslog(LOG_ERR, "%s: failed to write watch request (%m)",
+                       up->logname);
        }
 }
 
@@ -853,76 +1445,69 @@ process_tpv(
 
        const char * gps_time;
        int          gps_mode;
-       double       ept, epp, epx, epy, epv;
+       double       ept;
        int          xlog2;
 
        gps_mode = (int)json_object_lookup_int_default(
                jctx, 0, "mode", 0);
 
-       gps_time = json_object_lookup_string_default(
-               jctx, 0, "time", NULL);
+       gps_time = json_object_lookup_string(
+               jctx, 0, "time");
 
-       if (gps_mode < 1 || NULL == gps_time) {
+       /* accept time stamps only in 2d or 3d fix */
+       if (gps_mode < 2 || NULL == gps_time) {
                /* receiver has no fix; tell about and avoid stale data */
                up->tc_breply += 1;
-               up->fl_tpv     = 0;
+               up->fl_sti     = 0;
                up->fl_pps     = 0;
+               up->fl_nosync  = -1;
                return;
        }
+       up->fl_nosync = 0;
 
-       /* save last time code to clock data */
-       save_ltc(pp, gps_time);
-
-       /* convert clock and set resulting ref time */
-       if (convert_ascii_time(&up->tpv_stamp, gps_time)) {
-               DPRINTF(2, ("GPSD_JSON(%d): process_tpv, stamp='%s', recvt='%s' mode=%u\n",
-                           up->unit,
-                           gmprettydate(&up->tpv_stamp),
-                           gmprettydate(&up->tpv_recvt),
-                           gps_mode));
-               
-               up->tpv_local = *rtime;
-               up->tpv_recvt = *rtime;/*TODO: hack until we get it remote from GPSD */
-               L_SUB(&up->tpv_recvt, &up->tpv_fudge);
-               up->fl_tpv = -1;
-       } else {
-               up->tc_btime += 1;
-               up->fl_tpv    = 0;
-       }
-               
-       /* Set the precision from the GPSD data
-        *
-        * Since EPT has some issues, we use EPT and a home-brewed error
-        * estimation base on a sphere derived from EPX/Y/V and the
-        * speed of light. Use the better one of those two.
+       /* convert clock and set resulting ref time, but only if the
+        * TOFF sentence is *not* available
         */
-       ept = json_object_lookup_float_default(jctx, 0, "ept", 1.0);
-       epx = json_object_lookup_float_default(jctx, 0, "epx", 1000.0);
-       epy = json_object_lookup_float_default(jctx, 0, "epy", 1000.0);
-       if (1 == gps_mode) {
-               /* 2d-fix: extend bounding rectangle to cuboid */
-               epv = max(epx, epy);
-       } else {
-               /* 3d-fix: get bounding cuboid */
-               epv = json_object_lookup_float_default(
-                               jctx, 0, "epv", 1000.0);
+       if ( ! up->pf_toff) {
+               /* save last time code to clock data */
+               save_ltc(pp, gps_time);
+               /* now parse the time string */
+               if (convert_ascii_time(&up->sti_stamp, gps_time)) {
+                       DPRINTF(2, ("%s: process_tpv, stamp='%s',"
+                                   " recvt='%s' mode=%u\n",
+                                   up->logname,
+                                   gmprettydate(&up->sti_stamp),
+                                   gmprettydate(&up->sti_recvt),
+                                   gps_mode));
+
+                       /* have to use local receive time as substitute
+                        * for the real receive time: TPV does not tell
+                        * us.
+                        */
+                       up->sti_local = *rtime;
+                       up->sti_recvt = *rtime;
+                       L_SUB(&up->sti_recvt, &up->sti_fudge);
+                       up->fl_sti = -1;
+               } else {
+                       up->tc_breply += 1;
+                       up->fl_sti     = 0;
+               }
        }
 
-       /* get diameter of enclosing sphere of bounding cuboid as spatial
-        * error, then divide spatial error by speed of light to get
-        * another time error estimate. Add extra 100 meters as
-        * optimistic lower bound. Then use the better one of the two
-        * estimations.
+       /* Set the precision from the GPSD data
+        * Use the ETP field for an estimation of the precision of the
+        * serial data. If ETP is not available, use the default serial
+        * data presion instead. (Note: The PPS branch has a different
+        * precision estimation, since it gets the proper value directly
+        * from GPSD!)
         */
-       epp = 2.0 * sqrt(epx*epx + epy*epy + epv*epv);
-       epp = (epp + 100.0) / 299792458.0;
-
-       ept = min(ept, epp  );
-       ept = min(ept, 0.5  );
-       ept = max(ept, 1.0-9);
-       ept = frexp(ept, &xlog2);
-
-       peer->precision = xlog2;
+       ept = json_object_lookup_float_default(jctx, 0, "ept", 2.0e-3);
+       ept = frexp(fabs(ept)*0.70710678, &xlog2); /* ~ sqrt(0.5) */
+       if (ept < 0.25)
+               xlog2 = INT_MIN;
+       if (ept > 2.0)
+               xlog2 = INT_MAX;
+       up->sti_prec = clamped_precision(xlog2);
 }
 
 /* ------------------------------------------------------------------ */
@@ -936,50 +1521,113 @@ process_pps(
        clockprocT * const pp = peer->procptr;
        gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
 
-       struct timespec ts;
-               
-       errno = 0;
-       ts.tv_sec = (time_t)json_object_lookup_int(
-               jctx, 0, "clock_sec");
-       if (up->fl_nsec)
-               ts.tv_nsec = json_object_lookup_int(
-                       jctx, 0, "clock_nsec");
-       else
-               ts.tv_nsec = json_object_lookup_int(
-                       jctx, 0, "clock_musec") * 1000;
+       int xlog2;
 
-       if (0 != errno)
-               goto fail;
+       /* Bail out if there's indication that time sync is bad or
+        * if we're explicitely requested to ignore PPS data.
+        */
+       if (up->fl_nosync)
+               return;
 
        up->pps_local = *rtime;
-       /* get fudged receive time */
-       up->pps_recvt = tspec_stamp_to_lfp(ts);
-       L_SUB(&up->pps_recvt, &up->pps_fudge);
+       /* Now grab the time values. 'clock_*' is the event time of the
+        * pulse measured on the local system clock; 'real_*' is the GPS
+        * reference time GPSD associated with the pulse.
+        */
+       if (up->pf_nsec) {
+               if ( ! get_binary_time(&up->pps_recvt2, jctx,
+                                      "clock_sec", "clock_nsec", 1))
+                       goto fail;
+               if ( ! get_binary_time(&up->pps_stamp2, jctx,
+                                      "real_sec", "real_nsec", 1))
+                       goto fail;
+       } else {
+               if ( ! get_binary_time(&up->pps_recvt2, jctx,
+                                      "clock_sec", "clock_musec", 1000))
+                       goto fail;
+               if ( ! get_binary_time(&up->pps_stamp2, jctx,
+                                      "real_sec", "real_musec", 1000))
+                       goto fail;
+       }
 
-       /* map to next full second as reference time stamp */
+       /* Try to read the precision field from the PPS record. If it's
+        * not there, take the precision from the serial data.
+        */
+       xlog2 = json_object_lookup_int_default(
+                       jctx, 0, "precision", up->sti_prec);
+       up->pps_prec = clamped_precision(xlog2);
+       
+       /* Get fudged receive times for primary & secondary unit */
+       up->pps_recvt = up->pps_recvt2;
+       L_SUB(&up->pps_recvt , &up->pps_fudge );
+       L_SUB(&up->pps_recvt2, &up->pps_fudge2);
+       pp->lastrec = up->pps_recvt;
+
+       /* Map to nearest full second as reference time stamp for the
+        * primary channel. Sanity checks are done in evaluation step.
+        */
        up->pps_stamp = up->pps_recvt;
        L_ADDUF(&up->pps_stamp, 0x80000000u);
        up->pps_stamp.l_uf = 0;
-       
-       pp->lastrec = up->pps_stamp;
 
-       DPRINTF(2, ("GPSD_JSON(%d): process_pps, stamp='%s', recvt='%s'\n", 
-                   up->unit,
-                   gmprettydate(&up->pps_stamp),
-                   gmprettydate(&up->pps_recvt)));
+       if (NULL != up->pps_peer)
+               save_ltc(up->pps_peer->procptr,
+                        gmprettydate(&up->pps_stamp2));
+       DPRINTF(2, ("%s: PPS record processed,"
+                   " stamp='%s', recvt='%s'\n",
+                   up->logname,
+                   gmprettydate(&up->pps_stamp2),
+                   gmprettydate(&up->pps_recvt2)));
        
-       /* When we have a time pulse, clear the TPV flag: the
-        * PPS is only valid for the >NEXT< TPV value!
-        */
-       up->fl_pps = -1;
-       up->fl_tpv =  0;
+       up->fl_pps  = (0 != (pp->sloppyclockflag & CLK_FLAG2)) - 1;
+       up->fl_pps2 = -1;
+       return;
+
+  fail:
+       DPRINTF(1, ("%s: PPS record processing FAILED\n",
+                   up->logname));
+       up->tc_breply += 1;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void
+process_toff(
+       peerT      * const peer ,
+       json_ctx   * const jctx ,
+       const l_fp * const rtime)
+{
+       clockprocT * const pp = peer->procptr;
+       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
+
+       /* remember this! */
+       up->pf_toff = -1;
+
+       /* bail out if there's indication that time sync is bad */
+       if (up->fl_nosync)
+               return;
+
+       if ( ! get_binary_time(&up->sti_recvt, jctx,
+                              "clock_sec", "clock_nsec", 1))
+                       goto fail;
+       if ( ! get_binary_time(&up->sti_stamp, jctx,
+                              "real_sec", "real_nsec", 1))
+                       goto fail;
+       L_SUB(&up->sti_recvt, &up->sti_fudge);
+       up->sti_local = *rtime;
+       up->fl_sti    = -1;
+
+       save_ltc(pp, gmprettydate(&up->sti_stamp));
+       DPRINTF(2, ("%s: TOFF record processed,"
+                   " stamp='%s', recvt='%s'\n",
+                   up->logname,
+                   gmprettydate(&up->sti_stamp),
+                   gmprettydate(&up->sti_recvt)));
        return;
 
   fail:
-       DPRINTF(2, ("GPSD_JSON(%d): process_pps FAILED, nsec=%d stamp='%s', recvt='%s'\n",
-                   up->unit, up->fl_nsec,
-                   gmprettydate(&up->pps_stamp),
-                   gmprettydate(&up->pps_recvt)));
+       DPRINTF(1, ("%s: TOFF record processing FAILED\n",
+                   up->logname));
        up->tc_breply += 1;
 }
 
@@ -993,53 +1641,71 @@ gpsd_parse(
        clockprocT * const pp = peer->procptr;
        gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
 
-       json_ctx     jctx;
        const char * clsid;
-       l_fp         tmpfp;
 
-        DPRINTF(2, ("GPSD_JSON(%d): gpsd_parse: time %s '%s'\n",
-                    up->unit, ulfptoa(rtime, 6), up->buffer));
+        DPRINTF(2, ("%s: gpsd_parse: time %s '%.*s'\n",
+                    up->logname, ulfptoa(rtime, 6),
+                   up->buflen, up->buffer));
 
-       /* See if we can grab anything potentially useful */
-       if (!json_parse_record(&jctx, up->buffer))
+       /* See if we can grab anything potentially useful. JSMN does not
+        * need a trailing NUL, but it needs the number of bytes to
+        * process. */
+       if (!json_parse_record(&up->json_parse, up->buffer, up->buflen))
                return;
 
        /* Now dispatch over the objects we know */
-       clsid = json_object_lookup_string_default(
-               &jctx, 0, "class", "-bad-repy-");
+       clsid = json_object_lookup_string(&up->json_parse, 0, "class");
+       if (NULL == clsid)
+           return;
 
-       up->tc_recv += 1;
-       if (!strcmp("VERSION", clsid))
-               process_version(peer, &jctx, rtime);
-       else if (!strcmp("TPV", clsid))
-               process_tpv(peer, &jctx, rtime);
+       if      (!strcmp("TPV", clsid))
+               process_tpv(peer, &up->json_parse, rtime);
        else if (!strcmp("PPS", clsid))
-               process_pps(peer, &jctx, rtime);
+               process_pps(peer, &up->json_parse, rtime);
+       else if (!strcmp("TOFF", clsid))
+               process_toff(peer, &up->json_parse, rtime);
+       else if (!strcmp("VERSION", clsid))
+               process_version(peer, &up->json_parse, rtime);
        else if (!strcmp("WATCH", clsid))
-               process_watch(peer, &jctx, rtime);
+               process_watch(peer, &up->json_parse, rtime);
        else
                return; /* nothing we know about... */
+       up->tc_recv += 1;
 
-       /* now aggregate TPV and PPS -- no PPS? just use TPV...*/
-       if (up->fl_tpv) {
-               /* TODO: also check remote receive time stamps */
-               tmpfp = up->tpv_local;
-               L_SUB(&tmpfp, &up->pps_local);
-
-               if (up->fl_pps && 0 == tmpfp.l_ui) {
-                       refclock_process_offset(
-                               pp, up->tpv_stamp, up->pps_recvt, 0.0);
-                       if (up->ppscount < PPS_MAXCOUNT)
-                               up->ppscount += 1;
-               } else {
-                       refclock_process_offset(
-                               pp, up->tpv_stamp, up->tpv_recvt, 0.0);
-                       if (up->ppscount > 0)
-                               up->ppscount -= 1;
-               }
-               up->fl_pps   = 0;
-               up->fl_tpv   = 0;
-               up->tc_good += 1;
+       /* if possible, feed the PPS side channel */
+       if (up->pps_peer)
+               eval_pps_secondary(
+                       up->pps_peer, up->pps_peer->procptr, up);
+
+       /* check PPS vs. STI receive times:
+        * If STI is before PPS, then clearly the STI is too old. If PPS
+        * is before STI by more than one second, then PPS is too old.
+        * Weed out stale time stamps & flags.
+        */
+       if (up->fl_pps && up->fl_sti) {
+               l_fp diff;
+               diff = up->sti_local;
+               L_SUB(&diff, &up->pps_local);
+               if (diff.l_i > 0)
+                       up->fl_pps = 0; /* pps too old */
+               else if (diff.l_i < 0)
+                       up->fl_sti = 0; /* serial data too old */
+       }
+
+       /* dispatch to the mode-dependent processing functions */
+       switch (up->mode) {
+       default:
+       case MODE_OP_STI:
+               eval_serial(peer, pp, up);
+               break;
+
+       case MODE_OP_STRICT:
+               eval_strict(peer, pp, up);
+               break;
+
+       case MODE_OP_AUTO:
+               eval_auto(peer, pp, up);
+               break;
        }
 }
 
@@ -1052,17 +1718,21 @@ gpsd_stop_socket(
        clockprocT * const pp = peer->procptr;
        gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
 
-       if (-1 != pp->io.fd)
+       if (-1 != pp->io.fd) {
+               if (syslogok(pp, up))
+                       msyslog(LOG_INFO,
+                               "%s: closing socket to GPSD, fd=%d",
+                               up->logname, pp->io.fd);
+               else
+                       DPRINTF(1, ("%s: closing socket to GPSD, fd=%d\n",
+                                   up->logname, pp->io.fd));
                io_closeclock(&pp->io);
-       pp->io.fd = -1;
-       if (syslogok(pp, up))
-               msyslog(LOG_INFO,
-                       "%s: closing socket to GPSD",
-                       refnumtoa(&peer->srcadr));
+               pp->io.fd = -1;
+       }
        up->tickover = up->tickpres;
        up->tickpres = min(up->tickpres + 5, TICKOVER_HIGH);
        up->fl_vers  = 0;
-       up->fl_tpv   = 0;
+       up->fl_sti   = 0;
        up->fl_pps   = 0;
        up->fl_watch = 0;
 }
@@ -1092,20 +1762,28 @@ gpsd_init_socket(
                if (syslogok(pp, up))
                        msyslog(LOG_ERR,
                                "%s: cannot create GPSD socket: %m",
-                               refnumtoa(&peer->srcadr));
+                               up->logname);
                goto no_socket;
        }
-       
-       /* make sure the socket is non-blocking */
+
+       /* Make sure the socket is non-blocking. Connect/reconnect and
+        * IO happen in an event-driven environment, and synchronous
+        * operations wreak havoc on that.
+        */
        rc = fcntl(up->fdt, F_SETFL, O_NONBLOCK, 1);
        if (-1 == rc) {
                if (syslogok(pp, up))
                        msyslog(LOG_ERR,
                                "%s: cannot set GPSD socket to non-blocking: %m",
-                               refnumtoa(&peer->srcadr));
+                               up->logname);
                goto no_socket;
        }
-       /* disable nagling */
+       /* Disable nagling. The way both GPSD and NTPD handle the
+        * protocol makes it record-oriented, and in most cases
+        * complete records (JSON serialised objects) will be sent in
+        * one sweep. Nagling gives not much advantage but adds another
+        * delay, which can worsen the situation for some packets.
+        */
        ov = 1;
        rc = setsockopt(up->fdt, IPPROTO_TCP, TCP_NODELAY,
                        (char*)&ov, sizeof(ov));
@@ -1113,24 +1791,53 @@ gpsd_init_socket(
                if (syslogok(pp, up))
                        msyslog(LOG_INFO,
                                "%s: cannot disable TCP nagle: %m",
-                               refnumtoa(&peer->srcadr));
+                               up->logname);
        }
 
-       /* start a non-blocking connect */
+       /* Start a non-blocking connect. There might be a synchronous
+        * connection result we have to handle.
+        */
        rc = connect(up->fdt, ai->ai_addr, ai->ai_addrlen);
-       if (-1 == rc && errno != EINPROGRESS) {
+       if (-1 == rc) {
+               if (errno == EINPROGRESS) {
+                       DPRINTF(1, ("%s: async connect pending, fd=%d\n",
+                                   up->logname, up->fdt));
+                       return;
+               }
+
                if (syslogok(pp, up))
                        msyslog(LOG_ERR,
                                "%s: cannot connect GPSD socket: %m",
-                               refnumtoa(&peer->srcadr));
+                               up->logname);
+               goto no_socket;
+       }
+
+       /* We had a successful synchronous connect, so we add the
+        * refclock processing ASAP. We still have to wait for the
+        * version string and apply the watch command later on, but we
+        * might as well get the show on the road now.
+        */
+       DPRINTF(1, ("%s: new socket connection, fd=%d\n",
+                   up->logname, up->fdt));
+
+       pp->io.fd = up->fdt;
+       up->fdt   = -1;
+       if (0 == io_addclock(&pp->io)) {
+               if (syslogok(pp, up))
+                       msyslog(LOG_ERR,
+                               "%s: failed to register with I/O engine",
+                               up->logname);
                goto no_socket;
        }
 
        return;
-  
+
   no_socket:
+       if (-1 != pp->io.fd)
+               close(pp->io.fd);
        if (-1 != up->fdt)
                close(up->fdt);
+       pp->io.fd    = -1;
        up->fdt      = -1;
        up->tickover = up->tickpres;
        up->tickpres = min(up->tickpres + 5, TICKOVER_HIGH);
@@ -1152,8 +1859,8 @@ gpsd_test_socket(
         * socket for writeability. Use the 'poll()' API if available
         * and 'select()' otherwise.
         */
-       DPRINTF(2, ("GPSD_JSON(%d): check connect, fd=%d\n",
-                   up->unit, up->fdt));
+       DPRINTF(2, ("%s: check connect, fd=%d\n",
+                   up->logname, up->fdt));
 
 #if defined(HAVE_SYS_POLL_H)
        {
@@ -1188,16 +1895,26 @@ gpsd_test_socket(
        ec = 0;
        lc = sizeof(ec);
        rc = getsockopt(up->fdt, SOL_SOCKET, SO_ERROR, &ec, &lc);
-       DPRINTF(1, ("GPSD_JSON(%d): connect finshed, fd=%d, ec=%d(%s)\n",
-                   up->unit, up->fdt, ec, strerror(ec)));
        if (-1 == rc || 0 != ec) {
-               errno = ec;
+               const char *errtxt;
+               if (0 == ec)
+                       ec = errno;
+               errtxt = strerror(ec);
                if (syslogok(pp, up))
                        msyslog(LOG_ERR,
-                               "%s: (async)cannot connect GPSD socket: %m",
-                               refnumtoa(&peer->srcadr));
+                               "%s: async connect to GPSD failed,"
+                               " fd=%d, ec=%d(%s)",
+                               up->logname, up->fdt, ec, errtxt);
+               else
+                       DPRINTF(1, ("%s: async connect to GPSD failed,"
+                               " fd=%d, ec=%d(%s)\n",
+                                   up->logname, up->fdt, ec, errtxt));
                goto no_socket;
-       }       
+       } else {
+               DPRINTF(1, ("%s: async connect to GPSD succeeded, fd=%d\n",
+                           up->logname, up->fdt));
+       }
+
        /* swap socket FDs, and make sure the clock was added */
        pp->io.fd = up->fdt;
        up->fdt   = -1;
@@ -1205,14 +1922,17 @@ gpsd_test_socket(
                if (syslogok(pp, up))
                        msyslog(LOG_ERR,
                                "%s: failed to register with I/O engine",
-                               refnumtoa(&peer->srcadr));
+                               up->logname);
                goto no_socket;
        }
        return;
-       
+
   no_socket:
-       if (-1 != up->fdt)
+       if (-1 != up->fdt) {
+               DPRINTF(1, ("%s: closing socket, fd=%d\n",
+                           up->logname, up->fdt));
                close(up->fdt);
+       }
        up->fdt      = -1;
        up->tickover = up->tickpres;
        up->tickpres = min(up->tickpres + 5, TICKOVER_HIGH);
@@ -1222,35 +1942,22 @@ gpsd_test_socket(
  * helper stuff
  */
 
-/*
- * shm_clockstats - dump and reset counters
+/* -------------------------------------------------------------------
+ * store a properly clamped precision value
  */
-static void
-gpsd_clockstats(
-       int           unit,
-       peerT * const peer
-       )
+static int16_t
+clamped_precision(
+       int rawprec)
 {
-       clockprocT * const pp = peer->procptr;
-       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
-
-       char logbuf[128];
-       unsigned int llen;
-
-       /* if snprintf() returns a negative values on errors (some older
-       * ones do) make sure we are NUL terminated. Using an unsigned
-       * result does the trick.
-       */
-       llen = snprintf(logbuf, sizeof(logbuf),
-                       "good=%-3u badtime=%-3u baddate=%-3u badreply=%-3u recv=%-3u",
-                       up->tc_good, up->tc_btime, up->tc_bdate,
-                       up->tc_breply, up->tc_recv);
-       logbuf[min(llen, sizeof(logbuf)-1)] = '\0';
-       record_clock_stats(&peer->srcadr, logbuf);
+       if (rawprec > 0)
+               rawprec = 0;
+       if (rawprec < -32)
+               rawprec = -32;
+       return (int16_t)rawprec;
 }
 
 /* -------------------------------------------------------------------
- * Convert a GPSD timestam (ISO8601 Format) to an l_fp
+ * Convert a GPSD timestamp (ISO8601 Format) to an l_fp
  */
 static BOOL
 convert_ascii_time(
@@ -1260,7 +1967,7 @@ convert_ascii_time(
        char           *ep;
        struct tm       gd;
        struct timespec ts;
-       long            dw;
+       uint32_t        dw;
 
        /* Use 'strptime' to take the brunt of the work, then parse
         * the fractional part manually, starting with a digit weight of
@@ -1268,17 +1975,22 @@ convert_ascii_time(
         */
        ts.tv_nsec = 0;
        ep = strptime(gps_time, "%Y-%m-%dT%H:%M:%S", &gd);
+       if (NULL == ep)
+               return FALSE; /* could not parse the mandatory stuff! */
        if (*ep == '.') {
-               dw = 100000000;
-               while (isdigit((unsigned char)*++ep)) {
-                       ts.tv_nsec += (*ep - '0') * dw;
-                       dw /= 10;
+               dw = 100000000u;
+               while (isdigit(*(unsigned char*)++ep)) {
+                       ts.tv_nsec += (*(unsigned char*)ep - '0') * dw;
+                       dw /= 10u;
                }
        }
        if (ep[0] != 'Z' || ep[1] != '\0')
-               return FALSE;
+               return FALSE; /* trailing garbage */
 
-       /* now convert the whole thing into a 'l_fp' */
+       /* Now convert the whole thing into a 'l_fp'. We do not use
+        * 'mkgmtime()' since its not standard and going through the
+        * calendar routines is not much effort, either.
+        */
        ts.tv_sec = (ntpcal_tm_to_rd(&gd) - DAY_NTP_STARTS) * SECSPERDAY
                  + ntpcal_tm_to_daysec(&gd);
        *fp = tspec_intv_to_lfp(ts);
@@ -1305,8 +2017,7 @@ save_ltc(
        pp->a_lastcode[len] = '\0';
 }
 
-/*
- * -------------------------------------------------------------------
+/* -------------------------------------------------------------------
  * asprintf replacement... it's not available everywhere...
  */
 static int
@@ -1336,6 +2047,57 @@ myasprintf(
        return (int)plen;
 }
 
+/* -------------------------------------------------------------------
+ * dump a raw data buffer
+ */
+
+static char *
+add_string(
+       char *dp,
+       char *ep,
+       const char *sp)
+{
+       while (dp != ep && *sp)
+               *dp++ = *sp++;
+       return dp;
+}
+
+static void
+log_data(
+       peerT      *peer,
+       const char *what,
+       const char *buf ,
+       size_t      len )
+{
+       /* we're running single threaded with regards to the clocks. */
+       static char s_lbuf[2048];
+
+       clockprocT * const pp = peer->procptr;
+       gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr;
+
+       if (debug > 1) {
+               const char *sptr = buf;
+               const char *stop = buf + len;
+               char       *dptr = s_lbuf;
+               char       *dtop = s_lbuf + sizeof(s_lbuf) - 1; /* for NUL */
+
+               while (sptr != stop && dptr != dtop) {
+                       if (*sptr == '\\') {
+                               dptr = add_string(dptr, dtop, "\\\\");
+                       } else if (isprint(*sptr)) {
+                               *dptr++ = *sptr;
+                       } else {
+                               char fbuf[6];
+                               snprintf(fbuf, sizeof(fbuf), "\\%03o", *(const u_char*)sptr);
+                               dptr = add_string(dptr, dtop, fbuf);
+                       }
+                       sptr++;
+               }
+               *dptr = '\0';
+               mprintf("%s[%s]: '%s'\n", up->logname, what, s_lbuf);
+       }
+}
+
 #else
 NONEMPTY_TRANSLATION_UNIT
 #endif /* REFCLOCK && CLOCK_GPSDJSON */