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