View Javadoc

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