schwehr / libais

C++ decoder for Automatic Identification System for tracking ships and decoding maritime information
Other
215 stars 94 forks source link

Cannot hadle VDM messages with pad ≥ 6 #185

Open lsix opened 5 years ago

lsix commented 5 years ago

Hello,

I am currently working on data received with a kanaton 3 receiver we use. The receiver gives me AIVDM messages with a padding of 6 bits:

!AIVDM,1,1,1,B,13HP:R?P1UwcS7fKaH=h6gvP2L0Qr,6*50

Even if this is odd to add an extra char in the payload and then tell to ignore it via the padding, it is remains kind of coherent (I do not know enough the standard to tell if his is illegal or suboptimal). I can decode it properly if handle this strange padding manually:

import ais

ais.decode('13HP:R?P1UwcS7fKaH=h6gvP2L0Q', 0)

Do you think it would be reasonable to have libais able decode this kind of message? I have versions working locally (only for messages 1 2 and 3, I did not work with other messages yet, see end of ticket)

If supporting this sort of malformed data is not an option, I will handle the VDM parsing and payload normalization outside of libais, but it makes more sense for me to have it shared.

Please let me know what is your favorite option, I’ll create a proper PR with the option you see most appropriate.

Best regards, lsix.

Ways to correct the issue

I see 2 ways to have my strange messages handled:

remove a few asserts

The first approach would be to remove the asserts that require that pad < 6, and a few others related to the pad value. It has pros and cons, but the ::libais::AisBitset already does all the work, even if with strange padding numbers. After, as long as the subclasses uses num_bits, and do not care about pad value, they already have properly normalized data and are safe.

It looks something like

