docs: function AisDecode (input, session) {
      this.bitarray = [];
      this.valid = false; // will move to 'true' if parsing succeed
      this.error = "";    // for returning error message if not valid
  
      if (Object.prototype.toString.call(input) !== "[object String]") {
          this.error = "AisDecode: Sentence is not of type string.";
          return;
      } else {
          input = input.trim();
      }
  
      if (input.length === 0) {
          this.error = "AisDecode: Sentence is empty or spaces.";
          return;
      } else if (!this.validateChecksum(input)) {
          this.error = "AisDecode: Sentence checksum is invalid.";
          return;
      }
  
      // split nmea message !AIVDM,1,1,,B,B69>7mh0?J<:>05B0`0e;wq2PHI8,0*3D'
      var nmea = input.split(",");
  
      if (nmea.length !== 7) {
          this.error = "AisDecode: Sentence contains invalid number of parts.";
          return;
      } else if (nmea[0] !== "!AIVDM" && nmea[0] !== "!AIVDO") {   //AIVDM = standard, AIVDO = own ship
          this.error = "AisDecode: Invalid message prefix.";
          return;
      }
  
      // the input string is part of a multipart message, make sure we were
      // passed a session object.
      var message_count = Number(nmea[1]);
      var message_id = Number(nmea[2]);
      var sequence_id = nmea[3].length > 0 ? Number(nmea[3]) : NaN;
  
      if(message_count > 1) {
          if(Object.prototype.toString.call(session) !== "[object Object]") {
             throw "A session object is required to maintain state for decoding multipart AIS messages.";
          }
  
          if(message_id > 1) {
              if(nmea[0] !== session.formatter) {
                  this.error = "AisDecode: Sentence does not match formatter of current session.";
                  return;
              }
  
              if(session[message_id - 1] === undefined) {
                  this.error = "AisDecode: Session is missing prior message part, cannot parse partial AIS message.";
                  return;
              }
  
              if(session.sequence_id !== sequence_id) {
                  this.error = "AisDecode: Session IDs do not match. Cannot recontruct AIS message.";
                  return;
              }
          } else {
              session.formatter = nmea[0];
              session.message_count = message_count;
              session.sequence_id = sequence_id;
          }
      }
  
      // extract binary payload and other usefull information from nmea paquet
      this.payload  = new Buffer (nmea [5]);
      this.msglen   = this.payload.length;
  
      this.channel = nmea[4];  // vhf channel A/B
  
      if(message_count > 1) {
          session[message_id] = {payload: this.payload, length: this.msglen};
  
          // Not done building the session
          if(message_id < message_count) return;
  
          var payloads = [];
          var len = 0;
  
          for(var i = 1; i <= session.message_count; ++i) {
              payloads.push(session[i].payload);
              len += session[i].length;
          }
          
          this.payload = Buffer.concat(payloads, len);
          this.msglen = this.payload.length;
      }
  
      
      // decode printable 6bit AIS/IEC binary format
      for(var i = 0; i < this.msglen; i++) {
          var byte = this.payload[i];
  
          // check byte is not out of range
          if ((byte < 0x30) || (byte > 0x77))  return;
          if ((0x57 < byte) && (byte < 0x60))  return;
  
          // move from printable char to wacky AIS/IEC 6 bit representation
          byte += 0x28;
          if(byte > 0x80)  byte += 0x20;
          else             byte += 0x28;
          this.bitarray[i]=byte;
      }
  
      this.aistype   = this.GetInt (0,6);
      this.repeat    = this.GetInt (6,2);
      this.immsi     = this.GetInt (8,30);
      this.mmsi      = ("000000000" + this.immsi).slice(-9);
  
      switch (this.aistype) {
          case 1:
          case 2:
          case 3: // class A position report
              this.class      = 'A';
              this.navstatus  = this.GetInt( 38, 4);
  
              var lon         = this.GetInt(61, 28);
              if (lon & 0x08000000 ) lon |= 0xf0000000;
              lon = parseFloat (lon / 600000);
  
              var lat = this.GetInt(89, 27);
              if( lat & 0x04000000 ) lat |= 0xf8000000;
              lat = parseFloat (lat / 600000);
  
              if( ( lon <= 180. ) && ( lat <= 90. ) ) {
                  this.lon = lon;
                  this.lat = lat;
                  this.valid = true;
              } else this.valid = false;
  
              this.rot = this.GetInt( 42, 8, true )                   // Rate of turn
              this.sog = this.GetInt(  50, 10) / 10;                  //speed over ground
              this.cog = this.GetInt( 116, 12) / 10;                  //course over ground
              this.hdg = parseFloat (this.GetInt( 128,  9));          //magnetic heading
              this.utc = this.GetInt( 137, 6 );
              this.smi = this.GetInt( 143, 2 );
              
  
              break;
          case 18: // class B position report
              this.class  = 'B';
              this.status = -1;  // Class B targets have no status.  Enforce this...
              var lon = this.GetInt(57, 28 );
              if (lon & 0x08000000 ) lon |= 0xf0000000;
              lon = parseFloat (lon / 600000);
  
              var lat = this.GetInt(85, 27 );
              if( lat & 0x04000000 ) lat |= 0xf8000000;
              lat = parseFloat (lat / 600000);
  
              if( ( lon <= 180. ) && ( lat <= 90. ) ) {
                  this.lon = lon;
                  this.lat = lat;
                  this.valid = true;
              } else this.valid = false;
  
              this.sog = this.GetInt( 46, 10 ) / 10;                //speed over ground
              this.cog = this.GetInt( 112, 12) / 10;                //course over ground
              this.hdg = parseFloat (this.GetInt( 124,  9));        //magnetic heading
              this.utc = this.GetInt( 134, 6 );
  
              break;
          case 19: // Extended class B position report 
              this.class  = 'B';
              this.status = -1;  // Class B targets have no status.  Enforce this...
              
              var lon = this.GetInt(57, 28 );
              if (lon & 0x08000000 ) lon |= 0xf0000000;
              lon = parseFloat (lon / 600000);
  
              var lat = this.GetInt(85, 27 );
              if( lat & 0x04000000 ) lat |= 0xf8000000;
              lat = parseFloat (lat / 600000);
  
              if( ( lon <= 180. ) && ( lat <= 90. ) ) {
                  this.lon = lon;
                  this.lat = lat;
                  this.valid = true;
              } else this.valid = false;
  
              this.sog = this.GetInt( 46, 10 ) / 10;                //speed over ground
              this.cog = this.GetInt( 112, 12) / 10;                //course over ground
              this.hdg = parseFloat (this.GetInt( 124,  9));        //magnetic heading
              this.utc = this.GetInt( 133, 6 );
  
              this.shipname    = this.GetStr(143,120).trim();
              this.cargo       = this.GetInt(263,8);
  
              this.dimA   = this.GetInt(271, 9 );
              this.dimB   = this.GetInt(280, 9 );
              this.dimC   = this.GetInt(289, 6 );
              this.dimD   = this.GetInt(295, 6 );
              this.length = this.dimA + this.dimB;
              this.width  = this.dimC + this.dimD;
              
              break;
          case 5:
              this.class  = 'A';
  //          Get the AIS Version indicator
  //          0 = station compliant with Recommendation ITU-R M.1371-1
  //          1 = station compliant with Recommendation ITU-R M.1371-3 (or later)
  //          2 = station compliant with Recommendation ITU-R M.1371-5 (or later)
  //          3 = station compliant with future editions
              var AIS_version_indicator = this.GetInt(38,2);
              if( AIS_version_indicator < 3 )
                  {
                  this.imo = this.GetInt(40,30);
                  this.callsign    = this.GetStr(70,42).trim();
                  this.shipname    = this.GetStr(112,120).trim();
                  this.cargo       = this.GetInt(232,8);
                  this.dimA        = this.GetInt(240,9);
                  this.dimB        = this.GetInt(249,9);
                  this.dimC        = this.GetInt(258,6);
                  this.dimD        = this.GetInt(264,6);
                  this.etaMo       = this.GetInt(274,4);
                  this.etaDay      = this.GetInt(278,5);
                  this.etaHr       = this.GetInt(283,5);
                  this.etaMin      = this.GetInt(288,6);
                  this.draught     = this.GetInt(294, 8 ) / 10.0;
                  this.destination = this.GetStr(302, 120).trim();
                  this.length      = this.dimA + this.dimB;
                  this.width       = this.dimC + this.dimD;
                  this.valid       = true;
              }
  
              break;
          case 24:  // Vesel static information
              this.class='B';
              this.part = this.GetInt(38, 2 );
              if (0 === this.part ) {
                  this.shipname = this.GetStr(40, 120).trim();
                  this.valid    = true;
              } else if ( this.part === 1) {
                  this.cargo    = this.GetInt(40, 8 );
                  this.callsign = this.GetStr(90, 42).trim();
  
                  // 98 = auxiliary craft 
                  if (parseInt(this.immsi/10000000) === 98) {
                      var mothership  = this.GetInt (132, 30);
                      this.mothership = ("000000000" + mothership).slice(-9);
                  } else {
                      this.dimA   = this.GetInt(132, 9 );
                      this.dimB   = this.GetInt(141, 9 );
                      this.dimC   = this.GetInt(150, 6 );
                      this.dimD   = this.GetInt(156, 6 );
                      this.length = this.dimA + this.dimB;
                      this.width  = this.dimC + this.dimD;
                  }
                  this.valid  = true;
              }
              break;
          case 4:  // base station
          case 11: // UTC/Date Response
              this.class      = '-';
              
              var lon = this.GetInt(79, 28);
              if (lon & 0x08000000 ) lon |= 0xf0000000;
              lon = parseFloat (lon / 600000);
  
              var lat = this.GetInt(107, 27);
              if( lat & 0x04000000 ) lat |= 0xf8000000;
              lat = parseFloat (lat / 600000);
  
              if( ( lon <= 180. ) && ( lat <= 90. ) ) {
                  this.lon = lon;
                  this.lat = lat;
                  this.valid = true;
              } else this.valid = false;
              break;
          case 9: // sar aircraft
              this.class      = '-';
              
              this.alt = this.GetInt(38, 12);
              
              var lon = this.GetInt(61, 28);
              if (lon & 0x08000000 ) lon |= 0xf0000000;
              lon = parseFloat (lon / 600000);
  
              var lat = this.GetInt(89, 27);
              if( lat & 0x04000000 ) lat |= 0xf8000000;
              lat = parseFloat (lat / 600000);
  
              if( ( lon <= 180. ) && ( lat <= 90. ) ) {
                  this.lon = lon;
                  this.lat = lat;
                  this.valid = true;
              } else this.valid = false;
              
              this.sog = parseFloat (this.GetInt( 50, 10 ));  //speed over ground
              this.cog = this.GetInt( 116, 12) / 10;          //course over ground
  
              break;
          case 21: // aid to navigation 
              this.class      = '-';
              
              this.aidtype = this.GetInt(38, 5);
              this.shipname = this.GetStr(43, 120).trim();
              
              var lon = this.GetInt(164, 28);
              if (lon & 0x08000000 ) lon |= 0xf0000000;
              lon = parseFloat (lon / 600000);
  
              var lat = this.GetInt(192, 27);
              if( lat & 0x04000000 ) lat |= 0xf8000000;
              lat = parseFloat (lat / 600000);
  
              if( ( lon <= 180. ) && ( lat <= 90. ) ) {
                  this.lon = lon;
                  this.lat = lat;
                  this.valid = true;
              } else this.valid = false;
              
              this.dimA   = this.GetInt(219, 9 );
              this.dimB   = this.GetInt(228, 9 );
              this.dimC   = this.GetInt(237, 6 );
              this.dimD   = this.GetInt(243, 6 );
              this.length = this.dimA + this.dimB;
              this.width  = this.dimC + this.dimD;
  
              this.utc = this.GetInt(253, 6);
              this.offpos = this.GetInt(259, 1);
  
              var len = parseInt(( ( this.bitarray.length - 272 /6 ) / 6 ) * 6)*6;
              this.txt = this.GetStr(272 , len).trim();
              
              break;
          case 14: // text msg
              this.class      = '-';
              if (this.bitarray.length > 40/6) {
                  var len = parseInt(( ( this.bitarray.length - 40/6 ) / 6 ) * 6)*6;
                  this.txt = this.GetStr(40, len).trim();
                  this.valid = true;
              }
              break;
          case 8: // Binary Broadcast Message
                  this.dac = this.GetInt(40, 10 );
                  this.fid = this.GetInt(50, 6 );
                  // Inland ship static and voyage related data
                  if (this.dac === 200 && this.fid === 10 ) {
                      this.class       = '-';
                      this.ENI         = this.GetStr(56,48).trim();
                      this.length      = parseFloat(this.GetInt(104, 13 )) /10.;
                      this.width       = parseFloat(this.GetInt(117, 10 )) /10.;
                      this.draught     = parseFloat(this.GetInt(144, 11 )) / 100.0;
                      this.shiptypeERI = this.GetInt(127, 14 );
                      this.valid       = true;
                  } else {
                      if (DEBUG) {
                          console.log ('---- type=%d %s dac=%d fid=%d %s', this.aistype, this.mmsi, dac, fid, input);                
                      }
                  }
              break;
          case 27: // Long Range AIS Broadcast message
              this.class  = '-';
              this.navstatus  = this.GetInt( 40, 4);     
              
              var lon = this.GetInt(44, 18 );
              lon = parseFloat (lon) / 600;
  
              var lat = this.GetInt(62, 17 );
              lat = parseFloat (lat) / 600;
  
              if( ( lon <= 180. ) && ( lat <= 90. ) ) {
                  this.lon = lon;
                  this.lat = lat;
                  this.valid = true;
              } else this.valid = false;
  
              this.sog = this.GetInt( 79, 6 ) ;                //speed over ground
              this.cog = this.GetInt( 85, 9);                //course over ground
              break;
          default:
              if (DEBUG) {
                  console.log ('---- type=%d %s %s -> %s', this.aistype, this.Getaistype(this.aistype), this.mmsi, input);
              }
              break;
      }
  }