From 876e7d118497ba22b048dc3a681927a50c423523 Mon Sep 17 00:00:00 2001
From: Nicolas Pope <nwpope@utu.fi>
Date: Fri, 5 Apr 2019 09:56:05 +0300
Subject: [PATCH] JS Socket passes initial connection tests

---
 net/js/package.json        |   3 +-
 net/js/src/index.js        |   2 +
 net/js/src/socket.js       | 133 ++++++++++++++++++++++++-------------
 net/js/test/socket_unit.js |  17 +++--
 4 files changed, 99 insertions(+), 56 deletions(-)

diff --git a/net/js/package.json b/net/js/package.json
index 52bbcc4bd..b379f3846 100644
--- a/net/js/package.json
+++ b/net/js/package.json
@@ -1,6 +1,6 @@
 {
   "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"
diff --git a/net/js/src/index.js b/net/js/src/index.js
index e69de29bb..bddfe8142 100644
--- a/net/js/src/index.js
+++ b/net/js/src/index.js
@@ -0,0 +1,2 @@
+export.Socket = require('./socket.js');
+
diff --git a/net/js/src/socket.js b/net/js/src/socket.js
index 22b70d44c..48c3725a0 100644
--- a/net/js/src/socket.js
+++ b/net/js/src/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 {
diff --git a/net/js/test/socket_unit.js b/net/js/test/socket_unit.js
index a1cc4f78c..c5fd8fc7e 100644
--- a/net/js/test/socket_unit.js
+++ b/net/js/test/socket_unit.js
@@ -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();
 	});
 });
-- 
GitLab