Coverage Report

Created: 2026-08-31 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpsd/gpsd-3.27.6~dev/drivers/driver_zodiac.c
Line
Count
Source
1
/*
2
 * Handle the Rockwell binary packet format supported by the old Zodiac chipset
3
 *
4
 * Week counters are not limited to 10 bits. It's unknown what
5
 * the firmware is doing to disambiguate them, if anything; it might just
6
 * be adding a fixed offset based on a hidden epoch value, in which case
7
 * unhappy things will occur on the next rollover.
8
 *
9
 * This file is Copyright 2010 by the GPSD project
10
 * SPDX-License-Identifier: BSD-2-clause
11
 */
12
13
#include "../include/gpsd_config.h"  // must be before all includes
14
15
#include <math.h>
16
#include <stdbool.h>
17
#include <stdio.h>
18
#include <string.h>
19
#include <unistd.h>
20
21
#include "../include/gpsd.h"
22
#include "../include/bits.h"
23
#include "../include/strfuncs.h"
24
25
#define getstringz(to, from, s, e)                      \
26
0
    (void)memcpy(to, from+2*(s)-2, 2*((e)-(s)+1))
27
28
#ifdef ZODIAC_ENABLE
29
struct header
30
{
31
    unsigned short sync;
32
    unsigned short id;
33
    unsigned short ndata;
34
    unsigned short flags;
35
    unsigned short csum;
36
};
37
38
static unsigned short zodiac_checksum(unsigned short *w, int n)
39
0
{
40
0
    unsigned short csum = 0;
41
42
0
    while (n-- > 0)
43
0
        csum += *(w++);
44
0
    return -csum;
45
0
}
46
47
// write an array of shorts in little-endian format
48
static ssize_t end_write(int fd, void *d, size_t len)
49
0
{
50
0
    unsigned char buf[BUFSIZ];
51
0
    short *data = (short *)d;
52
53
0
    size_t n;
54
0
    for (n = 0; n < (size_t)(len/2); n++)
55
0
        putle16(buf, n*2, data[n]);
56
0
    return write(fd, (char*)buf, len);
57
0
}
58
59
/* zodiac_spew - Takes a message type, an array of data words, and a length
60
 * for the array, and prepends a 5 word header (including checksum).
61
 * The data words are expected to be checksummed.
62
 */
63
static ssize_t zodiac_spew(struct gps_device_t *session, unsigned short type,
64
                           unsigned short *dat, int dlen)
65
0
{
66
0
    struct header h;
67
0
    int i;
68
0
    char buf[BUFSIZ];
69
70
0
    h.sync = 0x81ff;
71
0
    h.id = (unsigned short)type;
72
0
    h.ndata = (unsigned short)(dlen - 1);
73
0
    h.flags = 0;
74
0
    h.csum = zodiac_checksum((unsigned short *)&h, 4);
75
76
0
    if (!BAD_SOCKET(session->gpsdata.gps_fd)) {
77
0
        size_t hlen, datlen;
78
0
        hlen = sizeof(h);
79
0
        datlen = sizeof(unsigned short) * dlen;
80
0
        if (end_write(session->gpsdata.gps_fd, &h, hlen) != (ssize_t) hlen ||
81
0
            end_write(session->gpsdata.gps_fd, dat,
82
0
                      datlen) != (ssize_t) datlen) {
83
0
            GPSD_LOG(LOG_INFO, &session->context->errout,
84
0
                     "ZODIAC: Reconfigure write failed\n");
85
0
            return -1;
86
0
        }
87
0
    }
88
89
0
    (void)snprintf(buf, sizeof(buf),
90
0
                   "%04x %04x %04x %04x %04x",
91
0
                   h.sync, h.id, h.ndata, h.flags, h.csum);
92
0
    for (i = 0; i < dlen; i++)
93
0
        str_appendf(buf, sizeof(buf), " %04x", dat[i]);
94
95
0
    GPSD_LOG(LOG_RAW, &session->context->errout,
96
0
             "ZODIAC: Sent Zodiac packet: %s\n", buf);
97
98
0
    return 0;
99
0
}
100
101
static void send_rtcm(struct gps_device_t *session,
102
                      const char *rtcmbuf, size_t rtcmbytes)
