]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
added the GPSD-JSON client driver
authorJuergen Perlinger <perlinger@ntp.org>
Sat, 15 Feb 2014 01:56:41 +0000 (02:56 +0100)
committerJuergen Perlinger <perlinger@ntp.org>
Sat, 15 Feb 2014 01:56:41 +0000 (02:56 +0100)
bk: 52fec959Me2Y-etymV07kDl5peagkA

12 files changed:
configure.ac
include/ntp.h
libjsmn/LICENSE [new file with mode: 0644]
libjsmn/Makefile [new file with mode: 0644]
libjsmn/README.md [new file with mode: 0644]
libjsmn/jsmn.c [new file with mode: 0644]
libjsmn/jsmn.h [new file with mode: 0644]
libjsmn/jsmn_test.c [new file with mode: 0644]
libntp/clocktypes.c
ntpd/Makefile.am
ntpd/refclock_conf.c
ntpd/refclock_gpsdjson.c [new file with mode: 0644]

index 8b1735b50ade0310ae82a8ac676cd54558bc2775..c4ae9b7bddc4967f009d3b1eaf47f281d6e21fc5 100644 (file)
@@ -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],
index 95b2542aba1110eae003394a20ebd3f361b6802f..d485de03b1f81eab4c910c17f57a34b5aff6b15f 100644 (file)
@@ -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 (file)
index 0000000..c84fb2e
--- /dev/null
@@ -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 (file)
index 0000000..ac947a3
--- /dev/null
@@ -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 (file)
index 0000000..abccffa
--- /dev/null
@@ -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:
+
+* <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
diff --git a/libjsmn/jsmn.c b/libjsmn/jsmn.c
new file mode 100644 (file)
index 0000000..4f70adb
--- /dev/null
@@ -0,0 +1,268 @@
+#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;
+}
+
diff --git a/libjsmn/jsmn.h b/libjsmn/jsmn.h
new file mode 100644 (file)
index 0000000..03b2c1a
--- /dev/null
@@ -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 (file)
index 0000000..fe5f000
--- /dev/null
@@ -0,0 +1,409 @@
+#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;
+}
+
index e0b71e0a8717173818fc18a614438921b64b8a31..de7f6b4f3854a3d702ca277c539746934d62e961 100644 (file)
@@ -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,                   "", "" }
 };
 
index 3cbf3769e151973078e53e8038852e2e89a43b5b..a7af593df84f17f903ba392bed3447f3dbc7f6c0 100644 (file)
@@ -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      \
index d56bf0a010eeed2c7d735c50046c6b3a64499044..803a6b0f5f31686ff2be9d148768af88b2f28510 100644 (file)
@@ -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 (file)
index 0000000..1432b79
--- /dev/null
@@ -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 <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 */