Skip to content
Snippets Groups Projects
Commit 876e7d11 authored by Nicolas Pope's avatar Nicolas Pope
Browse files

JS Socket passes initial connection tests

parent 569e6edf
No related branches found
No related tags found
No related merge requests found
Pipeline #9643 passed
{
"name": "@ftl/net",
"version": "0.0.1",
"version": "0.0.2",
"description": "P2P Network protocol for FT-Lab",
"main": "src/index.js",
"scripts": {
......@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"bops": "^1.0.0",
"detect-browser": "^4.2.0",
"struct": "0.0.12",
"uri-js": "^4.2.2",
"ws": "^6.2.1"
......
export.Socket = require('./socket.js');
......@@ -2,6 +2,8 @@ const net = require('net');
const ws = require('ws');
const urijs = require('uri-js');
const binary = require('bops');
const browser = require('detect-browser').detect();
const isbrowser = !browser || browser.name != "node";
function Socket(uri) {
let t = typeof uri;
......@@ -17,7 +19,11 @@ function Socket(uri) {
this.lasterr_ = null;
this.connected_ = false;
this.valid_ = false;
this.buffer_ = new Buffer(0);
this.uuid_ = binary.create(16);
if (!isbrowser) {
this.buffer_ = new Buffer(0); // Only in nodejs
}
if (t == "string") {
this._fromURI(uri);
......@@ -26,10 +32,17 @@ function Socket(uri) {
}
}
Socket.ERROR_BADPROTOCOL = 0x01;
Socket.ERROR_BADHOST = 0x02;
Socket.ERROR_BADHANDSHAKE = 0x03;
Socket.ERROR_MALFORMEDURI = 0x04;
Socket.ERROR_BADPROTOCOL = "Bad Protocol";
Socket.ERROR_BADHOST = "Unknown host";
Socket.ERROR_BADHANDSHAKE = "Invalid Handshake";
Socket.ERROR_MALFORMEDURI = "Malformed URI";
Socket.ERROR_TCPINBROWSER = "TCP invalid in browser";
Socket.ERROR_LARGEMESSAGE = "Network message too large";
Socket.prototype.error = function(errno) {
this.lasterr_ = errno;
this.dispatch('error', [errno]);
}
Socket.prototype.isValid = function() {
return this.valid_;
......@@ -45,8 +58,7 @@ Socket.prototype._fromURI = function(uri) {
// Could not parse uri so report error
if (uriobj.scheme === undefined || uriobj.host === undefined) {
this.lasterr_ = Socket.ERROR_MALFORMEDURI;
this.dispatch('error', [this.lasterr_]);
this.error(Socket.ERROR_MALFORMEDURI);
return;
}
......@@ -63,12 +75,15 @@ Socket.prototype._fromURI = function(uri) {
this._initWebsocket();
// TCP
} else if (this.scheme_ == "tcp") {
this.socket_ = net.connect(uriobj.port, uriobj.host);
this._initTCPSocket();
if (!isbrowser) {
this.socket_ = net.connect(uriobj.port, uriobj.host);
this._initTCPSocket();
} else {
this.error(Socket.ERROR_TCPINBROWSER);
}
// Unrecognised protocol
} else {
this.lasterr_ = Socket.ERROR_BADPROTOCOL;
this.dispatch('error', [this.lasterr_]);
this.error(Socket.ERROR_BADPROTOCOL)
}
}
......@@ -95,15 +110,7 @@ Socket.prototype._initWebsocket = function() {
this.valid_ = true;
let dataHandler = (data) => {
let size = binary.readUInt32LE(data, 0);
let service = binary.readUInt32LE(data, 4);
console.log("Message", service);
if (this.handlers_.hasOwnProperty(service)) {
this.handlers_[service](binary.subarray(data, 8));
} else {
console.error("No handler for service "+service);
}
this.processMessage(data);
};
if (this.socket_.addEventHandler) {
......@@ -114,8 +121,8 @@ Socket.prototype._initWebsocket = function() {
this.socket_.on('message', dataHandler);
}
this.socket_.on('open', () => {
this.connected_ = true;
this.dispatch('open', []);
//this.connected_ = true;
//this.dispatch('open', []);
});
this.socket_.on('error', (err) => {
this.connected_ = false;
......@@ -131,46 +138,78 @@ Socket.prototype._initWebsocket = function() {
});
}
function checkMagic(buffer) {
if (buffer.length < 8) return false;
let lo_magic = binary.readUInt32LE(buffer,0);
let hi_magic = binary.readUInt32LE(buffer,4);
return (lo_magic == 0x53640912 && hi_magic == 0x10993400)
}
Socket.prototype.processMessage = function(buffer) {
if (!this.handshake_) {
// Check handshake
if (!checkMagic(buffer)) {
this.close();
this.error(Socket.ERROR_BADHANDSHAKE);
return 0;
}
binary.copy(buffer, this.uuid_, 0, 8, 16);
let proto_size = binary.readUInt32LE(buffer,24);
this.handshake_ = true;
this.connected_ = true;
this.dispatch('open', []);
return 28 + proto_size;
} else {
let size = binary.readUInt32LE(buffer,0);
let service = binary.readUInt32LE(buffer,4);
console.log("Message: " + service + "(size="+size+")");
// Do we have a complete message yet?
if (size > 1024*1024*100) {
this.error(Socket.ERROR_LARGEMESSAGE);
this.close();
return 0;
} else if (buffer.length-4 >= size) {
// Yes, so dispatch
this.dispatch(service, [size, binary.subarray(buffer,8)]);
return size+4;
} else {
return 0;
}
}
}
/**
* Setup TCP socket handlers and message buffering mechanism.
*/
Socket.prototype._initTCPSocket = function() {
this.valid_ = true;
let dataHandler = (data) => {
console.log('Received: ' + data);
this.buffer_ = Buffer.concat([this.buffer_, data]);
if (this.buffer_.length >= 8) {
let size = binary.readUInt32LE(this.buffer_,0);
let service = binary.readUInt32LE(this.buffer_,4);
console.log("Message: " + service);
// Do we have a complete message yet?
if (size > 1024*1024*100) {
this.dispatch('error', ["invalid message size"]);
console.log("Message too big");
} else if (this.buffer_.length-4 >= size) {
// Yes, so dispatch
console.log("Complete message found");
this.dispatch(service, [size, binary.subarray(this.buffer_,8)]);
} else {
console.log("Incomplete message");
}
while (this.buffer_.length >= 8) {
let s = this.processMessage(this.buffer_);
if (s == 0) break;
this.buffer_ = binary.subarray(this.buffer_,s);
}
};
this.socket_.on('data', dataHandler);
this.socket_.on('connect', () => {
this.connected_ = true;
this.dispatch('open', []);
//this.connected_ = true;
//this.dispatch('open', []);
});
this.socket_.on('error', (err) => {
this.connected_ = false;
this.valid_ = false;
console.log("Sock error: ", err);
switch (err.errno) {
case 'ENOTFOUND' : this.lasterr_ = Socket.ERROR_BADHOST; break;
default : this.lasterr_ = err.errno;
case 'ENOTFOUND' : this.error(Socket.ERROR_BADHOST); break;
default : this.error(err.errno);
}
this.dispatch('error', [this.lasterr_]);
});
this.socket_.on('close', () => {
this.dispatch('close', []);
......@@ -216,6 +255,8 @@ Socket.prototype.dispatch = function(h, args) {
}
Socket.prototype.close = function() {
if (this.socket_ == null) return;
if (this.scheme_ == "ws") {
this.socket_.close();
} else {
......
......@@ -16,7 +16,7 @@ describe("Socket()", function() {
if (dobadhandshake) {
socket.write(Buffer.from([44,55,33,22,23,44,87]));
} else {
socket.write(Buffer.from([0x12,0x09,0x64,0x53,0x00,0x34,0x99,0x10,3,0,0,0,3,0,0,0,67,67,67,67,67,67]));
socket.write(Buffer.from([0x12,0x09,0x64,0x53,0x00,0x34,0x99,0x10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,67,67,67]));
}
});
server.listen(9000, 'localhost');
......@@ -27,7 +27,7 @@ describe("Socket()", function() {
if (dobadhandshake) {
ws.send(Buffer.from([44,55,33,22,23,44,87]));
} else {
ws.send(Buffer.from([0x12,0x09,0x64,0x53,0x00,0x34,0x99,0x10,3,0,0,0,3,0,0,0,67,67,67,67,67,67]));
ws.send(Buffer.from([0x12,0x09,0x64,0x53,0x00,0x34,0x99,0x10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,67,67,67]));
}
});
});
......@@ -52,18 +52,16 @@ describe("Socket()", function() {
});
});
context("with a valid uri but bad handshake", () => {
context("with a valid uri but bad handshake", (done) => {
it("should reject the connection", () => {
let diderror = false;
dobadhandshake = true;
let sock = new Socket("tcp://localhost:9000");
sock.on('error', (errno) => {
diderror = true;
assert.equal(errno, Socket.ERROR_BADHANDSHAKE);
assert.isOk(sock.isValid());
assert.isNotOk(sock.isConnected());
done();
});
assert.isOk(diderror);
});
});
......@@ -101,7 +99,7 @@ describe("Socket()", function() {
});
afterEach(() => {
server.close(() => { console.log("Closed"); });
server.close();
server.unref();
wss.close();
......@@ -113,6 +111,8 @@ describe("Receiving messages on a tcp socket", function() {
beforeEach(() => {
server = net.createServer(socket => {
// Handshake first
socket.write(Buffer.from([0x12,0x09,0x64,0x53,0x00,0x34,0x99,0x10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,67,67,67]));
socket.write(Buffer.from([8,0,0,0,44,0,0,0,23,0,0,0]));
});
server.listen(9001, 'localhost');
......@@ -121,7 +121,6 @@ describe("Receiving messages on a tcp socket", function() {
it("receives valid short message", function(done) {
let sock = new Socket("tcp://localhost:9001");
sock.on(44, (size, data) => {
// TODO Parse the data...
assert.equal(binary.readInt32LE(data,0), 23);
console.log("Received data....");
sock.close();
......@@ -130,7 +129,7 @@ describe("Receiving messages on a tcp socket", function() {
});
afterEach(() => {
server.close(() => { console.log("Closed"); });
server.close();
server.unref();
});
});
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment