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 * @author Daniel F. Savarese
052 * @see TFTP
053 * @see TFTPPacket
054 * @see TFTPPacketException
055 ***/
056
057 public class TFTPClient extends TFTP
058 {
059 /***
060 * The default number of times a receive attempt is allowed to timeout
061 * before ending attempts to retry the receive and failing. The default
062 * is 5 timeouts.
063 ***/
064 public static final int DEFAULT_MAX_TIMEOUTS = 5;
065
066 /*** The maximum number of timeouts allowed before failing. ***/
067 private int __maxTimeouts;
068
069 /***
070 * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
071 * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
072 * and buffered operations disabled.
073 ***/
074 public TFTPClient()
075 {
076 __maxTimeouts = DEFAULT_MAX_TIMEOUTS;
077 }
078
079 /***
080 * Sets the maximum number of times a receive attempt is allowed to
081 * timeout during a receiveFile() or sendFile() operation before ending
082 * attempts to retry the receive and failing.
083 * The default is DEFAULT_MAX_TIMEOUTS.
084 * <p>
085 * @param numTimeouts The maximum number of timeouts to allow. Values
086 * less than 1 should not be used, but if they are, they are
087 * treated as 1.
088 ***/
089 public void setMaxTimeouts(int numTimeouts)
090 {
091 if (numTimeouts < 1)
092 __maxTimeouts = 1;
093 else
094 __maxTimeouts = numTimeouts;
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 sent =
143 new TFTPReadRequestPacket(host, port, filename, mode);
144
145 _sendPacket:
146 do
147 {
148 bufferedSend(sent);
149
150 _receivePacket:
151 while (true)
152 {
153 timeouts = 0;
154 while (timeouts < __maxTimeouts)
155 {
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;
169 }
170 catch (InterruptedIOException e)
171 {
172 if (++timeouts >= __maxTimeouts)
173 {
174 endBufferedOps();
175 throw new IOException("Connection timed out.");
176 }
177 continue;
178 }
179 catch (TFTPPacketException e)
180 {
181 endBufferedOps();
182 throw new IOException("Bad packet: " + e.getMessage());
183 }
184 }
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 continue _receivePacket; // Start fetching packets again.
252 }
253 //break;
254
255 default:
256 endBufferedOps();
257 throw new IOException("Received unexpected packet type.");
258 }
259 }
260 else
261 {
262 error = new TFTPErrorPacket(received.getAddress(),
263 received.getPort(),
264 TFTPErrorPacket.UNKNOWN_TID,
265 "Unexpected host or port.");
266 bufferedSend(error);
267 continue _sendPacket;
268 }
269
270 // We should never get here, but this is a safety to avoid
271 // infinite loop. If only Java had the goto statement.
272 //break;
273 }
274
275 ack.setBlockNumber(lastBlock);
276 sent = ack;
277 bytesRead += dataLength;
278 } // First data packet less than 512 bytes signals end of stream.
279
280 while (dataLength == TFTPPacket.SEGMENT_SIZE);
281
282 bufferedSend(sent);
283 endBufferedOps();
284
285 return bytesRead;
286 }
287
288
289 /***
290 * Requests a named file from a remote host, writes the
291 * file to an OutputStream, closes the connection, and returns the number
292 * of bytes read. A local UDP socket must first be created by
293 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
294 * invoking this method. This method will not close the OutputStream
295 * containing the file; you must close it after the method invocation.
296 * <p>
297 * @param filename The name of the file to receive.
298 * @param mode The TFTP mode of the transfer (one of the MODE constants).
299 * @param output The OutputStream to which the file should be written.
300 * @param hostname The name of the remote host serving the file.
301 * @param port The port number of the remote TFTP server.
302 * @exception IOException If an I/O error occurs. The nature of the
303 * error will be reported in the message.
304 * @exception UnknownHostException If the hostname cannot be resolved.
305 ***/
306 public int receiveFile(String filename, int mode, OutputStream output,
307 String hostname, int port)
308 throws UnknownHostException, IOException
309 {
310 return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
311 port);
312 }
313
314
315 /***
316 * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT).
317 *
318 * @param filename The name of the file to receive.
319 * @param mode The TFTP mode of the transfer (one of the MODE constants).
320 * @param output The OutputStream to which the file should be written.
321 * @param host The remote host serving the file.
322 * @exception IOException If an I/O error occurs. The nature of the
323 * error will be reported in the message.
324 ***/
325 public int receiveFile(String filename, int mode, OutputStream output,
326 InetAddress host)
327 throws IOException
328 {
329 return receiveFile(filename, mode, output, host, DEFAULT_PORT);
330 }
331
332 /***
333 * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT).
334 *
335 * @param filename The name of the file to receive.
336 * @param mode The TFTP mode of the transfer (one of the MODE constants).
337 * @param output The OutputStream to which the file should be written.
338 * @param hostname The name of the remote host serving the file.
339 * @exception IOException If an I/O error occurs. The nature of the
340 * error will be reported in the message.
341 * @exception UnknownHostException If the hostname cannot be resolved.
342 ***/
343 public int receiveFile(String filename, int mode, OutputStream output,
344 String hostname)
345 throws UnknownHostException, IOException
346 {
347 return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
348 DEFAULT_PORT);
349 }
350
351
352 /***
353 * Requests to send a file to a remote host, reads the file from an
354 * InputStream, sends the file to the remote host, and closes the
355 * connection. A local UDP socket must first be created by
356 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
357 * invoking this method. This method will not close the InputStream
358 * containing the file; you must close it after the method invocation.
359 * <p>
360 * @param filename The name the remote server should use when creating
361 * the file on its file system.
362 * @param mode The TFTP mode of the transfer (one of the MODE constants).
363 * @param host The remote host receiving the file.
364 * @param port The port number of the remote TFTP server.
365 * @exception IOException If an I/O error occurs. The nature of the
366 * error will be reported in the message.
367 ***/
368 public void sendFile(String filename, int mode, InputStream input,
369 InetAddress host, int port) throws IOException
370 {
371 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset, totalThisPacket;
372 TFTPPacket sent, received = null;
373 TFTPErrorPacket error;
374 TFTPDataPacket data =
375 new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0);
376 TFTPAckPacket ack;
377
378 boolean justStarted = true;
379
380 beginBufferedOps();
381
382 dataLength = lastBlock = hostPort = bytesRead = totalThisPacket = 0;
383 block = 0;
384 boolean lastAckWait = false;
385
386 if (mode == TFTP.ASCII_MODE)
387 input = new ToNetASCIIInputStream(input);
388
389 sent =
390 new TFTPWriteRequestPacket(host, port, filename, mode);
391
392 _sendPacket:
393 do
394 {
395 // first time: block is 0, lastBlock is 0, send a request packet.
396 // subsequent: block is integer starting at 1, send data packet.
397 bufferedSend(sent);
398
399 // this is trying to receive an ACK
400 _receivePacket:
401 while (true)
402 {
403
404
405 timeouts = 0;
406 while (timeouts < __maxTimeouts)
407 {
408 try
409 {
410 received = bufferedReceive();
411 break;
412 }
413 catch (SocketException e)
414 {
415 if (++timeouts >= __maxTimeouts)
416 {
417 endBufferedOps();
418 throw new IOException("Connection timed out.");
419 }
420 continue;
421 }
422 catch (InterruptedIOException e)
423 {
424 if (++timeouts >= __maxTimeouts)
425 {
426 endBufferedOps();
427 throw new IOException("Connection timed out.");
428 }
429 continue;
430 }
431 catch (TFTPPacketException e)
432 {
433 endBufferedOps();
434 throw new IOException("Bad packet: " + e.getMessage());
435 }
436 } // end of while loop over tries to receive
437
438 // The first time we receive we get the port number and
439 // answering host address (for hosts with multiple IPs)
440 if (justStarted)
441 {
442 justStarted = false;
443 hostPort = received.getPort();
444 data.setPort(hostPort);
445 if(!host.equals(received.getAddress()))
446 {
447 host = received.getAddress();
448 data.setAddress(host);
449 sent.setAddress(host);
450 }
451 }
452
453 // Comply with RFC 783 indication that an error acknowledgement
454 // should be sent to originator if unexpected TID or host.
455 if (host.equals(received.getAddress()) &&
456 received.getPort() == hostPort)
457 {
458
459 switch (received.getType())
460 {
461 case TFTPPacket.ERROR:
462 error = (TFTPErrorPacket)received;
463 endBufferedOps();
464 throw new IOException("Error code " + error.getError() +
465 " received: " + error.getMessage());
466 case TFTPPacket.ACKNOWLEDGEMENT:
467 ack = (TFTPAckPacket)received;
468
469 lastBlock = ack.getBlockNumber();
470
471 if (lastBlock == block)
472 {
473 ++block;
474 if (block > 65535)
475 {
476 // wrap the block number
477 block = 0;
478 }
479 if (lastAckWait) {
480
481 break _sendPacket;
482 }
483 else {
484 break _receivePacket;
485 }
486 }
487 else
488 {
489 discardPackets();
490
491 if (lastBlock == (block == 0 ? 65535 : (block - 1)))
492 continue _sendPacket; // Resend last acknowledgement.
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 }