1 var dgram = require('dgram'); // UDP (datagram)
\r
3 // Timeout for ack/data packet
\r
4 var TIMEOUT = 5000; // 5 seconds
\r
5 // Max retransmits if timeout
\r
6 var MAX_RETRANSMITS = 3;
\r
10 'rrq': 1, // Read ReQuest
\r
11 'wrq': 2, // Write ReQuest
\r
21 // Error strings ([errorCode] = errorString)
\r
23 'Not defined, see error message (if any).',
\r
25 'Access violation.',
\r
26 'Disk full or allocation exceeded.',
\r
27 'Illegal TFTP operation.',
\r
28 'Unknown transfer ID.',
\r
29 'File already exists.',
\r
35 * @param {Number} port Port the tftp server is listening to
\r
36 * @param {String} ip Ip or hostname of the tftp server
\r
38 var TFTP = module.exports = function(port, ip) {
\r
39 this.ip = ip || '127.0.0.1';
\r
40 this.port = port || 69;
\r
42 // Hold the current operation
\r
43 this.current = false;
\r
44 // Holds the queue of the next operations
\r
47 // Client will be created when needed
\r
50 // Display debug messages?
\r
54 TFTP.prototype.createClient = function() {
\r
55 if (this.client !== null)
\r
58 // We need this inside onMessage...
\r
61 // Called when getting an message from the server
\r
62 var onMessage = function(msg, rinfo) {
\r
64 // Type of message (see `types` above)
\r
65 var type = msg.readUInt16BE(0);
\r
68 // Data - Respond with Ack
\r
70 var block = msg.readUInt16BE(2);
\r
71 var data = msg.slice(4); // From byte 4 is data
\r
74 console.log('> Received data. Block#: %d Bytes: %d', block, data.length);
\r
75 console.log('< Sending ACK. Block#: %d', block);
\r
78 // Concat the two buffers
\r
79 self.current.data = Buffer.concat([self.current.data, data]);
\r
81 // Create and send ACK packet
\r
82 var ack = createAck(block);
\r
85 // If less than 512 bytes were received, it's the end...
\r
86 if (data.length < 512) {
\r
87 // Send the data to the callback
\r
88 self.current.callback(null, self.current.data);
\r
90 // Go to next operation in queue
\r
94 // Ack - Respond with Data
\r
96 var block = msg.readUInt16BE(2);
\r
99 console.log('> Received ACK. Block#: %d', block);
\r
101 // If this is the ack for the last block
\r
102 if (block == self.current.blocks) {
\r
104 self.current.callback(null, self.current.data.length);
\r
106 // Go to next operation in queue
\r
109 // Break out of switch
\r
114 console.log('< Sending data. Block#: %d', block + 1);
\r
116 // Create the data packet for the next block of data
\r
117 var start = 512 * block;
\r
118 // End is `start + 512`, or end of data, whichever comes first
\r
119 var end = Math.min(start + 512, self.current.data.length);
\r
120 var data = self.current.data.slice(start, end);
\r
122 var packet = createData(block+1, data);
\r
129 // Create new Error(), and send callback
\r
130 var errorCode = msg.readUInt16BE(2);
\r
131 var errMsg = msg.toString('ascii', 4, msg.length - 1);
\r
132 // @TODO: Take errMsg from `errors`, unless code == 0?
\r
133 var err = new Error(errMsg);
\r
134 err.code = errorCode;
\r
137 console.log('> Received Error. code: %d - msg: %s', errorCode, ErrMsg);
\r
140 self.current.callback(err);
\r
142 // Go to next operation in queue
\r
148 // Create socket, and listen to messages
\r
149 this.client = dgram.createSocket("udp4", onMessage);
\r
153 * Shortcut for sending packet to server
\r
154 * @param {Buffer} buff The buffer to send
\r
155 * @param {Function} cb Callback - (err, bytes)
\r
157 TFTP.prototype.send = function(buff, cb) {
\r
158 if (typeof cb !== 'function') cb = function() {};
\r
160 // We need this later
\r
163 // Create function of sending this packet so that we can call it again on timeout
\r
164 var send = function() {
\r
165 self.client.send(buff, 0, buff.length, self.port, self.ip, cb);
\r
167 // Send the packet already
\r
170 var onTimeout = function() {
\r
171 // If we have retransmitted less than MAX_RETRANSMITS
\r
172 if (MAX_RETRANSMITS > ++self.current.timeout.retransmits) {
\r
175 console.log('! Timeout - Retransmitting (%d bytes)', buff.length);
\r
177 // Send the packet again
\r
180 // Set a new timeout for the packet we just sent
\r
181 self.current.timeout.id = setTimeout(onTimeout, TIMEOUT);
\r
184 // If we sent too many retransmitts, the remote server did not respond...
\r
187 console.log('! Timeout - End');
\r
190 var err = new Error("Timeout");
\r
193 self.current.callback(err);
\r
194 // Reset current, and check queue - (no answer from server)
\r
199 // If there is a timeout, clear the timeout
\r
200 if (this.current.timeout)
\r
201 clearTimeout(this.current.timeout.id);
\r
203 // Create a timeout object where we store information about this timeout
\r
204 this.current.timeout = {
\r
205 id: setTimeout(onTimeout, TIMEOUT),
\r
211 * Goes to next item in queue (if any). Same as `check()`, but clears current first
\r
213 TFTP.prototype.next = function() {
\r
214 // Has to clear any timeout
\r
215 if (this.current && this.current.timeout)
\r
216 clearTimeout(this.current.timeout.id);
\r
218 // Reset current, and check queue
\r
219 this.current = false;
\r
224 * Checks the Queue.
\r
225 * If no operation is currently active, go to the next item in the queue (if any).
\r
227 TFTP.prototype.check = function() {
\r
228 // If there currently is an active operation
\r
229 // Or if the queue is empty, just return
\r
230 if (this.current !== false)
\r
233 // If there is nothing running, and the queue is empty
\r
234 if (this.queue.length == 0) {
\r
236 this.client.close();
\r
240 this.createClient();
\r
243 // Take the first item in queue
\r
244 this.current = this.queue.shift();
\r
246 // We need this later
\r
251 console.log('< Sending request');
\r
253 // Create the request...
\r
254 var buff = createRequest(this.current.type, this.current.filename);
\r
255 // Send the request
\r
256 this.send(buff, function(err, bytes) {
\r
257 // If there was an error sending the packet
\r
259 // Create Error message
\r
260 var err = new Error(['Error when sending ',
\r
263 self.current.filename].join());
\r
265 // Send error to the callback of the current operation
\r
266 self.current.callback(err);
\r
269 self.current = false;
\r
271 // Then check queue
\r
278 * Sends a file to the server
\r
279 * @param {String} filename File to send
\r
280 * @param {Buffer} data The data to write/send to the server
\r
281 * @param {Function} cb Callback - (err)
\r
283 TFTP.prototype.write = function(filename, data, cb) {
\r
284 if (typeof cb !== 'function') cb = function() {}; // Default cb to an empty function
\r
285 if (!Buffer.isBuffer(data)) data = new Buffer(data); // If data is not a Buffer, make it a buffer
\r
287 // Item to put into the queue
\r
290 filename: filename,
\r
293 blocks: Math.ceil(data.length / 512) // Number of blocks to transfer data.
\r
294 // When we receive an ACK with this number, the transfer is finished
\r
297 // Push the queueItem to the end of the queue.
\r
298 this.queue.push(queueItem);
\r
300 // Check the queue...
\r
305 * Reads a file off the server
\r
306 * @param {String} filename File to read from server
\r
307 * @param {Function} cb Callback - (err, data)
\r
309 TFTP.prototype.read = function(filename, cb) {
\r
310 if (typeof cb !== 'function') cb = function() {}; // Default cb to an empty function
\r
312 // Item to put into the queue
\r
315 filename: filename,
\r
317 data: new Buffer(0) // Buffer of size 0 which will be filled up by onMessage
\r
320 // Push the queueItem to the end of the queue.
\r
321 this.queue.push(queueItem);
\r
323 // Check the queue...
\r
328 * Creates a buffer for a request.
\r
330 2 bytes string 1 byte string 1 byte
\r
331 ------------------------------------------------
\r
332 | Opcode | Filename | 0 | Mode | 0 |
\r
333 ------------------------------------------------
\r
335 * @param {String|Number} type Int Opcode, or String (read|write|rrq|wrq)
\r
336 * @param {String} filename Filename to add in the request
\r
337 * @param {String} mode optional Mode (netascii|octet|email) - Defaults to octet
\r
338 * @return {Buffer} The Buffer
\r
340 function createRequest(type, filename, mode) {
\r
341 // Figure out the opcode for the type
\r
342 if (typeof type === 'string') {
\r
343 type = type.toLowerCase();
\r
345 // If it exists in the types object, we found it
\r
346 if (types.hasOwnProperty(type))
\r
347 type = types[type];
\r
348 // If not, we dno what type
\r
350 throw 'Unknown type (' + type + ')';
\r
352 // Not a string, nor a number, then we dno what type of request it is...
\r
353 else if (typeof type !== 'number')
\r
354 throw 'Unknown type (' + type + ')';
\r
356 // Default mode to 'octet'
\r
357 mode = mode || 'octet';
\r
359 // Calculate the length of the buffer
\r
360 // mode (2 byte opcode) + length of filename + 1 null byte + length of mode + 1 null byte.
\r
361 var buffLen = 4 + filename.length + mode.length;
\r
363 // Create the buffer
\r
364 var buff = new Buffer(buffLen);
\r
365 // Write mode (as unsigned 16 bit integer) on offset 0
\r
366 buff.writeUInt16BE(type, 0);
\r
367 // Write filename as ascii on offset 2
\r
368 buff.write(filename, 2, 'ascii');
\r
369 // Write mode as ascii on offset filename.length + 3 (type + filename null termination)
\r
370 buff.write(mode, 2 + filename.length + 1, 'ascii');
\r
372 // Make sure the ends of the strings are null
\r
373 buff[2 + filename.length] = buff[buffLen - 1] = 0;
\r
375 // Return the new buffer
\r
380 * Creates a buffer for a data packet
\r
382 2 bytes 2 bytes n bytes
\r
383 ----------------------------------
\r
384 | Opcode | Block # | Data |
\r
385 ----------------------------------
\r
387 * @param {Number} blockNumber Which block of the transaction it is
\r
388 * @param {String} data The data to send
\r
389 * @return {Buffer} The Buffer
\r
391 function createData(blockNumber, data) {
\r
392 var type = types['data'];
\r
394 // Type + blocknumber + length of data(max 512)
\r
395 var dataLen = Math.min(data.length, 512);
\r
396 var buffLen = 4 + dataLen;
\r
398 var buff = new Buffer(buffLen);
\r
400 buff.writeUInt16BE(type, 0); // Type as UInt16BE on offset: 0
\r
401 buff.writeUInt16BE(blockNumber, 2); // BlockNumber as UInt16BE on offset: 2
\r
402 // Copy `data` into buff on offset 4. bytes 0 to 512 from `data`.
\r
403 data.copy(buff, 4, 0, dataLen); // targetBuffer, targetStart, sourceStart, sourceEnd
\r
409 * Creates a buffer for a ACK packet
\r
412 ---------------------
\r
413 | Opcode | Block # |
\r
414 ---------------------
\r
416 * @param {Number} blockNumber Which block to ack
\r
417 * @return {Buffer} The Buffer
\r
419 function createAck(blockNumber) {
\r
420 var type = types['ack'];
\r
422 var buff = new Buffer(4);
\r
423 buff.writeUInt16BE(type, 0); // Type as UInt16BE on offset: 0
\r
424 buff.writeUInt16BE(blockNumber, 2); // BlockNumber as UInt16BE on offset: 2
\r
430 * Creates a buffer for an ERROR packet
\r
432 2 bytes 2 bytes string 1 byte
\r
433 -----------------------------------------
\r
434 | Opcode | ErrorCode | ErrMsg | 0 |
\r
435 -----------------------------------------
\r
437 * @param {Number} code ErrorCode
\r
438 * @param {String} msg Optional message - defaults to the message of the error code
\r
439 * @return {Buffer} The Buffer
\r
441 function createError(code, msg) {
\r
442 // Default error message to the error message for the code (defined on top)
\r
443 msg = msg || errors[code];
\r
444 if (typeof msg !== 'string') msg = '';
\r
446 var type = types['error'];
\r
448 var buffLen = 4 + msg.length + 1;
\r
450 var buff = new Buffer(buffLen);
\r
451 buff.writeUInt16BE(type, 0); // Type as UInt16BE on offset: 0
\r
452 buff.writeUInt16BE(code, 2); // ErrorCode as UInt16BE on offset: 2
\r
453 buff.write(msg, 4, 'ascii'); // ErrMsg as ascii string on offset: 4
\r
454 buff[buffLen - 1] = 0; // Last byte is 0
\r