1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.net.tftp;
17
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.InterruptedIOException;
21 import java.io.OutputStream;
22 import java.net.InetAddress;
23 import java.net.SocketException;
24 import java.net.UnknownHostException;
25 import org.apache.commons.net.io.FromNetASCIIOutputStream;
26 import org.apache.commons.net.io.ToNetASCIIInputStream;
27
28 /***
29 * The TFTPClient class encapsulates all the aspects of the TFTP protocol
30 * necessary to receive and send files through TFTP. It is derived from
31 * the {@link org.apache.commons.net.tftp.TFTP} because
32 * it is more convenient than using aggregation, and as a result exposes
33 * the same set of methods to allow you to deal with the TFTP protocol
34 * directly. However, almost every user should only be concerend with the
35 * the {@link org.apache.commons.net.DatagramSocketClient#open open() },
36 * {@link org.apache.commons.net.DatagramSocketClient#close close() },
37 * {@link #sendFile sendFile() }, and
38 * {@link #receiveFile receiveFile() } methods. Additionally, the
39 * {@link #setMaxTimeouts setMaxTimeouts() } and
40 * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() }
41 * methods may be of importance for performance
42 * tuning.
43 * <p>
44 * Details regarding the TFTP protocol and the format of TFTP packets can
45 * be found in RFC 783. But the point of these classes is to keep you
46 * from having to worry about the internals.
47 * <p>
48 * <p>
49 * @author Daniel F. Savarese
50 * @see TFTP
51 * @see TFTPPacket
52 * @see TFTPPacketException
53 ***/
54
55 public class TFTPClient extends TFTP
56 {
57 /***
58 * The default number of times a receive attempt is allowed to timeout
59 * before ending attempts to retry the receive and failing. The default
60 * is 5 timeouts.
61 ***/
62 public static final int DEFAULT_MAX_TIMEOUTS = 5;
63
64 /*** The maximum number of timeouts allowed before failing. ***/
65 private int __maxTimeouts;
66
67 /***
68 * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
69 * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
70 * and buffered operations disabled.
71 ***/
72 public TFTPClient()
73 {
74 __maxTimeouts = DEFAULT_MAX_TIMEOUTS;
75 }
76
77 /***
78 * Sets the maximum number of times a receive attempt is allowed to
79 * timeout during a receiveFile() or sendFile() operation before ending
80 * attempts to retry the receive and failing.
81 * The default is DEFAULT_MAX_TIMEOUTS.
82 * <p>
83 * @param numTimeouts The maximum number of timeouts to allow. Values
84 * less than 1 should not be used, but if they are, they are
85 * treated as 1.
86 ***/
87 public void setMaxTimeouts(int numTimeouts)
88 {
89 if (numTimeouts < 1)
90 __maxTimeouts = 1;
91 else
92 __maxTimeouts = numTimeouts;
93 }
94
95 /***
96 * Returns the maximum number of times a receive attempt is allowed to
97 * timeout before ending attempts to retry the receive and failing.
98 * <p>
99 * @return The maximum number of timeouts allowed.
100 ***/
101 public int getMaxTimeouts()
102 {
103 return __maxTimeouts;
104 }
105
106
107 /***
108 * Requests a named file from a remote host, writes the
109 * file to an OutputStream, closes the connection, and returns the number
110 * of bytes read. A local UDP socket must first be created by
111 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
112 * invoking this method. This method will not close the OutputStream
113 * containing the file; you must close it after the method invocation.
114 * <p>
115 * @param filename The name of the file to receive.
116 * @param mode The TFTP mode of the transfer (one of the MODE constants).
117 * @param output The OutputStream to which the file should be written.
118 * @param host The remote host serving the file.
119 * @param port The port number of the remote TFTP server.
120 * @exception IOException If an I/O error occurs. The nature of the
121 * error will be reported in the message.
122 ***/
123 public int receiveFile(String filename, int mode, OutputStream output,
124 InetAddress host, int port) throws IOException
125 {
126 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength;
127 TFTPPacket sent, received = null;
128 TFTPErrorPacket error;
129 TFTPDataPacket data;
130 TFTPAckPacket ack = new TFTPAckPacket(host, port, 0);
131
132 beginBufferedOps();
133
134 dataLength = lastBlock = hostPort = bytesRead = 0;
135 block = 1;
136
137 if (mode == TFTP.ASCII_MODE)
138 output = new FromNetASCIIOutputStream(output);
139
140 sent =
141 new TFTPReadRequestPacket(host, port, filename, mode);
142
143 _sendPacket:
144 do
145 {
146 bufferedSend(sent);
147
148 _receivePacket:
149 while (true)
150 {
151 timeouts = 0;
152 while (timeouts < __maxTimeouts)
153 {
154 try
155 {
156 received = bufferedReceive();
157 break;
158 }
159 catch (SocketException e)
160 {
161 if (++timeouts >= __maxTimeouts)
162 {
163 endBufferedOps();
164 throw new IOException("Connection timed out.");
165 }
166 continue;
167 }
168 catch (InterruptedIOException e)
169 {
170 if (++timeouts >= __maxTimeouts)
171 {
172 endBufferedOps();
173 throw new IOException("Connection timed out.");
174 }
175 continue;
176 }
177 catch (TFTPPacketException e)
178 {
179 endBufferedOps();
180 throw new IOException("Bad packet: " + e.getMessage());
181 }
182 }
183
184
185
186 if (lastBlock == 0)
187 {
188 hostPort = received.getPort();
189 ack.setPort(hostPort);
190 if(!host.equals(received.getAddress()))
191 {
192 host = received.getAddress();
193 ack.setAddress(host);
194 sent.setAddress(host);
195 }
196 }
197
198
199
200 if (host.equals(received.getAddress()) &&
201 received.getPort() == hostPort)
202 {
203
204 switch (received.getType())
205 {
206 case TFTPPacket.ERROR:
207 error = (TFTPErrorPacket)received;
208 endBufferedOps();
209 throw new IOException("Error code " + error.getError() +
210 " received: " + error.getMessage());
211 case TFTPPacket.DATA:
212 data = (TFTPDataPacket)received;
213 dataLength = data.getDataLength();
214
215 lastBlock = data.getBlockNumber();
216
217 if (lastBlock == block)
218 {
219 try
220 {
221 output.write(data.getData(), data.getDataOffset(),
222 dataLength);
223 }
224 catch (IOException e)
225 {
226 error = new TFTPErrorPacket(host, hostPort,
227 TFTPErrorPacket.OUT_OF_SPACE,
228 "File write failed.");
229 bufferedSend(error);
230 endBufferedOps();
231 throw e;
232 }
233 ++block;
234 break _receivePacket;
235 }
236 else
237 {
238 discardPackets();
239
240 if (lastBlock == (block - 1))
241 continue _sendPacket;
242
243 continue _receivePacket;
244 }
245
246
247 default:
248 endBufferedOps();
249 throw new IOException("Received unexpected packet type.");
250 }
251 }
252 else
253 {
254 error = new TFTPErrorPacket(received.getAddress(),
255 received.getPort(),
256 TFTPErrorPacket.UNKNOWN_TID,
257 "Unexpected host or port.");
258 bufferedSend(error);
259 continue _sendPacket;
260 }
261
262
263
264
265 }
266
267 ack.setBlockNumber(lastBlock);
268 sent = ack;
269 bytesRead += dataLength;
270 }
271
272 while (dataLength == TFTPPacket.SEGMENT_SIZE);
273
274 bufferedSend(sent);
275 endBufferedOps();
276
277 return bytesRead;
278 }
279
280
281 /***
282 * Requests a named file from a remote host, writes the
283 * file to an OutputStream, closes the connection, and returns the number
284 * of bytes read. A local UDP socket must first be created by
285 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
286 * invoking this method. This method will not close the OutputStream
287 * containing the file; you must close it after the method invocation.
288 * <p>
289 * @param filename The name of the file to receive.
290 * @param mode The TFTP mode of the transfer (one of the MODE constants).
291 * @param output The OutputStream to which the file should be written.
292 * @param hostname The name of the remote host serving the file.
293 * @param port The port number of the remote TFTP server.
294 * @exception IOException If an I/O error occurs. The nature of the
295 * error will be reported in the message.
296 * @exception UnknownHostException If the hostname cannot be resolved.
297 ***/
298 public int receiveFile(String filename, int mode, OutputStream output,
299 String hostname, int port)
300 throws UnknownHostException, IOException
301 {
302 return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
303 port);
304 }
305
306
307 /***
308 * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT).
309 *
310 * @param filename The name of the file to receive.
311 * @param mode The TFTP mode of the transfer (one of the MODE constants).
312 * @param output The OutputStream to which the file should be written.
313 * @param host The remote host serving the file.
314 * @exception IOException If an I/O error occurs. The nature of the
315 * error will be reported in the message.
316 ***/
317 public int receiveFile(String filename, int mode, OutputStream output,
318 InetAddress host)
319 throws IOException
320 {
321 return receiveFile(filename, mode, output, host, DEFAULT_PORT);
322 }
323
324 /***
325 * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT).
326 *
327 * @param filename The name of the file to receive.
328 * @param mode The TFTP mode of the transfer (one of the MODE constants).
329 * @param output The OutputStream to which the file should be written.
330 * @param hostname The name of the remote host serving the file.
331 * @exception IOException If an I/O error occurs. The nature of the
332 * error will be reported in the message.
333 * @exception UnknownHostException If the hostname cannot be resolved.
334 ***/
335 public int receiveFile(String filename, int mode, OutputStream output,
336 String hostname)
337 throws UnknownHostException, IOException
338 {
339 return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
340 DEFAULT_PORT);
341 }
342
343
344 /***
345 * Requests to send a file to a remote host, reads the file from an
346 * InputStream, sends the file to the remote host, and closes the
347 * connection. A local UDP socket must first be created by
348 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
349 * invoking this method. This method will not close the InputStream
350 * containing the file; you must close it after the method invocation.
351 * <p>
352 * @param filename The name the remote server should use when creating
353 * the file on its file system.
354 * @param mode The TFTP mode of the transfer (one of the MODE constants).
355 * @param host The remote host receiving the file.
356 * @param port The port number of the remote TFTP server.
357 * @exception IOException If an I/O error occurs. The nature of the
358 * error will be reported in the message.
359 ***/
360 public void sendFile(String filename, int mode, InputStream input,
361 InetAddress host, int port) throws IOException
362 {
363 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset;
364 TFTPPacket sent, received = null;
365 TFTPErrorPacket error;
366 TFTPDataPacket data =
367 new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0);
368 ;
369 TFTPAckPacket ack;
370
371 beginBufferedOps();
372
373 dataLength = lastBlock = hostPort = bytesRead = 0;
374 block = 0;
375 boolean lastAckWait = false;
376
377 if (mode == TFTP.ASCII_MODE)
378 input = new ToNetASCIIInputStream(input);
379
380 sent =
381 new TFTPWriteRequestPacket(host, port, filename, mode);
382
383 _sendPacket:
384 do
385 {
386 bufferedSend(sent);
387
388 _receivePacket:
389 while (true)
390 {
391 timeouts = 0;
392 while (timeouts < __maxTimeouts)
393 {
394 try
395 {
396 received = bufferedReceive();
397 break;
398 }
399 catch (SocketException e)
400 {
401 if (++timeouts >= __maxTimeouts)
402 {
403 endBufferedOps();
404 throw new IOException("Connection timed out.");
405 }
406 continue;
407 }
408 catch (InterruptedIOException e)
409 {
410 if (++timeouts >= __maxTimeouts)
411 {
412 endBufferedOps();
413 throw new IOException("Connection timed out.");
414 }
415 continue;
416 }
417 catch (TFTPPacketException e)
418 {
419 endBufferedOps();
420 throw new IOException("Bad packet: " + e.getMessage());
421 }
422 }
423
424
425
426 if (lastBlock == 0)
427 {
428 hostPort = received.getPort();
429 data.setPort(hostPort);
430 if(!host.equals(received.getAddress()))
431 {
432 host = received.getAddress();
433 data.setAddress(host);
434 sent.setAddress(host);
435 }
436 }
437
438
439
440 if (host.equals(received.getAddress()) &&
441 received.getPort() == hostPort)
442 {
443
444 switch (received.getType())
445 {
446 case TFTPPacket.ERROR:
447 error = (TFTPErrorPacket)received;
448 endBufferedOps();
449 throw new IOException("Error code " + error.getError() +
450 " received: " + error.getMessage());
451 case TFTPPacket.ACKNOWLEDGEMENT:
452 ack = (TFTPAckPacket)received;
453
454 lastBlock = ack.getBlockNumber();
455
456 if (lastBlock == block)
457 {
458 ++block;
459 if (lastAckWait)
460 break _sendPacket;
461 else
462 break _receivePacket;
463 }
464 else
465 {
466 discardPackets();
467
468 if (lastBlock == (block - 1))
469 continue _sendPacket;
470
471 continue _receivePacket;
472 }
473
474
475 default:
476 endBufferedOps();
477 throw new IOException("Received unexpected packet type.");
478 }
479 }
480 else
481 {
482 error = new TFTPErrorPacket(received.getAddress(),
483 received.getPort(),
484 TFTPErrorPacket.UNKNOWN_TID,
485 "Unexpected host or port.");
486 bufferedSend(error);
487 continue _sendPacket;
488 }
489
490
491
492
493 }
494
495 dataLength = TFTPPacket.SEGMENT_SIZE;
496 offset = 4;
497 while (dataLength > 0 &&
498 (bytesRead = input.read(_sendBuffer, offset, dataLength)) > 0)
499 {
500 offset += bytesRead;
501 dataLength -= bytesRead;
502 }
503
504 data.setBlockNumber(block);
505 data.setData(_sendBuffer, 4, offset - 4);
506 sent = data;
507 }
508 while (dataLength == 0 || lastAckWait);
509
510 endBufferedOps();
511 }
512
513
514 /***
515 * Requests to send a file to a remote host, reads the file from an
516 * InputStream, sends the file to the remote host, and closes the
517 * connection. A local UDP socket must first be created by
518 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
519 * invoking this method. This method will not close the InputStream
520 * containing the file; you must close it after the method invocation.
521 * <p>
522 * @param filename The name the remote server should use when creating
523 * the file on its file system.
524 * @param mode The TFTP mode of the transfer (one of the MODE constants).
525 * @param hostname The name of the remote host receiving the file.
526 * @param port The port number of the remote TFTP server.
527 * @exception IOException If an I/O error occurs. The nature of the
528 * error will be reported in the message.
529 * @exception UnknownHostException If the hostname cannot be resolved.
530 ***/
531 public void sendFile(String filename, int mode, InputStream input,
532 String hostname, int port)
533 throws UnknownHostException, IOException
534 {
535 sendFile(filename, mode, input, InetAddress.getByName(hostname), port);
536 }
537
538
539 /***
540 * Same as calling sendFile(filename, mode, input, host, TFTP.DEFAULT_PORT).
541 *
542 * @param filename The name the remote server should use when creating
543 * the file on its file system.
544 * @param mode The TFTP mode of the transfer (one of the MODE constants).
545 * @param host The name of the remote host receiving the file.
546 * @exception IOException If an I/O error occurs. The nature of the
547 * error will be reported in the message.
548 * @exception UnknownHostException If the hostname cannot be resolved.
549 ***/
550 public void sendFile(String filename, int mode, InputStream input,
551 InetAddress host)
552 throws IOException
553 {
554 sendFile(filename, mode, input, host, DEFAULT_PORT);
555 }
556
557 /***
558 * Same as calling sendFile(filename, mode, input, hostname, TFTP.DEFAULT_PORT).
559 *
560 * @param filename The name the remote server should use when creating
561 * the file on its file system.
562 * @param mode The TFTP mode of the transfer (one of the MODE constants).
563 * @param hostname The name of the remote host receiving the file.
564 * @exception IOException If an I/O error occurs. The nature of the
565 * error will be reported in the message.
566 * @exception UnknownHostException If the hostname cannot be resolved.
567 ***/
568 public void sendFile(String filename, int mode, InputStream input,
569 String hostname)
570 throws UnknownHostException, IOException
571 {
572 sendFile(filename, mode, input, InetAddress.getByName(hostname),
573 DEFAULT_PORT);
574 }
575 }