103
0
{
104
0
    unsigned short data[34];
105
0
    int n = 1 + (int)(rtcmbytes / 2 + rtcmbytes % 2);
106
107
0
    if (session->driver.zodiac.sn++ > 32767)
108
0
        session->driver.zodiac.sn = 0;
109
110
0
    memset(data, 0, sizeof(data));
111
0
    data[0] = session->driver.zodiac.sn;        // sequence number
112
0
    memcpy(&data[1], rtcmbuf, rtcmbytes);
113
0
    data[n] = zodiac_checksum(data, n);
114
115
0
    (void)zodiac_spew(session, 1351, data, n + 1);
116
0
}
117
118
static ssize_t zodiac_send_rtcm(struct gps_device_t *session,
119
                                const char *rtcmbuf, size_t rtcmbytes)
120
0
{
121
0
    while (rtcmbytes > 0) {
122
0
        size_t len = (size_t) (rtcmbytes > 64 ? 64 : rtcmbytes);
123
0
        send_rtcm(session, rtcmbuf, len);
124
0
        rtcmbytes -= len;
125
0
        rtcmbuf += len;
126
0
    }
127
0
    return 1;
128
0
}
129
130
// Zodiac protocol description uses 1-origin indexing by little-endian word
131
// Why call get*() macros?  Because they are well tested.
132
0
#define getzword(n)      (getles16(session->lexer.outbuffer, 2 * (n) - 2))
133
235
#define getzu16(n)       (getleu16(session->lexer.outbuffer, 2 * (n) - 2))
134
0
#define getzlong(n)      (getles32(session->lexer.outbuffer, 2 * (n) - 2))
135
0
#define getzu32(n)       (getleu32(session->lexer.outbuffer, 2 * (n) - 2))
136
137
// time-position-velocity report
138
static gps_mask_t handle1000(struct gps_device_t *session)
139
0
{
140
0
    gps_mask_t mask;
141
0
    struct tm unpacked_date = {0};
142
0
    int datum;
143
0
    char ts_buf[TIMESPEC_LEN];
144
145
    // ticks                      = getzlong(6);
146
    // sequence                   = getzword(8);
147
    // measurement_sequence       = getzword(9);
148
0
    session->newdata.status = (getzword(10) & 0x1c) ? 0 : 1;
149
0
    if (0 != session->newdata.status) {
150
0
        session->newdata.mode = (getzword(10) & 1) ? MODE_2D : MODE_3D;
151
0
    } else {
152
0
        session->newdata.mode = MODE_NO_FIX;
153
0
    }
154
155
    // solution_type                 = getzword(11);
156
0
    session->gpsdata.satellites_used = (int)getzword(12);
157
    // polar_navigation              = getzword(13);
158
0
    session->context->gps_week = (unsigned short)getzword(14);
159
    // gps_seconds                   = getzlong(15);
160
    // gps_nanoseconds               = getzlong(17);
161
0
    unpacked_date.tm_mday = (int)getzword(19);
162
0
    unpacked_date.tm_mon = (int)getzword(20) - 1;
163
0
    unpacked_date.tm_year = (int)getzword(21) - 1900;
164
0
    unpacked_date.tm_hour = (int)getzword(22);
165
0
    unpacked_date.tm_min = (int)getzword(23);
166
0
    unpacked_date.tm_sec = (int)getzword(24);
167
0
    session->newdata.time.tv_sec = mkgmtime(&unpacked_date);
168
0
    session->newdata.time.tv_nsec = getzu32(25);
169
0
    session->newdata.latitude = ((long)getzlong(27)) * RAD_2_DEG * 1e-8;
170
0
    session->newdata.longitude = ((long)getzlong(29)) * RAD_2_DEG * 1e-8;
171
    /*
172
     * The Rockwell Jupiter TU30-D140 reports altitude as uncorrected height
173
     * above WGS84 geoid.  The Zodiac binary protocol manual does not
174
     * specify whether word 31 is geodetic or WGS 84.
175
     * Here we assume altitude is always wgs84.
176
     */
177
0
    session->newdata.altHAE = ((long)getzlong(31)) * 1e-2;
178
0
    session->newdata.geoid_sep = ((short)getzword(33)) * 1e-2;
179
0
    session->newdata.speed = (int)getzlong(34) * 1e-2;
180
0
    session->newdata.track = (int)getzword(36) * RAD_2_DEG * 1e-3;
181
0
    session->newdata.magnetic_var = ((short)getzword(37)) * RAD_2_DEG * 1e-4;
182
0
    session->newdata.climb = ((short)getzword(38)) * 1e-2;
183
0
    datum = getzword(39);
184
0
    datum_code_string(datum, session->newdata.datum,
185
0
                      sizeof(session->newdata.datum));
186
    /*
187
     * The manual says these are 1-sigma.  Device reports only eph, circular
188
     * error.  Let gpsd_model_error() do the rest
189
     */
190
0
    session->newdata.eph = (int)getzlong(40) * 1e-2 * GPSD_CONFIDENCE;
191
0
    session->newdata.epv = (int)getzlong(42) * 1e-2 * GPSD_CONFIDENCE;
192
0
    session->newdata.ept = (int)getzlong(44) * 1e-2 * GPSD_CONFIDENCE;
193
0
    session->newdata.eps = (int)getzword(46) * 1e-2 * GPSD_CONFIDENCE;
194
    // clock_bias                  = (int)getzlong(47) * 1e-2;
195
    // clock_bias_sd               = (int)getzlong(49) * 1e-2;
196
    // clock_drift                 = (int)getzlong(51) * 1e-2;
197
    // clock_drift_sd              = (int)getzlong(53) * 1e-2;
198
199
0
    mask = TIME_SET | NTPTIME_IS | LATLON_SET | ALTITUDE_SET | CLIMB_SET |
200
0
           SPEED_SET | TRACK_SET | STATUS_SET | MODE_SET |
201
0
           HERR_SET | SPEEDERR_SET | VERR_SET;
202
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
203
0
             "ZODIAC: 1000: time=%s lat=%.2f lon=%.2f altHAE=%.2f track=%.2f "
204
0
             "speed=%.2f climb=%.2f mode=%d status=%d\n",
205
0
             timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
206
0
             session->newdata.latitude,
207
0
             session->newdata.longitude, session->newdata.altHAE,
208
0
             session->newdata.track, session->newdata.speed,
209
0
             session->newdata.climb, session->newdata.mode,
210
0
             session->newdata.status);
