From: Juergen Perlinger Date: Sat, 15 Feb 2014 01:56:41 +0000 (+0100) Subject: added the GPSD-JSON client driver X-Git-Tag: NTP_4_2_7P427~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d25fd2c70fc8a990517f27535b5f797297f05f23;p=thirdparty%2Fntp.git added the GPSD-JSON client driver bk: 52fec959Me2Y-etymV07kDl5peagkA --- diff --git a/configure.ac b/configure.ac index 8b1735b50..c4ae9b7bd 100644 --- a/configure.ac +++ b/configure.ac @@ -2286,6 +2286,24 @@ case "$ntp_ok" in esac AC_MSG_RESULT([$ntp_ok]) +AC_MSG_CHECKING([for GPSD JSON receiver]) +AC_ARG_ENABLE( + [GPSD], + [AS_HELP_STRING( + [--enable-GPSD], + [+ GPSD JSON receiver] + )], + [ntp_ok=$enableval], + [ntp_ok=$ntp_eac] +) +case "$ntp_ok" in + yes) + ntp_refclock=yes + AC_DEFINE([CLOCK_GPSDJSON], [1], [GPSD JSON receiver]) + ;; +esac +AC_MSG_RESULT([$ntp_ok]) + AC_MSG_CHECKING([for ONCORE Motorola VP/UT Oncore GPS]) AC_ARG_ENABLE( [ONCORE], diff --git a/include/ntp.h b/include/ntp.h index 95b2542ab..d485de03b 100644 --- a/include/ntp.h +++ b/include/ntp.h @@ -517,8 +517,9 @@ struct peer { #define REFCLK_ZYFER 42 /* Zyfer GPStarplus receiver */ #define REFCLK_RIPENCC 43 /* RIPE NCC Trimble driver */ #define REFCLK_NEOCLOCK4X 44 /* NeoClock4X DCF77 or TDF receiver */ -#define REFCLK_TSYNCPCI 45 /* Spectracom TSYNC PCI timing board */ -#define REFCLK_MAX 45 /* Spectracom TSYNC PCI timing board */ +#define REFCLK_TSYNCPCI 45 /* Spectracom TSYNC PCI timing board */ +#define REFCLK_GPSDJSON 46 +#define REFCLK_MAX 46 /* diff --git a/libjsmn/LICENSE b/libjsmn/LICENSE new file mode 100644 index 000000000..c84fb2e97 --- /dev/null +++ b/libjsmn/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Serge A. Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/libjsmn/Makefile b/libjsmn/Makefile new file mode 100644 index 000000000..ac947a3af --- /dev/null +++ b/libjsmn/Makefile @@ -0,0 +1,27 @@ +# You can put your build options here +-include config.mk + +all: libjsmn.a + +libjsmn.a: jsmn.o + $(AR) rc $@ $^ + +%.o: %.c jsmn.h + $(CC) -c $(CFLAGS) $< -o $@ + +test: jsmn_test + ./jsmn_test + +jsmn_test: jsmn_test.o + $(CC) -L. -ljsmn $< -o $@ + +jsmn_test.o: jsmn_test.c libjsmn.a + +clean: + rm -f jsmn.o jsmn_test.o + rm -f jsmn_test + rm -f jsmn_test.exe + rm -f libjsmn.a + +.PHONY: all clean test + diff --git a/libjsmn/README.md b/libjsmn/README.md new file mode 100644 index 000000000..abccffa43 --- /dev/null +++ b/libjsmn/README.md @@ -0,0 +1,161 @@ + +JSMN +==== + +jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be +easily integrated into resource-limited or embedded projects. + +You can find more information about JSON format at [json.org][1] + +Library sources are available at [bitbucket.org/zserge/jsmn][2] + +The web page with some information about jsmn can be found at +[http://zserge.com/jsmn.html][3] + +Philosophy +---------- + +Most JSON parsers offer you a bunch of functions to load JSON data, parse it +and extract any value by its name. jsmn proves that checking the correctness of +every JSON packet or allocating temporary objects to store parsed JSON fields +often is an overkill. + +JSON format itself is extremely simple, so why should we complicate it? + +jsmn is designed to be **robust** (it should work fine even with erroneous +data), **fast** (it should parse data on the fly), **portable** (no superfluous +dependencies or non-standard C extensions). An of course, **simplicity** is a +key feature - simple code style, simple algorithm, simple integration into +other projects. + +Features +-------- + +* compatible with C89 +* no dependencies (even libc!) +* highly portable (tested on x86/amd64, ARM, AVR) +* about 200 lines of code +* extremely small code footprint +* API contains only 2 functions +* no dynamic memory allocation +* incremental single-pass parsing +* library code is covered with unit-tests + +Design +------ + +The rudimentary jsmn object is a **token**. Let's consider a JSON string: + + '{ "name" : "Jack", "age" : 27 }' + +It holds the following tokens: + +* Object: `{ "name" : "Jack", "age" : 27}` (the whole object) +* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) +* Number: `27` + +In jsmn, tokens do not hold any data, but point to token boundaries in JSON +string instead. In the example above jsmn will create tokens like: Object +[0..31], String [3..7], String [12..16], String [20..23], Number [27..29]. + +Every jsmn token has a type, which indicates the type of corresponding JSON +token. jsmn supports the following token types: + +* Object - a container of key-value pairs, e.g.: + `{ "foo":"bar", "x":0.3 }` +* Array - a sequence of values, e.g.: + `[ 1, 2, 3 ]` +* String - a quoted sequence of chars, e.g.: `"foo"` +* Primitive - a number, a boolean (`true`, `false`) or `null` + +Besides start/end positions, jsmn tokens for complex types (like arrays +or objects) also contain a number of child items, so you can easily follow +object hierarchy. + +This approach provides enough information for parsing any JSON data and makes +it possible to use zero-copy techniques. + +Install +------- + +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. + +To build the library, run `make`. It is also recommended to run `make test`. +Let me know, if some tests fail. + +If build was successful, you should get a `libjsmn.a` library. +The header file you should include is called `"jsmn.h"`. + +API +--- + +Token types are described by `jsmntype_t`: + + typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 + } jsmntype_t; + +**Note:** Unlike JSON data types, primitive tokens are not divided into +numbers, booleans and null, because one can easily tell the type using the +first character: + +* 't', 'f' - boolean +* 'n' - null +* '-', '0'..'9' - number + +Token is an object of `jsmntok_t` type: + + typedef struct { + jsmntype_t type; // Token type + int start; // Token start position + int end; // Token end position + int size; // Number of child (nested) tokens + } jsmntok_t; + +**Note:** string tokens point to the first character after +the opening quote and the previous symbol before final quote. This was made +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; + jsmntok_t tokens[10]; + + // js - pointer to JSON string + // tokens - an array of tokens available + // 10 - number of tokens available + jsmn_init_parser(&parser, js, tokens, 10); + +This will create a parser, that can parse up to 10 JSON tokens from `js` string. + +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 + +If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call +`jsmn_parse` once more. If you read json data from the stream, you can +periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`. +You will get this error until you reach the end of JSON data. + +Other info +---------- + +This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), + so feel free to integrate it in your commercial products. + +[1]: http://www.json.org/ +[2]: https://bitbucket.org/zserge/jsmn/wiki/Home +[3]: http://zserge.com/jsmn.html diff --git a/libjsmn/jsmn.c b/libjsmn/jsmn.c new file mode 100644 index 000000000..4f70adb0d --- /dev/null +++ b/libjsmn/jsmn.c @@ -0,0 +1,268 @@ +#include +#include + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * 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) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return JSMN_SUCCESS; +} + +/** + * 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) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return JSMN_SUCCESS; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\') { + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + int i = 0; + for(; i < 4 && 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 */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * 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 r; + int i; + jsmntok_t *token; + + for (; js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': + 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' : +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + + } + } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + + return JSMN_SUCCESS; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/libjsmn/jsmn.h b/libjsmn/jsmn.h new file mode 100644 index 000000000..03b2c1aa4 --- /dev/null +++ b/libjsmn/jsmn.h @@ -0,0 +1,67 @@ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; + +typedef enum { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* 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 +} jsmnerr_t; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +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, + jsmntok_t *tokens, unsigned int num_tokens); + +#endif /* __JSMN_H_ */ diff --git a/libjsmn/jsmn_test.c b/libjsmn/jsmn_test.c new file mode 100644 index 000000000..fe5f00045 --- /dev/null +++ b/libjsmn/jsmn_test.c @@ -0,0 +1,409 @@ +#include +#include +#include + +#include "jsmn.c" + +static int test_passed = 0; +static int test_failed = 0; + +/* Terminate current test with error */ +#define fail() return __LINE__ + +/* Successfull end of the test case */ +#define done() return 0 + +/* Check single condition */ +#define check(cond) do { if (!(cond)) fail(); } while (0) + +/* Test runner */ +static void test(int (*func)(void), const char *name) { + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } +} + +#define TOKEN_EQ(t, tok_start, tok_end, tok_type) \ + ((t).start == tok_start \ + && (t).end == tok_end \ + && (t).type == (tok_type)) + +#define TOKEN_STRING(js, t, s) \ + (strncmp(js+(t).start, s, (t).end - (t).start) == 0 \ + && strlen(s) == (t).end - (t).start) + +#define TOKEN_PRINT(t) \ + printf("start: %d, end: %d, type: %d, size: %d\n", \ + (t).start, (t).end, (t).type, (t).size) + +int test_empty() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t t[10]; + + js = "{}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, t, 10); + check(r == JSMN_SUCCESS); + 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); + 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); + 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); + 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); + return 0; +} + +int test_simple() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0}"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + 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)); + + check(TOKEN_STRING(js, tokens[0], js)); + check(TOKEN_STRING(js, tokens[1], "a")); + check(TOKEN_STRING(js, tokens[2], "0")); + + jsmn_init(&p); + js = "[\"a\":{},\"b\":{}]"; + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + 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); + + return 0; +} + +int test_primitive() { + 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 + && 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 + && 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 + && 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 + && 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 + && tok[1].type == JSMN_PRIMITIVE); + check(TOKEN_STRING(js, tok[0], "nullVar")); + check(TOKEN_STRING(js, tok[1], "null")); +#endif + return 0; +} + +int test_string() { + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + js = "\"strVar\" : \"hello world\""; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && 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 + && 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 + && tok[1].type == JSMN_STRING); + check(TOKEN_STRING(js, tok[0], "strVar")); + check(TOKEN_STRING(js, tok[1], "")); + + return 0; +} + +int test_partial_string() { + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "\"x\": \"va"; + r = jsmn_parse(&p, 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\": \"valu"; + r = jsmn_parse(&p, 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 + && 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 + && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING + && tok[3].type == JSMN_STRING); + check(TOKEN_STRING(js, tok[0], "x")); + check(TOKEN_STRING(js, tok[1], "value")); + check(TOKEN_STRING(js, tok[2], "y")); + check(TOKEN_STRING(js, tok[3], "value y")); + + return 0; +} + +int test_unquoted_keys() { +#ifndef JSMN_STRICT + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "key1: \"value\"\nkey2 : 123"; + + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && 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")); + check(TOKEN_STRING(js, tok[1], "value")); + check(TOKEN_STRING(js, tok[2], "key2")); + check(TOKEN_STRING(js, tok[3], "123")); +#endif + return 0; +} + +int test_partial_array() { + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = " [ 1, true, "; + r = jsmn_parse(&p, 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 + && 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 + && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE + && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE + && tok[5].type == JSMN_STRING); + /* check child nodes of the 2nd 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 + && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE + && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE + && tok[5].type == JSMN_STRING); + check(tok[3].size == 2); + check(tok[0].size == 3); + return 0; +} + +int test_array_nomem() { + int i; + int r; + jsmn_parser p; + jsmntok_t toksmall[10], toklarge[10]; + const char *js; + + js = " [ 1, true, [123, \"hello\"]]"; + + for (i = 0; i < 6; i++) { + jsmn_init(&p); + memset(toksmall, 0, sizeof(toksmall)); + memset(toklarge, 0, sizeof(toklarge)); + r = jsmn_parse(&p, js, toksmall, i); + check(r == JSMN_ERROR_NOMEM); + + memcpy(toklarge, toksmall, sizeof(toksmall)); + + r = jsmn_parse(&p, js, toklarge, 10); + check(r == JSMN_SUCCESS); + + check(toklarge[0].type == JSMN_ARRAY && toklarge[0].size == 3); + check(toklarge[3].type == JSMN_ARRAY && toklarge[3].size == 2); + } + return 0; +} + +int test_objects_arrays() { + int i; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + const char *js; + + js = "[10}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "[10]"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\": 1]"; + jsmn_init(&p); + r = jsmn_parse(&p, 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); + + return 0; +} + +int test_unicode_characters() { + jsmn_parser p; + jsmntok_t tokens[10]; + const char *js; + + int r; + js = "{\"a\":\"\\uAbcD\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"str\\u0000\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"\\uFFFFstr\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"str\\uFFGFstr\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":\"str\\u@FfF\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":[\"\\u028\"]}"; + jsmn_init(&p); + r = jsmn_parse(&p, 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); + + return 0; +} + +int main() { + test(test_empty, "general test for a empty JSON objects/arrays"); + test(test_simple, "general test for a simple JSON string"); + test(test_primitive, "test primitive JSON data types"); + test(test_string, "test string JSON data types"); + test(test_partial_string, "test partial JSON string parsing"); + test(test_partial_array, "test partial array reading"); + test(test_array_nomem, "test array reading with a smaller number of tokens"); + 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"); + printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); + return 0; +} + diff --git a/libntp/clocktypes.c b/libntp/clocktypes.c index e0b71e0a8..de7f6b4f3 100644 --- a/libntp/clocktypes.c +++ b/libntp/clocktypes.c @@ -103,6 +103,8 @@ struct clktype clktypes[] = { "NEOCLK4X"}, { REFCLK_TSYNCPCI, "Spectracom TSYNC PCI timing board (45)", "PCI_TSYNC"}, + { REFCLK_GPSDJSON, "GPSD JSON socket (46)", + "GPSD_JSON"}, { -1, "", "" } }; diff --git a/ntpd/Makefile.am b/ntpd/Makefile.am index 3cbf3769e..a7af593df 100644 --- a/ntpd/Makefile.am +++ b/ntpd/Makefile.am @@ -244,6 +244,7 @@ libntpd_a_SOURCES = \ refclock_datum.c \ refclock_dumbclock.c \ refclock_fg.c \ + refclock_gpsdjson.c \ refclock_gpsvme.c \ refclock_heath.c \ refclock_hopfser.c \ diff --git a/ntpd/refclock_conf.c b/ntpd/refclock_conf.c index d56bf0a01..803a6b0f5 100644 --- a/ntpd/refclock_conf.c +++ b/ntpd/refclock_conf.c @@ -257,6 +257,12 @@ extern struct refclock refclock_tsyncpci; #else #define refclock_tsyncpci refclock_none #endif + +#ifdef CLOCK_GPSDJSON +extern struct refclock refclock_gpsdjson; +#else +#define refclock_gpsdjson refclock_none +#endif /* * Order is clock_start(), clock_shutdown(), clock_poll(), * clock_control(), clock_init(), clock_buginfo, clock_flags; @@ -309,7 +315,8 @@ struct refclock * const refclock_conf[] = { &refclock_zyfer, /* 42 REFCLK_ZYFER */ &refclock_ripencc, /* 43 REFCLK_RIPENCC */ &refclock_neoclock4x, /* 44 REFCLK_NEOCLOCK4X */ - &refclock_tsyncpci /* 45 REFCLK_TSYNCPCI */ + &refclock_tsyncpci, /* 45 REFCLK_TSYNCPCI */ + &refclock_gpsdjson /* 46 REFCLK_GPSDJSON */ }; u_char num_refclock_conf = sizeof(refclock_conf)/sizeof(struct refclock *); diff --git a/ntpd/refclock_gpsdjson.c b/ntpd/refclock_gpsdjson.c new file mode 100644 index 000000000..1432b7997 --- /dev/null +++ b/ntpd/refclock_gpsdjson.c @@ -0,0 +1,921 @@ +/* + * refclock_gpsdjson.c - clock driver as GPSD JSON client + * Juergen Perlinger (perlinger@ntp.org) Feb 11, 2014 + * inspired by refclock_nmea.c + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#if defined(REFCLOCK) && defined(CLOCK_GPSDJSON) + + +/* ===================================================================== + * get the little JSMN library directly into our guts + */ +#include "../libjsmn/jsmn.c" + +/* ===================================================================== + * header stuff we need + */ +#include "ntp_types.h" +#include +//#include +#include +#include +#include +#include +#include + +//#include + +#include +#include +//#include +#include + + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" +#include "ntp_calendar.h" +#include "timespecops.h" + +#define PRECISION (-9) /* precision assumed (about 2 ms) */ +#define PPS_PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPSD" /* reference id */ +#define DESCRIPTION "GPSD JSON client clock" /* who we are */ + +#define MAX_PDU_LEN 1600 +#define TICKOVER_MAX 10 + +#ifndef BOOL +# define BOOL int +#endif +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +/* ===================================================================== + * We use the same device name scheme as does the NMEA driver; since + * GPSD supports the same links, we can select devices by a fixed name. + */ +static const char * s_dev_stem = "/dev/gps"; + +/* ===================================================================== + * forward declarations for transfer vector and the vector itself + */ + +static void gpsd_init (void); +static int gpsd_start (int, struct peer *); +static void gpsd_shutdown (int, struct peer *); +static void gpsd_receive (struct recvbuf *); +static void gpsd_poll (int, struct peer *); +static void gpsd_control (int, const struct refclockstat *, + struct refclockstat *, struct peer *); +static void gpsd_timer (int, struct peer *); + +struct refclock refclock_gpsdjson = { + gpsd_start, /* start up driver */ + gpsd_shutdown, /* shut down driver */ + gpsd_poll, /* transmit poll message */ + gpsd_control, /* fudge control */ + gpsd_init, /* initialize driver */ + noentry, /* buginfo */ + gpsd_timer /* called once per second */ +}; + +/* ===================================================================== + * our local clock unit and data + */ +struct gpsd_unit { + /* current line protocol version */ + uint16_t proto_major; + uint16_t proto_minor; + + /* PULSE time stamps */ + l_fp pps_stamp; /* effective PPS time stamp */ + l_fp pps_recvt; /* when we received the PPS message */ + + /* TPV (GPS) time stamps */ + l_fp tpv_stamp; /* effective GPS time stamp */ + l_fp tpv_recvt; /* whn we received the TPV message */ + + /* Flags to indicate available data */ + int fl_tpv : 1; /* valid TPV seen (have time) */ + int fl_pps : 1; /* valid pulse seen */ + int fl_ver : 1; /* have protocol version */ + + /* admin stuff for sockets and device selection */ + struct pollfd pfd; /* for non-blocking connect */ + struct addrinfo * addr; /* next address to try */ + int tickover; /* timeout countdown */ + char * device; /* device name of unit */ + + /* record assemby buffer and saved length */ + int buflen; + char buffer[MAX_PDU_LEN]; +}; + +/* ===================================================================== + * our local parser context + */ +#define JSMN_MAXTOK 100 + +struct json_ctx { + char * buf; + int ntok; + jsmntok_t tok[JSMN_MAXTOK]; +}; + +typedef struct json_ctx json_ctx; +typedef int tok_ref; +#define INVALID_TOKEN (-1) + +static BOOL json_parse_record(json_ctx * ctx, char * buf); +static int json_object_lookup(const json_ctx *ctx, tok_ref tok, const char * key); + +/* ===================================================================== + * static local helpers forward decls + */ +static void gpsd_init_socket(struct refclockproc * const pp, + struct gpsd_unit * const up); +static void gpsd_test_socket(struct refclockproc * const pp, + struct gpsd_unit * const up); +static void gpsd_stop_socket(struct refclockproc * const pp, + struct gpsd_unit * const up); +static void gpsd_parse(struct peer * const peer, + const l_fp * const rtime); +static BOOL convert_time(l_fp * fp, const char * gps_time); +static void save_ltc(struct refclockproc * const pp, const char * const tc); + + +/* ===================================================================== + * JSON parsing utils + */ +static tok_ref json_token_skip(const json_ctx * ctx, tok_ref tid); +static tok_ref json_object_lookup(const json_ctx * ctx, tok_ref tid, + const char * key); +static const char* json_object_lookup_string(const json_ctx * ctx, + tok_ref tid, const char * key); +#ifdef HAVE_LONG_LONG +static long long json_object_lookup_int(const json_ctx * ctx, + tok_ref tid, const char * key); +#else +# error Blooper! we need some 64-bit integer... +#endif +static double json_object_lookup_float(const json_ctx * ctx, + tok_ref tid, const char * key); +static BOOL json_parse_record(json_ctx * ctx, char * buf); + +/* ===================================================================== + * local / static stuff + */ + +/* The logon string is actually the ?WATCH command of GPSD, using JSON + * data and selecting the GPS device name we created from our unit + * number. [Note: This is a format string!] + */ +static const char * s_logon = + "?WATCH={\"enable\":true,\"json\":true,\"device\":\"%s\"};\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 struct addrinfo * s_gpsd_addr; + +/* ===================================================================== + * the clock functions + */ + +/* --------------------------------------------------------------------- + * Init: This currently just gets the socket address for the GPS daemon + */ +static void +gpsd_init(void) +{ + struct addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + + //hints.ai_family = AF_UNSPEC; + hints.ai_family = AF_INET; + 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)) + s_gpsd_addr = NULL; +} + +/* --------------------------------------------------------------------- + * Start: allocate a unit pointer and set up the runtime data + */ + +static int +gpsd_start( + int unit, + struct peer * peer) +{ + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = emalloc_zero(sizeof(*up)); + + struct stat sb; + + UNUSED_ARG(unit); + + up->pfd.fd = -1; + up->pfd.events = POLLIN; + up->addr = s_gpsd_addr; + + /* Allocate and initialize unit structure */ + pp->unitptr = (caddr_t)up; + pp->io.fd = -1; + pp->io.clock_recv = gpsd_receive; + pp->io.srcclock = peer; + pp->io.datalen = 0; + pp->a_lastcode[0] = '\0'; + + /* Initialize miscellaneous variables */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy(&pp->refid, REFID, 4); + + /* 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 == asprintf(&up->device, "%s%u", s_dev_stem, unit)) { + up->device = NULL; /* undefined if asprintf fails... */ + /*TODO: logging*/ + goto dev_fail; + } + if (-1 == stat(up->device, &sb)) { + /*TODO: logging */ + goto dev_fail; + } + if (!S_ISCHR(sb.st_mode)) { + /*TODO: logging */ + goto dev_fail; + } + + return TRUE; + +dev_fail: + /* On failure, remove all UNIT ressources and declare defeat. */ + if (up) { + free(up->device); + free(up); + } + pp->unitptr = (caddr_t)NULL; + return FALSE; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_shutdown( + int unit, + struct peer * peer) +{ + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = (struct gpsd_unit *)pp->unitptr; + + UNUSED_ARG(unit); + + if (up) { + free(up->device); + free(up); + } + pp->unitptr = (caddr_t)NULL; + if (-1 != pp->io.fd) + io_closeclock(&pp->io); + pp->io.fd = -1; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_receive( + struct recvbuf * rbufp) +{ + /* declare & init control structure ptrs */ + struct peer * const peer = rbufp->recv_peer; + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = (struct gpsd_unit*)pp->unitptr; + + const char *psrc, *esrc; + char *pdst, *edst, ch; + + /* 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 + * an EoL (that is, line feed) but we truncate the message if it + * does not fit the buffer. GPSD might truncate messages, too, + * so dealing with truncated buffers is necessary anyway. + */ + psrc = (const char*)rbufp->recv_buffer; + esrc = psrc + rbufp->recv_length; + + pdst = up->buffer + up->buflen; + edst = pdst + sizeof(up->buffer) - 1; /* for trailing NUL */ + + while (psrc != esrc) { + ch = *psrc++; + if (ch == '\n') { + /* trim trailing whitespace & terminate buffer */ + while (pdst != up->buffer && pdst[-1] <= ' ') + --pdst; + *pdst = '\0'; + /* process data and reset buffer */ + gpsd_parse(peer, &rbufp->recv_time); + pdst = up->buffer; + } else if (pdst != edst) { + /* add next char, ignoring leading whitespace */ + if (ch > ' ' || pdst != up->buffer) + *pdst++ = ch; + } + } + up->buflen = pdst - up->buffer; + up->tickover = TICKOVER_MAX; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_poll( + int unit, + struct peer * peer) +{ + struct refclockproc * const pp = peer->procptr; + + /* If the median filter is empty, claim a timeout; otherwise + * process the input data and keep the stats going. + */ + if (pp->coderecv == pp->codeproc) { + peer->flags &= ~FLAG_PPS; + peer->precision = PRECISION; + refclock_report(peer, CEVNT_TIMEOUT); + } else { + pp->polls++; + pp->lastref = pp->lastrec; + refclock_receive(peer); + } + record_clock_stats(&peer->srcadr, pp->a_lastcode); +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_control( + int unit, + const struct refclockstat * in_st, + struct refclockstat * out_st, + struct peer * peer ) +{ + /* Not yet implemented -- do we need it? */ +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_timer( + int unit, + struct peer * peer) +{ + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = (struct gpsd_unit *)pp->unitptr; + + /* This is used for timeout handling. Nothing that needs + * sub-second precison appens here, so receive/connec/retry + * timeouts are simply handled by a count down, and then we + * decide what to do by the socket values. + * + * Note that the timer stays at zero here, unless some of the + * functions set it to another value. + */ + if (up->tickover) + --up->tickover; + + if (0 == up->tickover) { + if (-1 != pp->io.fd) + gpsd_stop_socket(pp, up); + else if (-1 != up->pfd.fd) + gpsd_test_socket(pp, up); + else if (NULL != s_gpsd_addr) + gpsd_init_socket(pp, up); + } +} + +/* ===================================================================== + * static local helpers + */ + +static void +process_version( + struct peer * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = (struct gpsd_unit *)pp->unitptr; + + int len; + char * buf; + + /* get protocol version number */ + 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"); + if (0 == errno) { + up->fl_ver = -1; + printf("\n\nprotocol version = %u.%u\n\n\n", + up->proto_major, up->proto_minor); + } + /*TODO: validate protocol version! */ + + /* request watch for our GPS device + * 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 + * output otherwise, this should always work. (Unless the + * TCP/IP window size gets lower than the length of the + * request. We handle that whe it happens.) + */ + snprintf(up->buffer, sizeof(up->buffer), + s_logon, up->device); + buf = up->buffer; + len = strlen(buf); + if (len != write(pp->io.fd, buf, len)) { + /*TODO: log request error! */ + /*Note: if the server fails to read our request, the + * resulting data timeout will take care of the + * connection! + */ + } +} + +/* ------------------------------------------------------------------ */ + +static void +process_tpv( + struct peer * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = (struct gpsd_unit *)pp->unitptr; + + const char * gps_time; + uint8_t gps_mode; + + errno = 0; + gps_mode = (uint8_t)json_object_lookup_int( + jctx, 0, "mode"); + gps_time = json_object_lookup_string( + jctx, 0, "time"); + switch (errno) { + case 0: + save_ltc(pp, gps_time); + if (convert_time(&up->tpv_stamp, gps_time)) { + printf(" tpv, stamp='%s', recvt='%s' mode=%u\n", + gmprettydate(&up->tpv_stamp), + gmprettydate(&up->tpv_recvt), + gps_mode); + + up->tpv_recvt = *rtime; + up->fl_tpv = -1; + } else { + refclock_report(peer, CEVNT_BADTIME); + } + break; + + default: + refclock_report(peer, CEVNT_BADREPLY); + break; + } +} + +/* ------------------------------------------------------------------ */ + +static void +process_pps( + struct peer * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = (struct gpsd_unit *)pp->unitptr; + + uint32_t secs; + double frac; + + errno = 0; + secs = (uint32_t)json_object_lookup_int( + jctx, 0, "real_sec"); + frac = json_object_lookup_float( + jctx, 0, "real_musec"); + switch (errno) { + case 0: + frac *= 1.0e-6; + M_DTOLFP(frac, up->pps_stamp.l_ui, up->pps_stamp.l_uf); + up->pps_stamp.l_ui += secs; + up->pps_stamp.l_ui += JAN_1970; + printf(" pps, stamp='%s', recvt='%s'\n", + gmprettydate(&up->pps_stamp), + gmprettydate(&up->pps_recvt)); + + up->pps_recvt = *rtime; + /* 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; + break; + + case ERANGE: + refclock_report(peer, CEVNT_BADREPLY); + break; + + default: + refclock_report(peer, CEVNT_BADTIME); + break; + } +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_parse( + struct peer * const peer, + const l_fp * const rtime) +{ + struct refclockproc * const pp = peer->procptr; + struct gpsd_unit * const up = (struct gpsd_unit *)pp->unitptr; + + json_ctx jctx; + const char * clsid; + + DPRINTF(2, ("gpsd_parse: time %s '%s'\n", + ulfptoa(rtime, 6), up->buffer)); + + /* See if we can ab anything potentially useful */ + if (!json_parse_record(&jctx, up->buffer)) + return; + + /* Now dispatch over the objects we know */ + clsid = json_object_lookup_string(&jctx, 0, "class"); + if (NULL == clsid) + return; + /*printf("--> class='%s'\n", clsid);*/ + if (!strcmp("VERSION", clsid)) + process_version(peer, &jctx, rtime); + else if (!strcmp("TPV", clsid)) + process_tpv(peer, &jctx, rtime); + else if (!strcmp("PPS", clsid)) + process_pps(peer, &jctx, rtime); + else + return; /* nothin we know about... */ + + /* Bail out unless we have a pulse and a time stamp */ + if (!(up->fl_pps && up->fl_tpv)) + return; + + /* Check if the pulse and he time came close enough to be + * correlated. Ignore this pair if the difference is more than a + * second. + */ + L_SUB(&up->tpv_recvt, &up->pps_recvt); + if (0 == up->tpv_recvt.l_ui) { + /*TODO: set precision based on TDOP */ + peer->flags |= FLAG_PPS; + peer->precision = PPS_PRECISION; + /*TODO: logging, fudge processing */ + refclock_process_offset(pp, up->tpv_stamp, up->pps_stamp, + pp->fudgetime1); + } + + /* mark pulse and time as consumed */ + up->fl_pps = 0; + up->fl_tpv = 0; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_stop_socket( + struct refclockproc * const pp, + struct gpsd_unit * const up) +{ + if (-1 != pp->io.fd) + io_closeclock(&pp->io); + pp->io.fd = -1; + printf("\n\n\n CLOSED!\n\n\n"); + up->tickover = TICKOVER_MAX; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_init_socket( + struct refclockproc * const pp, + struct gpsd_unit * const up) +{ + struct addrinfo * ai; + int rc; + + /* draw next address to try */ + if (NULL == up->addr) + up->addr = s_gpsd_addr; + ai = up->addr; + up->addr = ai->ai_next; + + /* try to create a matching socket */ + up->pfd.fd = socket(ai->ai_family , + ai->ai_socktype, + ai->ai_protocol); + if (-1 == up->pfd.fd) + goto no_socket; + + /* make sure the socket is non-blocking */ + rc = fcntl(up->pfd.fd, F_SETFL, O_NONBLOCK, 1); + if (-1 == rc) + goto no_socket; + + /* start a non-blocking connect */ + rc = connect(up->pfd.fd , + ai->ai_addr , + ai->ai_addrlen); + if (-1 == rc && errno != EINPROGRESS) + goto no_socket; + + return; + + no_socket: + if (-1 != up->pfd.fd) + close(up->pfd.fd); + up->pfd.fd = -1; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_test_socket( + struct refclockproc * const pp, + struct gpsd_unit * const up) +{ + /* check if the socket becomes writeable */ + if (1 != poll(&up->pfd, 1, 0)) + return; + + /* next timeout is a full one... */ + up->tickover = TICKOVER_MAX; + + /* check for socket error */ + int error = 0; + socklen_t erlen = sizeof(error); + getsockopt(up->pfd.fd, SOL_SOCKET, SO_ERROR, &error, &erlen); + if (0 != error) + goto no_socket; + + /* swap socket FDs, and make sure the clock was added */ + pp->io.fd = up->pfd.fd; + up->pfd.fd = -1; + if (0 == io_addclock(&pp->io)) + goto no_socket; + up->tickover = TICKOVER_MAX; + + return; + + no_socket: + if (-1 != up->pfd.fd) + close(up->pfd.fd); + up->pfd.fd = -1; +} + +/* ===================================================================== + * helper stuff + */ +static BOOL +convert_time( + l_fp * fp , + const char * gps_time) +{ + char *ep, *sp; + struct tm gd; + double frac; + + /* Use 'strptime' to take the brunt of the work, then use 'strtoul' + * to read in any fractional part. + */ + ep = strptime(gps_time, "%Y-%m-%dT%H:%M:%S", &gd); + if (*ep == '.') { + errno = 0; + frac = strtoul((sp = ep + 1), &ep, 10); + if (errno && ep != sp) + return FALSE; + for (/*NOP*/; sp != ep; ++sp) + frac *= 0.1; + } else { + frac = 0.0; + } + if (ep[0] != 'Z' || ep[1] != '\0') + return FALSE; + + /* now convert the whole thing into a 'l_fp' */ + /*TODO: refactor the calendar stuff into 'ntp_calendar.c' */ + M_DTOLFP(frac, fp->l_ui, fp->l_uf); + fp->l_ui += (ntpcal_tm_to_rd(&gd) - DAY_NTP_STARTS) * SECSPERDAY; + fp->l_ui += ntpcal_tm_to_daysec(&gd); + + return TRUE; +} + +/* + * ------------------------------------------------------------------- + * Save the last timecode string, making sure it's properly truncated + * if necessary and NUL terminated in any case. + */ +static void +save_ltc( + struct refclockproc * const pp, + const char * const tc) +{ + size_t len; + + len = (tc) ? strlen(tc) : 0; + if (len >= sizeof(pp->a_lastcode)) + len = sizeof(pp->a_lastcode) - 1; + pp->lencode = (u_short)len; + memcpy(pp->a_lastcode, tc, len); + pp->a_lastcode[len] = '\0'; +} + + +/* ===================================================================== + * JSON parsing stuff + */ + +static tok_ref +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 + break; + if (tid > ctx->ntok) + tid = ctx->ntok; + return tid; +} + +/* ------------------------------------------------------------------ */ + +static int +json_object_lookup( + const json_ctx * ctx, + tok_ref tid, + const char * key) +{ + int len; + + if (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)) + return tid + 1; + tid = json_token_skip(ctx, tid + 1); + } + return INVALID_TOKEN; +} + +/* ------------------------------------------------------------------ */ + +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; + return NULL; +} + +/* ------------------------------------------------------------------ */ + +#ifdef HAVE_LONG_LONG +static long long +json_object_lookup_int( + const json_ctx * ctx, + tok_ref tid, + const char * key) +{ + long long 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 = strtoll(ctx->buf + ctx->tok[val_ref].start, &ep, 10); + if (*ep) + goto cvt_error; + return ret; + + cvt_error: + errno = EINVAL; + return 0; +} +#else +#endif + +/* ------------------------------------------------------------------ */ + +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; + return 0.0; +} + +/* ------------------------------------------------------------------ */ + +static BOOL +json_parse_record( + json_ctx * ctx, + char * buf) +{ + jsmn_parser jsm; + int idx, rc; + + jsmn_init(&jsm); + rc = jsmn_parse(&jsm, buf, ctx->tok, JSMN_MAXTOK); + ctx->buf = buf; + ctx->ntok = jsm.toknext; + + /* Make all tokens NUL terminated by overwriting the + * terminator symbol + */ + for (idx = 0; idx < jsm.toknext; ++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; +} + +#else +NONEMPTY_TRANSLATION_UNIT +#endif /* REFCLOCK && CLOCK_GPSDJSON */