1 /****************************************************************************
2 
3 NAME
4    gpsd_json.c - move data between in-core and JSON structures
5 
6 DESCRIPTION
7    These are functions (used only by the daemon) to dump the contents
8 of various core data structures in JSON.
9 
10 PERMISSIONS
11   Written by Eric S. Raymond, 2009
12   This file is Copyright (c) 2010-2019 by the GPSD project
13   SPDX-License-Identifier: BSD-2-clause
14 
15 ***************************************************************************/
16 
17 #include "gpsd_config.h"  /* must be before all includes */
18 
19 #include <assert.h>
20 #include <ctype.h>
21 #include <math.h>
22 #include <stdio.h>
23 #include <string.h>       /* for strcat(), strlcpy() */
24 
25 #include "gpsd.h"
26 #include "bits.h"
27 #include "strfuncs.h"
28 
29 #ifdef SOCKET_EXPORT_ENABLE
30 #include "gps_json.h"
31 #include "timespec.h"
32 #include "revision.h"
33 
34 /* *INDENT-OFF* */
35 #define JSON_BOOL(x)	((x)?"true":"false")
36 
37 /*
38  * Manifest names for the gnss_type enum - must be kept synced with it.
39  * Also, masks so we can tell what packet types correspond to each class.
40  */
41 /* the map of device class names */
42 struct classmap_t {
43     char	*name;
44     int		typemask;
45     int		packetmask;
46 };
47 #define CLASSMAP_NITEMS	5
48 
49 struct classmap_t classmap[CLASSMAP_NITEMS] = {
50     /* name	typemask	packetmask */
51     {"ANY",	0,       	0},
52     {"GPS",	SEEN_GPS, 	GPS_TYPEMASK},
53     {"RTCM2",	SEEN_RTCM2,	PACKET_TYPEMASK(RTCM2_PACKET)},
54     {"RTCM3",	SEEN_RTCM3,	PACKET_TYPEMASK(RTCM3_PACKET)},
55     {"AIS",	SEEN_AIS,  	PACKET_TYPEMASK(AIVDM_PACKET)},
56 };
57 /* *INDENT-ON* */
58 
json_stringify(char * to,size_t len,const char * from)59 char *json_stringify(char *to,
60 		     size_t len,
61 		     const char *from)
62 /* escape double quotes and control characters inside a JSON string */
63 {
64     const char *sp;
65     char *tp;
66 
67     tp = to;
68     /*
69      * The limit is len-6 here because we need to be leave room for
70      * each character to generate an up to 6-character Java-style
71      * escape
72      */
73     for (sp = from; *sp != '\0' && ((tp - to) < ((int)len - 6)); sp++) {
74 	if (!isascii((unsigned char) *sp) || iscntrl((unsigned char) *sp)) {
75 	    *tp++ = '\\';
76 	    switch (*sp) {
77 	    case '\b':
78 		*tp++ = 'b';
79 		break;
80 	    case '\f':
81 		*tp++ = 'f';
82 		break;
83 	    case '\n':
84 		*tp++ = 'n';
85 		break;
86 	    case '\r':
87 		*tp++ = 'r';
88 		break;
89 	    case '\t':
90 		*tp++ = 't';
91 		break;
92 	    default:
93 		/* ugh, we'd prefer a C-style escape here, but this is JSON */
94 		/* http://www.ietf.org/rfc/rfc4627.txt
95 		 * section 2.5, escape is \uXXXX */
96 		/* don't forget the NUL in the output count! */
97 		(void)snprintf(tp, 6, "u%04x", 0x00ff & (unsigned int)*sp);
98 		tp += strlen(tp);
99 	    }
100 	} else {
101 	    if (*sp == '"' || *sp == '\\')
102 		*tp++ = '\\';
103 	    *tp++ = *sp;
104 	}
105     }
106     *tp = '\0';
107 
108     return to;
109 }
110 
json_version_dump(char * reply,size_t replylen)111 void json_version_dump( char *reply, size_t replylen)
112 {
113     (void)snprintf(reply, replylen,
114 		   "{\"class\":\"VERSION\",\"release\":\"%s\",\"rev\":\"%s\","
115                    "\"proto_major\":%d,\"proto_minor\":%d}\r\n",
116 		   VERSION, REVISION,
117 		   GPSD_PROTO_MAJOR_VERSION, GPSD_PROTO_MINOR_VERSION);
118 }
119 
json_tpv_dump(const struct gps_device_t * session,const struct gps_policy_t * policy,char * reply,size_t replylen)120 void json_tpv_dump(const struct gps_device_t *session,
121 		   const struct gps_policy_t *policy,
122 		   char *reply, size_t replylen)
123 {
124     const struct gps_data_t *gpsdata = &session->gpsdata;
125 
126     assert(replylen > sizeof(char *));
127     (void)strlcpy(reply, "{\"class\":\"TPV\",", replylen);
128     if (gpsdata->dev.path[0] != '\0')
129 	/* Note: Assumes /dev paths are always plain ASCII */
130 	str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
131     if (STATUS_DGPS_FIX <= gpsdata->status) {
132         /* to save rebuilding all the regressions, skip NO_FIX and FIX */
133 	str_appendf(reply, replylen, "\"status\":%d,", gpsdata->status);
134     }
135     str_appendf(reply, replylen, "\"mode\":%d,", gpsdata->fix.mode);
136     if (0 < gpsdata->fix.time.tv_sec) {
137 	char tbuf[JSON_DATE_MAX+1];
138 	str_appendf(reply, replylen,
139 		       "\"time\":\"%s\",",
140 		       timespec_to_iso8601(gpsdata->fix.time,
141                                       tbuf, sizeof(tbuf)));
142     }
143     if (LEAP_SECOND_VALID == (session->context->valid & LEAP_SECOND_VALID)) {
144 	str_appendf(reply, replylen, "\"leapseconds\":%d,",
145                     session->context->leap_seconds);
146     }
147     if (isfinite(gpsdata->fix.ept) != 0)
148 	str_appendf(reply, replylen, "\"ept\":%.3f,", gpsdata->fix.ept);
149     /*
150      * Suppressing TPV fields that would be invalid because the fix
151      * quality doesn't support them is nice for cutting down on the
152      * volume of meaningless output, but the real reason to do it is
153      * that we've observed that geodetic fix computation is unstable
154      * in a way that tends to change low-order digits in invalid
155      * fixes. Dumping these tends to cause cross-architecture failures
156      * in the regression tests.  This effect has been seen on SiRF-II
157      * chips, which are quite common.
158      */
159     if (gpsdata->fix.mode >= MODE_2D) {
160         double altitude = NAN;
161 
162 	if (isfinite(gpsdata->fix.latitude) != 0)
163 	    str_appendf(reply, replylen,
164 			   "\"lat\":%.9f,", gpsdata->fix.latitude);
165 	if (isfinite(gpsdata->fix.longitude) != 0)
166 	    str_appendf(reply, replylen,
167 			   "\"lon\":%.9f,", gpsdata->fix.longitude);
168 	if (0 != isfinite(gpsdata->fix.altHAE)) {
169 	    altitude = gpsdata->fix.altHAE;
170 	    str_appendf(reply, replylen,
171 			   "\"altHAE\":%.3f,", gpsdata->fix.altHAE);
172         }
173 	if (0 != isfinite(gpsdata->fix.altMSL)) {
174 	    altitude = gpsdata->fix.altMSL;
175 	    str_appendf(reply, replylen,
176 			   "\"altMSL\":%.3f,", gpsdata->fix.altMSL);
177         }
178 	if (0 != isfinite(altitude)) {
179             // DEPRECATED, undefined
180 	    str_appendf(reply, replylen,
181 			   "\"alt\":%.3f,", altitude);
182         }
183 
184 	if (isfinite(gpsdata->fix.epx) != 0)
185 	    str_appendf(reply, replylen, "\"epx\":%.3f,", gpsdata->fix.epx);
186 	if (isfinite(gpsdata->fix.epy) != 0)
187 	    str_appendf(reply, replylen, "\"epy\":%.3f,", gpsdata->fix.epy);
188 	if (isfinite(gpsdata->fix.epv) != 0)
189 	    str_appendf(reply, replylen, "\"epv\":%.3f,", gpsdata->fix.epv);
190 	if (isfinite(gpsdata->fix.track) != 0)
191 	    str_appendf(reply, replylen, "\"track\":%.4f,", gpsdata->fix.track);
192 	if (0 != isfinite(gpsdata->fix.magnetic_track))
193 		str_appendf(reply, replylen, "\"magtrack\":%.4f,",
194                             gpsdata->fix.magnetic_track);
195 	if (0 != isfinite(gpsdata->fix.magnetic_var))
196 		str_appendf(reply, replylen, "\"magvar\":%.1f,",
197                             gpsdata->fix.magnetic_var);
198 	if (isfinite(gpsdata->fix.speed) != 0)
199 	    str_appendf(reply, replylen, "\"speed\":%.3f,", gpsdata->fix.speed);
200 	if ((gpsdata->fix.mode >= MODE_3D) && isfinite(gpsdata->fix.climb) != 0)
201 	    str_appendf(reply, replylen, "\"climb\":%.3f,", gpsdata->fix.climb);
202 	if (isfinite(gpsdata->fix.epd) != 0)
203 	    str_appendf(reply, replylen, "\"epd\":%.4f,", gpsdata->fix.epd);
204 	if (isfinite(gpsdata->fix.eps) != 0)
205 	    str_appendf(reply, replylen, "\"eps\":%.2f,", gpsdata->fix.eps);
206 	if (gpsdata->fix.mode >= MODE_3D) {
207             if (isfinite(gpsdata->fix.epc) != 0)
208 		str_appendf(reply, replylen, "\"epc\":%.2f,", gpsdata->fix.epc);
209 	    /* ECEF is in meters, so %.3f is millimeter resolution */
210 	    if (0 != isfinite(gpsdata->fix.ecef.x))
211 		str_appendf(reply, replylen, "\"ecefx\":%.2f,",
212 			    gpsdata->fix.ecef.x);
213 	    if (0 != isfinite(gpsdata->fix.ecef.y))
214 		str_appendf(reply, replylen, "\"ecefy\":%.2f,",
215 			    gpsdata->fix.ecef.y);
216 	    if (0 != isfinite(gpsdata->fix.ecef.z))
217 		str_appendf(reply, replylen, "\"ecefz\":%.2f,",
218 			    gpsdata->fix.ecef.z);
219 	    if (0 != isfinite(gpsdata->fix.ecef.vx))
220 		str_appendf(reply, replylen, "\"ecefvx\":%.2f,",
221 			    gpsdata->fix.ecef.vx);
222 	    if (0 != isfinite(gpsdata->fix.ecef.vy))
223 		str_appendf(reply, replylen, "\"ecefvy\":%.2f,",
224 			    gpsdata->fix.ecef.vy);
225 	    if (0 != isfinite(gpsdata->fix.ecef.vz))
226 		str_appendf(reply, replylen, "\"ecefvz\":%.2f,",
227 			    gpsdata->fix.ecef.vz);
228 	    if (0 != isfinite(gpsdata->fix.ecef.pAcc))
229 		str_appendf(reply, replylen, "\"ecefpAcc\":%.2f,",
230 			    gpsdata->fix.ecef.pAcc);
231 	    if (0 != isfinite(gpsdata->fix.ecef.vAcc))
232 		str_appendf(reply, replylen, "\"ecefvAcc\":%.2f,",
233 			    gpsdata->fix.ecef.vAcc);
234 	    /* NED is in meters, so %.3f is millimeter resolution */
235 	    if (0 != isfinite(gpsdata->fix.NED.relPosN))
236 		str_appendf(reply, replylen, "\"relN\":%.3f,",
237 			    gpsdata->fix.NED.relPosN);
238 	    if (0 != isfinite(gpsdata->fix.NED.relPosE))
239 		str_appendf(reply, replylen, "\"relE\":%.3f,",
240 			    gpsdata->fix.NED.relPosE);
241 	    if (0 != isfinite(gpsdata->fix.NED.relPosD))
242 		str_appendf(reply, replylen, "\"relD\":%.3f,",
243 			    gpsdata->fix.NED.relPosD);
244 	    if (0 != isfinite(gpsdata->fix.NED.velN))
245 		str_appendf(reply, replylen, "\"velN\":%.3f,",
246 			    gpsdata->fix.NED.velN);
247 	    if (0 != isfinite(gpsdata->fix.NED.velE))
248 		str_appendf(reply, replylen, "\"velE\":%.3f,",
249 			    gpsdata->fix.NED.velE);
250 	    if (0 != isfinite(gpsdata->fix.NED.velD))
251 		str_appendf(reply, replylen, "\"velD\":%.3f,",
252 			    gpsdata->fix.NED.velD);
253 	    if (0 != isfinite(gpsdata->fix.geoid_sep))
254 		str_appendf(reply, replylen, "\"geoidSep\":%.3f,",
255 			    gpsdata->fix.geoid_sep);
256         }
257 	if (policy->timing) {
258 	    char rtime_str[TIMESPEC_LEN];
259             char ts_buf[TIMESPEC_LEN];
260 	    struct timespec rtime_tmp;
261 	    (void)clock_gettime(CLOCK_REALTIME, &rtime_tmp);
262 	    str_appendf(reply, replylen, "\"rtime\":%s,",
263                         timespec_str(&rtime_tmp, rtime_str,
264                                      sizeof(rtime_str)));
265 	    if (session->pps_thread.ppsout_count) {
266 		char ts_str[TIMESPEC_LEN];
267 		struct timedelta_t timedelta;
268 		/* ugh - de-consting this might get us in trouble someday */
269 		pps_thread_ppsout(&((struct gps_device_t *)session)->pps_thread,
270 				  &timedelta);
271 		str_appendf(reply, replylen, "\"pps\":%s,",
272                             timespec_str(&timedelta.clock, ts_str,
273                                          sizeof(ts_str)));
274                 /* TODO: add PPS precision to JSON output */
275 	    }
276 	    str_appendf(reply, replylen,
277 			"\"sor\":%s,\"chars\":%lu,\"sats\":%2d,"
278 			"\"week\":%u,\"tow\":%lld.%03ld,\"rollovers\":%d",
279                         timespec_str(&session->sor, ts_buf, sizeof(ts_buf)),
280 			session->chars,
281 			gpsdata->satellites_used,
282 			session->context->gps_week,
283 			(long long)session->context->gps_tow.tv_sec,
284 			session->context->gps_tow.tv_nsec / 1000000L,
285 			session->context->rollovers);
286 	}
287         /* at the end because it is new and microjson chokes on new items */
288 	if (0 != isfinite(gpsdata->fix.eph))
289 	    str_appendf(reply, replylen, "\"eph\":%.3f,", gpsdata->fix.eph);
290 	if (0 != isfinite(gpsdata->fix.sep))
291 	    str_appendf(reply, replylen, "\"sep\":%.3f,", gpsdata->fix.sep);
292 	if ('\0' != gpsdata->fix.datum[0])
293 	    str_appendf(reply, replylen, "\"datum\":\"%.40s\",",
294                         gpsdata->fix.datum);
295 	if (0 != isfinite(gpsdata->fix.depth))
296 	    str_appendf(reply, replylen,
297 			   "\"depth\":%.3f,", gpsdata->fix.depth);
298 	if (0 != isfinite(gpsdata->fix.dgps_age) &&
299 	    0 <= gpsdata->fix.dgps_station) {
300             /* both, or none */
301 	    str_appendf(reply, replylen,
302 			   "\"dgpsAge\":%.1f,", gpsdata->fix.dgps_age);
303 	    str_appendf(reply, replylen,
304 			   "\"dgpsSta\":%d,", gpsdata->fix.dgps_station);
305         }
306     }
307     str_rstrip_char(reply, ',');
308     (void)strlcat(reply, "}\r\n", replylen);
309 }
310 
json_noise_dump(const struct gps_data_t * gpsdata,char * reply,size_t replylen)311 void json_noise_dump(const struct gps_data_t *gpsdata,
312 		   char *reply, size_t replylen)
313 {
314     assert(replylen > sizeof(char *));
315     (void)strlcpy(reply, "{\"class\":\"GST\",", replylen);
316     if (gpsdata->dev.path[0] != '\0')
317 	str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
318     if (0 < gpsdata->gst.utctime.tv_sec) {
319 	char tbuf[JSON_DATE_MAX+1];
320 	str_appendf(reply, replylen,
321 		   "\"time\":\"%s\",",
322 		   timespec_to_iso8601(gpsdata->gst.utctime,
323                                        tbuf, sizeof(tbuf)));
324     }
325 #define ADD_GST_FIELD(tag, field) do {                     \
326     if (isfinite(gpsdata->gst.field) != 0)              \
327 	str_appendf(reply, replylen, "\"" tag "\":%.3f,", gpsdata->gst.field); \
328     } while(0)
329 
330     ADD_GST_FIELD("rms",    rms_deviation);
331     ADD_GST_FIELD("major",  smajor_deviation);
332     ADD_GST_FIELD("minor",  sminor_deviation);
333     ADD_GST_FIELD("orient", smajor_orientation);
334     ADD_GST_FIELD("lat",    lat_err_deviation);
335     ADD_GST_FIELD("lon",    lon_err_deviation);
336     ADD_GST_FIELD("alt",    alt_err_deviation);
337 
338 #undef ADD_GST_FIELD
339 
340     str_rstrip_char(reply, ',');
341     (void)strlcat(reply, "}\r\n", replylen);
342 }
343 
json_sky_dump(const struct gps_data_t * datap,char * reply,size_t replylen)344 void json_sky_dump(const struct gps_data_t *datap,
345 		   char *reply, size_t replylen)
346 {
347     int i, reported = 0;
348 
349     assert(replylen > sizeof(char *));
350     (void)strlcpy(reply, "{\"class\":\"SKY\",", replylen);
351     if (datap->dev.path[0] != '\0')
352 	str_appendf(reply, replylen, "\"device\":\"%s\",", datap->dev.path);
353     if (0 < datap->skyview_time.tv_sec) {
354 	char tbuf[JSON_DATE_MAX+1];
355 	str_appendf(reply, replylen,
356 		       "\"time\":\"%s\",",
357 		       timespec_to_iso8601(datap->skyview_time,
358                                       tbuf, sizeof(tbuf)));
359     }
360     if (isfinite(datap->dop.xdop) != 0)
361 	str_appendf(reply, replylen, "\"xdop\":%.2f,", datap->dop.xdop);
362     if (isfinite(datap->dop.ydop) != 0)
363 	str_appendf(reply, replylen, "\"ydop\":%.2f,", datap->dop.ydop);
364     if (isfinite(datap->dop.vdop) != 0)
365 	str_appendf(reply, replylen, "\"vdop\":%.2f,", datap->dop.vdop);
366     if (isfinite(datap->dop.tdop) != 0)
367 	str_appendf(reply, replylen, "\"tdop\":%.2f,", datap->dop.tdop);
368     if (isfinite(datap->dop.hdop) != 0)
369 	str_appendf(reply, replylen, "\"hdop\":%.2f,", datap->dop.hdop);
370     if (isfinite(datap->dop.gdop) != 0)
371 	str_appendf(reply, replylen, "\"gdop\":%.2f,", datap->dop.gdop);
372     if (isfinite(datap->dop.pdop) != 0)
373 	str_appendf(reply, replylen, "\"pdop\":%.2f,", datap->dop.pdop);
374     /* insurance against flaky drivers */
375     for (i = 0; i < datap->satellites_visible; i++)
376 	if (datap->skyview[i].PRN)
377 	    reported++;
378     if (reported) {
379 	(void)strlcat(reply, "\"satellites\":[", replylen);
380 	for (i = 0; i < reported; i++) {
381 	    if (datap->skyview[i].PRN) {
382 		str_appendf(reply, replylen, "{\"PRN\":%d,",
383                             datap->skyview[i].PRN);
384 		if (0 != isfinite(datap->skyview[i].elevation) &&
385 		    90 >= fabs(datap->skyview[i].elevation)) {
386 		    str_appendf(reply, replylen, "\"el\":%.1f,",
387 		                datap->skyview[i].elevation);
388                 }
389 		if (0 != isfinite(datap->skyview[i].azimuth) &&
390 		    0 <= fabs(datap->skyview[i].azimuth) &&
391 		    359 >= fabs(datap->skyview[i].azimuth)) {
392 		    str_appendf(reply, replylen, "\"az\":%.1f,",
393 		                datap->skyview[i].azimuth);
394                 }
395 		if (0 != isfinite(datap->skyview[i].ss)) {
396 		    str_appendf(reply, replylen, "\"ss\":%.1f,",
397 		                datap->skyview[i].ss);
398                 }
399 		str_appendf(reply, replylen,
400                    "\"used\":%s",
401                    datap->skyview[i].used ? "true" : "false");
402                 if (0 != datap->skyview[i].svid) {
403                     str_appendf(reply, replylen,
404                        ",\"gnssid\":%d,\"svid\":%d",
405                        datap->skyview[i].gnssid,
406                        datap->skyview[i].svid);
407                 }
408                 if (0 != datap->skyview[i].sigid) {
409                     str_appendf(reply, replylen,
410                        ",\"sigid\":%d", datap->skyview[i].sigid);
411                 }
412                 if (GNSSID_GLO == datap->skyview[i].gnssid &&
413                     0 <= datap->skyview[i].freqid &&
414                     16 >= datap->skyview[i].freqid) {
415                     str_appendf(reply, replylen,
416                        ",\"freqid\":%d", datap->skyview[i].freqid);
417                 }
418                 if (SAT_HEALTH_UNK != datap->skyview[i].health) {
419                     str_appendf(reply, replylen,
420                        ",\"health\":%d", datap->skyview[i].health);
421                 }
422                 (void)strlcat(reply, "},", replylen);
423 	    }
424 	}
425 	str_rstrip_char(reply, ',');
426 	(void)strlcat(reply, "]", replylen);
427     }
428     str_rstrip_char(reply, ',');
429     (void)strlcat(reply, "}\r\n", replylen);
430 }
431 
json_device_dump(const struct gps_device_t * device,char * reply,size_t replylen)432 void json_device_dump(const struct gps_device_t *device,
433 		      char *reply, size_t replylen)
434 {
435     struct classmap_t *cmp;
436     char buf1[JSON_VAL_MAX * 2 + 1];
437 
438     (void)strlcpy(reply, "{\"class\":\"DEVICE\",\"path\":\"", replylen);
439     (void)strlcat(reply, device->gpsdata.dev.path, replylen);
440     (void)strlcat(reply, "\",", replylen);
441     if (device->device_type != NULL) {
442 	(void)strlcat(reply, "\"driver\":\"", replylen);
443 	(void)strlcat(reply, device->device_type->type_name, replylen);
444 	(void)strlcat(reply, "\",", replylen);
445     }
446     if (device->subtype[0] != '\0') {
447 	(void)strlcat(reply, "\"subtype\":\"", replylen);
448 	(void)strlcat(reply,
449 		      json_stringify(buf1, sizeof(buf1), device->subtype),
450 		      replylen);
451 	(void)strlcat(reply, "\",", replylen);
452     }
453     if (device->subtype1[0] != '\0') {
454 	(void)strlcat(reply, "\"subtype1\":\"", replylen);
455 	(void)strlcat(reply,
456 		      json_stringify(buf1, sizeof(buf1), device->subtype1),
457 		      replylen);
458 	(void)strlcat(reply, "\",", replylen);
459     }
460     /*
461      * There's an assumption here: Anything that we type service_sensor is
462      * a serial device with the usual control parameters.
463      */
464     if (0 < device->gpsdata.online.tv_sec) {
465         /* odd, using online, not activated, time */
466 	str_appendf(reply, replylen, "\"activated\":\"%s\",",
467 		    timespec_to_iso8601(device->gpsdata.online,
468                                         buf1, sizeof(buf1)));
469 	if (device->observed != 0) {
470 	    int mask = 0;
471 	    for (cmp = classmap; cmp < classmap + NITEMS(classmap); cmp++)
472 		if ((device->observed & cmp->packetmask) != 0)
473 		    mask |= cmp->typemask;
474 	    if (mask != 0)
475 		str_appendf(reply, replylen, "\"flags\":%d,", mask);
476 	}
477 	if (device->servicetype == service_sensor) {
478 	    /* speed can be 0 if the device is not currently active */
479 	    speed_t speed = gpsd_get_speed(device);
480 	    if (speed != 0)
481 		str_appendf(reply, replylen,
482 			    "\"native\":%d,\"bps\":%d,\"parity\":\"%c\","
483                             "\"stopbits\":%u,\"cycle\":%lld.%02ld,",
484 			    device->gpsdata.dev.driver_mode,
485 			    (int)speed,
486 			    device->gpsdata.dev.parity,
487 			    device->gpsdata.dev.stopbits,
488 			    (long long)device->gpsdata.dev.cycle.tv_sec,
489 			    device->gpsdata.dev.cycle.tv_nsec / 10000000);
490 #ifdef RECONFIGURE_ENABLE
491 	    if (device->device_type != NULL
492 		&& device->device_type->rate_switcher != NULL)
493 		str_appendf(reply, replylen,
494 			       "\"mincycle\":%lld.%02ld,",
495 			       (long long)device->device_type->min_cycle.tv_sec,
496 			       device->device_type->min_cycle.tv_nsec /
497                                10000000);
498 #endif /* RECONFIGURE_ENABLE */
499 	}
500     }
501     str_rstrip_char(reply, ',');
502     (void)strlcat(reply, "}\r\n", replylen);
503 }
504 
json_watch_dump(const struct gps_policy_t * ccp,char * reply,size_t replylen)505 void json_watch_dump(const struct gps_policy_t *ccp,
506 		     char *reply, size_t replylen)
507 {
508     (void)snprintf(reply, replylen,
509 		   "{\"class\":\"WATCH\",\"enable\":%s,\"json\":%s,"
510                    "\"nmea\":%s,\"raw\":%d,\"scaled\":%s,\"timing\":%s,"
511                    "\"split24\":%s,\"pps\":%s,",
512 		   ccp->watcher ? "true" : "false",
513 		   ccp->json ? "true" : "false",
514 		   ccp->nmea ? "true" : "false",
515 		   ccp->raw,
516 		   ccp->scaled ? "true" : "false",
517 		   ccp->timing ? "true" : "false",
518 		   ccp->split24 ? "true" : "false",
519 		   ccp->pps ? "true" : "false");
520     if (ccp->devpath[0] != '\0')
521 	str_appendf(reply, replylen, "\"device\":\"%s\",", ccp->devpath);
522     str_rstrip_char(reply, ',');
523     (void)strlcat(reply, "}\r\n", replylen);
524 }
525 
json_subframe_dump(const struct gps_data_t * datap,char buf[],size_t buflen)526 void json_subframe_dump(const struct gps_data_t *datap,
527 			char buf[], size_t buflen)
528 {
529     const struct subframe_t *subframe = &datap->subframe;
530     const bool scaled = datap->policy.scaled;
531 
532     (void)snprintf(buf, buflen, "{\"class\":\"SUBFRAME\",\"device\":\"%s\","
533 		   "\"tSV\":%u,\"TOW17\":%u,\"frame\":%u,\"scaled\":%s",
534 		   datap->dev.path,
535 		   (unsigned int)subframe->tSVID,
536 		   (unsigned int)subframe->TOW17,
537 		   (unsigned int)subframe->subframe_num,
538 		   JSON_BOOL(scaled));
539 
540     if ( 1 == subframe->subframe_num ) {
541 	if (scaled) {
542 	    str_appendf(buf, buflen,
543 			",\"EPHEM1\":{\"WN\":%u,\"IODC\":%u,\"L2\":%u,"
544 			"\"ura\":%u,\"hlth\":%u,\"L2P\":%u,\"Tgd\":%g,"
545 			"\"toc\":%lu,\"af2\":%.4g,\"af1\":%.6e,\"af0\":%.7e}",
546 			(unsigned int)subframe->sub1.WN,
547 			(unsigned int)subframe->sub1.IODC,
548 			(unsigned int)subframe->sub1.l2,
549 			subframe->sub1.ura,
550 			subframe->sub1.hlth,
551 			(unsigned int)subframe->sub1.l2p,
552 			subframe->sub1.d_Tgd,
553 			(unsigned long)subframe->sub1.l_toc,
554 			subframe->sub1.d_af2,
555 			subframe->sub1.d_af1,
556 			subframe->sub1.d_af0);
557 	} else {
558 	    str_appendf(buf, buflen,
559 			",\"EPHEM1\":{\"WN\":%u,\"IODC\":%u,\"L2\":%u,"
560 			"\"ura\":%u,\"hlth\":%u,\"L2P\":%u,\"Tgd\":%d,"
561 			"\"toc\":%u,\"af2\":%ld,\"af1\":%d,\"af0\":%d}",
562 			(unsigned int)subframe->sub1.WN,
563 			(unsigned int)subframe->sub1.IODC,
564 			(unsigned int)subframe->sub1.l2,
565 			subframe->sub1.ura,
566 			subframe->sub1.hlth,
567 			(unsigned int)subframe->sub1.l2p,
568 			(int)subframe->sub1.Tgd,
569 			(unsigned int)subframe->sub1.toc,
570 			(long)subframe->sub1.af2,
571 			(int)subframe->sub1.af1,
572 			(int)subframe->sub1.af0);
573 	}
574     } else if ( 2 == subframe->subframe_num ) {
575 	if (scaled) {
576 	    str_appendf(buf, buflen,
577 			",\"EPHEM2\":{\"IODE\":%u,\"Crs\":%.6e,\"deltan\":%.6e,"
578 			"\"M0\":%.11e,\"Cuc\":%.6e,\"e\":%f,\"Cus\":%.6e,"
579 			"\"sqrtA\":%.11g,\"toe\":%lu,\"FIT\":%u,\"AODO\":%u}",
580 			(unsigned int)subframe->sub2.IODE,
581 			subframe->sub2.d_Crs,
582 			subframe->sub2.d_deltan,
583 			subframe->sub2.d_M0,
584 			subframe->sub2.d_Cuc,
585 			subframe->sub2.d_eccentricity,
586 			subframe->sub2.d_Cus,
587 			subframe->sub2.d_sqrtA,
588 			(unsigned long)subframe->sub2.l_toe,
589 			(unsigned int)subframe->sub2.fit,
590 			(unsigned int)subframe->sub2.u_AODO);
591 	} else {
592 	    str_appendf(buf, buflen,
593 			",\"EPHEM2\":{\"IODE\":%u,\"Crs\":%d,\"deltan\":%d,"
594 			"\"M0\":%ld,\"Cuc\":%d,\"e\":%ld,\"Cus\":%d,"
595 			"\"sqrtA\":%lu,\"toe\":%lu,\"FIT\":%u,\"AODO\":%u}",
596 			(unsigned int)subframe->sub2.IODE,
597 			(int)subframe->sub2.Crs,
598 			(int)subframe->sub2.deltan,
599 			(long)subframe->sub2.M0,
600 			(int)subframe->sub2.Cuc,
601 			(long)subframe->sub2.e,
602 			(int)subframe->sub2.Cus,
603 			(unsigned long)subframe->sub2.sqrtA,
604 			(unsigned long)subframe->sub2.toe,
605 			(unsigned int)subframe->sub2.fit,
606 			(unsigned int)subframe->sub2.AODO);
607 	}
608     } else if ( 3 == subframe->subframe_num ) {
609 	if (scaled) {
610 	    str_appendf(buf, buflen,
611 		",\"EPHEM3\":{\"IODE\":%3u,\"IDOT\":%.6g,\"Cic\":%.6e,"
612 		"\"Omega0\":%.11e,\"Cis\":%.7g,\"i0\":%.11e,\"Crc\":%.7g,"
613 		"\"omega\":%.11e,\"Omegad\":%.6e}",
614 			(unsigned int)subframe->sub3.IODE,
615 			subframe->sub3.d_IDOT,
616 			subframe->sub3.d_Cic,
617 			subframe->sub3.d_Omega0,
618 			subframe->sub3.d_Cis,
619 			subframe->sub3.d_i0,
620 			subframe->sub3.d_Crc,
621 			subframe->sub3.d_omega,
622 			subframe->sub3.d_Omegad );
623 	} else {
624 	    str_appendf(buf, buflen,
625 		",\"EPHEM3\":{\"IODE\":%u,\"IDOT\":%u,\"Cic\":%u,"
626 		"\"Omega0\":%ld,\"Cis\":%d,\"i0\":%ld,\"Crc\":%d,"
627 		"\"omega\":%ld,\"Omegad\":%ld}",
628 			(unsigned int)subframe->sub3.IODE,
629 			(unsigned int)subframe->sub3.IDOT,
630 			(unsigned int)subframe->sub3.Cic,
631 			(long int)subframe->sub3.Omega0,
632 			(int)subframe->sub3.Cis,
633 			(long int)subframe->sub3.i0,
634 			(int)subframe->sub3.Crc,
635 			(long int)subframe->sub3.omega,
636 			(long int)subframe->sub3.Omegad );
637 	}
638     } else if ( subframe->is_almanac ) {
639 	if (scaled) {
640 	    str_appendf(buf, buflen,
641 			",\"ALMANAC\":{\"ID\":%d,\"Health\":%u,"
642 			"\"e\":%g,\"toa\":%lu,"
643 			"\"deltai\":%.10e,\"Omegad\":%.5e,\"sqrtA\":%.10g,"
644 			"\"Omega0\":%.10e,\"omega\":%.10e,\"M0\":%.11e,"
645 			"\"af0\":%.5e,\"af1\":%.5e}",
646 			(int)subframe->sub5.almanac.sv,
647 			(unsigned int)subframe->sub5.almanac.svh,
648 			subframe->sub5.almanac.d_eccentricity,
649 			(unsigned long)subframe->sub5.almanac.l_toa,
650 			subframe->sub5.almanac.d_deltai,
651 			subframe->sub5.almanac.d_Omegad,
652 			subframe->sub5.almanac.d_sqrtA,
653 			subframe->sub5.almanac.d_Omega0,
654 			subframe->sub5.almanac.d_omega,
655 			subframe->sub5.almanac.d_M0,
656 			subframe->sub5.almanac.d_af0,
657 			subframe->sub5.almanac.d_af1);
658 	} else {
659 	    str_appendf(buf, buflen,
660 			",\"ALMANAC\":{\"ID\":%d,\"Health\":%u,"
661 			"\"e\":%u,\"toa\":%u,"
662 			"\"deltai\":%d,\"Omegad\":%d,\"sqrtA\":%lu,"
663 			"\"Omega0\":%ld,\"omega\":%ld,\"M0\":%ld,"
664 			"\"af0\":%d,\"af1\":%d}",
665 			(int)subframe->sub5.almanac.sv,
666 			(unsigned int)subframe->sub5.almanac.svh,
667 			(unsigned int)subframe->sub5.almanac.e,
668 			(unsigned int)subframe->sub5.almanac.toa,
669 			(int)subframe->sub5.almanac.deltai,
670 			(int)subframe->sub5.almanac.Omegad,
671 			(unsigned long)subframe->sub5.almanac.sqrtA,
672 			(long)subframe->sub5.almanac.Omega0,
673 			(long)subframe->sub5.almanac.omega,
674 			(long)subframe->sub5.almanac.M0,
675 			(int)subframe->sub5.almanac.af0,
676 			(int)subframe->sub5.almanac.af1);
677 	}
678     } else if ( 4 == subframe->subframe_num ) {
679 	str_appendf(buf, buflen,
680 	    ",\"pageid\":%u",
681 		       (unsigned int)subframe->pageid);
682 	switch (subframe->pageid ) {
683 	case 13:
684 	case 52:
685 	{
686 		int i;
687 		/* decoding of ERD to SV is non trivial and not done yet */
688 		str_appendf(buf, buflen,
689 		    ",\"ERD\":{\"ai\":%u,", subframe->sub4_13.ai);
690 
691 		/* 1-index loop to construct json, rather than giant snprintf */
692 		for(i = 1 ; i <= 30; i++){
693 		    str_appendf(buf, buflen,
694 			"\"ERD%d\":%d,", i, subframe->sub4_13.ERD[i]);
695 		}
696 		str_rstrip_char(buf, ',');
697 		str_appendf(buf, buflen, "}");
698 		break;
699 	}
700 	case 55:
701 	    /* JSON is UTF-8. double quote, backslash and
702 	     * control charactores (U+0000 through U+001F).must be
703 	     * escaped. */
704 	    /* system message can be 24 bytes, JSON can escape all
705 	     * chars so up to 24*6 long. */
706 
707 	    {
708 		char buf1[25 * 6];
709 		(void)json_stringify(buf1, sizeof(buf1), subframe->sub4_17.str);
710 		str_appendf(buf, buflen,
711 			       ",\"system_message\":\"%.144s\"", buf1);
712 	    }
713 	    break;
714 	case 56:
715 	    if (scaled) {
716 		str_appendf(buf, buflen,
717 			",\"IONO\":{\"a0\":%.5g,\"a1\":%.5g,\"a2\":%.5g,"
718 			"\"a3\":%.5g,\"b0\":%.5g,\"b1\":%.5g,\"b2\":%.5g,"
719 			"\"b3\":%.5g,\"A1\":%.11e,\"A0\":%.11e,\"tot\":%lld,"
720 			"\"WNt\":%u,\"ls\":%d,\"WNlsf\":%u,\"DN\":%u,"
721 			"\"lsf\":%d}",
722 			    subframe->sub4_18.d_alpha0,
723 			    subframe->sub4_18.d_alpha1,
724 			    subframe->sub4_18.d_alpha2,
725 			    subframe->sub4_18.d_alpha3,
726 			    subframe->sub4_18.d_beta0,
727 			    subframe->sub4_18.d_beta1,
728 			    subframe->sub4_18.d_beta2,
729 			    subframe->sub4_18.d_beta3,
730 			    subframe->sub4_18.d_A1,
731 			    subframe->sub4_18.d_A0,
732 			    (long long)subframe->sub4_18.t_tot,
733 			    (unsigned int)subframe->sub4_18.WNt,
734 			    (int)subframe->sub4_18.leap,
735 			    (unsigned int)subframe->sub4_18.WNlsf,
736 			    (unsigned int)subframe->sub4_18.DN,
737 			    (int)subframe->sub4_18.lsf);
738 	    } else {
739 		str_appendf(buf, buflen,
740 			",\"IONO\":{\"a0\":%d,\"a1\":%d,\"a2\":%d,\"a3\":%d,"
741 			"\"b0\":%d,\"b1\":%d,\"b2\":%d,\"b3\":%d,"
742 			"\"A1\":%ld,\"A0\":%ld,\"tot\":%u,\"WNt\":%u,"
743 			"\"ls\":%d,\"WNlsf\":%u,\"DN\":%u,\"lsf\":%d}",
744 			    (int)subframe->sub4_18.alpha0,
745 			    (int)subframe->sub4_18.alpha1,
746 			    (int)subframe->sub4_18.alpha2,
747 			    (int)subframe->sub4_18.alpha3,
748 			    (int)subframe->sub4_18.beta0,
749 			    (int)subframe->sub4_18.beta1,
750 			    (int)subframe->sub4_18.beta2,
751 			    (int)subframe->sub4_18.beta3,
752 			    (long)subframe->sub4_18.A1,
753 			    (long)subframe->sub4_18.A0,
754 			    (unsigned int)subframe->sub4_18.tot,
755 			    (unsigned int)subframe->sub4_18.WNt,
756 			    (int)subframe->sub4_18.leap,
757 			    (unsigned int)subframe->sub4_18.WNlsf,
758 			    (unsigned int)subframe->sub4_18.DN,
759 			    (int)subframe->sub4_18.lsf);
760 	    }
761 	    break;
762 	case 25:
763 	case 63:
764 	{
765 	    int i;
766 	    str_appendf(buf, buflen,
767 			   ",\"HEALTH\":{\"data_id\":%d,",
768 			   (int)subframe->data_id);
769 
770 		/* 1-index loop to construct json, rather than giant snprintf */
771 		for(i = 1 ; i <= 32; i++){
772 		    str_appendf(buf, buflen,
773 				   "\"SV%d\":%d,",
774 				   i, (int)subframe->sub4_25.svf[i]);
775 		}
776 		for(i = 0 ; i < 8; i++){ /* 0-index */
777 		    str_appendf(buf, buflen,
778 				   "\"SVH%d\":%d,",
779 				   i+25, (int)subframe->sub4_25.svhx[i]);
780 		}
781 		str_rstrip_char(buf, ',');
782 		str_appendf(buf, buflen, "}");
783 
784 	    break;
785 	    }
786 	}
787     } else if ( 5 == subframe->subframe_num ) {
788 	str_appendf(buf, buflen,
789 	    ",\"pageid\":%u",
790 		       (unsigned int)subframe->pageid);
791 	if ( 51 == subframe->pageid ) {
792 	    int i;
793 	    /* subframe5, page 25 */
794 	    str_appendf(buf, buflen,
795 		",\"HEALTH2\":{\"toa\":%lu,\"WNa\":%u,",
796 			   (unsigned long)subframe->sub5_25.l_toa,
797 			   (unsigned int)subframe->sub5_25.WNa);
798 		/* 1-index loop to construct json */
799 		for(i = 1 ; i <= 24; i++){
800 		    str_appendf(buf, buflen,
801                                 "\"SV%d\":%d,",
802                                 i, (int)subframe->sub5_25.sv[i]);
803 		}
804 		str_rstrip_char(buf, ',');
805 		str_appendf(buf, buflen, "}");
806 
807 	}
808     }
809     (void)strlcat(buf, "}\r\n", buflen);
810 }
811 
812 /* RAW dump - should be good enough to make a RINEX 3 file */
json_raw_dump(const struct gps_data_t * gpsdata,char * reply,size_t replylen)813 void json_raw_dump(const struct gps_data_t *gpsdata,
814 		   char *reply, size_t replylen)
815 {
816     int i;
817 
818     assert(replylen > sizeof(char *));
819     if (0 == gpsdata->raw.mtime.tv_sec) {
820         /* no data to dump */
821         return;
822     }
823     (void)strlcpy(reply, "{\"class\":\"RAW\",", replylen);
824     if (gpsdata->dev.path[0] != '\0')
825 	str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
826 
827     str_appendf(reply, replylen, "\"time\":%lld,\"nsec\":%ld,\"rawdata\":[",
828                 (long long)gpsdata->raw.mtime.tv_sec,
829                 gpsdata->raw.mtime.tv_nsec);
830 
831     for (i = 0; i < MAXCHANNELS; i++) {
832         bool comma = false;
833         if (0 == gpsdata->raw.meas[i].svid ||
834             255 == gpsdata->raw.meas[i].svid) {
835             /* skip empty and GLONASS 255 */
836             continue;
837         }
838         str_appendf(reply, replylen,
839                     "{\"gnssid\":%u,\"svid\":%u,\"snr\":%u,"
840                     "\"obs\":\"%s\",\"lli\":%1u,\"locktime\":%u",
841                     gpsdata->raw.meas[i].gnssid, gpsdata->raw.meas[i].svid,
842                     gpsdata->raw.meas[i].snr,
843                     gpsdata->raw.meas[i].obs_code, gpsdata->raw.meas[i].lli,
844                     gpsdata->raw.meas[i].locktime);
845         if (0 < gpsdata->raw.meas[i].sigid) {
846 	    str_appendf(reply, replylen, ",\"sigid\":%u",
847 			gpsdata->raw.meas[i].sigid);
848         }
849         if (GNSSID_GLO == gpsdata->raw.meas[i].gnssid) {
850 	    str_appendf(reply, replylen, ",\"freqid\":%u",
851 			gpsdata->raw.meas[i].freqid);
852         }
853         comma = true;
854 
855         if (0 != isfinite(gpsdata->raw.meas[i].pseudorange) &&
856             1.0 < gpsdata->raw.meas[i].pseudorange) {
857             if (comma)
858                 (void)strlcat(reply, ",", replylen);
859             str_appendf(reply, replylen, "\"pseudorange\":%f",
860                         gpsdata->raw.meas[i].pseudorange);
861             comma = true;
862 
863 	    if (0 != isfinite(gpsdata->raw.meas[i].carrierphase)) {
864 		str_appendf(reply, replylen, ",\"carrierphase\":%f",
865 			    gpsdata->raw.meas[i].carrierphase);
866 		comma = true;
867 	    }
868         }
869         if (0 != isfinite(gpsdata->raw.meas[i].doppler)) {
870             if (comma)
871                 (void)strlcat(reply, ",", replylen);
872             str_appendf(reply, replylen, "\"doppler\":%f",
873                         gpsdata->raw.meas[i].doppler);
874             comma = true;
875         }
876 
877         /* L2 C/A pseudo range, RINEX C2C */
878         if (0 != isfinite(gpsdata->raw.meas[i].c2c) &&
879             1.0 < gpsdata->raw.meas[i].c2c) {
880             if (comma)
881                 (void)strlcat(reply, ",", replylen);
882             str_appendf(reply, replylen, "\"c2c\":%f",
883                         gpsdata->raw.meas[i].c2c);
884             comma = true;
885 
886 	    /* L2 C/A carrier phase, RINEX L2C */
887 	    if (0 != isfinite(gpsdata->raw.meas[i].l2c)) {
888 		if (comma)
889 		    (void)strlcat(reply, ",", replylen);
890 		str_appendf(reply, replylen, "\"l2c\":%f",
891 			    gpsdata->raw.meas[i].l2c);
892 		comma = true;
893 	    }
894         }
895         (void)strlcat(reply, "},", replylen);
896     }
897     str_rstrip_char(reply, ',');
898     (void)strlcat(reply, "]", replylen);
899 
900     str_rstrip_char(reply, ',');
901     (void)strlcat(reply, "}\r\n", replylen);
902 }
903 
904 #if defined(RTCM104V2_ENABLE)
json_rtcm2_dump(const struct rtcm2_t * rtcm,const char * device,char buf[],size_t buflen)905 void json_rtcm2_dump(const struct rtcm2_t *rtcm,
906 		     const char *device,
907 		     char buf[], size_t buflen)
908 /* dump the contents of a parsed RTCM104 message as JSON */
909 {
910     char buf1[JSON_VAL_MAX * 2 + 1];
911     unsigned int n;
912 
913     (void)snprintf(buf, buflen, "{\"class\":\"RTCM2\",");
914     if (device != NULL && device[0] != '\0')
915 	str_appendf(buf, buflen, "\"device\":\"%s\",", device);
916     str_appendf(buf, buflen,
917 		   "\"type\":%u,\"station_id\":%u,\"zcount\":%0.1f,"
918                    "\"seqnum\":%u,\"length\":%u,\"station_health\":%u,",
919 		   rtcm->type, rtcm->refstaid, rtcm->zcount, rtcm->seqnum,
920 		   rtcm->length, rtcm->stathlth);
921 
922     switch (rtcm->type) {
923     case 1:
924     case 9:
925 	(void)strlcat(buf, "\"satellites\":[", buflen);
926 	for (n = 0; n < rtcm->gps_ranges.nentries; n++) {
927 	    const struct gps_rangesat_t *rsp = &rtcm->gps_ranges.sat[n];
928 	    str_appendf(buf, buflen,
929 			   "{\"ident\":%u,\"udre\":%u,\"iod\":%u,"
930                            "\"prc\":%0.3f,\"rrc\":%0.3f},",
931 			   rsp->ident,
932 			   rsp->udre, rsp->iod,
933 			   rsp->prc, rsp->rrc);
934 	}
935 	str_rstrip_char(buf, ',');
936 	(void)strlcat(buf, "]", buflen);
937 	break;
938 
939     case 3:
940 	if (rtcm->ecef.valid)
941 	    str_appendf(buf, buflen,
942 			   "\"x\":%.2f,\"y\":%.2f,\"z\":%.2f,",
943 			   rtcm->ecef.x, rtcm->ecef.y, rtcm->ecef.z);
944 	break;
945 
946     case 4:
947 	if (rtcm->reference.valid) {
948 	    /*
949 	     * Beware! Needs to stay synchronized with a JSON
950 	     * enumeration map in the parser. This interpretation of
951 	     * NAVSYSTEM_GALILEO is assumed from RTCM3, it's not
952 	     * actually documented in RTCM 2.1.
953 	     */
954 	    static char *navsysnames[] = { "GPS", "GLONASS", "GALILEO" };
955 	    str_appendf(buf, buflen,
956 			   "\"system\":\"%s\",\"sense\":%1d,"
957                            "\"datum\":\"%s\",\"dx\":%.1f,\"dy\":%.1f,"
958                            "\"dz\":%.1f,",
959 			   rtcm->reference.system >= NITEMS(navsysnames)
960 			   ? "UNKNOWN"
961 			   : navsysnames[rtcm->reference.system],
962 			   rtcm->reference.sense,
963 			   rtcm->reference.datum,
964 			   rtcm->reference.dx,
965 			   rtcm->reference.dy, rtcm->reference.dz);
966 	}
967 	break;
968 
969     case 5:
970 	(void)strlcat(buf, "\"satellites\":[", buflen);
971 	for (n = 0; n < rtcm->conhealth.nentries; n++) {
972 	    const struct consat_t *csp = &rtcm->conhealth.sat[n];
973 	    str_appendf(buf, buflen,
974 			   "{\"ident\":%u,\"iodl\":%s,\"health\":%1u,"
975                            "\"snr\":%d,\"health_en\":%s,\"new_data\":%s,"
976                            "\"los_warning\":%s,\"tou\":%u},",
977 			   csp->ident,
978 			   JSON_BOOL(csp->iodl),
979 			   (unsigned)csp->health,
980 			   csp->snr,
981 			   JSON_BOOL(csp->health_en),
982 			   JSON_BOOL(csp->new_data),
983 			   JSON_BOOL(csp->los_warning), csp->tou);
984 	}
985 	str_rstrip_char(buf, ',');
986 	(void)strlcat(buf, "]", buflen);
987 	break;
988 
989     case 6:			/* NOP msg */
990 	break;
991 
992     case 7:
993 	(void)strlcat(buf, "\"satellites\":[", buflen);
994 	for (n = 0; n < rtcm->almanac.nentries; n++) {
995 	    const struct station_t *ssp = &rtcm->almanac.station[n];
996 	    str_appendf(buf, buflen,
997 			   "{\"lat\":%.4f,\"lon\":%.4f,\"range\":%u,"
998                            "\"frequency\":%.1f,\"health\":%u,"
999                            "\"station_id\":%u,\"bitrate\":%u},",
1000 			   ssp->latitude,
1001 			   ssp->longitude,
1002 			   ssp->range,
1003 			   ssp->frequency,
1004 			   ssp->health, ssp->station_id, ssp->bitrate);
1005 	}
1006 	str_rstrip_char(buf, ',');
1007 	(void)strlcat(buf, "]", buflen);
1008 	break;
1009 
1010     case 13:
1011 	str_appendf(buf, buflen,
1012 		       "\"status\":%s,\"rangeflag\":%s,"
1013 		       "\"lat\":%.2f,\"lon\":%.2f,\"range\":%u,",
1014 		       JSON_BOOL(rtcm->xmitter.status),
1015 		       JSON_BOOL(rtcm->xmitter.rangeflag),
1016 		       rtcm->xmitter.lat,
1017 		       rtcm->xmitter.lon,
1018 		       rtcm->xmitter.range);
1019 	break;
1020 
1021     case 14:
1022 	str_appendf(buf, buflen,
1023 		       "\"week\":%u,\"hour\":%u,\"leapsecs\":%u,",
1024 		       rtcm->gpstime.week,
1025 		       rtcm->gpstime.hour,
1026 		       rtcm->gpstime.leapsecs);
1027 	break;
1028 
1029     case 16:
1030 	str_appendf(buf, buflen,
1031 		       "\"message\":\"%s\"", json_stringify(buf1,
1032 							    sizeof(buf1),
1033 							    rtcm->message));
1034 	break;
1035 
1036     case 31:
1037 	(void)strlcat(buf, "\"satellites\":[", buflen);
1038 	for (n = 0; n < rtcm->glonass_ranges.nentries; n++) {
1039 	    const struct glonass_rangesat_t *rsp = &rtcm->glonass_ranges.sat[n];
1040 	    str_appendf(buf, buflen,
1041 			   "{\"ident\":%u,\"udre\":%u,\"change\":%s,"
1042                            "\"tod\":%u,\"prc\":%0.3f,\"rrc\":%0.3f},",
1043 			   rsp->ident,
1044 			   rsp->udre,
1045 			   JSON_BOOL(rsp->change),
1046 			   rsp->tod,
1047 			   rsp->prc, rsp->rrc);
1048 	}
1049 	str_rstrip_char(buf, ',');
1050 	(void)strlcat(buf, "]", buflen);
1051 	break;
1052 
1053     default:
1054 	(void)strlcat(buf, "\"data\":[", buflen);
1055 	for (n = 0; n < rtcm->length; n++)
1056 	    str_appendf(buf, buflen, "\"0x%08x\",", rtcm->words[n]);
1057 	str_rstrip_char(buf, ',');
1058 	(void)strlcat(buf, "]", buflen);
1059 	break;
1060     }
1061 
1062     str_rstrip_char(buf, ',');
1063     (void)strlcat(buf, "}\r\n", buflen);
1064 }
1065 #endif /* defined(RTCM104V2_ENABLE) */
1066 
1067 #if defined(RTCM104V3_ENABLE)
json_rtcm3_dump(const struct rtcm3_t * rtcm,const char * device,char buf[],size_t buflen)1068 void json_rtcm3_dump(const struct rtcm3_t *rtcm,
1069 		     const char *device,
1070 		     char buf[], size_t buflen)
1071 /* dump the contents of a parsed RTCM104v3 message as JSON */
1072 {
1073     char buf1[JSON_VAL_MAX * 2 + 1];
1074     unsigned short i;
1075     unsigned int n;
1076 
1077     (void)snprintf(buf, buflen, "{\"class\":\"RTCM3\",");
1078     if (device != NULL && device[0] != '\0')
1079 	str_appendf(buf, buflen, "\"device\":\"%s\",", device);
1080     str_appendf(buf, buflen, "\"type\":%u,", rtcm->type);
1081     str_appendf(buf, buflen, "\"length\":%u,", rtcm->length);
1082 
1083 #define CODE(x) (unsigned int)(x)
1084 #define INT(x) (unsigned int)(x)
1085     switch (rtcm->type) {
1086     case 1001:
1087 	str_appendf(buf, buflen,
1088 		       "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1089 		       "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1090 		       rtcm->rtcmtypes.rtcm3_1001.header.station_id,
1091 		       (int)rtcm->rtcmtypes.rtcm3_1001.header.tow,
1092 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1001.header.sync),
1093 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1001.header.smoothing),
1094 		       rtcm->rtcmtypes.rtcm3_1001.header.interval);
1095 	(void)strlcat(buf, "\"satellites\":[", buflen);
1096 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1001.header.satcount; i++) {
1097 #define R1001 rtcm->rtcmtypes.rtcm3_1001.rtk_data[i]
1098 	    str_appendf(buf, buflen,
1099 			   "{\"ident\":%u,\"ind\":%u,\"prange\":%8.2f,"
1100 			   "\"delta\":%6.4f,\"lockt\":%u},",
1101 			   R1001.ident,
1102 			   CODE(R1001.L1.indicator),
1103 			   R1001.L1.pseudorange,
1104 			   R1001.L1.rangediff,
1105 			   INT(R1001.L1.locktime));
1106 #undef R1001
1107 	}
1108 	str_rstrip_char(buf, ',');
1109 	(void)strlcat(buf, "]", buflen);
1110 	break;
1111 
1112     case 1002:
1113 	str_appendf(buf, buflen,
1114 		       "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1115 		       "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1116 		       rtcm->rtcmtypes.rtcm3_1002.header.station_id,
1117 		       (int)rtcm->rtcmtypes.rtcm3_1002.header.tow,
1118 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1002.header.sync),
1119 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1002.header.smoothing),
1120 		       rtcm->rtcmtypes.rtcm3_1002.header.interval);
1121 	(void)strlcat(buf, "\"satellites\":[", buflen);
1122 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1002.header.satcount; i++) {
1123 #define R1002 rtcm->rtcmtypes.rtcm3_1002.rtk_data[i]
1124 	    str_appendf(buf, buflen,
1125 			   "{\"ident\":%u,\"ind\":%u,\"prange\":%8.2f,"
1126 			   "\"delta\":%6.4f,\"lockt\":%u,\"amb\":%u,"
1127 			   "\"CNR\":%.2f},",
1128 			   R1002.ident,
1129 			   CODE(R1002.L1.indicator),
1130 			   R1002.L1.pseudorange,
1131 			   R1002.L1.rangediff,
1132 			   INT(R1002.L1.locktime),
1133 			   INT(R1002.L1.ambiguity),
1134 			   R1002.L1.CNR);
1135 #undef R1002
1136 	}
1137 	str_rstrip_char(buf, ',');
1138 	(void)strlcat(buf, "]", buflen);
1139 	break;
1140 
1141     case 1003:
1142 	str_appendf(buf, buflen,
1143 		       "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1144 		       "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1145 		       rtcm->rtcmtypes.rtcm3_1003.header.station_id,
1146 		       (int)rtcm->rtcmtypes.rtcm3_1003.header.tow,
1147 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1003.header.sync),
1148 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1003.header.smoothing),
1149 		       rtcm->rtcmtypes.rtcm3_1003.header.interval);
1150 	(void)strlcat(buf, "\"satellites\":[", buflen);
1151 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1003.header.satcount; i++) {
1152 #define R1003 rtcm->rtcmtypes.rtcm3_1003.rtk_data[i]
1153 	    str_appendf(buf, buflen,
1154 			   "{\"ident\":%u,"
1155 			   "\"L1\":{\"ind\":%u,\"prange\":%8.2f,"
1156 			   "\"delta\":%6.4f,\"lockt\":%u},"
1157 			   "\"L2\":{\"ind\":%u,\"prange\":%8.2f,"
1158 			   "\"delta\":%6.4f,\"lockt\":%u},"
1159 			   "},",
1160 			   R1003.ident,
1161 			   CODE(R1003.L1.indicator),
1162 			   R1003.L1.pseudorange,
1163 			   R1003.L1.rangediff,
1164 			   INT(R1003.L1.locktime),
1165 			   CODE(R1003.L2.indicator),
1166 			   R1003.L2.pseudorange,
1167 			   R1003.L2.rangediff,
1168 			   INT(R1003.L2.locktime));
1169 #undef R1003
1170 	}
1171 	str_rstrip_char(buf, ',');
1172 	(void)strlcat(buf, "]", buflen);
1173 	break;
1174 
1175     case 1004:
1176 	str_appendf(buf, buflen,
1177 		       "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1178 		       "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1179 		       rtcm->rtcmtypes.rtcm3_1004.header.station_id,
1180 		       (int)rtcm->rtcmtypes.rtcm3_1004.header.tow,
1181 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1004.header.sync),
1182 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1004.header.smoothing),
1183 		       rtcm->rtcmtypes.rtcm3_1004.header.interval);
1184 	(void)strlcat(buf, "\"satellites\":[", buflen);
1185 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1004.header.satcount; i++) {
1186 #define R1004 rtcm->rtcmtypes.rtcm3_1004.rtk_data[i]
1187 	    str_appendf(buf, buflen,
1188 			   "{\"ident\":%u,"
1189 			   "\"L1\":{\"ind\":%u,\"prange\":%8.2f,"
1190 			   "\"delta\":%6.4f,\"lockt\":%u,"
1191 			   "\"amb\":%u,\"CNR\":%.2f},"
1192 			   "\"L2\":{\"ind\":%u,\"prange\":%8.2f,"
1193 			   "\"delta\":%6.4f,\"lockt\":%u,"
1194 			   "\"CNR\":%.2f}"
1195 			   "},",
1196 			   R1004.ident,
1197 			   CODE(R1004.L1.indicator),
1198 			   R1004.L1.pseudorange,
1199 			   R1004.L1.rangediff,
1200 			   INT(R1004.L1.locktime),
1201 			   INT(R1004.L1.ambiguity),
1202 			   R1004.L1.CNR,
1203 			   CODE(R1004.L2.indicator),
1204 			   R1004.L2.pseudorange,
1205 			   R1004.L2.rangediff,
1206 			   INT(R1004.L2.locktime),
1207 			   R1004.L2.CNR);
1208 #undef R1004
1209 	}
1210 	str_rstrip_char(buf, ',');
1211 	(void)strlcat(buf, "]", buflen);
1212 	break;
1213 
1214     case 1005:
1215 	str_appendf(buf, buflen,
1216 		       "\"station_id\":%u,\"system\":[",
1217 		       rtcm->rtcmtypes.rtcm3_1005.station_id);
1218 	if ((rtcm->rtcmtypes.rtcm3_1005.system & 0x04)!=0)
1219 	    (void)strlcat(buf, "\"GPS\",", buflen);
1220 	if ((rtcm->rtcmtypes.rtcm3_1005.system & 0x02)!=0)
1221 	    (void)strlcat(buf, "\"GLONASS\",", buflen);
1222 	if ((rtcm->rtcmtypes.rtcm3_1005.system & 0x01)!=0)
1223 	    (void)strlcat(buf, "\"GALILEO\",", buflen);
1224 	str_rstrip_char(buf, ',');
1225 	str_appendf(buf, buflen,
1226 		       "],\"refstation\":%s,\"sro\":%s,"
1227 		       "\"x\":%.4f,\"y\":%.4f,\"z\":%.4f,",
1228 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1005.reference_station),
1229 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1005.single_receiver),
1230 		       rtcm->rtcmtypes.rtcm3_1005.ecef_x,
1231 		       rtcm->rtcmtypes.rtcm3_1005.ecef_y,
1232 		       rtcm->rtcmtypes.rtcm3_1005.ecef_z);
1233 	break;
1234 
1235     case 1006:
1236 	str_appendf(buf, buflen,
1237 		       "\"station_id\":%u,\"system\":[",
1238 		       rtcm->rtcmtypes.rtcm3_1006.station_id);
1239 	if ((rtcm->rtcmtypes.rtcm3_1006.system & 0x04)!=0)
1240 	    (void)strlcat(buf, "\"GPS\",", buflen);
1241 	if ((rtcm->rtcmtypes.rtcm3_1006.system & 0x02)!=0)
1242 	    (void)strlcat(buf, "\"GLONASS\",", buflen);
1243 	if ((rtcm->rtcmtypes.rtcm3_1006.system & 0x01)!=0)
1244 	    (void)strlcat(buf, "\"GALILEO\",", buflen);
1245 	str_rstrip_char(buf, ',');
1246 	str_appendf(buf, buflen,
1247 		       "],\"refstation\":%s,\"sro\":%s,"
1248 		       "\"x\":%.4f,\"y\":%.4f,\"z\":%.4f,",
1249 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1006.reference_station),
1250 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1006.single_receiver),
1251 		       rtcm->rtcmtypes.rtcm3_1006.ecef_x,
1252 		       rtcm->rtcmtypes.rtcm3_1006.ecef_y,
1253 		       rtcm->rtcmtypes.rtcm3_1006.ecef_z);
1254 	str_appendf(buf, buflen,
1255 		       "\"h\":%.4f,",
1256 		       rtcm->rtcmtypes.rtcm3_1006.height);
1257 	break;
1258 
1259     case 1007:
1260 	str_appendf(buf, buflen,
1261 		       "\"station_id\":%u,\"desc\":\"%s\",\"setup_id\":%u",
1262 		       rtcm->rtcmtypes.rtcm3_1007.station_id,
1263 		       rtcm->rtcmtypes.rtcm3_1007.descriptor,
1264 		       rtcm->rtcmtypes.rtcm3_1007.setup_id);
1265 	break;
1266 
1267     case 1008:
1268 	str_appendf(buf, buflen,
1269 		       "\"station_id\":%u,\"desc\":\"%s\","
1270 		       "\"setup_id\":%u,\"serial\":\"%s\"",
1271 		       rtcm->rtcmtypes.rtcm3_1008.station_id,
1272 		       rtcm->rtcmtypes.rtcm3_1008.descriptor,
1273 		       INT(rtcm->rtcmtypes.rtcm3_1008.setup_id),
1274 		       rtcm->rtcmtypes.rtcm3_1008.serial);
1275 	break;
1276 
1277     case 1009:
1278 	str_appendf(buf, buflen,
1279 		       "\"station_id\":%u,\"tow\":%lld,\"sync\":\"%s\","
1280 		       "\"smoothing\":\"%s\",\"interval\":\"%u\","
1281 		       "\"satcount\":\"%u\",",
1282 		       rtcm->rtcmtypes.rtcm3_1009.header.station_id,
1283 		       (long long)rtcm->rtcmtypes.rtcm3_1009.header.tow,
1284 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1009.header.sync),
1285 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1009.header.smoothing),
1286 		       rtcm->rtcmtypes.rtcm3_1009.header.interval,
1287 		       rtcm->rtcmtypes.rtcm3_1009.header.satcount);
1288 	(void)strlcat(buf, "\"satellites\":[", buflen);
1289 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1009.header.satcount; i++) {
1290 #define R1009 rtcm->rtcmtypes.rtcm3_1009.rtk_data[i]
1291 	    str_appendf(buf, buflen,
1292 			   "{\"ident\":%u,\"ind\":%u,\"channel\":%u,"
1293 			   "\"prange\":%8.2f,\"delta\":%6.4f,\"lockt\":%u},",
1294 			   R1009.ident,
1295 			   CODE(R1009.L1.indicator),
1296 			   R1009.L1.channel,
1297 			   R1009.L1.pseudorange,
1298 			   R1009.L1.rangediff,
1299 			   INT(R1009.L1.locktime));
1300 #undef R1009
1301 	}
1302 	str_rstrip_char(buf, ',');
1303 	(void)strlcat(buf, "]", buflen);
1304 	break;
1305 
1306     case 1010:
1307 	str_appendf(buf, buflen,
1308 		       "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1309 		       "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1310 		       rtcm->rtcmtypes.rtcm3_1010.header.station_id,
1311 		       (int)rtcm->rtcmtypes.rtcm3_1010.header.tow,
1312 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1010.header.sync),
1313 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1010.header.smoothing),
1314 		       rtcm->rtcmtypes.rtcm3_1010.header.interval);
1315 	(void)strlcat(buf, "\"satellites\":[", buflen);
1316 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1010.header.satcount; i++) {
1317 #define R1010 rtcm->rtcmtypes.rtcm3_1010.rtk_data[i]
1318 	    str_appendf(buf, buflen,
1319 			   "{\"ident\":%u,\"ind\":%u,\"channel\":%u,"
1320 			   "\"prange\":%8.2f,\"delta\":%6.4f,\"lockt\":%u,"
1321 			   "\"amb\":%u,\"CNR\":%.2f},",
1322 			   R1010.ident,
1323 			   CODE(R1010.L1.indicator),
1324 			   R1010.L1.channel,
1325 			   R1010.L1.pseudorange,
1326 			   R1010.L1.rangediff,
1327 			   INT(R1010.L1.locktime),
1328 			   INT(R1010.L1.ambiguity),
1329 			   R1010.L1.CNR);
1330 #undef R1010
1331 	}
1332 	str_rstrip_char(buf, ',');
1333 	(void)strlcat(buf, "]", buflen);
1334 	break;
1335 
1336     case 1011:
1337 	str_appendf(buf, buflen,
1338 		       "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1339 		       "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1340 		       rtcm->rtcmtypes.rtcm3_1011.header.station_id,
1341 		       (int)rtcm->rtcmtypes.rtcm3_1011.header.tow,
1342 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1011.header.sync),
1343 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1011.header.smoothing),
1344 		       rtcm->rtcmtypes.rtcm3_1011.header.interval);
1345 	(void)strlcat(buf, "\"satellites\":[", buflen);
1346 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1011.header.satcount; i++) {
1347 #define R1011 rtcm->rtcmtypes.rtcm3_1011.rtk_data[i]
1348 	    str_appendf(buf, buflen,
1349 			   "{\"ident\":%u,\"channel\":%u,"
1350 			   "\"L1\":{\"ind\":%u,"
1351 			   "\"prange\":%8.2f,\"delta\":%6.4f,\"lockt\":%u},"
1352 			   "\"L2:{\"ind\":%u,\"prange\":%8.2f,"
1353 			   "\"delta\":%6.4f,\"lockt\":%u}"
1354 			   "}",
1355 			   R1011.ident,R1011.L1.channel,
1356 			   CODE(R1011.L1.indicator),
1357 			   R1011.L1.pseudorange,
1358 			   R1011.L1.rangediff,
1359 			   INT(R1011.L1.locktime),
1360 			   CODE(R1011.L2.indicator),
1361 			   R1011.L2.pseudorange,
1362 			   R1011.L2.rangediff,
1363 			   INT(R1011.L2.locktime));
1364 #undef R1011
1365 	}
1366 	str_rstrip_char(buf, ',');
1367 	(void)strlcat(buf, "]", buflen);
1368 	break;
1369 
1370     case 1012:
1371 	str_appendf(buf, buflen,
1372 		       "\"station_id\":%u,\"tow\":%d,\"sync\":\"%s\","
1373 		       "\"smoothing\":\"%s\",\"interval\":\"%u\",",
1374 		       rtcm->rtcmtypes.rtcm3_1012.header.station_id,
1375 		       (int)rtcm->rtcmtypes.rtcm3_1012.header.tow,
1376 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1012.header.sync),
1377 		       JSON_BOOL(rtcm->rtcmtypes.rtcm3_1012.header.smoothing),
1378 		       rtcm->rtcmtypes.rtcm3_1012.header.interval);
1379 	(void)strlcat(buf, "\"satellites\":[", buflen);
1380 	for (i = 0; i < rtcm->rtcmtypes.rtcm3_1012.header.satcount; i++) {
1381 #define R1012 rtcm->rtcmtypes.rtcm3_1012.rtk_data[i]
1382 	    str_appendf(buf, buflen,
1383 			   "{\"ident\":%u,\"channel\":%u,"
1384 			   "\"L1\":{\"ind\":%u,\"prange\":%8.2f,"
1385 			   "\"delta\":%6.4f,\"lockt\":%u,\"amb\":%u,"
1386 			   "\"CNR\":%.2f},"
1387 			   "\"L2\":{\"ind\":%u,\"prange\":%8.2f,"
1388 			   "\"delta\":%6.4f,\"lockt\":%u,"
1389 			   "\"CNR\":%.2f},"
1390 			   "},",
1391 			   R1012.ident,
1392 			   R1012.L1.channel,
1393 			   CODE(R1012.L1.indicator),
1394 			   R1012.L1.pseudorange,
1395 			   R1012.L1.rangediff,
1396 			   INT(R1012.L1.locktime),
1397 			   INT(R1012.L1.ambiguity),
1398 			   R1012.L1.CNR,
1399 			   CODE(R1012.L2.indicator),
1400 			   R1012.L2.pseudorange,
1401 			   R1012.L2.rangediff,
1402 			   INT(R1012.L2.locktime),
1403 			   R1012.L2.CNR);
1404 #undef R1012
1405 	}
1406 	str_rstrip_char(buf, ',');
1407 	(void)strlcat(buf, "]", buflen);
1408 	break;
1409 
1410     case 1013:
1411 	str_appendf(buf, buflen,
1412 		       "\"station_id\":%u,\"mjd\":%u,\"sec\":%u,"
1413 		       "\"leapsecs\":%u,",
1414 		       rtcm->rtcmtypes.rtcm3_1013.station_id,
1415 		       rtcm->rtcmtypes.rtcm3_1013.mjd,
1416 		       rtcm->rtcmtypes.rtcm3_1013.sod,
1417 		       INT(rtcm->rtcmtypes.rtcm3_1013.leapsecs));
1418 	for (i = 0; i < (unsigned short)rtcm->rtcmtypes.rtcm3_1013.ncount; i++)
1419 	    str_appendf(buf, buflen,
1420 			   "{\"id\":%u,\"sync\":\"%s\",\"interval\":%u}",
1421 			   rtcm->rtcmtypes.rtcm3_1013.announcements[i].id,
1422 			   JSON_BOOL(rtcm->rtcmtypes.rtcm3_1013.
1423 				announcements[i].sync),
1424 			   rtcm->rtcmtypes.rtcm3_1013.
1425 			   announcements[i].interval);
1426 	break;
1427 
1428     case 1014:
1429 	str_appendf(buf, buflen,
1430 		       "\"netid\":%u,\"subnetid\":%u,\"statcount\":%u"
1431 		       "\"master\":%u,\"aux\":%u,\"lat\":%f,\"lon\":%f,"
1432                        "\"alt\":%f,",
1433 		       rtcm->rtcmtypes.rtcm3_1014.network_id,
1434 		       rtcm->rtcmtypes.rtcm3_1014.subnetwork_id,
1435 		       rtcm->rtcmtypes.rtcm3_1014.stationcount,
1436 		       rtcm->rtcmtypes.rtcm3_1014.master_id,
1437 		       rtcm->rtcmtypes.rtcm3_1014.aux_id,
1438 		       rtcm->rtcmtypes.rtcm3_1014.d_lat,
1439 		       rtcm->rtcmtypes.rtcm3_1014.d_lon,
1440 		       rtcm->rtcmtypes.rtcm3_1014.d_alt);
1441 	break;
1442 
1443     case 1015:
1444 	break;
1445 
1446     case 1016:
1447 	break;
1448 
1449     case 1017:
1450 	break;
1451 
1452     case 1018:
1453 	break;
1454 
1455     case 1019:
1456 	break;
1457 
1458     case 1020:
1459 	break;
1460 
1461     case 1029:
1462 	str_appendf(buf, buflen,
1463 		       "\"station_id\":%u,\"mjd\":%u,\"sec\":%u,"
1464 		       "\"len\":%zd,\"units\":%zd,\"msg\":\"%s\",",
1465 		       rtcm->rtcmtypes.rtcm3_1029.station_id,
1466 		       rtcm->rtcmtypes.rtcm3_1029.mjd,
1467 		       rtcm->rtcmtypes.rtcm3_1029.sod,
1468 		       rtcm->rtcmtypes.rtcm3_1029.len,
1469 		       rtcm->rtcmtypes.rtcm3_1029.unicode_units,
1470 		       json_stringify(buf1, sizeof(buf1),
1471 				      (char *)rtcm->rtcmtypes.rtcm3_1029.text));
1472 	break;
1473 
1474     case 1033:
1475 	str_appendf(buf, buflen,
1476 		       "\"station_id\":%u,\"desc\":\"%s\","
1477 		       "\"setup_id\":%u,\"serial\":\"%s\","
1478 		       "\"receiver\":\"%s\",\"firmware\":\"%s\"",
1479 		       rtcm->rtcmtypes.rtcm3_1033.station_id,
1480 		       rtcm->rtcmtypes.rtcm3_1033.descriptor,
1481 		       INT(rtcm->rtcmtypes.rtcm3_1033.setup_id),
1482 		       rtcm->rtcmtypes.rtcm3_1033.serial,
1483 		       rtcm->rtcmtypes.rtcm3_1033.receiver,
1484 		       rtcm->rtcmtypes.rtcm3_1033.firmware);
1485 	break;
1486 
1487     default:
1488 	(void)strlcat(buf, "\"data\":[", buflen);
1489 	for (n = 0; n < rtcm->length; n++)
1490 	    str_appendf(buf, buflen,
1491 			"\"0x%02x\",",(unsigned int)rtcm->rtcmtypes.data[n]);
1492 	str_rstrip_char(buf, ',');
1493 	(void)strlcat(buf, "]", buflen);
1494 	break;
1495     }
1496 
1497     str_rstrip_char(buf, ',');
1498     (void)strlcat(buf, "}\r\n", buflen);
1499 #undef CODE
1500 #undef INT
1501 }
1502 #endif /* defined(RTCM104V3_ENABLE) */
1503 
1504 #if defined(AIVDM_ENABLE)
json_aivdm_dump(const struct ais_t * ais,const char * device,bool scaled,char * buf,size_t buflen)1505 void json_aivdm_dump(const struct ais_t *ais,
1506 		     const char *device, bool scaled,
1507 		     char *buf, size_t buflen)
1508 {
1509     char buf1[JSON_VAL_MAX * 2 + 1];
1510     char buf2[JSON_VAL_MAX * 2 + 1];
1511     char buf3[JSON_VAL_MAX * 2 + 1];
1512     char scratchbuf[MAX_PACKET_LENGTH*2+1];
1513     int i;
1514 
1515     static char *nav_legends[] = {
1516 	"Under way using engine",
1517 	"At anchor",
1518 	"Not under command",
1519 	"Restricted manoeuverability",
1520 	"Constrained by her draught",
1521 	"Moored",
1522 	"Aground",
1523 	"Engaged in fishing",
1524 	"Under way sailing",
1525 	"Reserved for HSC",
1526 	"Reserved for WIG",
1527 	"Reserved",
1528 	"Reserved",
1529 	"Reserved",
1530 	"Reserved",
1531 	"Not defined",
1532     };
1533 
1534     static char *epfd_legends[] = {
1535 	"Undefined",
1536 	"GPS",
1537 	"GLONASS",
1538 	"Combined GPS/GLONASS",
1539 	"Loran-C",
1540 	"Chayka",
1541 	"Integrated navigation system",
1542 	"Surveyed",
1543 	"Galileo",
1544     };
1545 
1546 #define EPFD_DISPLAY(n) (((n) < (unsigned int)NITEMS(epfd_legends)) ? epfd_legends[n] : "INVALID EPFD")
1547 
1548     static char *ship_type_legends[100] = {
1549 	"Not available",
1550 	"Reserved for future use",
1551 	"Reserved for future use",
1552 	"Reserved for future use",
1553 	"Reserved for future use",
1554 	"Reserved for future use",
1555 	"Reserved for future use",
1556 	"Reserved for future use",
1557 	"Reserved for future use",
1558 	"Reserved for future use",
1559 	"Reserved for future use",
1560 	"Reserved for future use",
1561 	"Reserved for future use",
1562 	"Reserved for future use",
1563 	"Reserved for future use",
1564 	"Reserved for future use",
1565 	"Reserved for future use",
1566 	"Reserved for future use",
1567 	"Reserved for future use",
1568 	"Reserved for future use",
1569 	"Wing in ground (WIG) - all ships of this type",
1570 	"Wing in ground (WIG) - Hazardous category A",
1571 	"Wing in ground (WIG) - Hazardous category B",
1572 	"Wing in ground (WIG) - Hazardous category C",
1573 	"Wing in ground (WIG) - Hazardous category D",
1574 	"Wing in ground (WIG) - Reserved for future use",
1575 	"Wing in ground (WIG) - Reserved for future use",
1576 	"Wing in ground (WIG) - Reserved for future use",
1577 	"Wing in ground (WIG) - Reserved for future use",
1578 	"Wing in ground (WIG) - Reserved for future use",
1579 	"Fishing",
1580 	"Towing",
1581 	"Towing: length exceeds 200m or breadth exceeds 25m",
1582 	"Dredging or underwater ops",
1583 	"Diving ops",
1584 	"Military ops",
1585 	"Sailing",
1586 	"Pleasure Craft",
1587 	"Reserved",
1588 	"Reserved",
1589 	"High speed craft (HSC) - all ships of this type",
1590 	"High speed craft (HSC) - Hazardous category A",
1591 	"High speed craft (HSC) - Hazardous category B",
1592 	"High speed craft (HSC) - Hazardous category C",
1593 	"High speed craft (HSC) - Hazardous category D",
1594 	"High speed craft (HSC) - Reserved for future use",
1595 	"High speed craft (HSC) - Reserved for future use",
1596 	"High speed craft (HSC) - Reserved for future use",
1597 	"High speed craft (HSC) - Reserved for future use",
1598 	"High speed craft (HSC) - No additional information",
1599 	"Pilot Vessel",
1600 	"Search and Rescue vessel",
1601 	"Tug",
1602 	"Port Tender",
1603 	"Anti-pollution equipment",
1604 	"Law Enforcement",
1605 	"Spare - Local Vessel",
1606 	"Spare - Local Vessel",
1607 	"Medical Transport",
1608 	"Ship according to RR Resolution No. 18",
1609 	"Passenger - all ships of this type",
1610 	"Passenger - Hazardous category A",
1611 	"Passenger - Hazardous category B",
1612 	"Passenger - Hazardous category C",
1613 	"Passenger - Hazardous category D",
1614 	"Passenger - Reserved for future use",
1615 	"Passenger - Reserved for future use",
1616 	"Passenger - Reserved for future use",
1617 	"Passenger - Reserved for future use",
1618 	"Passenger - No additional information",
1619 	"Cargo - all ships of this type",
1620 	"Cargo - Hazardous category A",
1621 	"Cargo - Hazardous category B",
1622 	"Cargo - Hazardous category C",
1623 	"Cargo - Hazardous category D",
1624 	"Cargo - Reserved for future use",
1625 	"Cargo - Reserved for future use",
1626 	"Cargo - Reserved for future use",
1627 	"Cargo - Reserved for future use",
1628 	"Cargo - No additional information",
1629 	"Tanker - all ships of this type",
1630 	"Tanker - Hazardous category A",
1631 	"Tanker - Hazardous category B",
1632 	"Tanker - Hazardous category C",
1633 	"Tanker - Hazardous category D",
1634 	"Tanker - Reserved for future use",
1635 	"Tanker - Reserved for future use",
1636 	"Tanker - Reserved for future use",
1637 	"Tanker - Reserved for future use",
1638 	"Tanker - No additional information",
1639 	"Other Type - all ships of this type",
1640 	"Other Type - Hazardous category A",
1641 	"Other Type - Hazardous category B",
1642 	"Other Type - Hazardous category C",
1643 	"Other Type - Hazardous category D",
1644 	"Other Type - Reserved for future use",
1645 	"Other Type - Reserved for future use",
1646 	"Other Type - Reserved for future use",
1647 	"Other Type - Reserved for future use",
1648 	"Other Type - no additional information",
1649     };
1650 
1651 #define SHIPTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(ship_type_legends)) ? ship_type_legends[n] : "INVALID SHIP TYPE")
1652 
1653     static const char *station_type_legends[] = {
1654 	"All types of mobiles",
1655 	"Reserved for future use",
1656 	"All types of Class B mobile stations",
1657 	"SAR airborne mobile station",
1658 	"Aid to Navigation station",
1659 	"Class B shipborne mobile station",
1660 	"Regional use and inland waterways",
1661 	"Regional use and inland waterways",
1662 	"Regional use and inland waterways",
1663 	"Regional use and inland waterways",
1664 	"Reserved for future use",
1665 	"Reserved for future use",
1666 	"Reserved for future use",
1667 	"Reserved for future use",
1668 	"Reserved for future use",
1669 	"Reserved for future use",
1670     };
1671 
1672 #define STATIONTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(station_type_legends)) ? station_type_legends[n] : "INVALID STATION TYPE")
1673 
1674     static const char *navaid_type_legends[] = {
1675 	"Unspecified",
1676 	"Reference point",
1677 	"RACON",
1678 	"Fixed offshore structure",
1679 	"Spare, Reserved for future use.",
1680 	"Light, without sectors",
1681 	"Light, with sectors",
1682 	"Leading Light Front",
1683 	"Leading Light Rear",
1684 	"Beacon, Cardinal N",
1685 	"Beacon, Cardinal E",
1686 	"Beacon, Cardinal S",
1687 	"Beacon, Cardinal W",
1688 	"Beacon, Port hand",
1689 	"Beacon, Starboard hand",
1690 	"Beacon, Preferred Channel port hand",
1691 	"Beacon, Preferred Channel starboard hand",
1692 	"Beacon, Isolated danger",
1693 	"Beacon, Safe water",
1694 	"Beacon, Special mark",
1695 	"Cardinal Mark N",
1696 	"Cardinal Mark E",
1697 	"Cardinal Mark S",
1698 	"Cardinal Mark W",
1699 	"Port hand Mark",
1700 	"Starboard hand Mark",
1701 	"Preferred Channel Port hand",
1702 	"Preferred Channel Starboard hand",
1703 	"Isolated danger",
1704 	"Safe Water",
1705 	"Special Mark",
1706 	"Light Vessel / LANBY / Rigs",
1707     };
1708 
1709 #define NAVAIDTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(navaid_type_legends)) ? navaid_type_legends[n] : "INVALID NAVAID TYPE")
1710 
1711     // cppcheck-suppress variableScope
1712     static const char *signal_legends[] = {
1713 	"N/A",
1714 	"Serious emergency – stop or divert according to instructions.",
1715 	"Vessels shall not proceed.",
1716 	"Vessels may proceed. One way traffic.",
1717 	"Vessels may proceed. Two way traffic.",
1718 	"Vessels shall proceed on specific orders only.",
1719 	"Vessels in main channel shall not proceed.",
1720 	"Vessels in main channel shall proceed on specific orders only.",
1721 	"Vessels in main channel shall proceed on specific orders only.",
1722 	"I = \"in-bound\" only acceptable.",
1723 	"O = \"out-bound\" only acceptable.",
1724 	"F = both \"in- and out-bound\" acceptable.",
1725 	"XI = Code will shift to \"I\" in due time.",
1726 	"XO = Code will shift to \"O\" in due time.",
1727 	"X = Vessels shall proceed only on direction.",
1728     };
1729 
1730 #define SIGNAL_DISPLAY(n) (((n) < (unsigned int)NITEMS(signal_legends)) ? signal_legends[n] : "INVALID SIGNAL TYPE")
1731 
1732     static const char *route_type[32] = {
1733 	"Undefined (default)",
1734 	"Mandatory",
1735 	"Recommended",
1736 	"Alternative",
1737 	"Recommended route through ice",
1738 	"Ship route plan",
1739 	"Reserved for future use.",
1740 	"Reserved for future use.",
1741 	"Reserved for future use.",
1742 	"Reserved for future use.",
1743 	"Reserved for future use.",
1744 	"Reserved for future use.",
1745 	"Reserved for future use.",
1746 	"Reserved for future use.",
1747 	"Reserved for future use.",
1748 	"Reserved for future use.",
1749 	"Reserved for future use.",
1750 	"Reserved for future use.",
1751 	"Reserved for future use.",
1752 	"Reserved for future use.",
1753 	"Reserved for future use.",
1754 	"Reserved for future use.",
1755 	"Reserved for future use.",
1756 	"Reserved for future use.",
1757 	"Reserved for future use.",
1758 	"Reserved for future use.",
1759 	"Reserved for future use.",
1760 	"Reserved for future use.",
1761 	"Reserved for future use.",
1762 	"Reserved for future use.",
1763 	"Reserved for future use.",
1764 	"Cancel route identified by message linkage",
1765     };
1766 
1767     // cppcheck-suppress variableScope
1768     static const char *idtypes[] = {
1769 	"mmsi",
1770 	"imo",
1771 	"callsign",
1772 	"other",
1773     };
1774 
1775     // cppcheck-suppress variableScope
1776     static const char *racon_status[] = {
1777 	"No RACON installed",
1778 	"RACON not monitored",
1779 	"RACON operational",
1780 	"RACON ERROR"
1781     };
1782 
1783     // cppcheck-suppress variableScope
1784     static const char *light_status[] = {
1785 	"No light or no monitoring",
1786 	"Light ON",
1787 	"Light OFF",
1788 	"Light ERROR"
1789     };
1790 
1791     // cppcheck-suppress variableScope
1792     static const char *rta_status[] = {
1793 	"Operational",
1794 	"Limited operation",
1795 	"Out of order",
1796 	"N/A",
1797     };
1798 
1799     // cppcheck-suppress variableScope
1800     const char *position_types[8] = {
1801 	"Not available",
1802 	"Port-side to",
1803 	"Starboard-side to",
1804 	"Mediterranean (end-on) mooring",
1805 	"Mooring buoy",
1806 	"Anchorage",
1807 	"Reserved for future use",
1808 	"Reserved for future use",
1809     };
1810 
1811     (void)snprintf(buf, buflen, "{\"class\":\"AIS\",");
1812     if (device != NULL && device[0] != '\0')
1813 	str_appendf(buf, buflen, "\"device\":\"%s\",", device);
1814     str_appendf(buf, buflen,
1815 		   "\"type\":%u,\"repeat\":%u,\"mmsi\":%u,\"scaled\":%s,",
1816 		   ais->type, ais->repeat, ais->mmsi, JSON_BOOL(scaled));
1817     switch (ais->type) {
1818     case 1:			/* Position Report */
1819     case 2:
1820     case 3:
1821 	if (scaled) {
1822 	    char turnlegend[20];
1823 	    char speedlegend[20];
1824 
1825 	    /*
1826 	     * Express turn as nan if not available,
1827 	     * "fastleft"/"fastright" for fast turns.
1828 	     */
1829 	    if (ais->type1.turn == -128)
1830 		(void)strlcpy(turnlegend, "\"nan\"", sizeof(turnlegend));
1831 	    else if (ais->type1.turn == -127)
1832 		(void)strlcpy(turnlegend, "\"fastleft\"", sizeof(turnlegend));
1833 	    else if (ais->type1.turn == 127)
1834 		(void)strlcpy(turnlegend, "\"fastright\"",
1835 			      sizeof(turnlegend));
1836 	    else {
1837 		double rot1 = ais->type1.turn / 4.733;
1838 		(void)snprintf(turnlegend, sizeof(turnlegend),
1839 			       "%.0f", rot1 * rot1);
1840 	    }
1841 
1842 	    /*
1843 	     * Express speed as nan if not available,
1844 	     * "fast" for fast movers.
1845 	     */
1846 	    if (ais->type1.speed == AIS_SPEED_NOT_AVAILABLE)
1847 		(void)strlcpy(speedlegend, "\"nan\"", sizeof(speedlegend));
1848 	    else if (ais->type1.speed == AIS_SPEED_FAST_MOVER)
1849 		(void)strlcpy(speedlegend, "\"fast\"", sizeof(speedlegend));
1850 	    else
1851 		(void)snprintf(speedlegend, sizeof(speedlegend),
1852 			       "%.1f", ais->type1.speed / 10.0);
1853 
1854 	    str_appendf(buf, buflen,
1855 			   "\"status\":%u,\"status_text\":\"%s\","
1856 			   "\"turn\":%s,\"speed\":%s,"
1857 			   "\"accuracy\":%s,\"lon\":%.6f,\"lat\":%.6f,"
1858 			   "\"course\":%.1f,\"heading\":%u,\"second\":%u,"
1859 			   "\"maneuver\":%u,\"raim\":%s,\"radio\":%u}\r\n",
1860 			   ais->type1.status,
1861 			   nav_legends[ais->type1.status],
1862 			   turnlegend,
1863 			   speedlegend,
1864 			   JSON_BOOL(ais->type1.accuracy),
1865 			   ais->type1.lon / AIS_LATLON_DIV,
1866 			   ais->type1.lat / AIS_LATLON_DIV,
1867 			   ais->type1.course / 10.0,
1868 			   ais->type1.heading,
1869 			   ais->type1.second,
1870 			   ais->type1.maneuver,
1871 			   JSON_BOOL(ais->type1.raim), ais->type1.radio);
1872 	} else {
1873 	    str_appendf(buf, buflen,
1874 			   "\"status\":%u,\"status_text\":\"%s\","
1875 			   "\"turn\":%d,\"speed\":%u,"
1876 			   "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
1877 			   "\"course\":%u,\"heading\":%u,\"second\":%u,"
1878 			   "\"maneuver\":%u,\"raim\":%s,\"radio\":%u}\r\n",
1879 			   ais->type1.status,
1880 			   nav_legends[ais->type1.status],
1881 			   ais->type1.turn,
1882 			   ais->type1.speed,
1883 			   JSON_BOOL(ais->type1.accuracy),
1884 			   ais->type1.lon,
1885 			   ais->type1.lat,
1886 			   ais->type1.course,
1887 			   ais->type1.heading,
1888 			   ais->type1.second,
1889 			   ais->type1.maneuver,
1890 			   JSON_BOOL(ais->type1.raim), ais->type1.radio);
1891 	}
1892 	break;
1893     case 4:			/* Base Station Report */
1894     case 11:			/* UTC/Date Response */
1895 	/* some fields have beem merged to an ISO8601 date */
1896 	if (scaled) {
1897 	    // The use of %u instead of %04u for the year is to allow
1898 	    // out-of-band year values.
1899 	    str_appendf(buf, buflen,
1900 			   "\"timestamp\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\","
1901 			   "\"accuracy\":%s,\"lon\":%.6f,\"lat\":%.6f,"
1902 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
1903 			   "\"raim\":%s,\"radio\":%u}\r\n",
1904 			   ais->type4.year,
1905 			   ais->type4.month,
1906 			   ais->type4.day,
1907 			   ais->type4.hour,
1908 			   ais->type4.minute,
1909 			   ais->type4.second,
1910 			   JSON_BOOL(ais->type4.accuracy),
1911 			   ais->type4.lon / AIS_LATLON_DIV,
1912 			   ais->type4.lat / AIS_LATLON_DIV,
1913 			   ais->type4.epfd,
1914 			   EPFD_DISPLAY(ais->type4.epfd),
1915 			   JSON_BOOL(ais->type4.raim), ais->type4.radio);
1916 	} else {
1917 	    str_appendf(buf, buflen,
1918 			   "\"timestamp\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\","
1919 			   "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
1920 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
1921 			   "\"raim\":%s,\"radio\":%u}\r\n",
1922 			   ais->type4.year,
1923 			   ais->type4.month,
1924 			   ais->type4.day,
1925 			   ais->type4.hour,
1926 			   ais->type4.minute,
1927 			   ais->type4.second,
1928 			   JSON_BOOL(ais->type4.accuracy),
1929 			   ais->type4.lon,
1930 			   ais->type4.lat,
1931 			   ais->type4.epfd,
1932 			   EPFD_DISPLAY(ais->type4.epfd),
1933 			   JSON_BOOL(ais->type4.raim), ais->type4.radio);
1934 	}
1935 	break;
1936     case 5:			/* Ship static and voyage related data */
1937 	/* some fields have beem merged to an ISO8601 partial date */
1938 	if (scaled) {
1939             /* *INDENT-OFF* */
1940 	    str_appendf(buf, buflen,
1941 			   "\"imo\":%u,\"ais_version\":%u,\"callsign\":\"%s\","
1942 			   "\"shipname\":\"%s\","
1943 			   "\"shiptype\":%u,\"shiptype_text\":\"%s\","
1944 			   "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
1945 			   "\"to_starboard\":%u,"
1946 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
1947 			   "\"eta\":\"%02u-%02uT%02u:%02uZ\","
1948 			   "\"draught\":%.1f,\"destination\":\"%s\","
1949 			   "\"dte\":%u}\r\n",
1950 			   ais->type5.imo,
1951 			   ais->type5.ais_version,
1952 			   json_stringify(buf1, sizeof(buf1),
1953 					  ais->type5.callsign),
1954 			   json_stringify(buf2, sizeof(buf2),
1955 					  ais->type5.shipname),
1956 			   ais->type5.shiptype,
1957 			   SHIPTYPE_DISPLAY(ais->type5.shiptype),
1958 			   ais->type5.to_bow, ais->type5.to_stern,
1959 			   ais->type5.to_port, ais->type5.to_starboard,
1960 			   ais->type5.epfd,
1961 			   EPFD_DISPLAY(ais->type5.epfd),
1962 			   ais->type5.month,
1963 			   ais->type5.day,
1964 			   ais->type5.hour, ais->type5.minute,
1965 			   ais->type5.draught / 10.0,
1966 			   json_stringify(buf3, sizeof(buf3),
1967 					  ais->type5.destination),
1968 			   ais->type5.dte);
1969             /* *INDENT-ON* */
1970 	} else {
1971 	    str_appendf(buf, buflen,
1972 			   "\"imo\":%u,\"ais_version\":%u,\"callsign\":\"%s\","
1973 			   "\"shipname\":\"%s\","
1974 			   "\"shiptype\":%u,\"shiptype_text\":\"%s\","
1975 			   "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
1976 			   "\"to_starboard\":%u,"
1977 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
1978 			   "\"eta\":\"%02u-%02uT%02u:%02uZ\","
1979 			   "\"draught\":%u,\"destination\":\"%s\","
1980 			   "\"dte\":%u}\r\n",
1981 			   ais->type5.imo,
1982 			   ais->type5.ais_version,
1983 			   json_stringify(buf1, sizeof(buf1),
1984 					  ais->type5.callsign),
1985 			   json_stringify(buf2, sizeof(buf2),
1986 					  ais->type5.shipname),
1987 			   ais->type5.shiptype,
1988 			   SHIPTYPE_DISPLAY(ais->type5.shiptype),
1989 			   ais->type5.to_bow,
1990 			   ais->type5.to_stern,
1991 			   ais->type5.to_port,
1992 			   ais->type5.to_starboard,
1993 			   ais->type5.epfd,
1994 			   EPFD_DISPLAY(ais->type5.epfd),
1995 			   ais->type5.month,
1996 			   ais->type5.day,
1997 			   ais->type5.hour,
1998 			   ais->type5.minute,
1999 			   ais->type5.draught,
2000 			   json_stringify(buf3, sizeof(buf3),
2001 					  ais->type5.destination),
2002 			   ais->type5.dte);
2003 	}
2004 	break;
2005     case 6:			/* Binary Message */
2006 	str_appendf(buf, buflen,
2007 		       "\"seqno\":%u,\"dest_mmsi\":%u,"
2008 		       "\"retransmit\":%s,\"dac\":%u,\"fid\":%u,",
2009 		       ais->type6.seqno,
2010 		       ais->type6.dest_mmsi,
2011 		       JSON_BOOL(ais->type6.retransmit),
2012 		       ais->type6.dac,
2013 		       ais->type6.fid);
2014 	if (!ais->type6.structured) {
2015 	    str_appendf(buf, buflen,
2016 			   "\"data\":\"%zd:%s\"}\r\n",
2017 			   ais->type6.bitcount,
2018 			   json_stringify(buf1, sizeof(buf1),
2019                               gpsd_hexdump(
2020                                   scratchbuf, sizeof(scratchbuf),
2021                                   (char *)ais->type6.bitdata,
2022                                   BITS_TO_BYTES(ais->type6.bitcount))));
2023 	    break;
2024 	}
2025 	if (ais->type6.dac == 200) {
2026 	    switch (ais->type6.fid) {
2027 	    case 21:
2028 		str_appendf(buf, buflen,
2029 		    "\"country\":\"%s\",\"locode\":\"%s\","
2030 		    "\"section\":\"%s\",\"terminal\":\"%s\","
2031 		    "\"hectometre\":\"%s\",\"eta\":\"%u-%uT%u:%u\","
2032 		    "\"tugs\":%u,\"airdraught\":%u}\r\n",
2033 		    ais->type6.dac200fid21.country,
2034 		    ais->type6.dac200fid21.locode,
2035 		    ais->type6.dac200fid21.section,
2036 		    ais->type6.dac200fid21.terminal,
2037 		    ais->type6.dac200fid21.hectometre,
2038 		    ais->type6.dac200fid21.month,
2039 		    ais->type6.dac200fid21.day,
2040 		    ais->type6.dac200fid21.hour,
2041 		    ais->type6.dac200fid21.minute,
2042 		    ais->type6.dac200fid21.tugs,
2043 		    ais->type6.dac200fid21.airdraught);
2044 		break;
2045 	    case 22:
2046 		str_appendf(buf, buflen,
2047 			       "\"country\":\"%s\",\"locode\":\"%s\","
2048 			       "\"section\":\"%s\","
2049 			       "\"terminal\":\"%s\",\"hectometre\":\"%s\","
2050 			       "\"eta\":\"%u-%uT%u:%u\","
2051 			       "\"status\":%u,\"status_text\":\"%s\"}\r\n",
2052 			       ais->type6.dac200fid22.country,
2053 			       ais->type6.dac200fid22.locode,
2054 			       ais->type6.dac200fid22.section,
2055 			       ais->type6.dac200fid22.terminal,
2056 			       ais->type6.dac200fid22.hectometre,
2057 			       ais->type6.dac200fid22.month,
2058 			       ais->type6.dac200fid22.day,
2059 			       ais->type6.dac200fid22.hour,
2060 			       ais->type6.dac200fid22.minute,
2061 			       ais->type6.dac200fid22.status,
2062 			       rta_status[ais->type6.dac200fid22.status]);
2063 		break;
2064 	    case 55:
2065 		str_appendf(buf, buflen,
2066 		    "\"crew\":%u,\"passengers\":%u,\"personnel\":%u}\r\n",
2067 
2068 		    ais->type6.dac200fid55.crew,
2069 		    ais->type6.dac200fid55.passengers,
2070 		    ais->type6.dac200fid55.personnel);
2071 		break;
2072 	    }
2073 	}
2074 	else if (ais->type6.dac == 235 || ais->type6.dac == 250) {
2075 	    switch (ais->type6.fid) {
2076 	    case 10:	/* GLA - AtoN monitoring data */
2077 		str_appendf(buf, buflen,
2078 			       "\"off_pos\":%s,\"alarm\":%s,"
2079 			       "\"stat_ext\":%u,",
2080 			       JSON_BOOL(ais->type6.dac235fid10.off_pos),
2081 			       JSON_BOOL(ais->type6.dac235fid10.alarm),
2082 			       ais->type6.dac235fid10.stat_ext);
2083 		if (scaled && ais->type6.dac235fid10.ana_int != 0)
2084 		    str_appendf(buf, buflen,
2085 				   "\"ana_int\":%.2f,",
2086 				   ais->type6.dac235fid10.ana_int*0.05);
2087 		else
2088 		    str_appendf(buf, buflen,
2089 				   "\"ana_int\":%u,",
2090 				   ais->type6.dac235fid10.ana_int);
2091 		if (scaled && ais->type6.dac235fid10.ana_ext1 != 0)
2092 		    str_appendf(buf, buflen,
2093 				   "\"ana_ext1\":%.2f,",
2094 				   ais->type6.dac235fid10.ana_ext1*0.05);
2095 		else
2096 		    str_appendf(buf, buflen,
2097 				   "\"ana_ext1\":%u,",
2098 				   ais->type6.dac235fid10.ana_ext1);
2099 		if (scaled && ais->type6.dac235fid10.ana_ext2 != 0)
2100 		    str_appendf(buf, buflen,
2101 				   "\"ana_ext2\":%.2f,",
2102 				   ais->type6.dac235fid10.ana_ext2*0.05);
2103 		else
2104 		    str_appendf(buf, buflen,
2105 				   "\"ana_ext2\":%u,",
2106 				   ais->type6.dac235fid10.ana_ext2);
2107 		str_appendf(buf, buflen,
2108 			       "\"racon\":%u,"
2109 			       "\"racon_text\":\"%s\","
2110 			       "\"light\":%u,"
2111 			       "\"light_text\":\"%s\"",
2112 			       ais->type6.dac235fid10.racon,
2113 			       racon_status[ais->type6.dac235fid10.racon],
2114 			       ais->type6.dac235fid10.light,
2115 			       light_status[ais->type6.dac235fid10.light]);
2116 		str_rstrip_char(buf, ',');
2117 		(void)strlcat(buf, "}\r\n", buflen);
2118 		break;
2119 	    }
2120 	}
2121 	else if (ais->type6.dac == 1) {
2122 	    char buf4[JSON_VAL_MAX * 2 + 1];
2123 	    switch (ais->type6.fid) {
2124 	    case 12:	/* IMO236 -Dangerous cargo indication */
2125 		/* some fields have beem merged to an ISO8601 partial date */
2126 		str_appendf(buf, buflen,
2127 			       "\"lastport\":\"%s\","
2128                                "\"departure\":\"%02u-%02uT%02u:%02uZ\","
2129 			       "\"nextport\":\"%s\","
2130                                "\"eta\":\"%02u-%02uT%02u:%02uZ\","
2131 			       "\"dangerous\":\"%s\",\"imdcat\":\"%s\","
2132 			       "\"unid\":%u,\"amount\":%u,\"unit\":%u}\r\n",
2133 			       json_stringify(buf1, sizeof(buf1),
2134 					      ais->type6.dac1fid12.lastport),
2135 			       ais->type6.dac1fid12.lmonth,
2136 			       ais->type6.dac1fid12.lday,
2137 			       ais->type6.dac1fid12.lhour,
2138 			       ais->type6.dac1fid12.lminute,
2139 			       json_stringify(buf2, sizeof(buf2),
2140 					      ais->type6.dac1fid12.nextport),
2141 			       ais->type6.dac1fid12.nmonth,
2142 			       ais->type6.dac1fid12.nday,
2143 			       ais->type6.dac1fid12.nhour,
2144 			       ais->type6.dac1fid12.nminute,
2145 			       json_stringify(buf3, sizeof(buf3),
2146 					      ais->type6.dac1fid12.dangerous),
2147 			       json_stringify(buf4, sizeof(buf4),
2148 					      ais->type6.dac1fid12.imdcat),
2149 			       ais->type6.dac1fid12.unid,
2150 			       ais->type6.dac1fid12.amount,
2151 			       ais->type6.dac1fid12.unit);
2152 		break;
2153 	    case 15: /* IMO236 - Extended Ship Static and Voyage Related Data */
2154 		str_appendf(buf, buflen,
2155 		    "\"airdraught\":%u}\r\n",
2156 		    ais->type6.dac1fid15.airdraught);
2157 		break;
2158 	    case 16:	/* IMO236 - Number of persons on board */
2159 		str_appendf(buf, buflen,
2160 			   "\"persons\":%u}\r\n",
2161                             ais->type6.dac1fid16.persons);
2162 		break;
2163 	    case 18:	/* IMO289 - Clearance time to enter port */
2164 		str_appendf(buf, buflen,
2165 			       "\"linkage\":%u,"
2166                                "\"arrival\":\"%02u-%02uT%02u:%02uZ\","
2167                                "\"portname\":\"%s\",\"destination\":\"%s\",",
2168 			       ais->type6.dac1fid18.linkage,
2169 			       ais->type6.dac1fid18.month,
2170 			       ais->type6.dac1fid18.day,
2171 			       ais->type6.dac1fid18.hour,
2172 			       ais->type6.dac1fid18.minute,
2173 			       json_stringify(buf1, sizeof(buf1),
2174 					      ais->type6.dac1fid18.portname),
2175 			       json_stringify(buf2, sizeof(buf2),
2176 					      ais->type6.dac1fid18.destination));
2177 		if (scaled)
2178 		    str_appendf(buf, buflen,
2179 				   "\"lon\":%.4f,\"lat\":%.4f}\r\n",
2180 				   ais->type6.dac1fid18.lon/AIS_LATLON3_DIV,
2181 				   ais->type6.dac1fid18.lat/AIS_LATLON3_DIV);
2182 		else
2183 		    str_appendf(buf, buflen,
2184 			       "\"lon\":%d,\"lat\":%d}\r\n",
2185 			       ais->type6.dac1fid18.lon,
2186 			       ais->type6.dac1fid18.lat);
2187 		break;
2188 	    case 20:        /* IMO289 - Berthing Data */
2189                 str_appendf(buf, buflen,
2190 			       "\"linkage\":%u,\"berth_length\":%u,"
2191 			       "\"position\":%u,\"position_text\":\"%s\","
2192 			       "\"arrival\":\"%u-%uT%u:%u\","
2193 			       "\"availability\":%u,"
2194 			       "\"agent\":%u,\"fuel\":%u,\"chandler\":%u,"
2195 			       "\"stevedore\":%u,\"electrical\":%u,"
2196 			       "\"water\":%u,\"customs\":%u,\"cartage\":%u,"
2197 			       "\"crane\":%u,\"lift\":%u,\"medical\":%u,"
2198 			       "\"navrepair\":%u,\"provisions\":%u,"
2199 			       "\"shiprepair\":%u,\"surveyor\":%u,"
2200 			       "\"steam\":%u,\"tugs\":%u,\"solidwaste\":%u,"
2201 			       "\"liquidwaste\":%u,\"hazardouswaste\":%u,"
2202 			       "\"ballast\":%u,\"additional\":%u,"
2203 			       "\"regional1\":%u,\"regional2\":%u,"
2204 			       "\"future1\":%u,\"future2\":%u,"
2205 			       "\"berth_name\":\"%s\",",
2206 			       ais->type6.dac1fid20.linkage,
2207 			       ais->type6.dac1fid20.berth_length,
2208 			       ais->type6.dac1fid20.position,
2209 			       position_types[ais->type6.dac1fid20.position],
2210 			       ais->type6.dac1fid20.month,
2211 			       ais->type6.dac1fid20.day,
2212 			       ais->type6.dac1fid20.hour,
2213 			       ais->type6.dac1fid20.minute,
2214 			       ais->type6.dac1fid20.availability,
2215 			       ais->type6.dac1fid20.agent,
2216 			       ais->type6.dac1fid20.fuel,
2217 			       ais->type6.dac1fid20.chandler,
2218 			       ais->type6.dac1fid20.stevedore,
2219 			       ais->type6.dac1fid20.electrical,
2220 			       ais->type6.dac1fid20.water,
2221 			       ais->type6.dac1fid20.customs,
2222 			       ais->type6.dac1fid20.cartage,
2223 			       ais->type6.dac1fid20.crane,
2224 			       ais->type6.dac1fid20.lift,
2225 			       ais->type6.dac1fid20.medical,
2226 			       ais->type6.dac1fid20.navrepair,
2227 			       ais->type6.dac1fid20.provisions,
2228 			       ais->type6.dac1fid20.shiprepair,
2229 			       ais->type6.dac1fid20.surveyor,
2230 			       ais->type6.dac1fid20.steam,
2231 			       ais->type6.dac1fid20.tugs,
2232 			       ais->type6.dac1fid20.solidwaste,
2233 			       ais->type6.dac1fid20.liquidwaste,
2234 			       ais->type6.dac1fid20.hazardouswaste,
2235 			       ais->type6.dac1fid20.ballast,
2236 			       ais->type6.dac1fid20.additional,
2237 			       ais->type6.dac1fid20.regional1,
2238 			       ais->type6.dac1fid20.regional2,
2239 			       ais->type6.dac1fid20.future1,
2240 			       ais->type6.dac1fid20.future2,
2241 			       json_stringify(buf1, sizeof(buf1),
2242 					      ais->type6.dac1fid20.berth_name));
2243 		if (scaled)
2244 		    str_appendf(buf, buflen,
2245 			       "\"berth_lon\":%.4f,"
2246 			       "\"berth_lat\":%.4f,"
2247 			       "\"berth_depth\":%.1f}\r\n",
2248 			       ais->type6.dac1fid20.berth_lon / AIS_LATLON3_DIV,
2249 			       ais->type6.dac1fid20.berth_lat / AIS_LATLON3_DIV,
2250 			       ais->type6.dac1fid20.berth_depth * 0.1);
2251 		else
2252 		    str_appendf(buf, buflen,
2253 			       "\"berth_lon\":%d,"
2254 			       "\"berth_lat\":%d,"
2255 			       "\"berth_depth\":%u}\r\n",
2256 			       ais->type6.dac1fid20.berth_lon,
2257 			       ais->type6.dac1fid20.berth_lat,
2258 			       ais->type6.dac1fid20.berth_depth);
2259 		break;
2260 	    case 23:    /* IMO289 - Area notice - addressed */
2261 		break;
2262 	    case 25:	/* IMO289 - Dangerous cargo indication */
2263 		str_appendf(buf, buflen,
2264 			       "\"unit\":%u,\"amount\":%u,\"cargos\":[",
2265 			       ais->type6.dac1fid25.unit,
2266 			       ais->type6.dac1fid25.amount);
2267 		for (i = 0; i < (int)ais->type6.dac1fid25.ncargos; i++)
2268 		    str_appendf(buf, buflen,
2269 				   "{\"code\":%u,\"subtype\":%u},",
2270 
2271 				   ais->type6.dac1fid25.cargos[i].code,
2272 				   ais->type6.dac1fid25.cargos[i].subtype);
2273 		str_rstrip_char(buf, ',');
2274 		(void)strlcat(buf, "]}\r\n", buflen);
2275 		break;
2276 	    case 28:	/* IMO289 - Route info - addressed */
2277 		str_appendf(buf, buflen,
2278 			       "\"linkage\":%u,\"sender\":%u,"
2279 			       "\"rtype\":%u,"
2280 			       "\"rtype_text\":\"%s\","
2281 			       "\"start\":\"%02u-%02uT%02u:%02uZ\","
2282 			       "\"duration\":%u,\"waypoints\":[",
2283 			       ais->type6.dac1fid28.linkage,
2284 			       ais->type6.dac1fid28.sender,
2285 			       ais->type6.dac1fid28.rtype,
2286 			       route_type[ais->type6.dac1fid28.rtype],
2287 			       ais->type6.dac1fid28.month,
2288 			       ais->type6.dac1fid28.day,
2289 			       ais->type6.dac1fid28.hour,
2290 			       ais->type6.dac1fid28.minute,
2291 			       ais->type6.dac1fid28.duration);
2292 		for (i = 0; i < ais->type6.dac1fid28.waycount; i++) {
2293 		    if (scaled)
2294 			str_appendf(buf, buflen,
2295 			    "{\"lon\":%.6f,\"lat\":%.6f},",
2296 			    ais->type6.dac1fid28.waypoints[i].lon / AIS_LATLON4_DIV,
2297 			    ais->type6.dac1fid28.waypoints[i].lat / AIS_LATLON4_DIV);
2298 		    else
2299 			str_appendf(buf, buflen,
2300 			    "{\"lon\":%d,\"lat\":%d},",
2301 			    ais->type6.dac1fid28.waypoints[i].lon,
2302 			    ais->type6.dac1fid28.waypoints[i].lat);
2303 		}
2304 		str_rstrip_char(buf, ',');
2305 		(void)strlcat(buf, "]}\r\n", buflen);
2306 		break;
2307 	    case 30:	/* IMO289 - Text description - addressed */
2308 		str_appendf(buf, buflen,
2309 		       "\"linkage\":%u,\"text\":\"%s\"}\r\n",
2310 		       ais->type6.dac1fid30.linkage,
2311 		       json_stringify(buf1, sizeof(buf1),
2312 				      ais->type6.dac1fid30.text));
2313 		break;
2314 	    case 14:	/* IMO236 - Tidal Window */
2315 	    case 32:	/* IMO289 - Tidal Window */
2316 	      str_appendf(buf, buflen,
2317 		  "\"month\":%u,\"day\":%u,\"tidals\":[",
2318 		  ais->type6.dac1fid32.month,
2319 		  ais->type6.dac1fid32.day);
2320 	      for (i = 0; i < ais->type6.dac1fid32.ntidals; i++) {
2321 		  const struct tidal_t *tp =  &ais->type6.dac1fid32.tidals[i];
2322 		  if (scaled)
2323 		      str_appendf(buf, buflen,
2324 			  "{\"lon\":%.4f,\"lat\":%.4f,",
2325 			  tp->lon / AIS_LATLON3_DIV,
2326 			  tp->lat / AIS_LATLON3_DIV);
2327 		  else
2328 		      str_appendf(buf, buflen,
2329 			  "{\"lon\":%d,\"lat\":%d,",
2330 			  tp->lon,
2331 			  tp->lat);
2332 		  str_appendf(buf, buflen,
2333 		      "\"from_hour\":%u,\"from_min\":%u,"
2334                       "\"to_hour\":%u,\"to_min\":%u,\"cdir\":%u,",
2335 		      tp->from_hour,
2336 		      tp->from_min,
2337 		      tp->to_hour,
2338 		      tp->to_min,
2339 		      tp->cdir);
2340 		  if (scaled)
2341 		      str_appendf(buf, buflen,
2342 			  "\"cspeed\":%.1f},",
2343 			  tp->cspeed / 10.0);
2344 		  else
2345 		      str_appendf(buf, buflen,
2346 			  "\"cspeed\":%u},",
2347 			  tp->cspeed);
2348 	      }
2349 	      str_rstrip_char(buf, ',');
2350 	      (void)strlcat(buf, "]}\r\n", buflen);
2351 	      break;
2352 	    }
2353 	}
2354 	break;
2355     case 7:			/* Binary Acknowledge */
2356     case 13:			/* Safety Related Acknowledge */
2357 	str_appendf(buf, buflen,
2358 		       "\"mmsi1\":%u,\"mmsi2\":%u,\"mmsi3\":%u,"
2359                        "\"mmsi4\":%u}\r\n",
2360 		       ais->type7.mmsi1,
2361 		       ais->type7.mmsi2, ais->type7.mmsi3, ais->type7.mmsi4);
2362 	break;
2363     case 8:			/* Binary Broadcast Message */
2364 	str_appendf(buf, buflen,
2365 		       "\"dac\":%u,\"fid\":%u,",ais->type8.dac, ais->type8.fid);
2366 	if (!ais->type8.structured) {
2367 	    str_appendf(buf, buflen,
2368 			   "\"data\":\"%zd:%s\"}\r\n",
2369 			   ais->type8.bitcount,
2370 			   json_stringify(buf1, sizeof(buf1),
2371                               gpsd_hexdump(
2372                                   scratchbuf, sizeof(scratchbuf),
2373                                   (char *)ais->type8.bitdata,
2374                                   BITS_TO_BYTES(ais->type8.bitcount))));
2375 	    break;
2376 	}
2377 	if (ais->type8.dac == 1) {
2378 	    const char *trends[] = {
2379 		"steady",
2380 		"increasing",
2381 		"decreasing",
2382 		"N/A",
2383 	    };
2384 	    // WMO 306, Code table 4.201
2385 	    const char *preciptypes[] = {
2386 		"reserved",
2387 		"rain",
2388 		"thunderstorm",
2389 		"freezing rain",
2390 		"mixed/ice",
2391 		"snow",
2392 		"reserved",
2393 		"N/A",
2394 	    };
2395 	    const char *ice[] = {
2396 		"no",
2397 		"yes",
2398 		"reserved",
2399 		"N/A",
2400 	    };
2401 	    switch (ais->type8.fid) {
2402 	    case 11:        /* IMO236 - Meteorological/Hydrological data */
2403 		/* some fields have been merged to an ISO8601 partial date */
2404 		/* layout is almost identical to FID=31 from IMO289 */
2405 		if (scaled)
2406 		    str_appendf(buf, buflen,
2407 				   "\"lat\":%.4f,\"lon\":%.4f,",
2408 				   ais->type8.dac1fid11.lat / AIS_LATLON3_DIV,
2409 				   ais->type8.dac1fid11.lon / AIS_LATLON3_DIV);
2410 		else
2411 		    str_appendf(buf, buflen,
2412 				   "\"lat\":%d,\"lon\":%d,",
2413 				   ais->type8.dac1fid11.lat,
2414 				   ais->type8.dac1fid11.lon);
2415 		str_appendf(buf, buflen,
2416 			       "\"timestamp\":\"%02uT%02u:%02uZ\","
2417 			       "\"wspeed\":%u,\"wgust\":%u,\"wdir\":%u,"
2418 			       "\"wgustdir\":%u,\"humidity\":%u,",
2419 			       ais->type8.dac1fid11.day,
2420 			       ais->type8.dac1fid11.hour,
2421 			       ais->type8.dac1fid11.minute,
2422 			       ais->type8.dac1fid11.wspeed,
2423 			       ais->type8.dac1fid11.wgust,
2424 			       ais->type8.dac1fid11.wdir,
2425 			       ais->type8.dac1fid11.wgustdir,
2426 			       ais->type8.dac1fid11.humidity);
2427 		if (scaled)
2428 		    str_appendf(buf, buflen,
2429 				   "\"airtemp\":%.1f,\"dewpoint\":%.1f,"
2430 				   "\"pressure\":%u,\"pressuretend\":\"%s\",",
2431 				   ((signed int)ais->type8.dac1fid11.airtemp - DAC1FID11_AIRTEMP_OFFSET) / DAC1FID11_AIRTEMP_DIV,
2432 				   ((signed int)ais->type8.dac1fid11.dewpoint - DAC1FID11_DEWPOINT_OFFSET) / DAC1FID11_DEWPOINT_DIV,
2433 				   ais->type8.dac1fid11.pressure - DAC1FID11_PRESSURE_OFFSET,
2434 				   trends[ais->type8.dac1fid11.pressuretend]);
2435 		else
2436 		    str_appendf(buf, buflen,
2437 				   "\"airtemp\":%u,\"dewpoint\":%u,"
2438 				   "\"pressure\":%u,\"pressuretend\":%u,",
2439 				   ais->type8.dac1fid11.airtemp,
2440 				   ais->type8.dac1fid11.dewpoint,
2441 				   ais->type8.dac1fid11.pressure,
2442 				   ais->type8.dac1fid11.pressuretend);
2443 
2444 		if (scaled)
2445 		    str_appendf(buf, buflen,
2446 				   "\"visibility\":%.1f,",
2447 				   ais->type8.dac1fid11.visibility / DAC1FID11_VISIBILITY_DIV);
2448 		else
2449 		    str_appendf(buf, buflen,
2450 				   "\"visibility\":%u,",
2451 				   ais->type8.dac1fid11.visibility);
2452 		if (!scaled)
2453 		    str_appendf(buf, buflen,
2454 				   "\"waterlevel\":%d,",
2455 				   ais->type8.dac1fid11.waterlevel);
2456 		else
2457 		    str_appendf(buf, buflen,
2458 				   "\"waterlevel\":%.1f,",
2459 				   ((signed int)ais->type8.dac1fid11.waterlevel - DAC1FID11_WATERLEVEL_OFFSET) / DAC1FID11_WATERLEVEL_DIV);
2460 
2461 		if (scaled) {
2462 		    str_appendf(buf, buflen,
2463 				   "\"leveltrend\":\"%s\","
2464 				   "\"cspeed\":%.1f,\"cdir\":%u,"
2465 				   "\"cspeed2\":%.1f,\"cdir2\":%u,"
2466                                    "\"cdepth2\":%u,"
2467 				   "\"cspeed3\":%.1f,\"cdir3\":%u,"
2468                                    "\"cdepth3\":%u,"
2469 				   "\"waveheight\":%.1f,\"waveperiod\":%u,"
2470                                    "\"wavedir\":%u,"
2471 				   "\"swellheight\":%.1f,\"swellperiod\":%u,"
2472                                    "\"swelldir\":%u,"
2473 				   "\"seastate\":%u,\"watertemp\":%.1f,"
2474 				   "\"preciptype\":%u,"
2475                                    "\"preciptype_text\":\"%s\","
2476 				   "\"salinity\":%.1f,\"ice\":%u,"
2477                                    "\"ice_text\":\"%s\"",
2478 				   trends[ais->type8.dac1fid11.leveltrend],
2479 				   ais->type8.dac1fid11.cspeed / DAC1FID11_CSPEED_DIV,
2480 				   ais->type8.dac1fid11.cdir,
2481 				   ais->type8.dac1fid11.cspeed2 / DAC1FID11_CSPEED_DIV,
2482 				   ais->type8.dac1fid11.cdir2,
2483 				   ais->type8.dac1fid11.cdepth2,
2484 				   ais->type8.dac1fid11.cspeed3 / DAC1FID11_CSPEED_DIV,
2485 				   ais->type8.dac1fid11.cdir3,
2486 				   ais->type8.dac1fid11.cdepth3,
2487 				   ais->type8.dac1fid11.waveheight / DAC1FID11_WAVEHEIGHT_DIV,
2488 				   ais->type8.dac1fid11.waveperiod,
2489 				   ais->type8.dac1fid11.wavedir,
2490 				   ais->type8.dac1fid11.swellheight / DAC1FID11_WAVEHEIGHT_DIV,
2491 				   ais->type8.dac1fid11.swellperiod,
2492 				   ais->type8.dac1fid11.swelldir,
2493 				   ais->type8.dac1fid11.seastate,
2494 				   ((signed int)ais->type8.dac1fid11.watertemp - DAC1FID11_WATERTEMP_OFFSET) / DAC1FID11_WATERTEMP_DIV,
2495 				   ais->type8.dac1fid11.preciptype,
2496 				   preciptypes[ais->type8.dac1fid11.preciptype],
2497 				   ais->type8.dac1fid11.salinity / DAC1FID11_SALINITY_DIV,
2498 				   ais->type8.dac1fid11.ice,
2499 				   ice[ais->type8.dac1fid11.ice]);
2500 		} else
2501 		    str_appendf(buf, buflen,
2502 				   "\"leveltrend\":%u,"
2503 				   "\"cspeed\":%u,\"cdir\":%u,"
2504 				   "\"cspeed2\":%u,\"cdir2\":%u,"
2505                                    "\"cdepth2\":%u,"
2506 				   "\"cspeed3\":%u,\"cdir3\":%u,"
2507                                    "\"cdepth3\":%u,"
2508 				   "\"waveheight\":%u,\"waveperiod\":%u,"
2509                                    "\"wavedir\":%u,"
2510 				   "\"swellheight\":%u,\"swellperiod\":%u,"
2511                                    "\"swelldir\":%u,"
2512 				   "\"seastate\":%u,\"watertemp\":%u,"
2513 				   "\"preciptype\":%u,"
2514                                    "\"preciptype_text\":\"%s\","
2515 				   "\"salinity\":%u,\"ice\":%u,"
2516                                    "\"ice_text\":\"%s\"",
2517 				   ais->type8.dac1fid11.leveltrend,
2518 				   ais->type8.dac1fid11.cspeed,
2519 				   ais->type8.dac1fid11.cdir,
2520 				   ais->type8.dac1fid11.cspeed2,
2521 				   ais->type8.dac1fid11.cdir2,
2522 				   ais->type8.dac1fid11.cdepth2,
2523 				   ais->type8.dac1fid11.cspeed3,
2524 				   ais->type8.dac1fid11.cdir3,
2525 				   ais->type8.dac1fid11.cdepth3,
2526 				   ais->type8.dac1fid11.waveheight,
2527 				   ais->type8.dac1fid11.waveperiod,
2528 				   ais->type8.dac1fid11.wavedir,
2529 				   ais->type8.dac1fid11.swellheight,
2530 				   ais->type8.dac1fid11.swellperiod,
2531 				   ais->type8.dac1fid11.swelldir,
2532 				   ais->type8.dac1fid11.seastate,
2533 				   ais->type8.dac1fid11.watertemp,
2534 				   ais->type8.dac1fid11.preciptype,
2535 				   preciptypes[ais->type8.dac1fid11.preciptype],
2536 				   ais->type8.dac1fid11.salinity,
2537 				   ais->type8.dac1fid11.ice,
2538 				   ice[ais->type8.dac1fid11.ice]);
2539 		(void)strlcat(buf, "}\r\n", buflen);
2540 		break;
2541 	    case 13:        /* IMO236 - Fairway closed */
2542 		str_appendf(buf, buflen,
2543 			       "\"reason\":\"%s\",\"closefrom\":\"%s\","
2544 			       "\"closeto\":\"%s\",\"radius\":%u,"
2545 			       "\"extunit\":%u,"
2546 			       "\"from\":\"%02u-%02uT%02u:%02u\","
2547 			       "\"to\":\"%02u-%02uT%02u:%02u\"}\r\n",
2548 			       json_stringify(buf1, sizeof(buf1),
2549 					      ais->type8.dac1fid13.reason),
2550 			       json_stringify(buf2, sizeof(buf2),
2551 					      ais->type8.dac1fid13.closefrom),
2552 			       json_stringify(buf3, sizeof(buf3),
2553 					      ais->type8.dac1fid13.closeto),
2554 			       ais->type8.dac1fid13.radius,
2555 			       ais->type8.dac1fid13.extunit,
2556 			       ais->type8.dac1fid13.fmonth,
2557 			       ais->type8.dac1fid13.fday,
2558 			       ais->type8.dac1fid13.fhour,
2559 			       ais->type8.dac1fid13.fminute,
2560 			       ais->type8.dac1fid13.tmonth,
2561 			       ais->type8.dac1fid13.tday,
2562 			       ais->type8.dac1fid13.thour,
2563 			       ais->type8.dac1fid13.tminute);
2564 		break;
2565 	    case 15:        /* IMO236 - Extended ship and voyage */
2566 		str_appendf(buf, buflen,
2567 			       "\"airdraught\":%u}\r\n",
2568 			       ais->type8.dac1fid15.airdraught);
2569 		break;
2570 	    case 16:	/* IMO289 - Number of persons on board */
2571 		str_appendf(buf, buflen,
2572 			    "\"persons\":%u}\r\n",
2573                             ais->type6.dac1fid16.persons);
2574 		break;
2575 	    case 17:        /* IMO289 - VTS-generated/synthetic targets */
2576 		(void)strlcat(buf, "\"targets\":[", buflen);
2577 		for (i = 0; i < ais->type8.dac1fid17.ntargets; i++) {
2578 		    str_appendf(buf, buflen,
2579 				   "{\"idtype\":%u,\"idtype_text\":\"%s\",",
2580 				   ais->type8.dac1fid17.targets[i].idtype,
2581 				   idtypes[ais->type8.dac1fid17.targets[i].idtype]);
2582 		    switch (ais->type8.dac1fid17.targets[i].idtype) {
2583 		    case DAC1FID17_IDTYPE_MMSI:
2584 			str_appendf(buf, buflen,
2585 			    "\"%s\":\"%u\",",
2586 			    idtypes[ais->type8.dac1fid17.targets[i].idtype],
2587 			    ais->type8.dac1fid17.targets[i].id.mmsi);
2588 			break;
2589 		    case DAC1FID17_IDTYPE_IMO:
2590 			str_appendf(buf, buflen,
2591 			    "\"%s\":\"%u\",",
2592 			    idtypes[ais->type8.dac1fid17.targets[i].idtype],
2593 			    ais->type8.dac1fid17.targets[i].id.imo);
2594 			break;
2595 		    case DAC1FID17_IDTYPE_CALLSIGN:
2596 			str_appendf(buf, buflen,
2597 			    "\"%s\":\"%s\",",
2598 			    idtypes[ais->type8.dac1fid17.targets[i].idtype],
2599 			    json_stringify(buf1, sizeof(buf1),
2600 					   ais->type8.dac1fid17.targets[i].id.callsign));
2601 			break;
2602 		    default:
2603 			str_appendf(buf, buflen,
2604 			    "\"%s\":\"%s\",",
2605 			    idtypes[ais->type8.dac1fid17.targets[i].idtype],
2606 			    json_stringify(buf1, sizeof(buf1),
2607 					   ais->type8.dac1fid17.targets[i].id.other));
2608 		    }
2609 		    if (scaled)
2610 			str_appendf(buf, buflen,
2611 			    "\"lat\":%.4f,\"lon\":%.4f,",
2612 			    ais->type8.dac1fid17.targets[i].lat / AIS_LATLON3_DIV,
2613 			    ais->type8.dac1fid17.targets[i].lon / AIS_LATLON3_DIV);
2614 		    else
2615 			str_appendf(buf, buflen,
2616 			    "\"lat\":%d,\"lon\":%d,",
2617 			    ais->type8.dac1fid17.targets[i].lat,
2618 			    ais->type8.dac1fid17.targets[i].lon);
2619 		    str_appendf(buf, buflen,
2620 			"\"course\":%u,\"second\":%u,\"speed\":%u},",
2621 			ais->type8.dac1fid17.targets[i].course,
2622 			ais->type8.dac1fid17.targets[i].second,
2623 			ais->type8.dac1fid17.targets[i].speed);
2624 		}
2625 		str_rstrip_char(buf, ',');
2626 		(void)strlcat(buf, "]}\r\n", buflen);
2627 		break;
2628 	    case 19:        /* IMO289 - Marine Traffic Signal */
2629 		str_appendf(buf, buflen,
2630 			       "\"linkage\":%u,\"station\":\"%s\","
2631 			       "\"lon\":%.4f,\"lat\":%.4f,\"status\":%u,"
2632 			       "\"signal\":%u,\"signal_text\":\"%s\","
2633 			       "\"hour\":%u,\"minute\":%u,"
2634 			       "\"nextsignal\":%u"
2635 			       "\"nextsignal_text\":\"%s\""
2636 			       "}\r\n",
2637 			       ais->type8.dac1fid19.linkage,
2638 			       json_stringify(buf1, sizeof(buf1),
2639 					      ais->type8.dac1fid19.station),
2640 			       ais->type8.dac1fid19.lon / AIS_LATLON3_DIV,
2641 			       ais->type8.dac1fid19.lat / AIS_LATLON3_DIV,
2642 			       ais->type8.dac1fid19.status,
2643 			       ais->type8.dac1fid19.signal,
2644 			       SIGNAL_DISPLAY(ais->type8.dac1fid19.signal),
2645 			       ais->type8.dac1fid19.hour,
2646 			       ais->type8.dac1fid19.minute,
2647 			       ais->type8.dac1fid19.nextsignal,
2648 			       SIGNAL_DISPLAY(ais->type8.dac1fid19.nextsignal));
2649 		break;
2650 	    case 21:        /* IMO289 - Weather obs. report from ship */
2651 		break;
2652 	    case 22:        /* IMO289 - Area notice - broadcast */
2653 		break;
2654 	    case 24:  /* IMO289 - Extended ship static & voyage-related data */
2655 		break;
2656 	    case 25:        /* IMO289 - Dangerous Cargo Indication */
2657 		break;
2658 	    case 27:        /* IMO289 - Route information - broadcast */
2659 		str_appendf(buf, buflen,
2660 			       "\"linkage\":%u,\"sender\":%u,"
2661 			       "\"rtype\":%u,"
2662 			       "\"rtype_text\":\"%s\","
2663 			       "\"start\":\"%02u-%02uT%02u:%02uZ\","
2664 			       "\"duration\":%u,\"waypoints\":[",
2665 			       ais->type8.dac1fid27.linkage,
2666 			       ais->type8.dac1fid27.sender,
2667 			       ais->type8.dac1fid27.rtype,
2668 			       route_type[ais->type8.dac1fid27.rtype],
2669 			       ais->type8.dac1fid27.month,
2670 			       ais->type8.dac1fid27.day,
2671 			       ais->type8.dac1fid27.hour,
2672 			       ais->type8.dac1fid27.minute,
2673 			       ais->type8.dac1fid27.duration);
2674 		for (i = 0; i < ais->type8.dac1fid27.waycount; i++) {
2675 		    if (scaled)
2676 			str_appendf(buf, buflen,
2677 			    "{\"lon\":%.6f,\"lat\":%.6f},",
2678 			    ais->type8.dac1fid27.waypoints[i].lon / AIS_LATLON4_DIV,
2679 			    ais->type8.dac1fid27.waypoints[i].lat / AIS_LATLON4_DIV);
2680 		    else
2681 			str_appendf(buf, buflen,
2682 			    "{\"lon\":%d,\"lat\":%d},",
2683 			    ais->type8.dac1fid27.waypoints[i].lon,
2684 			    ais->type8.dac1fid27.waypoints[i].lat);
2685 		}
2686 		str_rstrip_char(buf, ',');
2687 		(void)strlcat(buf, "]}\r\n", buflen);
2688 		break;
2689 	    case 29:        /* IMO289 - Text Description - broadcast */
2690 		str_appendf(buf, buflen,
2691 		       "\"linkage\":%u,\"text\":\"%s\"}\r\n",
2692 		       ais->type8.dac1fid29.linkage,
2693 		       json_stringify(buf1, sizeof(buf1),
2694 				      ais->type8.dac1fid29.text));
2695 		break;
2696 	    case 31:        /* IMO289 - Meteorological/Hydrological data */
2697 		/* some fields have been merged to an ISO8601 partial date */
2698 		/* layout is almost identical to FID=11 from IMO236 */
2699 		if (scaled)
2700 		    str_appendf(buf, buflen,
2701 				   "\"lat\":%.4f,\"lon\":%.4f,",
2702 				   ais->type8.dac1fid31.lat / AIS_LATLON3_DIV,
2703 				   ais->type8.dac1fid31.lon / AIS_LATLON3_DIV);
2704 		else
2705 		    str_appendf(buf, buflen,
2706 				   "\"lat\":%d,\"lon\":%d,",
2707 				   ais->type8.dac1fid31.lat,
2708 				   ais->type8.dac1fid31.lon);
2709 		str_appendf(buf, buflen,
2710 			       "\"accuracy\":%s,",
2711 			       JSON_BOOL(ais->type8.dac1fid31.accuracy));
2712 		str_appendf(buf, buflen,
2713 			       "\"timestamp\":\"%02uT%02u:%02uZ\","
2714 			       "\"wspeed\":%u,\"wgust\":%u,\"wdir\":%u,"
2715 			       "\"wgustdir\":%u,\"humidity\":%u,",
2716 			       ais->type8.dac1fid31.day,
2717 			       ais->type8.dac1fid31.hour,
2718 			       ais->type8.dac1fid31.minute,
2719 			       ais->type8.dac1fid31.wspeed,
2720 			       ais->type8.dac1fid31.wgust,
2721 			       ais->type8.dac1fid31.wdir,
2722 			       ais->type8.dac1fid31.wgustdir,
2723 			       ais->type8.dac1fid31.humidity);
2724 		if (scaled)
2725 		    str_appendf(buf, buflen,
2726 				   "\"airtemp\":%.1f,\"dewpoint\":%.1f,"
2727 				   "\"pressure\":%u,\"pressuretend\":\"%s\","
2728 				   "\"visgreater\":%s,",
2729 				   ais->type8.dac1fid31.airtemp / DAC1FID31_AIRTEMP_DIV,
2730 				   ais->type8.dac1fid31.dewpoint / DAC1FID31_DEWPOINT_DIV,
2731 				   ais->type8.dac1fid31.pressure - DAC1FID31_PRESSURE_OFFSET,
2732 				   trends[ais->type8.dac1fid31.pressuretend],
2733 				   JSON_BOOL(ais->type8.dac1fid31.visgreater));
2734 		else
2735 		    str_appendf(buf, buflen,
2736 				   "\"airtemp\":%d,\"dewpoint\":%d,"
2737 				   "\"pressure\":%u,\"pressuretend\":%u,"
2738 				   "\"visgreater\":%s,",
2739 				   ais->type8.dac1fid31.airtemp,
2740 				   ais->type8.dac1fid31.dewpoint,
2741 				   ais->type8.dac1fid31.pressure,
2742 				   ais->type8.dac1fid31.pressuretend,
2743 				   JSON_BOOL(ais->type8.dac1fid31.visgreater));
2744 
2745 		if (scaled)
2746 		    str_appendf(buf, buflen,
2747 				   "\"visibility\":%.1f,",
2748 				   ais->type8.dac1fid31.visibility / DAC1FID31_VISIBILITY_DIV);
2749 		else
2750 		    str_appendf(buf, buflen,
2751 				   "\"visibility\":%u,",
2752 				   ais->type8.dac1fid31.visibility);
2753 		if (!scaled)
2754 		    str_appendf(buf, buflen,
2755 				   "\"waterlevel\":%d,",
2756 				   ais->type8.dac1fid31.waterlevel);
2757 		else
2758 		    str_appendf(buf, buflen,
2759 				   "\"waterlevel\":%.1f,",
2760 				   ((unsigned int)ais->type8.dac1fid31.waterlevel - DAC1FID31_WATERLEVEL_OFFSET) / DAC1FID31_WATERLEVEL_DIV);
2761 
2762 		if (scaled) {
2763 		    str_appendf(buf, buflen,
2764 				   "\"leveltrend\":\"%s\","
2765 				   "\"cspeed\":%.1f,\"cdir\":%u,"
2766 				   "\"cspeed2\":%.1f,\"cdir2\":%u,"
2767                                    "\"cdepth2\":%u,"
2768 				   "\"cspeed3\":%.1f,\"cdir3\":%u,"
2769                                    "\"cdepth3\":%u,"
2770 				   "\"waveheight\":%.1f,\"waveperiod\":%u,"
2771                                    "\"wavedir\":%u,"
2772 				   "\"swellheight\":%.1f,\"swellperiod\":%u,"
2773                                    "\"swelldir\":%u,"
2774 				   "\"seastate\":%u,\"watertemp\":%.1f,"
2775 				   "\"preciptype\":\"%s\",\"salinity\":%.1f,"
2776                                    "\"ice\":\"%s\"",
2777 				   trends[ais->type8.dac1fid31.leveltrend],
2778 				   ais->type8.dac1fid31.cspeed / DAC1FID31_CSPEED_DIV,
2779 				   ais->type8.dac1fid31.cdir,
2780 				   ais->type8.dac1fid31.cspeed2 / DAC1FID31_CSPEED_DIV,
2781 				   ais->type8.dac1fid31.cdir2,
2782 				   ais->type8.dac1fid31.cdepth2,
2783 				   ais->type8.dac1fid31.cspeed3 / DAC1FID31_CSPEED_DIV,
2784 				   ais->type8.dac1fid31.cdir3,
2785 				   ais->type8.dac1fid31.cdepth3,
2786 				   ais->type8.dac1fid31.waveheight / DAC1FID31_HEIGHT_DIV,
2787 				   ais->type8.dac1fid31.waveperiod,
2788 				   ais->type8.dac1fid31.wavedir,
2789 				   ais->type8.dac1fid31.swellheight / DAC1FID31_HEIGHT_DIV,
2790 				   ais->type8.dac1fid31.swellperiod,
2791 				   ais->type8.dac1fid31.swelldir,
2792 				   ais->type8.dac1fid31.seastate,
2793 				   ais->type8.dac1fid31.watertemp / DAC1FID31_WATERTEMP_DIV,
2794 				   preciptypes[ais->type8.dac1fid31.preciptype],
2795 				   ais->type8.dac1fid31.salinity / DAC1FID31_SALINITY_DIV,
2796 				   ice[ais->type8.dac1fid31.ice]);
2797 		} else
2798 		    str_appendf(buf, buflen,
2799 				   "\"leveltrend\":%u,"
2800 				   "\"cspeed\":%u,\"cdir\":%u,"
2801 				   "\"cspeed2\":%u,\"cdir2\":%u,"
2802                                    "\"cdepth2\":%u,"
2803 				   "\"cspeed3\":%u,\"cdir3\":%u,"
2804                                    "\"cdepth3\":%u,"
2805 				   "\"waveheight\":%u,\"waveperiod\":%u,"
2806                                    "\"wavedir\":%u,"
2807 				   "\"swellheight\":%u,\"swellperiod\":%u,"
2808                                    "\"swelldir\":%u,"
2809 				   "\"seastate\":%u,\"watertemp\":%d,"
2810 				   "\"preciptype\":%u,\"salinity\":%u,"
2811                                    "\"ice\":%u",
2812 				   ais->type8.dac1fid31.leveltrend,
2813 				   ais->type8.dac1fid31.cspeed,
2814 				   ais->type8.dac1fid31.cdir,
2815 				   ais->type8.dac1fid31.cspeed2,
2816 				   ais->type8.dac1fid31.cdir2,
2817 				   ais->type8.dac1fid31.cdepth2,
2818 				   ais->type8.dac1fid31.cspeed3,
2819 				   ais->type8.dac1fid31.cdir3,
2820 				   ais->type8.dac1fid31.cdepth3,
2821 				   ais->type8.dac1fid31.waveheight,
2822 				   ais->type8.dac1fid31.waveperiod,
2823 				   ais->type8.dac1fid31.wavedir,
2824 				   ais->type8.dac1fid31.swellheight,
2825 				   ais->type8.dac1fid31.swellperiod,
2826 				   ais->type8.dac1fid31.swelldir,
2827 				   ais->type8.dac1fid31.seastate,
2828 				   ais->type8.dac1fid31.watertemp,
2829 				   ais->type8.dac1fid31.preciptype,
2830 				   ais->type8.dac1fid31.salinity,
2831 				   ais->type8.dac1fid31.ice);
2832 		(void)strlcat(buf, "}\r\n", buflen);
2833 		break;
2834 	    }
2835 	}
2836 	else if (ais->type8.dac == 200) {
2837 	    struct {
2838 		const unsigned int code;
2839 		const unsigned int ais;
2840 		const char *legend;
2841 	    } *cp, shiptypes[] = {
2842 		/*
2843 		 * The Inland AIS standard is not clear which numbers are
2844 		 * supposed to be in the type slot.  The ranges are disjoint,
2845 		 * so we'll match on both.
2846 		 */
2847 		{8000, 99, "Vessel, type unknown"},
2848 		{8010, 79, "Motor freighter"},
2849 		{8020, 89, "Motor tanker"},
2850 		{8021, 80, "Motor tanker, liquid cargo, type N"},
2851 		{8022, 80, "Motor tanker, liquid cargo, type C"},
2852 		{8023, 89,
2853                  "Motor tanker, dry cargo as if liquid (e.g. cement)"},
2854 		{8030, 79, "Container vessel"},
2855 		{8040, 80, "Gas tanker"},
2856 		{8050, 79, "Motor freighter, tug"},
2857 		{8060, 89, "Motor tanker, tug"},
2858 		{8070, 79, "Motor freighter with one or more ships alongside"},
2859 		{8080, 89, "Motor freighter with tanker"},
2860 		{8090, 79, "Motor freighter pushing one or more freighters"},
2861 		{8100, 89, "Motor freighter pushing at least one tank-ship"},
2862 		{8110, 79, "Tug, freighter"},
2863 		{8120, 89, "Tug, tanker"},
2864 		{8130, 31, "Tug freighter, coupled"},
2865 		{8140, 31, "Tug, freighter/tanker, coupled"},
2866 		{8150, 99, "Freightbarge"},
2867 		{8160, 99, "Tankbarge"},
2868 		{8161, 90, "Tankbarge, liquid cargo, type N"},
2869 		{8162, 90, "Tankbarge, liquid cargo, type C"},
2870 		{8163, 99, "Tankbarge, dry cargo as if liquid (e.g. cement)"},
2871 		{8170, 99, "Freightbarge with containers"},
2872 		{8180, 90, "Tankbarge, gas"},
2873 		{8210, 79, "Pushtow, one cargo barge"},
2874 		{8220, 79, "Pushtow, two cargo barges"},
2875 		{8230, 79, "Pushtow, three cargo barges"},
2876 		{8240, 79, "Pushtow, four cargo barges"},
2877 		{8250, 79, "Pushtow, five cargo barges"},
2878 		{8260, 79, "Pushtow, six cargo barges"},
2879 		{8270, 79, "Pushtow, seven cargo barges"},
2880 		{8280, 79, "Pushtow, eigth cargo barges"},
2881 		{8290, 79, "Pushtow, nine or more barges"},
2882 		{8310, 80, "Pushtow, one tank/gas barge"},
2883 		{8320, 80,
2884                  "Pushtow, two barges at least one tanker or gas barge"},
2885 		{8330, 80,
2886                  "Pushtow, three barges at least one tanker or gas barge"},
2887 		{8340, 80,
2888                  "Pushtow, four barges at least one tanker or gas barge"},
2889 		{8350, 80,
2890                  "Pushtow, five barges at least one tanker or gas barge"},
2891 		{8360, 80,
2892                  "Pushtow, six barges at least one tanker or gas barge"},
2893 		{8370, 80,
2894                  "Pushtow, seven barges at least one tanker or gas barg"},
2895 		{0, 0, "Illegal ship type value."},
2896 	    };
2897 	    const char *hazard_types[] = {
2898 		"0 blue cones/lights",
2899 		"1 blue cone/light",
2900 		"2 blue cones/lights",
2901 		"3 blue cones/lights",
2902 		"4 B-Flag",
2903 		"Unknown",
2904 	    };
2905 #define HTYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(hazard_types)) ? hazard_types[n] : "INVALID HAZARD TYPE")
2906 	    const char *lstatus_types[] = {
2907 		"N/A (default)",
2908 		"Unloaded",
2909 		"Loaded",
2910 	    };
2911 #define LSTATUS_DISPLAY(n) (((n) < (unsigned int)NITEMS(lstatus_types)) ? lstatus_types[n] : "INVALID LOAD STATUS")
2912 	    const char *emma_types[] = {
2913 		"Not Available",
2914 		"Wind",
2915 		"Rain",
2916 		"Snow and ice",
2917 		"Thunderstorm",
2918 		"Fog",
2919 		"Low temperature",
2920 		"High temperature",
2921 		"Flood",
2922 		"Forest Fire",
2923 	    };
2924 #define EMMA_TYPE_DISPLAY(n) (((n) < (unsigned int)NITEMS(emma_types)) ? emma_types[n] : "INVALID EMMA TYPE")
2925 	    const char *emma_classes[] = {
2926 		"Slight",
2927 		"Medium",
2928 		"Strong",
2929 	    };
2930 #define EMMA_CLASS_DISPLAY(n) (((n) < (unsigned int)NITEMS(emma_classes)) ? emma_classes[n] : "INVALID EMMA TYPE")
2931 	    const char *emma_winds[] = {
2932 		"N/A",
2933 		"North",
2934 		"North East",
2935 		"East",
2936 		"South East",
2937 		"South",
2938 		"South West",
2939 		"West",
2940 		"North West",
2941 	    };
2942 #define EMMA_WIND_DISPLAY(n) (((n) < (unsigned int)NITEMS(emma_winds)) ? emma_winds[n] : "INVALID EMMA WIND DIRECTION")
2943 	    const char *direction_vocabulary[] = {
2944 		"Unknown",
2945 		"Upstream",
2946 		"Downstream",
2947 		"To left bank",
2948 		"To right bank",
2949 	    };
2950 #define DIRECTION_DISPLAY(n) (((n) < (unsigned int)NITEMS(direction_vocabulary)) ? direction_vocabulary[n] : "INVALID DIRECTION")
2951 	    const char *status_vocabulary[] = {
2952 		"Unknown",
2953 		"No light",
2954 		"White",
2955 		"Yellow",
2956 		"Green",
2957 		"Red",
2958 		"White flashing",
2959 		"Yellow flashing.",
2960 	    };
2961 #define STATUS_DISPLAY(n) (((n) < (unsigned int)NITEMS(status_vocabulary)) ? status_vocabulary[n] : "INVALID STATUS")
2962 
2963 	    switch (ais->type8.fid) {
2964 	    case 10:        /* Inland ship static and voyage-related data */
2965 		for (cp = shiptypes; cp < shiptypes + NITEMS(shiptypes); cp++)
2966 		    if (cp->code == ais->type8.dac200fid10.shiptype
2967 			|| cp->ais == ais->type8.dac200fid10.shiptype
2968 			|| cp->code == 0)
2969 			break;
2970 		str_appendf(buf, buflen,
2971 			       "\"vin\":\"%s\",\"length\":%u,\"beam\":%u,"
2972 			       "\"shiptype\":%u,\"shiptype_text\":\"%s\","
2973 			       "\"hazard\":%u,\"hazard_text\":\"%s\","
2974 			       "\"draught\":%u,"
2975 			       "\"loaded\":%u,\"loaded_text\":\"%s\","
2976 			       "\"speed_q\":%s,"
2977 			       "\"course_q\":%s,"
2978 			       "\"heading_q\":%s}\r\n",
2979 			       json_stringify(buf1, sizeof(buf1),
2980                                ais->type8.dac200fid10.vin),
2981 			       ais->type8.dac200fid10.length,
2982 			       ais->type8.dac200fid10.beam,
2983 			       ais->type8.dac200fid10.shiptype,
2984 			       cp->legend,
2985 			       ais->type8.dac200fid10.hazard,
2986 			       HTYPE_DISPLAY(ais->type8.dac200fid10.hazard),
2987 			       ais->type8.dac200fid10.draught,
2988 			       ais->type8.dac200fid10.loaded,
2989 			       LSTATUS_DISPLAY(ais->type8.dac200fid10.loaded),
2990 			       JSON_BOOL(ais->type8.dac200fid10.speed_q),
2991 			       JSON_BOOL(ais->type8.dac200fid10.course_q),
2992 			       JSON_BOOL(ais->type8.dac200fid10.heading_q));
2993 		break;
2994 	    case 23:	/* EMMA warning */
2995 		if (!ais->type8.structured)
2996 		    break;
2997 		str_appendf(buf, buflen,
2998 			       "\"start\":\"%4u-%02u-%02uT%02u:%02u\","
2999 			       "\"end\":\"%4u-%02u-%02uT%02u:%02u\",",
3000 			       ais->type8.dac200fid23.start_year + 2000,
3001 			       ais->type8.dac200fid23.start_month,
3002 			       ais->type8.dac200fid23.start_hour,
3003 			       ais->type8.dac200fid23.start_minute,
3004 			       ais->type8.dac200fid23.start_day,
3005 			       ais->type8.dac200fid23.end_year + 2000,
3006 			       ais->type8.dac200fid23.end_month,
3007 			       ais->type8.dac200fid23.end_day,
3008 			       ais->type8.dac200fid23.end_hour,
3009 			       ais->type8.dac200fid23.end_minute);
3010 		if (scaled)
3011 		    str_appendf(buf, buflen,
3012 			"\"start_lon\":%.6f,\"start_lat\":%.6f,"
3013                         "\"end_lon\":%.6f,\"end_lat\":%.6f,",
3014 			ais->type8.dac200fid23.start_lon / AIS_LATLON_DIV,
3015 			ais->type8.dac200fid23.start_lat / AIS_LATLON_DIV,
3016 			ais->type8.dac200fid23.end_lon / AIS_LATLON_DIV,
3017 			ais->type8.dac200fid23.end_lat / AIS_LATLON_DIV);
3018 		else
3019 		    str_appendf(buf, buflen,
3020 			"\"start_lon\":%d,\"start_lat\":%d,\"end_lon\":%d,"
3021                         "\"end_lat\":%d,",
3022 			ais->type8.dac200fid23.start_lon,
3023 			ais->type8.dac200fid23.start_lat,
3024 			ais->type8.dac200fid23.end_lon,
3025 			ais->type8.dac200fid23.end_lat);
3026 		str_appendf(buf, buflen,
3027 		    "\"type\":%u,\"type_text\":\"%s\",\"min\":%d,"
3028                     "\"max\":%d,\"class\":%u,\"class_text\":\"%s\","
3029                     "\"wind\":%u,\"wind_text\":\"%s\"}\r\n",
3030 
3031 		    ais->type8.dac200fid23.type,
3032 		    EMMA_TYPE_DISPLAY(ais->type8.dac200fid23.type),
3033 		    ais->type8.dac200fid23.min,
3034 		    ais->type8.dac200fid23.max,
3035 		    ais->type8.dac200fid23.intensity,
3036 		    EMMA_CLASS_DISPLAY(ais->type8.dac200fid23.intensity),
3037 		    ais->type8.dac200fid23.wind,
3038 		    EMMA_WIND_DISPLAY(ais->type8.dac200fid23.wind));
3039 		break;
3040 	    case 24:	/* Inland AIS Water Levels */
3041 		str_appendf(buf, buflen,
3042 		    "\"country\":\"%s\",\"gauges\":[",
3043 		    ais->type8.dac200fid24.country);
3044 		for (i = 0; i < ais->type8.dac200fid24.ngauges; i++) {
3045 		    str_appendf(buf, buflen,
3046 			"{\"id\":%u,\"level\":%d},",
3047 			ais->type8.dac200fid24.gauges[i].id,
3048 			ais->type8.dac200fid24.gauges[i].level);
3049 		}
3050 		str_rstrip_char(buf, ',');
3051 		(void)strlcat(buf, "]}\r\n", buflen);
3052 		break;
3053 	    case 40:	/* Inland AIS Signal Strength */
3054 		if (scaled)
3055 		    str_appendf(buf, buflen,
3056 			"\"lon\":%.6f,\"lat\":%.6f,",
3057 			ais->type8.dac200fid40.lon / AIS_LATLON_DIV,
3058 			ais->type8.dac200fid40.lat / AIS_LATLON_DIV);
3059 		else
3060 		    str_appendf(buf, buflen,
3061 			"\"lon\":%d,\"lat\":%d,",
3062 			ais->type8.dac200fid40.lon,
3063 			ais->type8.dac200fid40.lat);
3064 		str_appendf(buf, buflen,
3065 		    "\"form\":%u,\"facing\":%u,\"direction\":%u,"
3066                     "\"direction_text\":\"%s\",\"status\":%u,"
3067                     "\"status_text\":\"%s\"}\r\n",
3068 		    ais->type8.dac200fid40.form,
3069 		    ais->type8.dac200fid40.facing,
3070 		    ais->type8.dac200fid40.direction,
3071 		    DIRECTION_DISPLAY(ais->type8.dac200fid40.direction),
3072 		    ais->type8.dac200fid40.status,
3073 		    STATUS_DISPLAY(ais->type8.dac200fid40.status));
3074 		break;
3075 	    }
3076 	}
3077 	break;
3078     case 9:			/* Standard SAR Aircraft Position Report */
3079 	if (scaled) {
3080 	    char altlegend[20];
3081 	    char speedlegend[20];
3082 
3083 	    /*
3084 	     * Express altitude as nan if not available,
3085 	     * "high" for above the reporting ceiling.
3086 	     */
3087 	    if (ais->type9.alt == AIS_ALT_NOT_AVAILABLE)
3088 		(void)strlcpy(altlegend, "\"nan\"", sizeof(altlegend));
3089 	    else if (ais->type9.alt == AIS_ALT_HIGH)
3090 		(void)strlcpy(altlegend, "\"high\"", sizeof(altlegend));
3091 	    else
3092 		(void)snprintf(altlegend, sizeof(altlegend),
3093 			       "%u", ais->type9.alt);
3094 
3095 	    /*
3096 	     * Express speed as nan if not available,
3097 	     * "high" for above the reporting ceiling.
3098 	     */
3099 	    if (ais->type9.speed == AIS_SAR_SPEED_NOT_AVAILABLE)
3100 		(void)strlcpy(speedlegend, "\"nan\"", sizeof(speedlegend));
3101 	    else if (ais->type9.speed == AIS_SAR_FAST_MOVER)
3102 		(void)strlcpy(speedlegend, "\"fast\"", sizeof(speedlegend));
3103 	    else
3104 		(void)snprintf(speedlegend, sizeof(speedlegend),
3105 			       "%u", ais->type9.speed);
3106 
3107 	    str_appendf(buf, buflen,
3108 			   "\"alt\":%s,\"speed\":%s,\"accuracy\":%s,"
3109 			   "\"lon\":%.6f,\"lat\":%.6f,\"course\":%.1f,"
3110 			   "\"second\":%u,\"regional\":%u,\"dte\":%u,"
3111 			   "\"raim\":%s,\"radio\":%u}\r\n",
3112 			   altlegend,
3113 			   speedlegend,
3114 			   JSON_BOOL(ais->type9.accuracy),
3115 			   ais->type9.lon / AIS_LATLON_DIV,
3116 			   ais->type9.lat / AIS_LATLON_DIV,
3117 			   ais->type9.course / 10.0,
3118 			   ais->type9.second,
3119 			   ais->type9.regional,
3120 			   ais->type9.dte,
3121 			   JSON_BOOL(ais->type9.raim), ais->type9.radio);
3122 	} else {
3123 	    str_appendf(buf, buflen,
3124 			   "\"alt\":%u,\"speed\":%u,\"accuracy\":%s,"
3125 			   "\"lon\":%d,\"lat\":%d,\"course\":%u,"
3126 			   "\"second\":%u,\"regional\":%u,\"dte\":%u,"
3127 			   "\"raim\":%s,\"radio\":%u}\r\n",
3128 			   ais->type9.alt,
3129 			   ais->type9.speed,
3130 			   JSON_BOOL(ais->type9.accuracy),
3131 			   ais->type9.lon,
3132 			   ais->type9.lat,
3133 			   ais->type9.course,
3134 			   ais->type9.second,
3135 			   ais->type9.regional,
3136 			   ais->type9.dte,
3137 			   JSON_BOOL(ais->type9.raim), ais->type9.radio);
3138 	}
3139 	break;
3140     case 10:			/* UTC/Date Inquiry */
3141 	str_appendf(buf, buflen,
3142 		       "\"dest_mmsi\":%u}\r\n", ais->type10.dest_mmsi);
3143 	break;
3144     case 12:			/* Safety Related Message */
3145 	str_appendf(buf, buflen,
3146 		       "\"seqno\":%u,\"dest_mmsi\":%u,\"retransmit\":%s,"
3147                        "\"text\":\"%s\"}\r\n",
3148 		       ais->type12.seqno,
3149 		       ais->type12.dest_mmsi,
3150 		       JSON_BOOL(ais->type12.retransmit),
3151 		       json_stringify(buf1, sizeof(buf1), ais->type12.text));
3152 	break;
3153     case 14:			/* Safety Related Broadcast Message */
3154 	str_appendf(buf, buflen,
3155 		       "\"text\":\"%s\"}\r\n",
3156 		       json_stringify(buf1, sizeof(buf1), ais->type14.text));
3157 	break;
3158     case 15:			/* Interrogation */
3159 	str_appendf(buf, buflen,
3160 		       "\"mmsi1\":%u,\"type1_1\":%u,\"offset1_1\":%u,"
3161 		       "\"type1_2\":%u,\"offset1_2\":%u,\"mmsi2\":%u,"
3162 		       "\"type2_1\":%u,\"offset2_1\":%u}\r\n",
3163 		       ais->type15.mmsi1,
3164 		       ais->type15.type1_1,
3165 		       ais->type15.offset1_1,
3166 		       ais->type15.type1_2,
3167 		       ais->type15.offset1_2,
3168 		       ais->type15.mmsi2,
3169 		       ais->type15.type2_1, ais->type15.offset2_1);
3170 	break;
3171     case 16:
3172 	str_appendf(buf, buflen,
3173 		       "\"mmsi1\":%u,\"offset1\":%u,\"increment1\":%u,"
3174 		       "\"mmsi2\":%u,\"offset2\":%u,\"increment2\":%u}\r\n",
3175 		       ais->type16.mmsi1,
3176 		       ais->type16.offset1,
3177 		       ais->type16.increment1,
3178 		       ais->type16.mmsi2,
3179 		       ais->type16.offset2, ais->type16.increment2);
3180 	break;
3181     case 17:
3182 	if (scaled) {
3183 	    str_appendf(buf, buflen,
3184 			   "\"lon\":%.1f,\"lat\":%.1f,\"data\":\"%zd:%s\"}\r\n",
3185 			   ais->type17.lon / AIS_GNSS_LATLON_DIV,
3186 			   ais->type17.lat / AIS_GNSS_LATLON_DIV,
3187 			   ais->type17.bitcount,
3188 			   gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3189 					(char *)ais->type17.bitdata,
3190 					BITS_TO_BYTES(ais->type17.bitcount)));
3191 	} else {
3192 	    str_appendf(buf, buflen,
3193 			   "\"lon\":%d,\"lat\":%d,\"data\":\"%zd:%s\"}\r\n",
3194 			   ais->type17.lon,
3195 			   ais->type17.lat,
3196 			   ais->type17.bitcount,
3197 			   gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3198 					(char *)ais->type17.bitdata,
3199 					BITS_TO_BYTES(ais->type17.bitcount)));
3200 	}
3201 	break;
3202     case 18:
3203 	if (scaled) {
3204 	    str_appendf(buf, buflen,
3205 			   "\"reserved\":%u,\"speed\":%.1f,\"accuracy\":%s,"
3206 			   "\"lon\":%.6f,\"lat\":%.6f,\"course\":%.1f,"
3207 			   "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3208 			   "\"cs\":%s,\"display\":%s,\"dsc\":%s,\"band\":%s,"
3209 			   "\"msg22\":%s,\"raim\":%s,\"radio\":%u}\r\n",
3210 			   ais->type18.reserved,
3211 			   ais->type18.speed / 10.0,
3212 			   JSON_BOOL(ais->type18.accuracy),
3213 			   ais->type18.lon / AIS_LATLON_DIV,
3214 			   ais->type18.lat / AIS_LATLON_DIV,
3215 			   ais->type18.course / 10.0,
3216 			   ais->type18.heading,
3217 			   ais->type18.second,
3218 			   ais->type18.regional,
3219 			   JSON_BOOL(ais->type18.cs),
3220 			   JSON_BOOL(ais->type18.display),
3221 			   JSON_BOOL(ais->type18.dsc),
3222 			   JSON_BOOL(ais->type18.band),
3223 			   JSON_BOOL(ais->type18.msg22),
3224 			   JSON_BOOL(ais->type18.raim), ais->type18.radio);
3225 	} else {
3226 	    str_appendf(buf, buflen,
3227 			   "\"reserved\":%u,\"speed\":%u,\"accuracy\":%s,"
3228 			   "\"lon\":%d,\"lat\":%d,\"course\":%u,"
3229 			   "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3230 			   "\"cs\":%s,\"display\":%s,\"dsc\":%s,\"band\":%s,"
3231 			   "\"msg22\":%s,\"raim\":%s,\"radio\":%u}\r\n",
3232 			   ais->type18.reserved,
3233 			   ais->type18.speed,
3234 			   JSON_BOOL(ais->type18.accuracy),
3235 			   ais->type18.lon,
3236 			   ais->type18.lat,
3237 			   ais->type18.course,
3238 			   ais->type18.heading,
3239 			   ais->type18.second,
3240 			   ais->type18.regional,
3241 			   JSON_BOOL(ais->type18.cs),
3242 			   JSON_BOOL(ais->type18.display),
3243 			   JSON_BOOL(ais->type18.dsc),
3244 			   JSON_BOOL(ais->type18.band),
3245 			   JSON_BOOL(ais->type18.msg22),
3246 			   JSON_BOOL(ais->type18.raim), ais->type18.radio);
3247 	}
3248 	break;
3249     case 19:
3250 	if (scaled) {
3251 	    str_appendf(buf, buflen,
3252 			   "\"reserved\":%u,\"speed\":%.1f,\"accuracy\":%s,"
3253 			   "\"lon\":%.6f,\"lat\":%.6f,\"course\":%.1f,"
3254 			   "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3255 			   "\"shipname\":\"%s\","
3256 			   "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3257 			   "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
3258 			   "\"to_starboard\":%u,"
3259 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
3260 			   "\"raim\":%s,\"dte\":%u,\"assigned\":%s}\r\n",
3261 			   ais->type19.reserved,
3262 			   ais->type19.speed / 10.0,
3263 			   JSON_BOOL(ais->type19.accuracy),
3264 			   ais->type19.lon / AIS_LATLON_DIV,
3265 			   ais->type19.lat / AIS_LATLON_DIV,
3266 			   ais->type19.course / 10.0,
3267 			   ais->type19.heading,
3268 			   ais->type19.second,
3269 			   ais->type19.regional,
3270 			   json_stringify(buf1, sizeof(buf1),
3271 					  ais->type19.shipname),
3272 			   ais->type19.shiptype,
3273 			   SHIPTYPE_DISPLAY(ais->type19.shiptype),
3274 			   ais->type19.to_bow,
3275 			   ais->type19.to_stern,
3276 			   ais->type19.to_port,
3277 			   ais->type19.to_starboard,
3278 			   ais->type19.epfd,
3279 			   EPFD_DISPLAY(ais->type19.epfd),
3280 			   JSON_BOOL(ais->type19.raim),
3281 			   ais->type19.dte,
3282 			   JSON_BOOL(ais->type19.assigned));
3283 	} else {
3284 	    str_appendf(buf, buflen,
3285 			   "\"reserved\":%u,\"speed\":%u,\"accuracy\":%s,"
3286 			   "\"lon\":%d,\"lat\":%d,\"course\":%u,"
3287 			   "\"heading\":%u,\"second\":%u,\"regional\":%u,"
3288 			   "\"shipname\":\"%s\","
3289 			   "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3290 			   "\"to_bow\":%u,\"to_stern\":%u,\"to_port\":%u,"
3291 			   "\"to_starboard\":%u,"
3292 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
3293 			   "\"raim\":%s,\"dte\":%u,\"assigned\":%s}\r\n",
3294 			   ais->type19.reserved,
3295 			   ais->type19.speed,
3296 			   JSON_BOOL(ais->type19.accuracy),
3297 			   ais->type19.lon,
3298 			   ais->type19.lat,
3299 			   ais->type19.course,
3300 			   ais->type19.heading,
3301 			   ais->type19.second,
3302 			   ais->type19.regional,
3303 			   json_stringify(buf1, sizeof(buf1),
3304 					  ais->type19.shipname),
3305 			   ais->type19.shiptype,
3306 			   SHIPTYPE_DISPLAY(ais->type19.shiptype),
3307 			   ais->type19.to_bow,
3308 			   ais->type19.to_stern,
3309 			   ais->type19.to_port,
3310 			   ais->type19.to_starboard,
3311 			   ais->type19.epfd,
3312 			   EPFD_DISPLAY(ais->type19.epfd),
3313 			   JSON_BOOL(ais->type19.raim),
3314 			   ais->type19.dte,
3315 			   JSON_BOOL(ais->type19.assigned));
3316 	}
3317 	break;
3318     case 20:			/* Data Link Management Message */
3319 	str_appendf(buf, buflen,
3320 		       "\"offset1\":%u,\"number1\":%u,"
3321 		       "\"timeout1\":%u,\"increment1\":%u,"
3322 		       "\"offset2\":%u,\"number2\":%u,"
3323 		       "\"timeout2\":%u,\"increment2\":%u,"
3324 		       "\"offset3\":%u,\"number3\":%u,"
3325 		       "\"timeout3\":%u,\"increment3\":%u,"
3326 		       "\"offset4\":%u,\"number4\":%u,"
3327 		       "\"timeout4\":%u,\"increment4\":%u}\r\n",
3328 		       ais->type20.offset1,
3329 		       ais->type20.number1,
3330 		       ais->type20.timeout1,
3331 		       ais->type20.increment1,
3332 		       ais->type20.offset2,
3333 		       ais->type20.number2,
3334 		       ais->type20.timeout2,
3335 		       ais->type20.increment2,
3336 		       ais->type20.offset3,
3337 		       ais->type20.number3,
3338 		       ais->type20.timeout3,
3339 		       ais->type20.increment3,
3340 		       ais->type20.offset4,
3341 		       ais->type20.number4,
3342 		       ais->type20.timeout4, ais->type20.increment4);
3343 	break;
3344     case 21:			/* Aid to Navigation */
3345 	if (scaled) {
3346 	    str_appendf(buf, buflen,
3347 			   "\"aid_type\":%u,\"aid_type_text\":\"%s\","
3348 			   "\"name\":\"%s\",\"lon\":%.6f,"
3349 			   "\"lat\":%.6f,\"accuracy\":%s,\"to_bow\":%u,"
3350 			   "\"to_stern\":%u,\"to_port\":%u,\"to_starboard\":%u,"
3351 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
3352 			   "\"second\":%u,\"regional\":%u,"
3353 			   "\"off_position\":%s,\"raim\":%s,"
3354 			   "\"virtual_aid\":%s}\r\n",
3355 			   ais->type21.aid_type,
3356 			   NAVAIDTYPE_DISPLAY(ais->type21.aid_type),
3357 			   json_stringify(buf1, sizeof(buf1),
3358 					  ais->type21.name),
3359 			   ais->type21.lon / AIS_LATLON_DIV,
3360 			   ais->type21.lat / AIS_LATLON_DIV,
3361 			   JSON_BOOL(ais->type21.accuracy),
3362 			   ais->type21.to_bow, ais->type21.to_stern,
3363 			   ais->type21.to_port, ais->type21.to_starboard,
3364 			   ais->type21.epfd,
3365 			   EPFD_DISPLAY(ais->type21.epfd),
3366 			   ais->type21.second,
3367 			   ais->type21.regional,
3368 			   JSON_BOOL(ais->type21.off_position),
3369 			   JSON_BOOL(ais->type21.raim),
3370 			   JSON_BOOL(ais->type21.virtual_aid));
3371 	} else {
3372 	    str_appendf(buf, buflen,
3373 			   "\"aid_type\":%u,\"aid_type_text\":\"%s\","
3374 			   "\"name\":\"%s\",\"accuracy\":%s,"
3375 			   "\"lon\":%d,\"lat\":%d,\"to_bow\":%u,"
3376 			   "\"to_stern\":%u,\"to_port\":%u,\"to_starboard\":%u,"
3377 			   "\"epfd\":%u,\"epfd_text\":\"%s\","
3378 			   "\"second\":%u,\"regional\":%u,"
3379 			   "\"off_position\":%s,\"raim\":%s,"
3380 			   "\"virtual_aid\":%s}\r\n",
3381 			   ais->type21.aid_type,
3382 			   NAVAIDTYPE_DISPLAY(ais->type21.aid_type),
3383 			   json_stringify(buf1, sizeof(buf1),
3384 					  ais->type21.name),
3385 			   JSON_BOOL(ais->type21.accuracy),
3386 			   ais->type21.lon,
3387 			   ais->type21.lat,
3388 			   ais->type21.to_bow,
3389 			   ais->type21.to_stern,
3390 			   ais->type21.to_port,
3391 			   ais->type21.to_starboard,
3392 			   ais->type21.epfd,
3393 			   EPFD_DISPLAY(ais->type21.epfd),
3394 			   ais->type21.second,
3395 			   ais->type21.regional,
3396 			   JSON_BOOL(ais->type21.off_position),
3397 			   JSON_BOOL(ais->type21.raim),
3398 			   JSON_BOOL(ais->type21.virtual_aid));
3399 	}
3400 	break;
3401     case 22:			/* Channel Management */
3402 	str_appendf(buf, buflen,
3403 		       "\"channel_a\":%u,\"channel_b\":%u,"
3404 		       "\"txrx\":%u,\"power\":%s,",
3405 		       ais->type22.channel_a,
3406 		       ais->type22.channel_b,
3407 		       ais->type22.txrx, JSON_BOOL(ais->type22.power));
3408 	if (ais->type22.addressed) {
3409 	    str_appendf(buf, buflen,
3410 			   "\"dest1\":%u,\"dest2\":%u,",
3411 			   ais->type22.mmsi.dest1, ais->type22.mmsi.dest2);
3412 	} else if (scaled) {
3413 	    str_appendf(buf, buflen,
3414 			   "\"ne_lon\":\"%f\",\"ne_lat\":\"%f\","
3415 			   "\"sw_lon\":\"%f\",\"sw_lat\":\"%f\",",
3416 			   ais->type22.area.ne_lon / AIS_CHANNEL_LATLON_DIV,
3417 			   ais->type22.area.ne_lat / AIS_CHANNEL_LATLON_DIV,
3418 			   ais->type22.area.sw_lon / AIS_CHANNEL_LATLON_DIV,
3419 			   ais->type22.area.sw_lat /
3420 			   AIS_CHANNEL_LATLON_DIV);
3421 	} else {
3422 	    str_appendf(buf, buflen,
3423 			   "\"ne_lon\":%d,\"ne_lat\":%d,"
3424 			   "\"sw_lon\":%d,\"sw_lat\":%d,",
3425 			   ais->type22.area.ne_lon,
3426 			   ais->type22.area.ne_lat,
3427 			   ais->type22.area.sw_lon, ais->type22.area.sw_lat);
3428 	}
3429 	str_appendf(buf, buflen,
3430 		       "\"addressed\":%s,\"band_a\":%s,"
3431 		       "\"band_b\":%s,\"zonesize\":%u}\r\n",
3432 		       JSON_BOOL(ais->type22.addressed),
3433 		       JSON_BOOL(ais->type22.band_a),
3434 		       JSON_BOOL(ais->type22.band_b), ais->type22.zonesize);
3435 	break;
3436     case 23:			/* Group Assignment Command */
3437 	if (scaled) {
3438 	    str_appendf(buf, buflen,
3439 			   "\"ne_lon\":\"%f\",\"ne_lat\":\"%f\","
3440 			   "\"sw_lon\":\"%f\",\"sw_lat\":\"%f\","
3441 			   "\"stationtype\":%u,\"stationtype_text\":\"%s\","
3442 			   "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3443 			   "\"interval\":%u,\"quiet\":%u}\r\n",
3444 			   ais->type23.ne_lon / AIS_CHANNEL_LATLON_DIV,
3445 			   ais->type23.ne_lat / AIS_CHANNEL_LATLON_DIV,
3446 			   ais->type23.sw_lon / AIS_CHANNEL_LATLON_DIV,
3447 			   ais->type23.sw_lat / AIS_CHANNEL_LATLON_DIV,
3448 			   ais->type23.stationtype,
3449 			   STATIONTYPE_DISPLAY(ais->type23.stationtype),
3450 			   ais->type23.shiptype,
3451 			   SHIPTYPE_DISPLAY(ais->type23.shiptype),
3452 			   ais->type23.interval, ais->type23.quiet);
3453 	} else {
3454 	    str_appendf(buf, buflen,
3455 			   "\"ne_lon\":%d,\"ne_lat\":%d,"
3456 			   "\"sw_lon\":%d,\"sw_lat\":%d,"
3457 			   "\"stationtype\":%u,\"stationtype_text\":\"%s\","
3458 			   "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3459 			   "\"interval\":%u,\"quiet\":%u}\r\n",
3460 			   ais->type23.ne_lon,
3461 			   ais->type23.ne_lat,
3462 			   ais->type23.sw_lon,
3463 			   ais->type23.sw_lat,
3464 			   ais->type23.stationtype,
3465 			   STATIONTYPE_DISPLAY(ais->type23.stationtype),
3466 			   ais->type23.shiptype,
3467 			   SHIPTYPE_DISPLAY(ais->type23.shiptype),
3468 			   ais->type23.interval, ais->type23.quiet);
3469 	}
3470 	break;
3471     case 24:			/* Class B CS Static Data Report */
3472 	if (ais->type24.part != both) {
3473 	    static char *partnames[] = {"AB", "A", "B"};
3474 	    str_appendf(buf, buflen,
3475 			   "\"part\":\"%s\",",
3476 			   json_stringify(buf1, sizeof(buf1),
3477 					  partnames[ais->type24.part]));
3478 	}
3479 	if (ais->type24.part != part_b)
3480 	    str_appendf(buf, buflen,
3481 			   "\"shipname\":\"%s\",",
3482 			   json_stringify(buf1, sizeof(buf1),
3483 				      ais->type24.shipname));
3484 	if (ais->type24.part != part_a) {
3485 	    str_appendf(buf, buflen,
3486 			   "\"shiptype\":%u,\"shiptype_text\":\"%s\","
3487 			   "\"vendorid\":\"%s\",\"model\":%u,\"serial\":%u,"
3488 			   "\"callsign\":\"%s\",",
3489 			   ais->type24.shiptype,
3490 			   SHIPTYPE_DISPLAY(ais->type24.shiptype),
3491 			   json_stringify(buf1, sizeof(buf1),
3492 					  ais->type24.vendorid),
3493 			   ais->type24.model,
3494 			   ais->type24.serial,
3495 			   json_stringify(buf2, sizeof(buf2),
3496 					  ais->type24.callsign));
3497 	    if (AIS_AUXILIARY_MMSI(ais->mmsi)) {
3498 		str_appendf(buf, buflen,
3499 			       "\"mothership_mmsi\":%u",
3500 			       ais->type24.mothership_mmsi);
3501 	    } else {
3502 		str_appendf(buf, buflen,
3503 			       "\"to_bow\":%u,\"to_stern\":%u,"
3504 			       "\"to_port\":%u,\"to_starboard\":%u",
3505 			       ais->type24.dim.to_bow,
3506 			       ais->type24.dim.to_stern,
3507 			       ais->type24.dim.to_port,
3508 			       ais->type24.dim.to_starboard);
3509 	    }
3510 	}
3511 	str_rstrip_char(buf, ',');
3512 	(void)strlcat(buf, "}\r\n", buflen);
3513 	break;
3514     case 25:			/* Binary Message, Single Slot */
3515 	str_appendf(buf, buflen,
3516 		       "\"addressed\":%s,\"structured\":%s,\"dest_mmsi\":%u,"
3517 		       "\"app_id\":%u,\"data\":\"%zd:%s\"}\r\n",
3518 		       JSON_BOOL(ais->type25.addressed),
3519 		       JSON_BOOL(ais->type25.structured),
3520 		       ais->type25.dest_mmsi,
3521 		       ais->type25.app_id,
3522 		       ais->type25.bitcount,
3523 		       gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3524 				    (char *)ais->type25.bitdata,
3525 				    BITS_TO_BYTES(ais->type25.bitcount)));
3526 	break;
3527     case 26:			/* Binary Message, Multiple Slot */
3528 	str_appendf(buf, buflen,
3529 		       "\"addressed\":%s,\"structured\":%s,\"dest_mmsi\":%u,"
3530 		       "\"app_id\":%u,\"data\":\"%zd:%s\",\"radio\":%u}\r\n",
3531 		       JSON_BOOL(ais->type26.addressed),
3532 		       JSON_BOOL(ais->type26.structured),
3533 		       ais->type26.dest_mmsi,
3534 		       ais->type26.app_id,
3535 		       ais->type26.bitcount,
3536 		       gpsd_hexdump(scratchbuf, sizeof(scratchbuf),
3537 				    (char *)ais->type26.bitdata,
3538 				    BITS_TO_BYTES(ais->type26.bitcount)),
3539 		       ais->type26.radio);
3540 	break;
3541     case 27:			/* Long Range AIS Broadcast message */
3542 	if (scaled)
3543 	    str_appendf(buf, buflen,
3544 			   "\"status\":\"%s\","
3545 			   "\"accuracy\":%s,\"lon\":%.1f,\"lat\":%.1f,"
3546 			   "\"speed\":%u,\"course\":%u,\"raim\":%s,"
3547                            "\"gnss\":%s}\r\n",
3548 			   nav_legends[ais->type27.status],
3549 			   JSON_BOOL(ais->type27.accuracy),
3550 			   ais->type27.lon / AIS_LONGRANGE_LATLON_DIV,
3551 			   ais->type27.lat / AIS_LONGRANGE_LATLON_DIV,
3552 			   ais->type27.speed,
3553 			   ais->type27.course,
3554 			   JSON_BOOL(ais->type27.raim),
3555 			   JSON_BOOL(ais->type27.gnss));
3556 	else
3557 	    str_appendf(buf, buflen,
3558 			   "\"status\":%u,"
3559 			   "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
3560 			   "\"speed\":%u,\"course\":%u,\"raim\":%s,"
3561                            "\"gnss\":%s}\r\n",
3562 			   ais->type27.status,
3563 			   JSON_BOOL(ais->type27.accuracy),
3564 			   ais->type27.lon,
3565 			   ais->type27.lat,
3566 			   ais->type27.speed,
3567 			   ais->type27.course,
3568 			   JSON_BOOL(ais->type27.raim),
3569 			   JSON_BOOL(ais->type27.gnss));
3570 	break;
3571     default:
3572 	str_rstrip_char(buf, ',');
3573 	(void)strlcat(buf, "}\r\n", buflen);
3574 	break;
3575     }
3576 }
3577 #endif /* defined(AIVDM_ENABLE) */
3578 
3579 #ifdef COMPASS_ENABLE
json_att_dump(const struct gps_data_t * gpsdata,char * reply,size_t replylen)3580 void json_att_dump(const struct gps_data_t *gpsdata,
3581 		   char *reply, size_t replylen)
3582 /* dump the contents of an attitude_t structure as JSON */
3583 {
3584     assert(replylen > sizeof(char *));
3585     (void)strlcpy(reply, "{\"class\":\"ATT\",", replylen);
3586     str_appendf(reply, replylen, "\"device\":\"%s\",", gpsdata->dev.path);
3587     if (isfinite(gpsdata->attitude.heading) != 0) {
3588         /* Trimble outputs %.3f, so we do too. */
3589 	str_appendf(reply, replylen,
3590 		       "\"heading\":%.3f,", gpsdata->attitude.heading);
3591 	if (gpsdata->attitude.mag_st != '\0')
3592 	    str_appendf(reply, replylen,
3593 			   "\"mag_st\":\"%c\",", gpsdata->attitude.mag_st);
3594 
3595     }
3596     if (isfinite(gpsdata->attitude.pitch) != 0) {
3597 	str_appendf(reply, replylen,
3598 		       "\"pitch\":%.2f,", gpsdata->attitude.pitch);
3599 	if (gpsdata->attitude.pitch_st != '\0')
3600 	    str_appendf(reply, replylen,
3601 			   "\"pitch_st\":\"%c\",",
3602 			   gpsdata->attitude.pitch_st);
3603 
3604     }
3605     if (isfinite(gpsdata->attitude.yaw) != 0) {
3606 	str_appendf(reply, replylen,
3607 		       "\"yaw\":%.2f,", gpsdata->attitude.yaw);
3608 	if (gpsdata->attitude.yaw_st != '\0')
3609 	    str_appendf(reply, replylen,
3610 			   "\"yaw_st\":\"%c\",", gpsdata->attitude.yaw_st);
3611 
3612     }
3613     if (isfinite(gpsdata->attitude.roll) != 0) {
3614 	str_appendf(reply, replylen,
3615 		       "\"roll\":%.2f,", gpsdata->attitude.roll);
3616 	if (gpsdata->attitude.roll_st != '\0')
3617 	    str_appendf(reply, replylen,
3618 			   "\"roll_st\":\"%c\",", gpsdata->attitude.roll_st);
3619 
3620     }
3621 
3622     if (isfinite(gpsdata->attitude.dip) != 0)
3623 	str_appendf(reply, replylen,
3624 		       "\"dip\":%.3f,", gpsdata->attitude.dip);
3625 
3626     if (isfinite(gpsdata->attitude.mag_len) != 0)
3627 	str_appendf(reply, replylen,
3628 		       "\"mag_len\":%.3f,", gpsdata->attitude.mag_len);
3629     if (isfinite(gpsdata->attitude.mag_x) != 0)
3630 	str_appendf(reply, replylen,
3631 		       "\"mag_x\":%.3f,", gpsdata->attitude.mag_x);
3632     if (isfinite(gpsdata->attitude.mag_y) != 0)
3633 	str_appendf(reply, replylen,
3634 		       "\"mag_y\":%.3f,", gpsdata->attitude.mag_y);
3635     if (isfinite(gpsdata->attitude.mag_z) != 0)
3636 	str_appendf(reply, replylen,
3637 		       "\"mag_z\":%.3f,", gpsdata->attitude.mag_z);
3638 
3639     if (isfinite(gpsdata->attitude.acc_len) != 0)
3640 	str_appendf(reply, replylen,
3641 		       "\"acc_len\":%.3f,", gpsdata->attitude.acc_len);
3642     if (isfinite(gpsdata->attitude.acc_x) != 0)
3643 	str_appendf(reply, replylen,
3644 		       "\"acc_x\":%.3f,", gpsdata->attitude.acc_x);
3645     if (isfinite(gpsdata->attitude.acc_y) != 0)
3646 	str_appendf(reply, replylen,
3647 		       "\"acc_y\":%.3f,", gpsdata->attitude.acc_y);
3648     if (isfinite(gpsdata->attitude.acc_z) != 0)
3649 	str_appendf(reply, replylen,
3650 		       "\"acc_z\":%.3f,", gpsdata->attitude.acc_z);
3651 
3652     if (isfinite(gpsdata->attitude.gyro_x) != 0)
3653 	str_appendf(reply, replylen,
3654 		       "\"gyro_x\":%.3f,", gpsdata->attitude.gyro_x);
3655     if (isfinite(gpsdata->attitude.gyro_y) != 0)
3656 	str_appendf(reply, replylen,
3657 		       "\"gyro_y\":%.3f,", gpsdata->attitude.gyro_y);
3658 
3659     if (isfinite(gpsdata->attitude.temp) != 0)
3660 	str_appendf(reply, replylen,
3661 		       "\"temp\":%.3f,", gpsdata->attitude.temp);
3662     if (isfinite(gpsdata->attitude.depth) != 0)
3663 	str_appendf(reply, replylen,
3664 		       "\"depth\":%.3f,", gpsdata->attitude.depth);
3665 
3666     str_rstrip_char(reply, ',');
3667     (void)strlcat(reply, "}\r\n", replylen);
3668 }
3669 #endif /* COMPASS_ENABLE */
3670 
3671 #ifdef OSCILLATOR_ENABLE
json_oscillator_dump(const struct gps_data_t * datap,char * reply,size_t replylen)3672 void json_oscillator_dump(const struct gps_data_t *datap,
3673 			  char *reply, size_t replylen)
3674 /* dump the contents of an oscillator_t structure as JSON */
3675 {
3676     (void)snprintf(reply, replylen,
3677 		   "{\"class\":\"OSC\",\"device\":\"%s\",\"running\":%s,"
3678                    "\"reference\":%s,\"disciplined\":%s,\"delta\":%d}\r\n",
3679 		   datap->dev.path,
3680 		   JSON_BOOL(datap->osc.running),
3681 		   JSON_BOOL(datap->osc.reference),
3682 		   JSON_BOOL(datap->osc.disciplined),
3683 		   datap->osc.delta);
3684 }
3685 #endif /* OSCILLATOR_ENABLE */
3686 
json_data_report(const gps_mask_t changed,const struct gps_device_t * session,const struct gps_policy_t * policy,char * buf,size_t buflen)3687 void json_data_report(const gps_mask_t changed,
3688 		 const struct gps_device_t *session,
3689 		 const struct gps_policy_t *policy,
3690 		 char *buf, size_t buflen)
3691 /* report a session state in JSON */
3692 {
3693     const struct gps_data_t *datap = &session->gpsdata;
3694     buf[0] = '\0';
3695 
3696     if ((changed & REPORT_IS) != 0) {
3697 	json_tpv_dump(session, policy, buf+strlen(buf), buflen-strlen(buf));
3698     }
3699 
3700     if ((changed & GST_SET) != 0) {
3701 	json_noise_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3702     }
3703 
3704     if ((changed & SATELLITE_SET) != 0) {
3705 	json_sky_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3706     }
3707 
3708     if ((changed & SUBFRAME_SET) != 0) {
3709 	json_subframe_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3710     }
3711 
3712     if ((changed & RAW_IS) != 0) {
3713 	json_raw_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3714     }
3715 
3716 #ifdef COMPASS_ENABLE
3717     if ((changed & ATTITUDE_SET) != 0) {
3718 	json_att_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3719     }
3720 #endif /* COMPASS_ENABLE */
3721 
3722 #ifdef RTCM104V2_ENABLE
3723     if ((changed & RTCM2_SET) != 0) {
3724 	json_rtcm2_dump(&datap->rtcm2, datap->dev.path,
3725 			buf+strlen(buf), buflen-strlen(buf));
3726     }
3727 #endif /* RTCM104V2_ENABLE */
3728 
3729 #ifdef RTCM104V3_ENABLE
3730     if ((changed & RTCM3_SET) != 0) {
3731 	json_rtcm3_dump(&datap->rtcm3, datap->dev.path,
3732 			buf+strlen(buf), buflen-strlen(buf));
3733     }
3734 #endif /* RTCM104V3_ENABLE */
3735 
3736 #ifdef AIVDM_ENABLE
3737     if ((changed & AIS_SET) != 0) {
3738 	json_aivdm_dump(&datap->ais, datap->dev.path,
3739 			policy->scaled,
3740 			buf+strlen(buf), buflen-strlen(buf));
3741     }
3742 #endif /* AIVDM_ENABLE */
3743 
3744 #ifdef OSCILLATOR_ENABLE
3745     if ((changed & OSCILLATOR_SET) != 0) {
3746 	json_oscillator_dump(datap, buf+strlen(buf), buflen-strlen(buf));
3747     }
3748 #endif /* OSCILLATOR_ENABLE */
3749 }
3750 
3751 #undef JSON_BOOL
3752 #endif /* SOCKET_EXPORT_ENABLE */
3753 
3754 /* gpsd_json.c ends here */
3755