211
0
    return mask | CLEAR_IS | REPORT_IS;
212
0
}
213
214
// Message 1002: Channel Summary Message
215
static gps_mask_t handle1002(struct gps_device_t *session)
216
0
{
217
0
    unsigned i;
218
0
    timespec_t ts_tow;
219
220
    // ticks                      = getzlong(6);
221
    // sequence                   = getzword(8);
222
    // measurement_sequence       = getzword(9);
223
0
    unsigned short gps_week = getzu16(10);
224
0
    time_t gps_seconds = (time_t)getzu32(11);
225
0
    unsigned long gps_nanoseconds = getzu32(13);
226
0
    char ts_buf[TIMESPEC_LEN];
227
228
    // Note: this week counter is not limited to 10 bits.
229
0
    session->context->gps_week = gps_week;
230
0
    session->gpsdata.satellites_used = 0;
231
0
    for (i = 0; i < ZODIAC_CHANNELS; i++) {
232
0
        int status, prn;
233
0
        session->driver.zodiac.Zv[i] = status = (int)getzword(15 + (3 * i));
234
0
        session->driver.zodiac.Zs[i] = prn = (int)getzword(16 + (3 * i));
235
236
0
        if (status & 1) {
237
0
            session->gpsdata.satellites_used++;
238
0
        }
239
240
0
        session->gpsdata.skyview[i].PRN = (short)prn;
241
0
        session->gpsdata.skyview[i].ss = (float)getzword(17 + (3 * i));
242
0
        session->gpsdata.skyview[i].used = (bool)(status & 1);
243
0
    }
244
0
    ts_tow.tv_sec = gps_seconds;
245
0
    ts_tow.tv_nsec = gps_nanoseconds;
246
0
    session->gpsdata.skyview_time = gpsd_gpstime_resolv(session, gps_week,
247
0
                                                        ts_tow);
248
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
249
0
             "ZODIAC: 1002: visible=%d used=%d mask={SATELLITE|USED} "
250
0
             "ime %s\n",
251
0
             session->gpsdata.satellites_visible,
252
0
             session->gpsdata.satellites_used,
253
0
             timespec_str(&session->gpsdata.skyview_time, ts_buf,
254
0
                          sizeof(ts_buf)));
