<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>
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 ≥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>
#include <stdlib.h>
#include <string.h>
-#include "jsmn.c"
-
static int test_passed = 0;
static int test_failed = 0;
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;
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);
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));
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"));
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], ""));
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"));
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"));
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);
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);
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);
}
int test_objects_arrays() {
- int i;
int r;
jsmn_parser p;
jsmntok_t tokens[10];
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;
}
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;
}
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;
}
*
* 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.
*/
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 */
/* =====================================================================
* 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
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
/* 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
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;
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;
{
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)));
}
/* 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
--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) {
/* ------------------------------------------------------------------ */
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);
}
/* ------------------------------------------------------------------ */
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
--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);
}
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;
+}
/* ------------------------------------------------------------------ */
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(
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;
}
/* ------------------------------------------------------------------ */
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;
}
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(
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;
}
/* ------------------------------------------------------------------ */
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;
}
/* =====================================================================
* 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
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)));
}
/* ------------------------------------------------------------------ */
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
* 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);
}
}
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);
}
/* ------------------------------------------------------------------ */
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;
}
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;
}
}
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;
}
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));
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);
* 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)
{
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;
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);
* 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(
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
*/
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);
pp->a_lastcode[len] = '\0';
}
-/*
- * -------------------------------------------------------------------
+/* -------------------------------------------------------------------
* asprintf replacement... it's not available everywhere...
*/
static int
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 */