lucyclient
Version:
Lucy Node JS Client Library
351 lines (328 loc) • 11.8 kB
JavaScript
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
};