255
0
    return SATELLITE_SET | USED_IS;
256
0
}
257
258
// skyview report
259
static gps_mask_t handle1003(struct gps_device_t *session)
260
0
{
261
0
    unsigned i, n;
262
0
    gps_mask_t mask = 0;
263
264
    /* The Polaris (and probably the DAGR) emit some strange variant of
265
     * this message which causes gpsd to crash filtering on impossible
266
     * number of satellites avoids this */
267
0
    n = getzword(14);
268
0
    if (ZODIAC_CHANNELS < n) {
269
0
        return 0;
270
0
    }
271
272
0
    gpsd_zero_satellites(&session->gpsdata);
273
274
    // ticks              = getzlong(6);
275
    // sequence           = getzword(8);
276
0
    session->gpsdata.dop.gdop = (unsigned int)getzword(9) * 1e-2;
277
0
    session->gpsdata.dop.pdop = (unsigned int)getzword(10) * 1e-2;
278
0
    session->gpsdata.dop.hdop = (unsigned int)getzword(11) * 1e-2;
279
0
    session->gpsdata.dop.vdop = (unsigned int)getzword(12) * 1e-2;
280
0
    session->gpsdata.dop.tdop = (unsigned int)getzword(13) * 1e-2;
281
0
    mask |= DOP_SET;
282
0
    session->gpsdata.satellites_visible = n;
283
284
0
    for (i = 0; i < ZODIAC_CHANNELS; i++) {
285
0
        if (i < session->gpsdata.satellites_visible) {
286
0
            session->gpsdata.skyview[i].PRN = (short)getzword(15 + (3 * i));
287
0
            session->gpsdata.skyview[i].azimuth =
288
0
                (((double)getzword(16 + (3 * i))) * RAD_2_DEG * 1e-4);
289
0
            if (session->gpsdata.skyview[i].azimuth < 0)
290
0
                session->gpsdata.skyview[i].azimuth += 360;
291
0
            session->gpsdata.skyview[i].elevation =
292
0
                (((double)getzword(17 + (3 * i))) * RAD_2_DEG * 1e-4);
293
0
        } else {
294
0
            session->gpsdata.skyview[i].PRN = 0;
295
0
            session->gpsdata.skyview[i].azimuth = NAN;
296
0
            session->gpsdata.skyview[i].elevation = NAN;
297
0
            session->gpsdata.skyview[i].ss = NAN;
298
0
        }
299
0
    }
300
0
    session->gpsdata.skyview_time.tv_sec = 0;
301
0
    session->gpsdata.skyview_time.tv_nsec = 0;
302
0
    mask |= SATELLITE_SET;
303
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
304
0
             "ZODIAC: NAVDOP: visible=%d gdop=%.2f pdop=%.2f "
305
0
             "hdop=%.2f vdop=%.2f tdop=%.2f mask={SATELLITE|DOP}\n",
306
0
             session->gpsdata.satellites_visible,
307
0
             session->gpsdata.dop.gdop,
308
0
             session->gpsdata.dop.hdop,
309
0
             session->gpsdata.dop.vdop,
310
0
             session->gpsdata.dop.pdop, session->gpsdata.dop.tdop);
311
0
    return mask;
312
0
}
313
314
// fix quality report
315
static gps_mask_t handle1005(struct gps_device_t *session UNUSED)
316
0
{
317
    // ticks              = getzlong(6);
318
    // sequence           = getzword(8);
319
0
    int numcorrections = (int)getzword(12);
320
321
0
    if (MODE_NO_FIX == session->newdata.mode) {
322
0
        session->newdata.status = STATUS_UNK;
323
0
    } else if (0 == numcorrections) {
324
0
        session->newdata.status = STATUS_GPS;
325
0
    } else {
326
0
        session->newdata.status = STATUS_DGPS;
327
0
    }
328
329
0
    return 0;
330
0
}
331
332
// version report
333
static gps_mask_t handle1011(struct gps_device_t *session)
334
0
{
335
    /*
336
     * This is UNTESTED -- but harmless if buggy.  Added to support
337
     * client querying of the ID with firmware version in 2006.
338
     * The Zodiac is supposed to send one of these messages on startup.
339
     */
340
    // software version field
341
0
    getstringz(session->subtype, session->lexer.outbuffer, 19, 28);
342
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
343
0
             "ZODIAC: 1011: subtype=%s mask={DEVICEID}\n",
344
0
             session->subtype);
