Coverage Report

Created: 2025-07-18 06:25

/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