diff --git a/src/libais/ais.cpp b/src/libais/ais.cpp
index ebb537a..587e8ca 100644
--- a/src/libais/ais.cpp
+++ b/src/libais/ais.cpp
@@ -71,7 +71,6 @@ AisMsg::AisMsg(const char *nmea_payload, const size_t pad)
     : message_id(0), repeat_indicator(0), mmsi(0), status(AIS_UNINITIALIZED),
       num_chars(0), num_bits(0), bits() {
   assert(nmea_payload);
-  assert(pad < 6);

   const AIS_STATUS r = bits.ParseNmeaPayload(nmea_payload, pad);
   if (r != AIS_OK) {
diff --git a/src/libais/ais1_2_3.cpp b/src/libais/ais1_2_3.cpp
index e39e400..418f8ed 100644
--- a/src/libais/ais1_2_3.cpp
+++ b/src/libais/ais1_2_3.cpp
@@ -24,7 +24,7 @@ Ais1_2_3::Ais1_2_3(const char *nmea_payload, const size_t pad)
   if (!CheckStatus()) {
     return;
   }
-  if (pad != 0 || num_chars != 28) {
+  if (num_bits != 168) {
     status = AIS_ERR_BAD_BIT_COUNT;
     return;
   }
diff --git a/src/libais/ais_bitset.cpp b/src/libais/ais_bitset.cpp
index baf7d61..b0f0f72 100644
--- a/src/libais/ais_bitset.cpp
+++ b/src/libais/ais_bitset.cpp
@@ -6,7 +6,6 @@ AisBitset::AisBitset() : num_bits(0), num_chars(0), current_position(0) {}

 AIS_STATUS AisBitset::ParseNmeaPayload(const char *nmea_payload, int pad) {
   assert(nmea_payload);
-  assert(pad >= 0 && pad < 6);

   InitNmeaOrd();

normalize nmea_payload

Another approach is to add a normalization phase at the beginnig of the ::libais::AisMsg::AisMsg, and make sure only the normalized data is used after it. This adds an extra step and at the end, the bitset is the same (for bets before num_bits, after they might defer but this makes no difference).

Here is a way to normalize messages in order to ensure 0 ≤ pad < 6 (just a POC, it can be improved, and all messages should be updated):

diff --git a/src/libais/ais.cpp b/src/libais/ais.cpp
index ebb537a..c4e06a7 100644
--- a/src/libais/ais.cpp
+++ b/src/libais/ais.cpp
@@ -67,13 +67,17 @@ const char * const AIS_STATUS_STRINGS[AIS_STATUS_NUM_CODES] = {
   "AIS_ERR_BAD_SUB_SUB_MSG",
 };

+NmeaPayload::NmeaPayload(const char * _nmea_payload, const size_t _pad) :
+    data(std::string(_nmea_payload).substr(0, std::string(_nmea_payload).size() - _pad / 6)),
+    pad(_pad % 6) { }
+
 AisMsg::AisMsg(const char *nmea_payload, const size_t pad)
-    : message_id(0), repeat_indicator(0), mmsi(0), status(AIS_UNINITIALIZED),
-      num_chars(0), num_bits(0), bits() {
+    : message_id(0), repeat_indicator(0), mmsi(0), original_payload(nmea_payload, pad),
+      status(AIS_UNINITIALIZED), num_chars(0), num_bits(0), bits() {
   assert(nmea_payload);
-  assert(pad < 6);
+  assert(original_payload.pad < 6);

-  const AIS_STATUS r = bits.ParseNmeaPayload(nmea_payload, pad);
+  const AIS_STATUS r = bits.ParseNmeaPayload(original_payload.data.c_str(), original_payload.pad);
   if (r != AIS_OK) {
     status = r;
     return;
diff --git a/src/libais/ais.h b/src/libais/ais.h
index 4619db1..33cbeef 100644
--- a/src/libais/ais.h
+++ b/src/libais/ais.h
@@ -428,6 +428,16 @@ class AisBitset : protected bitset<MAX_BITS> {
   mutable int current_position;
 };

+// used for payload normalization
+class NmeaPayload {
+ public:
+  const std::string data;
+  const size_t pad;
+                                                                                                                                                                                                                                 
+  NmeaPayload() : data(), pad(0) { }; // for un initialized data                                                                                                                                                                 
+  NmeaPayload(const char *, const size_t);                                                                                                                                                                                       
+};                                                                                                                                                                                                                               
+                                                                                                                                                                                                                                 
 class AisMsg {                                                                                                                                                                                                                   
  public:                                                                                                                                                                                                                         
   int message_id;                                                                                                                                                                                                                
@@ -441,6 +451,7 @@ class AisMsg {                                                                                                                                                                                                
   virtual ~AisMsg() {}                                                                                                                                                                                                           

  protected:                                                                                                                                                                                                                      
+  const NmeaPayload original_payload;  // normailzed payload                                                                                                                                                                     
   AIS_STATUS status;  // AIS_OK or error code                                                                                                                                                                                    
   int num_chars;  // Number of characters in the nmea_payload.                                                                                                                                                                   
   size_t num_bits;  // Number of bits in the nmea_payload.                                                                                                                                                                       
diff --git a/src/libais/ais1_2_3.cpp b/src/libais/ais1_2_3.cpp                                                                                                                                                                    
index e39e400..ab1894a 100644                                                                                                                                                                                                     
--- a/src/libais/ais1_2_3.cpp                                                                                                                                                                                                     
+++ b/src/libais/ais1_2_3.cpp                                                                                                                                                                                                     
@@ -24,7 +24,7 @@ Ais1_2_3::Ais1_2_3(const char *nmea_payload, const size_t pad)                                                                                                                                                  
   if (!CheckStatus()) {                                                                                                                                                                                                          
     return;                                                                                                                                                                                                                      
   }                                                                                                                                                                                                                              
-  if (pad != 0 || num_chars != 28) {                                                                                                                                                                                             
+  if (original_payload.pad != 0 || num_chars != 28) {                                                                                                                                                                            
     status = AIS_ERR_BAD_BIT_COUNT;                                                                                                                                                                                              
     return;                                                                                                                                                                                                                      
   }