Parsing TCP packet buffers in Node.js - node.js

I am trying to create a TCP game server (for an old version of Minecraft), and I want to use Node.js as the server software. I have created a basic socket server, and I am wondering what the best way to go about parsing the data buffers would be?
Here's my code right now:
const net = require('net')
const serialize = require('node-serialize');
const server = net.createServer();
server.on('connection', (socket) => {
socket.on('data', (buffer) => {
let packetID = buffer.readUInt8(0);
console.log(`Packet ID: ${packetID}`)
switch (packetID) {
case 1:
// Login Request
// Log some info about the request
let protocol = buffer.readUIntBE(2, 3)
let playername = buffer.slice(7, buffer.length).toString()
console.log(`Player name: ${playername}`)
console.log(`Client protocol version: ${protocol}`)
console.log(buffer)
// Send login confirmation packet
var packetInfo = [0x01, 0]
var entityID = intToByteArray(1298)
var seed = intToByteArray(971768181197178410)
var mode = [0]
var dimension = [0]
var difficulty = [1]
var height = [128]
var maxPlayers = [8]
var buff = Buffer.from([].concat.apply([], [packetInfo, entityID, seed, mode, dimension, difficulty, height, maxPlayers]))
console.log(`Bytes: ${buff.byteLength}`)
//socket.write(buff)
// Disconnect/Kick Packet
var buffr = stringToByteArray("§eKicked from the server (cause world doesn't load yet)")
var packetbugger = [0xFF, 0, buffr.length / 2, 0]
var finalbuffr = Buffer.from([].concat.apply([], [packetbugger, buffr]))
socket.write(finalbuffr)
case 2:
// Handshake
// Sends a "-"
console.log(serialize.unserialize(buffer).Username);
socket.write(Buffer.from([0x02, 0, 1, 0, 0x2d]))
}
})
socket.on('error', (error) => {
console.error(error)
})
})
server.listen("25565", "localhost")
I think that there's probably a better way to parse packets than what I am doing. Every packet that Minecraft sends to the server has it's own id, as detailed on wiki.vg. Here's a sample packet structure for a server to client packet:
How would I be able to retrieve the readable data from a packet buffer in Node.js? For example, I'd like to be able to extract the player name string from the packet. However, the most important thing for me would be extracting the packet ID in hex form. I have tried, but I am unable to do so. Any help or suggestions as to how to go about this would be massively appreciated.
Also, how would I be able to craft my own packet in the same format as the picture above? All of these are questions I am struggling to find good answers to.

Related

NodeJs Socket programming how to handle and manage Sockets? (Without Using socket.io) What is the efficient way?

