summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Gagneraud <cgagneraud@techworks.ie>2012-06-04 01:14:27 +0100
committerChristian Gagneraud <chris@techworks.ie>2012-06-20 10:05:41 +0100
commit5214d309cf07b428ccfac647f7ac9ed8d597186f (patch)
tree672ecb9d61a1973252be347eb5b1bf13b7dfe7d1
parent9e6cdcb27d6af143721bb27bd886fc43d1214626 (diff)
downloadgpsd-5214d309cf07b428ccfac647f7ac9ed8d597186f.tar.gz
[AIS] Fix plenty of small bugs unveiled by aishub test campaign.
A test campaign was run with live data from aishub.net (ca. 25 millions sentence, 1.2 GB of logs), the goal was to check the decoding and the JSON parsing and dumping code. On all the AIS data, the following tests were done: - AIVDM decoding - JSON scaled dump - JSON validity check on scaled dump. - JSON unscaled dump - JSON validity check on unscaled dump - JSON unscaled idempotency check - JSON unscaled/scaled idempotency check The last check consisted of: - parsing the unscaled dump - dumping it back in scaled mode - compare it with the original scaled dump. This test campaign has unveiled plenty of small bugs all around the AIS code. This patch fixes all of them and adds new sentences in the regression tests.
-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 ==