domotz-remote-pawn
Version:
Domotz Agent
195 lines (175 loc) • 7.4 kB
JavaScript
/** 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);
}
},
};
};