summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ais_json.c14
-rwxr-xr-xdevtools/ais.py2
-rwxr-xr-xdevtools/tablegen.py2
-rw-r--r--driver_ais.c24
-rw-r--r--gpsd_json.c157
-rw-r--r--gpsdecode.c16
-rw-r--r--jsongen.py.in5
-rw-r--r--test/sample.aivdm88
-rw-r--r--test/sample.aivdm.chk24
-rw-r--r--test/sample.aivdm.js.chk26
-rw-r--r--test/sample.aivdm.ju.chk24
-rw-r--r--www/AIVDM.txt2
12 files changed, 319 insertions, 65 deletions
diff --git a/ais_json.c b/ais_json.c
index f7a69665..75130280 100644
--- a/ais_json.c
+++ b/ais_json.c
@@ -97,8 +97,9 @@ int json_ais_read(const char *buf,
ais->type4.hour = AIS_HOUR_NOT_AVAILABLE;
ais->type4.minute = AIS_MINUTE_NOT_AVAILABLE;
ais->type4.second = AIS_SECOND_NOT_AVAILABLE;
+ // We use %u for the date to allow for dodgy years (>9999) to go through
// cppcheck-suppress uninitvar
- (void)sscanf(timestamp, "%4u-%02u-%02uT%02u:%02u:%02uZ",
+ (void)sscanf(timestamp, "%u-%02u-%02uT%02u:%02u:%02uZ",
&ais->type4.year,
&ais->type4.month,
&ais->type4.day,
@@ -157,14 +158,15 @@ int json_ais_read(const char *buf,
status = json_read_object(buf, json_ais6_fid16, endptr);
imo = true;
}
- else if (strstr(buf, "\"fid\":18,") != NULL || strstr(buf, "\"fid\":11,") != NULL) {
+ else if (strstr(buf, "\"fid\":18,") != NULL) {
status = json_read_object(buf, json_ais6_fid18, endptr);
if (status == 0) {
ais->type6.dac1fid18.day = AIS_DAY_NOT_AVAILABLE;
ais->type6.dac1fid18.hour = AIS_HOUR_NOT_AVAILABLE;
ais->type6.dac1fid18.minute = AIS_MINUTE_NOT_AVAILABLE;
// cppcheck-suppress uninitvar
- (void)sscanf(arrival, "%02uT%02u:%02uZ",
+ (void)sscanf(arrival, "%02u-%02uT%02u:%02uZ",
+ &ais->type6.dac1fid18.month,
&ais->type6.dac1fid18.day,
&ais->type6.dac1fid18.hour,
&ais->type6.dac1fid18.minute);
@@ -360,8 +362,14 @@ int json_ais_read(const char *buf,
status = json_read_object(buf, json_ais24, endptr);
} else if (strstr(buf, "\"type\":25,") != NULL) {
status = json_read_object(buf, json_ais25, endptr);
+ if (status == 0)
+ lenhex_unpack(data, &ais->type25.bitcount,
+ ais->type25.bitdata, sizeof(ais->type25.bitdata));
} else if (strstr(buf, "\"type\":26,") != NULL) {
status = json_read_object(buf, json_ais26, endptr);
+ if (status == 0)
+ lenhex_unpack(data, &ais->type26.bitcount,
+ ais->type26.bitdata, sizeof(ais->type26.bitdata));
} else if (strstr(buf, "\"type\":27,") != NULL) {
status = json_read_object(buf, json_ais27, endptr);
} else {
diff --git a/devtools/ais.py b/devtools/ais.py
index a2ea58ac..f9f5706d 100755
--- a/devtools/ais.py
+++ b/devtools/ais.py
@@ -864,7 +864,7 @@ type27 = (
aivdm_decode = (
bitfield('msgtype', 6, 'unsigned', 0, "Message Type",
- validator=lambda n: n > 0 and n <= 26),
+ validator=lambda n: n > 0 and n <= 27),
bitfield('repeat', 2, 'unsigned', None, "Repeat Indicator"),
bitfield('mmsi', 30, 'unsigned', 0, "MMSI"),
# This is the master dispatch on AIS message type
diff --git a/devtools/tablegen.py b/devtools/tablegen.py
index 4a68b8df..c0b3cba8 100755
--- a/devtools/tablegen.py
+++ b/devtools/tablegen.py
@@ -327,7 +327,7 @@ def make_json_dumper(wfp):
print >>wfp, base + "}"
print >>wfp, base + "if (buf[strlen(buf) - 1] == ',')"
print >>wfp, base + step + "buf[strlen(buf)-1] = '\0';"
- print >>wfp, base + "(void)strlcat(buf, ']}\r\n,', buflen - strlen(buf));"
+ print >>wfp, base + "(void)strlcat(buf, ']}\r\n', buflen - strlen(buf));"
def make_json_generator(wfp):
# Write a stanza for jsongen.py.in describing how to generate a
diff --git a/driver_ais.c b/driver_ais.c
index 385a448c..af58fcaa 100644
--- a/driver_ais.c
+++ b/driver_ais.c
@@ -204,6 +204,7 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type6.dac1fid12.amount = UBITS(345, 10);
ais->type6.dac1fid12.unit = UBITS(355, 2);
/* skip 3 bits */
+ imo = true;
break;
case 14: /* IMO236 - Tidal Window */
ais->type6.dac1fid32.month = UBITS(88, 4);
@@ -225,9 +226,11 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type6.dac1fid32.ntidals = u;
#undef ARRAY_BASE
#undef ELEMENT_SIZE
+ imo = true;
break;
case 15: /* IMO236 - Extended Ship Static and Voyage Related Data */
ais->type6.dac1fid15.airdraught = UBITS(56, 11);
+ imo = true;
break;
case 16: /* IMO236 - Number of persons on board */
if (ais->type6.bitcount == 136)
@@ -247,6 +250,7 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type6.dac1fid18.lon = SBITS(268, 25);
ais->type6.dac1fid18.lat = SBITS(293, 24);
/* skip 43 bits */
+ imo = true;
break;
case 20: /* IMO289 - Berthing data - addressed */
ais->type6.dac1fid20.linkage = UBITS(88, 10);
@@ -287,6 +291,7 @@ bool ais_binary_decode(struct ais_t *ais,
UCHARS(191, ais->type6.dac1fid20.berth_name);
ais->type6.dac1fid20.berth_lon = SBITS(311, 25);
ais->type6.dac1fid20.berth_lat = SBITS(336, 24);
+ imo = true;
break;
case 23: /* IMO289 - Area notice - addressed */
break;
@@ -298,6 +303,7 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type6.dac1fid25.cargos[u].subtype = UBITS(104+u*17,13);
}
ais->type6.dac1fid25.ncargos = u;
+ imo = true;
break;
case 28: /* IMO289 - Route info - addressed */
ais->type6.dac1fid28.linkage = UBITS(88, 10);
@@ -318,12 +324,14 @@ bool ais_binary_decode(struct ais_t *ais,
}
#undef ARRAY_BASE
#undef ELEMENT_SIZE
+ imo = true;
break;
case 30: /* IMO289 - Text description - addressed */
ais->type6.dac1fid30.linkage = UBITS(88, 10);
from_sixbit((char *)bits,
98, bitlen-98,
ais->type6.dac1fid30.text);
+ imo = true;
break;
case 32: /* IMO289 - Tidal Window */
ais->type6.dac1fid32.month = UBITS(88, 4);
@@ -345,6 +353,7 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type6.dac1fid32.ntidals = u;
#undef ARRAY_BASE
#undef ELEMENT_SIZE
+ imo = true;
break;
}
if (!imo)
@@ -443,10 +452,12 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type8.dac1fid13.thour = UBITS(457, 5);
ais->type8.dac1fid13.tminute = UBITS(462, 6);
/* skip 4 bits */
+ imo = true;
break;
case 15: /* IMO236 - Extended ship and voyage */
ais->type8.dac1fid15.airdraught = UBITS(56, 11);
/* skip 5 bits */
+ imo = true;
break;
case 17: /* IMO289 - VTS-generated/synthetic targets */
#define ARRAY_BASE 56
@@ -479,6 +490,7 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type8.dac1fid17.ntargets = u;
#undef ARRAY_BASE
#undef ELEMENT_SIZE
+ imo = true;
break;
case 19: /* IMO289 - Marine Traffic Signal */
ais->type8.dac1fid19.linkage = UBITS(56, 10);
@@ -491,6 +503,7 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type8.dac1fid19.minute = UBITS(247, 6);
ais->type8.dac1fid19.nextsignal = UBITS(253, 5);
/* skip 102 bits */
+ imo = true;
break;
case 21: /* IMO289 - Weather obs. report from ship */
break;
@@ -519,12 +532,14 @@ bool ais_binary_decode(struct ais_t *ais,
}
#undef ARRAY_BASE
#undef ELEMENT_SIZE
+ imo = true;
break;
case 29: /* IMO289 - Text Description - broadcast */
ais->type8.dac1fid29.linkage = UBITS(56, 10);
from_sixbit((char *)bits,
66, bitlen-66,
ais->type8.dac1fid29.text);
+ imo = true;
break;
case 31: /* IMO289 - Meteorological/Hydrological data */
ais->type8.dac1fid31.lon = SBITS(56, 25);
@@ -807,8 +822,8 @@ bool ais_binary_decode(struct ais_t *ais,
ais->type22.area.sw_lon = SBITS(104, 18);
ais->type22.area.sw_lat = SBITS(122, 17);
} else {
- ais->type22.mmsi.dest1 = SBITS(69, 30);
- ais->type22.mmsi.dest2 = SBITS(104, 30);
+ ais->type22.mmsi.dest1 = UBITS(69, 30);
+ ais->type22.mmsi.dest2 = UBITS(104, 30);
}
ais->type22.band_a = UBITS(140, 1);
ais->type22.band_b = UBITS(141, 1);
@@ -946,6 +961,11 @@ bool ais_binary_decode(struct ais_t *ais,
(ais->type26.bitcount + 7) / 8);
break;
case 27: /* Long Range AIS Broadcast message */
+ if (bitlen != 96) {
+ gpsd_report(LOG_WARN, "AIVDM message type 27 size not 96 bits (%zd).\n",
+ bitlen);
+ return false;
+ }
ais->type27.accuracy = (bool)UBITS(38, 1);
ais->type27.raim = UBITS(39, 1)!=0;
ais->type27.status = UBITS(40, 4);
diff --git a/gpsd_json.c b/gpsd_json.c
index c5a00849..7fb359ab 100644
--- a/gpsd_json.c
+++ b/gpsd_json.c
@@ -1369,6 +1369,7 @@ void json_aivdm_dump(const struct ais_t *ais,
char buf1[JSON_VAL_MAX * 2 + 1];
char buf2[JSON_VAL_MAX * 2 + 1];
char buf3[JSON_VAL_MAX * 2 + 1];
+ char buf4[JSON_VAL_MAX * 2 + 1];
bool imo;
int i;
@@ -1636,8 +1637,8 @@ void json_aivdm_dump(const struct ais_t *ais,
static const char *light_status[] = {
"No light or no monitoring",
- "Light ON"
- "Light OFF"
+ "Light ON",
+ "Light OFF",
"Light ERROR"
};
@@ -1725,8 +1726,10 @@ void json_aivdm_dump(const struct ais_t *ais,
case 11: /* UTC/Date Response */
/* some fields have beem merged to an ISO8601 date */
if (scaled) {
+ // The use of %u instead of %04u for the year is to allow
+ // out-of-band year values.
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "\"timestamp\":\"%4u-%02u-%02uT%02u:%02u:%02uZ\","
+ "\"timestamp\":\"%u-%02u-%02uT%02u:%02u:%02uZ\","
"\"accuracy\":%s,\"lon\":%.4f,\"lat\":%.4f,"
"\"epfd\":\"%s\",\"raim\":%s,\"radio\":%u}\r\n",
ais->type4.year,
@@ -1742,7 +1745,7 @@ void json_aivdm_dump(const struct ais_t *ais,
JSON_BOOL(ais->type4.raim), ais->type4.radio);
} else {
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "\"timestamp\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\","
+ "\"timestamp\":\"%u-%02u-%02uT%02u:%02u:%02uZ\","
"\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
"\"epfd\":%u,\"raim\":%s,\"radio\":%u}\r\n",
ais->type4.year,
@@ -1882,26 +1885,32 @@ void json_aivdm_dump(const struct ais_t *ais,
"\"nextport\":\"%s\",\"eta\":\"%02u-%02uT%02u:%02uZ\","
"\"dangerous\":\"%s\",\"imdcat\":\"%s\","
"\"unid\":%u,\"amount\":%u,\"unit\":%u}\r\n",
- ais->type6.dac1fid12.lastport,
+ json_stringify(buf1, sizeof(buf1),
+ ais->type6.dac1fid12.lastport),
ais->type6.dac1fid12.lmonth,
ais->type6.dac1fid12.lday,
ais->type6.dac1fid12.lhour,
ais->type6.dac1fid12.lminute,
- ais->type6.dac1fid12.nextport,
+ json_stringify(buf2, sizeof(buf2),
+ ais->type6.dac1fid12.nextport),
ais->type6.dac1fid12.nmonth,
ais->type6.dac1fid12.nday,
ais->type6.dac1fid12.nhour,
ais->type6.dac1fid12.nminute,
- ais->type6.dac1fid12.dangerous,
- ais->type6.dac1fid12.imdcat,
+ json_stringify(buf3, sizeof(buf3),
+ ais->type6.dac1fid12.dangerous),
+ json_stringify(buf4, sizeof(buf4),
+ ais->type6.dac1fid12.imdcat),
ais->type6.dac1fid12.unid,
ais->type6.dac1fid12.amount,
ais->type6.dac1fid12.unit);
+ imo = true;
break;
case 15: /* IMO236 - Extended Ship Static and Voyage Related Data */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"airdraught\":%u}\r\n",
ais->type6.dac1fid15.airdraught);
+ imo = true;
break;
case 16: /* IMO236 - Number of persons on board */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
@@ -1910,14 +1919,16 @@ void json_aivdm_dump(const struct ais_t *ais,
break;
case 18: /* IMO289 - Clearance time to enter port */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "\"linkage\":%u,\"arrival\":\"%u-%uT%u:%uZ\",\"portname\":\"%s\",\"destination\":\"%s\",",
+ "\"linkage\":%u,\"arrival\":\"%02u-%02uT%02u:%02uZ\",\"portname\":\"%s\",\"destination\":\"%s\",",
ais->type6.dac1fid18.linkage,
ais->type6.dac1fid18.month,
ais->type6.dac1fid18.day,
- ais->type6.dac1fid18.hour,
+ ais->type6.dac1fid18.hour,
ais->type6.dac1fid18.minute,
- ais->type6.dac1fid18.portname,
- ais->type6.dac1fid18.destination);
+ json_stringify(buf1, sizeof(buf1),
+ ais->type6.dac1fid18.portname),
+ json_stringify(buf2, sizeof(buf2),
+ ais->type6.dac1fid18.destination));
if (scaled)
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"lon\":%.3f,\"lat\":%.3f}\r\n",
@@ -1928,6 +1939,7 @@ void json_aivdm_dump(const struct ais_t *ais,
"\"lon\":%d,\"lat\":%d}\r\n",
ais->type6.dac1fid18.lon,
ais->type6.dac1fid18.lat);
+ imo = true;
break;
case 20: /* IMO289 - Berthing Data */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
@@ -1942,7 +1954,7 @@ void json_aivdm_dump(const struct ais_t *ais,
"\"shiprepair\":%u,\"surveyor\":%u,"
"\"steam\":%u,\"tugs\":%u,\"solidwaste\":%u,"
"\"liquidwaste\":%u,\"hazardouswaste\":%u,"
- "\"ballast\":%u,\"additional\":%u,\""
+ "\"ballast\":%u,\"additional\":%u,"
"\"regional1\":%u,\"regional2\":%u,"
"\"future1\":%u,\"future2\":%u,"
"\"berth_name\":\"%s\",",
@@ -1980,7 +1992,8 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type6.dac1fid20.regional2,
ais->type6.dac1fid20.future1,
ais->type6.dac1fid20.future2,
- ais->type6.dac1fid20.berth_name);
+ json_stringify(buf1, sizeof(buf1),
+ ais->type6.dac1fid20.berth_name));
if (scaled)
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"berth_lon\":%.3f,"
@@ -1997,6 +2010,7 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type6.dac1fid20.berth_lon,
ais->type6.dac1fid20.berth_lat,
ais->type6.dac1fid20.berth_depth);
+ imo = true;
break;
case 23: /* IMO289 - Area notice - addressed */
break;
@@ -2013,7 +2027,8 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type6.dac1fid25.cargos[i].subtype);
if (buf[strlen(buf) - 1] == ',')
buf[strlen(buf) - 1] = '\0';
- (void)strlcat(buf, "]}\r\n,", buflen);
+ (void)strlcat(buf, "]}\r\n", buflen);
+ imo = true;
break;
case 28: /* IMO289 - Route info - addressed */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
@@ -2029,7 +2044,7 @@ void json_aivdm_dump(const struct ais_t *ais,
"\"rtype\":%u,",
ais->type6.dac1fid28.rtype);
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "\"start\":\"%02u-%02uT%02u:%02uZ\",\"duration\":%u,\"waypoints:[",
+ "\"start\":\"%02u-%02uT%02u:%02uZ\",\"duration\":%u,\"waypoints\":[",
ais->type6.dac1fid28.month,
ais->type6.dac1fid28.day,
ais->type6.dac1fid28.hour,
@@ -2049,14 +2064,16 @@ void json_aivdm_dump(const struct ais_t *ais,
}
if (buf[strlen(buf) - 1] == ',')
buf[strlen(buf)-1] = '\0';
- (void)strlcat(buf, "]}\r\n,", buflen);
+ (void)strlcat(buf, "]}\r\n", buflen);
+ imo = true;
break;
case 30: /* IMO289 - Text description - addressed */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"linkage\":%u,\"text\":\"%s\"}\r\n",
ais->type6.dac1fid30.linkage,
- json_stringify(buf1, sizeof(buf1),
+ json_stringify(buf1, sizeof(buf1),
ais->type6.dac1fid30.text));
+ imo = true;
break;
case 14: /* IMO236 - Tidal Window */
case 32: /* IMO289 - Tidal Window */
@@ -2077,7 +2094,7 @@ void json_aivdm_dump(const struct ais_t *ais,
tp->lon,
tp->lat);
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "\"from_hour\":%u,\"from_min\":%u,\"to_hour\":%u,\"to_min\":%u,\"cdir\":%u",
+ "\"from_hour\":%u,\"from_min\":%u,\"to_hour\":%u,\"to_min\":%u,\"cdir\":%u,",
tp->from_hour,
tp->from_min,
tp->to_hour,
@@ -2094,15 +2111,17 @@ void json_aivdm_dump(const struct ais_t *ais,
}
if (buf[strlen(buf) - 1] == ',')
buf[strlen(buf)-1] = '\0';
- (void)strlcat(buf, "]}\r\n,", buflen);
+ (void)strlcat(buf, "]}\r\n", buflen);
+ imo = true;
break;
}
if (!imo)
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"data\":\"%zd:%s\"}\r\n",
ais->type6.bitcount,
- gpsd_hexdump((char *)ais->type6.bitdata,
- (ais->type6.bitcount + 7) / 8));
+ json_stringify(buf1, sizeof(buf1),
+ gpsd_hexdump((char *)ais->type6.bitdata,
+ (ais->type6.bitcount + 7) / 8)));
break;
case 7: /* Binary Acknowledge */
case 13: /* Safety Related Acknowledge */
@@ -2269,9 +2288,12 @@ void json_aivdm_dump(const struct ais_t *ais,
"\"extunit\":%u,"
"\"from\":\"%02u-%02uT%02u:%02u\","
"\"to\":\"%02u-%02uT%02u:%02u\"}\r\n",
- ais->type8.dac1fid13.reason,
- ais->type8.dac1fid13.closefrom,
- ais->type8.dac1fid13.closeto,
+ json_stringify(buf1, sizeof(buf1),
+ ais->type8.dac1fid13.reason),
+ json_stringify(buf2, sizeof(buf2),
+ ais->type8.dac1fid13.closefrom),
+ json_stringify(buf3, sizeof(buf3),
+ ais->type8.dac1fid13.closeto),
ais->type8.dac1fid13.radius,
ais->type8.dac1fid13.extunit,
ais->type8.dac1fid13.fmonth,
@@ -2282,11 +2304,13 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type8.dac1fid13.tday,
ais->type8.dac1fid13.thour,
ais->type8.dac1fid13.tminute);
+ imo = true;
break;
case 15: /* IMO236 - Extended ship and voyage */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"airdraught\":%u}\r\n",
ais->type8.dac1fid15.airdraught);
+ imo = true;
break;
case 17: /* IMO289 - VTS-generated/synthetic targets */
(void)strlcat(buf, "\"targets\":[", buflen);
@@ -2316,13 +2340,15 @@ void json_aivdm_dump(const struct ais_t *ais,
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"%s\":\"%s\",",
idtypes[ais->type8.dac1fid17.targets[i].idtype],
- ais->type8.dac1fid17.targets[i].id.callsign);
+ json_stringify(buf1, sizeof(buf1),
+ ais->type8.dac1fid17.targets[i].id.callsign));
break;
default:
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"%s\":\"%s\",",
idtypes[ais->type8.dac1fid17.targets[i].idtype],
- ais->type8.dac1fid17.targets[i].id.other);
+ json_stringify(buf1, sizeof(buf1),
+ ais->type8.dac1fid17.targets[i].id.other));
}
if (scaled)
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
@@ -2342,14 +2368,16 @@ void json_aivdm_dump(const struct ais_t *ais,
}
if (buf[strlen(buf) - 1] == ',')
buf[strlen(buf) - 1] = '\0';
- (void)strlcat(buf, "]}\r\n,", buflen);
+ (void)strlcat(buf, "]}\r\n", buflen);
+ imo = true;
break;
case 19: /* IMO289 - Marine Traffic Signal */
if (scaled)
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"linkage\":%u,\"station\":\"%s\",\"lon\":%.3f,\"lat\":%.3f,\"status\":%u,\"signal\":\"%s\",\"hour\":%u,\"minute\":%u,\"nextsignal\":\"%s\"}\r\n",
ais->type8.dac1fid19.linkage,
- ais->type8.dac1fid19.station,
+ json_stringify(buf1, sizeof(buf1),
+ ais->type8.dac1fid19.station),
ais->type8.dac1fid19.lon / AIS_LATLON3_SCALE,
ais->type8.dac1fid19.lat / AIS_LATLON3_SCALE,
ais->type8.dac1fid19.status,
@@ -2361,7 +2389,8 @@ void json_aivdm_dump(const struct ais_t *ais,
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"linkage\":%u,\"station\":\"%s\",\"lon\":%d,\"lat\":%d,\"status\":%u,\"signal\":%u,\"hour\":%u,\"minute\":%u,\"nextsignal\":%u}\r\n",
ais->type8.dac1fid19.linkage,
- ais->type8.dac1fid19.station,
+ json_stringify(buf1, sizeof(buf1),
+ ais->type8.dac1fid19.station),
ais->type8.dac1fid19.lon,
ais->type8.dac1fid19.lat,
ais->type8.dac1fid19.status,
@@ -2369,6 +2398,7 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type8.dac1fid19.hour,
ais->type8.dac1fid19.minute,
ais->type8.dac1fid19.nextsignal);
+ imo = true;
break;
case 21: /* IMO289 - Weather obs. report from ship */
break;
@@ -2412,14 +2442,16 @@ void json_aivdm_dump(const struct ais_t *ais,
}
if (buf[strlen(buf) - 1] == ',')
buf[strlen(buf) - 1] = '\0';
- (void)strlcat(buf, "]}\r\n,", buflen);
+ (void)strlcat(buf, "]}\r\n", buflen);
+ imo = true;
break;
case 29: /* IMO289 - Text Description - broadcast */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"linkage\":%u,\"text\":\"%s\"}\r\n",
ais->type8.dac1fid29.linkage,
- json_stringify(buf1, sizeof(buf1),
+ json_stringify(buf1, sizeof(buf1),
ais->type8.dac1fid29.text));
+ imo = true;
break;
case 31: /* IMO289 - Meteorological/Hydrological data */
/* some fields have been merged to an ISO8601 partial date */
@@ -2556,8 +2588,9 @@ void json_aivdm_dump(const struct ais_t *ais,
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"data\":\"%zd:%s\"}\r\n",
ais->type8.bitcount,
- gpsd_hexdump((char *)ais->type8.bitdata,
- (ais->type8.bitcount + 7) / 8));
+ json_stringify(buf1, sizeof(buf1),
+ gpsd_hexdump((char *)ais->type8.bitdata,
+ (ais->type8.bitcount + 7) / 8)));
break;
case 9: /* Standard SAR Aircraft Position Report */
if (scaled) {
@@ -2746,7 +2779,8 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type19.heading,
ais->type19.second,
ais->type19.regional,
- ais->type19.shipname,
+ json_stringify(buf1, sizeof(buf1),
+ ais->type19.shipname),
SHIPTYPE_DISPLAY(ais->type19.shiptype),
ais->type19.to_bow,
ais->type19.to_stern,
@@ -2773,7 +2807,8 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type19.heading,
ais->type19.second,
ais->type19.regional,
- ais->type19.shipname,
+ json_stringify(buf1, sizeof(buf1),
+ ais->type19.shipname),
ais->type19.shiptype,
ais->type19.to_bow,
ais->type19.to_stern,
@@ -2842,7 +2877,8 @@ void json_aivdm_dump(const struct ais_t *ais,
"\"off_position\":%s,\"raim\":%s,"
"\"virtual_aid\":%s}\r\n",
ais->type21.aid_type,
- ais->type21.name,
+ json_stringify(buf1, sizeof(buf1),
+ ais->type21.name),
JSON_BOOL(ais->type21.accuracy),
ais->type21.lon,
ais->type21.lat,
@@ -2867,7 +2903,7 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type22.txrx, JSON_BOOL(ais->type22.power));
if (ais->type22.addressed) {
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "\"dest1\":%u,\"dest2\":%u",
+ "\"dest1\":%u,\"dest2\":%u,",
ais->type22.mmsi.dest1, ais->type22.mmsi.dest2);
} else if (scaled) {
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
@@ -2937,7 +2973,10 @@ void json_aivdm_dump(const struct ais_t *ais,
}
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"vendorid\":\"%s\",\"callsign\":\"%s\",",
- ais->type24.vendorid, ais->type24.callsign);
+ json_stringify(buf1, sizeof(buf1),
+ ais->type24.vendorid),
+ json_stringify(buf2, sizeof(buf2),
+ ais->type24.callsign));
if (AIS_AUXILIARY_MMSI(ais->mmsi)) {
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"mothership_\"mmsi\":%u}\r\n",
@@ -2967,7 +3006,7 @@ void json_aivdm_dump(const struct ais_t *ais,
case 26: /* Binary Message, Multiple Slot */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
"\"addressed\":%s,\"structured\":%s,\"dest_mmsi\":%u,"
- "\"app_id\":%u,\"data\":\"%zd:%s\"\"radio\":%u}\r\n",
+ "\"app_id\":%u,\"data\":\"%zd:%s\",\"radio\":%u}\r\n",
JSON_BOOL(ais->type26.addressed),
JSON_BOOL(ais->type26.structured),
ais->type26.dest_mmsi,
@@ -2978,18 +3017,32 @@ void json_aivdm_dump(const struct ais_t *ais,
ais->type26.radio);
break;
case 27: /* Long Range AIS Broadcast message */
- (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "\"status\":\"%s\","
- "\"accuracy\":%s,\"lon\":%.1f,\"lat\":%.1f,"
- "\"speed\":%u,\"course\":%u,raim\":%s,\"gnss\":%s}\r\n",
- nav_legends[ais->type27.status],
- JSON_BOOL(ais->type27.accuracy),
- ais->type27.lon / AIS_LONGRANGE_LATLON_SCALE,
- ais->type27.lat / AIS_LONGRANGE_LATLON_SCALE,
- ais->type27.speed,
- ais->type27.course,
- JSON_BOOL(ais->type27.raim),
- JSON_BOOL(ais->type27.gnss));
+ if (scaled)
+ (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
+ "\"status\":\"%s\","
+ "\"accuracy\":%s,\"lon\":%.1f,\"lat\":%.1f,"
+ "\"speed\":%u,\"course\":%u,\"raim\":%s,\"gnss\":%s}\r\n",
+ nav_legends[ais->type27.status],
+ JSON_BOOL(ais->type27.accuracy),
+ ais->type27.lon / AIS_LONGRANGE_LATLON_SCALE,
+ ais->type27.lat / AIS_LONGRANGE_LATLON_SCALE,
+ ais->type27.speed,
+ ais->type27.course,
+ JSON_BOOL(ais->type27.raim),
+ JSON_BOOL(ais->type27.gnss));
+ else
+ (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
+ "\"status\":%u,"
+ "\"accuracy\":%s,\"lon\":%d,\"lat\":%d,"
+ "\"speed\":%u,\"course\":%u,\"raim\":%s,\"gnss\":%s}\r\n",
+ ais->type27.status,
+ JSON_BOOL(ais->type27.accuracy),
+ ais->type27.lon,
+ ais->type27.lat,
+ ais->type27.speed,
+ ais->type27.course,
+ JSON_BOOL(ais->type27.raim),
+ JSON_BOOL(ais->type27.gnss));
break;
default:
if (buf[strlen(buf) - 1] == ',')
diff --git a/gpsdecode.c b/gpsdecode.c
index 51a9c2eb..1664e5ac 100644
--- a/gpsdecode.c
+++ b/gpsdecode.c
@@ -443,7 +443,7 @@ static void aivdm_csv_dump(struct ais_t *ais, char *buf, size_t buflen)
break;
case 25: /* Binary Message, Single Slot */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "%u|%u|%u|%u|%zd:%s\r\n",
+ "%u|%u|%u|%u|%zd:%s",
(uint) ais->type25.addressed,
(uint) ais->type25.structured,
ais->type25.dest_mmsi,
@@ -454,7 +454,7 @@ static void aivdm_csv_dump(struct ais_t *ais, char *buf, size_t buflen)
break;
case 26: /* Binary Message, Multiple Slot */
(void)snprintf(buf + strlen(buf), buflen - strlen(buf),
- "%u|%u|%u|%u|%zd:%s:%u\r\n",
+ "%u|%u|%u|%u|%zd:%s:%u",
(uint) ais->type26.addressed,
(uint) ais->type26.structured,
ais->type26.dest_mmsi,
@@ -464,6 +464,18 @@ static void aivdm_csv_dump(struct ais_t *ais, char *buf, size_t buflen)
(ais->type26.bitcount + 7) / 8),
ais->type26.radio);
break;
+ case 27: /* Long Range AIS Broadcast message */
+ (void)snprintf(buf + strlen(buf), buflen - strlen(buf),
+ "%u|%u|%d|%d|%u|%u|%u|%u",
+ ais->type27.status,
+ (uint)ais->type27.accuracy,
+ ais->type27.lon,
+ ais->type27.lat,
+ ais->type27.speed,
+ ais->type27.course,
+ (uint)ais->type27.raim,
+ (uint)ais->type27.gnss);
+ break;
default:
(void)snprintf(buf + strlen(buf),
buflen - strlen(buf),
diff --git a/jsongen.py.in b/jsongen.py.in
index cf785291..227a8f38 100644
--- a/jsongen.py.in
+++ b/jsongen.py.in
@@ -808,7 +808,10 @@ def generate(spec):
else:
print >>sys.stderr, "explicit length specification required"
raise SystemExit, 1
- report += " };\n"
+ report += """\
+ {NULL}
+ };
+"""
# Generate the main structure definition describing this parse.
# It may have object subarrays.
if pacify_splint:
diff --git a/test/sample.aivdm b/test/sample.aivdm
index a12dc169..7637fe0c 100644
--- a/test/sample.aivdm
+++ b/test/sample.aivdm
@@ -201,6 +201,14 @@
#
# FIX-ME: We need a type 6 test case that requires more than one AIVDM fragment.
#
+# Type 6:
+# Advertised as DAC 1, FID 12, but full of garbage.
+!AIVDM,1,1,,A,63LBA4;WBevJ04k0=@E=B0td,0*17
+# Advertised as DAC 1, FID 14, but full of garbage
+!AIVDM,1,1,,A,6h2E:81>NmKC04p0J<000?vv20Ru,0*31
+# Advertised as DAC 1, FID 18, but full of 0
+!AIVDM,1,1,,B,6h2E3MDrDRiB0580@00000000000,0*04
+
# Type 7:
# From AISHub - reported immediately after the preceding type 6,
# which matches it. One destination MMSI. noaadata-0.43 fails
@@ -591,7 +599,10 @@
# Message type : 0
# Slot offset : 0
#
-# FIX-ME: Need an example of the 160-bit variant of type 15 with two MMSIs.
+# Type 15:
+# From Christian Gagneraud via aishub
+# This is the 160-bit variant of type 15 with two MMSIs.
+!AIVDM,1,1,,A,?39a?2PjKFFPD01o:Gq1igvp2<3w,0*0B
#
# Type 16:
# From AISHub. These are only a regression test to check that the C and Python
@@ -792,6 +803,11 @@ DGNSS data : 376:7c0556c07031febbf52924fe33fa2933ffa0fd2932fdb7062
# Channel A Band : 0
# Zone size : 4
#
+# Type 22:
+# From Christian Gagneraud via aishub
+# Addressed case.
+!AIVDM,1,1,,A,F@@W>gOP00PH=JrN9l000?wB2HH;,0*44
+#
# Type 23:
# From AISHub. Only a regression test to check that the C and Python decoders
# do the same thing, not yet checked against other decoders.
@@ -831,6 +847,51 @@ DGNSS data : 376:7c0556c07031febbf52924fe33fa2933ffa0fd2932fdb7062
# dimC: 0
# dimD: 5
#
+# Type 25:
+# From Christian Gagneraud via aishub
+# Addressed and structured
+!AIVDM,1,1,,A,JB3R0GO7p>vQL8tjw0b5hqpd0706kh9d3lR2vbl0400,2*40
+#
+# Type 25:
+# From Christian Gagneraud via aishub
+# Addressed and not structured
+!AIVDM,1,1,,A,I6SWo?8P00a3PKpEKEVj0?vNP<65,0*73
+#
+# Type 25:
+# From Christian Gagneraud via aishub
+# Broadcast and structured
+!AIVDM,1,1,,A,I8IRGB40QPPa0:<HP::V=gwv0l48,0*0E
+#
+# Type 25:
+# From Christian Gagneraud via aishub
+# Broadcast and not structured
+!AIVDM,1,1,,A,I6SWVNP001a3P8FEKNf=Qb0@00S8,0*6B
+#
+# Type 26:
+# From Christian Gagneraud via aishub
+# Addressed and structured
+!AIVDM,1,1,,A,JB3R0GO7p>vQL8tjw0b5hqpd0706kh9d3lR2vbl0400,2*40
+#
+# Type 26:
+# From Christian Gagneraud via aishub
+# Addressed and not structured
+!AIVDM,1,1,,A,J1@@0IK70PGgT740000000000@000?D0ih1e00006JlPC9C3,0*6B
+#
+# Type 26
+# From Christian Gagneraud via aishub
+# Broadcast and structured
+!AIVDM,1,1,,B,JaL0mr5P000DtRDMddr@0?vF06iD,0*75
+#
+# Type 26
+# From Christian Gagneraud via aishub
+# Broadcast and not structured
+!AIVDM,1,1,,A,J0@00@370>t0Lh3P0000200H:2rN92,4*14
+#
+# Type 27:
+# From Christian Gagneraud via aishub
+# The only message 27 in more than 25 millions messages that is 96 bits long!
+!AIVDM,1,1,,A,KCQ9r=hrFUnH7P00,0*41
+#
#
##############################################################################
# Invalid packet tests:
@@ -874,3 +935,28 @@ AIVDM,2,2,1,B,00000000000,2*26
!AIVDM,2,1,2,A,542M92h00001@<7;?G0PD4i@R0<tqA8tj37>220o0h:2240Ht500000000000000,0*3C
!AIVDM,2,2,2,A,0000002,2*24
!AIVDM,2,2,6,B,00000000000,2*21
+##############################################################################
+# Error and corner case tests:
+##############################################################################
+# Non printable and control character in data or string fields
+!AIVDM,1,1,,A,647sBv00b=E006P9>0,4*1B
+# Type 4 with date/time set to N/A
+!AIVDM,1,1,,A,402Fha0000Htt<tSF0l4Q@000d20,0*65
+# Type 4 with YYYY/MM/DD OK, but HHmmSS N/A
+!AIVDM,1,1,,B,4028n@iuiPpttwIWI<Hl>8700PS:,0*60
+# Type 4 with year set to 10196 (0x27D4), idempotency use to fail on this one
+# It has a bad epfd as well.
+!AIVDM,1,1,,B,4>O7m7Iu@<9qUfbtm`vSnwvH20S8,0*46
+# Type 22 with MMSI1=4059193694, use to fail due to UBITS() instead of SBITS()
+!AIVDM,1,1,,B,Fe3>>MOD@GDF?ThcoCk02?ioQie4,0*03
+# A type 6, DAC 1, FID 18 with plenty of N/A fields, idempotency use to fail
+!AIVDM,1,1,,B,602E:s0tw@9B0580@00000000000,0*68
+# A type 6, DAC 1, FID 30. use to fail idempotency
+!AIVDM,1,1,,B,6h2E3MPr<buN05p0J00000000000,0*0A
+# A type 6, DAC 1, FID 18, use to fail idempotency
+!AIVDM,1,1,,B,602E:s0tw@9B0580@00000000000,0*68
+# Array of struct. use to crash "gpsdecode -j -e -u"
+!AIVDM,1,1,,A,6h2E3MHrg19P0600@00000000000,0*02
+!AIVDM,1,1,,A,6h2E3N0rThqP0600J00000000000,0*51
+
+
diff --git a/test/sample.aivdm.chk b/test/sample.aivdm.chk
index 3b698060..31dc496e 100644
--- a/test/sample.aivdm.chk
+++ b/test/sample.aivdm.chk
@@ -7,6 +7,9 @@
6|1|150834090|3|313240222|0|669|11|48:eb2f118f7ff1
6|0|992509976|0|2500912|0|235|10|274|1|1|2|2|0|0|0
6|0|265538450|0|2655651|0|1|40|16:0000
+6|0|230986000|2|970110950|1|1|12|56:30435445530000
+6|3|002443808|0|329176500|1|1|14|80:00000000000000000000
+6|3|002442101|1|244615956|1|1|18|80:01000000000000000000
7|0|002655651|265538450|0|0|0
7|1|655901842|158483613|321823389|836359488|0
7|2|537411077|43101326|717096664|76161024|0
@@ -32,6 +35,7 @@
14|0|311764000|TEST
15|0|368578000|5158|5|0|0|0|0|0|0
15|3|003669720|367014320|3|516|5|617|0|0|0
+15|0|211439370|211507560|5|0|55|663|605843451|32|560
16|0|002053501|224251000|200|0|0|0|0
17|0|002734450|17478|35992|376:7c0556c07031febbf52924fe33fa2933ffa0fd2932fdb7062922fe3809292afde9122929fcf7002923ffd20c29aaaa
18|0|338087471|0|1|0|-44443279|24410724|796|511|49|0x0|1|0|1|1|1|1|0xe0006
@@ -42,8 +46,28 @@
20|0|003160097|47|1|7|250|2250|1|7|1125|856|5|7|1125|0|0|0|0
21|0|123456789|20|CHINA ROSE MURPHY EXPRESS ALERT|0|-73619155|28752371|5|5|5|5|1|50|165|0x0|0|0
22|0|003160048|2087|2088|0|0|-44100|27330|-48100|25400|0|0|0|4
+22|1|017419965|3584|8|1|1|28144881|268435519|1|0|0|4
23|0|002268120|1578|30642|1096|30408|6|0|2|9|0
24|0|271041815|PROGUY|60|1D00014|TC6163|0|15|0|5
+26|1|137920605|1|1|838351848|23587|180:efa1708f32fc0a85c39e2c007006cf026c0f4882faad00:0
+25|0|440006460|1|0|134218384|0|128:20000a4381be156d59b200ff9e80c185
+25|0|563648328|0|1|0|134|112:082900a31880a2a636fffe034108
+25|0|440002170|0|0|0|0|128:00001a438085956deb8d86a0100008c8
+26|1|137920605|1|1|838351848|23587|180:efa1708f32fc0a85c39e2c007006cf026c0f4882faad00:0
+26|0|084148325|1|0|834699643|0|228:q:0
+26|2|633353704|0|1|0|24576|92:0014f2251db2ce9000ff9600:0
+26|0|016777280|0|0|0|0|116::0
+27|1|236091959|3|0|-92521|52239|0|0|0|0
24|0|271040660|GOZDEM-1|37|1C00045|YM5504|0|24|0|6
5|0|271010059|0|0|TCA2350|HEALTH CONTROL 13|55|6|10|2|2|1|00-00T24:60Z|20||0
5|0|271010059|0|0|TCA2350|HEALTH CONTROL 13|55|6|10|2|2|1|00-00T24:60Z|20||0
+6|0|276747000|0|2766160|0|1|40|16: 8
+4|0|002470052|0000-00-00T24:60:60Z|0|108600000|54600000|0|0|0x2c080
+4|0|002242115|2012-06-01T24:60:60Z|1|-5031130|26021408|7|0|0x208ca
+4|0|972158237|10196-00-24T09:57:37Z|1|123070132|65599231|14|1|0x8c8
+22|2|875794037|3396|373|1|0|837968222|254804543|1|0|1|7
+6|0|002444012|0|255803540|1|1|18|80:01000000000000000000
+6|3|002442102|0|244100055|1|1|30|80:01000000280000000000
+6|0|002444012|0|255803540|1|1|18|80:01000000000000000000
+6|3|002442101|2|246351000|0|1|32|80:00000000000000000000
+6|3|002442104|0|245679000|0|1|32|80:00000000000000000000
diff --git a/test/sample.aivdm.js.chk b/test/sample.aivdm.js.chk
index edaf887b..ff61c1b5 100644
--- a/test/sample.aivdm.js.chk
+++ b/test/sample.aivdm.js.chk
@@ -5,8 +5,11 @@
{"class":"AIS","device":"stdin","type":4,"repeat":0,"mmsi":3669702,"scaled":true,"timestamp":"2007-05-14T19:57:39Z","accuracy":true,"lon":-76.3524,"lat":36.8838,"epfd":"Surveyed","raim":false,"radio":67039}
{"class":"AIS","device":"stdin","type":5,"repeat":0,"mmsi":351759000,"scaled":true,"imo":9134270,"ais_version":0,"callsign":"3FOF8","shipname":"EVER DIADEM","shiptype":"Cargo - all ships of this type","to_bow":225,"to_stern":70,"to_port":1,"to_starboard":31,"epfd":"GPS","eta":"05-15T14:00Z","draught":12.2,"destination":"NEW YORK","dte":0}
{"class":"AIS","device":"stdin","type":6,"repeat":1,"mmsi":150834090,"scaled":true,"seqno":3,"dest_mmsi":313240222,"retransmit":false,"dac":669,"fid":11,"data":"48:eb2f118f7ff1"}
-{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":992509976,"scaled":true,"seqno":0,"dest_mmsi":2500912,"retransmit":false,"dac":235,"fid":10,"off_pos":false,"alarm":false,"stat_ext":0,"ana_int":13.70,"ana_ext1":0.05,"ana_ext2":0.05,"racon":"RACON operational","light":"No RACON installed"}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":992509976,"scaled":true,"seqno":0,"dest_mmsi":2500912,"retransmit":false,"dac":235,"fid":10,"off_pos":false,"alarm":false,"stat_ext":0,"ana_int":13.70,"ana_ext1":0.05,"ana_ext2":0.05,"racon":"RACON operational","light":"Light OFF"}
{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":265538450,"scaled":true,"seqno":0,"dest_mmsi":2655651,"retransmit":false,"dac":1,"fid":40,"data":"16:0000"}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":230986000,"scaled":true,"seqno":2,"dest_mmsi":970110950,"retransmit":true,"dac":1,"fid":12,"lastport":"0CTES","departure":"05-04T00:60Z","nextport":",","eta":"00-00T00:00Z","dangerous":"","imdcat":"","unid":0,"amount":0,"unit":0}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2443808,"scaled":true,"seqno":0,"dest_mmsi":329176500,"retransmit":true,"dac":1,"fid":14,"month":0,"day":0,"tidals":[{"lon":279.339,"lat":-406.323,"from_hour":1,"from_min":5,"to_hour":29,"to_min":0,"cdir":0,"cspeed":0.0}]}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442101,"scaled":true,"seqno":1,"dest_mmsi":244615956,"retransmit":true,"dac":1,"fid":18,"linkage":1,"arrival":"00-00T00:00Z","portname":"","destination":"","lon":0.000,"lat":0.000}
{"class":"AIS","device":"stdin","type":7,"repeat":0,"mmsi":2655651,"scaled":true,"mmsi1":265538450,"mmsi2":0,"mmsi3":0,"mmsi4":0}
{"class":"AIS","device":"stdin","type":7,"repeat":1,"mmsi":655901842,"scaled":true,"mmsi1":158483613,"mmsi2":321823389,"mmsi3":836359488,"mmsi4":0}
{"class":"AIS","device":"stdin","type":7,"repeat":2,"mmsi":537411077,"scaled":true,"mmsi1":43101326,"mmsi2":717096664,"mmsi3":76161024,"mmsi4":0}
@@ -32,6 +35,7 @@
{"class":"AIS","device":"stdin","type":14,"repeat":0,"mmsi":311764000,"scaled":true,"text":"TEST"}
{"class":"AIS","device":"stdin","type":15,"repeat":0,"mmsi":368578000,"scaled":true,"mmsi1":5158,"type1_1":5,"offset1_1":0,"type1_2":0,"offset1_2":0,"mmsi2":0,"type2_1":0,"offset2_1":0}
{"class":"AIS","device":"stdin","type":15,"repeat":3,"mmsi":3669720,"scaled":true,"mmsi1":367014320,"type1_1":3,"offset1_1":516,"type1_2":5,"offset1_2":617,"mmsi2":0,"type2_1":0,"offset2_1":0}
+{"class":"AIS","device":"stdin","type":15,"repeat":0,"mmsi":211439370,"scaled":true,"mmsi1":211507560,"type1_1":5,"offset1_1":0,"type1_2":55,"offset1_2":663,"mmsi2":605843451,"type2_1":32,"offset2_1":560}
{"class":"AIS","device":"stdin","type":16,"repeat":0,"mmsi":2053501,"scaled":true,"mmsi1":224251000,"offset1":200,"increment1":0,"mmsi2":0,"offset2":0,"increment2":0}
{"class":"AIS","device":"stdin","type":17,"repeat":0,"mmsi":2734450,"scaled":true,"lon":29.1,"lat":60.0,"data":"376:7c0556c07031febbf52924fe33fa2933ffa0fd2932fdb7062922fe3809292afde9122929fcf7002923ffd20c29aaaa"}
{"class":"AIS","device":"stdin","type":18,"repeat":0,"mmsi":338087471,"scaled":true,"reserved":0,"speed":0.1,"accuracy":false,"lon":-74.0721,"lat":40.6845,"course":79.6,"heading":511,"second":49,"regional":0,"cs":true,"display":false,"dsc":true,"band":true,"msg22":true,"raim":true,"radio":917510}
@@ -42,8 +46,28 @@
{"class":"AIS","device":"stdin","type":20,"repeat":0,"mmsi":3160097,"scaled":true,"offset1":47,"number1":1,"timeout1":7,"increment1":250,"offset2":2250,"number2":1,"timeout2":7,"increment2":1125,"offset3":856,"number3":5,"timeout3":7,"increment3":1125,"offset4":0,"number4":0,"timeout4":0,"increment4":0}
{"class":"AIS","device":"stdin","type":21,"repeat":0,"mmsi":123456789,"scaled":true,"aid_type":"INVALID NAVAID TYPE","name":"CHINA ROSE MURPHY EXPRESS ALERT","lon":-122.6986,"lat":47.9206,"accuracy":false,"to_bow":5,"to_stern":5,"to_port":5,"to_starboard":5,"epfd":"GPS","second":50,"regional":165,"off_position":false,"raim":false,"virtual_aid":false}
{"class":"AIS","device":"stdin","type":22,"repeat":0,"mmsi":3160048,"scaled":true,"channel_a":2087,"channel_b":2088,"txrx":0,"power":false,"ne_lon":"-73.500000","ne_lat":"45.550000","sw_lon":"-80.166667","sw_lat":"42.333333","addressed":false,"band_a":false,"band_b":false,"zonesize":4}
+{"class":"AIS","device":"stdin","type":22,"repeat":1,"mmsi":17419965,"scaled":true,"channel_a":3584,"channel_b":8,"txrx":1,"power":true,"dest1":28144881,"dest2":268435519,"addressed":true,"band_a":false,"band_b":false,"zonesize":4}
{"class":"AIS","device":"stdin","type":23,"repeat":0,"mmsi":2268120,"scaled":true,"ne_lon":"2.630000","ne_lat":"51.070000","sw_lon":"1.826667","sw_lat":"50.680000","stationtype":"Regional use and inland waterways","shiptype":"Not available","interval":9,"quiet":0}
{"class":"AIS","device":"stdin","type":24,"repeat":0,"mmsi":271041815,"scaled":true,"shipname":"PROGUY","shiptype":"Passenger - all ships of this type","vendorid":"1D00014","callsign":"TC6163","to_bow":0,"to_stern":15,"to_port":0,"to_starboard":5}
+{"class":"AIS","device":"stdin","type":26,"repeat":1,"mmsi":137920605,"scaled":true,"addressed":true,"structured":true,"dest_mmsi":838351848,"app_id":23587,"data":"180:efa1708f32fc0a85c39e2c007006cf026c0f4882faad00","radio":0}
+{"class":"AIS","device":"stdin","type":25,"repeat":0,"mmsi":440006460,"scaled":true,"addressed":true,"structured":false,"dest_mmsi":134218384,"app_id":0,"data":"128:20000a4381be156d59b200ff9e80c185"}
+{"class":"AIS","device":"stdin","type":25,"repeat":0,"mmsi":563648328,"scaled":true,"addressed":false,"structured":true,"dest_mmsi":0,"app_id":134,"data":"112:082900a31880a2a636fffe034108"}
+{"class":"AIS","device":"stdin","type":25,"repeat":0,"mmsi":440002170,"scaled":true,"addressed":false,"structured":false,"dest_mmsi":0,"app_id":0,"data":"128:00001a438085956deb8d86a0100008c8"}
+{"class":"AIS","device":"stdin","type":26,"repeat":1,"mmsi":137920605,"scaled":true,"addressed":true,"structured":true,"dest_mmsi":838351848,"app_id":23587,"data":"180:efa1708f32fc0a85c39e2c007006cf026c0f4882faad00","radio":0}
+{"class":"AIS","device":"stdin","type":26,"repeat":0,"mmsi":84148325,"scaled":true,"addressed":true,"structured":false,"dest_mmsi":834699643,"app_id":0,"data":"228:q","radio":0}
+{"class":"AIS","device":"stdin","type":26,"repeat":2,"mmsi":633353704,"scaled":true,"addressed":false,"structured":true,"dest_mmsi":0,"app_id":24576,"data":"92:0014f2251db2ce9000ff9600","radio":0}
+{"class":"AIS","device":"stdin","type":26,"repeat":0,"mmsi":16777280,"scaled":true,"addressed":false,"structured":false,"dest_mmsi":0,"app_id":0,"data":"116:","radio":0}
+{"class":"AIS","device":"stdin","type":27,"repeat":1,"mmsi":236091959,"scaled":true,"status":"Restricted manoeuverability","accuracy":false,"lon":-154.2,"lat":87.1,"speed":0,"course":0,"raim":false,"gnss":false}
{"class":"AIS","device":"stdin","type":24,"repeat":0,"mmsi":271040660,"scaled":true,"shipname":"GOZDEM-1","shiptype":"Pleasure Craft","vendorid":"1C00045","callsign":"YM5504","to_bow":0,"to_stern":24,"to_port":0,"to_starboard":6}
{"class":"AIS","device":"stdin","type":5,"repeat":0,"mmsi":271010059,"scaled":true,"imo":0,"ais_version":0,"callsign":"TCA2350","shipname":"HEALTH CONTROL 13","shiptype":"Law Enforcement","to_bow":6,"to_stern":10,"to_port":2,"to_starboard":2,"epfd":"GPS","eta":"00-00T24:60Z","draught":2.0,"destination":"","dte":0}
{"class":"AIS","device":"stdin","type":5,"repeat":0,"mmsi":271010059,"scaled":true,"imo":0,"ais_version":0,"callsign":"TCA2350","shipname":"HEALTH CONTROL 13","shiptype":"Law Enforcement","to_bow":6,"to_stern":10,"to_port":2,"to_starboard":2,"epfd":"GPS","eta":"00-00T24:60Z","draught":2.0,"destination":"","dte":0}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":276747000,"scaled":true,"seqno":0,"dest_mmsi":2766160,"retransmit":false,"dac":1,"fid":40,"data":"16:\t8"}
+{"class":"AIS","device":"stdin","type":4,"repeat":0,"mmsi":2470052,"scaled":true,"timestamp":"0-00-00T24:60:60Z","accuracy":false,"lon":181.0000,"lat":91.0000,"epfd":"Undefined","raim":false,"radio":180352}
+{"class":"AIS","device":"stdin","type":4,"repeat":0,"mmsi":2242115,"scaled":true,"timestamp":"2012-06-01T24:60:60Z","accuracy":true,"lon":-8.3852,"lat":43.3690,"epfd":"Surveyed","raim":false,"radio":133322}
+{"class":"AIS","device":"stdin","type":4,"repeat":0,"mmsi":972158237,"scaled":true,"timestamp":"10196-00-24T09:57:37Z","accuracy":true,"lon":205.1169,"lat":109.3321,"epfd":"(null)","raim":true,"radio":2248}
+{"class":"AIS","device":"stdin","type":22,"repeat":2,"mmsi":875794037,"scaled":true,"channel_a":3396,"channel_b":373,"txrx":1,"power":false,"dest1":837968222,"dest2":254804543,"addressed":true,"band_a":false,"band_b":true,"zonesize":7}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":2444012,"scaled":true,"seqno":0,"dest_mmsi":255803540,"retransmit":true,"dac":1,"fid":18,"linkage":1,"arrival":"00-00T00:00Z","portname":"","destination":"","lon":0.000,"lat":0.000}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442102,"scaled":true,"seqno":0,"dest_mmsi":244100055,"retransmit":true,"dac":1,"fid":30,"linkage":1,"text":"("}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":2444012,"scaled":true,"seqno":0,"dest_mmsi":255803540,"retransmit":true,"dac":1,"fid":18,"linkage":1,"arrival":"00-00T00:00Z","portname":"","destination":"","lon":0.000,"lat":0.000}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442101,"scaled":true,"seqno":2,"dest_mmsi":246351000,"retransmit":false,"dac":1,"fid":32,"month":0,"day":0,"tidals":[{"lon":-279.620,"lat":0.000,"from_hour":0,"from_min":0,"to_hour":0,"to_min":0,"cdir":0,"cspeed":0.0}]}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442104,"scaled":true,"seqno":0,"dest_mmsi":245679000,"retransmit":false,"dac":1,"fid":32,"month":0,"day":0,"tidals":[{"lon":-104.858,"lat":0.000,"from_hour":0,"from_min":0,"to_hour":0,"to_min":0,"cdir":0,"cspeed":0.0}]}
diff --git a/test/sample.aivdm.ju.chk b/test/sample.aivdm.ju.chk
index db3b3f8c..e73afb80 100644
--- a/test/sample.aivdm.ju.chk
+++ b/test/sample.aivdm.ju.chk
@@ -7,6 +7,9 @@
{"class":"AIS","device":"stdin","type":6,"repeat":1,"mmsi":150834090,"scaled":false,"seqno":3,"dest_mmsi":313240222,"retransmit":false,"dac":669,"fid":11,"data":"48:eb2f118f7ff1"}
{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":992509976,"scaled":false,"seqno":0,"dest_mmsi":2500912,"retransmit":false,"dac":235,"fid":10,"off_pos":false,"alarm":false,"stat_ext":0,"ana_int":274,"ana_ext1":1,"ana_ext2":1,"racon":2,"light":2}
{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":265538450,"scaled":false,"seqno":0,"dest_mmsi":2655651,"retransmit":false,"dac":1,"fid":40,"data":"16:0000"}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":230986000,"scaled":false,"seqno":2,"dest_mmsi":970110950,"retransmit":true,"dac":1,"fid":12,"lastport":"0CTES","departure":"05-04T00:60Z","nextport":",","eta":"00-00T00:00Z","dangerous":"","imdcat":"","unid":0,"amount":0,"unit":0}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2443808,"scaled":false,"seqno":0,"dest_mmsi":329176500,"retransmit":true,"dac":1,"fid":14,"month":0,"day":0,"tidals":[{"lon":16760328,"lat":-24379392,"from_hour":1,"from_min":5,"to_hour":29,"to_min":0,"cdir":0,"cspeed":0}]}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442101,"scaled":false,"seqno":1,"dest_mmsi":244615956,"retransmit":true,"dac":1,"fid":18,"linkage":1,"arrival":"00-00T00:00Z","portname":"","destination":"","lon":0,"lat":0}
{"class":"AIS","device":"stdin","type":7,"repeat":0,"mmsi":2655651,"scaled":false,"mmsi1":265538450,"mmsi2":0,"mmsi3":0,"mmsi4":0}
{"class":"AIS","device":"stdin","type":7,"repeat":1,"mmsi":655901842,"scaled":false,"mmsi1":158483613,"mmsi2":321823389,"mmsi3":836359488,"mmsi4":0}
{"class":"AIS","device":"stdin","type":7,"repeat":2,"mmsi":537411077,"scaled":false,"mmsi1":43101326,"mmsi2":717096664,"mmsi3":76161024,"mmsi4":0}
@@ -32,6 +35,7 @@
{"class":"AIS","device":"stdin","type":14,"repeat":0,"mmsi":311764000,"scaled":false,"text":"TEST"}
{"class":"AIS","device":"stdin","type":15,"repeat":0,"mmsi":368578000,"scaled":false,"mmsi1":5158,"type1_1":5,"offset1_1":0,"type1_2":0,"offset1_2":0,"mmsi2":0,"type2_1":0,"offset2_1":0}
{"class":"AIS","device":"stdin","type":15,"repeat":3,"mmsi":3669720,"scaled":false,"mmsi1":367014320,"type1_1":3,"offset1_1":516,"type1_2":5,"offset1_2":617,"mmsi2":0,"type2_1":0,"offset2_1":0}
+{"class":"AIS","device":"stdin","type":15,"repeat":0,"mmsi":211439370,"scaled":false,"mmsi1":211507560,"type1_1":5,"offset1_1":0,"type1_2":55,"offset1_2":663,"mmsi2":605843451,"type2_1":32,"offset2_1":560}
{"class":"AIS","device":"stdin","type":16,"repeat":0,"mmsi":2053501,"scaled":false,"mmsi1":224251000,"offset1":200,"increment1":0,"mmsi2":0,"offset2":0,"increment2":0}
{"class":"AIS","device":"stdin","type":17,"repeat":0,"mmsi":2734450,"scaled":false,"lon":17478,"lat":35992,"data":"376:7c0556c07031febbf52924fe33fa2933ffa0fd2932fdb7062922fe3809292afde9122929fcf7002923ffd20c29aaaa"}
{"class":"AIS","device":"stdin","type":18,"repeat":0,"mmsi":338087471,"scaled":false,"reserved":0,"speed":1,"accuracy":false,"lon":-44443279,"lat":24410724,"course":796,"heading":511,"second":49,"regional":0,"cs":true,"display":false,"dsc":true,"band":true,"msg22":true,"raim":true,"radio":917510}
@@ -42,8 +46,28 @@
{"class":"AIS","device":"stdin","type":20,"repeat":0,"mmsi":3160097,"scaled":false,"offset1":47,"number1":1,"timeout1":7,"increment1":250,"offset2":2250,"number2":1,"timeout2":7,"increment2":1125,"offset3":856,"number3":5,"timeout3":7,"increment3":1125,"offset4":0,"number4":0,"timeout4":0,"increment4":0}
{"class":"AIS","device":"stdin","type":21,"repeat":0,"mmsi":123456789,"scaled":false,"aid_type":20,"name":"CHINA ROSE MURPHY EXPRESS ALERT","accuracy":false,"lon":-73619155,"lat":28752371,"to_bow":5,"to_stern":5,"to_port":5,"to_starboard":5,"epfd":1,"second":50,"regional":165,"off_position":false,"raim":false,"virtual_aid":false}
{"class":"AIS","device":"stdin","type":22,"repeat":0,"mmsi":3160048,"scaled":false,"channel_a":2087,"channel_b":2088,"txrx":0,"power":false,"ne_lon":-44100,"ne_lat":27330,"sw_lon":-48100,"sw_lat":25400,"addressed":false,"band_a":false,"band_b":false,"zonesize":4}
+{"class":"AIS","device":"stdin","type":22,"repeat":1,"mmsi":17419965,"scaled":false,"channel_a":3584,"channel_b":8,"txrx":1,"power":true,"dest1":28144881,"dest2":268435519,"addressed":true,"band_a":false,"band_b":false,"zonesize":4}
{"class":"AIS","device":"stdin","type":23,"repeat":0,"mmsi":2268120,"scaled":false,"ne_lon":1578,"ne_lat":30642,"sw_lon":1096,"sw_lat":30408,"stationtype":6,"shiptype":0,"interval":9,"quiet":0}
{"class":"AIS","device":"stdin","type":24,"repeat":0,"mmsi":271041815,"scaled":false,"shipname":"PROGUY","shiptype":60,"vendorid":"1D00014","callsign":"TC6163","to_bow":0,"to_stern":15,"to_port":0,"to_starboard":5}
+{"class":"AIS","device":"stdin","type":26,"repeat":1,"mmsi":137920605,"scaled":false,"addressed":true,"structured":true,"dest_mmsi":838351848,"app_id":23587,"data":"180:efa1708f32fc0a85c39e2c007006cf026c0f4882faad00","radio":0}
+{"class":"AIS","device":"stdin","type":25,"repeat":0,"mmsi":440006460,"scaled":false,"addressed":true,"structured":false,"dest_mmsi":134218384,"app_id":0,"data":"128:20000a4381be156d59b200ff9e80c185"}
+{"class":"AIS","device":"stdin","type":25,"repeat":0,"mmsi":563648328,"scaled":false,"addressed":false,"structured":true,"dest_mmsi":0,"app_id":134,"data":"112:082900a31880a2a636fffe034108"}
+{"class":"AIS","device":"stdin","type":25,"repeat":0,"mmsi":440002170,"scaled":false,"addressed":false,"structured":false,"dest_mmsi":0,"app_id":0,"data":"128:00001a438085956deb8d86a0100008c8"}
+{"class":"AIS","device":"stdin","type":26,"repeat":1,"mmsi":137920605,"scaled":false,"addressed":true,"structured":true,"dest_mmsi":838351848,"app_id":23587,"data":"180:efa1708f32fc0a85c39e2c007006cf026c0f4882faad00","radio":0}
+{"class":"AIS","device":"stdin","type":26,"repeat":0,"mmsi":84148325,"scaled":false,"addressed":true,"structured":false,"dest_mmsi":834699643,"app_id":0,"data":"228:q","radio":0}
+{"class":"AIS","device":"stdin","type":26,"repeat":2,"mmsi":633353704,"scaled":false,"addressed":false,"structured":true,"dest_mmsi":0,"app_id":24576,"data":"92:0014f2251db2ce9000ff9600","radio":0}
+{"class":"AIS","device":"stdin","type":26,"repeat":0,"mmsi":16777280,"scaled":false,"addressed":false,"structured":false,"dest_mmsi":0,"app_id":0,"data":"116:","radio":0}
+{"class":"AIS","device":"stdin","type":27,"repeat":1,"mmsi":236091959,"scaled":false,"status":3,"accuracy":false,"lon":-92521,"lat":52239,"speed":0,"course":0,"raim":false,"gnss":false}
{"class":"AIS","device":"stdin","type":24,"repeat":0,"mmsi":271040660,"scaled":false,"shipname":"GOZDEM-1","shiptype":37,"vendorid":"1C00045","callsign":"YM5504","to_bow":0,"to_stern":24,"to_port":0,"to_starboard":6}
{"class":"AIS","device":"stdin","type":5,"repeat":0,"mmsi":271010059,"scaled":false,"imo":0,"ais_version":0,"callsign":"TCA2350","shipname":"HEALTH CONTROL 13","shiptype":55,"to_bow":6,"to_stern":10,"to_port":2,"to_starboard":2,"epfd":1,"eta":"00-00T24:60Z","draught":20,"destination":"","dte":0}
{"class":"AIS","device":"stdin","type":5,"repeat":0,"mmsi":271010059,"scaled":false,"imo":0,"ais_version":0,"callsign":"TCA2350","shipname":"HEALTH CONTROL 13","shiptype":55,"to_bow":6,"to_stern":10,"to_port":2,"to_starboard":2,"epfd":1,"eta":"00-00T24:60Z","draught":20,"destination":"","dte":0}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":276747000,"scaled":false,"seqno":0,"dest_mmsi":2766160,"retransmit":false,"dac":1,"fid":40,"data":"16:\t8"}
+{"class":"AIS","device":"stdin","type":4,"repeat":0,"mmsi":2470052,"scaled":false,"timestamp":"0-00-00T24:60:60Z","accuracy":false,"lon":108600000,"lat":54600000,"epfd":0,"raim":false,"radio":180352}
+{"class":"AIS","device":"stdin","type":4,"repeat":0,"mmsi":2242115,"scaled":false,"timestamp":"2012-06-01T24:60:60Z","accuracy":true,"lon":-5031130,"lat":26021408,"epfd":7,"raim":false,"radio":133322}
+{"class":"AIS","device":"stdin","type":4,"repeat":0,"mmsi":972158237,"scaled":false,"timestamp":"10196-00-24T09:57:37Z","accuracy":true,"lon":123070132,"lat":65599231,"epfd":14,"raim":true,"radio":2248}
+{"class":"AIS","device":"stdin","type":22,"repeat":2,"mmsi":875794037,"scaled":false,"channel_a":3396,"channel_b":373,"txrx":1,"power":false,"dest1":837968222,"dest2":254804543,"addressed":true,"band_a":false,"band_b":true,"zonesize":7}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":2444012,"scaled":false,"seqno":0,"dest_mmsi":255803540,"retransmit":true,"dac":1,"fid":18,"linkage":1,"arrival":"00-00T00:00Z","portname":"","destination":"","lon":0,"lat":0}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442102,"scaled":false,"seqno":0,"dest_mmsi":244100055,"retransmit":true,"dac":1,"fid":30,"linkage":1,"text":"("}
+{"class":"AIS","device":"stdin","type":6,"repeat":0,"mmsi":2444012,"scaled":false,"seqno":0,"dest_mmsi":255803540,"retransmit":true,"dac":1,"fid":18,"linkage":1,"arrival":"00-00T00:00Z","portname":"","destination":"","lon":0,"lat":0}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442101,"scaled":false,"seqno":2,"dest_mmsi":246351000,"retransmit":false,"dac":1,"fid":32,"month":0,"day":0,"tidals":[{"lon":-16777216,"lat":0,"from_hour":0,"from_min":0,"to_hour":0,"to_min":0,"cdir":0,"cspeed":0}]}
+{"class":"AIS","device":"stdin","type":6,"repeat":3,"mmsi":2442104,"scaled":false,"seqno":0,"dest_mmsi":245679000,"retransmit":false,"dac":1,"fid":32,"month":0,"day":0,"tidals":[{"lon":-6291456,"lat":0,"from_hour":0,"from_min":0,"to_hour":0,"to_min":0,"cdir":0,"cspeed":0}]}
diff --git a/www/AIVDM.txt b/www/AIVDM.txt
index 9bf9b54b..3909dd6e 100644
--- a/www/AIVDM.txt
+++ b/www/AIVDM.txt
@@ -4079,7 +4079,7 @@ increased propagation delays associated with long-range detection
|95-95 | 1 |Spare | |x|Not used
|==============================================================================
-Note: Type 27 has not been observed in the wild as of April 2011.
+Note: Type 27 is quite rare, and when seen it is rarely 96-bits long.
== Local extensions ==