After connection to the Server, every time Data coming from this connection (Socket.on('data',...)), Server fetches UserID from Data and check the ClientList (array of Socket objects), to see if Socket with this UserID exists in ClientList, if not : adds UserID as a property of Socket and then adds Socket object to Client list.
So when user with ID=1 want to send a message to user with ID = 2,
Server search for Socket with UserID = 2 in ClientList to find the right Socket and send user 1's message to the found Socket (user 2's socket).
I'm trying to accomplish this without using socket.io! That's what my employer made me to do! :))
Now my question is: am I doing this right? Is this efficient to check ClientList array (every time a connection send Data) to see if this UserID exists in ClientList? if not, then what is the right and efficient way? there is no problem with my code and it works. but what if there are thousands of connections?
Any Sample code , example or link would be appreciated. Thank you.
here is a pseudo code :
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
Socket.UserData = {}; // I want to add user data as a property to Socket
Socket.on('data', function (Data) {
var userID = myAuth.Authenticate_and_getUserID(Data);
if(userID != undefined){
var found = false;
ClientList.filter(function(item){
// check if Socket is in ClientList
if(item.UserData.ID == userID){
// client was connected before
found = true;
}
});
if(!found){
// this is a new connection, Add it to ClientList
Socket.UserData.ID = userID;
ClientList.push(Socket);
}
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
UPDATE for clarification:
is this efficient to look into ClientList every time Data is coming to Server, to check for receiverID (presence of receiver) and to Update ClientList with current connection UserID if not exists?
how should I manage new connections(users) and store them in server for later use when number of users are thousands or millions! NOT 10 or 100. How socket.io is doing this?
later usages could be:
check to see if one specific user is online (have an object in ClientList)
send realtime message to a user if he/she is online
etc . . .
Actually I am doing this wrong!
Arrays in JavaScript are passed by reference! So there is no need to update ClientList every time a Socket send data.
Therefor the Code changes like Following:
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
ClientList.push(Socket);
Socket._isAuthorized = false;
// when socket send data for the first time
// it gets authenticated and next time it send data
// server does not authenticate it
Socket.on('data', function (Data) {
var userID = getUserID(Data);
if(Socket._isAuthorized != true){
if(authenticate(Socket)){
Socket._isAuthorized = true;
Socket._userID = userID;
return;
}
}
// do something with data...
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
And its efficient!

Best Approach to send real time analytics using socket.io

I am using socket.io and node.js/mongoDB for an app which will send real time analytics between Parents and Drivers
Let's say Driver is moving along a path and for every location change he will send his location to a list of specific parents.
I can think of one approach to achieve such functionality
1- I create two arrays
var userSockets = {};
var driverSockets = {};
Whenever a user/driver is connected i do
For Driver - driverSockets[accId] = socket
For User - userSockets[accId] = socket
Now if a driver has to emit a location change, he will do something like
userSockets[userId].emit(abc)
I would like to know if this approach is better? Would it be better to save users as onlineUsers in MongoDB but even then how to access their sockets to emit data.
What would be the best approach.
You should use "room" to store online user and emit to this room(channel) when location change.
//join a room(channel)
socket.join('online');
//sending to sender client, only if they are in 'online' room(channel)
socket.to('online').emit('location', {user_type:'driver'});
Here is the example code to seperate driver and parents in different array with user_type key-word and send driver location to all the parents in real time. Furthermore, We can add users mobile number to send specific driver location to a specific parent etc.
var parents = [];
var drivers = [];
var users = [];
io.on('connection', function(socket){
users.push(socket);
socket.on('disconnect', function(msg){
var i = users.indexOf(socket);
users.splice(i, 1);
var j = parents.indexOf(socket);
if(j !== -1){
parents.splice(j, 1);
}
var k = drivers.indexOf(socket);
if(k !== -1){
drivers.splice(k, 1);
}
});
socket.on('register', function(msg){
console.log(msg);//send json- {"user_type":"driver"}
var data = JSON.parse(msg);
var i = users.indexOf(socket);
if(data.user_type === 'driver'){
drivers.push(users[i]);
}else{
parents.push(users[i]);
}
users[i].emit('register', '{"status":"registered"}');
});
socket.on('location', function(msg){
console.log(msg);//send json- {"user_type":"driver","location":"lat,long"}
var data = JSON.parse(msg);
if(data.user_type === 'driver'){
for(var x=0;x<parents.length;x++){
parents[x].emit('location', '{"user_type":"driver", "location":"lat,long"}');
}
}
});
});

how to send a buffer or byte array in a MQTT using nodeJS and protobufjs

I have a nodejs (node v0.10.26) MQTT code which publishes strings to a topic on a remote MQTT server. Now I am trying to publish a byte array and a buffer to the same topic, but I get an error
The code :
var ProtoBuf = require("protobufjs");
var mqtt = require('mqtt');
var builder = ProtoBuf.loadProtoFile('event.proto'),
as2 = builder.build('as2'),
Message=as2.Message;
var message = new Message({
"message_type": "EMOTICON"
});
console.log("message : "+message);
var buffer_message= message.encode();
client = mqtt.createClient(1883,'hostNameOfMQTT',{ encoding: 'binary' });
client.publish('NewTopic',buffer_message);
client.on('connect', function() {
console.log("Message published : "+buffer_message);
client.end();
});
and I get this error when I execute
node.exe sampleMqtt.js
C:\node>node.exe SimpleMQTT.js
message : .as2.Message
C:\node\node_modules\mqtt\lib\generate.js:178
length += Buffer.byteLength(payload);
^
TypeError: Argument must be a string
at Object.module.exports.publish (C:\node\node_modules\mqtt\lib\
generate.js:178:22)
at Connection.eval [as publish] (eval at <anonymous> (C:\node\no
de_modules\mqtt\lib\connection.js:58:29), <anonymous>:2:26)
at MqttClient._sendPacket (C:\node\node_modules\mqtt\lib\client.
js:430:20)
at MqttClient.<anonymous> (C:\node\node_modules\mqtt\lib\client.
js:105:12)
at MqttClient.EventEmitter.emit (events.js:117:20)
at MqttClient._handleConnack (C:\node\node_modules\mqtt\lib\clie
nt.js:498:10)
at Connection.<anonymous> (C:\node\node_modules\mqtt\lib\client.
js:191:10)
at Connection.EventEmitter.emit (events.js:95:17)
at Connection._write (C:\node\node_modules\mqtt\lib\connection.j
s:187:12)
at doWrite (_stream_writable.js:226:10)
However When I try to publish buffer_message.tostring(), I get the object details as a string but not the actual ArrayBuffer??
client.publish('AnkitTopic',buffer_message.toString());
Output with toString() -> ByteBuffer(offser=0,markedOffSet=-1,length=2,capacity=16)
I dont want a to string, I want the actual byteBuffer to be transmitted via MQTT.
Someone, Please suggest how can I do this!! Im guessing its not too hard to get, It's possible in java, then why not in nodeJS?
Can you try to send another kind of binary content like a file to see that it works?
Latest MQTT.js versions supports binary payloads https://github.com/adamvr/MQTT.js/issues/109
I found the solution to this problem and hope it helps other :
We can attach a header with every payload which we send to MQ, encode it and the final payload should look something similar (this is how we convert it into byte buffer) :
var headerLengthBuffer = new ByteBuffer();
var headerBuffer = header.encode();
var messageLengthBuffer = new ByteBuffer();
var messageBuffer = event1.encode();
headerLengthBuffer.writeVarint32(headerBuffer.length);
messageLengthBuffer.writeVarint32(messageBuffer.length);
//Create the payload object
var payload = headerLengthBuffer.toBuffer() + headerBuffer.toBuffer()+ messageLengthBuffer.toBuffer() + messageBuffer.toBuffer() ;
return payload;
The above payload created can be published easily and is accepted by MQ as well.
the protobufjs package supports native node.js Buffers.
According to protobufjs/message API you just have to call message.toBuffer().
Here is a working example:
var ProtoBuf = require("protobufjs");
var mqtt = require('mqtt');
var builder = ProtoBuf.loadProtoFile('company.proto'),
Company = builder.build('Company'),
Employee = Company.Employee;
var employee = new Employee("James Bond", 007);
var buffer_message = employee.toBuffer();
var client = mqtt.connect('mqtt://test.mosquitto.org');
client.on('connect', function() {
client.publish('MyCompany', buffer_message);
client.end();
});
Using the following company.proto file:
package Company;
message Employee{
optional string name = 1;
optional uint32 id = 2;
}

Broadcasting with shoe sockjs

I am trying to setup a simple scenerio using shoe + dnode +sockjs and I do not know how to broadcast a message to all users connected to the web application.
Do you know if there is a function or method which manage this? or should it be make by "hand"?
AFAIK, you have to roll it by "hand" as you say. Here is what I do:
server.js:
var shoe = require('shoe')
var connectedClients = {}
var conCount = 0
var sock = shoe(function(clientStream) {
clienStream.id = conCount
connectedClients[clientStream.id] = clientStream
conCount += 1
})
somewhere else in your server-side program:
//write to all connected clients
Object.keys(connectedClients).forEach(function(cid) {
var clientStream = connectedClients[cid]
clientStream.write(yourData)
})
Note, you'll want to introduce additional logic to only write to connected clients, so you'll want to remove disconnected clients from connectedClients, something like delete connectedClients[id].
Hopefully that helps.

direct (non-tcp) connection to redis from nodejs

hello all
I looked at the at the redis-node-client source (relevant part is shown bellow) and I see that it connects to redis via the 'net' package, which is TCP based.
line 370
exports.createClient = function (port, host, options) {
var port = port || exports.DEFAULT_PORT;
var host = host || exports.DEFAULT_HOST;
var client = new Client(net.createConnection(port, host), options);
client.port = port;
client.host = host;
return client;
};
I was wondering if there's a more direct client for redis, preferably via domain-sockets or something of that sort. Im using redis localy, as cache, without going over the wire so its unnecessary to encode/decode messages with TCP headers...
thank you
Unix Domain Socket support appears to have landed in Redis as of Nov 4th.
http://code.google.com/p/redis/issues/detail?id=231
To connect to a Unix Domain Socket, you need to supply the pathname to net.createConnection. Maybe something like this in redis-node-client:
exports.createSocketClient = function (path, options) {
var client = new Client(net.createConnection(path), options);
client.path = path;
return client;
};

Resources