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