/src/gpsd/gpsd-3.26.2~dev/gpsd/net_ntrip.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* net_ntrip.c -- gather and dispatch DGNSS data from NTRIP broadcasters |
2 | | * |
3 | | * This file is Copyright 2010 by the GPSD project |
4 | | * SPDX-License-Identifier: BSD-2-clause |
5 | | * |
6 | | * See: |
7 | | * https://igs.bkg.bund.de/root_ftp/NTRIP/documentation/NtripDocumentation.pdf |
8 | | * |
9 | | * NTRIP is not an open protocol. So this file is based on guesswork. |
10 | | */ |
11 | | |
12 | | #include "../include/gpsd_config.h" // must be before all includes |
13 | | |
14 | | #include <errno.h> |
15 | | #include <fcntl.h> |
16 | | #include <math.h> |
17 | | #include <netdb.h> |
18 | | #include <stdbool.h> |
19 | | #include <stdio.h> |
20 | | #include <stdlib.h> |
21 | | #include <string.h> |
22 | | #include <strings.h> |
23 | | #include <sys/socket.h> |
24 | | #include <sys/stat.h> |
25 | | #include <sys/types.h> |
26 | | #include <unistd.h> |
27 | | |
28 | | #include "../include/gpsd.h" |
29 | | #include "../include/strfuncs.h" |
30 | | |
31 | | // NTRIP 1.0 caster responses. Based on Icecast audio servers |
32 | 0 | #define NTRIP_SOURCETABLE "SOURCETABLE 200 OK\r\n" |
33 | 0 | #define NTRIP_ENDSOURCETABLE "ENDSOURCETABLE" |
34 | 0 | #define NTRIP_ICY "ICY 200 OK\r\n" |
35 | | |
36 | | // NTRIP 2.0 caster responses. Based on HTTP 1.1 |
37 | 0 | #define NTRIP_SOURCETABLE2 "Content-Type: gnss/sourcetable\r\n" |
38 | 0 | #define NTRIP_BODY "\r\n\r\n" |
39 | 0 | #define NTRIP_HTTP "HTTP/1.1 200 OK\r\n" |
40 | | |
41 | | // sourcetable stuff |
42 | 0 | #define NTRIP_CAS "CAS;" |
43 | 0 | #define NTRIP_NET "NET;" |
44 | 0 | #define NTRIP_STR "STR;" |
45 | 0 | #define NTRIP_BR "\r\n" |
46 | 0 | #define NTRIP_QSC "\";\"" |
47 | | |
48 | | // HTTP 1.1 |
49 | 0 | #define NTRIP_UNAUTH "401 Unauthorized\r\n" |
50 | 0 | #define NTRIP_CHUNKED "Transfer-Encoding: chunked\r\n" |
51 | | |
52 | | // ntrip_state() -- stringify conn_state |
53 | | static const char *ntrip_state(unsigned state) |
54 | 0 | { |
55 | | // NTRIP conn_states. See include/gpsd.h |
56 | 0 | const char *ntrip_states[] = { |
57 | 0 | "INIT", |
58 | 0 | "SENT_PROBE", |
59 | 0 | "SENT_GET", |
60 | 0 | "ESTABLISHED", |
61 | 0 | "ERR", |
62 | 0 | "CLOSED", |
63 | 0 | "INPROGRESS", |
64 | 0 | "UNKNOWN", |
65 | 0 | }; |
66 | 0 | unsigned num_states = sizeof(ntrip_states)/sizeof(ntrip_states[0]); |
67 | |
|
68 | 0 | if (num_states <= state) { |
69 | | // huh? |
70 | 0 | state = num_states - 1; |
71 | 0 | } |
72 | 0 | return ntrip_states[state]; |
73 | 0 | } |
74 | | |
75 | | // table to convert format string to enum ntrip_fmt |
76 | | static struct ntrip_fmt_s { |
77 | | const char *string; |
78 | | const enum ntrip_fmt format; |
79 | | } const ntrip_fmts[] = { |
80 | | {"CMR+", FMT_CMRP}, |
81 | | // RTCM1 required for the SAPOS server in Gemany, confirmed as RTCM2.3 |
82 | | {"RTCM1_", FMT_RTCM2_3}, |
83 | | {"RTCM 2.0", FMT_RTCM2_0}, |
84 | | {"RTCM 2.1", FMT_RTCM2_1}, |
85 | | {"RTCM 2.2", FMT_RTCM2_2}, |
86 | | {"RTCM22", FMT_RTCM2_2}, |
87 | | {"RTCM 2.3", FMT_RTCM2_3}, |
88 | | {"RTCM2.3", FMT_RTCM2_3}, |
89 | | {"RTCM 2", FMT_RTCM2}, |
90 | | {"RTCM2", FMT_RTCM2}, |
91 | | {"RTCM 3.0", FMT_RTCM3_0}, |
92 | | {"RTCM3.0", FMT_RTCM3_0}, |
93 | | {"RTCM 3.1", FMT_RTCM3_1}, |
94 | | {"RTCM3.1", FMT_RTCM3_1}, |
95 | | {"RTCM 3.2", FMT_RTCM3_2}, |
96 | | {"RTCM3.2", FMT_RTCM3_2}, // for http://sapos.geonord-od.de:2101/ |
97 | | {"RTCM32", FMT_RTCM3_2}, |
98 | | {"RTCM 3.3", FMT_RTCM3_3}, |
99 | | {"RTCM 3", FMT_RTCM3_0}, |
100 | | {"RTCM3", FMT_RTCM3_0}, |
101 | | {NULL, FMT_UNKNOWN}, |
102 | | }; |
103 | | |
104 | | /* Return pointer to one NUL terminated source table field |
105 | | * Return NULL on error |
106 | | * fields are separated by a semicolon (;) |
107 | | */ |
108 | | static char *ntrip_field_iterate(char *start, |
109 | | char *prev, |
110 | | const char *eol, |
111 | | const struct gpsd_errout_t *errout) |
112 | 0 | { |
113 | 0 | char *s, *t, *u; |
114 | |
|
115 | 0 | if (start) { |
116 | 0 | s = start; |
117 | 0 | } else { |
118 | 0 | if (!prev) { |
119 | 0 | return NULL; |
120 | 0 | } |
121 | 0 | s = prev + strnlen(prev, BUFSIZ) + 1; |
122 | 0 | if (s >= eol) { |
123 | 0 | return NULL; |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | | // ignore any quoted ; chars as they are part of the field content |
128 | 0 | t = s; |
129 | 0 | while ((u = strstr(t, NTRIP_QSC))) { |
130 | 0 | t = u + sizeof(NTRIP_QSC) - 1; |
131 | 0 | } |
132 | |
|
133 | 0 | if ((t = strstr(t, ";"))) { |
134 | 0 | *t = '\0'; |
135 | 0 | } |
136 | |
|
137 | 0 | GPSD_LOG(LOG_RAW, errout, "NTRIP: Next source table field %s\n", s); |
138 | |
|
139 | 0 | return s; |
140 | 0 | } |
141 | | |
142 | | |
143 | | /* Decode a stream record from the sourcetable |
144 | | * See: http://software.rtcm-ntrip.org/wiki/STR |
145 | | */ |
146 | | static void ntrip_str_parse(char *str, size_t len, |
147 | | struct ntrip_stream_t *hold, |
148 | | const struct gpsd_errout_t *errout) |
149 | 0 | { |
150 | 0 | char *s, *eol = str + len; |
151 | |
|
152 | 0 | memset(hold, 0, sizeof(*hold)); |
153 | | |
154 | | // <mountpoint> |
155 | 0 | if (NULL != (s = ntrip_field_iterate(str, NULL, eol, errout))) { |
156 | 0 | (void)strlcpy(hold->mountpoint, s, sizeof(hold->mountpoint)); |
157 | 0 | } |
158 | | // <identifier> |
159 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
160 | | // <format> |
161 | 0 | if (NULL != (s = ntrip_field_iterate(NULL, s, eol, errout))) { |
162 | 0 | struct ntrip_fmt_s const *pfmt; |
163 | |
|
164 | 0 | hold->format = FMT_UNKNOWN; |
165 | 0 | for (pfmt = ntrip_fmts; NULL != pfmt->string; pfmt++) { |
166 | 0 | if (0 == strcasecmp(pfmt->string, s)) { |
167 | 0 | hold->format = pfmt->format; |
168 | 0 | break; |
169 | 0 | } |
170 | 0 | } |
171 | 0 | if (FMT_UNKNOWN == hold->format) { |
172 | 0 | GPSD_LOG(LOG_WARN, errout, "NTRIP: Got unknown format '%s'\n", s); |
173 | 0 | } |
174 | 0 | } |
175 | | // <format-details> |
176 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
177 | | // <carrier> |
178 | 0 | if (NULL != (s = ntrip_field_iterate(NULL, s, eol, errout))) { |
179 | 0 | hold->carrier = atoi(s); |
180 | 0 | } |
181 | | // <nav-system> |
182 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
183 | | // <network> |
184 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
185 | | // <country> |
186 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
187 | | // <latitude> |
188 | 0 | hold->latitude = NAN; |
189 | 0 | if (NULL != (s = ntrip_field_iterate(NULL, s, eol, errout))) { |
190 | 0 | hold->latitude = safe_atof(s); |
191 | 0 | } |
192 | | // <longitude> |
193 | 0 | hold->longitude = NAN; |
194 | 0 | if (NULL != (s = ntrip_field_iterate(NULL, s, eol, errout))) { |
195 | 0 | hold->longitude = safe_atof(s); |
196 | 0 | } |
197 | | // <nmea> 0 == do not send GGA, 1 == send GGA |
198 | 0 | if (NULL != (s = ntrip_field_iterate(NULL, s, eol, errout))) { |
199 | 0 | hold->nmea = atoi(s); |
200 | 0 | } |
201 | | // <solution> |
202 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
203 | | // <generator> |
204 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
205 | | // <compr-encryp> |
206 | 0 | if (NULL != (s = ntrip_field_iterate(NULL, s, eol, errout))) { |
207 | |
|
208 | 0 | if (('\0' == s[0]) || |
209 | 0 | (0 == strcmp(" ", s)) || |
210 | 0 | (0 == strcasecmp("none", s))) { |
211 | 0 | hold->compr_encryp = CMP_ENC_NONE; |
212 | 0 | } else { |
213 | 0 | hold->compr_encryp = CMP_ENC_UNKNOWN; |
214 | 0 | GPSD_LOG(LOG_WARN, errout, |
215 | 0 | "NTRIP: Got unknown {compress,encrypt}ion '%s'\n", s); |
216 | 0 | } |
217 | 0 | } else { |
218 | 0 | GPSD_LOG(LOG_WARN, errout, |
219 | 0 | "NTRIP: STR missing encryption and authentication fields\n"); |
220 | 0 | return; // done |
221 | 0 | } |
222 | | // <authentication> |
223 | 0 | if (NULL != (s = ntrip_field_iterate(NULL, s, eol, errout))) { |
224 | 0 | if (0 == strcasecmp("N", s)) { |
225 | 0 | hold->authentication = AUTH_NONE; |
226 | 0 | } else if (0 == strcasecmp("B", s)) { |
227 | 0 | hold->authentication = AUTH_BASIC; |
228 | 0 | } else if (0 == strcasecmp("D", s)) { |
229 | 0 | hold->authentication = AUTH_DIGEST; |
230 | 0 | } else { |
231 | 0 | hold->authentication = AUTH_UNKNOWN; |
232 | 0 | GPSD_LOG(LOG_WARN, errout, |
233 | 0 | "NTRIP: Got unknown authenticatiion '%s'\n", s); |
234 | 0 | } |
235 | 0 | } else { |
236 | 0 | GPSD_LOG(LOG_WARN, errout, |
237 | 0 | "NTRIP: STR missing authenticatiion field\n"); |
238 | 0 | return; // done |
239 | 0 | } |
240 | | // <fee> |
241 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
242 | 0 | if (NULL == s) { |
243 | | // done, no more |
244 | 0 | return; |
245 | 0 | } |
246 | 0 | hold->fee = atoi(s); |
247 | | |
248 | | // <bitrate> |
249 | 0 | s = ntrip_field_iterate(NULL, s, eol, errout); |
250 | 0 | if (NULL == s) { |
251 | | // done, no more |
252 | 0 | return; |
253 | 0 | } |
254 | 0 | hold->bitrate = atoi(s); |
255 | | |
256 | | // ...<misc> |
257 | | // we don't care about extra fields |
258 | 0 | } |
259 | | |
260 | | /* Parse the sourcetable, looking for a match to requested stream. |
261 | | * |
262 | | * Return 1 -- found match |
263 | | * 0 -- no match, maybe more data to parse? |
264 | | * less than zero -- error |
265 | | */ |
266 | | static int ntrip_sourcetable_parse(struct gps_device_t *device) |
267 | 0 | { |
268 | 0 | struct ntrip_stream_t hold; |
269 | 0 | ssize_t llen, len = 0; |
270 | 0 | char *line; |
271 | 0 | char buf[BUFSIZ / 2]; // half of BUFSIZE, so we can GPSD_LOG() it |
272 | 0 | socket_t fd = (socket_t)device->gpsdata.gps_fd; |
273 | |
|
274 | 0 | for (;;) { |
275 | 0 | ssize_t rlen; |
276 | 0 | long long buf_avail; |
277 | |
|
278 | 0 | if (0 > len) { |
279 | | // Pacify Coverity 498046 |
280 | 0 | len = 0; |
281 | 0 | } |
282 | 0 | buf_avail = sizeof(buf) - len; |
283 | 0 | if (0 > buf_avail) { |
284 | | // Pacify Coverity 498046 |
285 | 0 | buf_avail = 0; |
286 | 0 | } |
287 | 0 | memset(&buf[len], 0, buf_avail); |
288 | 0 | errno = 0; // paranoia |
289 | | // read, leave room for trailing NUL |
290 | 0 | rlen = read(fd, &buf[len], buf_avail - 1); |
291 | | // cast for 32-bit ints |
292 | 0 | GPSD_LOG(LOG_RAW, &device->context->errout, |
293 | 0 | "NTRIP: on fd %ld len %zd tried %zd, got %zd\n", |
294 | 0 | (long)fd, len, sizeof(buf) - (size_t)(1 + len), rlen); |
295 | 0 | if (0 > rlen) { |
296 | 0 | if (EINTR == errno) { |
297 | 0 | continue; |
298 | 0 | } |
299 | 0 | if (device->ntrip.sourcetable_parse && |
300 | 0 | EAGAIN == errno) { |
301 | | // not found a match, but there is no more data |
302 | 0 | return 0; |
303 | 0 | } |
304 | | // cast for 32-bit ints |
305 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
306 | 0 | "NTRIP: stream read error %s(%d) on fd %ld\n", |
307 | 0 | strerror(errno), errno, (long)fd); |
308 | 0 | return -1; |
309 | 0 | } |
310 | 0 | if (0 == rlen) { // server closed the connection |
311 | | // cast for 32-bit ints |
312 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
313 | 0 | "NTRIP: stream unexpected close %s(%d) on fd %ld " |
314 | 0 | "during sourcetable read\n", |
315 | 0 | strerror(errno), errno, (long)fd); |
316 | 0 | return -2; |
317 | 0 | } |
318 | | |
319 | 0 | line = buf; |
320 | 0 | len += rlen; |
321 | 0 | rlen = len; |
322 | | // line points to the next char in buf to analyze |
323 | | // rlen is length of all data in buf |
324 | | // len is length of remaining data in buf, |
325 | |
|
326 | 0 | GPSD_LOG(LOG_IO, &device->context->errout, |
327 | 0 | "NTRIP: source table buffer >%.*s<\n", (int)rlen, buf); |
328 | |
|
329 | 0 | line[rlen] = '\0'; // pacify coverity that this is NUL terminated |
330 | |
|
331 | 0 | if (!device->ntrip.sourcetable_parse) { |
332 | | /* For ntrip v1 the very first line s/b: |
333 | | * "SOURCETABLE 200 OK\r\n" |
334 | | * For ntrip v2, the header should contain: |
335 | | * "Content-Type: gnss/sourcetable\r\n" |
336 | | */ |
337 | |
|
338 | 0 | if (str_starts_with(line, NTRIP_SOURCETABLE)) { |
339 | | // parse SOURCETABLE, NTRIP 1.0 |
340 | 0 | device->ntrip.sourcetable_parse = true; |
341 | 0 | } else if (NULL != strstr(line, NTRIP_SOURCETABLE2)) { |
342 | | // parse sourcetable, NTRIP 2.0 |
343 | 0 | device->ntrip.sourcetable_parse = true; |
344 | 0 | } else { |
345 | 0 | GPSD_LOG(LOG_WARN, &device->context->errout, |
346 | 0 | "NTRIP: Unexpected reply: %s.\n", |
347 | 0 | buf); |
348 | 0 | return -3; |
349 | 0 | } |
350 | 0 | line = strstr(line, NTRIP_BODY); |
351 | 0 | if (NULL == line) { |
352 | 0 | return -4; |
353 | 0 | } |
354 | 0 | line += 4; // point to 1st line of body |
355 | 0 | len = rlen - (line - buf); |
356 | 0 | } |
357 | | |
358 | 0 | while (0 < len) { |
359 | 0 | char *eol; |
360 | |
|
361 | 0 | if (str_starts_with(line, NTRIP_ENDSOURCETABLE)) { |
362 | | // found ENDSOURCETABLE |
363 | | // we got to the end of source table |
364 | 0 | return -5; |
365 | 0 | } |
366 | | |
367 | 0 | eol = strstr(line, NTRIP_BR); |
368 | 0 | if (NULL == eol){ |
369 | | // no full line in the buffer |
370 | 0 | break; |
371 | 0 | } |
372 | | |
373 | 0 | *eol = '\0'; |
374 | 0 | llen = (ssize_t)(eol - line); |
375 | |
|
376 | 0 | GPSD_LOG(LOG_IO, &device->context->errout, |
377 | 0 | "NTRIP: checking: >%s<\n", line); |
378 | |
|
379 | 0 | if (str_starts_with(line, NTRIP_STR)) { |
380 | | // parse STR |
381 | 0 | ntrip_str_parse(line + sizeof(NTRIP_STR) - 1, |
382 | 0 | (size_t)(llen - (sizeof(NTRIP_STR) - 1)), |
383 | 0 | &hold, &device->context->errout); |
384 | |
|
385 | 0 | if (0 == strcmp(device->ntrip.stream.mountpoint, |
386 | 0 | hold.mountpoint)) { |
387 | | // Found a match to requested stream |
388 | | |
389 | | // TODO: support for more formats. Not that we care |
390 | | // about the format. |
391 | 0 | if (FMT_UNKNOWN == hold.format) { |
392 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
393 | 0 | "NTRIP: stream %s format not supported\n", |
394 | 0 | line); |
395 | 0 | return -6; |
396 | 0 | } |
397 | | // TODO: support encryption and compression algorithms |
398 | 0 | if (CMP_ENC_NONE != hold.compr_encryp) { |
399 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
400 | 0 | "NTRIP. stream %s compression/encryption " |
401 | 0 | "algorithm not supported\n", |
402 | 0 | line); |
403 | 0 | return -7; |
404 | 0 | } |
405 | | // TODO: support digest authentication |
406 | 0 | if (AUTH_NONE != hold.authentication && |
407 | 0 | AUTH_BASIC != hold.authentication) { |
408 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
409 | 0 | "NTRIP. stream %s authentication method " |
410 | 0 | "not supported\n", |
411 | 0 | line); |
412 | 0 | return -8; |
413 | 0 | } |
414 | | // no memcpy, so we can keep the other infos |
415 | 0 | device->ntrip.stream.format = hold.format; |
416 | 0 | device->ntrip.stream.carrier = hold.carrier; |
417 | 0 | device->ntrip.stream.latitude = hold.latitude; |
418 | 0 | device->ntrip.stream.longitude = hold.longitude; |
419 | 0 | device->ntrip.stream.nmea = hold.nmea; |
420 | 0 | device->ntrip.stream.compr_encryp = hold.compr_encryp; |
421 | 0 | device->ntrip.stream.authentication = hold.authentication; |
422 | 0 | device->ntrip.stream.fee = hold.fee; |
423 | 0 | device->ntrip.stream.bitrate = hold.bitrate; |
424 | 0 | device->ntrip.stream.set = true; |
425 | 0 | return 1; |
426 | 0 | } |
427 | | /* TODO: compare stream location to own location to |
428 | | * find nearest stream if user hasn't provided one */ |
429 | 0 | } else if (str_starts_with(line, NTRIP_CAS)) { |
430 | | // TODO: parse CAS, why? |
431 | | // See: http://software.rtcm-ntrip.org/wiki/CAS |
432 | 0 | GPSD_LOG(LOG_IO, &device->context->errout, |
433 | 0 | "NTRIP: Skipping: '%s'\n", line); |
434 | 0 | } else if (str_starts_with(line, NTRIP_NET)) { |
435 | | // TODO: parse NET, why? |
436 | | // See: http://software.rtcm-ntrip.org/wiki/NET |
437 | 0 | GPSD_LOG(LOG_IO, &device->context->errout, |
438 | 0 | "NTRIP: Skipping '%s'\n", line); |
439 | 0 | } |
440 | | // else ??? |
441 | | |
442 | 0 | llen += sizeof(NTRIP_BR) - 1; |
443 | 0 | line += llen; // point to start of next line |
444 | 0 | len -= llen; // calculate remaining data in buf. |
445 | 0 | GPSD_LOG(LOG_IO, &device->context->errout, |
446 | 0 | "NTRIP: Remaining source table buffer len %zd\n", len); |
447 | 0 | } |
448 | | |
449 | 0 | GPSD_LOG(LOG_IO, &device->context->errout, |
450 | 0 | "NTRIP: Remaining source table buffer len %zd\n", len); |
451 | |
|
452 | 0 | if (0 < len) { |
453 | | // shuffle any remaining fragment to front of buf |
454 | | // line points to the last fragment in buf |
455 | 0 | memmove(buf, line, (size_t)len); |
456 | 0 | } |
457 | 0 | } |
458 | | |
459 | | // fell out of loop, no joy |
460 | 0 | return -9; |
461 | 0 | } |
462 | | |
463 | | /* Connect to NTRIP caster |
464 | | * |
465 | | * Warning: Blocking. if the host is unresponsive, this will hang forever. |
466 | | * |
467 | | * Return: fntrip_stream_get_parseile descriptor of connection |
468 | | * negative number on failure |
469 | | */ |
470 | | static int ntrip_stream_req_probe(const struct ntrip_stream_t *stream, |
471 | | struct gpsd_errout_t *errout) |
472 | 0 | { |
473 | 0 | int dsock; |
474 | 0 | ssize_t r, blen; |
475 | 0 | char buf[BUFSIZ]; |
476 | 0 | char outbuf[BUFSIZ]; |
477 | | |
478 | | // open blocking |
479 | 0 | dsock = netlib_connectsock(AF_UNSPEC, stream->host, stream->port, "tcp"); |
480 | 0 | if (0 > dsock) { |
481 | 0 | GPSD_LOG(LOG_ERROR, errout, |
482 | 0 | "NTRIP: ntrip_stream_req_probe(%s) connect error %s(%d)\n", |
483 | 0 | stream->url, netlib_errstr(dsock), dsock); |
484 | 0 | return -1; |
485 | 0 | } |
486 | 0 | blen = snprintf(buf, sizeof(buf), |
487 | 0 | "GET / HTTP/1.1\r\n" |
488 | 0 | "Ntrip-Version: Ntrip/2.0\r\n" |
489 | 0 | "User-Agent: NTRIP gpsd/%s\r\n" |
490 | 0 | "Host: %s\r\n" |
491 | 0 | "Connection: close\r\n" |
492 | 0 | "\r\n", VERSION, stream->host); |
493 | 0 | if (1 > blen) { |
494 | 0 | GPSD_LOG(LOG_ERROR, errout, |
495 | 0 | "NTRIP: ntrip_stream_req_probe(%s) snprintf() fail\n", |
496 | 0 | stream->url); |
497 | 0 | return -1; |
498 | 0 | } |
499 | | |
500 | 0 | GPSD_LOG(LOG_IO, errout, |
501 | 0 | "NTRIP: ntrip_stream_req_probe(%s) fd %d sending >%s<\n", |
502 | 0 | stream->url, dsock, |
503 | 0 | gps_visibilize(outbuf, sizeof(outbuf), buf, blen)); |
504 | |
|
505 | 0 | r = write(dsock, buf, blen); |
506 | 0 | if (blen != r) { |
507 | 0 | GPSD_LOG(LOG_ERROR, errout, |
508 | 0 | "NTRIP: stream write error %s(%d) on fd %d " |
509 | 0 | "during probe request %zd\n", |
510 | 0 | strerror(errno), errno, dsock, r); |
511 | 0 | (void)close(dsock); |
512 | 0 | return -1; |
513 | 0 | } |
514 | | // coverity[leaked_handle] This is an intentional allocation |
515 | 0 | return dsock; |
516 | 0 | } |
517 | | |
518 | | /* ntrip_auth_encode() - compute the HTTP auth string, if required. |
519 | | * |
520 | | * Return: 0 == OK |
521 | | * -1 = auth error |
522 | | */ |
523 | | static int ntrip_auth_encode(struct ntrip_stream_t *stream) |
524 | 0 | { |
525 | 0 | char authenc[64]; // base 64 encoding of auth (username:password) |
526 | 0 | int ret = 0; |
527 | |
|
528 | 0 | memset(stream->authStr, 0, sizeof(stream->authStr)); |
529 | 0 | switch (stream->authentication) { |
530 | 0 | case AUTH_NONE: |
531 | 0 | if ('\0' == *stream->credentials) { |
532 | | // nothing to do. |
533 | 0 | break; |
534 | 0 | } |
535 | | /* The user provided a user:password, but the SOURCETABLE did |
536 | | * not request AUTH_BASIC. The RTKLIB Ver 2.4.2 (2013) str2str |
537 | | * forgets to ask for basic auth even when needed. |
538 | | * So if the user gave us a u:p, send it anyway |
539 | | */ |
540 | 0 | FALLTHROUGH |
541 | 0 | case AUTH_BASIC: |
542 | | // RFC 7617 Basic Access Authentication. |
543 | | // username may not contain a colon (") |
544 | 0 | memset(authenc, 0, sizeof(authenc)); |
545 | 0 | if (0 > b64_ntop((const unsigned char *)stream->credentials, |
546 | 0 | strnlen(stream->credentials, |
547 | 0 | sizeof(stream->credentials)), |
548 | 0 | authenc, sizeof(authenc) - 1)) { |
549 | 0 | ret = -1; |
550 | 0 | break; |
551 | 0 | } |
552 | 0 | (void)snprintf(stream->authStr, sizeof(stream->authStr), |
553 | 0 | "Authorization: Basic %s\r\n", authenc); |
554 | 0 | break; |
555 | 0 | case AUTH_DIGEST: |
556 | | // TODO: support digest authentication, who needs it? |
557 | | // Possibly: RFC 2617 |
558 | | // HTTP Authentication: Basic and Digest Access Authentication) |
559 | | /* WWW-Authenticate: Digest realm="testrealm@host.com", |
560 | | * qop="auth,auth-int", |
561 | | * nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", |
562 | | * opaque="5ccc069c403ebaf9f0171e9517f40e41" |
563 | | */ |
564 | 0 | ret = -1; |
565 | 0 | break; |
566 | 0 | case AUTH_UNKNOWN: |
567 | | // WTF? |
568 | 0 | FALLTHROUGH |
569 | 0 | default: |
570 | 0 | ret = -1; |
571 | 0 | break; |
572 | 0 | } |
573 | 0 | return ret; |
574 | 0 | } |
575 | | |
576 | | /* netlib_connectsock() open a blocking socket to host. |
577 | | * |
578 | | * Return: socket to ntrip server on success |
579 | | * less than zero on error |
580 | | */ |
581 | | static socket_t ntrip_stream_get_req(const struct ntrip_stream_t *stream, |
582 | | const struct gpsd_errout_t *errout) |
583 | 0 | { |
584 | 0 | int dsock; |
585 | 0 | char buf[BUFSIZ]; |
586 | 0 | char outbuf[BUFSIZ]; |
587 | 0 | ssize_t cnt, cnt1; |
588 | | |
589 | | // open blocking |
590 | 0 | dsock = netlib_connectsock(AF_UNSPEC, stream->host, stream->port, "tcp"); |
591 | 0 | if (BAD_SOCKET(dsock)) { |
592 | 0 | GPSD_LOG(LOG_ERROR, errout, |
593 | 0 | "NTRIP: stream connect error %ss(%d)\n", |
594 | 0 | netlib_errstr(dsock), dsock); |
595 | 0 | return -1; |
596 | 0 | } |
597 | | |
598 | 0 | GPSD_LOG(LOG_SPIN, errout, |
599 | 0 | "NTRIP: netlib_connectsock() returns socket on fd %d\n", |
600 | 0 | dsock); |
601 | |
|
602 | 0 | cnt = snprintf(buf, sizeof(buf), |
603 | 0 | "GET /%s HTTP/1.1\r\n" |
604 | 0 | "Ntrip-Version: Ntrip/2.0\r\n" |
605 | 0 | "User-Agent: NTRIP gpsd/%s\r\n" |
606 | 0 | "Host: %s\r\n" |
607 | 0 | "Accept: rtk/rtcm, dgps/rtcm\r\n" |
608 | 0 | "%s" |
609 | 0 | "Connection: close\r\n" |
610 | 0 | "\r\n", stream->mountpoint, VERSION, stream->host, |
611 | 0 | stream->authStr); |
612 | 0 | if (1 > cnt) { |
613 | 0 | GPSD_LOG(LOG_ERROR, errout, |
614 | 0 | "NTRIP: netlib_connectsock() snprintf fail<\n"); |
615 | 0 | return -1; |
616 | 0 | } |
617 | | |
618 | 0 | GPSD_LOG(LOG_IO, errout, |
619 | 0 | "NTRIP: netlib_connectsock() sending >%s<\n", |
620 | 0 | gps_visibilize(outbuf, sizeof(outbuf), buf, cnt)); |
621 | |
|
622 | 0 | cnt1 = write(dsock, buf, cnt); |
623 | 0 | if (cnt != cnt1) { |
624 | 0 | GPSD_LOG(LOG_ERROR, errout, |
625 | 0 | "NTRIP: stream write error %s(%d) on fd %d during " |
626 | 0 | "get request\n", |
627 | 0 | strerror(errno), errno, dsock); |
628 | 0 | (void)close(dsock); |
629 | 0 | return -1; |
630 | 0 | } |
631 | 0 | return dsock; |
632 | 0 | } |
633 | | |
634 | | /* lexer_getline() -- get one line, ending in \n or \0, from lexer->inbuffer, |
635 | | * put in lexer->outbuffer. NUL terminate outbuffer. |
636 | | * |
637 | | * Assume: inbufptr is correct. inbuffer is NUL terminated. |
638 | | * |
639 | | * Can not handle buffer wrap. |
640 | | * |
641 | | * Return: void |
642 | | */ |
643 | | static void lexer_getline(struct gps_lexer_t *lexer) |
644 | 0 | { |
645 | 0 | unsigned i; |
646 | |
|
647 | 0 | for (i = 0; i < sizeof(lexer->outbuffer) - 2; i++) { |
648 | 0 | unsigned char u; |
649 | |
|
650 | 0 | if (0 == lexer->inbuflen || |
651 | 0 | sizeof(lexer->inbuffer) <= lexer->inbuflen) { // paranoia |
652 | | // nothing left to read, ending not found |
653 | 0 | break; |
654 | 0 | } |
655 | 0 | u = *lexer->inbufptr++; |
656 | 0 | lexer->outbuffer[i] = u; |
657 | 0 | lexer->inbuflen--; |
658 | |
|
659 | 0 | if ('\0' == u) { |
660 | | // found NUL |
661 | 0 | break; |
662 | 0 | } |
663 | 0 | if ('\n' == u) { |
664 | | // found return |
665 | 0 | i++; |
666 | 0 | break; |
667 | 0 | } |
668 | 0 | } |
669 | 0 | lexer->outbuffer[i] = '\0'; // Ensure a NUL |
670 | 0 | lexer->outbuflen = i; |
671 | 0 | } |
672 | | |
673 | | /* ntrip_stream_get_parse(s) -- read, then parse, the stream header. |
674 | | * Assume the entire header is ready to be read, and is less than |
675 | | * 1024 bytes. |
676 | | * |
677 | | * Return: 0 == OK |
678 | | * less than zero == failure |
679 | | */ |
680 | | static int ntrip_stream_get_parse(struct gps_device_t *device) |
681 | 0 | { |
682 | 0 | char dbgbuf[128]; |
683 | 0 | int opts; |
684 | 0 | const struct ntrip_stream_t *stream = &device->ntrip.stream; |
685 | | // (int) to shut up cadacy about USE_QT. |
686 | 0 | const int dsock = (int)device->gpsdata.gps_fd; |
687 | 0 | const struct gpsd_errout_t *errout = &device->context->errout; |
688 | 0 | ssize_t read_ret; // value retuend from read() |
689 | 0 | struct gps_lexer_t *lexer = &device->lexer; |
690 | 0 | char *ibuf = (char *)lexer->inbuffer; |
691 | 0 | char *obuf = (char *)lexer->outbuffer; |
692 | 0 | bool got_header; |
693 | |
|
694 | 0 | GPSD_LOG(LOG_PROG, errout, |
695 | 0 | "NTRIP: ntrip_stream_get_parse(fd %d)\n", dsock); |
696 | 0 | lexer_init(lexer, &device->context->errout); |
697 | | /* We expect the header comes in as one TCP packet. |
698 | | * dsock is still blocking, so get exactly 1024 bytes */ |
699 | 0 | while (0 >= (read_ret = read(dsock, ibuf, 1024))) { |
700 | 0 | if (EINTR == errno) { |
701 | 0 | continue; |
702 | 0 | } |
703 | 0 | GPSD_LOG(LOG_ERROR, errout, |
704 | 0 | "NTRIP: stream read error %s(%d) on fd %d during get rsp\n", |
705 | 0 | strerror(errno), errno, dsock); |
706 | 0 | return -1; |
707 | 0 | } |
708 | 0 | ibuf[read_ret] = '\0'; // Make a nice NUL terminated string. |
709 | 0 | lexer->inbuflen = (size_t)read_ret; |
710 | 0 | lexer_getline(lexer); |
711 | 0 | GPSD_LOG(LOG_IO, errout, |
712 | 0 | "NTRIP: lexer_getline() >%s<\n", |
713 | 0 | gps_visibilize(dbgbuf, sizeof(dbgbuf), |
714 | 0 | (char *)lexer->outbuffer, lexer->outbuflen)); |
715 | | |
716 | | /* check for which of the 4 things we expect to start the reply: |
717 | | * |
718 | | * 401 Unauthorized\r\n -- missing or wrong authentication |
719 | | * SOURCETABLE 200 OK\r\n -- incorrect mount point requested |
720 | | * ICY 200 OK\r\n -- NTRIP v1 |
721 | | * HTTP/1.1 200 OK\r\n -- NTRIP v2 |
722 | | * |
723 | | * Anything else is not understood. |
724 | | */ |
725 | |
|
726 | 0 | if (0 == strncmp(obuf, NTRIP_UNAUTH, sizeof(NTRIP_UNAUTH))) { |
727 | 0 | GPSD_LOG(LOG_ERROR, errout, |
728 | 0 | "NTRIP: not authorized for %s\n", stream->url); |
729 | 0 | return -1; |
730 | 0 | } |
731 | | // parse "ICY 200 OK" or "HTTP/1.1 200 OK" |
732 | 0 | if (0 != strncmp(obuf, NTRIP_ICY, sizeof(NTRIP_ICY)) && |
733 | 0 | 0 != strncmp(obuf, NTRIP_HTTP, sizeof(NTRIP_HTTP))) { |
734 | 0 | GPSD_LOG(LOG_ERROR, errout, |
735 | 0 | "NTRIP: Unknown reply %s from caster: %s:%s/%s\n", obuf, |
736 | 0 | stream->host, stream->port, stream->mountpoint); |
737 | 0 | return -1; |
738 | 0 | } |
739 | | |
740 | | // first line is good. |
741 | | |
742 | | /* The NTRIP v2.0 is heavily based on HTTP/1.1, with some casters |
743 | | * also using chunked transfers, as defined by RFC 9112, chap. 7.1, |
744 | | * Chunked Transfer Coding, like so: |
745 | | * |
746 | | * HTTP/1.1 200 OK\r\n |
747 | | * [...headers...]\r\n |
748 | | * Transfer-Encoding: chunked\r\n |
749 | | * \r\n |
750 | | * 64\r\n |
751 | | * x64-bytes-worth-of-binary-message |
752 | | * \r\n |
753 | | * 27;\r\n |
754 | | * x27-bytes-worth-of-binary-message |
755 | | * \r\n |
756 | | * 42;foo=bar\r\n |
757 | | * x42-bytes-worth-of-binary-message |
758 | | * \r\n |
759 | | * |
760 | | * Annoyingly the chunks are NOT aligned on NTRIP message boundaries. |
761 | | * So one possible benefit is lost. |
762 | | * |
763 | | * http/2 removed support for chunking. Good riddance! |
764 | | * |
765 | | */ |
766 | | |
767 | 0 | got_header = false; |
768 | 0 | while (0 < lexer->inbuflen) { |
769 | 0 | lexer_getline(lexer); |
770 | 0 | GPSD_LOG(LOG_IO, errout, |
771 | 0 | "NTRIP: lexer_getline() >%s<\n", |
772 | 0 | gps_visibilize(dbgbuf, sizeof(dbgbuf), |
773 | 0 | (char *)lexer->outbuffer, lexer->outbuflen)); |
774 | | |
775 | | // Chunking needed? |
776 | 0 | if (0 == strncmp(obuf, NTRIP_CHUNKED, sizeof(NTRIP_CHUNKED))) { |
777 | 0 | GPSD_LOG(LOG_PROG, errout, |
778 | 0 | "NTRIP: caster sends chunked data\n"); |
779 | 0 | lexer->chunked = true; |
780 | 0 | } |
781 | 0 | if ('\0' == *lexer->outbuffer) { |
782 | | // done, never got end of headers. |
783 | 0 | break; |
784 | 0 | } |
785 | 0 | if (0 == strncmp(obuf, NTRIP_BR, sizeof(NTRIP_BR))) { |
786 | | // done |
787 | 0 | got_header = true; |
788 | 0 | break; |
789 | 0 | } |
790 | 0 | } |
791 | 0 | if (false == got_header) { |
792 | 0 | GPSD_LOG(LOG_WARN, errout, |
793 | 0 | "NTRIP: did not get end of headers.\n"); |
794 | | /* do something about it? If we are not chunked it'll work out |
795 | | * anyway. */ |
796 | 0 | } |
797 | |
|
798 | 0 | opts = fcntl(dsock, F_GETFL); |
799 | |
|
800 | 0 | if (-1 == opts) { |
801 | 0 | GPSD_LOG(LOG_ERROR, errout, "NTRIP: fcntl(%d) %s(%d)\n", |
802 | 0 | dsock, strerror(errno), errno); |
803 | 0 | } else { |
804 | 0 | (void)fcntl(dsock, F_SETFL, opts | O_NONBLOCK); |
805 | 0 | } |
806 | | // The excess data from this first read is now in device->lexer. |
807 | | // So far we have only seen zero here. |
808 | 0 | GPSD_LOG(LOG_IO, errout, |
809 | 0 | "NTRIP: ntrip_stream_get_parse(), %zu leftover bytes\n", |
810 | 0 | lexer->inbuflen); |
811 | 0 | if (0 == lexer->inbuflen || |
812 | 0 | sizeof(lexer->inbuffer) <= lexer->inbuflen) { // paranoia |
813 | 0 | packet_reset(lexer); |
814 | 0 | } else { |
815 | | /* The "leftover" is the start of the datastream. Chunked or |
816 | | * unchunked. */ |
817 | 0 | if (lexer->inbufptr != lexer->inbuffer) { |
818 | | // Shift inbufptr to the start. Yes, a bit brutal. |
819 | 0 | memmove(lexer->inbuffer, lexer->inbufptr, lexer->inbuflen); |
820 | 0 | lexer->inbufptr = lexer->inbuffer; |
821 | 0 | } |
822 | 0 | GPSD_LOG(LOG_IO, errout, |
823 | 0 | "NTRIP: leftover: >%s<\n", |
824 | 0 | gps_visibilize(dbgbuf, sizeof(dbgbuf), |
825 | 0 | (char *)lexer->inbuffer, lexer->inbuflen)); |
826 | 0 | } |
827 | | // empty the outbuffer of the ehader stuff |
828 | 0 | lexer->inbufptr = lexer->inbuffer; |
829 | 0 | return 0; |
830 | 0 | } |
831 | | |
832 | | /* |
833 | | * parse an ntrip:// url, "ntrip://" already stripped off |
834 | | * see tests/test_timespec.c for possible inputs and results |
835 | | * FIXME: merge with test_parse_uri_dest() |
836 | | * |
837 | | * Return 0 on success |
838 | | * less than zero on failure |
839 | | */ |
840 | | int ntrip_parse_url(const struct gpsd_errout_t *errout, |
841 | | struct ntrip_stream_t *stream, const char *fullurl) |
842 | 0 | { |
843 | 0 | char dup[256]; // working copy of url |
844 | 0 | char *at; // pointer to at |
845 | 0 | char *colon; // pointer to colon |
846 | 0 | char *slash; // pointer to slash |
847 | 0 | char *lsb; // pointer to left square bracket ([) |
848 | 0 | char *rsb; // pointer to right square bracket (]) |
849 | 0 | char *auth = NULL; // user:pass |
850 | 0 | char *host = NULL; // hostname, IPv4 or IPv6 |
851 | 0 | char *port = NULL; |
852 | 0 | char *mountpoint = NULL; // mount point |
853 | | |
854 | | // save original URL |
855 | 0 | strlcpy(stream->url, fullurl, sizeof(stream->url) - 1); |
856 | | |
857 | | // make a local copy |
858 | 0 | strlcpy(dup, fullurl, sizeof(dup) - 1); |
859 | | |
860 | | // find mountpoint, searching from right to left |
861 | 0 | if (NULL == (slash = strrchr(dup, '/'))) { |
862 | 0 | GPSD_LOG(LOG_ERROR, errout, |
863 | 0 | "NTRIP: can't extract stream from %s\n", dup); |
864 | 0 | return -1; |
865 | 0 | } |
866 | 0 | *slash = '\0'; |
867 | 0 | mountpoint = slash + 1; |
868 | | // dup now ends in host or host:port |
869 | |
|
870 | 0 | if ('\0' == mountpoint[0]) { |
871 | | // this also handle the trailing / case |
872 | 0 | GPSD_LOG(LOG_ERROR, errout, |
873 | 0 | "NTRIP: ntrip_parse_url(%s) missing mountpoint.\n", fullurl); |
874 | 0 | return -1; |
875 | 0 | } |
876 | 0 | (void)strlcpy(stream->mountpoint, |
877 | 0 | mountpoint, sizeof(stream->mountpoint)); |
878 | | |
879 | | // dup now contains in order any of username, password, host and port |
880 | | // we know "host" has a dot (hostname or IPv4) or a ] (IPv6) |
881 | 0 | at = strrchr(dup, '@'); // user@pass |
882 | 0 | colon = strrchr(dup, ':'); // pass:host:port, pass:host, host:port |
883 | 0 | rsb = strrchr(dup, ']'); // host is IPv6 literal |
884 | 0 | lsb = strrchr(dup, '['); // host is IPv6 literal |
885 | |
|
886 | 0 | if (NULL == colon) { |
887 | | // no port (:2101), no auth (user:pass@), not IPv6 [fe80::] |
888 | 0 | port = NULL; |
889 | 0 | auth = NULL; |
890 | 0 | host = dup; |
891 | 0 | } else { |
892 | | /* have a colon, could be: |
893 | | * user@pass:host |
894 | | * user@pass:host:port |
895 | | * [fe80::] |
896 | | * [fe80::]:port |
897 | | * user:pass@[fe80::]:port |
898 | | * user:pass@[fe80::] |
899 | | */ |
900 | |
|
901 | 0 | if (NULL == at) { |
902 | | // no @, so no auth |
903 | | // host:port, [fe80::], [fe80::]:port |
904 | 0 | if (NULL == rsb || |
905 | 0 | NULL == lsb) { |
906 | | // allow one of lsb or rsb, could be in the password |
907 | | // host:port |
908 | 0 | auth = NULL; |
909 | 0 | host = dup; |
910 | 0 | *colon = '\0'; |
911 | 0 | port = colon + 1; |
912 | 0 | } else { |
913 | | // [fe80::], [fe80::]:port |
914 | 0 | auth = NULL; |
915 | 0 | host = dup + 1; |
916 | 0 | *rsb = '\0'; |
917 | 0 | if (rsb < colon) { |
918 | | // [fe80::]:port |
919 | 0 | *colon = '\0'; |
920 | 0 | port = colon + 1; |
921 | 0 | } else { |
922 | | // [fe80::] |
923 | 0 | port = NULL; |
924 | 0 | } |
925 | 0 | } |
926 | 0 | } else { |
927 | | // NULL != at, have @, so have auth |
928 | 0 | auth = dup; |
929 | 0 | if (colon < at) { |
930 | | // user:pass@host, can't be IPv6, can't have port |
931 | | // better not be a colon in the password! |
932 | 0 | *at = '\0'; |
933 | 0 | host = at + 1; |
934 | 0 | port = NULL; |
935 | 0 | } else { |
936 | | // colon > at |
937 | | // user:pass@host:port |
938 | | // user:pass@[fe80::1] |
939 | | // user:pass@[fe80::1]:2101 |
940 | 0 | *at = '\0'; |
941 | 0 | if (NULL == rsb || |
942 | 0 | NULL == lsb) { |
943 | | // user:pass@host:port |
944 | | // allow one of lsb or rsb, could be in the password |
945 | 0 | host = at + 1; |
946 | 0 | *colon = '\0'; |
947 | 0 | port = colon + 1; |
948 | 0 | } else { |
949 | | // have lsb and rsb |
950 | | // user:pass@[fe80::1] |
951 | | // user:pass@[fe80::1]:2101 |
952 | 0 | host = lsb + 1; |
953 | 0 | *rsb = '\0'; |
954 | 0 | if (rsb < colon) { |
955 | | // user:pass@[fe80::1]:2101 |
956 | 0 | port = rsb + 2; |
957 | 0 | } else { |
958 | | // user:pass@[fe80::1] |
959 | 0 | port = NULL; |
960 | 0 | } |
961 | 0 | } |
962 | 0 | } |
963 | 0 | } |
964 | 0 | } |
965 | 0 | if (NULL != auth) { |
966 | 0 | (void)strlcpy(stream->credentials, auth, sizeof(stream->credentials)); |
967 | 0 | } |
968 | |
|
969 | 0 | if (NULL == port || |
970 | 0 | '\0' == port[0]) { |
971 | 0 | port = "rtcm-sc104"; |
972 | 0 | if (NULL == getservbyname(port, "tcp")) { |
973 | | // Debian does not have rtcm-sc104 in /etc/services!! |
974 | 0 | port = DEFAULT_RTCM_PORT; |
975 | 0 | } |
976 | 0 | } |
977 | | // port ought to be non-NULL by now, but just in case appease Coverity. |
978 | 0 | if (NULL != port) { |
979 | 0 | (void)strlcpy(stream->port, port, sizeof(stream->port)); |
980 | 0 | } |
981 | | |
982 | | // host ought to be non-NULL by now, but just in case appease Coverity. |
983 | 0 | if (NULL != host) { |
984 | 0 | (void)strlcpy(stream->host, host, sizeof(stream->host)); |
985 | 0 | } |
986 | |
|
987 | 0 | GPSD_LOG(LOG_PROG, errout, |
988 | 0 | "NTRIP: ntrip_parse_url(%s) credentials %s host %s port %s " |
989 | 0 | "moutpoint %s\n", fullurl, |
990 | 0 | stream->credentials, |
991 | 0 | stream->host, |
992 | 0 | stream->port, |
993 | 0 | stream->mountpoint); |
994 | 0 | return 0; |
995 | 0 | } |
996 | | |
997 | | /* reopen a nonblocking connection to an NTRIP broadcaster |
998 | | * Need to already have the sourcetable from a successful ntrip_open() |
999 | | * |
1000 | | * Return: socket on success |
1001 | | * -1 on error |
1002 | | * PLACEHOLDING_FD (-2) on no connect |
1003 | | */ |
1004 | | static int ntrip_reconnect(struct gps_device_t *device) |
1005 | 0 | { |
1006 | 0 | #if defined(SOCK_NONBLOCK) |
1007 | 0 | socket_t dsock = -1; |
1008 | 0 | char addrbuf[50]; // INET6_ADDRSTRLEN |
1009 | |
|
1010 | 0 | GPSD_LOG(LOG_PROG, &device->context->errout, |
1011 | 0 | "NTRIP: ntrip_reconnect() %.60s\n", |
1012 | 0 | device->gpsdata.dev.path); |
1013 | 0 | dsock = netlib_connectsock1(AF_UNSPEC, device->ntrip.stream.host, |
1014 | 0 | device->ntrip.stream.port, |
1015 | 0 | "tcp", 1, false, |
1016 | 0 | addrbuf, sizeof(addrbuf)); |
1017 | 0 | device->gpsdata.gps_fd = dsock; |
1018 | | // nonblocking means we have the fd, but the connection is not |
1019 | | // finished yet. Connection may fail, later. |
1020 | 0 | if (0 > dsock) { |
1021 | | /* no way to recover from this, except wait and try again later |
1022 | | * cast for 32-bit ints. */ |
1023 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
1024 | 0 | "NTRIP: ntrip_reconnect(%s) IP %s, failed: %s(%ld)\n", |
1025 | 0 | device->gpsdata.dev.path, addrbuf, |
1026 | 0 | netlib_errstr(dsock), (long)dsock); |
1027 | | // set time for retry |
1028 | 0 | (void)clock_gettime(CLOCK_REALTIME, &device->ntrip.stream.stream_time); |
1029 | | // leave in connextion closed state for later retry. |
1030 | 0 | device->ntrip.conn_state = NTRIP_CONN_CLOSED; |
1031 | 0 | return PLACEHOLDING_FD; |
1032 | 0 | } |
1033 | | /* will have to wait for select() to confirm connection, then send |
1034 | | * the ntrip request again. |
1035 | | * cast for 32-bit ints */ |
1036 | 0 | device->ntrip.conn_state = NTRIP_CONN_INPROGRESS; |
1037 | 0 | GPSD_LOG(LOG_PROG, &device->context->errout, |
1038 | 0 | "NTRIP: ntrip_reconnect(%s) IP %s, fd %ld " |
1039 | 0 | "NTRIP_CONN_INPROGRESS \n", |
1040 | 0 | device->gpsdata.dev.path, addrbuf, (long)dsock); |
1041 | | #else // no SOCK_NONBLOCK |
1042 | | GPSD_LOG(LOG_PROG, &device->context->errout, |
1043 | | "NTRIP: ntrip_reconnect(%s) no SOCK_NONBLOCK, can't reconnect.\n", |
1044 | | device->gpsdata.dev.path); |
1045 | | device->gpsdata.gps_fd = -1; |
1046 | | #endif // no SOCK_NONBLOCK |
1047 | 0 | return (int)device->gpsdata.gps_fd; |
1048 | 0 | } |
1049 | | |
1050 | | /* open a connection to a NTRIP broadcaster |
1051 | | * orig contains full url |
1052 | | * |
1053 | | * Return: 0 on success, or the new fd |
1054 | | * less than zero on failure |
1055 | | */ |
1056 | | socket_t ntrip_open(struct gps_device_t *device, char *orig) |
1057 | 0 | { |
1058 | 0 | socket_t ret = -1; |
1059 | 0 | char buf[BUFSIZ]; |
1060 | 0 | char outbuf[BUFSIZ]; |
1061 | 0 | ssize_t blen; |
1062 | | |
1063 | | // cast for 32-bit ints |
1064 | 0 | GPSD_LOG(LOG_PROG, &device->context->errout, |
1065 | 0 | "NTRIP: ntrip_open(%s) fd %ld state = %s(%d)\n", |
1066 | 0 | orig, (long)device->gpsdata.gps_fd, |
1067 | 0 | ntrip_state(device->ntrip.conn_state), |
1068 | 0 | device->ntrip.conn_state); |
1069 | |
|
1070 | 0 | switch (device->ntrip.conn_state) { |
1071 | 0 | case NTRIP_CONN_INIT: // state = 0 |
1072 | | /* this has to be done here, |
1073 | | * because it is needed for multi-stage connection */ |
1074 | | // strlcpy() ensures dup is NUL terminated. |
1075 | 0 | device->servicetype = SERVICE_NTRIP; |
1076 | 0 | device->ntrip.works = false; |
1077 | 0 | device->ntrip.sourcetable_parse = false; |
1078 | 0 | device->ntrip.stream.set = false; |
1079 | 0 | device->gpsdata.gps_fd = PLACEHOLDING_FD; |
1080 | |
|
1081 | 0 | ret = ntrip_parse_url(&device->context->errout, &device->ntrip.stream, |
1082 | 0 | orig); |
1083 | 0 | if (0 > ret) { |
1084 | | // failed to parse url |
1085 | 0 | device->gpsdata.gps_fd = PLACEHOLDING_FD; |
1086 | 0 | device->ntrip.conn_state = NTRIP_CONN_ERR; |
1087 | 0 | return -1; |
1088 | 0 | } |
1089 | | |
1090 | 0 | ret = ntrip_stream_req_probe(&device->ntrip.stream, |
1091 | 0 | &device->context->errout); |
1092 | | // cast for 32-bit intptr_t |
1093 | 0 | GPSD_LOG(LOG_PROG, &device->context->errout, |
1094 | 0 | "NTRIP: ntrip_stream_req_probe(%s) ret %ld\n", |
1095 | 0 | device->ntrip.stream.url, (long)ret); |
1096 | 0 | if (-1 == ret) { |
1097 | 0 | device->gpsdata.gps_fd = PLACEHOLDING_FD; |
1098 | 0 | device->ntrip.conn_state = NTRIP_CONN_ERR; |
1099 | 0 | return -1; |
1100 | 0 | } |
1101 | | // set timeouts to give time for caster to reply. |
1102 | | // can't use device->lexer.pkt_time and gpsd_clear() reset it |
1103 | 0 | (void)clock_gettime(CLOCK_REALTIME, &device->ntrip.stream.stream_time); |
1104 | |
|
1105 | 0 | device->gpsdata.gps_fd = (gps_fd_t)ret; |
1106 | 0 | device->ntrip.conn_state = NTRIP_CONN_SENT_PROBE; |
1107 | 0 | return ret; |
1108 | 0 | case NTRIP_CONN_SENT_PROBE: // state = 1 |
1109 | 0 | ret = ntrip_sourcetable_parse(device); |
1110 | | // cast for 32-bit intptr_t |
1111 | 0 | GPSD_LOG(LOG_PROG, &device->context->errout, |
1112 | 0 | "NTRIP: ntrip_sourcetable_parse(%s) = %ld\n", |
1113 | 0 | device->ntrip.stream.mountpoint, (long)ret); |
1114 | 0 | if (0 > ret) { |
1115 | 0 | device->ntrip.conn_state = NTRIP_CONN_ERR; |
1116 | 0 | return -1; |
1117 | 0 | } |
1118 | 0 | if (0 == ret && |
1119 | 0 | false == device->ntrip.stream.set) { |
1120 | 0 | return ret; |
1121 | 0 | } |
1122 | 0 | if (NULL != device->gpsdata.update_fd) { |
1123 | 0 | device->gpsdata.update_fd(device->gpsdata.gps_fd, false); |
1124 | 0 | } |
1125 | 0 | (void)close(device->gpsdata.gps_fd); |
1126 | 0 | device->gpsdata.gps_fd = PLACEHOLDING_FD; |
1127 | 0 | GPSD_LOG(LOG_PROG, &device->context->errout, |
1128 | 0 | "NTRIP: found %s: %s: %d,%d,%f,%f,%d,%d,%d,%d,%d\n", |
1129 | 0 | device->ntrip.stream.url, |
1130 | 0 | device->ntrip.stream.mountpoint, |
1131 | 0 | device->ntrip.stream.format, |
1132 | 0 | device->ntrip.stream.carrier, |
1133 | 0 | device->ntrip.stream.latitude, |
1134 | 0 | device->ntrip.stream.longitude, |
1135 | 0 | device->ntrip.stream.nmea, |
1136 | 0 | device->ntrip.stream.compr_encryp, |
1137 | 0 | device->ntrip.stream.authentication, |
1138 | 0 | device->ntrip.stream.fee, |
1139 | 0 | device->ntrip.stream.bitrate); |
1140 | 0 | if (0 != ntrip_auth_encode(&device->ntrip.stream)) { |
1141 | 0 | device->ntrip.conn_state = NTRIP_CONN_ERR; |
1142 | 0 | return -1; |
1143 | 0 | } |
1144 | 0 | ret = ntrip_stream_get_req(&device->ntrip.stream, |
1145 | 0 | &device->context->errout); |
1146 | 0 | if (-1 == ret) { |
1147 | 0 | device->gpsdata.gps_fd = PLACEHOLDING_FD; |
1148 | 0 | device->ntrip.conn_state = NTRIP_CONN_ERR; |
1149 | 0 | return -1; |
1150 | 0 | } |
1151 | 0 | device->gpsdata.gps_fd = ret; |
1152 | 0 | if (NULL != device->gpsdata.update_fd) { |
1153 | 0 | device->gpsdata.update_fd(device->gpsdata.gps_fd, true); |
1154 | 0 | } |
1155 | 0 | device->ntrip.conn_state = NTRIP_CONN_SENT_GET; |
1156 | 0 | break; |
1157 | 0 | case NTRIP_CONN_SENT_GET: // state = 2 |
1158 | 0 | ret = ntrip_stream_get_parse(device); |
1159 | 0 | if (-1 == ret) { |
1160 | 0 | (void)close(device->gpsdata.gps_fd); |
1161 | 0 | device->gpsdata.gps_fd = PLACEHOLDING_FD; |
1162 | 0 | device->ntrip.conn_state = NTRIP_CONN_ERR; |
1163 | 0 | return -1; |
1164 | 0 | } |
1165 | 0 | device->ntrip.conn_state = NTRIP_CONN_ESTABLISHED; |
1166 | 0 | device->ntrip.works = true; // we know, this worked. |
1167 | 0 | break; |
1168 | 0 | case NTRIP_CONN_CLOSED: // state = 5 |
1169 | 0 | if (6 > llabs((time(NULL) - device->ntrip.stream.stream_time.tv_sec))) { |
1170 | | // wait a bit longer |
1171 | 0 | ret = PLACEHOLDING_FD; |
1172 | 0 | break; |
1173 | 0 | } |
1174 | 0 | ret = ntrip_reconnect(device); |
1175 | 0 | if (0 <= ret && |
1176 | 0 | NULL != device->gpsdata.update_fd) { |
1177 | 0 | device->gpsdata.update_fd(ret, true); |
1178 | 0 | } |
1179 | 0 | break; |
1180 | 0 | case NTRIP_CONN_INPROGRESS: // state = 6 |
1181 | | // Need to send GET within about 40 seconds or caster times out. |
1182 | | // FIXME: partially duplicates ntrip_stream_get_req() |
1183 | | // try a write, it will fail if connection still in process, or failed. |
1184 | 0 | blen = snprintf(buf, sizeof(buf), |
1185 | 0 | "GET /%s HTTP/1.1\r\n" |
1186 | 0 | "Ntrip-Version: Ntrip/2.0\r\n" |
1187 | 0 | "User-Agent: NTRIP gpsd/%s\r\n" |
1188 | 0 | "Host: %s\r\n" |
1189 | 0 | "Accept: rtk/rtcm, dgps/rtcm\r\n" |
1190 | 0 | "%s" |
1191 | 0 | "Connection: close\r\n" |
1192 | 0 | "\r\n", device->ntrip.stream.mountpoint, VERSION, |
1193 | 0 | device->ntrip.stream.host, |
1194 | 0 | device->ntrip.stream.authStr); |
1195 | |
|
1196 | 0 | if (1 > blen) { |
1197 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
1198 | 0 | "NTRIP: ntrip_open() snprintf fail<\n"); |
1199 | 0 | return -1; |
1200 | 0 | } |
1201 | | |
1202 | 0 | GPSD_LOG(LOG_IO, &device->context->errout, |
1203 | 0 | "NTRIP: ntrip_open() sending >%s<\n", |
1204 | 0 | gps_visibilize(outbuf, sizeof(outbuf), buf, blen)); |
1205 | |
|
1206 | 0 | if (blen != write(device->gpsdata.gps_fd, buf, blen)) { |
1207 | | // cast for 32-bit intptr_t |
1208 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
1209 | 0 | "NTRIP: stream write error %s(%d) on fd %ld during " |
1210 | 0 | "get request\n", |
1211 | 0 | strerror(errno), errno, (long)device->gpsdata.gps_fd); |
1212 | 0 | device->ntrip.conn_state = NTRIP_CONN_ERR; |
1213 | | // leave FD so deactivate_device() can remove from the |
1214 | | // select() loop |
1215 | 0 | } else { |
1216 | 0 | GPSD_LOG(LOG_ERROR, &device->context->errout, |
1217 | 0 | "NTRIP: stream write success get request\n"); |
1218 | 0 | device->ntrip.conn_state = NTRIP_CONN_SENT_GET; |
1219 | 0 | } |
1220 | 0 | ret = (int)device->gpsdata.gps_fd; |
1221 | 0 | break; |
1222 | 0 | case NTRIP_CONN_ESTABLISHED: // state = 3 |
1223 | 0 | FALLTHROUGH |
1224 | 0 | case NTRIP_CONN_ERR: // state = 4 |
1225 | 0 | return -1; |
1226 | 0 | } |
1227 | 0 | return ret; |
1228 | 0 | } |
1229 | | |
1230 | | // may be time to ship a GGA report to the NTRIP caster |
1231 | | void ntrip_report(struct gps_context_t *context, |
1232 | | struct gps_device_t *gps, |
1233 | | struct gps_device_t *caster) |
1234 | 0 | { |
1235 | 0 | static int count = 0; |
1236 | 0 | char buf[BUFSIZ]; |
1237 | 0 | ssize_t blen; |
1238 | |
|
1239 | 0 | if (0 == caster->ntrip.stream.nmea) { |
1240 | 0 | return; // no need to be here... |
1241 | 0 | } |
1242 | | // cast for 32-bit intptr_t |
1243 | 0 | GPSD_LOG(LOG_IO, &context->errout, |
1244 | 0 | "NTRIP: = ntrip_report() fixcnt %d count %d caster %ld\n", |
1245 | 0 | context->fixcnt, count, (long)caster->gpsdata.gps_fd); |
1246 | | |
1247 | | /* 10 is an arbitrary number, the point is to have gotten several good |
1248 | | * fixes before reporting usage to our NTRIP caster. |
1249 | | */ |
1250 | 0 | if (10 > context->fixcnt) { |
1251 | 0 | return; // no good fix to send... |
1252 | 0 | } |
1253 | | |
1254 | | /* count % 5 is as arbitrary a number as the fix dump delay. |
1255 | | * But some delay * was needed here |
1256 | | */ |
1257 | 0 | count++; |
1258 | 0 | if (0 != (count % 5)) { |
1259 | 0 | return; // wait some more |
1260 | 0 | } |
1261 | 0 | if (0 > caster->gpsdata.gps_fd) { |
1262 | 0 | return; // huh? No NTRIP fd to write to?? |
1263 | 0 | } |
1264 | | |
1265 | 0 | blen = gpsd_position_fix_dump(gps, buf, sizeof(buf)); |
1266 | 0 | if (0 < blen) { |
1267 | 0 | ssize_t ret; |
1268 | |
|
1269 | 0 | ret = write(caster->gpsdata.gps_fd, buf, blen); |
1270 | 0 | if (blen == ret) { |
1271 | 0 | GPSD_LOG(LOG_IO, &context->errout, "NTRIP: => caster %s\n", |
1272 | 0 | buf); |
1273 | 0 | } else if (0 > ret) { |
1274 | | // cast for 32-bit intptr_t |
1275 | 0 | GPSD_LOG(LOG_ERROR, &context->errout, |
1276 | 0 | "NTRIP: ntrip_report() write(%ld) error %s(%d)\n", |
1277 | 0 | (long)caster->gpsdata.gps_fd, strerror(errno), errno); |
1278 | 0 | } else { |
1279 | | // cast for 32-bit intptr_t |
1280 | 0 | GPSD_LOG(LOG_ERROR, &context->errout, |
1281 | 0 | "NTRIP: ntrip_report() short write(%ld) = %zd\n", |
1282 | 0 | (long)caster->gpsdata.gps_fd, ret); |
1283 | 0 | } |
1284 | 0 | } |
1285 | 0 | } |
1286 | | |
1287 | | // Close an ntrip connection |
1288 | | void ntrip_close(struct gps_device_t *session) |
1289 | 0 | { |
1290 | 0 | if (0 > session->gpsdata.gps_fd) { |
1291 | | // UNALLOCATED_FD (-1) or PLACEHOLDING_FD (-2). Nothing to do. |
1292 | | // cast for 32-bit intptr_t |
1293 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1294 | 0 | "NTRIP: ntrip_close(%s), close(%ld) bad fd\n", |
1295 | 0 | session->gpsdata.dev.path, (long)session->gpsdata.gps_fd); |
1296 | 0 | session->gpsdata.gps_fd = PLACEHOLDING_FD; |
1297 | 0 | return; |
1298 | 0 | } |
1299 | | |
1300 | 0 | if (-1 == close(session->gpsdata.gps_fd)) { |
1301 | | // cast for 32-bit intptr_t |
1302 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1303 | 0 | "NTRIP: ntrip_close(%s), close(%ld), %s(%d)\n", |
1304 | 0 | session->gpsdata.dev.path, |
1305 | 0 | (long)session->gpsdata.gps_fd, strerror(errno), errno); |
1306 | 0 | } else { |
1307 | | // cast for 32-bit intptr_t |
1308 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
1309 | 0 | "NTRIP: ntrip_close(%s), close(%ld)\n", |
1310 | 0 | session->gpsdata.dev.path, |
1311 | 0 | (long)session->gpsdata.gps_fd); |
1312 | 0 | } |
1313 | | // Prepare for a retry, don't use opentime as that gets reset elsewhere |
1314 | 0 | (void)clock_gettime(CLOCK_REALTIME, &session->ntrip.stream.stream_time); |
1315 | |
|
1316 | 0 | session->gpsdata.gps_fd = PLACEHOLDING_FD; |
1317 | 0 | session->ntrip.conn_state = NTRIP_CONN_CLOSED; |
1318 | 0 | } |
1319 | | // vim: set expandtab shiftwidth=4 |