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