From 3f5228fcccb1165cb86a1e099fdfdfaed11d26f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Beye?= Date: Mon, 26 Jul 2021 20:27:40 +0200 Subject: [PATCH] Initial idea commit --- backend/lib/Tools.js | 4 ++ backend/lib/miio/Dummycloud.js | 10 ++-- .../miio/{MiioSocket.js => MiioUDPSocket.js} | 6 +-- backend/lib/miio/RetryWrapper.js | 2 +- backend/lib/robots/MiioValetudoRobot.js | 4 +- backend/lib/utils/DualUseTCPServer.js | 52 +++++++++++++++++++ backend/lib/webserver/WebServer.js | 13 ++++- 7 files changed, 79 insertions(+), 12 deletions(-) rename backend/lib/miio/{MiioSocket.js => MiioUDPSocket.js} (99%) create mode 100644 backend/lib/utils/DualUseTCPServer.js diff --git a/backend/lib/Tools.js b/backend/lib/Tools.js index b3e8e0f2..df3d0a78 100644 --- a/backend/lib/Tools.js +++ b/backend/lib/Tools.js @@ -105,6 +105,10 @@ class Tools { load: os.loadavg().map(v => v / os.cpus().length) }; } + + static IS_ASCII(str) { + return /^[\x00-\x7F]*$/.test(str); + } } module.exports = Tools; diff --git a/backend/lib/miio/Dummycloud.js b/backend/lib/miio/Dummycloud.js index 0f8285d7..d6be7e99 100644 --- a/backend/lib/miio/Dummycloud.js +++ b/backend/lib/miio/Dummycloud.js @@ -1,6 +1,6 @@ const dgram = require("dgram"); const Logger = require("../Logger"); -const MiioSocket = require("./MiioSocket"); +const MiioUDPSocket = require("./MiioUDPSocket"); class Dummycloud { /** @@ -29,7 +29,7 @@ class Dummycloud { this.socket.bind(Dummycloud.PORT, this.bindIP); - this.miioSocket = new MiioSocket({ + this.miioUDPSocket = new MiioUDPSocket({ socket: this.socket, token: options.cloudSecret, onMessage: this.handleMessage.bind(this), @@ -48,7 +48,7 @@ class Dummycloud { // some default handling. switch (msg.method) { case "_otc.info": - this.miioSocket.sendMessage({ + this.miioUDPSocket.sendMessage({ "id": msg.id, "result": { "otc_list": [{"ip": this.spoofedIP, "port": Dummycloud.PORT}], @@ -81,12 +81,12 @@ class Dummycloud { * @returns {Promise} */ async shutdown() { - await this.miioSocket.shutdown(); + await this.miioUDPSocket.shutdown(); } } /** * @constant - * The miio port the dummycloud listens on. + * The miio UDP port the dummycloud listens on. */ Dummycloud.PORT = 8053; diff --git a/backend/lib/miio/MiioSocket.js b/backend/lib/miio/MiioUDPSocket.js similarity index 99% rename from backend/lib/miio/MiioSocket.js rename to backend/lib/miio/MiioUDPSocket.js index 5f9a1964..ebd79ba1 100644 --- a/backend/lib/miio/MiioSocket.js +++ b/backend/lib/miio/MiioUDPSocket.js @@ -21,7 +21,7 @@ const TRACE_METHODS = [ * Performs encryption and decryption, and tracks message ids and retries to provide an easy * promise interface. */ -class MiioSocket { +class MiioUDPSocket { /** * @param {object} options * @param {import("dgram").Socket} options.socket @@ -250,6 +250,6 @@ class MiioSocket { } /** The default remote port. @const {int} */ -MiioSocket.PORT = 54321; +MiioUDPSocket.PORT = 54321; -module.exports = MiioSocket; +module.exports = MiioUDPSocket; diff --git a/backend/lib/miio/RetryWrapper.js b/backend/lib/miio/RetryWrapper.js index 33bb0be4..29175c0b 100644 --- a/backend/lib/miio/RetryWrapper.js +++ b/backend/lib/miio/RetryWrapper.js @@ -14,7 +14,7 @@ const STATES = Object.freeze({ */ class RetryWrapper { /** - * @param {import("./MiioSocket")} socket + * @param {import("./MiioUDPSocket")} socket * @param {() => Buffer} tokenProvider */ constructor(socket, tokenProvider) { diff --git a/backend/lib/robots/MiioValetudoRobot.js b/backend/lib/robots/MiioValetudoRobot.js index b852cb71..9a6fdd6f 100644 --- a/backend/lib/robots/MiioValetudoRobot.js +++ b/backend/lib/robots/MiioValetudoRobot.js @@ -8,7 +8,7 @@ const path = require("path"); const Dummycloud = require("../miio/Dummycloud"); const Logger = require("../Logger"); -const MiioSocket = require("../miio/MiioSocket"); +const MiioUDPSocket = require("../miio/MiioUDPSocket"); const NotImplementedError = require("../core/NotImplementedError"); const RetryWrapper = require("../miio/RetryWrapper"); const ValetudoRobot = require("../core/ValetudoRobot"); @@ -38,7 +38,7 @@ class MiioValetudoRobot extends ValetudoRobot { const socket = dgram.createSocket("udp4"); socket.bind(); - return new MiioSocket({ + return new MiioUDPSocket({ socket: socket, token: this.localSecret, onMessage: () => {}, diff --git a/backend/lib/utils/DualUseTCPServer.js b/backend/lib/utils/DualUseTCPServer.js new file mode 100644 index 00000000..b2a1105f --- /dev/null +++ b/backend/lib/utils/DualUseTCPServer.js @@ -0,0 +1,52 @@ +const net = require("net"); +const http = require("http"); +const Tools = require("../Tools"); + +//Adapted from https://stackoverflow.com/a/42019773/10951033 +class DualUseTCPServer { + + /** + * @param {*} handler + */ + constructor(handler) { + this.server = net.createServer(socket => { + socket.once('data', buffer => { + // Pause the socket + socket.pause(); + + + //ASCII = HTTP, lol + if (Tools.IS_ASCII(buffer.toString())) { + socket.unshift(buffer); + + this.httpServer.emit("connection", socket); + } else if (this.miioServer) { + socket.unshift(buffer); + + this.miioServer.emit("connection", socket); + } + + // As of NodeJS 10.x the socket must be + // resumed asynchronously or the socket + // connection hangs, potentially crashing + // the process. Prior to NodeJS 10.x + // the socket may be resumed synchronously. + process.nextTick(() => socket.resume()); + }); + }); + + this.miioServer = undefined; + this.httpServer = http.createServer(handler); + } + + getServer() { + return this.server; + } + + setMiioServer(miioServer) { + this.miioServer = miioServer; + } + +} + +module.exports = DualUseTCPServer; diff --git a/backend/lib/webserver/WebServer.js b/backend/lib/webserver/WebServer.js index 3bde11cb..8673e407 100644 --- a/backend/lib/webserver/WebServer.js +++ b/backend/lib/webserver/WebServer.js @@ -22,6 +22,7 @@ const MiioValetudoRobot = require("../robots/MiioValetudoRobot"); const NTPClientRouter = require("./NTPClientRouter"); const SystemRouter = require("./SystemRouter"); const TimerRouter = require("./TimerRouter"); +const DualUseTCPServer = require("../utils/DualUseTCPServer"); class WebServer { /** @@ -72,7 +73,9 @@ class WebServer { } }); - const server = http.createServer(this.app); + this.dualUseTCPServer = new DualUseTCPServer(this.app); + + const server = this.dualUseTCPServer.getServer(); this.loadApiSpec(); this.validator = function noOpValidationMiddleware(req, res, next) { @@ -208,6 +211,14 @@ class WebServer { }); } + /** + * @public + * @returns {DualUseTCPServer} + */ + getDualUseTCPServer() { + return this.dualUseTCPServer; + } + /** * @private */