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 BitsValueDescription - 0..10 - 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 */