345
0
    return DEVICEID_SET;
346
0
}
347
348
// leap-second correction report
349
static gps_mask_t handle1108(struct gps_device_t *session)
350
0
{
351
    // ticks              = getzlong(6);
352
    // sequence           = getzword(8);
353
    // utc_week_seconds   = getzlong(14);
354
    // leap_nanoseconds   = getzlong(17);
355
0
    if ((getzword(19) & 3) == 3) {
356
0
        session->context->valid |= LEAP_SECOND_VALID;
357
0
        session->context->leap_seconds = (int)getzword(16);
358
0
    }
359
0
    return 0;
360
0
}
361
362
static gps_mask_t zodiac_analyze(struct gps_device_t *session)
363
235
{
364
235
    unsigned bad_len = 0;
365
235
    gps_mask_t mask = 0;
366
235
    unsigned id = 0;
367
368
235
    if (10 > session->lexer.outbuflen) {
369
0
        return 0;
370
0
    }
371
372
235
    id = getzu16(2);
373
374
235
    GPSD_LOG(LOG_PROG, &session->context->errout,
375
235
             "ZODIAC: %u: length %zd\n",
376
235
             id, session->lexer.outbuflen);
377
235
    GPSD_LOG(LOG_RAW, &session->context->errout,
378
235
             "ZODIAC: Raw Zodiac packet type %d length %zd: %s\n",
379
235
             id, session->lexer.outbuflen, gpsd_prettydump(session));
380
381
382
    /*
383
     * Normal cycle for these devices is 1001 1002.
384
     * We count 1001 as end of cycle because 1002 doesn't
385
     * carry fix information.
386
     */
387
235
    session->cycle_end_reliable = true;
388
389
235
    switch (id) {
390
0
    case 1000:
391
0
        if (110 > session->lexer.outbuflen) {
392
0
            bad_len = 110;
393
0
            break;
394
0
        }
395
0
        mask = handle1000(session);
396
0
        break;
397
0
    case 1002:
398
0
        if (102 > session->lexer.outbuflen) {
399
0
            bad_len = 102;
400
0
            break;
401
0
        }
402
0
        mask = handle1002(session);
403
0
        break;
404
0
    case 1003:
405
0
        if (102 > session->lexer.outbuflen) {
406
0
            bad_len = 102;
407
0
            break;
408
0
        }
409
0
        mask = handle1003(session);
410
0
        break;
411
0
    case 1005:
412
0
        if (50 > session->lexer.outbuflen) {
413
0
            bad_len = 50;
414
0
            break;
415
0
        }
416
0
        mask = handle1005(session);
417
0
        break;
418
0
    case 1011:
419
0
        if (118 > session->lexer.outbuflen) {
420
0
            bad_len = 118;
421
0
            break;
422
0
        }
423
0
        mask = handle1011(session);
424
0
        break;
425
0
    case 1108:
426
0
        if (40 > session->lexer.outbuflen) {
427
0
            bad_len = 40;
428
0
            break;
429
0
        }
430
0
        mask = handle1108(session);
431
0
        break;
432
235
    default:
433
235
        GPSD_LOG(LOG_WARN, &session->context->errout,
434
235
                 "ZODIAC: %u: unknwon message id, length %zd",
435
235
                 id, session->lexer.outbuflen);
436
235
        break;
437
235
    }
438
235
    if (bad_len) {
439
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
440
0
                 "ZODIAC: %u: runt payload len %u s/b %zd",
441
0
                 id, bad_len, session->lexer.outbuflen);
442
0
    }
443
235
    return mask;
444
235
}
445
446
static ssize_t zodiac_control_send(struct gps_device_t *session,
447
                                   char *msg, size_t len)
