From: Juergen Perlinger
Date: Mon, 13 Apr 2015 18:39:08 +0000 (+0200)
Subject: [Bug 2808] GPSD_JSON driver enhancements, step 1
X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=db5db6873e229898e362673d9014dcd500c1f60a;p=thirdparty%2Fntp.git
[Bug 2808] GPSD_JSON driver enhancements, step 1
bk: 552c0d4cpnRd1PUiIkQWVXqqr06iuA
---
diff --git a/ChangeLog b/ChangeLog
index e75384c88..66bf1ab6c 100644
--- 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
(4.2.8p2-RC3) 2015/04/03 Released by Harlan Stenn
diff --git a/html/drivers/driver46.html b/html/drivers/driver46.html
index 40aded80c..f52412f5d 100644
--- a/html/drivers/driver46.html
+++ b/html/drivers/driver46.html
@@ -14,7 +14,7 @@
GPSD NG client driver
Last update:
- 1-Mar-2014 03:48
+ 3-Apr-2015 10:32
UTC
Synopsis
@@ -28,8 +28,9 @@
Features:
- Description
+
+
Description
This driver is a client driver to the GPSD daemon, which
over the time became increasingly popular for UN*Xish
@@ -68,25 +69,37 @@
The overall accuracy depends on the receiver used. The driver
uses the error estimations (95% probability limits) provided by
- GPSD to set the clock precision dynamically according to these
- readings.
+ GPSD to set the clock precision dynamically according to
+ these readings.
- The driver needs the VERSION, TPV, PPS and WATCH objects of
- the GPSD protocol. (Others are quietly ignored.)
+ The driver needs the VERSION, TPV, PPS, WATCH and TOFF objects
+ of the GPSD protocol. (Others are quietly ignored.) The
+ driver can operate without the TOFF objects, which are available
+ with the protocol version 3.10 and above. (Not to be
+ confused with the release version of GPSD!)
+ Running without TOFF objects has a negative impact on the jitter
+ and offset of the serial timing information; if possible, a
+ version of GPSD with support for TOFF objects should be
+ used.
+
+ The acronym STI is used here as a synonym for serial
+ time information from the data channel of the receiver, no
+ matter what objects were used to obtain it.
+
- Naming a Device
+
Naming a Device
- The GPSD driver uses the same name as the NMEA driver,
- namely /dev/gpsu. There is a simple reason for
- that: While the NMEA driver and the GPSD driver can be
- active at the same time for different devices,
- 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 GPSD, as no new links etc need to be
- created.
+ The GPSD driver uses the same device name as the NMEA
+ driver, namely /dev/gpsu. There is a simple
+ reason for that: While the NMEA driver and the GPSD
+ driver can be active at the same time for different
+ devices, 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 GPSD, as no
+ new links etc need to be created.
GPSD is normally started with the device name to access;
@@ -97,14 +110,16 @@
identification. This makes the migration from the built-in NMEA
driver a bit easier.
- Note: GPSD (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.
+
Note: GPSD (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.
- The 'mode' byte
+
+
+
The 'mode' word
A few operation modes can be selected with the mode word.
@@ -113,70 +128,196 @@
The Mode Word |
| Bits | Value | Description |
- | 0..1 | 0 |
- 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. |
+
| 0..1 |
+ 0 |
+ STI only operation. This mode is affected by the timing
+ stability of whatever protocol is used between the GPS
+ device and GPSD.
+
+ Running on STI only is not recommended in general. Possible
+ use cases include:
+
+ - The receiver does not provide a PPS signal.
+
- The receiver does provide a PPS signal and
+ the secondary PPS unit is used.
+
- The receiver has a stable serial timing and a proper
+ fudge can be established.
+
- You have other time sources available and want to
+ establish a useful fudge value for time2.
+
+ |
- | 1 |
- Require TPV and PPS to work. |
+
+ | 1 |
+ 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.
+
+ 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.
+ |
| 2 |
- 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. |
+ 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.
+
Important Notice: This is an expiremental
+ feature! 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. |
- | 3 |
- 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. |
+
+ | 3 |
+ (reserved for future extension, do not use) |
+
+
+ | 2..31 |
+ (reserved for future extension, do not
+ use) |
- IMPORTANT: work in progress, mode
- word ignored right now. Fixed mode '0' operation.
- Syslog flood throttle
+
+
+
Syslog flood throttle
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.
+ 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.
Therefore, fudge flag3 can be used to disable 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.
+ 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.
- Fudge Factors
+
+
+
PPS secondary clock unit
+ 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 GPSD and needs the corresponding master
+ unit to work1. Use
+ the 'noselect' keyword on the primary unit if you are not
+ interested in its data.
+
The secondary unit employs the usual precautions before
+ feeding clock samples:
+
+ - The system must be already in a synchronised state.
+
- The system offset must be less than 400ms absolute.
+
- The phase adjustment from the PPS signal must also be less
+ than 400ms absolute.
+
+ If fudge flag flag1 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 signal2. 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.
+ Attention: This unit uses its own PPS fudge value
+ which must be set as fudge time1. Only the fudge
+ values time1 and flag1 have an impact on secondary
+ units.
+
+
+
+
Clockstats
+ 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.
+
+ The 4th field is the number of good time samples processed
+ since the last poll.
+
+ 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.
+
+ The 6th field is the number of received and known JSON records
+ since the last poll. (Note: Since the sample data is
+ collected from several JSON records and some JSON data is not
+ related to samples, this is not the sum of the other
+ fields.
+
+
+
+
+
Fudge Factors
- time1 time
- Specifies the PPS time offset calibration factor, in seconds
and fraction, with default 0.0.
- time2 time
- - Specifies the TPV time offset calibration factor, in seconds
- and fraction, with default 0.0.
+ - [Primary Unit] Specifies the TPV/TIME time offset
+ calibration factor, in seconds and fraction, with default
+ 0.0.
- stratum number
- - Specifies the driver stratum, in decimal from 0 to 15, with default 0.
+ - Specifies the driver stratum, in decimal from 0 to 15, with
+ default 0.
- refid string
- Specifies the driver reference identifier, an ASCII string
from one to four characters, with default GPSD.
- - flag1 0 | 1
- (not used)
- - flag2 0 | 1
- (not used)
- - flag3 0 | 1
- If set, disable the
- log throttle. Useful when tracking problems in the interaction
- between GPSD and NTPD, 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.
- - flag4 0 | 1
- If set, write a clock stats
- line on every poll cycle.
+ - flag1 0 | 1
- [Secondary
+ Unit] When set, flags the secondary clock unit as a
+ potential PPS peer as long as good PPS data is available.
+
+ - flag2 0 | 1
+ - [Primary Unit] When set, disables the
+ processing of incoming PPS records. Intended as an aide to
+ test the effects of a PPS dropout when using automatic mode
+ (mode 2).
+
+ - flag3 0 | 1
- [Primary Unit]
+ If set, disables the log throttle. Useful when tracking
+ problems in the interaction between GPSD and NTPD,
+ 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.
+ - flag4 0 | 1
- [Primary Unit]
+ If set, write a clock stats line on every poll cycle.
+
+
+
+
+ 1) 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.
+
+ 2) 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 GPSD. 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.
+
Additional Information
Reference Clock Drivers
diff --git a/libjsmn/Makefile b/libjsmn/Makefile
index ac947a3af..5e3e2a97f 100644
--- a/libjsmn/Makefile
+++ b/libjsmn/Makefile
@@ -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
diff --git a/libjsmn/README.md b/libjsmn/README.md
index abccffa43..353af94a8 100644
--- a/libjsmn/README.md
+++ b/libjsmn/README.md
@@ -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
index 000000000..3490bbf49
--- /dev/null
+++ b/libjsmn/example/jsondump.c
@@ -0,0 +1,112 @@
+#include
+#include
+#include
+#include
+#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
index 000000000..a6f8e6a98
--- /dev/null
+++ b/libjsmn/example/simple.c
@@ -0,0 +1,75 @@
+#include
+#include
+#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;
+}
diff --git a/libjsmn/jsmn.c b/libjsmn/jsmn.c
index aa8b12b80..a0f4f69c6 100644
--- a/libjsmn/jsmn.c
+++ b/libjsmn/jsmn.c
@@ -1,15 +1,14 @@
#include
-#include
#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) {
diff --git a/libjsmn/jsmn.h b/libjsmn/jsmn.h
index 03b2c1aa4..95fb2cabd 100644
--- a/libjsmn/jsmn.h
+++ b/libjsmn/jsmn.h
@@ -1,6 +1,12 @@
#ifndef __JSMN_H_
#define __JSMN_H_
+#include
+
+#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_ */
diff --git a/libjsmn/jsmn_test.c b/libjsmn/jsmn_test.c
index fe5f00045..396885922 100644
--- a/libjsmn/jsmn_test.c
+++ b/libjsmn/jsmn_test.c
@@ -2,8 +2,6 @@
#include
#include
-#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;
}
diff --git a/ntpd/refclock_gpsdjson.c b/ntpd/refclock_gpsdjson.c
index 2171a4fff..8cf43996e 100644
--- a/ntpd/refclock_gpsdjson.c
+++ b/ntpd/refclock_gpsdjson.c
@@ -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
@@ -28,13 +55,44 @@
#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
*/
@@ -44,6 +102,7 @@
#include
#include
#include
+#include
#include
#include
@@ -66,6 +125,42 @@
#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 */
@@ -76,9 +171,24 @@
#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
@@ -90,7 +200,11 @@
# 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 */