UNPKG

@qnext/iso-on-tcp

Version:

ISO-on-TCP Protocol implementation

252 lines (209 loc) 6.47 kB
//@ts-check const { Transform } = require('stream'); const constants = require('./constants.json'); const { debuglog } = require('util'); const debug = debuglog('iso-on-tcp'); /** * Transform Stream that parses buffers into Javascript * objects according to the ISO-on-TCP protocol */ class ISOOnTCPParser extends Transform { constructor(opts) { opts = opts || {}; opts.readableObjectMode = true; opts.decodeStrings = true; super(opts); this._nBuffer = null; debug('new ISOOnTCPParser'); } _transform(chunk, encoding, cb) { debug('ISOOnTCPParser _transform'); let ptr = 0; if (this._nBuffer !== null) { chunk = Buffer.concat([this._nBuffer, chunk]); this._nBuffer = null; } // test for minimum length if (chunk.length < 7) { this._nBuffer = chunk; cb(); return; } while (ptr < chunk.length) { // TPKT header let tpktStart = ptr; let tpkt_version = chunk.readUInt8(ptr); let tpkt_reserved = chunk.readUInt8(ptr + 1); let tpkt_length = chunk.readUInt16BE(ptr + 2); let tpktEnd = tpktStart + tpkt_length; //we don't have enough data, let's backup it if (chunk.length < ptr + tpkt_length) { this._nBuffer = chunk.slice(ptr); cb(); return; } ptr += 4; let obj = { tpkt: { version: tpkt_version, reserved: tpkt_reserved, }, }; // TPDU let tpduStart = ptr; let tpdu_length = chunk.readUInt8(ptr) + 1; //+1, because the length itself is not included in protocol ptr += 1; let tpduEnd = tpduStart + tpdu_length; // test if TPDU header is within TPKT boundaries if (tpduEnd > tpktEnd) { cb( new Error( `TPDU header length [${tpdu_length}] out of bounds, TPKT lenght is [${tpkt_length}]` ) ); return; } // TPDU - fixed part let type_and_credit = chunk.readUInt8(ptr); ptr += 1; obj.type = type_and_credit >> 4; obj.credit = type_and_credit & 0xf; switch (obj.type) { case constants.tpdu_type.CR: case constants.tpdu_type.CC: case constants.tpdu_type.DR: if (tpduEnd - ptr < 5) { cb( new Error( `Not enough bytes for parsing TPDU header of type [${ obj.type }], needs 5, has [${tpduEnd - ptr}]` ) ); return; } obj.destination = chunk.readUInt16BE(ptr); ptr += 2; obj.source = chunk.readUInt16BE(ptr); ptr += 2; let varfield = chunk.readUInt8(ptr); if (obj.type === constants.tpdu_type.DR) { obj.reason = varfield; } else { obj.class = varfield >> 4; obj.no_flow_control = (varfield & 0x1) > 0; obj.extended_format = (varfield & 0x2) > 0; } ptr += 1; break; case constants.tpdu_type.DT: case constants.tpdu_type.ED: if (tpduEnd - ptr < 1) { cb( new Error( `Not enough bytes for parsing TPDU header of type [${ obj.type }], needs 1, has [${tpduEnd - ptr}]` ) ); return; } let nr_and_eot = chunk.readUInt8(ptr); obj.tpdu_number = nr_and_eot & 0x7f; obj.last_data_unit = (nr_and_eot & 0x80) > 0; ptr += 1; break; default: //throw if we can't handle it cb( new Error( `Unknown or not implemented TPDU type [${obj.type}]:[${ constants.tpdu_type_desc[obj.type] }]` ) ); return; } // TPDU - variable part let var_params = []; while (ptr - tpduStart < tpdu_length) { if (tpduEnd - ptr < 2) { cb( new Error( `Not enough bytes for TPDU variable part header, ptr [${ptr}], start [${tpduStart}], length [${tpdu_length}]` ) ); return; } let var_code = chunk.readUInt8(ptr); ptr += 1; let var_length = chunk.readUInt8(ptr); ptr += 1; if (tpduEnd - ptr < var_length) { cb( new Error( `Not enough bytes for TPDU variable part item, ptr [${ptr}], start [${tpduStart}], length [${tpdu_length}]` ) ); return; } var_params.push({ code: var_code, data: chunk.slice(ptr, ptr + var_length), }); ptr += var_length; } for (let elm of var_params) { switch (elm.code) { case constants.var_type.TPDU_SIZE: obj.tpdu_size = 1 << elm.data.readUInt8(0); break; case constants.var_type.SRC_TSAP: switch (elm.data.length) { case 2: obj.srcTSAP = elm.data.readUInt16BE(0); break; case 8: obj.srcTSAP = elm.data.toString(); break; default: throw new Error( `Expected srcTSAP data size of 2 or 8 - got ${elm.data.length}` ); } break; case constants.var_type.DST_TSAP: switch (elm.data.length) { case 2: obj.dstTSAP = elm.data.readUInt16BE(0); break; case 8: obj.dstTSAP = elm.data.toString(); break; default: throw new Error( `Expected dstTSAP data size of 2 or 8 - got ${elm.data.length}` ); } break; default: //for now, throw if we don't have it implemented cb( new Error( `Unknown or not implemented variable parameter code [${ elm.code }]:[${constants.var_type_desc[elm.code]}]` ) ); return; } } // TPDU - user data - data between tpduEnd and tpktEnd obj.payload = chunk.slice(tpduEnd, tpktEnd); this.push(obj); ptr = tpktEnd; } cb(); } } module.exports = ISOOnTCPParser;