448
0
{
449
0
    unsigned short shortwords[256];
450
451
0
    if (sizeof(shortwords) <= len) {
452
0
        return -1;
453
0
    }
454
455
0
#define min(x,y)        ((x) < (y) ? x : y)
456
    /*
457
     * We used to just cast msg to an unsigned short pointer.
458
     * This can fail on word-oriented architectures like a SPARC.
459
     */
460
0
    memcpy((char *)shortwords, msg, min(sizeof(shortwords), len));
461
462
    // and if len isn't even, it's your own fault
463
0
    return zodiac_spew(session, shortwords[0], shortwords + 1,
464
0
                       (int)(len / 2 - 1));
465
0
}
466
467
static bool zodiac_speed_switch(struct gps_device_t *session,
468
                                speed_t speed, char parity, int stopbits)
469
0
{
470
0
    unsigned short data[15];
471
472
0
    if (32767 < session->driver.zodiac.sn++) {
473
0
        session->driver.zodiac.sn = 0;
474
0
    }
475
476
0
    switch (parity) {
477
0
    case 'E':
478
0
    case 2:
479
0
        parity = (char)2;
480
0
        break;
481
0
    case 'O':
482
0
    case 1:
483
0
        parity = (char)1;
484
0
        break;
485
0
    case 'N':
486
0
    case 0:
487
0
    default:
488
0
        parity = (char)0;
489
0
        break;
490
0
    }
491
492
0
    memset(data, 0, sizeof(data));
493
    // data is the part of the message starting at word 6
494
0
    data[0] = session->driver.zodiac.sn;        // sequence number
495
0
    data[1] = 1;                                // port 1 data valid
496
0
    data[2] = (unsigned short)parity;   // port 1 character width (8 bits)
497
    // port 1 stop bits (1 stopbit)
498
0
    data[3] = (unsigned short)(stopbits - 1);
499
0
    data[4] = 0;                // port 1 parity (none)
500
    // port 1 speed
501
0
    data[5] = (unsigned short)(round(log((double)speed / 300) / GPS_LN2) + 1);
502
0
    data[14] = zodiac_checksum(data, 14);
503
504
0
    (void)zodiac_spew(session, 1330, data, 15);
505
0
    return true;                // it would be nice to error-check this
506
0
}
507
508
static double zodiac_time_offset(struct gps_device_t *session UNUSED)
509
0
{
510
    /* Removing/changing the magic number below is likely to disturb
511
     * the handling of the 1pps signal from the gps device. The regression
512
     * tests and simple gps applications do not detect this. A live test
513
     * with the 1pps signal active is required. */
514
0
    return 1.1;
515
0
}
516
517
// this is everything we export
518
// *INDENT-OFF*
519
const struct gps_type_t driver_zodiac =
520
{
521
    .type_name      = "Zodiac",            // full name of type
522
    .packet_type    = ZODIAC_PACKET,       // associated lexer packet type
523
    .flags          = DRIVER_STICKY,       // no flags set
524
    .trigger        = NULL,                // no trigger
525
    .channels       = 12,                  // consumer-grade GPS
526
    .probe_detect   = NULL,                // no probe
527
    .get_packet     = packet_get1,         // use the generic packet getter
528
    .parse_packet   = zodiac_analyze,      // parse message packets
529
    .rtcm_writer    = zodiac_send_rtcm,    // send DGPS correction
530
    .init_query     = NULL,                // non-perturbing initial query
531
    .event_hook     = NULL,                // no configuration
532
    .speed_switcher = zodiac_speed_switch, // we can change baud rate
533
    .mode_switcher  = NULL,                // no mode switcher
534
    .rate_switcher  = NULL,                // no sample-rate switcher
535
    .min_cycle.tv_sec  = 1,                // not relevant, no rate switch
536
    .min_cycle.tv_nsec = 0,                // not relevant, no rate switch
537
    .control_send   = zodiac_control_send, // for gpsctl and friends
538
    .time_offset     = zodiac_time_offset, // compute NTO fudge factor
539
};
540
// *INDENT-ON*
541
542
#endif  // ZODIAC_ENABLE
543
544
// vim: set expandtab shiftwidth=4