+++ /dev/null
-var dgram = require('dgram'); // UDP (datagram)\r
-\r
-// Timeout for ack/data packet\r
-var TIMEOUT = 5000; // 5 seconds\r
-// Max retransmits if timeout\r
-var MAX_RETRANSMITS = 3;\r
-\r
-// type: Opcode\r
-var types = {\r
- 'rrq': 1, // Read ReQuest\r
- 'wrq': 2, // Write ReQuest\r
- 'data':3,\r
- 'ack': 4,\r
- 'err': 5,\r
-\r
- // aliases:\r
- 'read':1,\r
- 'write':2\r
-}\r
-\r
-// Error strings ([errorCode] = errorString)\r
-var errors = [\r
- 'Not defined, see error message (if any).',\r
- 'File not found.',\r
- 'Access violation.',\r
- 'Disk full or allocation exceeded.',\r
- 'Illegal TFTP operation.',\r
- 'Unknown transfer ID.',\r
- 'File already exists.',\r
- 'No such user.'\r
-];\r
-\r
-/**\r
- * Constructor\r
- * @param {Number} port Port the tftp server is listening to\r
- * @param {String} ip Ip or hostname of the tftp server\r
- */\r
-var TFTP = module.exports = function(port, ip) {\r
- this.ip = ip || '127.0.0.1';\r
- this.port = port || 69;\r
-\r
- // Hold the current operation\r
- this.current = false;\r
- // Holds the queue of the next operations\r
- this.queue = [];\r
-\r
- // Client will be created when needed\r
- this.client = null;\r
-\r
- // Display debug messages?\r
- this.debug = false;\r
-}\r
-\r
-TFTP.prototype.createClient = function() {\r
- if (this.client !== null)\r
- return;\r
-\r
- // We need this inside onMessage...\r
- var self = this;\r
- \r
- // Called when getting an message from the server\r
- var onMessage = function(msg, rinfo) {\r
-\r
- // Type of message (see `types` above)\r
- var type = msg.readUInt16BE(0);\r
-\r
- switch (type) {\r
- // Data - Respond with Ack\r
- case types.data:\r
- var block = msg.readUInt16BE(2);\r
- var data = msg.slice(4); // From byte 4 is data\r
-\r
- if (self.debug) {\r
- console.log('> Received data. Block#: %d Bytes: %d', block, data.length);\r
- console.log('< Sending ACK. Block#: %d', block);\r
- }\r
-\r
- // Concat the two buffers\r
- self.current.data = Buffer.concat([self.current.data, data]);\r
-\r
- // Create and send ACK packet\r
- var ack = createAck(block);\r
- self.send(ack);\r
-\r
- // If less than 512 bytes were received, it's the end...\r
- if (data.length < 512) {\r
- // Send the data to the callback\r
- self.current.callback(null, self.current.data);\r
-\r
- // Go to next operation in queue\r
- self.next();\r
- }\r
- break;\r
- // Ack - Respond with Data\r
- case types.ack:\r
- var block = msg.readUInt16BE(2);\r
-\r
- if (self.debug)\r
- console.log('> Received ACK. Block#: %d', block);\r
-\r
- // If this is the ack for the last block\r
- if (block == self.current.blocks) {\r
- // Send callback\r
- self.current.callback(null, self.current.data.length);\r
-\r
- // Go to next operation in queue\r
- self.next();\r
-\r
- // Break out of switch\r
- break;\r
- }\r
-\r
- if (self.debug)\r
- console.log('< Sending data. Block#: %d', block + 1);\r
-\r
- // Create the data packet for the next block of data\r
- var start = 512 * block;\r
- // End is `start + 512`, or end of data, whichever comes first\r
- var end = Math.min(start + 512, self.current.data.length);\r
- var data = self.current.data.slice(start, end);\r
-\r
- var packet = createData(block+1, data);\r
-\r
- // Send data block\r
- self.send(packet);\r
- break;\r
- // Error\r
- case types.err:\r
- // Create new Error(), and send callback\r
- var errorCode = msg.readUInt16BE(2);\r
- var errMsg = msg.toString('ascii', 4, msg.length - 1);\r
- // @TODO: Take errMsg from `errors`, unless code == 0?\r
- var err = new Error(errMsg);\r
- err.code = errorCode;\r
-\r
- if (self.debug)\r
- console.log('> Received Error. code: %d - msg: %s', errorCode, ErrMsg);\r
-\r
- // Callback\r
- self.current.callback(err);\r
-\r
- // Go to next operation in queue\r
- self.next();\r
- break;\r
- }\r
- }\r
-\r
- // Create socket, and listen to messages\r
- this.client = dgram.createSocket("udp4", onMessage);\r
-};\r
-\r
-/**\r
- * Shortcut for sending packet to server\r
- * @param {Buffer} buff The buffer to send\r
- * @param {Function} cb Callback - (err, bytes)\r
- */\r
-TFTP.prototype.send = function(buff, cb) {\r
- if (typeof cb !== 'function') cb = function() {};\r
-\r
- // We need this later\r
- var self = this;\r
- \r
- // Create function of sending this packet so that we can call it again on timeout\r
- var send = function() {\r
- self.client.send(buff, 0, buff.length, self.port, self.ip, cb);\r
- };\r
- // Send the packet already\r
- send();\r
-\r
- var onTimeout = function() {\r
- // If we have retransmitted less than MAX_RETRANSMITS\r
- if (MAX_RETRANSMITS > ++self.current.timeout.retransmits) {\r
-\r
- if (self.debug)\r
- console.log('! Timeout - Retransmitting (%d bytes)', buff.length);\r
-\r
- // Send the packet again\r
- send();\r
-\r
- // Set a new timeout for the packet we just sent\r
- self.current.timeout.id = setTimeout(onTimeout, TIMEOUT);\r
- }\r
-\r
- // If we sent too many retransmitts, the remote server did not respond...\r
- else {\r
- if (self.debug)\r
- console.log('! Timeout - End');\r
-\r
- // Create error\r
- var err = new Error("Timeout");\r
- err.code = 0;\r
-\r
- self.current.callback(err);\r
- // Reset current, and check queue - (no answer from server)\r
- self.next();\r
- }\r
- }\r
-\r
- // If there is a timeout, clear the timeout\r
- if (this.current.timeout)\r
- clearTimeout(this.current.timeout.id);\r
-\r
- // Create a timeout object where we store information about this timeout\r
- this.current.timeout = {\r
- id: setTimeout(onTimeout, TIMEOUT),\r
- retransmits: 0\r
- }\r
-};\r
-\r
-/**\r
- * Goes to next item in queue (if any). Same as `check()`, but clears current first\r
- */\r
-TFTP.prototype.next = function() {\r
- // Has to clear any timeout\r
- if (this.current && this.current.timeout)\r
- clearTimeout(this.current.timeout.id);\r
-\r
- // Reset current, and check queue\r
- this.current = false;\r
- this.check();\r
-};\r
-\r
-/**\r
- * Checks the Queue.\r
- * If no operation is currently active, go to the next item in the queue (if any).\r
- */\r
-TFTP.prototype.check = function() {\r
- // If there currently is an active operation\r
- // Or if the queue is empty, just return\r
- if (this.current !== false)\r
- return;\r
-\r
- // If there is nothing running, and the queue is empty\r
- if (this.queue.length == 0) {\r
- // Close client\r
- this.client.close();\r
- return;\r
- } else {\r
- // Create client\r
- this.createClient();\r
- }\r
-\r
- // Take the first item in queue\r
- this.current = this.queue.shift();\r
-\r
- // We need this later\r
- var self = this;\r
-\r
-\r
- if (self.debug)\r
- console.log('< Sending request');\r
-\r
- // Create the request...\r
- var buff = createRequest(this.current.type, this.current.filename);\r
- // Send the request\r
- this.send(buff, function(err, bytes) {\r
- // If there was an error sending the packet\r
- if (err) {\r
- // Create Error message\r
- var err = new Error(['Error when sending ',\r
- self.current.type,\r
- ' request for ',\r
- self.current.filename].join());\r
-\r
- // Send error to the callback of the current operation\r
- self.current.callback(err);\r
-\r
- // Reset current\r
- self.current = false;\r
-\r
- // Then check queue\r
- self.check();\r
- }\r
- });\r
-};\r
-\r
-/**\r
- * Sends a file to the server\r
- * @param {String} filename File to send\r
- * @param {Buffer} data The data to write/send to the server\r
- * @param {Function} cb Callback - (err)\r
- */\r
-TFTP.prototype.write = function(filename, data, cb) {\r
- if (typeof cb !== 'function') cb = function() {}; // Default cb to an empty function\r
- if (!Buffer.isBuffer(data)) data = new Buffer(data); // If data is not a Buffer, make it a buffer\r
-\r
- // Item to put into the queue\r
- var queueItem = {\r
- type: 'write',\r
- filename: filename,\r
- callback: cb,\r
- data: data,\r
- blocks: Math.ceil(data.length / 512) // Number of blocks to transfer data.\r
- // When we receive an ACK with this number, the transfer is finished\r
- };\r
-\r
- // Push the queueItem to the end of the queue.\r
- this.queue.push(queueItem);\r
-\r
- // Check the queue...\r
- this.check();\r
-};\r
-\r
-/**\r
- * Reads a file off the server\r
- * @param {String} filename File to read from server\r
- * @param {Function} cb Callback - (err, data)\r
- */\r
-TFTP.prototype.read = function(filename, cb) {\r
- if (typeof cb !== 'function') cb = function() {}; // Default cb to an empty function\r
-\r
- // Item to put into the queue\r
- var queueItem = {\r
- type: 'read',\r
- filename: filename,\r
- callback: cb,\r
- data: new Buffer(0) // Buffer of size 0 which will be filled up by onMessage\r
- };\r
-\r
- // Push the queueItem to the end of the queue.\r
- this.queue.push(queueItem);\r
-\r
- // Check the queue...\r
- this.check();\r
-};\r
-\r
-/**\r
- * Creates a buffer for a request.\r
-\r
- 2 bytes string 1 byte string 1 byte\r
- ------------------------------------------------\r
- | Opcode | Filename | 0 | Mode | 0 |\r
- ------------------------------------------------\r
-\r
- * @param {String|Number} type Int Opcode, or String (read|write|rrq|wrq)\r
- * @param {String} filename Filename to add in the request\r
- * @param {String} mode optional Mode (netascii|octet|email) - Defaults to octet\r
- * @return {Buffer} The Buffer\r
- */\r
-function createRequest(type, filename, mode) {\r
- // Figure out the opcode for the type\r
- if (typeof type === 'string') {\r
- type = type.toLowerCase();\r
-\r
- // If it exists in the types object, we found it\r
- if (types.hasOwnProperty(type))\r
- type = types[type];\r
- // If not, we dno what type\r
- else\r
- throw 'Unknown type (' + type + ')';\r
- }\r
- // Not a string, nor a number, then we dno what type of request it is...\r
- else if (typeof type !== 'number')\r
- throw 'Unknown type (' + type + ')';\r
-\r
- // Default mode to 'octet'\r
- mode = mode || 'octet';\r
-\r
- // Calculate the length of the buffer\r
- // mode (2 byte opcode) + length of filename + 1 null byte + length of mode + 1 null byte.\r
- var buffLen = 4 + filename.length + mode.length;\r
-\r
- // Create the buffer\r
- var buff = new Buffer(buffLen);\r
- // Write mode (as unsigned 16 bit integer) on offset 0\r
- buff.writeUInt16BE(type, 0);\r
- // Write filename as ascii on offset 2\r
- buff.write(filename, 2, 'ascii');\r
- // Write mode as ascii on offset filename.length + 3 (type + filename null termination)\r
- buff.write(mode, 2 + filename.length + 1, 'ascii');\r
-\r
- // Make sure the ends of the strings are null\r
- buff[2 + filename.length] = buff[buffLen - 1] = 0;\r
-\r
- // Return the new buffer\r
- return buff;\r
-}\r
-\r
-/**\r
- * Creates a buffer for a data packet\r
-\r
- 2 bytes 2 bytes n bytes\r
- ----------------------------------\r
- | Opcode | Block # | Data |\r
- ----------------------------------\r
-\r
- * @param {Number} blockNumber Which block of the transaction it is\r
- * @param {String} data The data to send\r
- * @return {Buffer} The Buffer\r
- */\r
-function createData(blockNumber, data) {\r
- var type = types['data'];\r
-\r
- // Type + blocknumber + length of data(max 512)\r
- var dataLen = Math.min(data.length, 512);\r
- var buffLen = 4 + dataLen;\r
-\r
- var buff = new Buffer(buffLen);\r
-\r
- buff.writeUInt16BE(type, 0); // Type as UInt16BE on offset: 0\r
- buff.writeUInt16BE(blockNumber, 2); // BlockNumber as UInt16BE on offset: 2\r
- // Copy `data` into buff on offset 4. bytes 0 to 512 from `data`.\r
- data.copy(buff, 4, 0, dataLen); // targetBuffer, targetStart, sourceStart, sourceEnd\r
-\r
- return buff;\r
-}\r
-\r
-/**\r
- * Creates a buffer for a ACK packet\r
-\r
- 2 bytes 2 bytes\r
- ---------------------\r
- | Opcode | Block # |\r
- ---------------------\r
-\r
- * @param {Number} blockNumber Which block to ack\r
- * @return {Buffer} The Buffer\r
- */\r
-function createAck(blockNumber) {\r
- var type = types['ack'];\r
-\r
- var buff = new Buffer(4);\r
- buff.writeUInt16BE(type, 0); // Type as UInt16BE on offset: 0\r
- buff.writeUInt16BE(blockNumber, 2); // BlockNumber as UInt16BE on offset: 2\r
-\r
- return buff;\r
-}\r
-\r
-/**\r
- * Creates a buffer for an ERROR packet\r
-\r
- 2 bytes 2 bytes string 1 byte\r
- -----------------------------------------\r
- | Opcode | ErrorCode | ErrMsg | 0 |\r
- -----------------------------------------\r
-\r
- * @param {Number} code ErrorCode\r
- * @param {String} msg Optional message - defaults to the message of the error code\r
- * @return {Buffer} The Buffer\r
- */\r
-function createError(code, msg) {\r
- // Default error message to the error message for the code (defined on top)\r
- msg = msg || errors[code];\r
- if (typeof msg !== 'string') msg = '';\r
-\r
- var type = types['error'];\r
-\r
- var buffLen = 4 + msg.length + 1;\r
-\r
- var buff = new Buffer(buffLen);\r
- buff.writeUInt16BE(type, 0); // Type as UInt16BE on offset: 0\r
- buff.writeUInt16BE(code, 2); // ErrorCode as UInt16BE on offset: 2\r
- buff.write(msg, 4, 'ascii'); // ErrMsg as ascii string on offset: 4\r
- buff[buffLen - 1] = 0; // Last byte is 0\r
-\r
- return buff;\r
-}\r