Coverage Report

Created: 2025-12-14 06:42

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