UNPKG

lucyclient

Version:
351 lines (328 loc) 11.8 kB
const logger = require('winston').loggers.get('lucyLogger'); const javaMaxInt = 2147483647; const javaMinInt = -2147483648; function TCPInPacket(data,offset){ //Parse packet from stream logger.silly("Data received incomming packet:" + data.toString('hex')); this.requestFlag = data[offset] !== 0; offset++; this.actionId = data.readInt32BE(offset); offset += 4; this.uniqueRequestId = data.readInt32BE(offset); offset += 4; this.payloadLength = data.readInt32BE(offset); offset += 4; this.readOffset = 0; if(this.payloadLength > 0){ this.payload = data.slice(offset,data.length); } } TCPInPacket.prototype = { createResponse: function(){ return new TCPOutPacket(false,0,this.uniqueRequestId); }, readBoolean: function(){ this.readOffset++; return this.payload[this.readOffset-1] !== 0; //return this.payload.readInt8(this.readOffset-1) !== 0; }, readByte : function(){ this.readOffset += 1; return this.payload.readInt8(readOffset-1); }, readShort: function(){ this.readOffset += 2; return this.payload.readInt16BE(this.readOffset-2); }, readUnsignedShort: function(){ this.readOffset +=2; return this.payload.readUInt16BE(this.readOffset-2); }, readInt: function(){ this.readOffset += 4; return this.payload.readInt32BE(this.readOffset-4); }, //Javascript does not support 64 bit integer as all numbers are saved as 64 bit floating points //With 54 bits for the mantissa /* readLong: function(){ this.readOffset += 8; this.payload.readInt32BE(); this. }*/ readFloat: function(){ this.readOffset += 4; this.payload.readFloatBE(this.readOffset-4); }, readDouble: function() { this.readOffset += 8; this.payload.readDoubleBE(this.readOffset - 8); }, readColor : function(){ const colorAsInt = this.readInt(); const alpha = (colorAsInt >> 24) & 0x000000FF; const red = (colorAsInt >> 16) & 0x000000FF; const green = (colorAsInt >> 8) & 0x000000FF; const blue = colorAsInt & 0x000000FF; return{ rgb: colorAsInt, alpha : alpha, red : red, green: green, blue: blue } }, readString : function(){ const length = this.readUnsignedShort(); const string = this.payload.toString('utf8',this.readOffset,this.readOffset+length); this.readOffset += length; return string; }, readBooleanArray : function(){ const arrLength = this.readInt(); const boolArray = []; for(let i = 0; i < arrLength; i++){ boolArray.push(this.readBoolean()); } return boolArray; }, readByteArray : function(){ const arrLength = this.readInt(); const byteArray = []; for(let i = 0; i < arrLength; i++){ byteArray.push(this.readByte()); } return byteArray; }, readShortArray : function(){ const arrLength = this.readShort(); const shortArray = []; for(let i = 0; i < arrLength; i++){ shortArray.push(this.readShort()); } return shortArray; }, readIntArray : function(){ const arrLength = this.readInt(); const intArray = []; for(let i = 0; i < arrLength; i++){ intArray.push(this.readInt()); } return intArray; }, readFloatArray : function(){ const arrLength = this.readInt(); const floatArray = []; for(let i = 0; i < arrLength; i++){ floatArray.push(this.readFloat()); } return floatArray; }, readDoubleArray : function(){ const arrLength = this.readInt(); const doubleArray = []; for(let i = 0; i < arrLength; i++){ doubleArray.push(this.readDouble()); } return intArray; }, readColorArray : function(){ const arrLength = this.readInt(); const colorArray = []; for(let i = 0; i < arrLength; i++){ colorArray.push(this.readColor()); } return colorArray; }, /** * Reads an integer from the payload without moving the cursor forward. * The next invokation of read or peek int will return the same result. * @returns {Number} the next 4 bytes decoded as integer. */ peekInt: function(){ return this.payload.readInt32BE(this.readOffset); }, peekUnsignedShort : function(){ return this.payload.readUInt16BE(this.readOffset); } }; function TCPOutPacket(request,actionId,uniqueRequestId){ this.beginRequestFlag = Buffer.from([0x00,0x01,0x02]); this.requestFlag = request; this.actionId = actionId; this.uniqueRequestId = uniqueRequestId; this.payload = Buffer.alloc(0); } /* TODO resizing the buffer upon every write operation might be prone to severe performance issues. * Maybe it is worth to create a bigger buffer and keep track of the used size independatly. * We also could write our own array functions to push indivdual datatypes to an array which can handle * dynamic increase in sizes */ TCPOutPacket.prototype ={ sendDataToServer: function(client) { //Begin request let offset = 4; const outBuffer = Buffer.alloc(3+1+4+4+4+this.payload.length); logger.silly("Buffer length: " + outBuffer.length); logger.silly("Request flag: " + this.requestFlag); outBuffer[0] = 0x00; outBuffer[1] = 0x01; outBuffer[2] = 0x02; outBuffer[3] = this.requestFlag; logger.silly("actionID: " + this.actionId); outBuffer.writeInt32BE(this.actionId,offset); offset += 4; logger.silly("uniqueRequestId: " + this.uniqueRequestId); outBuffer.writeInt32BE(this.uniqueRequestId,offset); offset += 4; logger.silly("Payload Length: " + this.payload.length); if (this.payload.length == 0) { outBuffer.writeInt32BE(0,offset); } else { outBuffer.writeInt32BE(this.payload.length,offset); offset += 4; outBuffer.write(this.payload); } logger.debug("Send data to server"); logger.silly(outBuffer.toString('hex')); client.write(outBuffer); }, writeBoolean : function(bool){ //append buffer const dataToAppend = Buffer.allocUnsafe(1); dataToAppend[0] = bool === true ? 0 : 1; this.payload = Buffer.concat(this.payload,dataToAppend); }, writeByte: function(byte){ const dataToAppend = Buffer.allocUnsafe(1); dataToAppend.writeInt8(byte,0); this.payload = Buffer.concat([this.payload,dataToAppend]); }, writeShort: function(short){ const dataToAppend = Buffer.allocUnsafe(2); dataToAppend.writeInt16BE(short,0); this.payload = Buffer.concat([this.payload,dataToAppend]); }, writeInt : function(int){ const dataToAppend = Buffer.allocUnsafe(4); dataToAppend.writeInt32BE(int,0); this.payload = Buffer.concat([this.payload,dataToAppend]); }, writeFloat: function(float){ const dataToAppend = Buffer.allocUnsafe(4); dataToAppend.writeFloatBE(float,0); this.payload = Buffer.concat([this.payload,dataToAppend]); }, writeDouble: function(double){ const dataToAppend = Buffer.allocUnsafe(8); dataToAppend.writeDoubleBE(double,0); this.payload = Buffer.concat([this.payload,dataToAppend]); }, writeColor: function(color){ this.writeInt(color); }, writeString: function(string){ const dataToAppend = Buffer.allocUnsafe(Buffer.byteLength(string, 'utf8')); dataToAppend.write(string,0); this.payload = Buffer.concat([this.payload,dataToAppend]); }, writeBooleanArray: function (bools){ this.writeInt(bools.length); for(let i = 0; i < bools.length; i++){ this.writeBoolean(bools[i]); } }, writeByteArray: function (bytes){ this.writeInt(bytes.length); for(let i = 0; i < bytes.length; i++){ this.writeByte(bytes[i]); } }, writeShortArray: function (shorts){ this.writeInt(shorts.length); for(let i = 0; i < shorts.length; i++){ this.writeShort(shorts[i]); } }, writeIntArray: function (ints){ this.writeInt(ints.length); for(let i = 0; i < ints.length; i++){ this.writeInt(ints[i]); } }, writeFloatArray: function (floats){ this.writeInt(floats.length); for(let i = 0; i < floats.length; i++){ this.writeFloat(floats[i]); } }, writeDoubleArray: function (doubles){ this.writeInt(doubles.length); for(let i = 0; i < doubles.length; i++){ this.writeDouble(doubles[i]); } }, writeColorArray: function (colors){ this.writeInt(colors.length); for(let i = 0; i < colors.length; i++){ this.writeColor(colors[i]); } } }; /** * Maps outgoing tcp packet requests (from the client) to a callback function * enabling the correct function to be called once a response by the server arrives * @type {Map<int, callackFunction()>} */ TCPOutPacket.packetMapping = new Map(); /** * Create a tcp request packet which will be send to the server * @param actionId action id the server can use to identify the packet intention * @param callback callback function which will be called once the server responds to this packet * undefined if no responds is expected * @param timeoutCallback function will be called in case of the package not receiving a timeout within the specified * time interval * @param timeout timeout in ms nodejs will wait until discarding the callback. This avoid resource leaks * in case of never handled packages and indicates server failure. * defaults to 20 seconds * @returns {TCPOutPacket} */ TCPOutPacket.createRequest = function(actionId,callback, timeoutCallback,timeout = 20000){ if( typeof TCPOutPacket.uniqueRequestId === undefined ) { TCPOutPacket.uniqueRequestId = 0; }else{ TCPOutPacket.uniqueRequestId++; if(TCPOutPacket.uniqueRequestId > javaMaxInt){ TCPOutPacket.uniqueRequestId = javaMinInt; } } //Should we register the callback when we send it to the server? if(callback !== undefined){ var key = TCPOutPacket.uniqueRequestId; TCPOutPacket.packetMapping.set(key,callback); setTimeout(function(){ if(TCPOutPacket.packetMapping.has(key)){ TCPOutPacket.packetMapping.delete(key); logger.warn("Callback timeouted. No response received from server for: " + key); if(timeoutCallback !== undefined) timeoutCallback(); } },timeout); } //Register an array which maps the callback to the uniqueRequestId. return new TCPOutPacket(true,actionId,key); }; TCPOutPacket.invokeResponseCallback = function(inPacket){ var key = inPacket.uniqueRequestId; if(TCPOutPacket.packetMapping.has(key)){ //Invoke callback TCPOutPacket.packetMapping.get(key)(inPacket); TCPOutPacket.packetMapping.delete(key); }else{ logger.warn("No callback specified for: " + key); } } module.exports = { TCPIn: TCPInPacket, TCPOut: TCPOutPacket };