adding the eclipse .project file
[random_node_code.git] / tftp / node_modules / tftp-client / lib / tftp.js
1 var dgram = require('dgram');   // UDP (datagram)\r
2 \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
7 \r
8 // type: Opcode\r
9 var types = {\r
10         'rrq': 1, // Read ReQuest\r
11         'wrq': 2, // Write ReQuest\r
12         'data':3,\r
13         'ack': 4,\r
14         'err': 5,\r
15 \r
16         // aliases:\r
17         'read':1,\r
18         'write':2\r
19 }\r
20 \r
21 // Error strings ([errorCode] = errorString)\r
22 var errors = [\r
23         'Not defined, see error message (if any).',\r
24         'File not found.',\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
30         'No such user.'\r
31 ];\r
32 \r
33 /**\r
34  * Constructor\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
37  */\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
41 \r
42         // Hold the current operation\r
43         this.current = false;\r
44         // Holds the queue of the next operations\r
45         this.queue = [];\r
46 \r
47         // Client will be created when needed\r
48         this.client = null;\r
49 \r
50         // Display debug messages?\r
51         this.debug = false;\r
52 }\r
53 \r
54 TFTP.prototype.createClient = function() {\r
55         if (this.client !== null)\r
56                 return;\r
57 \r
58         // We need this inside onMessage...\r
59         var self = this;\r
60         \r
61         // Called when getting an message from the server\r
62         var onMessage = function(msg, rinfo) {\r
63 \r
64                 // Type of message (see `types` above)\r
65                 var type  = msg.readUInt16BE(0);\r
66 \r
67                 switch (type) {\r
68                         // Data - Respond with Ack\r
69                         case types.data:\r
70                                 var block = msg.readUInt16BE(2);\r
71                                 var data = msg.slice(4); // From byte 4 is data\r
72 \r
73                                 if (self.debug) {\r
74                                         console.log('> Received data. Block#: %d Bytes: %d', block, data.length);\r
75                                         console.log('< Sending ACK. Block#: %d', block);\r
76                                 }\r
77 \r
78                                 // Concat the two buffers\r
79                                 self.current.data = Buffer.concat([self.current.data, data]);\r
80 \r
81                                 // Create and send ACK packet\r
82                                 var ack = createAck(block);\r
83                                 self.send(ack);\r
84 \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
89 \r
90                                         // Go to next operation in queue\r
91                                         self.next();\r
92                                 }\r
93                                 break;\r
94                         // Ack - Respond with Data\r
95                         case types.ack:\r
96                                 var block = msg.readUInt16BE(2);\r
97 \r
98                                 if (self.debug)\r
99                                         console.log('> Received ACK. Block#: %d', block);\r
100 \r
101                                 // If this is the ack for the last block\r
102                                 if (block == self.current.blocks) {\r
103                                         // Send callback\r
104                                         self.current.callback(null, self.current.data.length);\r
105 \r
106                                         // Go to next operation in queue\r
107                                         self.next();\r
108 \r
109                                         // Break out of switch\r
110                                         break;\r
111                                 }\r
112 \r
113                                 if (self.debug)\r
114                                         console.log('< Sending data. Block#: %d', block + 1);\r
115 \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
121 \r
122                                 var packet = createData(block+1, data);\r
123 \r
124                                 // Send data block\r
125                                 self.send(packet);\r
126                                 break;\r
127                         // Error\r
128                         case types.err:\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
135 \r
136                                 if (self.debug)\r
137                                         console.log('> Received Error. code: %d - msg: %s', errorCode, ErrMsg);\r
138 \r
139                                 // Callback\r
140                                 self.current.callback(err);\r
141 \r
142                                 // Go to next operation in queue\r
143                                 self.next();\r
144                                 break;\r
145                 }\r
146         }\r
147 \r
148         // Create socket, and listen to messages\r
149         this.client = dgram.createSocket("udp4", onMessage);\r
150 };\r
151 \r
152 /**\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
156  */\r
157 TFTP.prototype.send = function(buff, cb) {\r
158         if (typeof cb !== 'function') cb = function() {};\r
159 \r
160         // We need this later\r
161         var self = this;\r
162         \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
166         };\r
167         // Send the packet already\r
168         send();\r
169 \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
173 \r
174                         if (self.debug)\r
175                                 console.log('! Timeout - Retransmitting (%d bytes)', buff.length);\r
176 \r
177                         // Send the packet again\r
178                         send();\r
179 \r
180                         // Set a new timeout for the packet we just sent\r
181                         self.current.timeout.id = setTimeout(onTimeout, TIMEOUT);\r
182                 }\r
183 \r
184                 // If we sent too many retransmitts, the remote server did not respond...\r
185                 else {\r
186                         if (self.debug)\r
187                                 console.log('! Timeout - End');\r
188 \r
189                         // Create error\r
190                         var err = new Error("Timeout");\r
191                         err.code = 0;\r
192 \r
193                         self.current.callback(err);\r
194                         // Reset current, and check queue - (no answer from server)\r
195                         self.next();\r
196                 }\r
197         }\r
198 \r
199         // If there is a timeout, clear the timeout\r
200         if (this.current.timeout)\r
201                 clearTimeout(this.current.timeout.id);\r
202 \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
206                 retransmits: 0\r
207         }\r
208 };\r
209 \r
210 /**\r
211  * Goes to next item in queue (if any). Same as `check()`, but clears current first\r
212  */\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
217 \r
218         // Reset current, and check queue\r
219         this.current = false;\r
220         this.check();\r
221 };\r
222 \r
223 /**\r
224  * Checks the Queue.\r
225  * If no operation is currently active, go to the next item in the queue (if any).\r
226  */\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
231                 return;\r
232 \r
233         // If there is nothing running, and the queue is empty\r
234         if (this.queue.length == 0) {\r
235                 // Close client\r
236                 this.client.close();\r
237                 return;\r
238         } else {\r
239                 // Create client\r
240                 this.createClient();\r
241         }\r
242 \r
243         // Take the first item in queue\r
244         this.current = this.queue.shift();\r
245 \r
246         // We need this later\r
247         var self = this;\r
248 \r
249 \r
250         if (self.debug)\r
251                 console.log('< Sending request');\r
252 \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
258                 if (err) {\r
259                         // Create Error message\r
260                         var err = new Error(['Error when sending ',\r
261                                                                 self.current.type,\r
262                                                                 ' request for ',\r
263                                                                 self.current.filename].join());\r
264 \r
265                         // Send error to the callback of the current operation\r
266                         self.current.callback(err);\r
267 \r
268                         // Reset current\r
269                         self.current = false;\r
270 \r
271                         // Then check queue\r
272                         self.check();\r
273                 }\r
274         });\r
275 };\r
276 \r
277 /**\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
282  */\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
286 \r
287         // Item to put into the queue\r
288         var queueItem = {\r
289                 type: 'write',\r
290                 filename: filename,\r
291                 callback: cb,\r
292                 data: data,\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
295         };\r
296 \r
297         // Push the queueItem to the end of the queue.\r
298         this.queue.push(queueItem);\r
299 \r
300         // Check the queue...\r
301         this.check();\r
302 };\r
303 \r
304 /**\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
308  */\r
309 TFTP.prototype.read = function(filename, cb) {\r
310         if (typeof cb !== 'function') cb = function() {}; // Default cb to an empty function\r
311 \r
312         // Item to put into the queue\r
313         var queueItem = {\r
314                 type: 'read',\r
315                 filename: filename,\r
316                 callback: cb,\r
317                 data: new Buffer(0) // Buffer of size 0 which will be filled up by onMessage\r
318         };\r
319 \r
320         // Push the queueItem to the end of the queue.\r
321         this.queue.push(queueItem);\r
322 \r
323         // Check the queue...\r
324         this.check();\r
325 };\r
326 \r
327 /**\r
328  * Creates a buffer for a request.\r
329 \r
330    2 bytes     string    1 byte     string   1 byte\r
331    ------------------------------------------------\r
332   | Opcode |  Filename  |   0  |    Mode    |   0  |\r
333    ------------------------------------------------\r
334 \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
339  */\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
344 \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
349                 else\r
350                         throw 'Unknown type (' + type + ')';\r
351         }\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
355 \r
356         // Default mode to 'octet'\r
357         mode = mode || 'octet';\r
358 \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
362 \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
371 \r
372         // Make sure the ends of the strings are null\r
373         buff[2 + filename.length] = buff[buffLen - 1] = 0;\r
374 \r
375         // Return the new buffer\r
376         return buff;\r
377 }\r
378 \r
379 /**\r
380  * Creates a buffer for a data packet\r
381 \r
382    2 bytes     2 bytes      n bytes\r
383    ----------------------------------\r
384   | Opcode |   Block #  |   Data     |\r
385    ----------------------------------\r
386 \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
390  */\r
391 function createData(blockNumber, data) {\r
392         var type = types['data'];\r
393 \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
397 \r
398         var buff = new Buffer(buffLen);\r
399 \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
404 \r
405         return buff;\r
406 }\r
407 \r
408 /**\r
409  * Creates a buffer for a ACK packet\r
410 \r
411    2 bytes     2 bytes\r
412    ---------------------\r
413   | Opcode |   Block #  |\r
414    ---------------------\r
415 \r
416  * @param  {Number} blockNumber Which block to ack\r
417  * @return {Buffer}             The Buffer\r
418  */\r
419 function createAck(blockNumber) {\r
420         var type = types['ack'];\r
421 \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
425 \r
426         return buff;\r
427 }\r
428 \r
429 /**\r
430  * Creates a buffer for an ERROR packet\r
431 \r
432    2 bytes     2 bytes      string    1 byte\r
433    -----------------------------------------\r
434   | Opcode |  ErrorCode |   ErrMsg   |   0  |\r
435    -----------------------------------------\r
436 \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
440  */\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
445 \r
446         var type = types['error'];\r
447 \r
448         var buffLen = 4 + msg.length + 1;\r
449 \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
455 \r
456         return buff;\r
457 }\r