net_ntrip.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. /* net_ntrip.c -- gather and dispatch DGNSS data from Ntrip broadcasters
  2. *
  3. * This file is Copyright (c) 2010-2018 by the GPSD project
  4. * SPDX-License-Identifier: BSD-2-clause
  5. */
  6. #include "gpsd_config.h" /* must be before all includes */
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <stdbool.h>
  10. #include <string.h>
  11. #include <errno.h>
  12. #include <fcntl.h>
  13. #include <math.h>
  14. #include <strings.h>
  15. #include <sys/types.h>
  16. #include <sys/stat.h>
  17. #include <netdb.h>
  18. #include <sys/socket.h>
  19. #include <unistd.h>
  20. #include "gpsd.h"
  21. #include "strfuncs.h"
  22. #define NTRIP_SOURCETABLE "SOURCETABLE 200 OK\r\n"
  23. #define NTRIP_ENDSOURCETABLE "ENDSOURCETABLE"
  24. #define NTRIP_CAS "CAS;"
  25. #define NTRIP_NET "NET;"
  26. #define NTRIP_STR "STR;"
  27. #define NTRIP_BR "\r\n"
  28. #define NTRIP_QSC "\";\""
  29. #define NTRIP_ICY "ICY 200 OK"
  30. #define NTRIP_UNAUTH "401 Unauthorized"
  31. static char *ntrip_field_iterate(char *start,
  32. char *prev,
  33. const char *eol,
  34. const struct gpsd_errout_t *errout)
  35. {
  36. char *s, *t, *u;
  37. if (start)
  38. s = start;
  39. else {
  40. if (!prev)
  41. return NULL;
  42. s = prev + strlen(prev) + 1;
  43. if (s >= eol)
  44. return NULL;
  45. }
  46. /* ignore any quoted ; chars as they are part of the field content */
  47. t = s;
  48. while ((u = strstr(t, NTRIP_QSC)))
  49. t = u + strlen(NTRIP_QSC);
  50. if ((t = strstr(t, ";")))
  51. *t = '\0';
  52. GPSD_LOG(LOG_RAW, errout, "Next Ntrip source table field %s\n", s);
  53. return s;
  54. }
  55. static void ntrip_str_parse(char *str, size_t len,
  56. struct ntrip_stream_t *hold,
  57. const struct gpsd_errout_t *errout)
  58. {
  59. char *s, *eol = str + len;
  60. memset(hold, 0, sizeof(*hold));
  61. /* <mountpoint> */
  62. if ((s = ntrip_field_iterate(str, NULL, eol, errout)))
  63. (void)strlcpy(hold->mountpoint, s, sizeof(hold->mountpoint));
  64. /* <identifier> */
  65. s = ntrip_field_iterate(NULL, s, eol, errout);
  66. /* <format> */
  67. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  68. if (strcasecmp("RTCM 2", s) == 0)
  69. hold->format = fmt_rtcm2;
  70. else if (strcasecmp("RTCM 2.0", s) == 0)
  71. hold->format = fmt_rtcm2_0;
  72. else if (strcasecmp("RTCM 2.1", s) == 0)
  73. hold->format = fmt_rtcm2_1;
  74. else if (strcasecmp("RTCM 2.2", s) == 0)
  75. hold->format = fmt_rtcm2_2;
  76. else if ((strcasecmp("RTCM2.3", s) == 0) ||
  77. (strcasecmp("RTCM 2.3", s) == 0))
  78. hold->format = fmt_rtcm2_3;
  79. /* required for the SAPOS derver in Gemany, confirmed as RTCM2.3 */
  80. else if (strcasecmp("RTCM1_", s) == 0)
  81. hold->format = fmt_rtcm2_3;
  82. else if ((strcasecmp("RTCM 3", s) == 0) ||
  83. (strcasecmp("RTCM 3.0", s) == 0))
  84. hold->format = fmt_rtcm3_0;
  85. else if ((strcasecmp("RTCM3.1", s) == 0) ||
  86. (strcasecmp("RTCM 3.1", s) == 0))
  87. hold->format = fmt_rtcm3_1;
  88. else if (strcasecmp("RTCM 3.2", s) == 0)
  89. hold->format = fmt_rtcm3_2;
  90. else if (strcasecmp("RTCM 3.3", s) == 0)
  91. hold->format = fmt_rtcm3_3;
  92. else
  93. hold->format = fmt_unknown;
  94. }
  95. /* <format-details> */
  96. s = ntrip_field_iterate(NULL, s, eol, errout);
  97. /* <carrier> */
  98. if ((s = ntrip_field_iterate(NULL, s, eol, errout)))
  99. hold->carrier = atoi(s);
  100. /* <nav-system> */
  101. s = ntrip_field_iterate(NULL, s, eol, errout);
  102. /* <network> */
  103. s = ntrip_field_iterate(NULL, s, eol, errout);
  104. /* <country> */
  105. s = ntrip_field_iterate(NULL, s, eol, errout);
  106. /* <latitude> */
  107. hold->latitude = NAN;
  108. if ((s = ntrip_field_iterate(NULL, s, eol, errout)))
  109. hold->latitude = safe_atof(s);
  110. /* <longitude> */
  111. hold->longitude = NAN;
  112. if ((s = ntrip_field_iterate(NULL, s, eol, errout)))
  113. hold->longitude = safe_atof(s);
  114. /* <nmea> */
  115. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  116. hold->nmea = atoi(s);
  117. }
  118. /* <solution> */
  119. s = ntrip_field_iterate(NULL, s, eol, errout);
  120. /* <generator> */
  121. s = ntrip_field_iterate(NULL, s, eol, errout);
  122. /* <compr-encryp> */
  123. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  124. if (strcasecmp("none", s) == 0)
  125. hold->compr_encryp = cmp_enc_none;
  126. else
  127. hold->compr_encryp = cmp_enc_unknown;
  128. }
  129. /* <authentication> */
  130. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  131. if (strcasecmp("N", s) == 0)
  132. hold->authentication = auth_none;
  133. else if (strcasecmp("B", s) == 0)
  134. hold->authentication = auth_basic;
  135. else if (strcasecmp("D", s) == 0)
  136. hold->authentication = auth_digest;
  137. else
  138. hold->authentication = auth_unknown;
  139. }
  140. /* <fee> */
  141. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  142. hold->fee = atoi(s);
  143. }
  144. /* <bitrate> */
  145. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  146. hold->bitrate = atoi(s);
  147. }
  148. /* ...<misc> */
  149. while ((s = ntrip_field_iterate(NULL, s, eol, errout)));
  150. }
  151. static int ntrip_sourcetable_parse(struct gps_device_t *device)
  152. {
  153. struct ntrip_stream_t hold;
  154. ssize_t llen, len = 0;
  155. char *line;
  156. bool sourcetable = false;
  157. bool match = false;
  158. char buf[BUFSIZ];
  159. size_t blen = sizeof(buf);
  160. int fd = device->gpsdata.gps_fd;
  161. for (;;) {
  162. char *eol;
  163. ssize_t rlen;
  164. memset(&buf[len], 0, (size_t) (blen - len));
  165. rlen = read(fd, &buf[len], (size_t) (blen - 1 - len));
  166. if (rlen == -1) {
  167. if (errno == EINTR) {
  168. continue;
  169. }
  170. if (sourcetable && !match && errno == EAGAIN) { // we have not yet found a match, but there currently is no more data
  171. return 0;
  172. }
  173. if (match) {
  174. return 1;
  175. }
  176. GPSD_LOG(LOG_ERROR, &device->context->errout,
  177. "ntrip stream read error %d on fd %d\n",
  178. errno, fd);
  179. return -1;
  180. } else if (rlen == 0) { // server closed the connection
  181. GPSD_LOG(LOG_ERROR, &device->context->errout,
  182. "ntrip stream unexpected close %d on fd %d "
  183. "during sourcetable read\n",
  184. errno, fd);
  185. return -1;
  186. }
  187. line = buf;
  188. rlen = len += rlen;
  189. GPSD_LOG(LOG_RAW, &device->context->errout,
  190. "Ntrip source table buffer %s\n", buf);
  191. sourcetable = device->ntrip.sourcetable_parse;
  192. if (!sourcetable) {
  193. /* parse SOURCETABLE */
  194. if (str_starts_with(line, NTRIP_SOURCETABLE)) {
  195. sourcetable = true;
  196. device->ntrip.sourcetable_parse = true;
  197. llen = (ssize_t) strlen(NTRIP_SOURCETABLE);
  198. line += llen;
  199. len -= llen;
  200. } else {
  201. GPSD_LOG(LOG_WARN, &device->context->errout,
  202. "Received unexpexted Ntrip reply %s.\n",
  203. buf);
  204. return -1;
  205. }
  206. }
  207. while (len > 0) {
  208. /* parse ENDSOURCETABLE */
  209. if (str_starts_with(line, NTRIP_ENDSOURCETABLE))
  210. goto done;
  211. /* coverity[string_null] - nul-terminated by previous memset */
  212. if (!(eol = strstr(line, NTRIP_BR)))
  213. break;
  214. GPSD_LOG(LOG_DATA, &device->context->errout,
  215. "next Ntrip source table line %s\n", line);
  216. *eol = '\0';
  217. llen = (ssize_t) (eol - line);
  218. /* TODO: parse headers */
  219. /* parse STR */
  220. if (str_starts_with(line, NTRIP_STR)) {
  221. ntrip_str_parse(line + strlen(NTRIP_STR),
  222. (size_t) (llen - strlen(NTRIP_STR)),
  223. &hold, &device->context->errout);
  224. if (strcmp(device->ntrip.stream.mountpoint, hold.mountpoint) == 0) {
  225. /* TODO: support for RTCM 3.0, SBAS (WAAS, EGNOS), ... */
  226. if (hold.format == fmt_unknown) {
  227. GPSD_LOG(LOG_ERROR, &device->context->errout,
  228. "Ntrip stream %s format not supported\n",
  229. line);
  230. return -1;
  231. }
  232. /* TODO: support encryption and compression algorithms */
  233. if (hold.compr_encryp != cmp_enc_none) {
  234. GPSD_LOG(LOG_ERROR, &device->context->errout,
  235. "Ntrip stream %s compression/encryption "
  236. "algorithm not supported\n",
  237. line);
  238. return -1;
  239. }
  240. /* TODO: support digest authentication */
  241. if (hold.authentication != auth_none
  242. && hold.authentication != auth_basic) {
  243. GPSD_LOG(LOG_ERROR, &device->context->errout,
  244. "Ntrip stream %s authentication method not supported\n",
  245. line);
  246. return -1;
  247. }
  248. /* no memcpy, so we can keep the other infos */
  249. device->ntrip.stream.format = hold.format;
  250. device->ntrip.stream.carrier = hold.carrier;
  251. device->ntrip.stream.latitude = hold.latitude;
  252. device->ntrip.stream.longitude = hold.longitude;
  253. device->ntrip.stream.nmea = hold.nmea;
  254. device->ntrip.stream.compr_encryp = hold.compr_encryp;
  255. device->ntrip.stream.authentication = hold.authentication;
  256. device->ntrip.stream.fee = hold.fee;
  257. device->ntrip.stream.bitrate = hold.bitrate;
  258. device->ntrip.stream.set = true;
  259. match = true;
  260. }
  261. /* TODO: compare stream location to own location to
  262. * find nearest stream if user hasn't provided one */
  263. }
  264. /* TODO: parse CAS */
  265. /* else if (str_starts_with(line, NTRIP_CAS)); */
  266. /* TODO: parse NET */
  267. /* else if (str_starts_with(line, NTRIP_NET)); */
  268. llen += strlen(NTRIP_BR);
  269. line += llen;
  270. len -= llen;
  271. GPSD_LOG(LOG_RAW, &device->context->errout,
  272. "Remaining Ntrip source table buffer %zd %s\n", len,
  273. line);
  274. }
  275. /* message too big to fit into buffer */
  276. if ((size_t)len == blen - 1)
  277. return -1;
  278. if (len > 0)
  279. memmove(buf, &buf[rlen - len], (size_t) len);
  280. }
  281. done:
  282. return match ? 1 : -1;
  283. }
  284. static int ntrip_stream_req_probe(const struct ntrip_stream_t *stream,
  285. struct gpsd_errout_t *errout)
  286. {
  287. int dsock;
  288. ssize_t r;
  289. char buf[BUFSIZ];
  290. dsock = netlib_connectsock(AF_UNSPEC, stream->url, stream->port, "tcp");
  291. if (dsock < 0) {
  292. GPSD_LOG(LOG_ERROR, errout,
  293. "ntrip stream connect error %d in req probe\n", dsock);
  294. return -1;
  295. }
  296. GPSD_LOG(LOG_SPIN, errout,
  297. "ntrip stream for req probe connected on fd %d\n", dsock);
  298. (void)snprintf(buf, sizeof(buf),
  299. "GET / HTTP/1.1\r\n"
  300. "User-Agent: NTRIP gpsd/%s\r\n"
  301. "Host: %s\r\n"
  302. "Connection: close\r\n"
  303. "\r\n", VERSION, stream->url);
  304. r = write(dsock, buf, strlen(buf));
  305. if (r != (ssize_t)strlen(buf)) {
  306. GPSD_LOG(LOG_ERROR, errout,
  307. "ntrip stream write error %d on fd %d during probe request %zd\n",
  308. errno, dsock, r);
  309. (void)close(dsock);
  310. return -1;
  311. }
  312. /* coverity[leaked_handle] This is an intentional allocation */
  313. return dsock;
  314. }
  315. static int ntrip_auth_encode(const struct ntrip_stream_t *stream,
  316. const char *auth,
  317. char buf[],
  318. size_t size)
  319. {
  320. memset(buf, 0, size);
  321. if (stream->authentication == auth_none)
  322. return 0;
  323. else if (stream->authentication == auth_basic) {
  324. char authenc[64];
  325. if (!auth)
  326. return -1;
  327. memset(authenc, 0, sizeof(authenc));
  328. if (b64_ntop
  329. ((unsigned char *)auth, strlen(auth), authenc,
  330. sizeof(authenc) - 1) < 0)
  331. return -1;
  332. (void)snprintf(buf, size - 1, "Authorization: Basic %s\r\n", authenc);
  333. } else {
  334. /* TODO: support digest authentication */
  335. }
  336. return 0;
  337. }
  338. /* *INDENT-ON* */
  339. static int ntrip_stream_get_req(const struct ntrip_stream_t *stream,
  340. const struct gpsd_errout_t *errout)
  341. {
  342. int dsock;
  343. char buf[BUFSIZ];
  344. dsock = netlib_connectsock(AF_UNSPEC, stream->url, stream->port, "tcp");
  345. if (BAD_SOCKET(dsock)) {
  346. GPSD_LOG(LOG_ERROR, errout,
  347. "ntrip stream connect error %d\n", dsock);
  348. return -1;
  349. }
  350. GPSD_LOG(LOG_SPIN, errout,
  351. "netlib_connectsock() returns socket on fd %d\n",
  352. dsock);
  353. (void)snprintf(buf, sizeof(buf),
  354. "GET /%s HTTP/1.1\r\n"
  355. "User-Agent: NTRIP gpsd/%s\r\n"
  356. "Host: %s\r\n"
  357. "Accept: rtk/rtcm, dgps/rtcm\r\n"
  358. "%s"
  359. "Connection: close\r\n"
  360. "\r\n", stream->mountpoint, VERSION, stream->url, stream->authStr);
  361. if (write(dsock, buf, strlen(buf)) != (ssize_t) strlen(buf)) {
  362. GPSD_LOG(LOG_ERROR, errout,
  363. "ntrip stream write error %d on fd %d during get request\n", errno,
  364. dsock);
  365. (void)close(dsock);
  366. return -1;
  367. }
  368. return dsock;
  369. }
  370. static int ntrip_stream_get_parse(const struct ntrip_stream_t *stream,
  371. const int dsock,
  372. const struct gpsd_errout_t *errout)
  373. {
  374. char buf[BUFSIZ];
  375. int opts;
  376. memset(buf, 0, sizeof(buf));
  377. while (read(dsock, buf, sizeof(buf) - 1) == -1) {
  378. if (errno == EINTR)
  379. continue;
  380. GPSD_LOG(LOG_ERROR, errout,
  381. "ntrip stream read error %d on fd %d during get rsp\n", errno,
  382. dsock);
  383. goto close;
  384. }
  385. /* parse 401 Unauthorized */
  386. /* coverity[string_null] - guaranteed terminated by the memset above */
  387. if (strstr(buf, NTRIP_UNAUTH)!=NULL) {
  388. GPSD_LOG(LOG_ERROR, errout,
  389. "not authorized for Ntrip stream %s/%s\n", stream->url,
  390. stream->mountpoint);
  391. goto close;
  392. }
  393. /* parse SOURCETABLE */
  394. if (strstr(buf, NTRIP_SOURCETABLE)!=NULL) {
  395. GPSD_LOG(LOG_ERROR, errout,
  396. "Broadcaster doesn't recognize Ntrip stream %s:%s/%s\n",
  397. stream->url, stream->port, stream->mountpoint);
  398. goto close;
  399. }
  400. /* parse ICY 200 OK */
  401. if (strstr(buf, NTRIP_ICY)==NULL) {
  402. GPSD_LOG(LOG_ERROR, errout,
  403. "Unknown reply %s from Ntrip service %s:%s/%s\n", buf,
  404. stream->url, stream->port, stream->mountpoint);
  405. goto close;
  406. }
  407. opts = fcntl(dsock, F_GETFL);
  408. if (opts >= 0)
  409. (void)fcntl(dsock, F_SETFL, opts | O_NONBLOCK);
  410. return dsock;
  411. close:
  412. (void)close(dsock);
  413. return -1;
  414. }
  415. int ntrip_open(struct gps_device_t *device, char *caster)
  416. /* open a connection to a Ntrip broadcaster */
  417. {
  418. char *amp, *colon, *slash;
  419. char *auth = NULL;
  420. char *port = NULL;
  421. char *stream = NULL;
  422. char *url = NULL;
  423. int ret = -1;
  424. switch (device->ntrip.conn_state) {
  425. case ntrip_conn_init:
  426. /* this has to be done here, because it is needed for multi-stage connection */
  427. device->servicetype = service_ntrip;
  428. device->ntrip.works = false;
  429. device->ntrip.sourcetable_parse = false;
  430. device->ntrip.stream.set = false;
  431. if ((amp = strchr(caster, '@')) != NULL) {
  432. if (((colon = strchr(caster, ':')) != NULL) && colon < amp) {
  433. auth = caster;
  434. *amp = '\0';
  435. caster = amp + 1;
  436. url = caster;
  437. } else {
  438. GPSD_LOG(LOG_ERROR, &device->context->errout,
  439. "can't extract user-ID and password from %s\n",
  440. caster);
  441. device->ntrip.conn_state = ntrip_conn_err;
  442. return -1;
  443. }
  444. }
  445. if ((slash = strchr(caster, '/')) != NULL) {
  446. *slash = '\0';
  447. stream = slash + 1;
  448. } else {
  449. /* TODO: add autoconnect like in dgpsip.c */
  450. GPSD_LOG(LOG_ERROR, &device->context->errout,
  451. "can't extract Ntrip stream from %s\n",
  452. caster);
  453. device->ntrip.conn_state = ntrip_conn_err;
  454. return -1;
  455. }
  456. if ((colon = strchr(caster, ':')) != NULL) {
  457. port = colon + 1;
  458. *colon = '\0';
  459. }
  460. if (!port) {
  461. port = "rtcm-sc104";
  462. if (!getservbyname(port, "tcp"))
  463. port = DEFAULT_RTCM_PORT;
  464. }
  465. (void)strlcpy(device->ntrip.stream.mountpoint,
  466. stream,
  467. sizeof(device->ntrip.stream.mountpoint));
  468. if (auth != NULL)
  469. (void)strlcpy(device->ntrip.stream.credentials,
  470. auth,
  471. sizeof(device->ntrip.stream.credentials));
  472. /*
  473. * Semantically url and port ought to be non-NULL by now,
  474. * but just in case...this code appeases Coverity.
  475. */
  476. if (url != NULL)
  477. (void)strlcpy(device->ntrip.stream.url,
  478. url,
  479. sizeof(device->ntrip.stream.url));
  480. if (port != NULL)
  481. (void)strlcpy(device->ntrip.stream.port,
  482. port,
  483. sizeof(device->ntrip.stream.port));
  484. ret = ntrip_stream_req_probe(&device->ntrip.stream,
  485. &device->context->errout);
  486. if (ret == -1) {
  487. device->ntrip.conn_state = ntrip_conn_err;
  488. return -1;
  489. }
  490. device->gpsdata.gps_fd = ret;
  491. device->ntrip.conn_state = ntrip_conn_sent_probe;
  492. return ret;
  493. case ntrip_conn_sent_probe:
  494. ret = ntrip_sourcetable_parse(device);
  495. if (ret == -1) {
  496. device->ntrip.conn_state = ntrip_conn_err;
  497. return -1;
  498. }
  499. if (ret == 0 && device->ntrip.stream.set == false) {
  500. return ret;
  501. }
  502. (void)close(device->gpsdata.gps_fd);
  503. if (ntrip_auth_encode(&device->ntrip.stream, device->ntrip.stream.credentials, device->ntrip.stream.authStr, sizeof(device->ntrip.stream.authStr)) != 0) {
  504. device->ntrip.conn_state = ntrip_conn_err;
  505. return -1;
  506. }
  507. ret = ntrip_stream_get_req(&device->ntrip.stream,
  508. &device->context->errout);
  509. if (ret == -1) {
  510. device->ntrip.conn_state = ntrip_conn_err;
  511. return -1;
  512. }
  513. device->gpsdata.gps_fd = ret;
  514. device->ntrip.conn_state = ntrip_conn_sent_get;
  515. break;
  516. case ntrip_conn_sent_get:
  517. ret = ntrip_stream_get_parse(&device->ntrip.stream,
  518. device->gpsdata.gps_fd,
  519. &device->context->errout);
  520. if (ret == -1) {
  521. device->ntrip.conn_state = ntrip_conn_err;
  522. return -1;
  523. }
  524. device->ntrip.conn_state = ntrip_conn_established;
  525. device->ntrip.works = true; // we know, this worked.
  526. break;
  527. case ntrip_conn_established:
  528. case ntrip_conn_err:
  529. return -1;
  530. }
  531. return ret;
  532. }
  533. void ntrip_report(struct gps_context_t *context,
  534. struct gps_device_t *gps,
  535. struct gps_device_t *caster)
  536. /* may be time to ship a usage report to the Ntrip caster */
  537. {
  538. static int count;
  539. /*
  540. * 10 is an arbitrary number, the point is to have gotten several good
  541. * fixes before reporting usage to our Ntrip caster.
  542. *
  543. * count % 5 is as arbitrary a number as the fixcnt. But some delay
  544. * was needed here
  545. */
  546. count ++;
  547. if (caster->ntrip.stream.nmea != 0 && context->fixcnt > 10 && (count % 5)==0) {
  548. if (caster->gpsdata.gps_fd > -1) {
  549. char buf[BUFSIZ];
  550. gpsd_position_fix_dump(gps, buf, sizeof(buf));
  551. if (write(caster->gpsdata.gps_fd, buf, strlen(buf)) ==
  552. (ssize_t) strlen(buf)) {
  553. GPSD_LOG(LOG_IO, &context->errout, "=> dgps %s\n", buf);
  554. } else {
  555. GPSD_LOG(LOG_IO, &context->errout,
  556. "ntrip report write failed\n");
  557. }
  558. }
  559. }
  560. }