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 }