1 /* net_ntrip.c -- gather and dispatch DGNSS data from Ntrip broadcasters
3 * This file is Copyright (c) 2010 by the GPSD project
4 * BSD terms apply: see the file COPYING in the distribution root for details.
7 #include "gpsd_config.h"
10 #ifdef HAVE_SYS_SOCKET_H
11 #include <sys/socket.h>
14 #endif /* HAVE_SYS_SOCKET_H */
16 #endif /* S_SPLINT_S */
24 #endif /* S_SPLINT_S */
30 #include "bsd-base64.h"
50 { cmp_enc_none, cmp_enc_unknown } compr_encryp;
52 { auth_none, auth_basic, auth_digest, auth_unknown } authentication;
57 #define NTRIP_SOURCETABLE "SOURCETABLE 200 OK\r\n"
58 #define NTRIP_ENDSOURCETABLE "ENDSOURCETABLE"
59 #define NTRIP_CAS "CAS;"
60 #define NTRIP_NET "NET;"
61 #define NTRIP_STR "STR;"
62 #define NTRIP_BR "\r\n"
63 #define NTRIP_QSC "\";\""
64 #define NTRIP_ICY "ICY 200 OK"
65 #define NTRIP_UNAUTH "401 Unauthorized"
67 static struct ntrip_stream_t ntrip_stream;
69 /*@ -temptrans -mustfreefresh @*/
70 static /*@null@*/ char *ntrip_field_iterate( /*@null@ */ char *start,
71 /*@null@*/ char *prev,
81 s = prev + strlen(prev) + 1;
86 /* ignore any quoted ; chars as they are part of the field content */
88 while ((u = strstr(t, NTRIP_QSC)))
89 t = u + strlen(NTRIP_QSC);
91 if ((t = strstr(t, ";")))
94 gpsd_report(LOG_RAW, "Next Ntrip source table field %s\n", s);
99 /*@ +temptrans +mustfreefresh @*/
101 /*@ -mustfreefresh @*/
102 static void ntrip_str_parse(char *str, size_t len,
103 /*@out@*/ struct ntrip_stream_t *hold)
105 char *s, *eol = str + len;
107 memset(hold, 0, sizeof(*hold));
110 if ((s = ntrip_field_iterate(str, NULL, eol)))
111 strncpy(hold->mountpoint, s, sizeof(hold->mountpoint) - 1);
113 s = ntrip_field_iterate(NULL, s, eol);
115 if ((s = ntrip_field_iterate(NULL, s, eol))) {
116 if (strcasecmp("RTCM 2", s) == 0)
117 hold->format = fmt_rtcm2;
118 else if (strcasecmp("RTCM 2.0", s) == 0)
119 hold->format = fmt_rtcm2_0;
120 else if (strcasecmp("RTCM 2.1", s) == 0)
121 hold->format = fmt_rtcm2_1;
122 else if (strcasecmp("RTCM 2.2", s) == 0)
123 hold->format = fmt_rtcm2_2;
124 else if (strcasecmp("RTCM 2.3", s) == 0)
125 hold->format = fmt_rtcm2_3;
126 else if (strcasecmp("RTCM 3.0", s) == 0)
127 hold->format = fmt_rtcm3;
129 hold->format = fmt_unknown;
131 /* <format-details> */
132 s = ntrip_field_iterate(NULL, s, eol);
134 if ((s = ntrip_field_iterate(NULL, s, eol)))
135 (void)sscanf(s, "%d", &hold->carrier);
137 s = ntrip_field_iterate(NULL, s, eol);
139 s = ntrip_field_iterate(NULL, s, eol);
141 s = ntrip_field_iterate(NULL, s, eol);
143 hold->latitude = NAN;
144 if ((s = ntrip_field_iterate(NULL, s, eol)))
145 (void)sscanf(s, "%lf", &hold->latitude);
147 hold->longitude = NAN;
148 if ((s = ntrip_field_iterate(NULL, s, eol)))
149 (void)sscanf(s, "%lf", &hold->longitude);
151 if ((s = ntrip_field_iterate(NULL, s, eol))) {
152 (void)sscanf(s, "%d", &hold->nmea);
155 s = ntrip_field_iterate(NULL, s, eol);
157 s = ntrip_field_iterate(NULL, s, eol);
159 if ((s = ntrip_field_iterate(NULL, s, eol))) {
160 if (strcasecmp("none", s) == 0)
161 hold->compr_encryp = cmp_enc_none;
163 hold->compr_encryp = cmp_enc_unknown;
165 /* <authentication> */
166 if ((s = ntrip_field_iterate(NULL, s, eol))) {
167 if (strcasecmp("N", s) == 0)
168 hold->authentication = auth_none;
169 else if (strcasecmp("B", s) == 0)
170 hold->authentication = auth_basic;
171 else if (strcasecmp("D", s) == 0)
172 hold->authentication = auth_digest;
174 hold->authentication = auth_unknown;
177 if ((s = ntrip_field_iterate(NULL, s, eol))) {
178 (void)sscanf(s, "%d", &hold->fee);
181 if ((s = ntrip_field_iterate(NULL, s, eol))) {
182 (void)sscanf(s, "%d", &hold->bitrate);
185 while ((s = ntrip_field_iterate(NULL, s, eol)));
188 /*@ +mustfreefresh @*/
190 static int ntrip_sourcetable_parse(int fd, char *buf, ssize_t blen,
192 struct ntrip_stream_t *keep)
194 struct ntrip_stream_t hold;
195 ssize_t llen, len = 0;
204 memset(&buf[len], 0, (size_t) (blen - len));
206 if ((rlen = recv(fd, &buf[len], (size_t) (blen - 1 - len), 0)) == -1) {
217 gpsd_report(LOG_RAW, "Ntrip source table buffer %s\n", buf);
220 /* parse SOURCETABLE */
221 if (strncmp(line, NTRIP_SOURCETABLE, strlen(NTRIP_SOURCETABLE)) ==
224 llen = (ssize_t) strlen(NTRIP_SOURCETABLE);
228 gpsd_report(LOG_WARN, "Received unexpexted Ntrip reply %s.\n",
237 /* parse ENDSOURCETABLE */
239 (line, NTRIP_ENDSOURCETABLE,
240 strlen(NTRIP_ENDSOURCETABLE)) == 0)
243 if (!(eol = strstr(line, NTRIP_BR)))
246 gpsd_report(LOG_IO, "next Ntrip source table line %s\n", line);
249 llen = (ssize_t) (eol - line);
251 /* todo: parse headers */
254 if (strncmp(line, NTRIP_STR, strlen(NTRIP_STR)) == 0) {
255 ntrip_str_parse(line + strlen(NTRIP_STR),
256 (size_t) (llen - strlen(NTRIP_STR)), &hold);
257 if (stream != NULL && strcmp(stream, hold.mountpoint) == 0) {
258 /* todo: support for RTCM 3.0, SBAS (WAAS, EGNOS), ... */
259 if (hold.format == fmt_unknown) {
260 gpsd_report(LOG_ERROR,
261 "Ntrip stream %s format not supported\n",
265 /* todo: support encryption and compression algorithms */
266 if (hold.compr_encryp != cmp_enc_none) {
267 gpsd_report(LOG_ERROR,
268 "Ntrip stream %s compression/encryption algorithm not supported\n",
272 /* todo: support digest authentication */
273 if (hold.authentication != auth_none
274 && hold.authentication != auth_basic) {
275 gpsd_report(LOG_ERROR,
276 "Ntrip stream %s authentication method not supported\n",
280 memcpy(keep, &hold, sizeof(hold));
283 /* todo: compare stream location to own location to
284 * find nearest stream if user hasn't provided one */
286 /* todo: parse CAS */
287 /* else if (strncmp(line, NTRIP_CAS, strlen(NTRIP_CAS))==0); */
289 /* todo: parse NET */
290 /* else if (strncmp(line, NTRIP_NET, strlen(NTRIP_NET))==0); */
292 llen += strlen(NTRIP_BR);
296 "Remaining Ntrip source table buffer %zd %s\n", len,
299 /* message too big to fit into buffer */
304 memcpy(buf, &buf[rlen - len], (size_t) len);
308 return match ? 0 : -1;
311 static int ntrip_stream_probe(const char *caster,
313 const char *stream, struct ntrip_stream_t *keep)
319 if ((dsock = netlib_connectsock(AF_UNSPEC, caster, port, "tcp")) == -1) {
320 printf("ntrip stream connect error %d\n", dsock);
323 (void)snprintf(buf, sizeof(buf),
325 "User-Agent: NTRIP gpsd/%s\r\n"
326 "Connection: close\r\n" "\r\n", VERSION);
327 if (write(dsock, buf, strlen(buf)) != (ssize_t) strlen(buf)) {
328 printf("ntrip stream write error %d\n", dsock);
332 ntrip_sourcetable_parse(dsock, buf, (ssize_t) sizeof(buf), stream,
338 static int ntrip_auth_encode(const struct ntrip_stream_t *stream,
340 /*@out@*/ char buf[],
343 memset(buf, 0, size);
344 if (stream->authentication == auth_none)
346 else if (stream->authentication == auth_basic) {
350 memset(authenc, 0, sizeof(authenc));
352 ((unsigned char *)auth, strlen(auth), authenc,
353 sizeof(authenc) - 1) < 0)
355 (void)snprintf(buf, size - 1, "Authorization: Basic %s\r\n", authenc);
357 /* todo: support digest authentication */
363 /*@ -nullpass @*//* work around a splint bug */
364 static int ntrip_stream_open(const char *caster, const char *port,
365 /*@null@*/ const char *auth,
366 struct gps_context_t *context,
367 struct ntrip_stream_t *stream)
373 if (ntrip_auth_encode(stream, auth, authstr, sizeof(authstr)) < 0) {
374 gpsd_report(LOG_ERROR, "User-ID and password needed for %s:%s/%s\n",
375 caster, port, stream->mountpoint);
378 if ((context->dsock =
379 netlib_connectsock(AF_UNSPEC, caster, port, "tcp")) < 0)
382 (void)snprintf(buf, sizeof(buf),
383 "GET /%s HTTP/1.1\r\n"
384 "User-Agent: NTRIP gpsd/%s\r\n"
385 "Accept: rtk/rtcm, dgps/rtcm\r\n"
387 "Connection: close\r\n"
388 "\r\n", stream->mountpoint, VERSION, authstr);
389 if (write(context->dsock, buf, strlen(buf)) != (ssize_t) strlen(buf)) {
390 printf("ntrip stream write error on %d\n", context->dsock);
394 memset(buf, 0, sizeof(buf));
395 if (read(context->dsock, buf, sizeof(buf) - 1) == -1)
398 /* parse 401 Unauthorized */
399 if (strstr(buf, NTRIP_UNAUTH)) {
400 gpsd_report(LOG_ERROR,
401 "%s not authorized for Ntrip stream %s:%s/%s\n", auth,
402 caster, port, stream->mountpoint);
405 /* parse SOURCETABLE */
406 if (strstr(buf, NTRIP_SOURCETABLE)) {
407 gpsd_report(LOG_ERROR,
408 "Broadcaster doesn't recognize Ntrip stream %s:%s/%s\n",
409 caster, port, stream->mountpoint);
412 /* parse ICY 200 OK */
413 if (!strstr(buf, NTRIP_ICY)) {
414 gpsd_report(LOG_ERROR,
415 "Unknown reply %s from Ntrip service %s:%s/%s\n", buf,
416 caster, port, stream->mountpoint);
419 opts = fcntl(context->dsock, F_GETFL);
422 (void)fcntl(context->dsock, F_SETFL, opts | O_NONBLOCK);
424 context->netgnss_service = netgnss_ntrip;
426 context->netgnss_privdata = stream;
428 return context->dsock;
430 (void)close(context->dsock);
438 int ntrip_open(struct gps_context_t *context, char *caster)
439 /* open a connection to a Ntrip broadcaster */
441 char *amp, *colon, *slash;
448 if ((amp = strchr(caster, '@')) != NULL) {
449 if (((colon = strchr(caster, ':')) != NULL) && colon < amp) {
454 gpsd_report(LOG_ERROR,
455 "can't extract user-ID and password from %s\n",
461 if ((slash = strchr(caster, '/')) != NULL) {
465 /* todo: add autoconnect like in dgpsip.c */
466 gpsd_report(LOG_ERROR, "can't extract Ntrip stream from %s\n",
470 if ((colon = strchr(caster, ':')) != NULL) {
476 if (!getservbyname(port, "tcp"))
477 port = DEFAULT_RTCM_PORT;
479 if (ntrip_stream_probe(caster, port, stream, &ntrip_stream)) {
480 gpsd_report(LOG_ERROR,
481 "unable to probe for data about stream %s:%s/%s\n",
482 caster, port, stream);
485 ret = ntrip_stream_open(caster, port, auth, context, &ntrip_stream);
487 gpsd_report(LOG_PROG,
488 "connection to Ntrip broadcaster %s established.\n",
491 gpsd_report(LOG_ERROR, "can't connect to Ntrip stream %s:%s/%s.\n",
492 caster, port, stream);
498 void ntrip_report(struct gps_device_t *session)
499 /* may be time to ship a usage report to the Ntrip caster */
501 struct ntrip_stream_t *stream = session->context->netgnss_privdata;
503 * 10 is an arbitrary number, the point is to have gotten several good
504 * fixes before reporting usage to our Ntrip caster.
506 if (stream != NULL && stream->nmea != 0
507 && session->context->fixcnt > 10 && !session->context->sentdgps) {
508 session->context->sentdgps = true;
509 if (session->context->dsock > -1) {
511 gpsd_position_fix_dump(session, buf, sizeof(buf));
512 if (write(session->context->dsock, buf, strlen(buf)) ==
513 (ssize_t) strlen(buf))
514 gpsd_report(LOG_IO, "=> dgps %s\n", buf);
516 gpsd_report(LOG_IO, "ntrip report write failed\n");