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],
#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
/*
--- /dev/null
+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.
+
--- /dev/null
+# 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
+
--- /dev/null
+
+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:
+
+* <code>'t', 'f'</code> - boolean
+* <code>'n'</code> - null
+* <code>'-', '0'..'9'</code> - 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
--- /dev/null
+#include <stdlib.h>
+#include <string.h>
+
+#include "jsmn.h"
+
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ 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;
+}
+
--- /dev/null
+#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_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}
+
"NEOCLK4X"},
{ REFCLK_TSYNCPCI, "Spectracom TSYNC PCI timing board (45)",
"PCI_TSYNC"},
+ { REFCLK_GPSDJSON, "GPSD JSON socket (46)",
+ "GPSD_JSON"},
{ -1, "", "" }
};
refclock_datum.c \
refclock_dumbclock.c \
refclock_fg.c \
+ refclock_gpsdjson.c \
refclock_gpsvme.c \
refclock_heath.c \
refclock_hopfser.c \
#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;
&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 *);
--- /dev/null
+/*
+ * 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 <config.h>
+#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 <stdio.h>
+//#include <ctype.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <poll.h>
+
+//#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+//#include <sys/epoll.h>
+#include <sys/stat.h>
+
+
+#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 */