001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.net.tftp;
019
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.io.InterruptedIOException;
023 import java.io.OutputStream;
024 import java.net.InetAddress;
025 import java.net.SocketException;
026 import java.net.UnknownHostException;
027 import org.apache.commons.net.io.FromNetASCIIOutputStream;
028 import org.apache.commons.net.io.ToNetASCIIInputStream;
029
030 /***
031 * The TFTPClient class encapsulates all the aspects of the TFTP protocol
032 * necessary to receive and send files through TFTP. It is derived from
033 * the {@link org.apache.commons.net.tftp.TFTP} because
034 * it is more convenient than using aggregation, and as a result exposes
035 * the same set of methods to allow you to deal with the TFTP protocol
036 * directly. However, almost every user should only be concerend with the
037 * the {@link org.apache.commons.net.DatagramSocketClient#open open() },
038 * {@link org.apache.commons.net.DatagramSocketClient#close close() },
039 * {@link #sendFile sendFile() }, and
040 * {@link #receiveFile receiveFile() } methods. Additionally, the
041 * {@link #setMaxTimeouts setMaxTimeouts() } and
042 * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() }
043 * methods may be of importance for performance
044 * tuning.
045 * <p>
046 * Details regarding the TFTP protocol and the format of TFTP packets can
047 * be found in RFC 783. But the point of these classes is to keep you
048 * from having to worry about the internals.
049 * <p>
050 * <p>
051 * @see TFTP
052 * @see TFTPPacket
053 * @see TFTPPacketException
054 ***/
055
056 public class TFTPClient extends TFTP
057 {
058 /***
059 * The default number of times a receive attempt is allowed to timeout
060 * before ending attempts to retry the receive and failing. The default
061 * is 5 timeouts.
062 ***/
063 public static final int DEFAULT_MAX_TIMEOUTS = 5;
064
065 /*** The maximum number of timeouts allowed before failing. ***/
066 private int __maxTimeouts;
067
068 /***
069 * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
070 * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
071 * and buffered operations disabled.
072 ***/
073 public TFTPClient()
074 {
075 __maxTimeouts = DEFAULT_MAX_TIMEOUTS;
076 }
077
078 /***
079 * Sets the maximum number of times a receive attempt is allowed to
080 * timeout during a receiveFile() or sendFile() operation before ending
081 * attempts to retry the receive and failing.
082 * The default is DEFAULT_MAX_TIMEOUTS.
083 * <p>
084 * @param numTimeouts The maximum number of timeouts to allow. Values
085 * less than 1 should not be used, but if they are, they are
086 * treated as 1.
087 ***/
088 public void setMaxTimeouts(int numTimeouts)
089 {
090 if (numTimeouts < 1) {
091 __maxTimeouts = 1;
092 } else {
093 __maxTimeouts = numTimeouts;
094 }
095 }
096
097 /***
098 * Returns the maximum number of times a receive attempt is allowed to
099 * timeout before ending attempts to retry the receive and failing.
100 * <p>
101 * @return The maximum number of timeouts allowed.
102 ***/
103 public int getMaxTimeouts()
104 {
105 return __maxTimeouts;
106 }
107
108
109 /***
110 * Requests a named file from a remote host, writes the
111 * file to an OutputStream, closes the connection, and returns the number
112 * of bytes read. A local UDP socket must first be created by
113 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
114 * invoking this method. This method will not close the OutputStream
115 * containing the file; you must close it after the method invocation.
116 * <p>
117 * @param filename The name of the file to receive.
118 * @param mode The TFTP mode of the transfer (one of the MODE constants).
119 * @param output The OutputStream to which the file should be written.
120 * @param host The remote host serving the file.
121 * @param port The port number of the remote TFTP server.
122 * @exception IOException If an I/O error occurs. The nature of the
123 * error will be reported in the message.
124 ***/
125 public int receiveFile(String filename, int mode, OutputStream output,
126 InetAddress host, int port) throws IOException
127 {
128 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength;
129 TFTPPacket sent, received = null;
130 TFTPErrorPacket error;
131 TFTPDataPacket data;
132 TFTPAckPacket ack = new TFTPAckPacket(host, port, 0);
133
134 beginBufferedOps();
135
136 dataLength = lastBlock = hostPort = bytesRead = 0;
137 block = 1;
138
139 if (mode == TFTP.ASCII_MODE) {
140 output = new FromNetASCIIOutputStream(output);
141 }
142
143 sent =
144 new TFTPReadRequestPacket(host, port, filename, mode);
145
146 _sendPacket:
147 do
148 {
149 bufferedSend(sent);
150
151 _receivePacket:
152 while (true)
153 {
154 timeouts = 0;
155 do {
156 try
157 {
158 received = bufferedReceive();
159 break;
160 }
161 catch (SocketException e)
162 {
163 if (++timeouts >= __maxTimeouts)
164 {
165 endBufferedOps();
166 throw new IOException("Connection timed out.");
167 }
168 continue _sendPacket;
169 }
170 catch (InterruptedIOException e)
171 {
172 if (++timeouts >= __maxTimeouts)
173 {
174 endBufferedOps();
175 throw new IOException("Connection timed out.");
176 }
177 continue _sendPacket;
178 }
179 catch (TFTPPacketException e)
180 {
181 endBufferedOps();
182 throw new IOException("Bad packet: " + e.getMessage());
183 }
184 } while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once
185
186 // The first time we receive we get the port number and
187 // answering host address (for hosts with multiple IPs)
188 if (lastBlock == 0)
189 {
190 hostPort = received.getPort();
191 ack.setPort(hostPort);
192 if(!host.equals(received.getAddress()))
193 {
194 host = received.getAddress();
195 ack.setAddress(host);
196 sent.setAddress(host);
197 }
198 }
199
200 // Comply with RFC 783 indication that an error acknowledgement
201 // should be sent to originator if unexpected TID or host.
202 if (host.equals(received.getAddress()) &&
203 received.getPort() == hostPort)
204 {
205
206 switch (received.getType())
207 {
208 case TFTPPacket.ERROR:
209 error = (TFTPErrorPacket)received;
210 endBufferedOps();
211 throw new IOException("Error code " + error.getError() +
212 " received: " + error.getMessage());
213 case TFTPPacket.DATA:
214 data = (TFTPDataPacket)received;
215 dataLength = data.getDataLength();
216
217 lastBlock = data.getBlockNumber();
218
219 if (lastBlock == block)
220 {
221 try
222 {
223 output.write(data.getData(), data.getDataOffset(),
224 dataLength);
225 }
226 catch (IOException e)
227 {
228 error = new TFTPErrorPacket(host, hostPort,
229 TFTPErrorPacket.OUT_OF_SPACE,
230 "File write failed.");
231 bufferedSend(error);
232 endBufferedOps();
233 throw e;
234 }
235 ++block;
236 if (block > 65535)
237 {
238 // wrap the block number
239 block = 0;
240 }
241
242 break _receivePacket;
243 }
244 else
245 {
246 discardPackets();
247
248 if (lastBlock == (block == 0 ? 65535 : (block - 1))) {
249 continue _sendPacket; // Resend last acknowledgement.
250 }
251
252 continue _receivePacket; // Start fetching packets again.
253 }
254 //break;
255
256 default:
257 endBufferedOps();
258 throw new IOException("Received unexpected packet type.");
259 }
260 }
261 else
262 {
263 error = new TFTPErrorPacket(received.getAddress(),
264 received.getPort(),
265 TFTPErrorPacket.UNKNOWN_TID,
266 "Unexpected host or port.");
267 bufferedSend(error);
268 continue _sendPacket;
269 }
270
271 // We should never get here, but this is a safety to avoid
272 // infinite loop. If only Java had the goto statement.
273 //break;
274 }
275
276 ack.setBlockNumber(lastBlock);
277 sent = ack;
278 bytesRead += dataLength;
279 } // First data packet less than 512 bytes signals end of stream.
280
281 while (dataLength == TFTPPacket.SEGMENT_SIZE);
282
283 bufferedSend(sent);
284 endBufferedOps();
285
286 return bytesRead;
287 }
288
289
290 /***
291 * Requests a named file from a remote host, writes the
292 * file to an OutputStream, closes the connection, and returns the number
293 * of bytes read. A local UDP socket must first be created by
294 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
295 * invoking this method. This method will not close the OutputStream
296 * containing the file; you must close it after the method invocation.
297 * <p>
298 * @param filename The name of the file to receive.
299 * @param mode The TFTP mode of the transfer (one of the MODE constants).
300 * @param output The OutputStream to which the file should be written.
301 * @param hostname The name of the remote host serving the file.
302 * @param port The port number of the remote TFTP server.
303 * @exception IOException If an I/O error occurs. The nature of the
304 * error will be reported in the message.
305 * @exception UnknownHostException If the hostname cannot be resolved.
306 ***/
307 public int receiveFile(String filename, int mode, OutputStream output,
308 String hostname, int port)
309 throws UnknownHostException, IOException
310 {
311 return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
312 port);
313 }
314
315
316 /***
317 * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT).
318 *
319 * @param filename The name of the file to receive.
320 * @param mode The TFTP mode of the transfer (one of the MODE constants).
321 * @param output The OutputStream to which the file should be written.
322 * @param host The remote host serving the file.
323 * @exception IOException If an I/O error occurs. The nature of the
324 * error will be reported in the message.
325 ***/
326 public int receiveFile(String filename, int mode, OutputStream output,
327 InetAddress host)
328 throws IOException
329 {
330 return receiveFile(filename, mode, output, host, DEFAULT_PORT);
331 }
332
333 /***
334 * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT).
335 *
336 * @param filename The name of the file to receive.
337 * @param mode The TFTP mode of the transfer (one of the MODE constants).
338 * @param output The OutputStream to which the file should be written.
339 * @param hostname The name of the remote host serving the file.
340 * @exception IOException If an I/O error occurs. The nature of the
341 * error will be reported in the message.
342 * @exception UnknownHostException If the hostname cannot be resolved.
343 ***/
344 public int receiveFile(String filename, int mode, OutputStream output,
345 String hostname)
346 throws UnknownHostException, IOException
347 {
348 return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
349 DEFAULT_PORT);
350 }
351
352
353 /***
354 * Requests to send a file to a remote host, reads the file from an
355 * InputStream, sends the file to the remote host, and closes the
356 * connection. A local UDP socket must first be created by
357 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
358 * invoking this method. This method will not close the InputStream
359 * containing the file; you must close it after the method invocation.
360 * <p>
361 * @param filename The name the remote server should use when creating
362 * the file on its file system.
363 * @param mode The TFTP mode of the transfer (one of the MODE constants).
364 * @param host The remote host receiving the file.
365 * @param port The port number of the remote TFTP server.
366 * @exception IOException If an I/O error occurs. The nature of the
367 * error will be reported in the message.
368 ***/
369 public void sendFile(String filename, int mode, InputStream input,
370 InetAddress host, int port) throws IOException
371 {
372 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset, totalThisPacket;
373 TFTPPacket sent, received = null;
374 TFTPErrorPacket error;
375 TFTPDataPacket data =
376 new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0);
377 TFTPAckPacket ack;
378
379 boolean justStarted = true;
380
381 beginBufferedOps();
382
383 dataLength = lastBlock = hostPort = bytesRead = totalThisPacket = 0;
384 block = 0;
385 boolean lastAckWait = false;
386
387 if (mode == TFTP.ASCII_MODE) {
388 input = new ToNetASCIIInputStream(input);
389 }
390
391 sent =
392 new TFTPWriteRequestPacket(host, port, filename, mode);
393
394 _sendPacket:
395 do
396 {
397 // first time: block is 0, lastBlock is 0, send a request packet.
398 // subsequent: block is integer starting at 1, send data packet.
399 bufferedSend(sent);
400
401 // this is trying to receive an ACK
402 _receivePacket:
403 while (true)
404 {
405
406
407 timeouts = 0;
408 do {
409 try
410 {
411 received = bufferedReceive();
412 break;
413 }
414 catch (SocketException e)
415 {
416 if (++timeouts >= __maxTimeouts)
417 {
418 endBufferedOps();
419 throw new IOException("Connection timed out.");
420 }
421 continue _sendPacket;
422 }
423 catch (InterruptedIOException e)
424 {
425 if (++timeouts >= __maxTimeouts)
426 {
427 endBufferedOps();
428 throw new IOException("Connection timed out.");
429 }
430 continue _sendPacket;
431 }
432 catch (TFTPPacketException e)
433 {
434 endBufferedOps();
435 throw new IOException("Bad packet: " + e.getMessage());
436 }
437 } // end of while loop over tries to receive
438 while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once
439
440
441 // The first time we receive we get the port number and
442 // answering host address (for hosts with multiple IPs)
443 if (justStarted)
444 {
445 justStarted = false;
446 hostPort = received.getPort();
447 data.setPort(hostPort);
448 if(!host.equals(received.getAddress()))
449 {
450 host = received.getAddress();
451 data.setAddress(host);
452 sent.setAddress(host);
453 }
454 }
455
456 // Comply with RFC 783 indication that an error acknowledgement
457 // should be sent to originator if unexpected TID or host.
458 if (host.equals(received.getAddress()) &&
459 received.getPort() == hostPort)
460 {
461
462 switch (received.getType())
463 {
464 case TFTPPacket.ERROR:
465 error = (TFTPErrorPacket)received;
466 endBufferedOps();
467 throw new IOException("Error code " + error.getError() +
468 " received: " + error.getMessage());
469 case TFTPPacket.ACKNOWLEDGEMENT:
470 ack = (TFTPAckPacket)received;
471
472 lastBlock = ack.getBlockNumber();
473
474 if (lastBlock == block)
475 {
476 ++block;
477 if (block > 65535)
478 {
479 // wrap the block number
480 block = 0;
481 }
482 if (lastAckWait) {
483
484 break _sendPacket;
485 }
486 else {
487 break _receivePacket;
488 }
489 }
490 else
491 {
492 discardPackets();
493
494 continue _receivePacket; // Start fetching packets again.
495 }
496 //break;
497
498 default:
499 endBufferedOps();
500 throw new IOException("Received unexpected packet type.");
501 }
502 }
503 else
504 {
505 error = new TFTPErrorPacket(received.getAddress(),
506 received.getPort(),
507 TFTPErrorPacket.UNKNOWN_TID,
508 "Unexpected host or port.");
509 bufferedSend(error);
510 continue _sendPacket;
511 }
512
513 // We should never get here, but this is a safety to avoid
514 // infinite loop. If only Java had the goto statement.
515 //break;
516 }
517
518 // OK, we have just gotten ACK about the last data we sent. Make another
519 // and send it
520
521 dataLength = TFTPPacket.SEGMENT_SIZE;
522 offset = 4;
523 totalThisPacket = 0;
524 while (dataLength > 0 &&
525 (bytesRead = input.read(_sendBuffer, offset, dataLength)) > 0)
526 {
527 offset += bytesRead;
528 dataLength -= bytesRead;
529 totalThisPacket += bytesRead;
530 }
531
532 if( totalThisPacket < TFTPPacket.SEGMENT_SIZE ) {
533 /* this will be our last packet -- send, wait for ack, stop */
534 lastAckWait = true;
535 }
536 data.setBlockNumber(block);
537 data.setData(_sendBuffer, 4, totalThisPacket);
538 sent = data;
539 }
540 while ( totalThisPacket > 0 || lastAckWait );
541 // Note: this was looping while dataLength == 0 || lastAckWait,
542 // which was discarding the last packet if it was not full size
543 // Should send the packet.
544
545 endBufferedOps();
546 }
547
548
549 /***
550 * Requests to send a file to a remote host, reads the file from an
551 * InputStream, sends the file to the remote host, and closes the
552 * connection. A local UDP socket must first be created by
553 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
554 * invoking this method. This method will not close the InputStream
555 * containing the file; you must close it after the method invocation.
556 * <p>
557 * @param filename The name the remote server should use when creating
558 * the file on its file system.
559 * @param mode The TFTP mode of the transfer (one of the MODE constants).
560 * @param hostname The name of the remote host receiving the file.
561 * @param port The port number of the remote TFTP server.
562 * @exception IOException If an I/O error occurs. The nature of the
563 * error will be reported in the message.
564 * @exception UnknownHostException If the hostname cannot be resolved.
565 ***/
566 public void sendFile(String filename, int mode, InputStream input,
567 String hostname, int port)
568 throws UnknownHostException, IOException
569 {
570 sendFile(filename, mode, input, InetAddress.getByName(hostname), port);
571 }
572
573
574 /***
575 * Same as calling sendFile(filename, mode, input, host, TFTP.DEFAULT_PORT).
576 *
577 * @param filename The name the remote server should use when creating
578 * the file on its file system.
579 * @param mode The TFTP mode of the transfer (one of the MODE constants).
580 * @param host The name of the remote host receiving the file.
581 * @exception IOException If an I/O error occurs. The nature of the
582 * error will be reported in the message.
583 * @exception UnknownHostException If the hostname cannot be resolved.
584 ***/
585 public void sendFile(String filename, int mode, InputStream input,
586 InetAddress host)
587 throws IOException
588 {
589 sendFile(filename, mode, input, host, DEFAULT_PORT);
590 }
591
592 /***
593 * Same as calling sendFile(filename, mode, input, hostname, TFTP.DEFAULT_PORT).
594 *
595 * @param filename The name the remote server should use when creating
596 * the file on its file system.
597 * @param mode The TFTP mode of the transfer (one of the MODE constants).
598 * @param hostname The name of the remote host receiving the file.
599 * @exception IOException If an I/O error occurs. The nature of the
600 * error will be reported in the message.
601 * @exception UnknownHostException If the hostname cannot be resolved.
602 ***/
603 public void sendFile(String filename, int mode, InputStream input,
604 String hostname)
605 throws UnknownHostException, IOException
606 {
607 sendFile(filename, mode, input, InetAddress.getByName(hostname),
608 DEFAULT_PORT);
609 }
610 }