UNPKG

@qnext/iso-on-tcp

Version:

ISO-on-TCP Protocol implementation

234 lines (188 loc) 6.32 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 takes in javascript objects that * represents ISO-on-TCP telegrams and serializes them into * buffers * * @example <caption>Example of a CR telegram</caption> * serializer.write({ * type: 0x0e, //CR * destination: 0, * source: 2, * //class: 0, //default if not present * //extended_format: false, //default if not present * //no_flow_control: false, //default if not present * tpdu_size: 1024, * srcTSAP: 0x0100, * dstTSAP: 0x0102 * }); * * @example <caption>Example of a DT telegram</caption> * serializer.write({ * type: constants.tpdu_type.DT, * last_data_unit: true, * payload: Buffer.from('32010000000000080000f0000008000803c0', 'hex') * }); */ class ISOOnTCPSerializer extends Transform { constructor(opts) { opts = opts || {}; opts.writableObjectMode = true; super(opts); debug('new ISOOnTCPSerializer'); } _transform(chunk, encoding, cb) { debug('ISOOnTCPSerializer _transform'); //checks if (!chunk.type) { cb(new Error('Missing telegram type')); return; } if (chunk.payload !== undefined && !Buffer.isBuffer(chunk.payload)) { cb(new Error('Payload is not of type Buffer')); return; } //tpkt(4) + tpdu_length(1) + payload (tpdu content will be added later depending on type) let tpkt_length = 4 + 1 + (chunk.payload ? chunk.payload.length : 0); let tpdu_length = 1; //(length) + type (the "length" byte doesn't count) let ptr = 6; //tpkt(4) + tpdu_len(1) + tpdu_type(1) let buf; switch (chunk.type) { case constants.tpdu_type.CR: case constants.tpdu_type.CC: case constants.tpdu_type.DR: tpdu_length += 5; //src + dst + reason_or_class let destination = parseInt(chunk.destination) || 0; let source = parseInt(chunk.source) || 0; let reason_or_class; if (chunk.type === constants.tpdu_type.DR) { reason_or_class = parseInt(chunk.reason) || 0; } else { reason_or_class = ((parseInt(chunk.class) || 0) & 0x0f) << 4; reason_or_class |= chunk.no_flow_control ? 0x1 : 0; reason_or_class |= chunk.extended_format ? 0x2 : 0; } // variable data if (chunk.tpdu_size !== undefined) { tpdu_length += 3; } if (chunk.srcTSAP !== undefined) { tpdu_length += getTSAPTPDULength(chunk.srcTSAP, 'srcTSAP'); } if (chunk.dstTSAP !== undefined) { tpdu_length += getTSAPTPDULength(chunk.dstTSAP, 'dstTSAP'); } tpkt_length += tpdu_length; // allocate buffer and write buf = Buffer.alloc(tpkt_length); buf.writeUInt16BE(destination, ptr); //source ptr += 2; buf.writeUInt16BE(source, ptr); //source ptr += 2; buf.writeUInt8(reason_or_class, ptr); //source ptr += 1; if (chunk.tpdu_size !== undefined) { buf.writeUInt8(constants.var_type.TPDU_SIZE, ptr); buf.writeUInt8(1, ptr + 1); //length buf.writeUInt8(highestOrderBit(chunk.tpdu_size), ptr + 2); ptr += 3; } if (chunk.srcTSAP !== undefined) { buf.writeUInt8(constants.var_type.SRC_TSAP, ptr); switch (typeof chunk.srcTSAP) { case 'string': buf.writeUInt8(8, ptr + 1); //length buf.write(chunk.srcTSAP, ptr + 2); ptr += 10; break; case 'number': buf.writeUInt8(2, ptr + 1); //length buf.writeUInt16BE(chunk.srcTSAP, ptr + 2); ptr += 4; break; default: throw new Error( 'srcTSAP type must be in [number, string] but ' + typeof chunk.srcTSAP + 'given' ); } } if (chunk.dstTSAP !== undefined) { buf.writeUInt8(constants.var_type.DST_TSAP, ptr); switch (typeof chunk.dstTSAP) { case 'string': buf.writeUInt8(8, ptr + 1); //length buf.write(chunk.dstTSAP, ptr + 2); ptr += 10; break; case 'number': buf.writeUInt8(2, ptr + 1); //length buf.writeUInt16BE(chunk.dstTSAP, ptr + 2); ptr += 4; break; default: throw new Error( 'dstTSAP type must be in [number, string] but ' + typeof chunk.dstTSAP + ' given' ); } } break; case constants.tpdu_type.DT: case constants.tpdu_type.ED: tpdu_length += 1; //number/ldu let nr_and_eot = (parseInt(chunk.tpdu_number) || 0) & 0x7f; nr_and_eot |= chunk.last_data_unit ? 0x80 : 0; tpkt_length += tpdu_length; // allocate buffer and write buf = Buffer.alloc(tpkt_length); buf.writeUInt8(nr_and_eot, ptr); ptr += 1; break; default: cb(new Error(`Telegram type [${chunk.type}] not yet implemented`)); return; } //tpkt buf.writeUInt8(3, 0); //version buf.writeUInt8(0, 1); //reserved buf.writeUInt16BE(tpkt_length, 2); //length //tpdu let type_and_credit = (chunk.type << 4) & 0xff; type_and_credit |= (parseInt(chunk.credit) || 0) & 0xf; buf.writeUInt8(tpdu_length, 4); //length buf.writeUInt8(type_and_credit, 5); //type and credit if (chunk.payload) { chunk.payload.copy(buf, 5 + tpdu_length); } this.push(buf); cb(); } } // -- helper function getTSAPTPDULength(value, name) { switch (typeof value) { case 'string': return 10; case 'number': return 4; default: throw new Error( name + ' type must be in [number, string] but ' + typeof value + 'given' ); } } function highestOrderBit(num) { if (!num) return 0; let ret = 0; //while(num >>= 1) ret <<= 1; while ((num >>= 1)) ret++; return ret; } module.exports = ISOOnTCPSerializer;