diff options
Diffstat (limited to 'ntpd/refclock_gpsdjson.c')
-rw-r--r-- | ntpd/refclock_gpsdjson.c | 2210 |
1 files changed, 2210 insertions, 0 deletions
diff --git a/ntpd/refclock_gpsdjson.c b/ntpd/refclock_gpsdjson.c new file mode 100644 index 0000000000000..c2bf09a805e8d --- /dev/null +++ b/ntpd/refclock_gpsdjson.c @@ -0,0 +1,2210 @@ +/* + * refclock_gpsdjson.c - clock driver as GPSD JSON client + * Juergen Perlinger (perlinger@ntp.org) + * Feb 11, 2014 for the NTP project. + * The contents of 'html/copyright.html' apply. + * + * Heavily inspired by refclock_nmea.c + * + * Special thanks to Gary Miller and Hal Murray for their comments and + * ideas. + * + * Note: This will currently NOT work with Windows due to some + * limitations: + * + * - There is no GPSD for Windows. (There is an unofficial port to + * cygwin, but Windows is not officially supported.) + * + * - To work properly, this driver needs PPS and TPV/TOFF sentences + * from GPSD. I don't see how the cygwin port should deal with the + * PPS signal. + * + * - The device name matching must be done in a different way for + * Windows. (Can be done with COMxx matching, as done for NMEA.) + * + * Apart from those minor hickups, once GPSD has been fully ported to + * Windows, there's no reason why this should not work there ;-) If this + * is ever to happen at all is a different question. + * + * --------------------------------------------------------------------- + * + * This driver works slightly different from most others, as the PPS + * information (if available) is also coming from GPSD via the data + * connection. This makes using both the PPS data and the serial data + * easier, but OTOH it's not possible to use the ATOM driver to feed a + * raw PPS stream to the core of NTPD. + * + * To go around this, the driver can use a secondary clock unit + * (units>=128) that operate in tandem with the primary clock unit + * (unit%128). The primary clock unit does all the IO stuff and data + * decoding; if a a secondary unit is attached to a primary unit, this + * secondary unit is feed with the PPS samples only and can act as a PPS + * source to the clock selection. + * + * The drawback is that the primary unit must be present for the + * secondary unit to work. + * + * This design is a compromise to reduce the IO load for both NTPD and + * GPSD; it also ensures that data is transmitted and evaluated only + * once on the side of NTPD. + * + * --------------------------------------------------------------------- + * + * trouble shooting hints: + * + * Enable and check the clock stats. Check if there are bad replies; + * there should be none. If there are actually bad replies, then the + * driver cannot parse all JSON records from GPSD, and some record + * types are vital for the operation of the driver. This indicates a + * problem on the protocol level. + * + * When started on the command line with a debug level >= 2, the + * driver dumps the raw received data and the parser input to + * stdout. Since the debug level is global, NTPD starts to create a + * *lot* of output. It makes sense to pipe it through '(f)grep + * GPSD_JSON' before writing the result to disk. + * + * A bit less intrusive is using netcat or telnet to connect to GPSD + * and snoop what NTPD would get. If you try this, you have to send a + * WATCH command to GPSD: + * + * ?WATCH={"device":"/dev/gps0","enable":true,"json":true,"pps":true};<CRLF> + * + * should show you what GPSD has to say to NTPD. Replace "/dev/gps0" + * with the device link used by GPSD, if necessary. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "ntp_types.h" + +#if defined(REFCLOCK) && defined(CLOCK_GPSDJSON) && !defined(SYS_WINNT) + +/* ===================================================================== + * Get the little JSMN library directly into our guts. Use the 'parent + * link' feature for maximum speed. + */ +#define JSMN_PARENT_LINKS +#include "../libjsmn/jsmn.c" + +/* ===================================================================== + * JSON parsing stuff + */ + +#define JSMN_MAXTOK 350 +#define INVALID_TOKEN (-1) + +typedef struct json_ctx { + char * buf; + int ntok; + jsmntok_t tok[JSMN_MAXTOK]; +} json_ctx; + +typedef int tok_ref; + +/* Not all targets have 'long long', and not all of them have 'strtoll'. + * Sigh. We roll our own integer number parser. + */ +#ifdef HAVE_LONG_LONG +typedef signed long long int json_int; +typedef unsigned long long int json_uint; +#define JSON_INT_MAX LLONG_MAX +#define JSON_INT_MIN LLONG_MIN +#else +typedef signed long int json_int; +typedef unsigned long int json_uint; +#define JSON_INT_MAX LONG_MAX +#define JSON_INT_MIN LONG_MIN +#endif + +/* ===================================================================== + * header stuff we need + */ + +#include <netdb.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <ctype.h> +#include <math.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/tcp.h> + +#if defined(HAVE_SYS_POLL_H) +# include <sys/poll.h> +#elif defined(HAVE_SYS_SELECT_H) +# include <sys/select.h> +#else +# error need poll() or select() +#endif + +#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" + +/* get operation modes from mode word. + + * + SERIAL (default) evaluates only serial time information ('STI') as + * provided by TPV and TOFF records. TPV evaluation suffers from a + * bigger jitter than TOFF, sine it does not contain the receive time + * from GPSD and therefore the receive time of NTPD must be + * substituted for it. The network latency makes this a second rate + * guess. + * + * If TOFF records are detected in the data stream, the timing + * information is gleaned from this record -- it contains the local + * receive time stamp from GPSD and therefore eliminates the + * transmission latency between GPSD and NTPD. The timing information + * from TPV is ignored once a TOFF is detected or expected. + * + * TPV is still used to check the fix status, so the driver can stop + * feeding samples when GPSD says that the time information is + * effectively unreliable. + * + * + STRICT means only feed clock samples when a valid STI/PPS pair is + * available. Combines the reference time from STI with the pulse time + * from PPS. Masks the serial data jitter as long PPS is available, + * but can rapidly deteriorate once PPS drops out. + * + * + AUTO tries to use STI/PPS pairs if available for some time, and if + * this fails for too long switches back to STI only until the PPS + * signal becomes available again. See the HTML docs for this driver + * about the gotchas and why this is not the default. + */ +#define MODE_OP_MASK 0x03 +#define MODE_OP_STI 0 +#define MODE_OP_STRICT 1 +#define MODE_OP_AUTO 2 +#define MODE_OP_MAXVAL 2 +#define MODE_OP_MODE(x) ((x) & MODE_OP_MASK) + +#define PRECISION (-9) /* precision assumed (about 2 ms) */ +#define PPS_PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPSD" /* reference id */ +#define DESCRIPTION "GPSD JSON client clock" /* who we are */ + +#define MAX_PDU_LEN 1600 +#define TICKOVER_LOW 10 +#define TICKOVER_HIGH 120 +#define LOGTHROTTLE 3600 + +/* Primary channel PPS avilability dance: + * Every good PPS sample gets us a credit of PPS_INCCOUNT points, every + * bad/missing PPS sample costs us a debit of PPS_DECCOUNT points. When + * the account reaches the upper limit we change to a mode where only + * PPS-augmented samples are fed to the core; when the account drops to + * zero we switch to a mode where TPV-only timestamps are fed to the + * core. + * This reduces the chance of rapid alternation between raw and + * PPS-augmented time stamps. + */ +#define PPS_MAXCOUNT 60 /* upper limit of account */ +#define PPS_INCCOUNT 3 /* credit for good samples */ +#define PPS_DECCOUNT 1 /* debit for bad samples */ + +/* The secondary (PPS) channel uses a different strategy to avoid old + * PPS samples in the median filter. + */ +#define PPS2_MAXCOUNT 10 + +#ifndef BOOL +# define BOOL int +#endif +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +#define PROTO_VERSION(hi,lo) \ + ((((uint32_t)(hi) << 16) & 0xFFFF0000u) | \ + ((uint32_t)(lo) & 0x0FFFFu)) + +/* some local typedefs: The NTPD formatting style cries for short type + * names, and we provide them locally. Note:the suffix '_t' is reserved + * for the standard; I use a capital T instead. + */ +typedef struct peer peerT; +typedef struct refclockproc clockprocT; +typedef struct addrinfo addrinfoT; + +/* ===================================================================== + * 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, peerT *); +static void gpsd_shutdown (int, peerT *); +static void gpsd_receive (struct recvbuf *); +static void gpsd_poll (int, peerT *); +static void gpsd_control (int, const struct refclockstat *, + struct refclockstat *, peerT *); +static void gpsd_timer (int, peerT *); + +static int myasprintf(char**, char const*, ...) NTP_PRINTF(2, 3); + +static void enter_opmode(peerT *peer, int mode); +static void leave_opmode(peerT *peer, int mode); + +struct refclock refclock_gpsdjson = { + gpsd_start, /* start up driver */ + gpsd_shutdown, /* shut down driver */ + 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; +typedef struct gpsd_unit gpsd_unitT; + +struct gpsd_unit { + /* links for sharing between master/slave units */ + gpsd_unitT *next_unit; + size_t refcount; + + /* data for the secondary PPS channel */ + peerT *pps_peer; + + /* unit and operation modes */ + int unit; + int mode; + char *logname; /* cached name for log/print */ + char * device; /* device name of unit */ + + /* current line protocol version */ + uint32_t proto_version; + + /* PPS time stamps primary + secondary channel */ + l_fp pps_local; /* when we received the PPS message */ + l_fp pps_stamp; /* related reference time */ + l_fp pps_recvt; /* when GPSD detected the pulse */ + l_fp pps_stamp2;/* related reference time (secondary) */ + l_fp pps_recvt2;/* when GPSD detected the pulse (secondary)*/ + int ppscount; /* PPS counter (primary unit) */ + int ppscount2; /* PPS counter (secondary unit) */ + + /* TPV or TOFF serial time information */ + l_fp sti_local; /* when we received the TPV/TOFF message */ + l_fp sti_stamp; /* effective GPS time stamp */ + l_fp sti_recvt; /* when GPSD got the fix */ + + /* precision estimates */ + int16_t sti_prec; /* serial precision based on EPT */ + int16_t pps_prec; /* PPS precision from GPSD or above */ + + /* fudge values for correction, mirrored as 'l_fp' */ + l_fp pps_fudge; /* PPS fudge primary channel */ + l_fp pps_fudge2; /* PPS fudge secondary channel */ + l_fp sti_fudge; /* TPV/TOFF serial data fudge */ + + /* Flags to indicate available data */ + int fl_nosync: 1; /* GPSD signals bad quality */ + int fl_sti : 1; /* valid TPV/TOFF seen (have time) */ + int fl_pps : 1; /* valid pulse seen */ + int fl_pps2 : 1; /* valid pulse seen for PPS channel */ + int fl_rawsti: 1; /* permit raw TPV/TOFF time stamps */ + int fl_vers : 1; /* have protocol version */ + int fl_watch : 1; /* watch reply seen */ + /* protocol flags */ + int pf_nsec : 1; /* have nanosec PPS info */ + int pf_toff : 1; /* have TOFF record for timing */ + + /* admin stuff for sockets and device selection */ + int fdt; /* current connecting socket */ + addrinfoT * addr; /* next address to try */ + u_int tickover; /* timeout countdown */ + u_int tickpres; /* timeout preset */ + + /* tallies for the various events */ + u_int tc_recv; /* received known records */ + u_int tc_breply; /* bad replies / parsing errors */ + u_int tc_nosync; /* TPV / sample cycles w/o fix */ + u_int tc_sti_recv;/* received serial time info records */ + u_int tc_sti_used;/* used --^-- */ + u_int tc_pps_recv;/* received PPS timing info records */ + u_int tc_pps_used;/* used --^-- */ + + /* log bloat throttle */ + u_int logthrottle;/* seconds to next log slot */ + + /* The parse context for the current record */ + json_ctx json_parse; + + /* record assemby buffer and saved length */ + int buflen; + char buffer[MAX_PDU_LEN]; +}; + +/* ===================================================================== + * static local helpers forward decls + */ +static void gpsd_init_socket(peerT * const peer); +static void gpsd_test_socket(peerT * const peer); +static void gpsd_stop_socket(peerT * const peer); + +static void gpsd_parse(peerT * const peer, + const l_fp * const rtime); +static BOOL convert_ascii_time(l_fp * fp, const char * gps_time); +static void save_ltc(clockprocT * const pp, const char * const tc); +static int syslogok(clockprocT * const pp, gpsd_unitT * const up); +static void log_data(peerT *peer, const char *what, + const char *buf, size_t len); +static int16_t clamped_precision(int rawprec); + +/* ===================================================================== + * local / static stuff + */ + +/* 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. We have an old a newer version that request PPS (and TOFF) + * transmission. + * Note: These are actually format strings! + */ +static const char * const s_req_watch[2] = { + "?WATCH={\"device\":\"%s\",\"enable\":true,\"json\":true};\r\n", + "?WATCH={\"device\":\"%s\",\"enable\":true,\"json\":true,\"pps\":true};\r\n" +}; + +static const char * const s_req_version = + "?VERSION;\r\n"; + +/* We keep a static list of network addresses for 'localhost:gpsd' or a + * fallback alias of it, and we try to connect to them in round-robin + * fashion. The service lookup is done during the driver init + * function to minmise the impact of 'getaddrinfo()'. + * + * Alas, the init function is called even if there are no clocks + * configured for this driver. So it makes sense to defer the logging of + * any errors or other notifications until the first clock unit is + * started -- otherwise there might be syslog entries from a driver that + * is not used at all. + */ +static addrinfoT *s_gpsd_addr; +static gpsd_unitT *s_clock_units; + +/* list of service/socket names we want to resolve against */ +static const char * const s_svctab[][2] = { + { "localhost", "gpsd" }, + { "localhost", "2947" }, + { "127.0.0.1", "2947" }, + { NULL, NULL } +}; + +/* list of address resolution errors and index of service entry that + * finally worked. + */ +static int s_svcerr[sizeof(s_svctab)/sizeof(s_svctab[0])]; +static int s_svcidx; + +/* ===================================================================== + * log throttling + */ +static int/*BOOL*/ +syslogok( + clockprocT * const pp, + gpsd_unitT * const up) +{ + int res = (0 != (pp->sloppyclockflag & CLK_FLAG3)) + || (0 == up->logthrottle ) + || (LOGTHROTTLE == up->logthrottle ); + if (res) + up->logthrottle = LOGTHROTTLE; + return res; +} + +/* ===================================================================== + * the clock functions + */ + +/* --------------------------------------------------------------------- + * Init: This currently just gets the socket address for the GPS daemon + */ +static void +gpsd_init(void) +{ + addrinfoT hints; + int rc, idx; + + memset(s_svcerr, 0, sizeof(s_svcerr)); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + + for (idx = 0; s_svctab[idx][0] && !s_gpsd_addr; idx++) { + rc = getaddrinfo(s_svctab[idx][0], s_svctab[idx][1], + &hints, &s_gpsd_addr); + s_svcerr[idx] = rc; + if (0 == rc) + break; + s_gpsd_addr = NULL; + } + s_svcidx = idx; +} + +/* --------------------------------------------------------------------- + * Init Check: flush pending log messages and check if we can proceed + */ +static int/*BOOL*/ +gpsd_init_check(void) +{ + int idx; + + /* Check if there is something to log */ + if (s_svcidx == 0) + return (s_gpsd_addr != NULL); + + /* spool out the resolver errors */ + for (idx = 0; idx < s_svcidx; ++idx) { + msyslog(LOG_WARNING, + "GPSD_JSON: failed to resolve '%s:%s', rc=%d (%s)", + s_svctab[idx][0], s_svctab[idx][1], + s_svcerr[idx], gai_strerror(s_svcerr[idx])); + } + + /* check if it was fatal, or if we can proceed */ + if (s_gpsd_addr == NULL) + msyslog(LOG_ERR, "%s", + "GPSD_JSON: failed to get socket address, giving up."); + else if (idx != 0) + msyslog(LOG_WARNING, + "GPSD_JSON: using '%s:%s' instead of '%s:%s'", + s_svctab[idx][0], s_svctab[idx][1], + s_svctab[0][0], s_svctab[0][1]); + + /* make sure this gets logged only once and tell if we can + * proceed or not + */ + s_svcidx = 0; + return (s_gpsd_addr != NULL); +} + +/* --------------------------------------------------------------------- + * Start: allocate a unit pointer and set up the runtime data + */ +static int +gpsd_start( + int unit, + peerT * peer) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * up; + gpsd_unitT ** uscan = &s_clock_units; + + struct stat sb; + + /* check if we can proceed at all or if init failed */ + if ( ! gpsd_init_check()) + return FALSE; + + /* search for matching unit */ + while ((up = *uscan) != NULL && up->unit != (unit & 0x7F)) + uscan = &up->next_unit; + if (up == NULL) { + /* alloc unit, add to list and increment use count ASAP. */ + up = emalloc_zero(sizeof(*up)); + *uscan = up; + ++up->refcount; + + /* initialize the unit structure */ + up->logname = estrdup(refnumtoa(&peer->srcadr)); + up->unit = unit & 0x7F; + up->fdt = -1; + up->addr = s_gpsd_addr; + up->tickpres = TICKOVER_LOW; + + /* Create the device name and check for a Character + * Device. It's assumed that GPSD was started with the + * same link, so the names match. (If this is not + * practicable, we will have to read the symlink, if + * any, so we can get the true device file.) + */ + if (-1 == myasprintf(&up->device, "%s%u", + s_dev_stem, up->unit)) { + msyslog(LOG_ERR, "%s: clock device name too long", + up->logname); + goto dev_fail; + } + if (-1 == stat(up->device, &sb) || !S_ISCHR(sb.st_mode)) { + msyslog(LOG_ERR, "%s: '%s' is not a character device", + up->logname, up->device); + goto dev_fail; + } + } else { + /* All set up, just increment use count. */ + ++up->refcount; + } + + /* setup refclock processing */ + 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'; + pp->lencode = 0; + pp->clockdesc = DESCRIPTION; + memcpy(&pp->refid, REFID, 4); + + /* Initialize miscellaneous variables */ + if (unit >= 128) + peer->precision = PPS_PRECISION; + else + peer->precision = PRECISION; + + /* If the daemon name lookup failed, just give up now. */ + if (NULL == up->addr) { + msyslog(LOG_ERR, "%s: no GPSD socket address, giving up", + up->logname); + goto dev_fail; + } + + LOGIF(CLOCKINFO, + (LOG_NOTICE, "%s: startup, device is '%s'", + refnumtoa(&peer->srcadr), up->device)); + up->mode = MODE_OP_MODE(peer->ttl); + if (up->mode > MODE_OP_MAXVAL) + up->mode = 0; + if (unit >= 128) + up->pps_peer = peer; + else + enter_opmode(peer, up->mode); + return TRUE; + +dev_fail: + /* On failure, remove all UNIT ressources and declare defeat. */ + + INSIST (up); + if (!--up->refcount) { + *uscan = up->next_unit; + free(up->device); + free(up); + } + + pp->unitptr = (caddr_t)NULL; + return FALSE; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_shutdown( + int unit, + peerT * peer) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + gpsd_unitT ** uscan = &s_clock_units; + + UNUSED_ARG(unit); + + /* The unit pointer might have been removed already. */ + if (up == NULL) + return; + + /* now check if we must close IO resources */ + if (peer != up->pps_peer) { + if (-1 != pp->io.fd) { + DPRINTF(1, ("%s: closing clock, fd=%d\n", + up->logname, pp->io.fd)); + io_closeclock(&pp->io); + pp->io.fd = -1; + } + if (up->fdt != -1) + close(up->fdt); + } + /* decrement use count and eventually remove this unit. */ + if (!--up->refcount) { + /* unlink this unit */ + while (*uscan != NULL) + if (*uscan == up) + *uscan = up->next_unit; + else + uscan = &(*uscan)->next_unit; + free(up->logname); + free(up->device); + free(up); + } + pp->unitptr = (caddr_t)NULL; + LOGIF(CLOCKINFO, + (LOG_NOTICE, "%s: shutdown", refnumtoa(&peer->srcadr))); +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_receive( + struct recvbuf * rbufp) +{ + /* declare & init control structure ptrs */ + peerT * const peer = rbufp->recv_peer; + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + const char *psrc, *esrc; + char *pdst, *edst, ch; + + /* log the data stream, if this is enabled */ + log_data(peer, "recv", (const char*)rbufp->recv_buffer, + (size_t)rbufp->recv_length); + + + /* Since we're getting a raw stream data, we must assemble lines + * in our receive buffer. We can't use neither 'refclock_gtraw' + * not 'refclock_gtlin' here... We process chars until we reach + * 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 */ + up->buflen = pdst - up->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_LOW; +} + +/* ------------------------------------------------------------------ */ + +static void +poll_primary( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + if (pp->coderecv != pp->codeproc) { + /* all is well */ + pp->lastref = pp->lastrec; + refclock_report(peer, CEVNT_NOMINAL); + refclock_receive(peer); + } else { + /* Not working properly, admit to it. If we have no + * connection to GPSD, declare the clock as faulty. If + * there were bad replies, this is handled as the major + * cause, and everything else is just a timeout. + */ + peer->precision = PRECISION; + if (-1 == pp->io.fd) + refclock_report(peer, CEVNT_FAULT); + else if (0 != up->tc_breply) + refclock_report(peer, CEVNT_BADREPLY); + else + refclock_report(peer, CEVNT_TIMEOUT); + } + + if (pp->sloppyclockflag & CLK_FLAG4) + mprintf_clock_stats( + &peer->srcadr,"%u %u %u %u %u %u %u", + up->tc_recv, + up->tc_breply, up->tc_nosync, + up->tc_sti_recv, up->tc_sti_used, + up->tc_pps_recv, up->tc_pps_used); + + /* clear tallies for next round */ + up->tc_breply = 0; + up->tc_recv = 0; + up->tc_nosync = 0; + up->tc_sti_recv = 0; + up->tc_sti_used = 0; + up->tc_pps_recv = 0; + up->tc_pps_used = 0; +} + +static void +poll_secondary( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + if (pp->coderecv != pp->codeproc) { + /* all is well */ + pp->lastref = pp->lastrec; + refclock_report(peer, CEVNT_NOMINAL); + refclock_receive(peer); + } else { + peer->precision = PPS_PRECISION; + peer->flags &= ~FLAG_PPS; + refclock_report(peer, CEVNT_TIMEOUT); + } +} + +static void +gpsd_poll( + int unit, + peerT * peer) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + ++pp->polls; + if (peer == up->pps_peer) + poll_secondary(peer, pp, up); + else + poll_primary(peer, pp, up); +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_control( + int unit, + const struct refclockstat * in_st, + struct refclockstat * out_st, + peerT * peer ) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + if (peer == up->pps_peer) { + DTOLFP(pp->fudgetime1, &up->pps_fudge2); + if ( ! (pp->sloppyclockflag & CLK_FLAG1)) + peer->flags &= ~FLAG_PPS; + } else { + /* save preprocessed fudge times */ + DTOLFP(pp->fudgetime1, &up->pps_fudge); + DTOLFP(pp->fudgetime2, &up->sti_fudge); + + if (MODE_OP_MODE(up->mode ^ peer->ttl)) { + leave_opmode(peer, up->mode); + up->mode = MODE_OP_MODE(peer->ttl); + enter_opmode(peer, up->mode); + } + } + } + +/* ------------------------------------------------------------------ */ + +static void +timer_primary( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + int rc; + + /* This is used for timeout handling. Nothing that needs + * sub-second precison happens here, so receive/connect/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->logthrottle) + --up->logthrottle; + if (up->tickover) + --up->tickover; + switch (up->tickover) { + case 4: + /* If we are connected to GPSD, try to get a live signal + * by querying the version. Otherwise just check the + * socket to become ready. + */ + if (-1 != pp->io.fd) { + size_t rlen = strlen(s_req_version); + DPRINTF(2, ("%s: timer livecheck: '%s'\n", + up->logname, s_req_version)); + log_data(peer, "send", s_req_version, rlen); + rc = write(pp->io.fd, s_req_version, rlen); + (void)rc; + } else if (-1 != up->fdt) { + gpsd_test_socket(peer); + } + break; + + case 0: + if (-1 != pp->io.fd) + gpsd_stop_socket(peer); + else if (-1 != up->fdt) + gpsd_test_socket(peer); + else if (NULL != s_gpsd_addr) + gpsd_init_socket(peer); + break; + + default: + if (-1 == pp->io.fd && -1 != up->fdt) + gpsd_test_socket(peer); + } +} + +static void +timer_secondary( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + /* Reduce the count by one. Flush sample buffer and clear PPS + * flag when this happens. + */ + up->ppscount2 = max(0, (up->ppscount2 - 1)); + if (0 == up->ppscount2) { + if (pp->coderecv != pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + pp->coderecv = pp->codeproc; + } + peer->flags &= ~FLAG_PPS; + } +} + +static void +gpsd_timer( + int unit, + peerT * peer) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + if (peer == up->pps_peer) + timer_secondary(peer, pp, up); + else + timer_primary(peer, pp, up); +} + +/* ===================================================================== + * handle opmode switches + */ + +static void +enter_opmode( + peerT *peer, + int mode) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + DPRINTF(1, ("%s: enter operation mode %d\n", + up->logname, MODE_OP_MODE(mode))); + + if (MODE_OP_MODE(mode) == MODE_OP_AUTO) { + up->fl_rawsti = 0; + up->ppscount = PPS_MAXCOUNT / 2; + } + up->fl_pps = 0; + up->fl_sti = 0; +} + +/* ------------------------------------------------------------------ */ + +static void +leave_opmode( + peerT *peer, + int mode) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + DPRINTF(1, ("%s: leaving operation mode %d\n", + up->logname, MODE_OP_MODE(mode))); + + if (MODE_OP_MODE(mode) == MODE_OP_AUTO) { + up->fl_rawsti = 0; + up->ppscount = 0; + } + up->fl_pps = 0; + up->fl_sti = 0; +} + +/* ===================================================================== + * operation mode specific evaluation + */ + +static void +add_clock_sample( + peerT * const peer , + clockprocT * const pp , + l_fp stamp, + l_fp recvt) +{ + pp->lastref = stamp; + if (pp->coderecv == pp->codeproc) + refclock_report(peer, CEVNT_NOMINAL); + refclock_process_offset(pp, stamp, recvt, 0.0); +} + +/* ------------------------------------------------------------------ */ + +static void +eval_strict( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + if (up->fl_sti && up->fl_pps) { + /* use TPV reference time + PPS receive time */ + add_clock_sample(peer, pp, up->sti_stamp, up->pps_recvt); + peer->precision = up->pps_prec; + /* both packets consumed now... */ + up->fl_pps = 0; + up->fl_sti = 0; + ++up->tc_sti_used; + } +} + +/* ------------------------------------------------------------------ */ +/* PPS processing for the secondary channel. GPSD provides us with full + * timing information, so there's no danger of PLL-locking to the wrong + * second. The belts and suspenders needed for the raw ATOM clock are + * unnecessary here. + */ +static void +eval_pps_secondary( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + if (up->fl_pps2) { + /* feed data */ + add_clock_sample(peer, pp, up->pps_stamp2, up->pps_recvt2); + peer->precision = up->pps_prec; + /* PPS peer flag logic */ + up->ppscount2 = min(PPS2_MAXCOUNT, (up->ppscount2 + 2)); + if ((PPS2_MAXCOUNT == up->ppscount2) && + (pp->sloppyclockflag & CLK_FLAG1) ) + peer->flags |= FLAG_PPS; + /* mark time stamp as burned... */ + up->fl_pps2 = 0; + ++up->tc_pps_used; + } +} + +/* ------------------------------------------------------------------ */ + +static void +eval_serial( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + if (up->fl_sti) { + add_clock_sample(peer, pp, up->sti_stamp, up->sti_recvt); + peer->precision = up->sti_prec; + /* mark time stamp as burned... */ + up->fl_sti = 0; + ++up->tc_sti_used; + } +} + +/* ------------------------------------------------------------------ */ +static void +eval_auto( + peerT * const peer , + clockprocT * const pp , + gpsd_unitT * const up ) +{ + /* If there's no TPV available, stop working here... */ + if (!up->fl_sti) + return; + + /* check how to handle STI+PPS: Can PPS be used to augment STI + * (or vice versae), do we drop the sample because there is a + * temporary missing PPS signal, or do we feed on STI time + * stamps alone? + * + * Do a counter/threshold dance to decide how to proceed. + */ + if (up->fl_pps) { + up->ppscount = min(PPS_MAXCOUNT, + (up->ppscount + PPS_INCCOUNT)); + if ((PPS_MAXCOUNT == up->ppscount) && up->fl_rawsti) { + up->fl_rawsti = 0; + msyslog(LOG_INFO, + "%s: expect valid PPS from now", + up->logname); + } + } else { + up->ppscount = max(0, (up->ppscount - PPS_DECCOUNT)); + if ((0 == up->ppscount) && !up->fl_rawsti) { + up->fl_rawsti = -1; + msyslog(LOG_WARNING, + "%s: use TPV alone from now", + up->logname); + } + } + + /* now eventually feed the sample */ + if (up->fl_rawsti) + eval_serial(peer, pp, up); + else + eval_strict(peer, pp, up); +} + +/* ===================================================================== + * JSON parsing stuff + */ + +/* ------------------------------------------------------------------ */ +/* Parse a decimal integer with a possible sign. Works like 'strtoll()' + * or 'strtol()', but with a fixed base of 10 and without eating away + * leading whitespace. For the error codes, the handling of the end + * pointer and the return values see 'strtol()'. + */ +static json_int +strtojint( + const char *cp, char **ep) +{ + json_uint accu, limit_lo, limit_hi; + int flags; /* bit 0: overflow; bit 1: sign */ + const char * hold; + + /* pointer union to circumvent a tricky/sticky const issue */ + union { const char * c; char * v; } vep; + + /* store initial value of 'cp' -- see 'strtol()' */ + vep.c = cp; + + /* Eat away an optional sign and set the limits accordingly: The + * high limit is the maximum absolute value that can be returned, + * and the low limit is the biggest value that does not cause an + * overflow when multiplied with 10. Avoid negation overflows. + */ + if (*cp == '-') { + cp += 1; + flags = 2; + limit_hi = (json_uint)-(JSON_INT_MIN + 1) + 1; + } else { + cp += (*cp == '+'); + flags = 0; + limit_hi = (json_uint)JSON_INT_MAX; + } + limit_lo = limit_hi / 10; + + /* Now try to convert a sequence of digits. */ + hold = cp; + accu = 0; + while (isdigit(*(const u_char*)cp)) { + flags |= (accu > limit_lo); + accu = accu * 10 + (*(const u_char*)cp++ - '0'); + flags |= (accu > limit_hi); + } + /* Check for empty conversion (no digits seen). */ + if (hold != cp) + vep.c = cp; + else + errno = EINVAL; /* accu is still zero */ + /* Check for range overflow */ + if (flags & 1) { + errno = ERANGE; + accu = limit_hi; + } + /* If possible, store back the end-of-conversion pointer */ + if (ep) + *ep = vep.v; + /* If negative, return the negated result if the accu is not + * zero. Avoid negation overflows. + */ + if ((flags & 2) && accu) + return -(json_int)(accu - 1) - 1; + else + return (json_int)accu; +} + +/* ------------------------------------------------------------------ */ + +static tok_ref +json_token_skip( + const json_ctx * ctx, + tok_ref tid) +{ + if (tid >= 0 && tid < ctx->ntok) { + int len = ctx->tok[tid].size; + /* For arrays and objects, the size is the number of + * ITEMS in the compound. Thats the number of objects in + * the array, and the number of key/value pairs for + * objects. In theory, the key must be a string, and we + * could simply skip one token before skipping the + * value, which can be anything. We're a bit paranoid + * and lazy at the same time: We simply double the + * number of tokens to skip and fall through into the + * array processing when encountering an object. + */ + switch (ctx->tok[tid].type) { + case JSMN_OBJECT: + len *= 2; + /* FALLTHROUGH */ + case JSMN_ARRAY: + for (++tid; len; --len) + tid = json_token_skip(ctx, tid); + break; + + default: + ++tid; + break; + } + if (tid > ctx->ntok) /* Impossible? Paranoia rulez. */ + tid = ctx->ntok; + } + return tid; +} + +/* ------------------------------------------------------------------ */ + +static int +json_object_lookup( + const json_ctx * ctx , + tok_ref tid , + const char * key , + int what) +{ + int len; + + if (tid < 0 || tid >= ctx->ntok || + ctx->tok[tid].type != JSMN_OBJECT) + return INVALID_TOKEN; + + len = ctx->tok[tid].size; + for (++tid; len && tid+1 < ctx->ntok; --len) { + if (ctx->tok[tid].type != JSMN_STRING) { /* Blooper! */ + tid = json_token_skip(ctx, tid); /* skip key */ + tid = json_token_skip(ctx, tid); /* skip val */ + } else if (strcmp(key, ctx->buf + ctx->tok[tid].start)) { + tid = json_token_skip(ctx, tid+1); /* skip key+val */ + } else if (what < 0 || what == ctx->tok[tid+1].type) { + return tid + 1; + } else { + break; + } + /* if skipping ahead returned an error, bail out here. */ + if (tid < 0) + break; + } + return INVALID_TOKEN; +} + +/* ------------------------------------------------------------------ */ + +static const char* +json_object_lookup_primitive( + const json_ctx * ctx, + tok_ref tid, + const char * key) +{ + tid = json_object_lookup(ctx, tid, key, JSMN_PRIMITIVE); + if (INVALID_TOKEN != tid) + return ctx->buf + ctx->tok[tid].start; + else + return NULL; +} +/* ------------------------------------------------------------------ */ +/* look up a boolean value. This essentially returns a tribool: + * 0->false, 1->true, (-1)->error/undefined + */ +static int +json_object_lookup_bool( + const json_ctx * ctx, + tok_ref tid, + const char * key) +{ + const char *cp; + cp = json_object_lookup_primitive(ctx, tid, key); + switch ( cp ? *cp : '\0') { + case 't': return 1; + case 'f': return 0; + default : return -1; + } +} + +/* ------------------------------------------------------------------ */ + +static const char* +json_object_lookup_string( + const json_ctx * ctx, + tok_ref tid, + const char * key) +{ + tid = json_object_lookup(ctx, tid, key, JSMN_STRING); + if (INVALID_TOKEN != tid) + return ctx->buf + ctx->tok[tid].start; + return NULL; +} + +static const char* +json_object_lookup_string_default( + const json_ctx * ctx, + tok_ref tid, + const char * key, + const char * def) +{ + tid = json_object_lookup(ctx, tid, key, JSMN_STRING); + if (INVALID_TOKEN != tid) + return ctx->buf + ctx->tok[tid].start; + return def; +} + +/* ------------------------------------------------------------------ */ + +static json_int +json_object_lookup_int( + const json_ctx * ctx, + tok_ref tid, + const char * key) +{ + json_int ret; + const char * cp; + char * ep; + + cp = json_object_lookup_primitive(ctx, tid, key); + if (NULL != cp) { + ret = strtojint(cp, &ep); + if (cp != ep && '\0' == *ep) + return ret; + } else { + errno = EINVAL; + } + return 0; +} + +static json_int +json_object_lookup_int_default( + const json_ctx * ctx, + tok_ref tid, + const char * key, + json_int def) +{ + json_int ret; + const char * cp; + char * ep; + + cp = json_object_lookup_primitive(ctx, tid, key); + if (NULL != cp) { + ret = strtojint(cp, &ep); + if (cp != ep && '\0' == *ep) + return ret; + } + return def; +} + +/* ------------------------------------------------------------------ */ +#if 0 /* currently unused */ +static double +json_object_lookup_float( + const json_ctx * ctx, + tok_ref tid, + const char * key) +{ + double ret; + const char * cp; + char * ep; + + cp = json_object_lookup_primitive(ctx, tid, key); + if (NULL != cp) { + ret = strtod(cp, &ep); + if (cp != ep && '\0' == *ep) + return ret; + } else { + errno = EINVAL; + } + return 0.0; +} +#endif + +static double +json_object_lookup_float_default( + const json_ctx * ctx, + tok_ref tid, + const char * key, + double def) +{ + double ret; + const char * cp; + char * ep; + + cp = json_object_lookup_primitive(ctx, tid, key); + if (NULL != cp) { + ret = strtod(cp, &ep); + if (cp != ep && '\0' == *ep) + return ret; + } + return def; +} + +/* ------------------------------------------------------------------ */ + +static BOOL +json_parse_record( + json_ctx * ctx, + char * buf, + size_t len) +{ + jsmn_parser jsm; + int idx, rc; + + jsmn_init(&jsm); + rc = jsmn_parse(&jsm, buf, len, ctx->tok, JSMN_MAXTOK); + if (rc <= 0) + return FALSE; + ctx->buf = buf; + ctx->ntok = rc; + + if (JSMN_OBJECT != ctx->tok[0].type) + return FALSE; /* not object!?! */ + + /* Make all tokens NUL terminated by overwriting the + * terminator symbol. Makes string compares and number parsing a + * lot easier! + */ + for (idx = 0; idx < ctx->ntok; ++idx) + if (ctx->tok[idx].end > ctx->tok[idx].start) + ctx->buf[ctx->tok[idx].end] = '\0'; + return TRUE; +} + + +/* ===================================================================== + * static local helpers + */ +static BOOL +get_binary_time( + l_fp * const dest , + json_ctx * const jctx , + const char * const time_name, + const char * const frac_name, + long fscale ) +{ + BOOL retv = FALSE; + struct timespec ts; + + errno = 0; + ts.tv_sec = (time_t)json_object_lookup_int(jctx, 0, time_name); + ts.tv_nsec = (long )json_object_lookup_int(jctx, 0, frac_name); + if (0 == errno) { + ts.tv_nsec *= fscale; + *dest = tspec_stamp_to_lfp(ts); + retv = TRUE; + } + return retv; +} + +/* ------------------------------------------------------------------ */ +/* Process a WATCH record + * + * Currently this is only used to recognise that the device is present + * and that we're listed subscribers. + */ +static void +process_watch( + peerT * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + const char * path; + + path = json_object_lookup_string(jctx, 0, "device"); + if (NULL == path || strcmp(path, up->device)) + return; + + if (json_object_lookup_bool(jctx, 0, "enable") > 0 && + json_object_lookup_bool(jctx, 0, "json" ) > 0 ) + up->fl_watch = -1; + else + up->fl_watch = 0; + DPRINTF(2, ("%s: process_watch, enabled=%d\n", + up->logname, (up->fl_watch & 1))); +} + +/* ------------------------------------------------------------------ */ + +static void +process_version( + peerT * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + int len; + char * buf; + const char *revision; + const char *release; + uint16_t pvhi, pvlo; + + /* get protocol version number */ + revision = json_object_lookup_string_default( + jctx, 0, "rev", "(unknown)"); + release = json_object_lookup_string_default( + jctx, 0, "release", "(unknown)"); + errno = 0; + pvhi = (uint16_t)json_object_lookup_int(jctx, 0, "proto_major"); + pvlo = (uint16_t)json_object_lookup_int(jctx, 0, "proto_minor"); + + if (0 == errno) { + if ( ! up->fl_vers) + msyslog(LOG_INFO, + "%s: GPSD revision=%s release=%s protocol=%u.%u", + up->logname, revision, release, + pvhi, pvlo); + up->proto_version = PROTO_VERSION(pvhi, pvlo); + up->fl_vers = -1; + } else { + if (syslogok(pp, up)) + msyslog(LOG_INFO, + "%s: could not evaluate version data", + up->logname); + return; + } + /* With the 3.9 GPSD protocol, '*_musec' vanished from the PPS + * record and was replace by '*_nsec'. + */ + up->pf_nsec = -(up->proto_version >= PROTO_VERSION(3,9)); + + /* With the 3.10 protocol we can get TOFF records for better + * timing information. + */ + up->pf_toff = -(up->proto_version >= PROTO_VERSION(3,10)); + + /* request watch for our GPS device if not yet watched. + * + * The version string is also sent as a life signal, if we have + * seen useable data. So if we're already watching the device, + * skip the request. + * + * Reuse the input buffer, which is no longer needed in the + * current cycle. Also assume that we can write the watch + * request in one sweep into the socket; since we do not do + * output otherwise, this should always work. (Unless the + * TCP/IP window size gets lower than the length of the + * request. We handle that when it happens.) + */ + if (up->fl_watch) + return; + + snprintf(up->buffer, sizeof(up->buffer), + s_req_watch[up->pf_toff != 0], up->device); + buf = up->buffer; + len = strlen(buf); + log_data(peer, "send", buf, len); + if (len != write(pp->io.fd, buf, len) && (syslogok(pp, up))) { + /* Note: if the server fails to read our request, the + * resulting data timeout will take care of the + * connection! + */ + msyslog(LOG_ERR, "%s: failed to write watch request (%m)", + up->logname); + } +} + +/* ------------------------------------------------------------------ */ + +static void +process_tpv( + peerT * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + const char * gps_time; + int gps_mode; + double ept; + int xlog2; + + gps_mode = (int)json_object_lookup_int_default( + jctx, 0, "mode", 0); + + gps_time = json_object_lookup_string( + jctx, 0, "time"); + + /* accept time stamps only in 2d or 3d fix */ + if (gps_mode < 2 || NULL == gps_time) { + /* receiver has no fix; tell about and avoid stale data */ + if ( ! up->pf_toff) + ++up->tc_sti_recv; + ++up->tc_nosync; + up->fl_sti = 0; + up->fl_pps = 0; + up->fl_nosync = -1; + return; + } + up->fl_nosync = 0; + + /* convert clock and set resulting ref time, but only if the + * TOFF sentence is *not* available + */ + if ( ! up->pf_toff) { + ++up->tc_sti_recv; + /* save last time code to clock data */ + save_ltc(pp, gps_time); + /* now parse the time string */ + if (convert_ascii_time(&up->sti_stamp, gps_time)) { + DPRINTF(2, ("%s: process_tpv, stamp='%s'," + " recvt='%s' mode=%u\n", + up->logname, + gmprettydate(&up->sti_stamp), + gmprettydate(&up->sti_recvt), + gps_mode)); + + /* have to use local receive time as substitute + * for the real receive time: TPV does not tell + * us. + */ + up->sti_local = *rtime; + up->sti_recvt = *rtime; + L_SUB(&up->sti_recvt, &up->sti_fudge); + up->fl_sti = -1; + } else { + ++up->tc_breply; + up->fl_sti = 0; + } + } + + /* Set the precision from the GPSD data + * Use the ETP field for an estimation of the precision of the + * serial data. If ETP is not available, use the default serial + * data presion instead. (Note: The PPS branch has a different + * precision estimation, since it gets the proper value directly + * from GPSD!) + */ + ept = json_object_lookup_float_default(jctx, 0, "ept", 2.0e-3); + ept = frexp(fabs(ept)*0.70710678, &xlog2); /* ~ sqrt(0.5) */ + if (ept < 0.25) + xlog2 = INT_MIN; + if (ept > 2.0) + xlog2 = INT_MAX; + up->sti_prec = clamped_precision(xlog2); +} + +/* ------------------------------------------------------------------ */ + +static void +process_pps( + peerT * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + int xlog2; + + ++up->tc_pps_recv; + + /* Bail out if there's indication that time sync is bad or + * if we're explicitely requested to ignore PPS data. + */ + if (up->fl_nosync) + return; + + up->pps_local = *rtime; + /* Now grab the time values. 'clock_*' is the event time of the + * pulse measured on the local system clock; 'real_*' is the GPS + * reference time GPSD associated with the pulse. + */ + if (up->pf_nsec) { + if ( ! get_binary_time(&up->pps_recvt2, jctx, + "clock_sec", "clock_nsec", 1)) + goto fail; + if ( ! get_binary_time(&up->pps_stamp2, jctx, + "real_sec", "real_nsec", 1)) + goto fail; + } else { + if ( ! get_binary_time(&up->pps_recvt2, jctx, + "clock_sec", "clock_musec", 1000)) + goto fail; + if ( ! get_binary_time(&up->pps_stamp2, jctx, + "real_sec", "real_musec", 1000)) + goto fail; + } + + /* Try to read the precision field from the PPS record. If it's + * not there, take the precision from the serial data. + */ + xlog2 = json_object_lookup_int_default( + jctx, 0, "precision", up->sti_prec); + up->pps_prec = clamped_precision(xlog2); + + /* Get fudged receive times for primary & secondary unit */ + up->pps_recvt = up->pps_recvt2; + L_SUB(&up->pps_recvt , &up->pps_fudge ); + L_SUB(&up->pps_recvt2, &up->pps_fudge2); + pp->lastrec = up->pps_recvt; + + /* Map to nearest full second as reference time stamp for the + * primary channel. Sanity checks are done in evaluation step. + */ + up->pps_stamp = up->pps_recvt; + L_ADDUF(&up->pps_stamp, 0x80000000u); + up->pps_stamp.l_uf = 0; + + if (NULL != up->pps_peer) + save_ltc(up->pps_peer->procptr, + gmprettydate(&up->pps_stamp2)); + DPRINTF(2, ("%s: PPS record processed," + " stamp='%s', recvt='%s'\n", + up->logname, + gmprettydate(&up->pps_stamp2), + gmprettydate(&up->pps_recvt2))); + + up->fl_pps = (0 != (pp->sloppyclockflag & CLK_FLAG2)) - 1; + up->fl_pps2 = -1; + return; + + fail: + DPRINTF(1, ("%s: PPS record processing FAILED\n", + up->logname)); + ++up->tc_breply; +} + +/* ------------------------------------------------------------------ */ + +static void +process_toff( + peerT * const peer , + json_ctx * const jctx , + const l_fp * const rtime) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + ++up->tc_sti_recv; + + /* remember this! */ + up->pf_toff = -1; + + /* bail out if there's indication that time sync is bad */ + if (up->fl_nosync) + return; + + if ( ! get_binary_time(&up->sti_recvt, jctx, + "clock_sec", "clock_nsec", 1)) + goto fail; + if ( ! get_binary_time(&up->sti_stamp, jctx, + "real_sec", "real_nsec", 1)) + goto fail; + L_SUB(&up->sti_recvt, &up->sti_fudge); + up->sti_local = *rtime; + up->fl_sti = -1; + + save_ltc(pp, gmprettydate(&up->sti_stamp)); + DPRINTF(2, ("%s: TOFF record processed," + " stamp='%s', recvt='%s'\n", + up->logname, + gmprettydate(&up->sti_stamp), + gmprettydate(&up->sti_recvt))); + return; + + fail: + DPRINTF(1, ("%s: TOFF record processing FAILED\n", + up->logname)); + ++up->tc_breply; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_parse( + peerT * const peer , + const l_fp * const rtime) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + const char * clsid; + + DPRINTF(2, ("%s: gpsd_parse: time %s '%.*s'\n", + up->logname, ulfptoa(rtime, 6), + up->buflen, up->buffer)); + + /* See if we can grab anything potentially useful. JSMN does not + * need a trailing NUL, but it needs the number of bytes to + * process. */ + if (!json_parse_record(&up->json_parse, up->buffer, up->buflen)) { + ++up->tc_breply; + return; + } + + /* Now dispatch over the objects we know */ + clsid = json_object_lookup_string(&up->json_parse, 0, "class"); + if (NULL == clsid) { + ++up->tc_breply; + return; + } + + if (!strcmp("TPV", clsid)) + process_tpv(peer, &up->json_parse, rtime); + else if (!strcmp("PPS", clsid)) + process_pps(peer, &up->json_parse, rtime); + else if (!strcmp("TOFF", clsid)) + process_toff(peer, &up->json_parse, rtime); + else if (!strcmp("VERSION", clsid)) + process_version(peer, &up->json_parse, rtime); + else if (!strcmp("WATCH", clsid)) + process_watch(peer, &up->json_parse, rtime); + else + return; /* nothing we know about... */ + ++up->tc_recv; + + /* if possible, feed the PPS side channel */ + if (up->pps_peer) + eval_pps_secondary( + up->pps_peer, up->pps_peer->procptr, up); + + /* check PPS vs. STI receive times: + * If STI is before PPS, then clearly the STI is too old. If PPS + * is before STI by more than one second, then PPS is too old. + * Weed out stale time stamps & flags. + */ + if (up->fl_pps && up->fl_sti) { + l_fp diff; + diff = up->sti_local; + L_SUB(&diff, &up->pps_local); + if (diff.l_i > 0) + up->fl_pps = 0; /* pps too old */ + else if (diff.l_i < 0) + up->fl_sti = 0; /* serial data too old */ + } + + /* dispatch to the mode-dependent processing functions */ + switch (up->mode) { + default: + case MODE_OP_STI: + eval_serial(peer, pp, up); + break; + + case MODE_OP_STRICT: + eval_strict(peer, pp, up); + break; + + case MODE_OP_AUTO: + eval_auto(peer, pp, up); + break; + } +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_stop_socket( + peerT * const peer) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + if (-1 != pp->io.fd) { + if (syslogok(pp, up)) + msyslog(LOG_INFO, + "%s: closing socket to GPSD, fd=%d", + up->logname, pp->io.fd); + else + DPRINTF(1, ("%s: closing socket to GPSD, fd=%d\n", + up->logname, pp->io.fd)); + io_closeclock(&pp->io); + pp->io.fd = -1; + } + up->tickover = up->tickpres; + up->tickpres = min(up->tickpres + 5, TICKOVER_HIGH); + up->fl_vers = 0; + up->fl_sti = 0; + up->fl_pps = 0; + up->fl_watch = 0; +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_init_socket( + peerT * const peer) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + addrinfoT * ai; + int rc; + int ov; + + /* 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->fdt = socket( + ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (-1 == up->fdt) { + if (syslogok(pp, up)) + msyslog(LOG_ERR, + "%s: cannot create GPSD socket: %m", + up->logname); + goto no_socket; + } + + /* Make sure the socket is non-blocking. Connect/reconnect and + * IO happen in an event-driven environment, and synchronous + * operations wreak havoc on that. + */ + rc = fcntl(up->fdt, F_SETFL, O_NONBLOCK, 1); + if (-1 == rc) { + if (syslogok(pp, up)) + msyslog(LOG_ERR, + "%s: cannot set GPSD socket to non-blocking: %m", + up->logname); + goto no_socket; + } + /* Disable nagling. The way both GPSD and NTPD handle the + * protocol makes it record-oriented, and in most cases + * complete records (JSON serialised objects) will be sent in + * one sweep. Nagling gives not much advantage but adds another + * delay, which can worsen the situation for some packets. + */ + ov = 1; + rc = setsockopt(up->fdt, IPPROTO_TCP, TCP_NODELAY, + (char*)&ov, sizeof(ov)); + if (-1 == rc) { + if (syslogok(pp, up)) + msyslog(LOG_INFO, + "%s: cannot disable TCP nagle: %m", + up->logname); + } + + /* Start a non-blocking connect. There might be a synchronous + * connection result we have to handle. + */ + rc = connect(up->fdt, ai->ai_addr, ai->ai_addrlen); + if (-1 == rc) { + if (errno == EINPROGRESS) { + DPRINTF(1, ("%s: async connect pending, fd=%d\n", + up->logname, up->fdt)); + return; + } + + if (syslogok(pp, up)) + msyslog(LOG_ERR, + "%s: cannot connect GPSD socket: %m", + up->logname); + goto no_socket; + } + + /* We had a successful synchronous connect, so we add the + * refclock processing ASAP. We still have to wait for the + * version string and apply the watch command later on, but we + * might as well get the show on the road now. + */ + DPRINTF(1, ("%s: new socket connection, fd=%d\n", + up->logname, up->fdt)); + + pp->io.fd = up->fdt; + up->fdt = -1; + if (0 == io_addclock(&pp->io)) { + if (syslogok(pp, up)) + msyslog(LOG_ERR, + "%s: failed to register with I/O engine", + up->logname); + goto no_socket; + } + + return; + + no_socket: + if (-1 != pp->io.fd) + close(pp->io.fd); + if (-1 != up->fdt) + close(up->fdt); + pp->io.fd = -1; + up->fdt = -1; + up->tickover = up->tickpres; + up->tickpres = min(up->tickpres + 5, TICKOVER_HIGH); +} + +/* ------------------------------------------------------------------ */ + +static void +gpsd_test_socket( + peerT * const peer) +{ + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + int ec, rc; + socklen_t lc; + + /* Check if the non-blocking connect was finished by testing the + * socket for writeability. Use the 'poll()' API if available + * and 'select()' otherwise. + */ + DPRINTF(2, ("%s: check connect, fd=%d\n", + up->logname, up->fdt)); + +#if defined(HAVE_SYS_POLL_H) + { + struct pollfd pfd; + + pfd.events = POLLOUT; + pfd.fd = up->fdt; + rc = poll(&pfd, 1, 0); + if (1 != rc || !(pfd.revents & POLLOUT)) + return; + } +#elif defined(HAVE_SYS_SELECT_H) + { + struct timeval tout; + fd_set wset; + + memset(&tout, 0, sizeof(tout)); + FD_ZERO(&wset); + FD_SET(up->fdt, &wset); + rc = select(up->fdt+1, NULL, &wset, NULL, &tout); + if (0 == rc || !(FD_ISSET(up->fdt, &wset))) + return; + } +#else +# error Blooper! That should have been found earlier! +#endif + + /* next timeout is a full one... */ + up->tickover = TICKOVER_LOW; + + /* check for socket error */ + ec = 0; + lc = sizeof(ec); + rc = getsockopt(up->fdt, SOL_SOCKET, SO_ERROR, &ec, &lc); + if (-1 == rc || 0 != ec) { + const char *errtxt; + if (0 == ec) + ec = errno; + errtxt = strerror(ec); + if (syslogok(pp, up)) + msyslog(LOG_ERR, + "%s: async connect to GPSD failed," + " fd=%d, ec=%d(%s)", + up->logname, up->fdt, ec, errtxt); + else + DPRINTF(1, ("%s: async connect to GPSD failed," + " fd=%d, ec=%d(%s)\n", + up->logname, up->fdt, ec, errtxt)); + goto no_socket; + } else { + DPRINTF(1, ("%s: async connect to GPSD succeeded, fd=%d\n", + up->logname, up->fdt)); + } + + /* swap socket FDs, and make sure the clock was added */ + pp->io.fd = up->fdt; + up->fdt = -1; + if (0 == io_addclock(&pp->io)) { + if (syslogok(pp, up)) + msyslog(LOG_ERR, + "%s: failed to register with I/O engine", + up->logname); + goto no_socket; + } + return; + + no_socket: + if (-1 != up->fdt) { + DPRINTF(1, ("%s: closing socket, fd=%d\n", + up->logname, up->fdt)); + close(up->fdt); + } + up->fdt = -1; + up->tickover = up->tickpres; + up->tickpres = min(up->tickpres + 5, TICKOVER_HIGH); +} + +/* ===================================================================== + * helper stuff + */ + +/* ------------------------------------------------------------------- + * store a properly clamped precision value + */ +static int16_t +clamped_precision( + int rawprec) +{ + if (rawprec > 0) + rawprec = 0; + if (rawprec < -32) + rawprec = -32; + return (int16_t)rawprec; +} + +/* ------------------------------------------------------------------- + * Convert a GPSD timestamp (ISO8601 Format) to an l_fp + */ +static BOOL +convert_ascii_time( + l_fp * fp , + const char * gps_time) +{ + char *ep; + struct tm gd; + struct timespec ts; + uint32_t dw; + + /* Use 'strptime' to take the brunt of the work, then parse + * the fractional part manually, starting with a digit weight of + * 10^8 nanoseconds. + */ + ts.tv_nsec = 0; + ep = strptime(gps_time, "%Y-%m-%dT%H:%M:%S", &gd); + if (NULL == ep) + return FALSE; /* could not parse the mandatory stuff! */ + if (*ep == '.') { + dw = 100000000u; + while (isdigit(*(u_char*)++ep)) { + ts.tv_nsec += (*(u_char*)ep - '0') * dw; + dw /= 10u; + } + } + if (ep[0] != 'Z' || ep[1] != '\0') + return FALSE; /* trailing garbage */ + + /* Now convert the whole thing into a 'l_fp'. We do not use + * 'mkgmtime()' since its not standard and going through the + * calendar routines is not much effort, either. + */ + ts.tv_sec = (ntpcal_tm_to_rd(&gd) - DAY_NTP_STARTS) * SECSPERDAY + + ntpcal_tm_to_daysec(&gd); + *fp = tspec_intv_to_lfp(ts); + + 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( + clockprocT * 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'; +} + +/* ------------------------------------------------------------------- + * asprintf replacement... it's not available everywhere... + */ +static int +myasprintf( + char ** spp, + char const * fmt, + ... ) +{ + size_t alen, plen; + + alen = 32; + *spp = NULL; + do { + va_list va; + + alen += alen; + free(*spp); + *spp = (char*)malloc(alen); + if (NULL == *spp) + return -1; + + va_start(va, fmt); + plen = (size_t)vsnprintf(*spp, alen, fmt, va); + va_end(va); + } while (plen >= alen); + + return (int)plen; +} + +/* ------------------------------------------------------------------- + * dump a raw data buffer + */ + +static char * +add_string( + char *dp, + char *ep, + const char *sp) +{ + while (dp != ep && *sp) + *dp++ = *sp++; + return dp; +} + +static void +log_data( + peerT *peer, + const char *what, + const char *buf , + size_t len ) +{ + /* we're running single threaded with regards to the clocks. */ + static char s_lbuf[2048]; + + clockprocT * const pp = peer->procptr; + gpsd_unitT * const up = (gpsd_unitT *)pp->unitptr; + + if (debug > 1) { + const char *sptr = buf; + const char *stop = buf + len; + char *dptr = s_lbuf; + char *dtop = s_lbuf + sizeof(s_lbuf) - 1; /* for NUL */ + + while (sptr != stop && dptr != dtop) { + u_char uch = (u_char)*sptr++; + if (uch == '\\') { + dptr = add_string(dptr, dtop, "\\\\"); + } else if (isprint(uch)) { + *dptr++ = (char)uch; + } else { + char fbuf[6]; + snprintf(fbuf, sizeof(fbuf), "\\%03o", uch); + dptr = add_string(dptr, dtop, fbuf); + } + } + *dptr = '\0'; + mprintf("%s[%s]: '%s'\n", up->logname, what, s_lbuf); + } +} + +#else +NONEMPTY_TRANSLATION_UNIT +#endif /* REFCLOCK && CLOCK_GPSDJSON */ |