summaryrefslogtreecommitdiff
path: root/ntrip.c
diff options
context:
space:
mode:
authorEric S. Raymond <esr@thyrsus.com>2006-06-07 11:40:37 +0000
committerEric S. Raymond <esr@thyrsus.com>2006-06-07 11:40:37 +0000
commit17b4d514a3811a4ccdfbf2d379547dcb943b092f (patch)
treec32f25088ebf2ccbd5126c7e19b75d13a396c5a8 /ntrip.c
parent7472a7b846320ee0697e8cc3dab40d53a84effe8 (diff)
downloadgpsd-17b4d514a3811a4ccdfbf2d379547dcb943b092f.tar.gz
splint cleanup
Diffstat (limited to 'ntrip.c')
-rw-r--r--ntrip.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/ntrip.c b/ntrip.c
new file mode 100644
index 00000000..9d89c4e8
--- /dev/null
+++ b/ntrip.c
@@ -0,0 +1,473 @@
+/* ntrip.c -- gather and dispatch DGNSS data from Ntrip broadcasters */
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "gpsd.h"
+#include "bsd-base64.h"
+
+struct ntrip_stream_t {
+ char mountpoint[101];
+ enum { fmt_rtcm2, fmt_rtcm2_0, fmt_rtcm2_1, fmt_rtcm2_2, fmt_rtcm2_3, fmt_unknown } format;
+ int carrier;
+ double latitude;
+ double longitude;
+ int nmea;
+ enum { cmp_enc_none, cmp_enc_unknown } compr_encryp;
+ enum { auth_none, auth_basic, auth_digest, auth_unknown } authentication;
+ int fee;
+ int bitrate;
+};
+
+#define NTRIP_SOURCETABLE "SOURCETABLE 200 OK\r\n"
+#define NTRIP_ENDSOURCETABLE "ENDSOURCETABLE"
+#define NTRIP_CAS "CAS;"
+#define NTRIP_NET "NET;"
+#define NTRIP_STR "STR;"
+#define NTRIP_BR "\r\n"
+#define NTRIP_QSC "\";\""
+#define NTRIP_ICY "ICY 200 OK"
+#define NTRIP_UNAUTH "401 Unauthorized"
+
+/*@ -fullinitblock @*/
+static struct ntrip_stream_t ntrip_stream = {
+ .longitude = NAN,
+ .latitude = NAN
+};
+/*@ +fullinitblock @*/
+
+/*@ -temptrans -mustfreefresh @*/
+static char *ntrip_field_iterate(char *start, /*@null@*/char *prev, const char *eol)
+{
+ char *s, *t, *u;
+
+ if (start)
+ s = start;
+ else {
+ if (!prev)
+ return NULL;
+ s = prev + strlen(prev) + 1;
+ if (s >= eol)
+ return NULL;
+ }
+
+ /* ignore any quoted ; chars as they are part of the field content */
+ t = s;
+ while ((u = strstr(t, NTRIP_QSC)))
+ t = u + strlen(NTRIP_QSC);
+
+ if ((t = strstr(t, ";")))
+ *t = '\0';
+
+ gpsd_report(5, "Next Ntrip source table field %s\n", s);
+
+ return s;
+}
+/*@ +temptrans +mustfreefresh @*/
+
+/*@ -mustfreefresh @*/
+static void ntrip_str_parse(char *str, size_t len,
+ /*@out@*/struct ntrip_stream_t *hold)
+{
+ char *s, *eol = str + len;
+
+ memset(hold, 0, sizeof(*hold));
+
+ /* <mountpoint> */
+ if ((s = ntrip_field_iterate(str, NULL, eol)))
+ strncpy(hold->mountpoint, s, sizeof(hold->mountpoint) - 1);
+ /* <identifier> */
+ s = ntrip_field_iterate(NULL, s, eol);
+ /* <format> */
+ if ((s = ntrip_field_iterate(NULL, s, eol))) {
+ if (strcasecmp("RTCM 2", s)==0)
+ hold->format = fmt_rtcm2;
+ else if (strcasecmp("RTCM 2.0", s)==0)
+ hold->format = fmt_rtcm2_0;
+ else if (strcasecmp("RTCM 2.1", s)==0)
+ hold->format = fmt_rtcm2_1;
+ else if (strcasecmp("RTCM 2.2", s)==0)
+ hold->format = fmt_rtcm2_2;
+ else if (strcasecmp("RTCM 2.3", s)==0)
+ hold->format = fmt_rtcm2_3;
+ else
+ hold->format = fmt_unknown;
+ }
+ /* <format-details> */
+ s = ntrip_field_iterate(NULL, s, eol);
+ /* <carrier> */
+ if ((s = ntrip_field_iterate(NULL, s, eol)))
+ (void)sscanf(s, "%d", &hold->carrier);
+ /* <nav-system> */
+ s = ntrip_field_iterate(NULL, s, eol);
+ /* <network> */
+ s = ntrip_field_iterate(NULL, s, eol);
+ /* <country> */
+ s = ntrip_field_iterate(NULL, s, eol);
+ /* <latitude> */
+ hold->latitude = NAN;
+ if ((s = ntrip_field_iterate(NULL, s, eol)))
+ (void)sscanf(s, "%lf", &hold->latitude);
+ /* <longitude> */
+ hold->longitude = NAN;
+ if ((s = ntrip_field_iterate(NULL, s, eol)))
+ (void)sscanf(s, "%lf", &hold->longitude);
+ /* <nmea> */
+ if ((s = ntrip_field_iterate(NULL, s, eol))) {
+ (void)sscanf(s, "%d", &hold->nmea);
+ }
+ /* <solution> */
+ s = ntrip_field_iterate(NULL, s, eol);
+ /* <generator> */
+ s = ntrip_field_iterate(NULL, s, eol);
+ /* <compr-encryp> */
+ if ((s = ntrip_field_iterate(NULL, s, eol))) {
+ if (strcasecmp("none", s)==0)
+ hold->compr_encryp = cmp_enc_none;
+ else
+ hold->compr_encryp = cmp_enc_unknown;
+ }
+ /* <authentication> */
+ if ((s = ntrip_field_iterate(NULL, s, eol))) {
+ if (strcasecmp("N", s)==0)
+ hold->authentication = auth_none;
+ else if (strcasecmp("B", s)==0)
+ hold->authentication = auth_basic;
+ else if (strcasecmp("D", s)==0)
+ hold->authentication = auth_digest;
+ else
+ hold->authentication = auth_unknown;
+ }
+ /* <fee> */
+ if ((s = ntrip_field_iterate(NULL, s, eol))) {
+ (void)sscanf(s, "%d", &hold->fee);
+ }
+ /* <bitrate> */
+ if ((s = ntrip_field_iterate(NULL, s, eol))) {
+ (void)sscanf(s, "%d", &hold->bitrate);
+ }
+ /* ...<misc> */
+ while ((s = ntrip_field_iterate(NULL, s, eol)));
+}
+/*@ +mustfreefresh @*/
+
+static int ntrip_sourcetable_parse(int fd, char *buf, ssize_t blen,
+ const char *stream,
+ struct ntrip_stream_t *keep)
+{
+ struct ntrip_stream_t hold;
+ ssize_t llen, len = 0;
+ char *line;
+ bool srctbl = false;
+ bool match = false;
+
+ for (;;) {
+ char *eol;
+ ssize_t rlen;
+
+ memset(&buf[len], 0, (size_t)(blen - len));
+
+ if ((rlen = recv(fd, &buf[len], (size_t)(blen - 1 - len), 0)) < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ if (rlen == 0)
+ continue;
+
+ line = buf;
+ rlen = len += rlen;
+
+ gpsd_report(5, "Ntrip source table buffer %s\n", buf);
+
+ if (!srctbl) {
+ /* parse SOURCETABLE */
+ if (strncmp(line,NTRIP_SOURCETABLE,strlen(NTRIP_SOURCETABLE))==0) {
+ srctbl = true;
+ llen = (ssize_t)strlen(NTRIP_SOURCETABLE);
+ line += llen;
+ len -= llen;
+ } else {
+ gpsd_report(1, "Received unexpexted Ntrip reply %s.\n", buf);
+ return -1;
+ }
+ }
+ if (!srctbl)
+ return -1;
+
+ while (len > 0) {
+ /* parse ENDSOURCETABLE */
+ if (strncmp(line, NTRIP_ENDSOURCETABLE, strlen(NTRIP_ENDSOURCETABLE))==0)
+ return match ? 0 : -1;
+
+ if (!(eol = strstr(line, NTRIP_BR)))
+ break;
+
+ gpsd_report(4, "next Ntrip source table line %s\n", line);
+
+ *eol = '\0';
+ llen = (ssize_t)(eol - line);
+
+ /* todo: parse headers */
+
+ /* parse STR */
+ if (strncmp(line, NTRIP_STR, strlen(NTRIP_STR))==0) {
+ ntrip_str_parse(line + strlen(NTRIP_STR), (size_t)(llen - strlen(NTRIP_STR)), &hold);
+ if (stream!=NULL && strcmp(stream, hold.mountpoint)==0) {
+ /* todo: support for RTCM 3.0, SBAS (WAAS, EGNOS), ... */
+ if (hold.format == fmt_unknown) {
+ gpsd_report(1,
+ "Ntrip stream %s format not supported\n",
+ line);
+ return -1;
+ }
+ /* todo: support encryption and compression algorithms */
+ if (hold.compr_encryp != cmp_enc_none) {
+ gpsd_report(1,
+ "Ntrip stream %s compression/encryption algorithm not supported\n",
+ line);
+ return -1;
+ }
+ /* todo: support digest authentication */
+ if (hold.authentication != auth_none
+ && hold.authentication != auth_basic) {
+ gpsd_report(1,
+ "Ntrip stream %s authentication method not supported\n",
+ line);
+ return -1;
+ }
+ memcpy(keep, &hold, sizeof(hold));
+ match = true;
+ }
+ /* todo: compare stream location to own location to
+ find nearest stream if user hasn't provided one */
+ }
+ /* todo: parse CAS */
+ /* else if (strncmp(line, NTRIP_CAS, strlen(NTRIP_CAS))==0); */
+
+ /* todo: parse NET */
+ /* else if (strncmp(line, NTRIP_NET, strlen(NTRIP_NET))==0); */
+
+ llen += strlen(NTRIP_BR);
+ line += llen;
+ len -= llen;
+ gpsd_report(5, "Remaining Ntrip source table buffer %d %s\n", len, line);
+ }
+ /* message too big to fit into buffer */
+ if (len == blen - 1)
+ return -1;
+
+ if (len > 0)
+ memcpy(buf, &buf[rlen-len], (size_t)len);
+ }
+
+ return (int)len;
+}
+
+static int ntrip_stream_probe(const char *caster,
+ const char *port,
+ const char *stream,
+ struct ntrip_stream_t *keep)
+{
+ int ret;
+ int dsock;
+ char buf[BUFSIZ];
+
+ if ((dsock = netlib_connectsock(caster, port, "tcp")) < 0) {
+ printf("error %d\n", dsock);
+ return -1;
+ }
+ (void)snprintf(buf, sizeof(buf),
+ "GET / HTTP/1.1\r\n"
+ "User-Agent: NTRIP gpsd/%s\r\n"
+ "Connection: close\r\n"
+ "\r\n",
+ VERSION);
+ (void)write(dsock, buf, strlen(buf));
+ ret = ntrip_sourcetable_parse(dsock, buf, (ssize_t)sizeof(buf), stream, keep);
+ (void)close(dsock);
+ return ret;
+}
+
+static int ntrip_auth_encode(const struct ntrip_stream_t *stream,
+ const char *auth,
+ /*@out@*/char buf[],
+ size_t size)
+{
+ memset(buf, 0, size);
+ if (stream->authentication == auth_none)
+ return 0;
+ else if (stream->authentication == auth_basic) {
+ char authenc[64];
+ if (!auth)
+ return -1;
+ memset(authenc, 0, sizeof(authenc));
+ if (b64_ntop((u_char *) auth, strlen(auth), authenc, sizeof(authenc) - 1) < 0)
+ return -1;
+ (void)snprintf(buf, size - 1, "Authorization: Basic %s\r\n", authenc);
+ } else {
+ /* todo: support digest authentication */
+ }
+ return 0;
+}
+
+/*@ -nullpass @*/ /* work around a splint bug */
+static int ntrip_stream_open(const char *caster,
+ const char *port,
+ const char *auth,
+ struct gps_context_t *context,
+ struct ntrip_stream_t *stream)
+{
+ char buf[BUFSIZ];
+ char authstr[128];
+ int opts;
+
+ if (ntrip_auth_encode(stream, auth, authstr, sizeof(authstr)) < 0) {
+ gpsd_report(1, "User-ID and password needed for %s:%s/%s\n",
+ caster, port, stream->mountpoint);
+ return -1;
+ }
+ if ((context->dsock = netlib_connectsock(caster, port, "tcp")) < 0)
+ return -1;
+
+ (void)snprintf(buf, sizeof(buf),
+ "GET /%s HTTP/1.1\r\n"
+ "User-Agent: NTRIP gpsd/%s\r\n"
+ "Accept: rtk/rtcm, dgps/rtcm\r\n"
+ "%s"
+ "Connection: close\r\n"
+ "\r\n",
+ stream->mountpoint, VERSION, authstr);
+ (void)write(context->dsock, buf, strlen(buf));
+
+ memset(buf, 0, sizeof(buf));
+ if (read(context->dsock, buf, sizeof(buf) - 1) < 0)
+ goto close;
+
+ /* parse 401 Unauthorized */
+ if (strstr(buf, NTRIP_UNAUTH)) {
+ gpsd_report(1, "%s not authorized for Ntrip stream %s:%s/%s\n",
+ auth, caster, port, stream->mountpoint);
+ goto close;
+ }
+ /* parse SOURCETABLE */
+ if (strstr(buf, NTRIP_SOURCETABLE)) {
+ gpsd_report(1, "Broadcaster doesn't recognize Ntrip stream %s:%s/%s\n",
+ caster, port, stream->mountpoint);
+ goto close;
+ }
+ /* parse ICY 200 OK */
+ if (!strstr(buf, NTRIP_ICY)) {
+ gpsd_report(1, "Unknown reply %s from Ntrip service %s:%s/%s\n",
+ buf, caster, port, stream->mountpoint);
+ goto close;
+ }
+ opts = fcntl(context->dsock, F_GETFL);
+
+ if (opts >= 0)
+ (void)fcntl(context->dsock, F_SETFL, opts | O_NONBLOCK);
+
+ context->dgnss_service = dgnss_ntrip;
+#ifndef S_SPLINT_S
+ context->dgnss_privdata = stream;
+#endif
+ return context->dsock;
+close:
+ (void)close(context->dsock);
+ return -1;
+}
+/*@ +nullpass @*/
+
+/*@ -branchstate @*/
+int ntrip_open(struct gps_context_t *context, char *caster)
+/* open a connection to a Ntrip broadcaster */
+{
+ char *amp, *colon, *slash;
+ char *auth = NULL;
+ char *port = NULL;
+ char *stream = NULL;
+ int ret;
+
+ /*@ -boolops @*/
+ if ((amp = strchr(caster, '@'))) {
+ if ((colon = strchr(caster, ':')) && colon < amp) {
+ auth = caster;
+ *amp = '\0';
+ caster = amp + 1;
+ } else {
+ gpsd_report(1, "can't extract user-ID and password from %s\n",
+ caster);
+ return -1;
+ }
+ }
+ /*@ +boolops @*/
+ if ((slash = strchr(caster, '/'))) {
+ *slash = '\0';
+ stream = slash + 1;
+ } else {
+ /* todo: add autoconnect like in dgpsip.c */
+ gpsd_report(1, "can't extract Ntrip stream from %s\n", caster);
+ return -1;
+ }
+ if ((colon = strchr(caster, ':'))) {
+ port = colon + 1;
+ *colon = '\0';
+ }
+ if (!port) {
+ port = "rtcm-sc104";
+ if (!getservbyname(port, "tcp"))
+ port = DEFAULT_RTCM_PORT;
+ }
+ if (ntrip_stream_probe(caster, port, stream, &ntrip_stream)) {
+ gpsd_report(1, "unable to probe for data about stream %s:%s/%s\n",
+ caster, port, stream);
+ return -1;
+ }
+ ret = ntrip_stream_open(caster, port, auth, context, &ntrip_stream);
+ if (ret >= 0)
+ gpsd_report(1,"connection to Ntrip broadcaster %s established.\n",
+ caster);
+ else
+ gpsd_report(1, "can't connect to Ntrip stream %s:%s/%s.\n",
+ caster, port, stream);
+ return ret;
+}
+/*@ +branchstate @*/
+
+void ntrip_poll(struct gps_context_t *context)
+/* poll the NTRIP server for a correction report */
+{
+ if (context->dsock > -1) {
+ context->rtcmbytes = read(context->dsock, context->rtcmbuf, sizeof(context->rtcmbuf));
+ if (context->rtcmbytes < 0 && errno != EAGAIN)
+ gpsd_report(1, "Read from rtcm source failed\n");
+ else
+ context->rtcmtime = timestamp();
+ }
+}
+
+void ntrip_report(struct gps_device_t *session)
+/* may be time to ship a usage report to the Ntrip caster */
+{
+ struct ntrip_stream_t *stream = session->context->dgnss_privdata;
+ /*
+ * 10 is an arbitrary number, the point is to have gotten several good
+ * fixes before reporting usage to our Ntrip caster.
+ */
+ if (stream!=NULL && stream->nmea!=0
+ && session->context->fixcnt > 10 && !session->context->sentdgps) {
+ session->context->sentdgps = true;
+ if (session->context->dsock > -1) {
+ char buf[BUFSIZ];
+ gpsd_position_fix_dump(session, buf, sizeof(buf));
+ (void)write(session->context->dsock, buf, strlen(buf));
+ gpsd_report(2, "=> dgps %s", buf);
+ }
+ }
+}