UNPKG

domotz-remote-pawn

Version:

Domotz Agent

195 lines (175 loc) 7.4 kB
/** This file is part of Domotz Agent. * Copyright (C) 2020 Domotz Ltd * * Domotz Agent is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Domotz Agent is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>. * * Created by Andrea Azzara <a.azzara@domotz.com> on 16/04/20. */ module.exports.server = function (resourceLocator) { var myConsole = resourceLocator.log.decorateLogs(); var tftp = resourceLocator.tftp; var host = resourceLocator.discovery.host; var filenameIndex = 0; var closed = true; var content; function generateVirtualFilename() { filenameIndex += 1; return 'f' + filenameIndex; } function getIPAddress() { return host.getIP(); } function returnAndReleaseOnError(cb, e) { myConsole.error(e.message + '\n' + e.stack); closed = true; cb({ error: e.toString() }); } return { start: function (options, callback, shouldReceiveFile) { if (!closed) { callback({ error: 'a server is already active' }); return; } var port = options.port || 69; var ipAddress = options.ip || getIPAddress(); var connections = []; var putRequestData = []; var timeout = parseInt(options.timeout_ms) || 30000; var filename = options.filename || generateVirtualFilename(); var serverOptions = { port: port, host: ipAddress, windowSize: options.window_size || 4, timeout: timeout, }; if (shouldReceiveFile) { serverOptions.blockSize = options.block_size || 65464; // max size serverOptions.denyPUT = false; serverOptions.timeout = options.server_timeout_ms || 20000; } else { var retries = 3; if (options.retries !== undefined) { retries = options.retries; } serverOptions.blockSize = options.block_size || 1468; // max size serverOptions.denyPUT = true; serverOptions.timeout = options.server_timeout_ms || 3000; serverOptions.retries = retries; try { content = new Buffer(options.content, 'base64'); } catch (e) { myConsole.error('Unable to parse base64 content'); returnAndReleaseOnError(callback, 'Unable to parse base64 content'); return; } } var server = tftp.createServer(serverOptions, function () { //disable default listener }); server.on('listening', onListening); server.on('request', onRequest); server.on('error', onError); server.on('close', onClose); server.listen(); function onData(dataChunk) { var isLastChunk = this._lastReceived; putRequestData.push(dataChunk); if (isLastChunk === true) { myConsole.debug('Received last chunk for tftp transfer'); server.close(); } } function onClose() { closed = true; if (shouldReceiveFile) { var result = { output: Buffer.concat(putRequestData).toString('base64'), }; callback(result); } if (!connections.length) { return myConsole.info('Server (Receiver) closed'); } //Abort all the current transfers for (var i = 0; i < connections.length; i++) { myConsole.info('Connection ' + i + ' aborted'); connections[i].abort(); } } function onListening() { myConsole.info('Server listening to receive file %s, port=%s, timeout=%s', filename, port, timeout); if (!shouldReceiveFile) { var result = { port: port, host: ipAddress, filename: filename, }; myConsole.info('Server listening %s, timeout=%s', JSON.stringify(result), timeout); callback(result); } } function onRequest(req, tftpRes) { req.on('error', function (error) { //Error from the request myConsole.error(error.message + '\n' + error.stack); }); //Save the connection connections.push(req); //The "close" event is fired when the internal socket closes, regardless //whether it is produced by an error, the socket closes naturally due to the //end of the transfer or the transfer has been aborted req.on('close', onRequestClose); if (req.file !== filename) { myConsole.warn('unknown file name Request: "%s" not equal to Existing: "%s"', req.file, filename); return req.abort(tftp.ENOENT); } if (shouldReceiveFile) { if (req.method === 'PUT') { req._reader.onData = onData; } else { myConsole.warn('TFTP Server is set for receiving only: method must be PUT'); return req.abort(tftp.ENOGET); } } else { tftpRes.setSize(content.length); tftpRes.end(content); myConsole.info('Content %s sent %s bytes', filename, content.length); } } // Common TFTP Server functions function onRequestClose() { //Remove the connection connections.splice(connections.indexOf(this), 1); if (closed && !connections.length) { //The server and all the connections have been closed myConsole.info('Server closed'); } } function onError(error) { //Errors from the main socket returnAndReleaseOnError(callback, error); } function onTimeout() { if (!closed) { myConsole.info('Timeout expired, closing the server'); server.close(); } } closed = false; if (timeout !== 0) { setTimeout(onTimeout, timeout); } }, }; };