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      /***
69       * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
70       * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
71       * and buffered operations disabled.
72       ***/
73      public TFTPClient()
74      {
75          __maxTimeouts = DEFAULT_MAX_TIMEOUTS;
76      }
77  
78      /***
79       * Sets the maximum number of times a receive attempt is allowed to
80       * timeout during a receiveFile() or sendFile() operation before ending
81       * attempts to retry the receive and failing.
82       * The default is DEFAULT_MAX_TIMEOUTS.
83       *
84       * @param numTimeouts  The maximum number of timeouts to allow.  Values
85       *        less than 1 should not be used, but if they are, they are
86       *        treated as 1.
87       ***/
88      public void setMaxTimeouts(int numTimeouts)
89      {
90          if (numTimeouts < 1) {
91              __maxTimeouts = 1;
92          } else {
93              __maxTimeouts = numTimeouts;
94          }
95      }
96  
97      /***
98       * Returns the maximum number of times a receive attempt is allowed to
99       * timeout before ending attempts to retry the receive and failing.
100      *
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      *
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      * @return number of bytes read
123      * @exception IOException If an I/O error occurs.  The nature of the
124      *            error will be reported in the message.
125      ***/
126     public int receiveFile(String filename, int mode, OutputStream output,
127                            InetAddress host, int port) throws IOException
128     {
129         int bytesRead, timeouts, lastBlock, block, hostPort, dataLength;
130         TFTPPacket sent, received = null;
131         TFTPErrorPacket error;
132         TFTPDataPacket data;
133         TFTPAckPacket ack = new TFTPAckPacket(host, port, 0);
134 
135         beginBufferedOps();
136 
137         dataLength = lastBlock = hostPort = bytesRead = 0;
138         block = 1;
139 
140         if (mode == TFTP.ASCII_MODE) {
141             output = new FromNetASCIIOutputStream(output);
142         }
143 
144         sent =
145             new TFTPReadRequestPacket(host, port, filename, mode);
146 
147 _sendPacket:
148         do
149         {
150             bufferedSend(sent);
151 
152 _receivePacket:
153             while (true)
154             {
155                 timeouts = 0;
156                 do {
157                     try
158                     {
159                         received = bufferedReceive();
160                         break;
161                     }
162                     catch (SocketException e)
163                     {
164                         if (++timeouts >= __maxTimeouts)
165                         {
166                             endBufferedOps();
167                             throw new IOException("Connection timed out.");
168                         }
169                         continue _sendPacket;
170                     }
171                     catch (InterruptedIOException e)
172                     {
173                         if (++timeouts >= __maxTimeouts)
174                         {
175                             endBufferedOps();
176                             throw new IOException("Connection timed out.");
177                         }
178                         continue _sendPacket;
179                     }
180                     catch (TFTPPacketException e)
181                     {
182                         endBufferedOps();
183                         throw new IOException("Bad packet: " + e.getMessage());
184                     }
185                 } while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once
186 
187                 // The first time we receive we get the port number and
188                 // answering host address (for hosts with multiple IPs)
189                 if (lastBlock == 0)
190                 {
191                     hostPort = received.getPort();
192                     ack.setPort(hostPort);
193                     if(!host.equals(received.getAddress()))
194                     {
195                         host = received.getAddress();
196                         ack.setAddress(host);
197                         sent.setAddress(host);
198                     }
199                 }
200 
201                 // Comply with RFC 783 indication that an error acknowledgment
202                 // should be sent to originator if unexpected TID or host.
203                 if (host.equals(received.getAddress()) &&
204                         received.getPort() == hostPort)
205                 {
206 
207                     switch (received.getType())
208                     {
209                     case TFTPPacket.ERROR:
210                         error = (TFTPErrorPacket)received;
211                         endBufferedOps();
212                         throw new IOException("Error code " + error.getError() +
213                                               " received: " + error.getMessage());
214                     case TFTPPacket.DATA:
215                         data = (TFTPDataPacket)received;
216                         dataLength = data.getDataLength();
217 
218                         lastBlock = data.getBlockNumber();
219 
220                         if (lastBlock == block)
221                         {
222                             try
223                             {
224                                 output.write(data.getData(), data.getDataOffset(),
225                                              dataLength);
226                             }
227                             catch (IOException e)
228                             {
229                                 error = new TFTPErrorPacket(host, hostPort,
230                                                             TFTPErrorPacket.OUT_OF_SPACE,
231                                                             "File write failed.");
232                                 bufferedSend(error);
233                                 endBufferedOps();
234                                 throw e;
235                             }
236                             ++block;
237                             if (block > 65535)
238                             {
239                                 // wrap the block number
240                                 block = 0;
241                             }
242 
243                             break _receivePacket;
244                         }
245                         else
246                         {
247                             discardPackets();
248 
249                             if (lastBlock == (block == 0 ? 65535 : (block - 1))) {
250                                 continue _sendPacket;  // Resend last acknowledgement.
251                             }
252 
253                             continue _receivePacket; // Start fetching packets again.
254                         }
255                         //break;
256 
257                     default:
258                         endBufferedOps();
259                         throw new IOException("Received unexpected packet type.");
260                     }
261                 }
262                 else
263                 {
264                     error = new TFTPErrorPacket(received.getAddress(),
265                                                 received.getPort(),
266                                                 TFTPErrorPacket.UNKNOWN_TID,
267                                                 "Unexpected host or port.");
268                     bufferedSend(error);
269                     continue _sendPacket;
270                 }
271 
272                 // We should never get here, but this is a safety to avoid
273                 // infinite loop.  If only Java had the goto statement.
274                 //break;
275             }
276 
277             ack.setBlockNumber(lastBlock);
278             sent = ack;
279             bytesRead += dataLength;
280         } // First data packet less than 512 bytes signals end of stream.
281 
282         while (dataLength == TFTPPacket.SEGMENT_SIZE);
283 
284         bufferedSend(sent);
285         endBufferedOps();
286 
287         return bytesRead;
288     }
289 
290 
291     /***
292      * Requests a named file from a remote host, writes the
293      * file to an OutputStream, closes the connection, and returns the number
294      * of bytes read.  A local UDP socket must first be created by
295      * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
296      * invoking this method.  This method will not close the OutputStream
297      * containing the file; you must close it after the method invocation.
298      *
299      * @param filename The name of the file to receive.
300      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
301      * @param output   The OutputStream to which the file should be written.
302      * @param hostname The name of the remote host serving the file.
303      * @param port     The port number of the remote TFTP server.
304      * @return number of bytes read
305      * @exception IOException If an I/O error occurs.  The nature of the
306      *            error will be reported in the message.
307      * @exception UnknownHostException  If the hostname cannot be resolved.
308      ***/
309     public int receiveFile(String filename, int mode, OutputStream output,
310                            String hostname, int port)
311     throws UnknownHostException, IOException
312     {
313         return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
314                            port);
315     }
316 
317 
318     /***
319      * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT).
320      *
321      * @param filename The name of the file to receive.
322      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
323      * @param output   The OutputStream to which the file should be written.
324      * @param host     The remote host serving the file.
325      * @return number of bytes read
326      * @exception IOException If an I/O error occurs.  The nature of the
327      *            error will be reported in the message.
328      ***/
329     public int receiveFile(String filename, int mode, OutputStream output,
330                            InetAddress host)
331     throws IOException
332     {
333         return receiveFile(filename, mode, output, host, DEFAULT_PORT);
334     }
335 
336     /***
337      * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT).
338      *
339      * @param filename The name of the file to receive.
340      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
341      * @param output   The OutputStream to which the file should be written.
342      * @param hostname The name of the remote host serving the file.
343      * @return number of bytes read
344      * @exception IOException If an I/O error occurs.  The nature of the
345      *            error will be reported in the message.
346      * @exception UnknownHostException  If the hostname cannot be resolved.
347      ***/
348     public int receiveFile(String filename, int mode, OutputStream output,
349                            String hostname)
350     throws UnknownHostException, IOException
351     {
352         return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
353                            DEFAULT_PORT);
354     }
355 
356 
357     /***
358      * Requests to send a file to a remote host, reads the file from an
359      * InputStream, sends the file to the remote host, and closes the
360      * connection.  A local UDP socket must first be created by
361      * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
362      * invoking this method.  This method will not close the InputStream
363      * containing the file; you must close it after the method invocation.
364      *
365      * @param filename The name the remote server should use when creating
366      *        the file on its file system.
367      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
368      * @param input the input stream containing the data to be sent
369      * @param host     The remote host receiving the file.
370      * @param port     The port number of the remote TFTP server.
371      * @exception IOException If an I/O error occurs.  The nature of the
372      *            error will be reported in the message.
373      ***/
374     public void sendFile(String filename, int mode, InputStream input,
375                          InetAddress host, int port) throws IOException
376     {
377         int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset, totalThisPacket;
378         TFTPPacket sent, received = null;
379         TFTPErrorPacket error;
380         TFTPDataPacket data =
381             new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0);
382         TFTPAckPacket ack;
383 
384         boolean justStarted = true;
385 
386         beginBufferedOps();
387 
388         dataLength = lastBlock = hostPort = bytesRead = totalThisPacket = 0;
389         block = 0;
390         boolean lastAckWait = false;
391 
392         if (mode == TFTP.ASCII_MODE) {
393             input = new ToNetASCIIInputStream(input);
394         }
395 
396         sent =
397             new TFTPWriteRequestPacket(host, port, filename, mode);
398 
399 _sendPacket:
400         do
401         {
402             // first time: block is 0, lastBlock is 0, send a request packet.
403             // subsequent: block is integer starting at 1, send data packet.
404             bufferedSend(sent);
405 
406             // this is trying to receive an ACK
407 _receivePacket:
408             while (true)
409             {
410 
411 
412                 timeouts = 0;
413                 do {
414                     try
415                     {
416                         received = bufferedReceive();
417                         break;
418                     }
419                     catch (SocketException e)
420                     {
421                         if (++timeouts >= __maxTimeouts)
422                         {
423                             endBufferedOps();
424                             throw new IOException("Connection timed out.");
425                         }
426                         continue _sendPacket;
427                     }
428                     catch (InterruptedIOException e)
429                     {
430                         if (++timeouts >= __maxTimeouts)
431                         {
432                             endBufferedOps();
433                             throw new IOException("Connection timed out.");
434                         }
435                         continue _sendPacket;
436                     }
437                     catch (TFTPPacketException e)
438                     {
439                         endBufferedOps();
440                         throw new IOException("Bad packet: " + e.getMessage());
441                     }
442                 } // end of while loop over tries to receive
443                 while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once
444 
445 
446                 // The first time we receive we get the port number and
447                 // answering host address (for hosts with multiple IPs)
448                 if (justStarted)
449                 {
450                     justStarted = false;
451                     hostPort = received.getPort();
452                     data.setPort(hostPort);
453                     if(!host.equals(received.getAddress()))
454                     {
455                         host = received.getAddress();
456                         data.setAddress(host);
457                         sent.setAddress(host);
458                     }
459                 }
460 
461                 // Comply with RFC 783 indication that an error acknowledgment
462                 // should be sent to originator if unexpected TID or host.
463                 if (host.equals(received.getAddress()) &&
464                         received.getPort() == hostPort)
465                 {
466 
467                     switch (received.getType())
468                     {
469                     case TFTPPacket.ERROR:
470                         error = (TFTPErrorPacket)received;
471                         endBufferedOps();
472                         throw new IOException("Error code " + error.getError() +
473                                               " received: " + error.getMessage());
474                     case TFTPPacket.ACKNOWLEDGEMENT:
475                         ack = (TFTPAckPacket)received;
476 
477                         lastBlock = ack.getBlockNumber();
478 
479                         if (lastBlock == block)
480                         {
481                             ++block;
482                             if (block > 65535)
483                             {
484                                 // wrap the block number
485                                 block = 0;
486                             }
487                             if (lastAckWait) {
488 
489                               break _sendPacket;
490                             }
491                             else {
492                               break _receivePacket;
493                             }
494                         }
495                         else
496                         {
497                             discardPackets();
498 
499                             continue _receivePacket; // Start fetching packets again.
500                         }
501                         //break;
502 
503                     default:
504                         endBufferedOps();
505                         throw new IOException("Received unexpected packet type.");
506                     }
507                 }
508                 else
509                 {
510                     error = new TFTPErrorPacket(received.getAddress(),
511                                                 received.getPort(),
512                                                 TFTPErrorPacket.UNKNOWN_TID,
513                                                 "Unexpected host or port.");
514                     bufferedSend(error);
515                     continue _sendPacket;
516                 }
517 
518                 // We should never get here, but this is a safety to avoid
519                 // infinite loop.  If only Java had the goto statement.
520                 //break;
521             }
522 
523             // OK, we have just gotten ACK about the last data we sent. Make another
524             // and send it
525 
526             dataLength = TFTPPacket.SEGMENT_SIZE;
527             offset = 4;
528             totalThisPacket = 0;
529             while (dataLength > 0 &&
530                     (bytesRead = input.read(_sendBuffer, offset, dataLength)) > 0)
531             {
532                 offset += bytesRead;
533                 dataLength -= bytesRead;
534                 totalThisPacket += bytesRead;
535             }
536 
537             if( totalThisPacket < TFTPPacket.SEGMENT_SIZE ) {
538                 /* this will be our last packet -- send, wait for ack, stop */
539                 lastAckWait = true;
540             }
541             data.setBlockNumber(block);
542             data.setData(_sendBuffer, 4, totalThisPacket);
543             sent = data;
544         }
545         while ( totalThisPacket > 0 || lastAckWait );
546         // Note: this was looping while dataLength == 0 || lastAckWait,
547         // which was discarding the last packet if it was not full size
548         // Should send the packet.
549 
550         endBufferedOps();
551     }
552 
553 
554     /***
555      * Requests to send a file to a remote host, reads the file from an
556      * InputStream, sends the file to the remote host, and closes the
557      * connection.  A local UDP socket must first be created by
558      * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
559      * invoking this method.  This method will not close the InputStream
560      * containing the file; you must close it after the method invocation.
561      *
562      * @param filename The name the remote server should use when creating
563      *        the file on its file system.
564      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
565      * @param input the input stream containing the data to be sent
566      * @param hostname The name of the remote host receiving the file.
567      * @param port     The port number of the remote TFTP server.
568      * @exception IOException If an I/O error occurs.  The nature of the
569      *            error will be reported in the message.
570      * @exception UnknownHostException  If the hostname cannot be resolved.
571      ***/
572     public void sendFile(String filename, int mode, InputStream input,
573                          String hostname, int port)
574     throws UnknownHostException, IOException
575     {
576         sendFile(filename, mode, input, InetAddress.getByName(hostname), port);
577     }
578 
579 
580     /***
581      * Same as calling sendFile(filename, mode, input, host, TFTP.DEFAULT_PORT).
582      *
583      * @param filename The name the remote server should use when creating
584      *        the file on its file system.
585      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
586      * @param input the input stream containing the data to be sent
587      * @param host     The name of the remote host receiving the file.
588      * @exception IOException If an I/O error occurs.  The nature of the
589      *            error will be reported in the message.
590      * @exception UnknownHostException  If the hostname cannot be resolved.
591      ***/
592     public void sendFile(String filename, int mode, InputStream input,
593                          InetAddress host)
594     throws IOException
595     {
596         sendFile(filename, mode, input, host, DEFAULT_PORT);
597     }
598 
599     /***
600      * Same as calling sendFile(filename, mode, input, hostname, TFTP.DEFAULT_PORT).
601      *
602      * @param filename The name the remote server should use when creating
603      *        the file on its file system.
604      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
605      * @param input the input stream containing the data to be sent
606      * @param hostname The name of the remote host receiving the file.
607      * @exception IOException If an I/O error occurs.  The nature of the
608      *            error will be reported in the message.
609      * @exception UnknownHostException  If the hostname cannot be resolved.
610      ***/
611     public void sendFile(String filename, int mode, InputStream input,
612                          String hostname)
613     throws UnknownHostException, IOException
614     {
615         sendFile(filename, mode, input, InetAddress.getByName(hostname),
616                  DEFAULT_PORT);
617     }
618 }