'use strict'; const TCPDevice = require('./tcp-device'); const READ_0 = 0; const READ_ID = 1; const READ_LEN = 2; const READ_BODY = 3; const READ_BLOCKED = 99; const RESP_ID_HEADER = 0xE1; const RESP_CMD_HEADER = 0xF1; class RAPTCPDevice extends TCPDevice { constructor(socket, ops) { super(socket, ops); this.id = null; this.msgReceived = 0; this._idPart = null; this._lenPart = null; this._dataLength = 0; this._bodyPart = null; this._cmdBuf = null; this._readState = READ_0; } /** * _onData is triggered by the "readable" event on the underlying TCP socket. * It is called each time there is new data * received. It is responsible for reading data from the socket and * performing the appropriate action given the current read state. */ _onData() { try { // Loop while there was still data to process on the socket's buffer. // This will stop when we don't have enough data or encountering a back pressure issue; let readMore = true; do { switch (this._readState) { case READ_0: // this._idPart = null; // this._lenPart = null; // this._dataLength = 0; // this._bodyPart = null; readMore = this._processRead_0(); break; case READ_ID: readMore = this._processReadId(); break; case READ_LEN: readMore = this._processReadLen(); break; case READ_BODY: readMore = this._processReadBody(); break; case READ_BLOCKED: readMore = false; break; default: throw new Error('Unknown read state'); } } while (readMore); } catch (err) { // Terminate on failures as we won't be able to recovery since data was corrupted and we won't // be able to any more data without additional errors. this.destroy(err); } } _processRead_0() { this._firstByte = this._socket.read(1); if (!this._firstByte) return false; if (this._firstByte[0] === RESP_ID_HEADER) { this._readState = READ_ID; } else if (this._firstByte[0] === RESP_CMD_HEADER) { this._readState = READ_LEN; } else { this._readState = READ_0; this._guardInvalidTries(); } return true; } _getDeviceId(buf) { buf[7] = 0; // byte 8th is not used in the case ID is IMEI // Convert this buf to 64 integer number return buf.readBigUInt64LE(0).toString(); } _processReadId() { // Try to read the Report Data Length from the 3rd byte of the GPS Response message // If we cannot read the 8 bytes, the attempt to process the message will abort. // E1 => [8 byte ID] this._idPart = this._socket.read(8); if (!this._idPart) return false; if (!this.id) { // Parse the Device ID (8bytes) for the IMEI number const id8byte = this._idPart.slice(0); if (id8byte[7] === 0x01) this.id = this._getDeviceId(id8byte); } this._readState = READ_0; return true; } _processReadLen() { // E1[8 byte ID]F1 => this._lenPart = this._socket.read(2); if (!this._lenPart) return false; this._dataLength = this._lenPart[1]; this._readState = READ_BODY; return true; } _processReadBody() { // Read payload data by the length this._bodyPart = this._socket.read(this._dataLength); if (!this._bodyPart) { // this._socket.unshift(this._lenPart); // ??? needed return false; } this._invalidTry = 0; this.msgReceived++; this._cmdBuf = Buffer.alloc(this._lenPart.length + this._dataLength); // Copy the from read this._lenPart.copy(this._cmdBuf); this._bodyPart.copy(this._cmdBuf, this._lenPart.length); // Push the message onto the read buffer for the consumer to read. We are mindful of slow reads by the consumer // and will respect backpressure signals. const pushOk = this.push(this._cmdBuf); if (pushOk) { this._readState = READ_0; return true; } else { // debug("socket read is blocked"); console.log('socket read is blocked'); this._readState = READ_BLOCKED; return false; } } } module.exports = RAPTCPDevice;